133 lines
4.5 KiB
Go
133 lines
4.5 KiB
Go
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 }
|