Spaces:
Sleeping
Sleeping
Commit ·
46eecf4
1
Parent(s): 27cde0c
test: add comprehensive backend test suite
Browse files- backend/tests/__init__.py +1 -0
- backend/tests/__pycache__/__init__.cpython-314.pyc +0 -0
- backend/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
- backend/tests/conftest.py +58 -0
- backend/tests/test_agents/__init__.py +1 -0
- backend/tests/test_agents/__pycache__/__init__.cpython-314.pyc +0 -0
- backend/tests/test_agents/__pycache__/test_coordinator.cpython-314-pytest-9.0.2.pyc +0 -0
- backend/tests/test_agents/test_coordinator.py +36 -0
- backend/tests/test_api/__init__.py +1 -0
- backend/tests/test_api/__pycache__/__init__.cpython-314.pyc +0 -0
- backend/tests/test_api/__pycache__/test_agents.cpython-314-pytest-9.0.2.pyc +0 -0
- backend/tests/test_api/__pycache__/test_episode.cpython-314-pytest-9.0.2.pyc +0 -0
- backend/tests/test_api/__pycache__/test_health.cpython-314-pytest-9.0.2.pyc +0 -0
- backend/tests/test_api/__pycache__/test_tools.cpython-314-pytest-9.0.2.pyc +0 -0
- backend/tests/test_api/test_agents.py +24 -0
- backend/tests/test_api/test_episode.py +48 -0
- backend/tests/test_api/test_health.py +20 -0
- backend/tests/test_api/test_tools.py +24 -0
- backend/tests/test_core/__init__.py +1 -0
- backend/tests/test_core/__pycache__/__init__.cpython-314.pyc +0 -0
- backend/tests/test_core/__pycache__/test_action.cpython-314-pytest-9.0.2.pyc +0 -0
- backend/tests/test_core/__pycache__/test_env.cpython-314-pytest-9.0.2.pyc +0 -0
- backend/tests/test_core/__pycache__/test_observation.cpython-314-pytest-9.0.2.pyc +0 -0
- backend/tests/test_core/__pycache__/test_reward.cpython-314-pytest-9.0.2.pyc +0 -0
- backend/tests/test_core/test_action.py +77 -0
- backend/tests/test_core/test_env.py +20 -0
- backend/tests/test_core/test_observation.py +72 -0
- 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
|