"""Real AI assist for close tasks — eligibility + deterministic fallback + HTTP.""" from __future__ import annotations import pytest from app.domains.finance import close_ai_service as ai from app.domains.finance import close_task_service as svc @pytest.mark.asyncio async def test_assist_deterministic_fallback(db_session): # No LLM key configured in tests → grounded deterministic assist. await svc.instantiate_from_templates(db_session, "2026-06") tasks = {t.instance_key: t for t in await svc.list_tasks(db_session, "2026-06")} # cogs-08 is ai_work=assist in the seed. t = tasks["cogs-08"] res = await ai.generate_task_assist(db_session, t.id) assert res["mode"] == "assist" assert res["source"] == "deterministic" assert t.description.split(".")[0][:10] in res["text"] or "How to" in res["text"] @pytest.mark.asyncio async def test_perform_mode_is_draft(db_session): await svc.instantiate_from_templates(db_session, "2026-06") tasks = {t.instance_key: t for t in await svc.list_tasks(db_session, "2026-06")} t = tasks["cogs-09"] # ai_work=perform in the seed res = await ai.generate_task_assist(db_session, t.id) assert res["mode"] == "perform" assert "Draft" in res["text"] or "draft" in res["text"] @pytest.mark.asyncio async def test_non_ai_task_rejected(db_session): await svc.instantiate_from_templates(db_session, "2026-06") tasks = {t.instance_key: t for t in await svc.list_tasks(db_session, "2026-06")} t = tasks["ap-02"] # ai_work=no with pytest.raises(ai.CloseTaskNotEligible): await ai.generate_task_assist(db_session, t.id) @pytest.mark.asyncio async def test_http_ai_assist_and_rbac(client, finance_headers, auditor_headers): await client.post("/api/v1/finance/close/runs/2026-06/instantiate", headers=finance_headers) r = await client.get("/api/v1/finance/close/runs/2026-06/tasks?process=COGS", headers=finance_headers) tasks = {t["template_source_key"]: t for t in r.json()["data"]["tasks"]} assist_id = tasks["cogs-08"]["id"] no_id = tasks["cogs-10"]["id"] # ai_work=no r = await client.post(f"/api/v1/finance/close/tasks/{assist_id}/ai-assist", headers=finance_headers) assert r.status_code == 200, r.text assert r.json()["data"]["text"] assert r.json()["data"]["source"] in ("llm", "deterministic") # A non-AI task → 4xx (not eligible). r = await client.post(f"/api/v1/finance/close/tasks/{no_id}/ai-assist", headers=finance_headers) assert r.status_code >= 400 # Auditor (view-only) can't trigger AI generation. r = await client.post(f"/api/v1/finance/close/tasks/{assist_id}/ai-assist", headers=auditor_headers) assert r.status_code == 403