"""Tests for core/context_builder.py — context assembly per role.""" import pytest from core.db import init_db from core import models from core.context_builder import build_context, format_prompt @pytest.fixture def conn(): c = init_db(":memory:") # Seed project, modules, decisions, tasks models.create_project(c, "vdol", "ВДОЛЬ и ПОПЕРЕК", "~/projects/vdolipoperek", tech_stack=["vue3", "typescript", "nodejs"]) models.add_module(c, "vdol", "search", "frontend", "src/search/") models.add_module(c, "vdol", "api", "backend", "src/api/") models.add_decision(c, "vdol", "gotcha", "Safari bug", "position:fixed breaks", category="ui", tags=["ios"]) models.add_decision(c, "vdol", "workaround", "API rate limit", "10 req/s max", category="api") models.add_decision(c, "vdol", "convention", "Use WAL mode", "Always use WAL for SQLite", category="architecture") models.add_decision(c, "vdol", "decision", "Auth required", "All endpoints need auth", category="security") models.create_task(c, "VDOL-001", "vdol", "Fix search filters", brief={"module": "search", "route_type": "debug"}) models.create_task(c, "VDOL-002", "vdol", "Add payments", status="in_progress") yield c c.close() class TestBuildContext: def test_pm_gets_everything(self, conn): ctx = build_context(conn, "VDOL-001", "pm", "vdol") assert ctx["task"]["id"] == "VDOL-001" assert ctx["project"]["id"] == "vdol" assert len(ctx["modules"]) == 2 assert len(ctx["decisions"]) == 4 # all decisions assert len(ctx["active_tasks"]) == 1 # VDOL-002 in_progress assert "pm" in ctx["available_specialists"] def test_architect_gets_all_decisions_and_modules(self, conn): ctx = build_context(conn, "VDOL-001", "architect", "vdol") assert len(ctx["modules"]) == 2 assert len(ctx["decisions"]) == 4 def test_debugger_gets_only_gotcha_workaround(self, conn): ctx = build_context(conn, "VDOL-001", "debugger", "vdol") types = {d["type"] for d in ctx["decisions"]} assert types <= {"gotcha", "workaround"} assert "convention" not in types assert "decision" not in types assert ctx["module_hint"] == "search" def test_frontend_dev_gets_gotcha_workaround_convention(self, conn): ctx = build_context(conn, "VDOL-001", "frontend_dev", "vdol") types = {d["type"] for d in ctx["decisions"]} assert "gotcha" in types assert "workaround" in types assert "convention" in types assert "decision" not in types # plain decisions excluded def test_backend_dev_same_as_frontend(self, conn): ctx = build_context(conn, "VDOL-001", "backend_dev", "vdol") types = {d["type"] for d in ctx["decisions"]} assert types == {"gotcha", "workaround", "convention"} def test_reviewer_gets_only_conventions(self, conn): ctx = build_context(conn, "VDOL-001", "reviewer", "vdol") types = {d["type"] for d in ctx["decisions"]} assert types == {"convention"} def test_tester_gets_minimal_context(self, conn): ctx = build_context(conn, "VDOL-001", "tester", "vdol") assert ctx["task"] is not None assert ctx["project"] is not None assert "decisions" not in ctx assert "modules" not in ctx def test_security_gets_security_decisions(self, conn): ctx = build_context(conn, "VDOL-001", "security", "vdol") categories = {d.get("category") for d in ctx["decisions"]} assert categories == {"security"} def test_unknown_role_gets_fallback(self, conn): ctx = build_context(conn, "VDOL-001", "unknown_role", "vdol") assert "decisions" in ctx assert len(ctx["decisions"]) > 0 class TestFormatPrompt: def test_format_with_template(self, conn): ctx = build_context(conn, "VDOL-001", "debugger", "vdol") prompt = format_prompt(ctx, "debugger", "You are a debugger. Find bugs.") assert "You are a debugger" in prompt assert "VDOL-001" in prompt assert "Fix search filters" in prompt assert "vdol" in prompt assert "vue3" in prompt def test_format_includes_decisions(self, conn): ctx = build_context(conn, "VDOL-001", "debugger", "vdol") prompt = format_prompt(ctx, "debugger", "Debug this.") assert "Safari bug" in prompt assert "API rate limit" in prompt # Convention should NOT be here (debugger doesn't get it) assert "WAL mode" not in prompt def test_format_pm_includes_specialists(self, conn): ctx = build_context(conn, "VDOL-001", "pm", "vdol") prompt = format_prompt(ctx, "pm", "You are PM.") assert "Available specialists" in prompt assert "debugger" in prompt assert "Active tasks" in prompt assert "VDOL-002" in prompt def test_format_with_previous_output(self, conn): ctx = build_context(conn, "VDOL-001", "tester", "vdol") ctx["previous_output"] = "Found race condition in useSearch.ts" prompt = format_prompt(ctx, "tester", "Write tests.") assert "Previous step output" in prompt assert "race condition" in prompt def test_format_loads_prompt_file(self, conn): ctx = build_context(conn, "VDOL-001", "pm", "vdol") prompt = format_prompt(ctx, "pm") # Should load from agents/prompts/pm.md assert "decompose" in prompt.lower() or "pipeline" in prompt.lower() def test_format_missing_prompt_file(self, conn): ctx = build_context(conn, "VDOL-001", "analyst", "vdol") prompt = format_prompt(ctx, "analyst") # No analyst.md exists assert "analyst" in prompt.lower()