grantforge-api / backend /tests /test_generation_consent.py
GrantForge Bot
Deploy sha-565ad85979610064f6d1c18ab3b6404357d61073 — source build (no GHCR)
ce8f04a
Raw
History Blame Contribute Delete
10.6 kB
"""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