package cli import ( "errors" "io" "os" "path/filepath" "strings" "testing" ck "gitea.maximumdirect.net/eric/ConfigKit" "gitea.maximumdirect.net/eric/audita/v3/internal/core/config" ) func TestSharedConfigInput(t *testing.T) { for _, command := range []string{"process", "validate", "print-effective"} { t.Run(command, func(t *testing.T) { args := []string{"--config", "main.yml", "--overlay", "testing", "--proposal-profile", "economy", "--modules", "", "--target-sections", "0"} if command == "process" { args = append([]string{"transcript.json", "--glossary", "glossary.yml"}, args...) } input, help, err := parseConfigInput(command, args, io.Discard) if err != nil || help { t.Fatalf("parse: %+v %v %v", input, help, err) } if !input.ConfigPathSet || input.ConfigPath != "main.yml" || input.Overlay != "testing" || len(input.Overrides) != 3 { t.Fatalf("selection: %+v", input) } for _, override := range input.Overrides { switch override.Path.String() { case ck.Keys("pipeline", "modules").String(): if values, ok := override.Value.([]any); !ok || len(values) != 0 { t.Fatalf("empty modules: %#v", override.Value) } case ck.Keys("chunking", "target_sections").String(): if override.Value != 0 { t.Fatalf("zero target: %#v", override.Value) } } } }) } } func TestConfigInputRejectsRemovedAndProcessOnlyFlags(t *testing.T) { for _, command := range []string{"process", "validate", "print-effective"} { for _, flag := range []string{"--model", "--llm-api-key", "--max-retries", "--llm-concurrency", "--report-json", "--work-dir-retention"} { args := []string{flag, "value"} if command == "process" { args = append([]string{"transcript.json", "--glossary", "glossary.yml"}, args...) } if _, _, err := parseConfigInput(command, args, io.Discard); err == nil { t.Fatalf("%s accepted %s", command, flag) } } } for _, command := range []string{"validate", "print-effective"} { if _, _, err := parseConfigInput(command, []string{"--glossary", "glossary.yml"}, io.Discard); err == nil { t.Fatalf("%s accepted process path", command) } if _, _, err := parseConfigInput(command, []string{"transcript.json"}, io.Discard); err == nil { t.Fatalf("%s accepted positional", command) } } for _, command := range []string{"process", "validate", "print-effective"} { _, help, err := parseConfigInput(command, []string{"--help"}, io.Discard) if err != nil || !help { t.Fatalf("%s help: %v %v", command, help, err) } } } func TestConfigSelection(t *testing.T) { dir := t.TempDir() first, second := filepath.Join(dir, "first.yml"), filepath.Join(dir, "second.yml") if err := os.WriteFile(second, []byte("version: 2\n"), 0600); err != nil { t.Fatal(err) } path, source, err := selectConfigPath("", false, os.Stat, []string{first, second}) if err != nil || path != second || source != "default" { t.Fatalf("discovery: %q %q %v", path, source, err) } for _, tc := range []struct { explicit string set bool candidates []string }{ {"", true, []string{second}}, {first, true, []string{second}}, {"", false, []string{first}}, } { if _, _, err := selectConfigPath(tc.explicit, tc.set, os.Stat, tc.candidates); err == nil { t.Fatalf("accepted unavailable selection: %+v", tc) } } statDenied := func(string) (os.FileInfo, error) { return nil, os.ErrPermission } if _, _, err := selectConfigPath("", false, statDenied, []string{first}); !errors.Is(err, os.ErrPermission) { t.Fatalf("inaccessible candidate: %v", err) } path, source, err = selectConfigPath(second, true, os.Stat, []string{first}) if err != nil || path != second || source != "flag" { t.Fatalf("explicit path: %q %q %v", path, source, err) } } func TestLegacyEnvironmentDoesNotSelectConfig(t *testing.T) { t.Setenv("AUDITA_CONFIG", filepath.Join(t.TempDir(), "legacy.yml")) _, _, err := selectConfigPath("", false, os.Stat, []string{filepath.Join(t.TempDir(), "missing.yml")}) if err == nil || !strings.Contains(err.Error(), "no configuration file") { t.Fatalf("legacy environment changed discovery: %v", err) } } func TestEffectiveViewParsing(t *testing.T) { for _, view := range []string{"settings", "profiles", "explain"} { input, _, err := parseConfigInput("print-effective", []string{"--view", view}, io.Discard) if err != nil || input.View != view { t.Fatalf("%q: %+v %v", view, input, err) } } if _, _, err := parseConfigInput("print-effective", []string{"--view", "raw"}, io.Discard); err == nil { t.Fatal("unknown view accepted") } if _, _, err := parseConfigInput("validate", []string{"--view", "profiles"}, io.Discard); err == nil { t.Fatal("validate accepted inspection-only flag") } } func TestSharedLoadingUsesSelectedFileOverlayAndVisitedOverrides(t *testing.T) { dir := t.TempDir() if err := os.Mkdir(filepath.Join(dir, "overlays"), 0700); err != nil { t.Fatal(err) } main := filepath.Join(dir, "config.yml") base := "version: 2\nbackends:\n local:\n endpoint: https://local.invalid/v1\nllm:\n proposal_profile: base\nchunking:\n max_section_tokens: 900\n" if err := os.WriteFile(main, []byte(base), 0600); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, "overlays", "testing.yml"), []byte("llm:\n proposal_profile: testing\n"), 0600); err != nil { t.Fatal(err) } for _, command := range []string{"process", "validate", "print-effective"} { args := []string{"--config", main, "--overlay", "testing", "--min-section-tokens", "500"} if command == "process" { args = append([]string{"input.json", "--glossary", "glossary.yml"}, args...) } input, _, err := parseConfigInput(command, args, io.Discard) if err != nil { t.Fatal(err) } selected, err := loadConfigInput(input, command) if err != nil { t.Fatal(err) } c := selected.Result.Config if c.LLM.ProposalProfile != "testing" || c.LLM.ValidationProfile != "testing" || c.Chunking.MinSectionTokens != 500 || c.Chunking.MaxSectionTokens != 900 { t.Fatalf("%s resolved %+v", command, c) } } input, _, err := parseConfigInput("validate", []string{"--overlay", "testing"}, io.Discard) if err != nil { t.Fatal(err) } if _, err := loadConfigInput(input, "validate"); err == nil { t.Fatal("validate accepted discovery") } input, _, err = parseConfigInput("validate", []string{"--config", main}, io.Discard) if err != nil { t.Fatal(err) } if _, err := loadConfigInput(input, "validate"); err == nil { t.Fatal("unmodified invalid lower-precedence section relation accepted") } } func TestReportingAndDebugOverridesRemainIndependent(t *testing.T) { dir := t.TempDir() if err := os.Mkdir(filepath.Join(dir, "overlays"), 0700); err != nil { t.Fatal(err) } main := filepath.Join(dir, "config.yml") base := "version: 2\nbackends:\n local:\n endpoint: https://local.invalid/v1\nllm:\n proposal_profile: base\n" if err := os.WriteFile(main, []byte(base), 0600); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, "overlays", "testing.yml"), []byte("reporting:\n enabled: true\n stdout_dir: reports\ndiagnostics:\n debug: true\n"), 0600); err != nil { t.Fatal(err) } defaultInput, _, err := parseConfigInput("validate", []string{"--config", main}, io.Discard) if err != nil { t.Fatal(err) } defaults, err := loadConfigInput(defaultInput, "validate") if err != nil { t.Fatal(err) } if !defaults.Result.Config.Reporting.Enabled || defaults.Result.Config.Reporting.StdoutDir != "." || defaults.Result.Config.Diagnostics.Debug { t.Fatalf("unexpected defaults: %+v", defaults.Result.Config) } for _, command := range []string{"process", "validate", "print-effective"} { args := []string{"--config", main, "--overlay", "testing", "--reporting=false", "--debug=false"} if command == "process" { args = append([]string{"input.json", "--glossary", "glossary.yml"}, args...) } input, _, err := parseConfigInput(command, args, io.Discard) if err != nil { t.Fatal(err) } if len(input.Overrides) != 2 || input.Overrides[0].Value != false || input.Overrides[1].Value != false { t.Fatalf("boolean overrides must remain typed: %+v", input.Overrides) } selected, err := loadConfigInput(input, command) if err != nil { t.Fatal(err) } if selected.Result.Config.Reporting.Enabled || selected.Result.Config.Diagnostics.Debug || selected.Result.Config.Reporting.StdoutDir != "reports" { t.Fatalf("%s did not honor explicit false: %+v", command, selected.Result.Config) } } processInput, _, err := parseConfigInput("process", []string{"input.json", "--glossary", "glossary.yml", "--config", main, "--overlay", "testing"}, io.Discard) if err != nil { t.Fatal(err) } processConfig, err := loadConfigInput(processInput, "process") if err != nil { t.Fatal(err) } if got, err := selectedReportDir(processConfig.Result.Config, filepath.Join("out", "corrected.json"), processInput.ReportDirSet); err != nil || got != "out" { t.Fatalf("overlay stdout directory redirected file output: %q %v", got, err) } } func TestReportDirectorySelectionOnlyUsesActiveDestination(t *testing.T) { cfg := config.Config{Reporting: config.Reporting{Enabled: true, StdoutDir: "reports"}} if got, err := selectedReportDir(cfg, "", false); err != nil || got != "reports" { t.Fatalf("stdout destination: %q %v", got, err) } if got, err := selectedReportDir(cfg, filepath.Join("out", "corrected.json"), false); err != nil || got != "out" { t.Fatalf("file destination ignored stdout setting: %q %v", got, err) } if got, err := selectedReportDir(cfg, filepath.Join("out", "corrected.json"), true); err != nil || got != "reports" { t.Fatalf("explicit file report directory: %q %v", got, err) } cfg.Reporting.StdoutDir = "" if got, err := selectedReportDir(cfg, filepath.Join("out", "corrected.json"), false); err != nil || got != "out" { t.Fatalf("inactive blank configured report directory rejected for file output: %q %v", got, err) } if _, err := selectedReportDir(cfg, filepath.Join("out", "corrected.json"), true); err == nil { t.Fatal("active explicit blank report directory accepted for file output") } if _, err := selectedReportDir(cfg, "", false); err == nil { t.Fatal("enabled stdout reporting accepted blank destination") } cfg.Reporting.Enabled = false if got, err := selectedReportDir(cfg, filepath.Join("out", "corrected.json"), true); err != nil || got != "" { t.Fatalf("disabled reporting probed destination: %q %v", got, err) } } func TestBareBooleanFlagsAndReportDirectoryOverride(t *testing.T) { input, _, err := parseConfigInput("process", []string{"input.json", "--glossary", "glossary.yml", "--reporting", "--debug", "--report-dir", "reports"}, io.Discard) if err != nil { t.Fatal(err) } values := map[string]any{} for _, override := range input.Overrides { values[override.Path.String()] = override.Value } if values[ck.Keys("reporting", "enabled").String()] != true || values[ck.Keys("diagnostics", "debug").String()] != true || values[ck.Keys("reporting", "stdout_dir").String()] != "reports" { t.Fatalf("incorrect visited overrides: %+v", values) } if !input.ReportDirSet { t.Fatal("explicit --report-dir presence was not recorded") } withoutReportDir, _, err := parseConfigInput("process", []string{"input.json", "--glossary", "glossary.yml"}, io.Discard) if err != nil { t.Fatal(err) } if withoutReportDir.ReportDirSet { t.Fatal("absent --report-dir was recorded as explicit") } }