Files
breakpilot-compliance/backend-compliance/tests/test_company_profile_extend.py
Benjamin Admin 1e84df9769
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 32s
CI / test-python-backend-compliance (push) Successful in 30s
CI / test-python-document-crawler (push) Successful in 21s
CI / test-python-dsms-gateway (push) Successful in 18s
feat(sdk): Multi-Tenancy, Versionierung, Change-Requests, Dokumentengenerierung (Phase 1-6)
6-Phasen-Implementation fuer cloud-faehiges, mandantenfaehiges Compliance SDK:

Phase 1: Multi-Tenancy Fix
- Shared tenant_utils.py Dependency (UUID-Validierung, kein "default" mehr)
- VVT tenant_id Column + tenant-scoped Queries
- DSFA/Vendor DEFAULT_TENANT_ID von "default" auf UUID migriert
- Migration 035

Phase 2: Stammdaten-Erweiterung
- Company Profile um JSONB-Felder erweitert (processing_systems, ai_systems, technical_contacts)
- Regulierungs-Flags (NIS2, AI Act, ISO 27001)
- GET /template-context Endpoint
- Migration 036

Phase 3: Dokument-Versionierung
- 5 Versions-Tabellen (DSFA, VVT, TOM, Loeschfristen, Obligations)
- Shared versioning_utils.py Helper
- /{id}/versions Endpoints auf allen 5 Dokumenttypen
- Migration 037

Phase 4: Change-Request System
- Zentrale CR-Inbox mit CRUD + Accept/Reject/Edit Workflow
- Regelbasierte CR-Engine (VVT DPIA → DSFA CR, Datenkategorien → Loeschfristen CR)
- Audit-Trail
- Migration 038

Phase 5: Dokumentengenerierung
- 5 Template-Generatoren (DSFA, VVT, TOM, Loeschfristen, Obligations)
- Preview + Apply Endpoints (erzeugt CRs, keine direkten Dokumente)

Phase 6: Frontend-Integration
- Change-Request Inbox Page mit Stats, Filtern, Modals
- VersionHistory Timeline-Komponente
- SDKSidebar CR-Badge (60s Polling)
- Company Profile: 2 neue Wizard-Steps + "Dokumente generieren" CTA

Docs: 5 neue MkDocs-Seiten, CLAUDE.md aktualisiert
Tests: 97 neue Tests (alle bestanden)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 14:12:34 +01:00

269 lines
11 KiB
Python

"""Tests for Company Profile extension (Phase 2: Stammdaten).
Verifies:
- New JSONB fields in request/response models
- template-context endpoint returns flat dict
- Regulatory booleans default correctly
"""
import pytest
from compliance.api.company_profile_routes import (
CompanyProfileRequest,
CompanyProfileResponse,
row_to_response,
)
# =============================================================================
# Schema Tests — Request Model
# =============================================================================
class TestCompanyProfileRequestExtended:
def test_default_new_fields(self):
req = CompanyProfileRequest(company_name="Acme GmbH")
assert req.repos == []
assert req.document_sources == []
assert req.processing_systems == []
assert req.ai_systems == []
assert req.technical_contacts == []
assert req.subject_to_nis2 is False
assert req.subject_to_ai_act is False
assert req.subject_to_iso27001 is False
assert req.supervisory_authority is None
assert req.review_cycle_months == 12
def test_full_new_fields(self):
req = CompanyProfileRequest(
company_name="Test AG",
repos=[{"name": "backend", "url": "https://git.example.com/backend", "language": "Python", "has_personal_data": True}],
processing_systems=[{"name": "SAP HR", "vendor": "SAP", "hosting": "cloud", "personal_data_categories": ["Mitarbeiter"]}],
ai_systems=[{"name": "Chatbot", "purpose": "Kundenservice", "risk_category": "limited", "vendor": "OpenAI", "has_human_oversight": True}],
technical_contacts=[{"role": "CISO", "name": "Max Muster", "email": "ciso@example.com"}],
subject_to_nis2=True,
subject_to_ai_act=True,
supervisory_authority="LfDI Baden-Württemberg",
review_cycle_months=6,
)
assert len(req.repos) == 1
assert req.repos[0]["language"] == "Python"
assert len(req.ai_systems) == 1
assert req.subject_to_nis2 is True
assert req.review_cycle_months == 6
def test_serialization_includes_new_fields(self):
req = CompanyProfileRequest(company_name="Test")
data = req.model_dump()
assert "repos" in data
assert "processing_systems" in data
assert "ai_systems" in data
assert "subject_to_nis2" in data
assert "review_cycle_months" in data
def test_backward_compatible(self):
"""Old-format requests (without new fields) still work."""
req = CompanyProfileRequest(
company_name="Legacy Corp",
legal_form="GmbH",
industry="Manufacturing",
)
assert req.company_name == "Legacy Corp"
assert req.repos == []
assert req.subject_to_ai_act is False
# =============================================================================
# Schema Tests — Response Model
# =============================================================================
class TestCompanyProfileResponseExtended:
def test_response_includes_new_fields(self):
resp = CompanyProfileResponse(
id="test-id",
tenant_id="test-tenant",
company_name="Test",
legal_form="GmbH",
industry="IT",
founded_year=2020,
business_model="B2B",
offerings=[],
company_size="small",
employee_count="10-49",
annual_revenue="< 2 Mio",
headquarters_country="DE",
headquarters_city="Berlin",
has_international_locations=False,
international_countries=[],
target_markets=["DE"],
primary_jurisdiction="DE",
is_data_controller=True,
is_data_processor=False,
uses_ai=True,
ai_use_cases=["chatbot"],
dpo_name="DSB",
dpo_email="dsb@test.de",
legal_contact_name=None,
legal_contact_email=None,
machine_builder=None,
is_complete=True,
completed_at="2026-01-01",
created_at="2025-12-01",
updated_at="2026-01-01",
repos=[{"name": "main"}],
ai_systems=[{"name": "Bot"}],
subject_to_ai_act=True,
review_cycle_months=6,
)
assert resp.repos == [{"name": "main"}]
assert resp.ai_systems == [{"name": "Bot"}]
assert resp.subject_to_ai_act is True
assert resp.review_cycle_months == 6
def test_response_defaults(self):
resp = CompanyProfileResponse(
id="x", tenant_id="t", company_name="X", legal_form="GmbH",
industry="", founded_year=None, business_model="B2B", offerings=[],
company_size="small", employee_count="1-9", annual_revenue="< 2 Mio",
headquarters_country="DE", headquarters_city="",
has_international_locations=False, international_countries=[],
target_markets=["DE"], primary_jurisdiction="DE",
is_data_controller=True, is_data_processor=False,
uses_ai=False, ai_use_cases=[], dpo_name=None, dpo_email=None,
legal_contact_name=None, legal_contact_email=None,
machine_builder=None, is_complete=False,
completed_at=None, created_at="2026-01-01", updated_at="2026-01-01",
)
assert resp.repos == []
assert resp.processing_systems == []
assert resp.subject_to_nis2 is False
assert resp.review_cycle_months == 12
# =============================================================================
# row_to_response — extended column mapping
# =============================================================================
class TestRowToResponseExtended:
def _make_row(self, **overrides):
"""Build a 40-element tuple matching the SQL column order."""
base = [
"uuid-1", # 0: id
"tenant-1", # 1: tenant_id
"Acme GmbH", # 2: company_name
"GmbH", # 3: legal_form
"IT", # 4: industry
2020, # 5: founded_year
"B2B", # 6: business_model
["SaaS"], # 7: offerings
"medium", # 8: company_size
"50-249", # 9: employee_count
"2-10 Mio", # 10: annual_revenue
"DE", # 11: headquarters_country
"München", # 12: headquarters_city
False, # 13: has_international_locations
[], # 14: international_countries
["DE", "AT"], # 15: target_markets
"DE", # 16: primary_jurisdiction
True, # 17: is_data_controller
False, # 18: is_data_processor
True, # 19: uses_ai
["chatbot"], # 20: ai_use_cases
"DSB Person", # 21: dpo_name
"dsb@acme.de", # 22: dpo_email
None, # 23: legal_contact_name
None, # 24: legal_contact_email
None, # 25: machine_builder
True, # 26: is_complete
"2026-01-15", # 27: completed_at
"2025-12-01", # 28: created_at
"2026-01-15", # 29: updated_at
# Phase 2 fields
[{"name": "repo1"}], # 30: repos
[{"type": "policy", "title": "Privacy Policy"}], # 31: document_sources
[{"name": "SAP", "vendor": "SAP"}], # 32: processing_systems
[{"name": "Bot", "risk_category": "limited"}], # 33: ai_systems
[{"role": "CISO", "name": "Max"}], # 34: technical_contacts
True, # 35: subject_to_nis2
True, # 36: subject_to_ai_act
False, # 37: subject_to_iso27001
"LfDI BW", # 38: supervisory_authority
6, # 39: review_cycle_months
]
return tuple(base)
def test_maps_new_fields(self):
row = self._make_row()
resp = row_to_response(row)
assert resp.repos == [{"name": "repo1"}]
assert resp.document_sources[0]["type"] == "policy"
assert resp.processing_systems[0]["name"] == "SAP"
assert resp.ai_systems[0]["risk_category"] == "limited"
assert resp.technical_contacts[0]["role"] == "CISO"
assert resp.subject_to_nis2 is True
assert resp.subject_to_ai_act is True
assert resp.subject_to_iso27001 is False
assert resp.supervisory_authority == "LfDI BW"
assert resp.review_cycle_months == 6
def test_null_new_fields_default_gracefully(self):
base = list(self._make_row())
# Set new fields to None
for i in range(30, 40):
base[i] = None
row = tuple(base)
resp = row_to_response(row)
assert resp.repos == []
assert resp.processing_systems == []
assert resp.ai_systems == []
assert resp.subject_to_nis2 is False
assert resp.supervisory_authority is None
assert resp.review_cycle_months == 12
def test_old_fields_still_work(self):
row = self._make_row()
resp = row_to_response(row)
assert resp.company_name == "Acme GmbH"
assert resp.industry == "IT"
assert resp.is_complete is True
assert resp.dpo_name == "DSB Person"
# =============================================================================
# Template Context Tests
# =============================================================================
class TestTemplateContext:
def test_template_context_from_response(self):
"""Simulate what template-context endpoint returns."""
resp = CompanyProfileResponse(
id="x", tenant_id="t", company_name="Test Corp", legal_form="AG",
industry="Finance", founded_year=2015, business_model="B2C",
offerings=["Banking"], company_size="large", employee_count="1000+",
annual_revenue="> 50 Mio", headquarters_country="DE",
headquarters_city="Frankfurt", has_international_locations=True,
international_countries=["CH", "AT"], target_markets=["DE", "CH", "AT"],
primary_jurisdiction="DE", is_data_controller=True,
is_data_processor=True, uses_ai=True, ai_use_cases=["scoring"],
dpo_name="Dr. Privacy", dpo_email="dpo@test.de",
legal_contact_name="Legal Team", legal_contact_email="legal@test.de",
machine_builder=None, is_complete=True,
completed_at="2026-01-01", created_at="2025-06-01",
updated_at="2026-01-01",
ai_systems=[{"name": "Scoring Engine", "risk_category": "high"}],
subject_to_ai_act=True, subject_to_nis2=True,
review_cycle_months=3,
)
# Build context dict same as endpoint does
ctx = {
"company_name": resp.company_name,
"dpo_name": resp.dpo_name or "",
"uses_ai": resp.uses_ai,
"ai_systems": resp.ai_systems,
"has_ai_systems": len(resp.ai_systems) > 0,
"subject_to_ai_act": resp.subject_to_ai_act,
"review_cycle_months": resp.review_cycle_months,
}
assert ctx["company_name"] == "Test Corp"
assert ctx["has_ai_systems"] is True
assert ctx["subject_to_ai_act"] is True
assert ctx["review_cycle_months"] == 3