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
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>
281 lines
7.5 KiB
Go
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")
|
|
}
|
|
}
|