fix+test+docs: Archivierte Projekte, Vitest-Tests & Regulations-Doku
Some checks failed
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) Failing after 38s
CI / test-python-backend-compliance (push) Successful in 39s
CI / test-python-document-crawler (push) Successful in 26s
CI / test-python-dsms-gateway (push) Successful in 21s
Some checks failed
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) Failing after 38s
CI / test-python-backend-compliance (push) Successful in 39s
CI / test-python-document-crawler (push) Successful in 26s
CI / test-python-dsms-gateway (push) Successful in 21s
- fix(ProjectSelector): Archivierte Projekte anklickbar machen, doppelten "Neues Projekt" Button entfernen - test: 32 Vitest-Tests fuer scope-to-facts und supervisory-authority-resolver - docs(flow-data): Scope-Step outputs + Obligations inputs erweitert - docs(developer-portal): Feature-Highlight "Automatische Regulierungs-Ableitung" - docs(mkdocs): Neuer Abschnitt Regulierungs-Ableitung in obligations.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
193
admin-compliance/lib/sdk/__tests__/scope-to-facts.test.ts
Normal file
193
admin-compliance/lib/sdk/__tests__/scope-to-facts.test.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import {
|
||||
parseEmployeeRange,
|
||||
parseRevenueRange,
|
||||
buildAssessmentPayload,
|
||||
} from '../scope-to-facts'
|
||||
import type { CompanyProfile } from '../types'
|
||||
import type { ScopeProfilingAnswer, ScopeDecision } from '../compliance-scope-types'
|
||||
|
||||
// =============================================================================
|
||||
// parseEmployeeRange
|
||||
// =============================================================================
|
||||
|
||||
describe('parseEmployeeRange', () => {
|
||||
it('returns 5 for "1-9"', () => {
|
||||
expect(parseEmployeeRange('1-9')).toBe(5)
|
||||
})
|
||||
|
||||
it('returns 30 for "10-49"', () => {
|
||||
expect(parseEmployeeRange('10-49')).toBe(30)
|
||||
})
|
||||
|
||||
it('returns 150 for "50-249"', () => {
|
||||
expect(parseEmployeeRange('50-249')).toBe(150)
|
||||
})
|
||||
|
||||
it('returns 625 for "250-999"', () => {
|
||||
expect(parseEmployeeRange('250-999')).toBe(625)
|
||||
})
|
||||
|
||||
it('returns 1500 for "1000+"', () => {
|
||||
expect(parseEmployeeRange('1000+')).toBe(1500)
|
||||
})
|
||||
|
||||
it('returns 10 for null', () => {
|
||||
expect(parseEmployeeRange(null)).toBe(10)
|
||||
})
|
||||
|
||||
it('returns 10 for undefined', () => {
|
||||
expect(parseEmployeeRange(undefined)).toBe(10)
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================================================
|
||||
// parseRevenueRange
|
||||
// =============================================================================
|
||||
|
||||
describe('parseRevenueRange', () => {
|
||||
it('returns 1000000 for "< 2 Mio"', () => {
|
||||
expect(parseRevenueRange('< 2 Mio')).toBe(1000000)
|
||||
})
|
||||
|
||||
it('returns 6000000 for "2-10 Mio"', () => {
|
||||
expect(parseRevenueRange('2-10 Mio')).toBe(6000000)
|
||||
})
|
||||
|
||||
it('returns 30000000 for "10-50 Mio"', () => {
|
||||
expect(parseRevenueRange('10-50 Mio')).toBe(30000000)
|
||||
})
|
||||
|
||||
it('returns 75000000 for "> 50 Mio"', () => {
|
||||
expect(parseRevenueRange('> 50 Mio')).toBe(75000000)
|
||||
})
|
||||
|
||||
it('returns 1000000 for null', () => {
|
||||
expect(parseRevenueRange(null)).toBe(1000000)
|
||||
})
|
||||
|
||||
it('returns 1000000 for undefined', () => {
|
||||
expect(parseRevenueRange(undefined)).toBe(1000000)
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================================================
|
||||
// buildAssessmentPayload
|
||||
// =============================================================================
|
||||
|
||||
describe('buildAssessmentPayload', () => {
|
||||
const baseProfile: CompanyProfile = {
|
||||
companyName: 'Test GmbH',
|
||||
legalForm: 'GmbH',
|
||||
industry: ['IT', 'Software'],
|
||||
employeeCount: '50-249',
|
||||
annualRevenue: '10-50 Mio',
|
||||
headquartersCountry: 'DE',
|
||||
headquartersState: 'BW',
|
||||
isDataController: true,
|
||||
isDataProcessor: false,
|
||||
offerings: ['software_saas'],
|
||||
}
|
||||
|
||||
const baseAnswers: ScopeProfilingAnswer[] = [
|
||||
{ questionId: 'data_art9', value: false, blockId: 'data' },
|
||||
{ questionId: 'data_minors', value: false, blockId: 'data' },
|
||||
{ questionId: 'data_hr', value: true, blockId: 'data' },
|
||||
{ questionId: 'data_financial', value: false, blockId: 'data' },
|
||||
{ questionId: 'tech_third_country', value: true, blockId: 'tech' },
|
||||
{ questionId: 'tech_subprocessors', value: true, blockId: 'tech' },
|
||||
{ questionId: 'proc_adm_scoring', value: false, blockId: 'processing' },
|
||||
{ questionId: 'proc_employee_monitoring', value: false, blockId: 'processing' },
|
||||
{ questionId: 'proc_video_surveillance', value: false, blockId: 'processing' },
|
||||
{ questionId: 'proc_tracking', value: false, blockId: 'processing' },
|
||||
{ questionId: 'prod_cookies_consent', value: true, blockId: 'product' },
|
||||
{ questionId: 'data_volume', value: false, blockId: 'data' },
|
||||
{ questionId: 'ai_uses_ai', value: true, blockId: 'ai' },
|
||||
{ questionId: 'ai_categories', value: ['ai_provider'], blockId: 'ai' },
|
||||
{ questionId: 'ai_risk_assessment', value: 'limited', blockId: 'ai' },
|
||||
{ questionId: 'org_cert_target', value: 'iso27001', blockId: 'organisation' },
|
||||
]
|
||||
|
||||
it('maps a full profile correctly', () => {
|
||||
const payload = buildAssessmentPayload(baseProfile, baseAnswers, null)
|
||||
|
||||
expect(payload.employee_count).toBe(150)
|
||||
expect(payload.annual_revenue).toBe(30000000)
|
||||
expect(payload.country).toBe('DE')
|
||||
expect(payload.industry).toBe('IT, Software')
|
||||
expect(payload.legal_form).toBe('GmbH')
|
||||
expect(payload.is_controller).toBe(true)
|
||||
expect(payload.is_processor).toBe(false)
|
||||
expect(payload.cross_border_transfer).toBe(true)
|
||||
expect(payload.uses_processors).toBe(true)
|
||||
expect(payload.uses_cookies).toBe(true)
|
||||
expect(payload.processes_employee_data).toBe(true)
|
||||
expect(payload.operates_platform).toBe(true)
|
||||
expect(payload.proc_ai_usage).toBe(true)
|
||||
expect(payload.cert_target).toBe('iso27001')
|
||||
})
|
||||
|
||||
it('uses defaults for null/undefined profile fields', () => {
|
||||
const emptyProfile: CompanyProfile = {
|
||||
companyName: 'Minimal',
|
||||
}
|
||||
const payload = buildAssessmentPayload(emptyProfile, [], null)
|
||||
|
||||
expect(payload.employee_count).toBe(10) // parseEmployeeRange(undefined)
|
||||
expect(payload.annual_revenue).toBe(1000000)
|
||||
expect(payload.country).toBe('DE') // default
|
||||
expect(payload.industry).toBe('')
|
||||
expect(payload.legal_form).toBe('')
|
||||
expect(payload.is_controller).toBe(true) // default
|
||||
expect(payload.is_processor).toBe(false) // default
|
||||
expect(payload.determined_level).toBe('L2') // default
|
||||
})
|
||||
|
||||
it('detects AI provider from ai_categories', () => {
|
||||
const payload = buildAssessmentPayload(baseProfile, baseAnswers, null)
|
||||
|
||||
expect(payload.is_ai_provider).toBe(true)
|
||||
expect(payload.is_ai_deployer).toBe(false)
|
||||
expect(payload.limited_risk_ai).toBe(true)
|
||||
expect(payload.high_risk_ai).toBe(false)
|
||||
})
|
||||
|
||||
it('detects AI deployer from ai_categories', () => {
|
||||
const deployerAnswers = baseAnswers.map(a =>
|
||||
a.questionId === 'ai_categories'
|
||||
? { ...a, value: ['ai_deployer'] }
|
||||
: a
|
||||
)
|
||||
const payload = buildAssessmentPayload(baseProfile, deployerAnswers, null)
|
||||
|
||||
expect(payload.is_ai_provider).toBe(false)
|
||||
expect(payload.is_ai_deployer).toBe(true)
|
||||
})
|
||||
|
||||
it('detects financial institution from industry', () => {
|
||||
const finProfile: CompanyProfile = {
|
||||
...baseProfile,
|
||||
industry: ['Finanzdienstleistungen', 'Banking'],
|
||||
}
|
||||
const payload = buildAssessmentPayload(finProfile, baseAnswers, null)
|
||||
|
||||
expect(payload.is_financial_institution).toBe(true)
|
||||
})
|
||||
|
||||
it('includes decision data when provided', () => {
|
||||
const decision: ScopeDecision = {
|
||||
determinedLevel: 'L3',
|
||||
triggeredHardTriggers: [
|
||||
{ rule: { id: 'rule-1', name: 'Test Rule', description: '', targetLevel: 'L3', trigger: { field: '', op: 'eq', value: true } }, factValue: true },
|
||||
],
|
||||
requiredDocuments: [
|
||||
{ documentType: 'dsfa', reason: 'test', regulation: 'dsgvo' },
|
||||
],
|
||||
} as any
|
||||
const payload = buildAssessmentPayload(baseProfile, baseAnswers, decision)
|
||||
|
||||
expect(payload.determined_level).toBe('L3')
|
||||
expect(payload.triggered_rules).toEqual(['rule-1'])
|
||||
expect(payload.required_documents).toEqual(['dsfa'])
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,105 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { resolveAuthorities } from '../supervisory-authority-resolver'
|
||||
|
||||
// =============================================================================
|
||||
// Datenschutz-Aufsichtsbehoerde (DSGVO)
|
||||
// =============================================================================
|
||||
|
||||
describe('resolveAuthorities — Datenschutz', () => {
|
||||
it('resolves DE + BW to LfDI BW', () => {
|
||||
const results = resolveAuthorities('BW', 'DE', ['dsgvo'])
|
||||
const dp = results.find(r => r.domain === 'Datenschutz')
|
||||
expect(dp).toBeDefined()
|
||||
expect(dp!.authority.abbreviation).toBe('LfDI BW')
|
||||
})
|
||||
|
||||
it('resolves DE + BY to BayLDA', () => {
|
||||
const results = resolveAuthorities('BY', 'DE', ['dsgvo'])
|
||||
const dp = results.find(r => r.domain === 'Datenschutz')
|
||||
expect(dp).toBeDefined()
|
||||
expect(dp!.authority.abbreviation).toBe('BayLDA')
|
||||
})
|
||||
|
||||
it('resolves DE without state to BfDI', () => {
|
||||
const results = resolveAuthorities(undefined, 'DE', ['dsgvo'])
|
||||
const dp = results.find(r => r.domain === 'Datenschutz')
|
||||
expect(dp).toBeDefined()
|
||||
expect(dp!.authority.abbreviation).toBe('BfDI')
|
||||
})
|
||||
|
||||
it('resolves AT to DSB AT', () => {
|
||||
const results = resolveAuthorities(undefined, 'AT', ['dsgvo'])
|
||||
const dp = results.find(r => r.domain === 'Datenschutz')
|
||||
expect(dp).toBeDefined()
|
||||
expect(dp!.authority.abbreviation).toBe('DSB AT')
|
||||
})
|
||||
|
||||
it('resolves CH to EDOEB', () => {
|
||||
const results = resolveAuthorities(undefined, 'CH', ['dsgvo'])
|
||||
const dp = results.find(r => r.domain === 'Datenschutz')
|
||||
expect(dp).toBeDefined()
|
||||
expect(dp!.authority.abbreviation).toBe('EDOEB')
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================================================
|
||||
// Regulierungs-spezifische Behoerden
|
||||
// =============================================================================
|
||||
|
||||
describe('resolveAuthorities — regulation-specific', () => {
|
||||
it('includes BSI for NIS2 in DE', () => {
|
||||
const results = resolveAuthorities('BW', 'DE', ['dsgvo', 'nis2'])
|
||||
const nis2 = results.find(r => r.domain.includes('NIS2'))
|
||||
expect(nis2).toBeDefined()
|
||||
expect(nis2!.authority.abbreviation).toBe('BSI')
|
||||
})
|
||||
|
||||
it('includes BaFin for financial_policy in DE', () => {
|
||||
const results = resolveAuthorities('BW', 'DE', ['dsgvo', 'financial_policy'])
|
||||
const fin = results.find(r => r.domain.includes('Finanzaufsicht'))
|
||||
expect(fin).toBeDefined()
|
||||
expect(fin!.authority.abbreviation).toBe('BaFin')
|
||||
})
|
||||
|
||||
it('includes BNetzA for ai_act in DE', () => {
|
||||
const results = resolveAuthorities('BW', 'DE', ['dsgvo', 'ai_act'])
|
||||
const ai = results.find(r => r.domain.includes('KI-Aufsicht'))
|
||||
expect(ai).toBeDefined()
|
||||
expect(ai!.authority.abbreviation).toBe('BNetzA')
|
||||
})
|
||||
|
||||
it('includes NCSA for NIS2 outside DE', () => {
|
||||
const results = resolveAuthorities(undefined, 'AT', ['dsgvo', 'nis2'])
|
||||
const nis2 = results.find(r => r.domain.includes('NIS2'))
|
||||
expect(nis2).toBeDefined()
|
||||
expect(nis2!.authority.abbreviation).toBe('NCSA')
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================================================
|
||||
// Edge Cases
|
||||
// =============================================================================
|
||||
|
||||
describe('resolveAuthorities — edge cases', () => {
|
||||
it('returns empty array when no regulations', () => {
|
||||
const results = resolveAuthorities('BW', 'DE', [])
|
||||
expect(results).toEqual([])
|
||||
})
|
||||
|
||||
it('returns multiple authorities for multiple regulations', () => {
|
||||
const results = resolveAuthorities('BW', 'DE', ['dsgvo', 'nis2', 'ai_act', 'financial_policy'])
|
||||
expect(results.length).toBe(4)
|
||||
})
|
||||
|
||||
it('does not include BaFin for non-DE financial_policy', () => {
|
||||
const results = resolveAuthorities(undefined, 'AT', ['financial_policy'])
|
||||
const fin = results.find(r => r.domain.includes('Finanzaufsicht'))
|
||||
expect(fin).toBeUndefined()
|
||||
})
|
||||
|
||||
it('does not include BNetzA for non-DE ai_act', () => {
|
||||
const results = resolveAuthorities(undefined, 'AT', ['ai_act'])
|
||||
const ai = results.find(r => r.domain.includes('KI-Aufsicht'))
|
||||
expect(ai).toBeUndefined()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user