NeerajCodz commited on
Commit
46eecf4
·
1 Parent(s): 27cde0c

test: add comprehensive backend test suite

Browse files
Files changed (28) hide show
  1. backend/tests/__init__.py +1 -0
  2. backend/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  3. backend/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  4. backend/tests/conftest.py +58 -0
  5. backend/tests/test_agents/__init__.py +1 -0
  6. backend/tests/test_agents/__pycache__/__init__.cpython-314.pyc +0 -0
  7. backend/tests/test_agents/__pycache__/test_coordinator.cpython-314-pytest-9.0.2.pyc +0 -0
  8. backend/tests/test_agents/test_coordinator.py +36 -0
  9. backend/tests/test_api/__init__.py +1 -0
  10. backend/tests/test_api/__pycache__/__init__.cpython-314.pyc +0 -0
  11. backend/tests/test_api/__pycache__/test_agents.cpython-314-pytest-9.0.2.pyc +0 -0
  12. backend/tests/test_api/__pycache__/test_episode.cpython-314-pytest-9.0.2.pyc +0 -0
  13. backend/tests/test_api/__pycache__/test_health.cpython-314-pytest-9.0.2.pyc +0 -0
  14. backend/tests/test_api/__pycache__/test_tools.cpython-314-pytest-9.0.2.pyc +0 -0
  15. backend/tests/test_api/test_agents.py +24 -0
  16. backend/tests/test_api/test_episode.py +48 -0
  17. backend/tests/test_api/test_health.py +20 -0
  18. backend/tests/test_api/test_tools.py +24 -0
  19. backend/tests/test_core/__init__.py +1 -0
  20. backend/tests/test_core/__pycache__/__init__.cpython-314.pyc +0 -0
  21. backend/tests/test_core/__pycache__/test_action.cpython-314-pytest-9.0.2.pyc +0 -0
  22. backend/tests/test_core/__pycache__/test_env.cpython-314-pytest-9.0.2.pyc +0 -0
  23. backend/tests/test_core/__pycache__/test_observation.cpython-314-pytest-9.0.2.pyc +0 -0
  24. backend/tests/test_core/__pycache__/test_reward.cpython-314-pytest-9.0.2.pyc +0 -0
  25. backend/tests/test_core/test_action.py +77 -0
  26. backend/tests/test_core/test_env.py +20 -0
  27. backend/tests/test_core/test_observation.py +72 -0
  28. backend/tests/test_core/test_reward.py +22 -0
backend/tests/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Tests for API endpoints."""
backend/tests/__pycache__/__init__.cpython-314.pyc ADDED
Binary file (189 Bytes). View file
 
backend/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc ADDED
Binary file (3.61 kB). View file
 
backend/tests/conftest.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Pytest configuration and fixtures."""
2
+
3
+ import asyncio
4
+ from typing import AsyncGenerator, Generator
5
+
6
+ import pytest
7
+ from fastapi.testclient import TestClient
8
+ from httpx import AsyncClient, ASGITransport
9
+
10
+ from app.main import app, create_app
11
+
12
+
13
+ @pytest.fixture(scope="session")
14
+ def event_loop() -> Generator[asyncio.AbstractEventLoop, None, None]:
15
+ """Create event loop for async tests."""
16
+ loop = asyncio.new_event_loop()
17
+ yield loop
18
+ loop.close()
19
+
20
+
21
+ @pytest.fixture
22
+ def client() -> Generator[TestClient, None, None]:
23
+ """Create sync test client."""
24
+ with TestClient(app) as c:
25
+ yield c
26
+
27
+
28
+ @pytest.fixture
29
+ async def async_client() -> AsyncGenerator[AsyncClient, None]:
30
+ """Create async test client."""
31
+ transport = ASGITransport(app=app)
32
+ async with AsyncClient(transport=transport, base_url="http://test") as c:
33
+ yield c
34
+
35
+
36
+ @pytest.fixture
37
+ def sample_task() -> dict:
38
+ """Sample task data for testing."""
39
+ return {
40
+ "task_id": "test_task_001",
41
+ "task_name": "Extract Product Info",
42
+ "task_type": "extraction",
43
+ "target_fields": ["name", "price", "description"],
44
+ "target_url": "https://example.com/product/123",
45
+ }
46
+
47
+
48
+ @pytest.fixture
49
+ def sample_action() -> dict:
50
+ """Sample action data for testing."""
51
+ return {
52
+ "action_type": "extract_field",
53
+ "parameters": {
54
+ "field_name": "price",
55
+ "selector": ".product-price",
56
+ },
57
+ "confidence": 0.95,
58
+ }
backend/tests/test_agents/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Test agents package."""
backend/tests/test_agents/__pycache__/__init__.cpython-314.pyc ADDED
Binary file (197 Bytes). View file
 
backend/tests/test_agents/__pycache__/test_coordinator.cpython-314-pytest-9.0.2.pyc ADDED
Binary file (6.29 kB). View file
 
backend/tests/test_agents/test_coordinator.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tests for agent coordinator."""
2
+
3
+ import pytest
4
+ from app.agents.coordinator import AgentCoordinator
5
+ from app.agents.planner import PlannerAgent
6
+ from app.agents.navigator import NavigatorAgent
7
+ from app.agents.extractor import ExtractorAgent
8
+ from app.agents.verifier import VerifierAgent
9
+
10
+
11
+ def test_coordinator_creation() -> None:
12
+ """Test creating agent coordinator."""
13
+ coordinator = AgentCoordinator()
14
+ assert coordinator is not None
15
+
16
+
17
+ def test_agent_registration() -> None:
18
+ """Test registering agents with coordinator."""
19
+ coordinator = AgentCoordinator()
20
+ planner = PlannerAgent("planner_1")
21
+
22
+ coordinator.register_agent("custom_planner", planner)
23
+ assert coordinator.get_agent("custom_planner") is planner
24
+
25
+
26
+ def test_all_agents_instantiation() -> None:
27
+ """Test all agent types can be instantiated."""
28
+ planner = PlannerAgent("planner")
29
+ navigator = NavigatorAgent("navigator")
30
+ extractor = ExtractorAgent("extractor")
31
+ verifier = VerifierAgent("verifier")
32
+
33
+ assert planner.agent_id == "planner"
34
+ assert navigator.agent_id == "navigator"
35
+ assert extractor.agent_id == "extractor"
36
+ assert verifier.agent_id == "verifier"
backend/tests/test_api/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Test API package."""
backend/tests/test_api/__pycache__/__init__.cpython-314.pyc ADDED
Binary file (191 Bytes). View file
 
backend/tests/test_api/__pycache__/test_agents.cpython-314-pytest-9.0.2.pyc ADDED
Binary file (3.62 kB). View file
 
backend/tests/test_api/__pycache__/test_episode.cpython-314-pytest-9.0.2.pyc ADDED
Binary file (7.86 kB). View file
 
backend/tests/test_api/__pycache__/test_health.cpython-314-pytest-9.0.2.pyc ADDED
Binary file (3.84 kB). View file
 
backend/tests/test_api/__pycache__/test_tools.cpython-314-pytest-9.0.2.pyc ADDED
Binary file (3.59 kB). View file
 
backend/tests/test_api/test_agents.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tests for agent endpoints."""
2
+
3
+ import pytest
4
+ from fastapi.testclient import TestClient
5
+
6
+
7
+ def test_list_agents(client: TestClient) -> None:
8
+ """Test listing available agents."""
9
+ response = client.get("/api/agents/types/")
10
+ assert response.status_code == 200
11
+ data = response.json()
12
+ assert "agents" in data
13
+
14
+
15
+ def test_run_agent(client: TestClient, sample_task: dict) -> None:
16
+ """Test running an agent."""
17
+ run_request = {
18
+ "agent_type": "planner",
19
+ "episode_id": "test-episode-123",
20
+ "task_context": sample_task,
21
+ }
22
+ response = client.post("/api/agents/run", json=run_request)
23
+ # 200 for success, 500 for coordinator errors (expected without full setup)
24
+ assert response.status_code in [200, 500]
backend/tests/test_api/test_episode.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tests for episode endpoints."""
2
+
3
+ import pytest
4
+ from fastapi.testclient import TestClient
5
+
6
+
7
+ def test_reset_episode(client: TestClient, sample_task: dict) -> None:
8
+ """Test resetting an episode."""
9
+ reset_request = {"task_id": sample_task["task_id"]}
10
+ response = client.post("/api/episode/reset", json=reset_request)
11
+ assert response.status_code == 201
12
+ data = response.json()
13
+ assert "episode_id" in data
14
+ assert "observation" in data
15
+
16
+
17
+ def test_step_episode(client: TestClient, sample_task: dict, sample_action: dict) -> None:
18
+ """Test stepping through an episode."""
19
+ # First reset
20
+ reset_request = {"task_id": sample_task["task_id"]}
21
+ reset_response = client.post("/api/episode/reset", json=reset_request)
22
+ assert reset_response.status_code == 201
23
+ episode_id = reset_response.json()["episode_id"]
24
+
25
+ # Then step
26
+ step_data = {
27
+ "episode_id": episode_id,
28
+ "action": sample_action,
29
+ }
30
+ response = client.post("/api/episode/step", json=step_data)
31
+ assert response.status_code == 200
32
+ data = response.json()
33
+ assert "observation" in data
34
+ assert "reward" in data
35
+
36
+
37
+ def test_get_state(client: TestClient, sample_task: dict) -> None:
38
+ """Test getting episode state."""
39
+ # First reset
40
+ reset_request = {"task_id": sample_task["task_id"]}
41
+ reset_response = client.post("/api/episode/reset", json=reset_request)
42
+ episode_id = reset_response.json()["episode_id"]
43
+
44
+ # Get state
45
+ response = client.get(f"/api/episode/state/{episode_id}")
46
+ assert response.status_code == 200
47
+ data = response.json()
48
+ assert data["episode_id"] == episode_id
backend/tests/test_api/test_health.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tests for health check endpoints."""
2
+
3
+ import pytest
4
+ from fastapi.testclient import TestClient
5
+
6
+
7
+ def test_health_endpoint(client: TestClient) -> None:
8
+ """Test health check returns OK."""
9
+ response = client.get("/api/health")
10
+ assert response.status_code == 200
11
+ data = response.json()
12
+ assert data["status"] == "healthy"
13
+
14
+
15
+ def test_ready_endpoint(client: TestClient) -> None:
16
+ """Test readiness check."""
17
+ response = client.get("/api/ready")
18
+ assert response.status_code == 200
19
+ data = response.json()
20
+ assert "ready" in data
backend/tests/test_api/test_tools.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tests for tool endpoints."""
2
+
3
+ import pytest
4
+ from fastapi.testclient import TestClient
5
+
6
+
7
+ def test_list_tools(client: TestClient) -> None:
8
+ """Test listing available tools."""
9
+ response = client.get("/api/tools/registry")
10
+ assert response.status_code == 200
11
+ data = response.json()
12
+ assert "tools" in data
13
+
14
+
15
+ def test_tool_test_endpoint(client: TestClient) -> None:
16
+ """Test tool testing endpoint."""
17
+ test_request = {
18
+ "tool_name": "navigate_to",
19
+ "parameters": {"url": "https://example.com"},
20
+ "dry_run": True,
21
+ }
22
+ response = client.post("/api/tools/test", json=test_request)
23
+ # Either success or tool not found is acceptable
24
+ assert response.status_code in [200, 404]
backend/tests/test_core/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Test core package."""
backend/tests/test_core/__pycache__/__init__.cpython-314.pyc ADDED
Binary file (193 Bytes). View file
 
backend/tests/test_core/__pycache__/test_action.cpython-314-pytest-9.0.2.pyc ADDED
Binary file (12.7 kB). View file
 
backend/tests/test_core/__pycache__/test_env.cpython-314-pytest-9.0.2.pyc ADDED
Binary file (4.03 kB). View file
 
backend/tests/test_core/__pycache__/test_observation.cpython-314-pytest-9.0.2.pyc ADDED
Binary file (10.5 kB). View file
 
backend/tests/test_core/__pycache__/test_reward.cpython-314-pytest-9.0.2.pyc ADDED
Binary file (3.29 kB). View file
 
backend/tests/test_core/test_action.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tests for action model."""
2
+
3
+ import pytest
4
+ from app.core.action import Action, ActionType
5
+
6
+
7
+ def test_action_creation() -> None:
8
+ """Test creating an action."""
9
+ action = Action(
10
+ action_type=ActionType.EXTRACT_FIELD,
11
+ parameters={"field_name": "price", "selector": ".price"},
12
+ confidence=0.9,
13
+ )
14
+ assert action.action_type == ActionType.EXTRACT_FIELD
15
+ assert action.confidence == 0.9
16
+
17
+
18
+ def test_action_factory_methods() -> None:
19
+ """Test action factory methods."""
20
+ nav_action = Action.navigate("https://example.com")
21
+ assert nav_action.action_type == ActionType.NAVIGATE
22
+ assert nav_action.parameters["url"] == "https://example.com"
23
+
24
+ extract_action = Action.extract_field("price", ".product-price")
25
+ assert extract_action.action_type == ActionType.EXTRACT_FIELD
26
+ assert extract_action.parameters["field_name"] == "price"
27
+
28
+ done_action = Action.done(success=True, message="Task completed")
29
+ assert done_action.action_type == ActionType.DONE
30
+ assert done_action.parameters["success"] is True
31
+
32
+
33
+ def test_action_validation() -> None:
34
+ """Test action parameter validation."""
35
+ # Valid action
36
+ action = Action(
37
+ action_type=ActionType.NAVIGATE,
38
+ parameters={"url": "https://example.com"},
39
+ )
40
+ errors = action.validate_params()
41
+ assert len(errors) == 0
42
+
43
+ # Invalid action - missing required param
44
+ invalid_action = Action(
45
+ action_type=ActionType.NAVIGATE,
46
+ parameters={},
47
+ )
48
+ errors = invalid_action.validate_params()
49
+ assert len(errors) > 0
50
+ assert "url" in errors[0]
51
+
52
+
53
+ def test_action_confidence_bounds() -> None:
54
+ """Test confidence is bounded to 0-1 by Pydantic validation."""
55
+ import pytest
56
+ from pydantic import ValidationError
57
+
58
+ # Values > 1.0 should raise validation error
59
+ with pytest.raises(ValidationError):
60
+ Action(
61
+ action_type=ActionType.WAIT,
62
+ confidence=1.5,
63
+ )
64
+
65
+ # Values < 0.0 should raise validation error
66
+ with pytest.raises(ValidationError):
67
+ Action(
68
+ action_type=ActionType.WAIT,
69
+ confidence=-0.5,
70
+ )
71
+
72
+ # Valid boundary values should work
73
+ action_max = Action(action_type=ActionType.WAIT, confidence=1.0)
74
+ assert action_max.confidence == 1.0
75
+
76
+ action_min = Action(action_type=ActionType.WAIT, confidence=0.0)
77
+ assert action_min.confidence == 0.0
backend/tests/test_core/test_env.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tests for environment."""
2
+
3
+ import pytest
4
+ from app.core.env import WebScraperEnv
5
+
6
+
7
+ def test_env_creation() -> None:
8
+ """Test creating environment."""
9
+ env = WebScraperEnv(episode_id="test-episode-001")
10
+ assert env is not None
11
+ assert env.episode_id == "test-episode-001"
12
+
13
+
14
+ @pytest.mark.asyncio
15
+ async def test_env_reset() -> None:
16
+ """Test environment reset."""
17
+ env = WebScraperEnv(episode_id="test-episode-002")
18
+ observation, info = await env.reset(task_id="test_task", seed=42)
19
+ assert observation is not None
20
+ assert observation.step_number == 0
backend/tests/test_core/test_observation.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tests for observation model."""
2
+
3
+ import pytest
4
+ from app.core.observation import Observation, TaskContext, ExtractedField, MemoryContext
5
+
6
+
7
+ def test_observation_creation() -> None:
8
+ """Test creating an observation."""
9
+ obs = Observation(
10
+ episode_id="ep_001",
11
+ task_id="task_001",
12
+ step_number=0,
13
+ )
14
+ assert obs.episode_id == "ep_001"
15
+ assert obs.step_number == 0
16
+ assert obs.extraction_progress == 0.0
17
+
18
+
19
+ def test_observation_with_task_context() -> None:
20
+ """Test observation with task context."""
21
+ task_ctx = TaskContext(
22
+ task_id="task_001",
23
+ task_name="Extract Product",
24
+ task_type="extraction",
25
+ target_fields=["name", "price"],
26
+ required_fields=["name"],
27
+ )
28
+ obs = Observation(
29
+ episode_id="ep_001",
30
+ task_id="task_001",
31
+ step_number=0,
32
+ task_context=task_ctx,
33
+ )
34
+ assert obs.task_context is not None
35
+ assert obs.task_context.task_name == "Extract Product"
36
+
37
+
38
+ def test_observation_extraction_tracking() -> None:
39
+ """Test extraction progress tracking."""
40
+ obs = Observation(
41
+ episode_id="ep_001",
42
+ task_id="task_001",
43
+ step_number=1,
44
+ extracted_so_far=[
45
+ ExtractedField(field_name="name", value="Test Product", confidence=0.95),
46
+ ],
47
+ fields_remaining=["price", "description"],
48
+ )
49
+ assert len(obs.extracted_so_far) == 1
50
+ assert obs.is_field_extracted("name")
51
+ assert not obs.is_field_extracted("price")
52
+
53
+ extraction_dict = obs.get_extraction_dict()
54
+ assert extraction_dict["name"] == "Test Product"
55
+
56
+
57
+ def test_observation_context_summary() -> None:
58
+ """Test context summary generation."""
59
+ obs = Observation(
60
+ episode_id="ep_001",
61
+ task_id="task_001",
62
+ step_number=5,
63
+ current_url="https://example.com",
64
+ extraction_progress=0.5,
65
+ extracted_so_far=[
66
+ ExtractedField(field_name="name", value="Test"),
67
+ ],
68
+ fields_remaining=["price"],
69
+ )
70
+ summary = obs.get_context_summary()
71
+ assert "Step 5" in summary
72
+ assert "example.com" in summary
backend/tests/test_core/test_reward.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tests for reward computation."""
2
+
3
+ import pytest
4
+ from app.core.reward import RewardEngine, RewardBreakdown
5
+
6
+
7
+ def test_reward_engine_creation() -> None:
8
+ """Test creating reward engine."""
9
+ engine = RewardEngine()
10
+ assert engine is not None
11
+
12
+
13
+ def test_reward_breakdown() -> None:
14
+ """Test reward breakdown structure."""
15
+ breakdown = RewardBreakdown(
16
+ accuracy=0.8,
17
+ efficiency=0.6,
18
+ cost=-0.1,
19
+ total=0.7,
20
+ )
21
+ assert breakdown.total == 0.7
22
+ assert breakdown.accuracy == 0.8