Files
Benjamin Admin 4f6bc8f6f6
Some checks failed
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) Failing after 37s
CI/CD / test-python-backend-compliance (push) Successful in 39s
CI/CD / test-python-document-crawler (push) Successful in 26s
CI/CD / test-python-dsms-gateway (push) Successful in 23s
CI/CD / validate-canonical-controls (push) Successful in 12s
CI/CD / Deploy (push) Has been skipped
feat(training+controls): interactive video pipeline, training blocks, control generator, CE libraries
Interactive Training Videos (CP-TRAIN):
- DB migration 022: training_checkpoints + checkpoint_progress tables
- NarratorScript generation via Anthropic (AI Teacher persona, German)
- TTS batch synthesis + interactive video pipeline (slides + checkpoint slides + FFmpeg)
- 4 new API endpoints: generate-interactive, interactive-manifest, checkpoint submit, checkpoint progress
- InteractiveVideoPlayer component (HTML5 Video, quiz overlay, seek protection, progress tracking)
- Learner portal integration with automatic completion on all checkpoints passed
- 30 new tests (handler validation + grading logic + manifest/progress + seek protection)

Training Blocks:
- Block generator, block store, block config CRUD + preview/generate endpoints
- Migration 021: training_blocks schema

Control Generator + Canonical Library:
- Control generator routes + service enhancements
- Canonical control library helpers, sidebar entry
- Citation backfill service + tests
- CE libraries data (hazard, protection, evidence, lifecycle, components)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 21:41:48 +01:00

225 lines
5.8 KiB
Go

package training
import (
"testing"
)
func TestChunkControls_EmptySlice(t *testing.T) {
chunks := chunkControls(nil, 20)
if len(chunks) != 0 {
t.Errorf("expected 0 chunks, got %d", len(chunks))
}
}
func TestChunkControls_SingleChunk(t *testing.T) {
controls := make([]CanonicalControlSummary, 5)
for i := range controls {
controls[i].ControlID = "CTRL-" + string(rune('A'+i))
}
chunks := chunkControls(controls, 20)
if len(chunks) != 1 {
t.Errorf("expected 1 chunk, got %d", len(chunks))
}
if len(chunks[0]) != 5 {
t.Errorf("expected 5 controls in chunk, got %d", len(chunks[0]))
}
}
func TestChunkControls_MultipleChunks(t *testing.T) {
controls := make([]CanonicalControlSummary, 25)
for i := range controls {
controls[i].ControlID = "CTRL-" + string(rune('A'+i%26))
}
chunks := chunkControls(controls, 10)
if len(chunks) != 3 {
t.Errorf("expected 3 chunks, got %d", len(chunks))
}
if len(chunks[0]) != 10 {
t.Errorf("expected 10 in first chunk, got %d", len(chunks[0]))
}
if len(chunks[2]) != 5 {
t.Errorf("expected 5 in last chunk, got %d", len(chunks[2]))
}
}
func TestChunkControls_ExactMultiple(t *testing.T) {
controls := make([]CanonicalControlSummary, 20)
chunks := chunkControls(controls, 10)
if len(chunks) != 2 {
t.Errorf("expected 2 chunks, got %d", len(chunks))
}
}
func TestBlockGenerator_DeriveRoles_EnterpriseAudience(t *testing.T) {
bg := &BlockGenerator{}
controls := []CanonicalControlSummary{
{ControlID: "AUTH-001", Category: "authentication", TargetAudience: "enterprise"},
}
roles := bg.deriveRoles(controls, "enterprise")
// Should include enterprise mapping roles
roleSet := map[string]bool{}
for _, r := range roles {
roleSet[r] = true
}
// Enterprise maps to R1, R4, R5, R6, R7, R9
if !roleSet[RoleR1] {
t.Error("expected R1 for enterprise audience")
}
if !roleSet[RoleR9] {
t.Error("expected R9 for enterprise audience")
}
}
func TestBlockGenerator_DeriveRoles_AuthorityAudience(t *testing.T) {
bg := &BlockGenerator{}
controls := []CanonicalControlSummary{
{ControlID: "GOV-001", Category: "governance", TargetAudience: "authority"},
}
roles := bg.deriveRoles(controls, "authority")
roleSet := map[string]bool{}
for _, r := range roles {
roleSet[r] = true
}
// Authority maps to R10
if !roleSet[RoleR10] {
t.Error("expected R10 for authority audience")
}
}
func TestBlockGenerator_DeriveRoles_NoFilter(t *testing.T) {
bg := &BlockGenerator{}
controls := []CanonicalControlSummary{
{ControlID: "ENC-001", Category: "encryption", TargetAudience: "provider"},
}
roles := bg.deriveRoles(controls, "")
roleSet := map[string]bool{}
for _, r := range roles {
roleSet[r] = true
}
// Without audience filter, should use per-control audience + category
// encryption → R2, R8
// provider → R2, R8
if !roleSet[RoleR2] {
t.Error("expected R2 from encryption category + provider audience")
}
if !roleSet[RoleR8] {
t.Error("expected R8 from encryption category + provider audience")
}
}
func TestBlockGenerator_DeriveRoles_DefaultToR9(t *testing.T) {
bg := &BlockGenerator{}
controls := []CanonicalControlSummary{
{ControlID: "UNK-001", Category: "", TargetAudience: ""},
}
roles := bg.deriveRoles(controls, "")
if len(roles) != 1 || roles[0] != RoleR9 {
t.Errorf("expected [R9] default, got %v", roles)
}
}
func TestBlockGenerator_ExtractControlIDs(t *testing.T) {
bg := &BlockGenerator{}
controls := []CanonicalControlSummary{
{ControlID: "AUTH-001"},
{ControlID: "AUTH-002"},
{ControlID: "ENC-010"},
}
ids := bg.extractControlIDs(controls)
if len(ids) != 3 {
t.Errorf("expected 3 IDs, got %d", len(ids))
}
if ids[0] != "AUTH-001" || ids[1] != "AUTH-002" || ids[2] != "ENC-010" {
t.Errorf("unexpected IDs: %v", ids)
}
}
func TestBlockGenerator_BuildModuleTitle_SinglePart(t *testing.T) {
bg := &BlockGenerator{}
config := &TrainingBlockConfig{Name: "Authentifizierung"}
controls := []CanonicalControlSummary{{ControlID: "AUTH-001"}}
title := bg.buildModuleTitle(config, controls, 1, 1)
if title != "Authentifizierung" {
t.Errorf("expected 'Authentifizierung', got '%s'", title)
}
}
func TestBlockGenerator_BuildModuleTitle_MultiPart(t *testing.T) {
bg := &BlockGenerator{}
config := &TrainingBlockConfig{Name: "Authentifizierung"}
controls := []CanonicalControlSummary{{ControlID: "AUTH-001"}}
title := bg.buildModuleTitle(config, controls, 2, 3)
expected := "Authentifizierung (Teil 2/3)"
if title != expected {
t.Errorf("expected '%s', got '%s'", expected, title)
}
}
func TestNilIfEmpty(t *testing.T) {
tests := []struct {
input string
expected bool // true = nil result
}{
{"", true},
{" ", true},
{"value", false},
{" value ", false},
}
for _, tt := range tests {
result := nilIfEmpty(tt.input)
if tt.expected && result != nil {
t.Errorf("nilIfEmpty(%q) = %v, expected nil", tt.input, *result)
}
if !tt.expected && result == nil {
t.Errorf("nilIfEmpty(%q) = nil, expected non-nil", tt.input)
}
}
}
func TestTargetAudienceRoleMapping_AllKeys(t *testing.T) {
expectedKeys := []string{"enterprise", "authority", "provider", "all"}
for _, key := range expectedKeys {
roles, ok := TargetAudienceRoleMapping[key]
if !ok {
t.Errorf("missing key '%s' in TargetAudienceRoleMapping", key)
}
if len(roles) == 0 {
t.Errorf("empty roles for key '%s'", key)
}
}
}
func TestCategoryRoleMapping_HasEntries(t *testing.T) {
if len(CategoryRoleMapping) == 0 {
t.Error("CategoryRoleMapping is empty")
}
// Verify some expected entries
if _, ok := CategoryRoleMapping["encryption"]; !ok {
t.Error("missing 'encryption' in CategoryRoleMapping")
}
if _, ok := CategoryRoleMapping["authentication"]; !ok {
t.Error("missing 'authentication' in CategoryRoleMapping")
}
if _, ok := CategoryRoleMapping["data_protection"]; !ok {
t.Error("missing 'data_protection' in CategoryRoleMapping")
}
}