"""F1 generation consent + dossier gate (pure unit tests on shipped functions).""" from __future__ import annotations import os from types import SimpleNamespace import pytest from core.projects.generation_consent import ( ERROR_CODE, GROUNDING_BLOCKED, GROUNDING_REGULATION, GROUNDING_STRUCTURE, STRUCTURE_EXPORT_REASON, STRUCTURE_ONLY_PROMPT, apply_structure_consent, build_generation_gate, clear_structure_consent, consent_required, project_generation_meta, resolve_grounding_mode, sync_grounding_after_dossier_update, ) @pytest.fixture(autouse=True) def _flags_on(monkeypatch): monkeypatch.setenv("REQUIRE_DOSSIER_OR_CONSENT", "true") monkeypatch.setenv("ALLOW_STRUCTURE_WITHOUT_REGULATION", "true") def test_consent_without_ack_rejected(): ext, err = apply_structure_consent({}, ack=False, mode="structure_without_regulation", clerk_user_id="u1") assert err assert "ack" in err.lower() or "potwierdzenie" in err.lower() assert "generation_consent" not in ext def test_blind_without_consent_blocked(): ext = {"program_dossier": {"readiness": {"level": "blind"}}} gate = build_generation_gate(ext) assert gate["allowed"] is False assert gate["grounding_mode"] == GROUNDING_BLOCKED assert gate["code"] == ERROR_CODE assert gate["consent_required"] is True assert consent_required(ext) is True def test_blind_with_valid_consent_structure_only(): ext = {"program_dossier": {"readiness": {"level": "blind"}}} ext2, err = apply_structure_consent( ext, ack=True, mode="structure_without_regulation", clerk_user_id="user_abc", grant_id="g1" ) assert err is None assert ext2["generation_consent"]["mode"] == "structure_without_regulation" assert ext2["generation_consent"]["granted_by"] == "user_abc" assert ext2["generation_consent"]["granted_at"] gate = build_generation_gate(ext2) assert gate["allowed"] is True assert gate["grounding_mode"] == GROUNDING_STRUCTURE assert gate["structure_prompt"] assert "NIE cytuj regulaminu" in gate["structure_prompt"] assert gate["human_review_required"] is True assert STRUCTURE_EXPORT_REASON in (gate["export_reason"] or "") def test_after_non_blind_consent_cleared_to_regulation(): ext = { "program_dossier": {"readiness": {"level": "blind"}}, "generation_consent": { "mode": "structure_without_regulation", "granted_at": "2026-01-01T00:00:00+00:00", "granted_by": "u", }, "grounding_mode": "structure_only", } # Simulate dossier became ready ext["program_dossier"] = { "readiness": {"level": "ready"}, "regulation_documents": [ {"url": "https://x.pl/reg.pdf", "role": "regulamin", "score": 40}, {"url": "https://x.pl/rwp.pdf", "role": "rwp", "score": 38}, ], } ext["precise_regulation_url"] = "https://x.pl/reg.pdf" synced = sync_grounding_after_dossier_update(ext) assert "generation_consent" not in synced assert resolve_grounding_mode(synced) == GROUNDING_REGULATION assert synced["grounding_mode"] == GROUNDING_REGULATION def test_ready_does_not_need_consent(): ext = { "program_dossier": {"readiness": {"level": "ready"}}, "precise_regulation_url": "https://x.pl/a.pdf", "regulation_documents": [ {"url": "https://x.pl/a.pdf", "role": "regulamin", "score": 40}, {"url": "https://x.pl/b.pdf", "role": "rwp", "score": 30}, ], } assert resolve_grounding_mode(ext) == GROUNDING_REGULATION assert consent_required(ext) is False _, err = apply_structure_consent(ext, ack=True, mode="structure_without_regulation", clerk_user_id="u") assert err # not needed def test_flag_off_allows_generation(): os.environ["REQUIRE_DOSSIER_OR_CONSENT"] = "false" ext = {"program_dossier": {"readiness": {"level": "blind"}}} assert resolve_grounding_mode(ext) == GROUNDING_REGULATION assert build_generation_gate(ext)["allowed"] is True os.environ["REQUIRE_DOSSIER_OR_CONSENT"] = "true" def test_clear_consent_returns_blocked_when_blind(): ext = { "program_dossier": {"readiness": {"level": "blind"}}, "generation_consent": { "mode": "structure_without_regulation", "granted_at": "2026-01-01T00:00:00+00:00", "granted_by": "u", }, } cleared = clear_structure_consent(ext) assert "generation_consent" not in cleared assert resolve_grounding_mode(cleared) == GROUNDING_BLOCKED def test_project_generation_meta_fields(): ext = {"program_dossier": {"readiness": {"level": "blind"}}} meta = project_generation_meta(ext) assert meta["consent_required"] is True assert meta["generation_allowed"] is False assert meta["dossier_readiness"] == "blind" def test_export_gate_structure_only_human_review(): from core.projects.export_gate import evaluate_export_gate_with_guidance project = SimpleNamespace( external_context={ "program_dossier": {"readiness": {"level": "blind"}}, "generation_consent": { "mode": "structure_without_regulation", "granted_at": "2026-01-01T00:00:00+00:00", "granted_by": "u", }, "grounding_mode": "structure_only", }, final_document_audit_result={ "status": "completed", "export_status": "ok", "is_approved": True, "human_review_required": True, }, ) gate = evaluate_export_gate_with_guidance(project) assert gate.get("structure_only") is True assert gate.get("human_review_required") is True assert any("strukturalny" in (g.get("reason") or "").lower() or "regulaminu" in (g.get("reason") or "").lower() for g in gate.get("guidance") or []) def test_structure_prompt_constant(): assert "DO WERYFIKACJI" in STRUCTURE_ONLY_PROMPT or "WERYFIKACJI" in STRUCTURE_ONLY_PROMPT assert "NIE cytuj" in STRUCTURE_ONLY_PROMPT or "nie cytuj" in STRUCTURE_ONLY_PROMPT.lower() def test_dossier_stats_on_empty_db(monkeypatch): """Stats aggregation pure path with mock session.""" from core.grants import dossier_ops class FakeQuery: def filter(self, *a, **k): return self def limit(self, n): return self def all(self): return [] class FakeDB: def query(self, *a, **k): return FakeQuery() dist = dossier_ops.compute_readiness_distribution(FakeDB(), limit=10) assert dist["total"] == 0 assert "pct_blind" in dist assert "flags" in dist def test_soft_check_allows_structure_only_full_autopilot(): """F1: structure_only consent must not be blocked by regulation soft-check.""" from endpoints.generator import _soft_check_regulation_for_full_autopilot project = SimpleNamespace( external_context={ "program_dossier": {"readiness": {"level": "blind"}}, "generation_consent": { "mode": "structure_without_regulation", "granted_at": "2026-01-01T00:00:00+00:00", "granted_by": "u", }, "grounding_mode": "structure_only", "regulation_pending": True, "regulation_source": "none", } ) assert _soft_check_regulation_for_full_autopilot(project) is None def test_f3_structure_only_skips_regulation_section_seed(monkeypatch): """F3: required_sections from advisor_brief only seed under regulation mode.""" from endpoints import generator as gen_mod calls = {"reg": 0, "tmpl": 0} class FakeSection: def __init__(self, st="desc", content=""): self.section_type = st self.content = content self.order = 0 class FakeQ: def __init__(self, rows): self._rows = rows def filter(self, *a, **k): return self def order_by(self, *a, **k): return self def all(self): return self._rows class FakeDB: def query(self, model): name = getattr(model, "__name__", str(model)) if "Template" in name: return FakeQ([]) return FakeQ([]) # no existing sections def commit(self): pass def fake_seed_reg(db, pid, required, program_type): calls["reg"] += 1 return [FakeSection("from_reg")], {"from_reg": "From regulation"} def fake_seed_tmpl(db, pid, program_type): calls["tmpl"] += 1 return [FakeSection("from_tmpl", "x" * 20)] monkeypatch.setattr( "core.projects.section_seeder.seed_sections_from_regulation", fake_seed_reg, raising=False, ) # patch where used inside function import core.projects.section_seeder as seeder monkeypatch.setattr(seeder, "seed_sections_from_regulation", fake_seed_reg) monkeypatch.setattr(seeder, "seed_sections_from_templates", fake_seed_tmpl) project = SimpleNamespace( program_type="SMART", external_context={ "program_dossier": {"readiness": {"level": "blind"}}, "generation_consent": { "mode": "structure_without_regulation", "granted_at": "2026-01-01T00:00:00+00:00", "granted_by": "u", }, "grounding_mode": "structure_only", "required_sections": ["Sekcja z advisor_brief", "Inna"], }, ) plan, _ = gen_mod._ensure_sections_plan_from_project(FakeDB(), project, "p1") assert calls["reg"] == 0, "structure_only must not seed from regulation required_sections" assert calls["tmpl"] == 1 assert plan # templates used # regulation mode uses required_sections calls["reg"] = calls["tmpl"] = 0 project_reg = SimpleNamespace( program_type="SMART", external_context={ "program_dossier": { "readiness": {"level": "ready"}, "regulation_documents": [ {"url": "https://x.pl/a.pdf", "role": "regulamin", "score": 40}, {"url": "https://x.pl/b.pdf", "role": "rwp", "score": 30}, ], }, "precise_regulation_url": "https://x.pl/a.pdf", "required_sections": ["Sekcja A", "Sekcja B"], }, ) gen_mod._ensure_sections_plan_from_project(FakeDB(), project_reg, "p2") assert calls["reg"] == 1 assert calls["tmpl"] == 0