package config import ( "os" "path/filepath" "strings" "testing" ) func TestPipelineConfigKitImportsProfileAndBaseSelection(t *testing.T) { dir := t.TempDir() writePipelineFixture(t, dir, "pipeline.yml", `configkit: imports: - path: config/workspace.yml - path: config/artifact.yml whisperx: transcribe_url: https://transcription.example.test/transcribe `) writePipelineFixture(t, dir, "config/workspace.yml", "workspace:\n root: /srv/base\n") writePipelineFixture(t, dir, "config/artifact.yml", `scriptorium: artifacts: recap: enabled: false output_path: artifacts/recap.md `) writePipelineFixture(t, dir, "profiles/testing.yml", "workspace:\n root: /srv/testing\n") writePipelineFixture(t, dir, "profiles/unselected.yml", "unknown: [\n") root := filepath.Join(dir, "pipeline.yml") for _, profile := range []*string{nil, stringPointer(""), stringPointer("default")} { cfg, err := LoadPipelineWithOptions(root, PipelineLoadOptions{Profile: profile}) if err != nil { t.Fatalf("load base selection %#v: %v", profile, err) } if cfg.Workspace.Root != "/srv/base" { t.Fatalf("base workspace = %q", cfg.Workspace.Root) } } selected := "testing" cfg, err := LoadPipelineWithOptions(root, PipelineLoadOptions{Profile: &selected}) if err != nil { t.Fatal(err) } if cfg.Workspace.Root != "/srv/testing" || len(EffectivePipelineImports(cfg)) != 2 { t.Fatalf("profiled pipeline = %#v, imports=%#v", cfg.Workspace, EffectivePipelineImports(cfg)) } profile, ok := SelectedPipelineProfile(cfg) if !ok || profile.Name != "testing" || profile.OverlayPath != filepath.Join(dir, "profiles", "testing.yml") { t.Fatalf("selected profile = %#v, %v", profile, ok) } } func TestPipelineConfigKitRejectsDuplicateBaseDeclarations(t *testing.T) { for _, test := range []struct { name, first, second, clearedContainer string }{ {name: "artifact", first: "scriptorium:\n artifacts:\n recap:\n prompt_id: recap\n", second: "scriptorium:\n artifacts:\n recap:\n output_path: artifacts/recap.md\n", clearedContainer: "scriptorium:\n artifacts: null\n"}, {name: "artifact family", first: "scriptorium:\n artifact_families:\n recap:\n for_each: party.characters\n", second: "scriptorium:\n artifact_families:\n recap:\n output_path_pattern: artifacts/{character_id}.md\n", clearedContainer: "scriptorium:\n artifact_families: null\n"}, } { t.Run(test.name, func(t *testing.T) { dir := t.TempDir() writePipelineFixture(t, dir, "pipeline.yml", `configkit: imports: - path: first.yml - path: second.yml whisperx: transcribe_url: https://transcription.example.test/transcribe `) writePipelineFixture(t, dir, "first.yml", test.first) writePipelineFixture(t, dir, "second.yml", test.second) writePipelineFixture(t, dir, "profiles/clear-container.yml", test.clearedContainer) writePipelineFixture(t, dir, "profiles/clear-scriptorium.yml", "scriptorium: null\n") for _, profile := range []string{"", "clear-container", "clear-scriptorium"} { t.Run(profile, func(t *testing.T) { options := PipelineLoadOptions{} if profile != "" { options.Profile = &profile } _, err := LoadPipelineWithOptions(filepath.Join(dir, "pipeline.yml"), options) if err == nil || !strings.Contains(err.Error(), "declared by multiple base files") || !strings.Contains(err.Error(), "first.yml") || !strings.Contains(err.Error(), "second.yml") { t.Fatalf("duplicate declaration error = %v", err) } }) } }) } } func TestPipelineConfigKitAllowsOneBaseDeclarationWithProfileOverride(t *testing.T) { dir := t.TempDir() writePipelineFixture(t, dir, "pipeline.yml", `configkit: imports: - path: artifact.yml whisperx: transcribe_url: https://transcription.example.test/transcribe `) writePipelineFixture(t, dir, "artifact.yml", "scriptorium:\n artifacts:\n recap:\n prompt_id: base\n output_path: artifacts/recap.md\n") writePipelineFixture(t, dir, "profiles/testing.yml", "scriptorium:\n artifacts:\n recap:\n prompt_id: testing\n") profile := "testing" cfg, err := LoadPipelineWithOptions(filepath.Join(dir, "pipeline.yml"), PipelineLoadOptions{Profile: &profile}) if err != nil { t.Fatal(err) } if got := cfg.Scriptorium.Artifacts["recap"].PromptID; got != "testing" { t.Fatalf("profile prompt = %q, want testing", got) } } func TestPipelineConfigKitRejectsUnknownDirectiveAlias(t *testing.T) { dir := t.TempDir() path := writePipelineFixture(t, dir, "pipeline.yml", "composition:\n imports: []\n") _, err := LoadPipeline(path) if err == nil || !strings.Contains(err.Error(), "decode.unknown_field") { t.Fatalf("composition alias error = %v", err) } } func writePipelineFixture(t *testing.T, root, relative, contents string) string { t.Helper() path := filepath.Join(root, filepath.FromSlash(relative)) if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(path, []byte(contents), 0o644); err != nil { t.Fatal(err) } return path } func stringPointer(value string) *string { return &value }