All checks were successful
CI/CD / go-lint (push) Has been skipped
CI/CD / python-lint (push) Has been skipped
CI/CD / nodejs-lint (push) Has been skipped
CI/CD / test-go-ai-compliance (push) Successful in 41s
CI/CD / test-python-backend-compliance (push) Successful in 31s
CI/CD / test-python-document-crawler (push) Successful in 21s
CI/CD / test-python-dsms-gateway (push) Successful in 16s
CI/CD / validate-canonical-controls (push) Successful in 10s
CI/CD / Deploy (push) Successful in 4s
Tests were failing due to stale mock objects after schema extensions: - DSFA: add _mapping property to _DictRow, use proper mock instead of MagicMock - Company Profile: add 6 missing fields (project_id, offering_urls, etc.) - Legal Templates/Policy: update document type count 52→58 - VVT: add 13 missing attributes to activity mock - Legal Documents: align consent test assertions with production behavior Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
276 lines
12 KiB
Python
276 lines
12 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 46-element tuple matching _BASE_COLUMNS_LIST 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
|
|
# Additional fields
|
|
None, # 40: project_id
|
|
{}, # 41: offering_urls
|
|
"", # 42: headquarters_country_other
|
|
"", # 43: headquarters_street
|
|
"", # 44: headquarters_zip
|
|
"", # 45: headquarters_state
|
|
]
|
|
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
|