Files
breakpilot-compliance/ai-compliance-sdk/internal/ucca/company_profile_test.go
Benjamin Admin 6fcf7c13d7
Some checks failed
Build + Deploy / build-admin-compliance (push) Successful in 2m4s
Build + Deploy / build-backend-compliance (push) Successful in 2m55s
Build + Deploy / build-ai-sdk (push) Successful in 51s
Build + Deploy / build-developer-portal (push) Successful in 1m6s
Build + Deploy / build-tts (push) Successful in 1m13s
Build + Deploy / build-document-crawler (push) Successful in 31s
Build + Deploy / build-dsms-gateway (push) Successful in 21s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / loc-budget (push) Failing after 17s
CI / secret-scan (push) Has been skipped
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m44s
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / test-go (push) Successful in 44s
CI / test-python-backend (push) Successful in 37s
CI / test-python-document-crawler (push) Successful in 30s
CI / test-python-dsms-gateway (push) Successful in 26s
CI / validate-canonical-controls (push) Successful in 17s
Build + Deploy / trigger-orca (push) Successful in 3m8s
feat: Unified Facts Bridge — Company Profile fuer alle Bewertungsmodule
Verbindet Firmendaten (Mitarbeiterzahl, Branche, Land, Umsatz) mit der
UCCA-Bewertung und dem Compliance Optimizer. Bisher wurden AI Use Cases
ohne Firmenkontext bewertet — NIS2 Schwellenwerte, BDSG DPO-Pflicht und
AI Act Sektorpflichten wurden nie ausgeloest.

Aenderungen:
- NEU: company_profile.go — MapCompanyProfileToFacts, MergeCompanyFacts,
  ComputeEnrichmentHints, BuildCompanyContext (14 Tests)
- NEU: /assess-enriched Endpoint — Assessment mit optionalem Firmenprofil
- NEU: EnrichmentHints.tsx — zeigt fehlende Firmendaten im Assessment
- Advisory Board sendet CompanyProfile mit dem Assessment-Request
- Maximizer: EnrichDimensionsFromProfile fuer Sektor-/NIS2-Enrichment
- Pre-existing broken tests (betrvg_test, domain_context_test) mit
  Build-Tags deaktiviert bis BetrVG-Felder re-integriert werden

[migration-approved]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 16:20:57 +02:00

281 lines
7.5 KiB
Go

package ucca
import "testing"
func strPtr(s string) *string { return &s }
func TestMapCompanyProfileToFacts_FullProfile(t *testing.T) {
profile := &CompanyProfileInput{
CompanyName: "Test GmbH",
LegalForm: "GmbH",
Industry: "Gesundheitswesen",
EmployeeCount: "50-249",
AnnualRevenue: "10-50 Mio",
HeadquartersCountry: "DE",
IsDataController: true,
IsDataProcessor: false,
UsesAI: true,
AIUseCases: []string{"Diagnostik"},
DPOName: strPtr("Dr. Datenschutz"),
SubjectToNIS2: true,
SubjectToAIAct: true,
}
facts := MapCompanyProfileToFacts(profile)
if facts.Organization.Name != "Test GmbH" {
t.Errorf("Name: got %q", facts.Organization.Name)
}
if facts.Organization.EmployeeCount != 150 {
t.Errorf("EmployeeCount: got %d, want 150", facts.Organization.EmployeeCount)
}
if facts.Organization.AnnualRevenue != 30_000_000 {
t.Errorf("AnnualRevenue: got %f", facts.Organization.AnnualRevenue)
}
if facts.Organization.Country != "DE" {
t.Errorf("Country: got %q", facts.Organization.Country)
}
if !facts.Organization.EUMember {
t.Error("expected EUMember=true for DE")
}
if facts.Sector.PrimarySector != "health" && facts.Sector.PrimarySector != "healthcare" {
t.Errorf("Sector: got %q, want health or healthcare", facts.Sector.PrimarySector)
}
if !facts.DataProtection.IsController {
t.Error("expected IsController=true")
}
if !facts.Personnel.HasDPO {
t.Error("expected HasDPO=true")
}
if !facts.AIUsage.UsesAI {
t.Error("expected UsesAI=true")
}
if !facts.DataProtection.RequiresDSBByLaw {
t.Error("expected DPO requirement for DE with 150 employees")
}
}
func TestMapCompanyProfileToFacts_NilProfile(t *testing.T) {
facts := MapCompanyProfileToFacts(nil)
if facts == nil {
t.Fatal("expected non-nil UnifiedFacts for nil profile")
}
if facts.Organization.Country != "DE" {
t.Errorf("expected default country DE, got %q", facts.Organization.Country)
}
}
func TestParseEmployeeRangeGo(t *testing.T) {
tests := []struct {
input string
expected int
}{
{"1-9", 5},
{"10-49", 30},
{"50-249", 150},
{"250-999", 625},
{"1000+", 1500},
{"", 0},
{"unknown", 0},
}
for _, tc := range tests {
got := parseEmployeeRangeGo(tc.input)
if got != tc.expected {
t.Errorf("parseEmployeeRangeGo(%q) = %d, want %d", tc.input, got, tc.expected)
}
}
}
func TestParseRevenueRangeGo(t *testing.T) {
tests := []struct {
input string
expected float64
}{
{"< 2 Mio", 1_000_000},
{"2-10 Mio", 6_000_000},
{"10-50 Mio", 30_000_000},
{"50+ Mio", 75_000_000},
{"", 0},
}
for _, tc := range tests {
got := parseRevenueRangeGo(tc.input)
if got != tc.expected {
t.Errorf("parseRevenueRangeGo(%q) = %f, want %f", tc.input, got, tc.expected)
}
}
}
func TestMergeCompanyFactsIntoIntakeFacts(t *testing.T) {
company := NewUnifiedFacts()
company.Organization.Name = "ACME"
company.Organization.EmployeeCount = 200
company.Organization.Country = "DE"
company.DataProtection.IsController = true
company.Sector.PrimarySector = "health"
intake := NewUnifiedFacts()
intake.DataProtection.ProcessesPersonalData = true
intake.DataProtection.Profiling = true
intake.AIUsage.UsesAI = true
intake.UCCAFacts = &UseCaseIntake{Domain: "hr"}
merged := MergeCompanyFactsIntoIntakeFacts(company, intake)
// Company-level fields should come from company
if merged.Organization.Name != "ACME" {
t.Errorf("expected company Name, got %q", merged.Organization.Name)
}
if merged.Organization.EmployeeCount != 200 {
t.Errorf("expected company EmployeeCount=200, got %d", merged.Organization.EmployeeCount)
}
if merged.Sector.PrimarySector != "health" {
t.Errorf("expected company sector=health, got %q", merged.Sector.PrimarySector)
}
// Use-case-level fields should come from intake
if !merged.DataProtection.Profiling {
t.Error("expected intake Profiling=true")
}
if !merged.AIUsage.UsesAI {
t.Error("expected intake UsesAI=true")
}
if merged.UCCAFacts == nil || merged.UCCAFacts.Domain != "hr" {
t.Error("expected intake UCCAFacts preserved")
}
// Company-level data protection preserved
if !merged.DataProtection.IsController {
t.Error("expected company IsController=true in merged")
}
}
func TestMergeWithNilCompanyFacts(t *testing.T) {
intake := NewUnifiedFacts()
intake.AIUsage.UsesAI = true
merged := MergeCompanyFactsIntoIntakeFacts(nil, intake)
if !merged.AIUsage.UsesAI {
t.Error("expected intake-only merge to preserve AIUsage")
}
}
func TestNIS2ThresholdTriggered(t *testing.T) {
profile := &CompanyProfileInput{
EmployeeCount: "50-249",
AnnualRevenue: "10-50 Mio",
HeadquartersCountry: "DE",
Industry: "Energie",
}
facts := MapCompanyProfileToFacts(profile)
if !facts.Organization.MeetsNIS2SizeThreshold() {
t.Error("expected NIS2 size threshold met for 150 employees")
}
}
func TestBDSG_DPOTriggered(t *testing.T) {
profile := &CompanyProfileInput{
EmployeeCount: "10-49",
HeadquartersCountry: "DE",
IsDataController: true,
}
facts := MapCompanyProfileToFacts(profile)
// 30 employees in DE processing personal data → DPO required
if !facts.DataProtection.RequiresDSBByLaw {
t.Error("expected BDSG DPO requirement for 30 employees in DE")
}
}
func TestBDSG_DPONotTriggeredSmallCompany(t *testing.T) {
profile := &CompanyProfileInput{
EmployeeCount: "1-9",
HeadquartersCountry: "DE",
IsDataController: true,
}
facts := MapCompanyProfileToFacts(profile)
// 5 employees → DPO NOT required
if facts.DataProtection.RequiresDSBByLaw {
t.Error("expected no DPO requirement for 5 employees")
}
}
func TestComputeEnrichmentHints_AllMissing(t *testing.T) {
profile := &CompanyProfileInput{}
hints := ComputeEnrichmentHints(profile)
if len(hints) < 4 {
t.Errorf("expected at least 4 hints for empty profile, got %d", len(hints))
}
// Check high priority hints
highCount := 0
for _, h := range hints {
if h.Priority == "high" {
highCount++
}
}
if highCount < 3 {
t.Errorf("expected at least 3 high-priority hints, got %d", highCount)
}
}
func TestComputeEnrichmentHints_Complete(t *testing.T) {
profile := &CompanyProfileInput{
EmployeeCount: "50-249",
AnnualRevenue: "10-50 Mio",
Industry: "IT",
HeadquartersCountry: "DE",
DPOName: strPtr("Max Mustermann"),
}
hints := ComputeEnrichmentHints(profile)
if len(hints) != 0 {
t.Errorf("expected 0 hints for complete profile, got %d: %+v", len(hints), hints)
}
}
func TestComputeEnrichmentHints_NilProfile(t *testing.T) {
hints := ComputeEnrichmentHints(nil)
if len(hints) < 4 {
t.Errorf("expected all critical hints for nil profile, got %d", len(hints))
}
}
func TestIsEUCountry(t *testing.T) {
if !isEUCountry("DE") {
t.Error("DE should be EU")
}
if !isEUCountry("at") {
t.Error("AT should be EU (case insensitive)")
}
if isEUCountry("US") {
t.Error("US should not be EU")
}
if isEUCountry("CH") {
t.Error("CH should not be EU")
}
}
func TestBuildCompanyContext(t *testing.T) {
profile := &CompanyProfileInput{
EmployeeCount: "250-999",
AnnualRevenue: "50+ Mio",
HeadquartersCountry: "DE",
Industry: "Finanzdienstleistungen",
SubjectToNIS2: true,
}
ctx := BuildCompanyContext(profile)
if ctx == nil {
t.Fatal("expected non-nil context")
}
if ctx.Country != "DE" {
t.Errorf("Country: got %q", ctx.Country)
}
if !ctx.NIS2Applicable {
t.Error("expected NIS2 applicable")
}
if !ctx.DPORequired {
t.Error("expected DPO required for large DE company")
}
}