"""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