package config import ( "encoding/json" "path/filepath" "strings" "testing" "gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline" ) func TestVersion3DefaultsAndValidation(t *testing.T) { cfg := Default() if cfg.Output.Directory != "./notarius-output" || cfg.Debug.Directory != "./notarius-debug" || cfg.Cache.ChunkPlans.Mode != pipeline.ChunkCacheAuto { t.Fatalf("unexpected defaults: %#v", cfg) } if err := cfg.Validate(); err != nil { t.Fatalf("Validate() error = %v", err) } } func TestVersion3FileSchemaIsStrictAndRejectsVersion2BeforeDecode(t *testing.T) { _, err := ParseFileConfigYAML([]byte("version: 2\nworkspace:\n directory: /tmp/old\n")) if err == nil || !strings.Contains(err.Error(), "version 2-to-3 migration") { t.Fatalf("version 2 error = %v", err) } _, err = ParseFileConfigYAML([]byte("version: 3\nworkspace:\n directory: /tmp/old\n")) if err == nil || !strings.Contains(err.Error(), "field workspace not found") { t.Fatalf("unknown field error = %v", err) } _, err = ParseFileConfigYAML([]byte("version: 4\n")) if err == nil || !strings.Contains(err.Error(), "unsupported config version 4") { t.Fatalf("version 4 error = %v", err) } } func TestStatePrecedenceAndInvalidSources(t *testing.T) { file, err := ParseFileConfigYAML([]byte(`version: 3 output: directory: ./file-output cache: chunk_plans: directory: ./plans mode: refresh checkpoints: directory: ./checkpoints debug: directory: ./debug `)) if err != nil { t.Fatal(err) } cfg := Default() if err := cfg.ApplyFileConfig(file); err != nil { t.Fatal(err) } lookup := func(name string) (string, bool) { values := map[string]string{ "NOTARIUS_OUTPUT_DIR": "/env/output", "NOTARIUS_CACHE_CHUNK_PLANS_MODE": "auto", "NOTARIUS_CACHE_CHUNK_PLANS_DIR": "/env/plans", "NOTARIUS_CACHE_CHECKPOINTS_DIR": "/env/checkpoints", "NOTARIUS_DEBUG_DIR": "/env/debug", } v, ok := values[name] return v, ok } if err := cfg.ApplyEnvOverridesWithLookup(lookup); err != nil { t.Fatal(err) } if cfg.Output.Directory != "/env/output" || cfg.Cache.ChunkPlans.Directory != "/env/plans" || cfg.Cache.Checkpoints.Directory != "/env/checkpoints" || cfg.Debug.Directory != "/env/debug" || cfg.Cache.ChunkPlans.Mode != pipeline.ChunkCacheAuto { t.Fatalf("unexpected environment precedence: %#v", cfg) } bad := Default() err = bad.ApplyEnvOverridesWithLookup(func(name string) (string, bool) { if name == "NOTARIUS_DEBUG_DIR" { return " ", true } return "", false }) if err == nil || !strings.Contains(err.Error(), "NOTARIUS_DEBUG_DIR") { t.Fatalf("empty debug environment error = %v", err) } invalidFile, err := ParseFileConfigYAML([]byte("version: 3\noutput:\n directory: ' '\n")) if err != nil { t.Fatal(err) } bad = Default() if err := bad.ApplyFileConfig(invalidFile); err == nil || !strings.Contains(err.Error(), "output.directory") { t.Fatalf("invalid file error = %v", err) } } func TestRedactedSummaryContainsOnlyVersion3StateFields(t *testing.T) { cfg := Default() cfg.Pipelines["example"] = pipeline.PipelineProfile{Input: pipeline.ModuleBinding{Module: "input", Options: map[string]any{"api_key": "secret-value", "safe": "value"}}} payload, err := json.Marshal(cfg.RedactedSummaryPayload()) if err != nil { t.Fatal(err) } text := string(payload) for _, forbidden := range []string{"workspace", "diagnostics"} { if strings.Contains(text, forbidden) { t.Fatalf("payload contains %q: %s", forbidden, text) } } if strings.Contains(text, "secret-value") || !strings.Contains(text, "[REDACTED]") { t.Fatalf("payload did not redact sensitive option: %s", text) } } func TestCacheFamilyDefaultsAreIndependent(t *testing.T) { base := filepath.Join(t.TempDir(), "cache") resolver := func() (string, error) { return base, nil } plans, err := DefaultChunkPlanRoot(resolver) if err != nil { t.Fatal(err) } checkpoints, err := DefaultCheckpointRoot(resolver) if err != nil { t.Fatal(err) } if plans == checkpoints || plans != filepath.Join(base, "notarius", "chunk-plans") || checkpoints != filepath.Join(base, "notarius", "checkpoints") { t.Fatalf("roots = %q, %q", plans, checkpoints) } }