Bootstrap secrets before configuration loading
This commit is contained in:
132
internal/app/secrets_bootstrap_test.go
Normal file
132
internal/app/secrets_bootstrap_test.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"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}
|
||||
if err := bootstrap.load(filepath.Join(t.TempDir(), "pipeline.yml"), configkit.LoadSecretsDir); err != nil {
|
||||
t.Fatalf("bootstrap: %v", err)
|
||||
}
|
||||
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 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)
|
||||
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 stringPointer(value string) *string { return &value }
|
||||
Reference in New Issue
Block a user