package config import ( "strings" "testing" "gopkg.in/yaml.v3" ) func TestScriptoriumCredentialsComposeAndSerializeSafely(t *testing.T) { t.Setenv("OPENROUTER_API_KEY", "synthetic-first-secret") t.Setenv("CUSTOM_PROVIDER_KEY", "synthetic-second-secret") dir := t.TempDir() root := writePipelineFixture(t, dir, "pipeline.yml", "whisperx:\n transcribe_url: https://example.test/transcribe\nscriptorium:\n credentials: [OPENROUTER_API_KEY]\n") base, err := LoadPipeline(root) if err != nil { t.Fatal(err) } for _, marshal := range []func() ([]byte, error){ func() ([]byte, error) { return yaml.Marshal(base) }, func() ([]byte, error) { return MarshalEffectivePipeline(base) }, } { data, err := marshal() if err != nil { t.Fatal(err) } if !strings.Contains(string(data), "OPENROUTER_API_KEY") || strings.Contains(string(data), "synthetic-first-secret") { t.Fatal("unsafe or missing credential projection") } } t.Setenv("OPENROUTER_API_KEY", "rotated-secret") rotated, err := LoadPipeline(root) if err != nil { t.Fatal(err) } if EffectivePipelineDigest(base) != EffectivePipelineDigest(rotated) { t.Fatal("rotation changed effective digest") } for _, tc := range []struct{ name, value, want string }{ {"replace", "[CUSTOM_PROVIDER_KEY]", "CUSTOM_PROVIDER_KEY"}, {"clear", "[]", ""}, {"null", "null", ""}, } { t.Run(tc.name, func(t *testing.T) { writePipelineFixture(t, dir, "overlays/"+tc.name+".yml", "scriptorium:\n credentials: "+tc.value+"\n") loaded, err := LoadPipelineWithOptions(root, PipelineLoadOptions{Overlay: &tc.name}) if err != nil { t.Fatal(err) } creds := loaded.Scriptorium.Credentials if tc.want == "" { if len(creds) != 0 { t.Fatal("credentials not cleared") } return } if len(creds) != 1 || creds[0].EnvName() != tc.want || creds[0].Value() != "synthetic-second-secret" { t.Fatal("overlay did not replace resolved credentials") } }) } t.Setenv("OPENROUTER_API_KEY", "") if _, err := LoadPipeline(root); err == nil { t.Fatal("empty selected credential accepted") } }