multi-agent-system / tests /test_workspace_integration.py
firepenguindisopanda
Refactor test documentation and improve consistency in comments
e382248
Raw
History Blame Contribute Delete
11.9 kB
"""
Integration tests for the Workspace API router.
Tests verify:
- Endpoints are mounted and accept validated requests
- Document sections include user stories, functional/non-functional requirements, and use cases
- API rejects invalid input with proper validation errors
"""
from fastapi.testclient import TestClient
class TestWorkspaceEndpointsMounted:
"""Verify all workspace endpoints are mounted and responding."""
def test_evaluate_endpoint_exists(self):
from app.main import app
client = TestClient(app)
response = client.post("/api/workspace/evaluate", json={"idea": ""})
assert response.status_code == 200
def test_evaluate_rejects_missing_body(self):
from app.main import app
client = TestClient(app)
response = client.post("/api/workspace/evaluate")
assert response.status_code == 422
def test_directions_endpoint_exists(self):
from app.main import app
client = TestClient(app)
response = client.post("/api/workspace/directions", json={"answers": {}})
assert response.status_code == 200
def test_directions_rejects_missing_body(self):
from app.main import app
client = TestClient(app)
response = client.post("/api/workspace/directions")
assert response.status_code == 422
def test_generate_endpoint_exists(self):
from app.main import app
client = TestClient(app)
response = client.post("/api/workspace/generate", json={
"direction_id": "dir-a",
"brief": "Build a task manager"
})
assert response.status_code == 200
def test_generate_rejects_missing_body(self):
from app.main import app
client = TestClient(app)
response = client.post("/api/workspace/generate")
assert response.status_code == 422
def test_refine_endpoint_exists(self):
from app.main import app
client = TestClient(app)
response = client.post("/api/workspace/refine", json={
"section_id": "sec-test",
"content": "Some content",
"prompt": "Make it better"
})
assert response.status_code == 200
def test_refine_rejects_missing_body(self):
from app.main import app
client = TestClient(app)
response = client.post("/api/workspace/refine")
assert response.status_code == 422
class TestDocumentSectionsIncludeRequirements:
"""Verify document generation sections include user stories, requirements, and use cases."""
def test_sections_include_user_stories(self):
"""SECTIONS must include a User Stories section."""
from app.services.workspace_service import SECTIONS
section_titles = [s["title"].lower() for s in SECTIONS]
assert any("user stor" in t for t in section_titles), \
f"SECTIONS must include 'User Stories' but got: {section_titles}"
def test_sections_include_functional_requirements(self):
"""SECTIONS must include a Functional Requirements section."""
from app.services.workspace_service import SECTIONS
section_titles = [s["title"].lower() for s in SECTIONS]
assert any("functional requirement" in t for t in section_titles), \
f"SECTIONS must include 'Functional Requirements' but got: {section_titles}"
def test_sections_include_non_functional_requirements(self):
"""SECTIONS must include a Non-Functional Requirements section."""
from app.services.workspace_service import SECTIONS
section_titles = [s["title"].lower() for s in SECTIONS]
assert any("non-functional" in t or "non functional" in t for t in section_titles), \
f"SECTIONS must include 'Non-Functional Requirements' but got: {section_titles}"
def test_sections_include_use_cases(self):
"""SECTIONS must include a Use Cases section."""
from app.services.workspace_service import SECTIONS
section_titles = [s["title"].lower() for s in SECTIONS]
assert any("use case" in t for t in section_titles), \
f"SECTIONS must include 'Use Cases' but got: {section_titles}"
class TestEvaluateEndpointSchema:
"""Verify the evaluate endpoint returns correct response schema."""
def test_evaluate_returns_correct_structure(self):
from app.main import app
client = TestClient(app)
response = client.post("/api/workspace/evaluate", json={
"idea": "Build a task management app with AI-powered prioritization"
})
assert response.status_code == 200
data = response.json()
assert "scores" in data
assert "overall_score" in data
assert "threshold_met" in data
assert "weak_dimensions" in data
assert "targeted_questions" in data
def test_evaluate_empty_idea_returns_fallback(self):
from app.main import app
client = TestClient(app)
response = client.post("/api/workspace/evaluate", json={"idea": ""})
assert response.status_code == 200
data = response.json()
assert data["overall_score"] == 0.0
assert data["threshold_met"] is False
assert len(data["targeted_questions"]) > 0
class TestDirectionsEndpointSchema:
"""Verify the directions endpoint returns correct response schema."""
def test_directions_returns_correct_structure(self):
from app.main import app
client = TestClient(app)
response = client.post("/api/workspace/directions", json={
"answers": {"q1": "B2C", "q2": "Web app", "q3": "Task management", "q4": "AI-powered prioritization"}
})
assert response.status_code == 200
data = response.json()
assert "directions" in data
assert isinstance(data["directions"], list)
if len(data["directions"]) > 0:
direction = data["directions"][0]
assert "id" in direction
assert "title" in direction
assert "description" in direction
assert "tags" in direction
def test_directions_empty_answers_returns_fallback(self):
from app.main import app
client = TestClient(app)
response = client.post("/api/workspace/directions", json={"answers": {}})
assert response.status_code == 200
data = response.json()
assert len(data["directions"]) >= 2
class TestRefineEndpoint:
"""Verify the refine endpoint handles edge cases gracefully."""
def test_refine_empty_content_returns_original(self):
from app.main import app
client = TestClient(app)
response = client.post("/api/workspace/refine", json={
"section_id": "sec-test",
"content": "",
"prompt": "Make it better"
})
assert response.status_code == 200
data = response.json()
assert data["section_id"] == "sec-test"
assert data["content"] == ""
class TestPipelineGenerationPath:
"""Guards the path /api/workspace/generate actually takes.
`SECTIONS` and the tests above now describe only the degraded single-agent
fallback, used when the orchestrator cannot start. Workspace generation
normally runs the full agent team, and nothing above would notice if that
mapping broke - these cover it.
"""
def test_section_ids_round_trip_to_roles(self):
"""Every agent's section id must map back to that agent.
`save_workspace` uses this inverse to decide which sections are agent
output; if it stops round-tripping, saved projects silently lose every
agent and the dashboard reports "No output".
"""
from app.services.workspace_service import (
_role_section_id,
pipeline_section_order,
role_for_section_id,
)
for role in pipeline_section_order():
assert role_for_section_id(_role_section_id(role)) == role
def test_legacy_section_ids_are_not_mistaken_for_roles(self):
"""Fallback section ids must not be read as agent output."""
from app.services.workspace_service import SECTIONS, role_for_section_id
for section in SECTIONS:
assert role_for_section_id(section["id"]) is None
def test_pipeline_order_matches_the_orchestrator(self):
"""The section order must be derived from the phases, not restated."""
from app.core.pipeline_orchestrator import PipelineOrchestrator
from app.services.workspace_service import pipeline_section_order
expected = [
role.value
for role in PipelineOrchestrator.PHASE_1
+ PipelineOrchestrator.PHASE_2
+ PipelineOrchestrator.PHASE_3
]
assert pipeline_section_order() == expected
assert len(expected) == 11
def test_titles_render_acronyms(self):
"""`.title()` alone produces "Ux Designer" and "Qa Strategist"."""
from app.services.workspace_service import _role_section_title
assert _role_section_title("ux_designer") == "UX Designer"
assert _role_section_title("api_designer") == "API Designer"
assert _role_section_title("qa_strategist") == "QA Strategist"
assert _role_section_title("devops_architect") == "DevOps Architect"
assert _role_section_title("solution_architect") == "Solution Architect"
def test_save_puts_agent_output_at_top_level_and_metadata_under_underscore(self):
"""The dashboard reads `artifacts` as a role -> markdown map.
Writing workspace metadata at the top level made a saved spec report
"4 Agents" and render chips reading "type" and "brief".
"""
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.core.database import Base
from app.core import models
from app.services.workspace_service import _role_section_id, save_workspace
# Its own in-memory engine - never the shared one, whose teardown is
# destructive elsewhere in this suite.
engine = create_engine("sqlite://")
Base.metadata.create_all(engine)
db = sessionmaker(bind=engine)()
try:
user = models.User(
id=1, google_id="g", email="w@example.com", full_name="W", role="user"
)
db.add(user)
db.commit()
sections = [
{
"id": _role_section_id("solution_architect"),
"title": "Solution Architect",
"content": "## Architecture\nContent.",
"order": 0,
},
{
"id": "sec-overview",
"title": "Project Overview",
"content": "Fallback section.",
"order": 1,
},
]
project = save_workspace(
db=db,
current_user=user,
title="T",
direction_id="dir-a",
brief="b",
sections=sections,
)
assert project.artifacts["solution_architect"] == "## Architecture\nContent."
assert "_workspace" in project.artifacts
assert len(project.artifacts["_workspace"]["sections"]) == 2
# Metadata must not sit alongside roles, or it gets counted as one.
for key in ("type", "direction_id", "brief", "sections"):
assert key not in project.artifacts
# The fallback section has no role, so it is not agent output.
roles = [k for k in project.artifacts if not k.startswith("_")]
assert roles == ["solution_architect"]
finally:
db.close()
engine.dispose()