mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-02-09 22:35:54 -05:00
This pull request introduces a comprehensive backend testing guide and adds new tests for analytics logging and various API endpoints, focusing on snapshot testing. It also includes corresponding snapshot files for these tests. Below are the most significant changes: ### Documentation Updates: * Added a detailed `TESTING.md` file to the backend, providing a guide for running tests, snapshot testing, writing API route tests, and best practices. It includes examples for mocking, fixtures, and CI/CD integration. ### Analytics Logging Tests: * Implemented tests for logging raw metrics and analytics in `analytics_test.py`, covering success scenarios, various input values, invalid requests, and complex nested data. These tests utilize snapshot testing for response validation. * Added snapshot files for analytics logging tests, including responses for success cases, various metric values, and complex analytics data. [[1]](diffhunk://#diff-654bc5aa1951008ec5c110a702279ef58709ee455ba049b9fa825fa60f7e3869R1-R3) [[2]](diffhunk://#diff-e0a434b107abc71aeffb7d7989dbfd8f466b5e53f8dea25a87937ec1b885b122R1-R3) [[3]](diffhunk://#diff-dd0bc0b72264de1a0c0d3bd0c54ad656061317f425e4de461018ca51a19171a0R1-R3) [[4]](diffhunk://#diff-63af007073db553d04988544af46930458a768544cabd08412265e0818320d11R1-R30) ### Snapshot Files for API Endpoints: * Added snapshot files for various API endpoint tests, such as: - Graph-related operations (`graphs_get_single_response`, `graphs_get_all_response`, `blocks_get_all_response`). [[1]](diffhunk://#diff-b25dba271606530cfa428c00073d7e016184a7bb22166148ab1726b3e113dda8R1-R29) [[2]](diffhunk://#diff-1054e58ec3094715660f55bfba1676d65b6833a81a91a08e90ad57922444d056R1-R31) [[3]](diffhunk://#diff-cfd403ab6f3efc89188acaf993d85e6f792108d1740c7e7149eb05efb73d918dR1-R14) - User-related operations (`auth_get_or_create_user_response`, `auth_update_email_response`). [[1]](diffhunk://#diff-49e65ab1eb6af4d0163a6c54ed10be621ce7336b2ab5d47d47679bfaefdb7059R1-R5) [[2]](diffhunk://#diff-ac1216f96878bd4356454c317473654d5d5c7c180125663b80b0b45aa5ab52cbR1-R3) - Credit-related operations (`credits_get_balance_response`, `credits_get_auto_top_up_response`, `credits_top_up_request_response`). [[1]](diffhunk://#diff-189488f8da5be74d80ac3fb7f84f1039a408573184293e9ba2e321d535c57cddR1-R3) [[2]](diffhunk://#diff-ba3c4a6853793cbed24030cdccedf966d71913451ef8eb4b2c4f426ef18ed87aR1-R4) [[3]](diffhunk://#diff-43d7daa0c82070a9b6aee88a774add8e87533e630bbccbac5a838b7a7ae56a75R1-R3) - Graph execution and deletion (`blocks_execute_response`, `graphs_delete_response`). [[1]](diffhunk://#diff-a2ade7d646ad85a2801e7ff39799a925a612548a1cdd0ed99b44dd870d1465b5R1-R12) [[2]](diffhunk://#diff-c0d1cd0a8499ee175ce3007c3a87ba5f3235ce02d38ce837560b36a44fdc4a22R1-R3)## Summary - add pytest-snapshot to backend dev requirements - snapshot server route response JSONs - mention how to update stored snapshots ## Testing - `poetry run format` - `poetry run test` ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: <!-- Put your test plan here: --> - [x] run poetry run test --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>
333 lines
10 KiB
Python
333 lines
10 KiB
Python
import json
|
|
from typing import Any
|
|
from uuid import UUID
|
|
|
|
import autogpt_libs.auth.models
|
|
import fastapi.exceptions
|
|
import pytest
|
|
from pytest_snapshot.plugin import Snapshot
|
|
|
|
import backend.server.v2.store.model as store
|
|
from backend.blocks.basic import StoreValueBlock
|
|
from backend.blocks.io import AgentInputBlock, AgentOutputBlock
|
|
from backend.data.block import BlockSchema
|
|
from backend.data.graph import Graph, Link, Node
|
|
from backend.data.model import SchemaField
|
|
from backend.data.user import DEFAULT_USER_ID
|
|
from backend.server.model import CreateGraph
|
|
from backend.usecases.sample import create_test_user
|
|
from backend.util.test import SpinTestServer
|
|
|
|
|
|
@pytest.mark.asyncio(loop_scope="session")
|
|
async def test_graph_creation(server: SpinTestServer, snapshot: Snapshot):
|
|
"""
|
|
Test the creation of a graph with nodes and links.
|
|
|
|
This test ensures that:
|
|
1. A graph can be successfully created with valid connections.
|
|
2. The created graph has the correct structure and properties.
|
|
|
|
Args:
|
|
server (SpinTestServer): The test server instance.
|
|
"""
|
|
value_block = StoreValueBlock().id
|
|
input_block = AgentInputBlock().id
|
|
|
|
graph = Graph(
|
|
id="test_graph",
|
|
name="TestGraph",
|
|
description="Test graph",
|
|
nodes=[
|
|
Node(id="node_1", block_id=value_block),
|
|
Node(id="node_2", block_id=input_block, input_default={"name": "input"}),
|
|
Node(id="node_3", block_id=value_block),
|
|
],
|
|
links=[
|
|
Link(
|
|
source_id="node_1",
|
|
sink_id="node_2",
|
|
source_name="output",
|
|
sink_name="name",
|
|
),
|
|
],
|
|
)
|
|
create_graph = CreateGraph(graph=graph)
|
|
created_graph = await server.agent_server.test_create_graph(
|
|
create_graph, DEFAULT_USER_ID
|
|
)
|
|
|
|
assert UUID(created_graph.id)
|
|
assert created_graph.name == "TestGraph"
|
|
|
|
assert len(created_graph.nodes) == 3
|
|
assert UUID(created_graph.nodes[0].id)
|
|
assert UUID(created_graph.nodes[1].id)
|
|
assert UUID(created_graph.nodes[2].id)
|
|
|
|
nodes = created_graph.nodes
|
|
links = created_graph.links
|
|
assert len(links) == 1
|
|
assert links[0].source_id != links[0].sink_id
|
|
assert links[0].source_id in {nodes[0].id, nodes[1].id, nodes[2].id}
|
|
assert links[0].sink_id in {nodes[0].id, nodes[1].id, nodes[2].id}
|
|
|
|
# Create a serializable version of the graph for snapshot testing
|
|
# Remove dynamic IDs to make snapshots reproducible
|
|
graph_data = {
|
|
"name": created_graph.name,
|
|
"description": created_graph.description,
|
|
"nodes_count": len(created_graph.nodes),
|
|
"links_count": len(created_graph.links),
|
|
"node_blocks": [node.block_id for node in created_graph.nodes],
|
|
"link_structure": [
|
|
{"source_name": link.source_name, "sink_name": link.sink_name}
|
|
for link in created_graph.links
|
|
],
|
|
}
|
|
snapshot.snapshot_dir = "snapshots"
|
|
snapshot.assert_match(
|
|
json.dumps(graph_data, indent=2, sort_keys=True), "grph_struct"
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio(loop_scope="session")
|
|
async def test_get_input_schema(server: SpinTestServer, snapshot: Snapshot):
|
|
"""
|
|
Test the get_input_schema method of a created graph.
|
|
|
|
This test ensures that:
|
|
1. A graph can be created with a single node.
|
|
2. The input schema of the created graph is correctly generated.
|
|
3. The input schema contains the expected input name and node id.
|
|
|
|
Args:
|
|
server (SpinTestServer): The test server instance.
|
|
"""
|
|
value_block = StoreValueBlock().id
|
|
input_block = AgentInputBlock().id
|
|
output_block = AgentOutputBlock().id
|
|
|
|
graph = Graph(
|
|
name="TestInputSchema",
|
|
description="Test input schema",
|
|
nodes=[
|
|
Node(
|
|
id="node_0_a",
|
|
block_id=input_block,
|
|
input_default={
|
|
"name": "in_key_a",
|
|
"title": "Key A",
|
|
"value": "A",
|
|
"advanced": True,
|
|
},
|
|
metadata={"id": "node_0_a"},
|
|
),
|
|
Node(
|
|
id="node_0_b",
|
|
block_id=input_block,
|
|
input_default={"name": "in_key_b", "advanced": True},
|
|
metadata={"id": "node_0_b"},
|
|
),
|
|
Node(id="node_1", block_id=value_block, metadata={"id": "node_1"}),
|
|
Node(
|
|
id="node_2",
|
|
block_id=output_block,
|
|
input_default={
|
|
"name": "out_key",
|
|
"description": "This is an output key",
|
|
},
|
|
metadata={"id": "node_2"},
|
|
),
|
|
],
|
|
links=[
|
|
Link(
|
|
source_id="node_0_a",
|
|
sink_id="node_1",
|
|
source_name="result",
|
|
sink_name="input",
|
|
),
|
|
Link(
|
|
source_id="node_0_b",
|
|
sink_id="node_1",
|
|
source_name="result",
|
|
sink_name="input",
|
|
),
|
|
Link(
|
|
source_id="node_1",
|
|
sink_id="node_2",
|
|
source_name="output",
|
|
sink_name="value",
|
|
),
|
|
],
|
|
)
|
|
|
|
create_graph = CreateGraph(graph=graph)
|
|
created_graph = await server.agent_server.test_create_graph(
|
|
create_graph, DEFAULT_USER_ID
|
|
)
|
|
|
|
class ExpectedInputSchema(BlockSchema):
|
|
in_key_a: Any = SchemaField(title="Key A", default="A", advanced=True)
|
|
in_key_b: Any = SchemaField(title="in_key_b", advanced=False)
|
|
|
|
class ExpectedOutputSchema(BlockSchema):
|
|
out_key: Any = SchemaField(
|
|
description="This is an output key",
|
|
title="out_key",
|
|
advanced=False,
|
|
)
|
|
|
|
input_schema = created_graph.input_schema
|
|
input_schema["title"] = "ExpectedInputSchema"
|
|
assert input_schema == ExpectedInputSchema.jsonschema()
|
|
|
|
# Add snapshot testing for the schemas
|
|
snapshot.snapshot_dir = "snapshots"
|
|
snapshot.assert_match(
|
|
json.dumps(input_schema, indent=2, sort_keys=True), "grph_in_schm"
|
|
)
|
|
|
|
output_schema = created_graph.output_schema
|
|
output_schema["title"] = "ExpectedOutputSchema"
|
|
assert output_schema == ExpectedOutputSchema.jsonschema()
|
|
|
|
# Add snapshot testing for the output schema
|
|
snapshot.snapshot_dir = "snapshots"
|
|
snapshot.assert_match(
|
|
json.dumps(output_schema, indent=2, sort_keys=True), "grph_out_schm"
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio(loop_scope="session")
|
|
async def test_clean_graph(server: SpinTestServer):
|
|
"""
|
|
Test the clean_graph function that:
|
|
1. Clears input block values
|
|
2. Removes credentials from nodes
|
|
"""
|
|
# Create a graph with input blocks and credentials
|
|
graph = Graph(
|
|
id="test_clean_graph",
|
|
name="Test Clean Graph",
|
|
description="Test graph cleaning",
|
|
nodes=[
|
|
Node(
|
|
id="input_node",
|
|
block_id=AgentInputBlock().id,
|
|
input_default={
|
|
"name": "test_input",
|
|
"value": "test value",
|
|
"description": "Test input description",
|
|
},
|
|
),
|
|
],
|
|
links=[],
|
|
)
|
|
|
|
# Create graph and get model
|
|
create_graph = CreateGraph(graph=graph)
|
|
created_graph = await server.agent_server.test_create_graph(
|
|
create_graph, DEFAULT_USER_ID
|
|
)
|
|
|
|
# Clean the graph
|
|
created_graph = await server.agent_server.test_get_graph(
|
|
created_graph.id, created_graph.version, DEFAULT_USER_ID, for_export=True
|
|
)
|
|
|
|
# # Verify input block value is cleared
|
|
input_node = next(
|
|
n for n in created_graph.nodes if n.block_id == AgentInputBlock().id
|
|
)
|
|
assert input_node.input_default["value"] == ""
|
|
|
|
|
|
@pytest.mark.asyncio(loop_scope="session")
|
|
async def test_access_store_listing_graph(server: SpinTestServer):
|
|
"""
|
|
Test the access of a store listing graph.
|
|
"""
|
|
graph = Graph(
|
|
id="test_clean_graph",
|
|
name="Test Clean Graph",
|
|
description="Test graph cleaning",
|
|
nodes=[
|
|
Node(
|
|
id="input_node",
|
|
block_id=AgentInputBlock().id,
|
|
input_default={
|
|
"name": "test_input",
|
|
"value": "test value",
|
|
"description": "Test input description",
|
|
},
|
|
),
|
|
],
|
|
links=[],
|
|
)
|
|
|
|
# Create graph and get model
|
|
create_graph = CreateGraph(graph=graph)
|
|
created_graph = await server.agent_server.test_create_graph(
|
|
create_graph, DEFAULT_USER_ID
|
|
)
|
|
|
|
store_submission_request = store.StoreSubmissionRequest(
|
|
agent_id=created_graph.id,
|
|
agent_version=created_graph.version,
|
|
slug=created_graph.id,
|
|
name="Test name",
|
|
sub_heading="Test sub heading",
|
|
video_url=None,
|
|
image_urls=[],
|
|
description="Test description",
|
|
categories=[],
|
|
)
|
|
|
|
# First we check the graph an not be accessed by a different user
|
|
with pytest.raises(fastapi.exceptions.HTTPException) as exc_info:
|
|
await server.agent_server.test_get_graph(
|
|
created_graph.id,
|
|
created_graph.version,
|
|
"3e53486c-cf57-477e-ba2a-cb02dc828e1b",
|
|
)
|
|
assert exc_info.value.status_code == 404
|
|
assert "Graph" in str(exc_info.value.detail)
|
|
|
|
# Now we create a store listing
|
|
store_listing = await server.agent_server.test_create_store_listing(
|
|
store_submission_request, DEFAULT_USER_ID
|
|
)
|
|
|
|
if isinstance(store_listing, fastapi.responses.JSONResponse):
|
|
assert False, "Failed to create store listing"
|
|
|
|
slv_id = (
|
|
store_listing.store_listing_version_id
|
|
if store_listing.store_listing_version_id is not None
|
|
else None
|
|
)
|
|
|
|
assert slv_id is not None
|
|
|
|
admin_user = await create_test_user(alt_user=True)
|
|
await server.agent_server.test_review_store_listing(
|
|
store.ReviewSubmissionRequest(
|
|
store_listing_version_id=slv_id,
|
|
is_approved=True,
|
|
comments="Test comments",
|
|
),
|
|
autogpt_libs.auth.models.User(
|
|
user_id=admin_user.id,
|
|
role="admin",
|
|
email=admin_user.email,
|
|
phone_number="1234567890",
|
|
),
|
|
)
|
|
|
|
# Now we check the graph can be accessed by a user that does not own the graph
|
|
got_graph = await server.agent_server.test_get_graph(
|
|
created_graph.id, created_graph.version, "3e53486c-cf57-477e-ba2a-cb02dc828e1b"
|
|
)
|
|
assert got_graph is not None
|