package config import ( "os" "path/filepath" "reflect" "strings" "testing" ) func TestLoadPipelineProfilesSelectDefaultAndExplicitOverlay(t *testing.T) { dir := t.TempDir() rootPath := writeProfilePipeline(t, dir, `composition: imports: [conf.d/base.yml] default_profile: production profiles: production: overlay: profiles/production.yml testing: overlay: profiles/testing.yml whisperx: transcribe_url: https://transcription.example.com/transcribe `) importPath := writePipelineSource(t, dir, "conf.d/base.yml", `workspace: root: /srv/base audita: modules: [base, shared] `) productionPath := writePipelineSource(t, dir, "profiles/production.yml", `workspace: root: /srv/production whisperx: language: en `) testingPath := writePipelineSource(t, dir, "profiles/testing.yml", `workspace: root: /srv/testing whisperx: language: fr `) production, err := LoadPipeline(rootPath) if err != nil { t.Fatal(err) } if production.Workspace.Root != "/srv/production" || production.WhisperX.Language != "en" { t.Fatalf("default profile result = workspace=%q language=%q", production.Workspace.Root, production.WhisperX.Language) } assertSelectedPipelineProfile(t, production, "production", "default", productionPath) if want := []string{absolutePath(t, rootPath), absolutePath(t, importPath), absolutePath(t, productionPath)}; !reflect.DeepEqual(production.resolution.sources, want) { t.Fatalf("default sources = %#v, want %#v", production.resolution.sources, want) } assertPipelineFieldOwner(t, production, "workspace.root", absolutePath(t, productionPath)) assertPipelineFieldOwner(t, production, "audita.modules", absolutePath(t, importPath)) assertPipelineFieldOwner(t, production, "storage.backend", pipelineDefaultOwnershipSource) profile := "testing" testingCfg, err := LoadPipelineWithOptions(rootPath, PipelineLoadOptions{Profile: &profile}) if err != nil { t.Fatal(err) } if testingCfg.Workspace.Root != "/srv/testing" || testingCfg.WhisperX.Language != "fr" { t.Fatalf("explicit profile result = workspace=%q language=%q", testingCfg.Workspace.Root, testingCfg.WhisperX.Language) } assertSelectedPipelineProfile(t, testingCfg, "testing", "cli", testingPath) } func TestLoadPipelineProfileSelectionErrors(t *testing.T) { for _, test := range []struct { name string root string profile *string want string }{ {name: "profiles require selection", root: profileComposition("", "production"), want: "default_profile is omitted"}, {name: "unknown explicit", root: profileComposition("production", "production"), profile: profilePointer("unknown"), want: "not declared"}, {name: "stacked explicit", root: profileComposition("production", "production", "testing"), profile: profilePointer("production,testing"), want: "not declared"}, {name: "explicit empty", root: profileComposition("production", "production"), profile: profilePointer(""), want: "must be non-empty"}, {name: "explicit whitespace", root: profileComposition("production", "production"), profile: profilePointer(" production "), want: "surrounding whitespace"}, {name: "invalid default", root: profileComposition("unknown", "production"), want: "does not name a declared profile"}, {name: "empty default", root: profileComposition("EMPTY", "production"), want: "must be non-empty"}, {name: "empty profile name", root: `composition: default_profile: production profiles: "": overlay: production.yml whisperx: transcribe_url: https://transcription.example.com/transcribe `, want: "profile name must be non-empty"}, } { t.Run(test.name, func(t *testing.T) { dir := t.TempDir() root := test.root if test.name == "empty default" { root = strings.ReplaceAll(root, "default_profile: EMPTY", `default_profile: ""`) } rootPath := writeProfilePipeline(t, dir, root) writePipelineSource(t, dir, "production.yml", "whisperx:\n language: en\n") _, err := LoadPipelineWithOptions(rootPath, PipelineLoadOptions{Profile: test.profile}) if err == nil || !strings.Contains(strings.ToLower(err.Error()), strings.ToLower(test.want)) { t.Fatalf("LoadPipelineWithOptions() error = %v, want containing %q", err, test.want) } }) } t.Run("explicit against profile-free pipeline", func(t *testing.T) { dir := t.TempDir() rootPath := writeProfilePipeline(t, dir, testPipelineBaseYAML) selected := "testing" _, err := LoadPipelineWithOptions(rootPath, PipelineLoadOptions{Profile: &selected}) if err == nil || !strings.Contains(err.Error(), "declares no profiles") { t.Fatalf("error = %v, want profile-free rejection", err) } }) } func TestPipelineProfileNamesRejectControlCharacters(t *testing.T) { if _, err := normalizePipelineProfileName("production\x00testing", "profile"); err == nil || !strings.Contains(err.Error(), "control characters") { t.Fatalf("normalizePipelineProfileName() error = %v, want control-character rejection", err) } } func TestLoadPipelineProfilesValidateEveryDeclaredOverlay(t *testing.T) { for _, test := range []struct { name string path string content string setup func(*testing.T, string) want string }{ {name: "missing", path: "profiles/missing.yml", want: "open composition.profiles.testing.overlay"}, {name: "malformed", path: "profiles/testing.yml", content: "workspace: [\n", want: "decode yaml"}, {name: "duplicate keys", path: "profiles/testing.yml", content: "workspace:\n root: /one\n root: /two\n", want: "duplicate yaml key"}, {name: "trailing document", path: "profiles/testing.yml", content: "workspace:\n root: /one\n---\nworkspace:\n root: /two\n", want: "exactly one yaml document"}, {name: "nested composition", path: "profiles/testing.yml", content: "composition:\n imports: [nested.yml]\n", want: "only the root pipeline"}, {name: "inheritance", path: "profiles/testing.yml", content: "workspace:\n root: /testing\n", setup: func(t *testing.T, dir string) { rootPath := filepath.Join(dir, "pipeline.yml") data, err := os.ReadFile(rootPath) if err != nil { t.Fatal(err) } updated := strings.Replace(string(data), "testing:\n overlay:", "testing:\n extends: production\n overlay:", 1) if err := os.WriteFile(rootPath, []byte(updated), 0o644); err != nil { t.Fatal(err) } }, want: "only overlay is supported"}, {name: "traversal", path: "../testing.yml", want: "invalid"}, {name: "extension", path: "profiles/testing.json", content: "{}\n", want: ".yml or .yaml"}, {name: "directory", path: "profiles/testing.yml", setup: func(t *testing.T, dir string) { if err := os.MkdirAll(filepath.Join(dir, "profiles/testing.yml"), 0o755); err != nil { t.Fatal(err) } }, want: "regular file"}, } { t.Run(test.name, func(t *testing.T) { dir := t.TempDir() rootPath := writeProfilePipeline(t, dir, `composition: default_profile: production profiles: production: overlay: profiles/production.yml testing: overlay: `+test.path+` whisperx: transcribe_url: https://transcription.example.com/transcribe `) writePipelineSource(t, dir, "profiles/production.yml", "whisperx:\n language: en\n") if test.content != "" { writePipelineSource(t, dir, test.path, test.content) } if test.setup != nil { test.setup(t, dir) } _, err := LoadPipeline(rootPath) if err == nil || !strings.Contains(strings.ToLower(err.Error()), strings.ToLower(test.want)) { t.Fatalf("LoadPipeline() error = %v, want containing %q", err, test.want) } }) } } func TestLoadPipelineProfileOverlaySemantics(t *testing.T) { dir := t.TempDir() rootPath := writeProfilePipeline(t, dir, `composition: default_profile: testing profiles: testing: overlay: testing.yml workspace: root: /base cleanup_after_publish: true whisperx: transcribe_url: https://transcription.example.com/transcribe retries: 3 audita: modules: [base, shared] scriptorium: artifacts: session_recap: enabled: true prompt_id: dnd.session_recap output_path: artifacts/session_recap.md `) writePipelineSource(t, dir, "testing.yml", `workspace: cleanup_after_publish: false whisperx: retries: 0 audita: modules: [] scriptorium: artifacts: session_recap: enabled: false experimental: enabled: false `) cfg, err := LoadPipeline(rootPath) if err != nil { t.Fatal(err) } if cfg.Workspace.Root != "/base" || cfg.Workspace.CleanupAfterPublish || cfg.WhisperX.Retries == nil || *cfg.WhisperX.Retries != 0 { t.Fatalf("recursive/zero overlay = workspace=%#v retries=%#v", cfg.Workspace, cfg.WhisperX.Retries) } if cfg.Audita.Modules == nil || len(cfg.Audita.Modules) != 0 { t.Fatalf("list replacement = %#v, want explicit empty list", cfg.Audita.Modules) } if cfg.Scriptorium == nil || cfg.Scriptorium.Artifacts["session_recap"].Enabled || len(cfg.Scriptorium.Artifacts) != 2 { t.Fatalf("keyed artifact overlay = %#v", cfg.Scriptorium) } } func TestLoadPipelineProfileRejectsNullAndKindChanges(t *testing.T) { for _, test := range []struct { name string overlay string want string }{ {name: "null deletion", overlay: "workspace:\n root: null\n", want: "null cannot delete"}, {name: "mapping scalar", overlay: "workspace: /other\n", want: "kind change"}, {name: "list mapping", overlay: "audita:\n modules:\n testing: true\n", want: "kind change"}, } { t.Run(test.name, func(t *testing.T) { dir := t.TempDir() rootPath := writeProfilePipeline(t, dir, `composition: default_profile: testing profiles: testing: overlay: testing.yml workspace: root: /base audita: modules: [base] whisperx: transcribe_url: https://transcription.example.com/transcribe `) writePipelineSource(t, dir, "testing.yml", test.overlay) _, err := LoadPipeline(rootPath) if err == nil || !strings.Contains(strings.ToLower(err.Error()), strings.ToLower(test.want)) { t.Fatalf("LoadPipeline() error = %v, want containing %q", err, test.want) } }) } } func TestPipelineEffectiveDigestTracksNormalizedMeaning(t *testing.T) { dir := t.TempDir() monolithicPath := writePipelineSource(t, dir, "monolithic.yml", `workspace: root: /srv/narratio whisperx: transcribe_url: https://transcription.example.com/transcribe language: en `) composedPath := writePipelineSource(t, dir, "pipeline.yml", `composition: imports: [workspace.yml] whisperx: transcribe_url: https://transcription.example.com/transcribe language: en `) writePipelineSource(t, dir, "workspace.yml", "workspace:\n root: /srv/narratio\n") monolithic, err := LoadPipeline(monolithicPath) if err != nil { t.Fatal(err) } composed, err := LoadPipeline(composedPath) if err != nil { t.Fatal(err) } if monolithic.resolution.effectiveDigest == "" || monolithic.resolution.effectiveDigest != composed.resolution.effectiveDigest { t.Fatalf("equal effective results have digests %q and %q", monolithic.resolution.effectiveDigest, composed.resolution.effectiveDigest) } changedPath := writePipelineSource(t, dir, "changed.yml", `workspace: root: /srv/narratio whisperx: transcribe_url: https://transcription.example.com/transcribe language: fr `) changed, err := LoadPipeline(changedPath) if err != nil { t.Fatal(err) } if changed.resolution.effectiveDigest == monolithic.resolution.effectiveDigest { t.Fatal("semantic value change retained effective digest") } t.Setenv("AWS_SECRET_ACCESS_KEY", "secret-one") first, err := LoadPipeline(monolithicPath) if err != nil { t.Fatal(err) } t.Setenv("AWS_SECRET_ACCESS_KEY", "secret-two") second, err := LoadPipeline(monolithicPath) if err != nil { t.Fatal(err) } if first.resolution.effectiveDigest != second.resolution.effectiveDigest || strings.Contains(first.resolution.effectiveDigest, "secret") { t.Fatalf("environment secret affected or appeared in digest: %q / %q", first.resolution.effectiveDigest, second.resolution.effectiveDigest) } } func TestPipelineEffectiveDigestExcludesProfileIdentity(t *testing.T) { dir := t.TempDir() rootPath := writeProfilePipeline(t, dir, profileComposition("production", "production", "testing")) overlay := "workspace:\n root: /srv/narratio\n" writePipelineSource(t, dir, "production.yml", overlay) writePipelineSource(t, dir, "testing.yml", overlay) production := "production" productionCfg, err := LoadPipelineWithOptions(rootPath, PipelineLoadOptions{Profile: &production}) if err != nil { t.Fatal(err) } testing := "testing" testingCfg, err := LoadPipelineWithOptions(rootPath, PipelineLoadOptions{Profile: &testing}) if err != nil { t.Fatal(err) } if productionCfg.resolution.effectiveDigest != testingCfg.resolution.effectiveDigest { t.Fatalf("profile identity changed equal effective digests: %q / %q", productionCfg.resolution.effectiveDigest, testingCfg.resolution.effectiveDigest) } } func TestLoadWithSessionOptionsCarriesExplicitProfilePresence(t *testing.T) { dir := t.TempDir() rootPath := writeProfilePipeline(t, dir, profileComposition("production", "production", "testing")) writePipelineSource(t, dir, "production.yml", "workspace:\n root: /production\n") writePipelineSource(t, dir, "testing.yml", "workspace:\n root: /testing\n") campaignPath := writePipelineSource(t, dir, "campaign.yml", "campaign_id: campaign\ninputs:\n speakers_file: speakers.yml\n autocorrect_file: autocorrect.yml\n glossary_file: glossary.yml\n players_file: players.yml\n party_file: party.yml\n") writePipelineSource(t, dir, "party.yml", "legacy: party\n") sessionPath := writePipelineSource(t, dir, "session.yml", "session_id: session\ncampaign: campaign\n") selected := "testing" cfg, err := LoadWithSessionOptions(rootPath, campaignPath, sessionPath, SessionLoadOptions{Profile: &selected}) if err != nil { t.Fatal(err) } if cfg.Pipeline.Workspace.Root != "/testing" || cfg.Pipeline.resolution.selectedProfile.source != "cli" { t.Fatalf("combined profile result = workspace=%q metadata=%#v", cfg.Pipeline.Workspace.Root, cfg.Pipeline.resolution.selectedProfile) } } func writeProfilePipeline(t *testing.T, dir, content string) string { t.Helper() return writePipelineSource(t, dir, "pipeline.yml", content) } func profileComposition(defaultProfile string, profiles ...string) string { var builder strings.Builder builder.WriteString("composition:\n") if defaultProfile != "" { builder.WriteString(" default_profile: " + defaultProfile + "\n") } builder.WriteString(" profiles:\n") for _, profile := range profiles { builder.WriteString(" " + profile + ":\n overlay: " + profile + ".yml\n") } builder.WriteString("whisperx:\n transcribe_url: https://transcription.example.com/transcribe\n") return builder.String() } func profilePointer(value string) *string { return &value } func assertSelectedPipelineProfile(t *testing.T, cfg *PipelineConfig, name, source, overlayPath string) { t.Helper() if cfg == nil || cfg.resolution == nil || cfg.resolution.selectedProfile == nil { t.Fatal("selected profile provenance is absent") } selection := cfg.resolution.selectedProfile if selection.name != name || selection.source != source || selection.overlayPath != absolutePath(t, overlayPath) { t.Fatalf("selected profile = %#v, want name=%q source=%q overlay=%q", selection, name, source, absolutePath(t, overlayPath)) } }