grantforge-api / backend /tests /test_batch_c_audit.py
GrantForge Bot
Deploy sha-565ad85979610064f6d1c18ab3b6404357d61073 — source build (no GHCR)
ce8f04a
Raw
History Blame Contribute Delete
5.47 kB
"""Tests for Batch C — snapshot gap audit, regulation schemas, tenant audit log."""
import pytest
from httpx import ASGITransport, AsyncClient
from core.audit.snapshot_gap_audit import run_snapshot_gap_audit
from core.regulation.schema_registry import get_schema, list_schema_ids
from core.search.regulation_engine import regulation_engine
from server import app
SAMPLE_DOCUMENT = """
# Wniosek o dofinansowanie
## Opis projektu i innowacji
Projekt zakłada rozwój innowacyjnego oprogramowania SaaS na poziomie TRL 6.
Komponent B+R obejmuje badania nad modelem predykcyjnym.
## Harmonogram i kamienie milowe
Q1: prototyp, Q2: pilotaż, Q3: wdrożenie komercyjne.
## Budżet i kwalifikowalność kosztów
- Wynagrodzenia personelu B+R: 120 000 PLN
- Oprogramowanie i licencje: 30 000 PLN
- Szkolenia: 10 000 PLN
## Analiza rynku i wdrożenie
Rynek MŚP w Polsce, plan sprzedaży subskrypcji.
Status MŚP: mikroprzedsiębiorstwo zgodnie z 651/2014.
"""
def test_snapshot_gap_audit_detects_complete_smart_document():
rules = regulation_engine.get_structured_rules_for_program("SMART")
report = run_snapshot_gap_audit(
SAMPLE_DOCUMENT,
"SMART",
structured_rules=rules,
company_profile={"size": "mikro", "nip": "5250000000"},
)
assert report.overall_score >= 70
assert not report.has_critical_gaps
assert report.innovation_status.get("adequate") is True
assert report.sme_status.get("eligible_signal") is True
def test_snapshot_gap_audit_flags_missing_sections_and_ineligible_costs():
bad_doc = """
## Budżet
Zakup samochodu osobowego dla prezesa: 80 000 PLN
Marketing i reklama: 50 000 PLN
Status MŚP: mikroprzedsiębiorstwo.
"""
report = run_snapshot_gap_audit(
bad_doc,
"SMART",
company_profile={"size": "duże"},
)
assert report.overall_score < 80
assert any(g.category == "costs" for g in report.gaps)
assert any(g.category == "sections" for g in report.gaps)
assert any(g.code == "SME_STATUS_CONFLICT" for g in report.gaps)
def test_snapshot_gap_audit_rejects_short_document():
report = run_snapshot_gap_audit("krótki", "PARP")
assert report.overall_score == 0
assert report.has_critical_gaps
def test_regulation_schema_registry():
ids = list_schema_ids()
assert "program_rules" in ids
assert "gap_audit_report" in ids
schema = get_schema("gap_audit_report")
assert schema and schema["type"] == "object"
@pytest.mark.asyncio
async def test_api_regulation_schemas(auth_headers):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
resp = await client.get("/api/regulation/schemas", headers=auth_headers)
assert resp.status_code == 200
data = resp.json()
assert "program_rules" in data["schema_ids"]
assert "schemas" in data
@pytest.mark.asyncio
async def test_api_gap_report_json(auth_headers):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
resp = await client.post(
"/api/audit/gap-report",
headers=auth_headers,
json={
"program_name": "SMART",
"document_text": SAMPLE_DOCUMENT,
"export_format": "json",
"company_profile": {"size": "mikro"},
},
)
assert resp.status_code == 200
body = resp.json()
assert body["program"] == "SMART"
assert "overall_score" in body
assert "gaps" in body
@pytest.mark.asyncio
async def test_api_audit_logs_after_gap_report(auth_headers):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
await client.post(
"/api/audit/gap-report",
headers=auth_headers,
json={
"program_name": "PARP",
"document_text": SAMPLE_DOCUMENT,
"export_format": "json",
},
)
logs = await client.get("/api/audit-logs", headers=auth_headers)
assert logs.status_code == 200
items = logs.json()["items"]
assert any(i["action"] == "gap_audit_completed" for i in items)
@pytest.mark.asyncio
async def test_collaborator_add_remove_creates_audit_logs(auth_headers):
from core.projects.models import Project
from core.subscription.db import SessionLocal
db = SessionLocal()
try:
project = Project(
clerk_user_id="test_clerk_id_e2e",
title="Audit Collab Test",
program_type="SMART",
)
db.add(project)
db.commit()
db.refresh(project)
project_id = project.id
finally:
db.close()
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
add = await client.post(
f"/api/audit/collaborators/{project_id}",
headers=auth_headers,
json={"collaborator_user_id": "collab_user_99", "role": "viewer"},
)
assert add.status_code == 200
remove = await client.delete(
f"/api/audit/collaborators/{project_id}/collab_user_99",
headers=auth_headers,
)
assert remove.status_code == 200
logs = await client.get("/api/audit-logs", headers=auth_headers)
actions = [i["action"] for i in logs.json()["items"]]
assert "collaborator_added" in actions
assert "collaborator_removed" in actions