package app import ( "bytes" "fmt" "io" "os" "path/filepath" "strings" "testing" configkit "gitea.maximumdirect.net/eric/ConfigKit" "gitea.maximumdirect.net/eric/narratio/internal/config" ) func TestPipelineLoadSkipsAbsentConventionalSecretsDirectory(t *testing.T) { pipelinePath := filepath.Join(t.TempDir(), "pipeline.yml") if err := os.WriteFile(pipelinePath, []byte("version: 1\n"), 0o644); err != nil { t.Fatal(err) } secretCalls := 0 pipelineCalls := 0 deps := commandDependencies{ loadSecretsDir: func(string) (*configkit.SecretsReport, error) { secretCalls++ return nil, nil }, loadPipelineConfig: func(string, config.PipelineLoadOptions) (*config.PipelineConfig, error) { pipelineCalls++ return &config.PipelineConfig{}, nil }, } if _, _, err := loadPipelineConfigWithDependencies(pipelinePath, config.PipelineLoadOptions{}, nil, deps); err != nil { t.Fatalf("load pipeline: %v", err) } if secretCalls != 0 || pipelineCalls != 1 { t.Fatalf("calls = secrets %d, pipeline %d; want 0, 1", secretCalls, pipelineCalls) } } func TestExplicitSecretsDirectoryIsResolvedFromCallerWorkingDirectory(t *testing.T) { callerDir := t.TempDir() selected := filepath.Join(callerDir, "credentials") if err := os.Mkdir(selected, 0o700); err != nil { t.Fatal(err) } original, err := os.Getwd() if err != nil { t.Fatal(err) } if err := os.Chdir(callerDir); err != nil { t.Fatal(err) } t.Cleanup(func() { _ = os.Chdir(original) }) got, required, err := selectedSecretsDirectory(filepath.Join(t.TempDir(), "pipeline.yml"), stringPointer("credentials")) if err != nil { t.Fatalf("select secrets: %v", err) } if !required || got != selected { t.Fatalf("selection = %q, required=%t; want %q, true", got, required, selected) } } func TestExplicitSecretsFailurePrecedesPipelineLoad(t *testing.T) { pipelinePath := filepath.Join(t.TempDir(), "pipeline.yml") if err := os.WriteFile(pipelinePath, []byte("version: 1\n"), 0o644); err != nil { t.Fatal(err) } for name, selection := range map[string]string{"empty": "", "missing": filepath.Join(t.TempDir(), "missing")} { t.Run(name, func(t *testing.T) { pipelineCalls := 0 deps := commandDependencies{loadPipelineConfig: func(string, config.PipelineLoadOptions) (*config.PipelineConfig, error) { pipelineCalls++ return &config.PipelineConfig{}, nil }} if _, _, err := loadPipelineConfigWithDependencies(pipelinePath, config.PipelineLoadOptions{}, &selection, deps); err == nil { t.Fatal("load succeeded, want bootstrap error") } if pipelineCalls != 0 { t.Fatalf("pipeline load calls = %d, want 0", pipelineCalls) } }) } } func TestSecretsBootstrapPreservesExistingEnvironmentValues(t *testing.T) { directory := t.TempDir() const emptyName = "NARRATIO_BOOTSTRAP_EXISTING_EMPTY" const valueName = "NARRATIO_BOOTSTRAP_EXISTING_VALUE" const loadedName = "NARRATIO_BOOTSTRAP_NEW_VALUE" t.Setenv(emptyName, "") t.Setenv(valueName, "from-environment") restoreEnvAfterTest(t, loadedName) for name, value := range map[string]string{ emptyName: "from-file-empty\n", valueName: "from-file-value\n", loadedName: "new-value\n", } { mustWriteSecretFile(t, filepath.Join(directory, name), value) } bootstrap := &secretsBootstrap{selection: &directory, selectionSet: true} var diagnostics bytes.Buffer if err := bootstrap.load(filepath.Join(t.TempDir(), "pipeline.yml"), configkit.LoadSecretsDir, &diagnostics); err != nil { t.Fatalf("bootstrap: %v", err) } if got := diagnostics.String(); !strings.Contains(got, `status="skipped_existing"`) || !strings.Contains(got, `existing_empty=true`) { t.Fatalf("diagnostics lack explicit skipped-empty result: %q", got) } if got := os.Getenv(emptyName); got != "" { t.Fatalf("existing empty value = %q, want empty", got) } if got := os.Getenv(valueName); got != "from-environment" { t.Fatalf("existing value = %q, want environment value", got) } if got := os.Getenv(loadedName); got != "new-value" { t.Fatalf("new value = %q, want file value", got) } } func TestNotariusCredentialLoadsFromCommandSecretBootstrap(t *testing.T) { tests := []struct { name string explicit bool environment string fileValue string wantValue string }{ {name: "conventional", fileValue: "conventional-value", wantValue: "conventional-value"}, {name: "explicit", explicit: true, fileValue: "explicit-value", wantValue: "explicit-value"}, {name: "environment precedence", explicit: true, environment: "environment-value", fileValue: "file-value", wantValue: "environment-value"}, } for index, test := range tests { t.Run(test.name, func(t *testing.T) { envName := fmt.Sprintf("NARRATIO_NOTARIUS_BOOTSTRAP_%d", index) restoreEnvAfterTest(t, envName) root := t.TempDir() pipelinePath := filepath.Join(root, "pipeline.yml") if err := os.WriteFile(pipelinePath, []byte("whisperx:\n transcribe_url: https://transcription.example.test/transcribe\nnotarius:\n enabled: false\n credentials: ["+envName+"]\n"), 0o644); err != nil { t.Fatal(err) } secretsDir := filepath.Join(root, "secrets") var selection *string if test.explicit { secretsDir = filepath.Join(t.TempDir(), "selected-secrets") selection = &secretsDir } mustWriteSecretFile(t, filepath.Join(secretsDir, envName), test.fileValue+"\n") if test.environment != "" { if err := os.Setenv(envName, test.environment); err != nil { t.Fatal(err) } } _, pipeline, err := loadPipelineConfigWithDependencies(pipelinePath, config.PipelineLoadOptions{}, selection, commandDependencies{diagnostics: io.Discard}) if err != nil { t.Fatalf("load pipeline: %v", err) } if pipeline.Notarius == nil || len(pipeline.Notarius.Credentials) != 1 || !pipeline.Notarius.Credentials[0].IsResolved() || pipeline.Notarius.Credentials[0].EnvName() != envName || pipeline.Notarius.Credentials[0].Value() != test.wantValue { t.Fatal("Notarius credential did not resolve from the selected command bootstrap source") } }) } } func TestSecretsBootstrapPreservesSafePartialReportOnFailure(t *testing.T) { directory := t.TempDir() mustWriteSecretFile(t, filepath.Join(directory, "INVALID-NAME"), "must-not-appear\n") bootstrap := &secretsBootstrap{selection: &directory, selectionSet: true} err := bootstrap.load(filepath.Join(t.TempDir(), "pipeline.yml"), configkit.LoadSecretsDir, io.Discard) if err == nil { t.Fatal("bootstrap succeeded, want error") } if !strings.Contains(err.Error(), `status="failed"`) || !strings.Contains(err.Error(), `name="INVALID-NAME"`) { t.Fatalf("error lacks partial report: %v", err) } if strings.Contains(err.Error(), "must-not-appear") { t.Fatalf("error exposed secret value: %v", err) } } func TestExecuteConfigShowReportsSuccessfulSecretsBootstrapOnStderr(t *testing.T) { workspaceRoot := t.TempDir() pipelinePath, _, _ := writeValidConfigFiles(t, workspaceRoot) secretsDir := t.TempDir() const name = "NARRATIO_BOOTSTRAP_REPORT_SECRET" const canary = "synthetic-secret-must-not-appear" restoreEnvAfterTest(t, name) if err := os.Unsetenv(name); err != nil { t.Fatal(err) } mustWriteSecretFile(t, filepath.Join(secretsDir, name), canary+"\n") var stdout bytes.Buffer var stderr bytes.Buffer if code := Execute([]string{"config", "show", "--config", pipelinePath, "--secrets-dir", secretsDir}, &stdout, &stderr); code != 0 { t.Fatalf("config show exit = %d, stderr = %q", code, stderr.String()) } if got := stderr.String(); !strings.Contains(got, `status="loaded"`) || !strings.Contains(got, `name="`+name+`"`) { t.Fatalf("stderr lacks safe bootstrap report: %q", got) } if strings.Contains(stderr.String(), canary) || strings.Contains(stdout.String(), canary) { t.Fatal("command output exposed secret value") } if strings.Contains(stdout.String(), `status="loaded"`) || strings.Contains(stdout.String(), name) { t.Fatalf("show stdout was polluted by bootstrap report: %q", stdout.String()) } } func TestExecuteConfigValidateReportsSkippedEmptyBeforeTypedLoadFailure(t *testing.T) { workspaceRoot := t.TempDir() pipelinePath, _, _ := writeValidConfigFiles(t, workspaceRoot) const name = "NARRATIO_REVIEW_KEY" const canary = "synthetic-skipped-secret-must-not-appear" t.Setenv(name, "") pipelineYAML, err := os.ReadFile(pipelinePath) if err != nil { t.Fatal(err) } pipelineYAML = append(pipelineYAML, []byte("notarius:\n enabled: false\n credentials: ["+name+"]\n")...) if err := os.WriteFile(pipelinePath, pipelineYAML, 0o644); err != nil { t.Fatal(err) } secretsDir := t.TempDir() mustWriteSecretFile(t, filepath.Join(secretsDir, name), canary+"\n") var stdout bytes.Buffer var stderr bytes.Buffer if code := Execute([]string{"config", "validate", "--config", pipelinePath, "--secrets-dir", secretsDir}, &stdout, &stderr); code == 0 { t.Fatal("config validate succeeded, want empty-secret failure") } got := stderr.String() if !strings.Contains(got, `status="skipped_existing"`) || !strings.Contains(got, `existing_empty=true`) { t.Fatalf("stderr lacks useful skipped-empty diagnostic: %q", got) } if !strings.Contains(got, "secret.empty") { t.Fatalf("stderr lacks typed load failure: %q", got) } if strings.Contains(got, canary) || strings.Contains(stdout.String(), canary) { t.Fatal("command output exposed skipped secret value") } } func stringPointer(value string) *string { return &value }