package config import ( "os" "path/filepath" "strings" "testing" ) func TestOmittingNormalizeSpeakersDoesNotRequireSpeakers(t *testing.T) { dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") _, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, PreprocessingModules: "validate-raw", PostprocessingModules: DefaultPostprocessingModules, }) if err != nil { t.Fatalf("expected speakers file to be optional when normalize-speakers is omitted, got %v", err) } } func TestDuplicateInputFilesFailValidation(t *testing.T) { dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") _, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input, input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, }) if err == nil { t.Fatal("expected duplicate input error") } if !strings.Contains(err.Error(), "duplicate --input-file") { t.Fatalf("unexpected error: %v", err) } } func TestOutputSchemaDefaultsToIntermediate(t *testing.T) { t.Setenv(OutputSchemaEnv, "") dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") cfg, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, }) if err != nil { t.Fatalf("config failed: %v", err) } if cfg.OutputSchema != DefaultOutputSchema { t.Fatalf("output schema = %q, want %q", cfg.OutputSchema, DefaultOutputSchema) } } func TestOutputSchemaAcceptsIntermediate(t *testing.T) { t.Setenv(OutputSchemaEnv, "") dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") cfg, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, OutputSchema: OutputSchemaIntermediate, PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, }) if err != nil { t.Fatalf("config failed: %v", err) } if cfg.OutputSchema != OutputSchemaIntermediate { t.Fatalf("output schema = %q, want %q", cfg.OutputSchema, OutputSchemaIntermediate) } } func TestOutputSchemaAcceptsMinimal(t *testing.T) { t.Setenv(OutputSchemaEnv, "") dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") cfg, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, OutputSchema: OutputSchemaMinimal, PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, }) if err != nil { t.Fatalf("config failed: %v", err) } if cfg.OutputSchema != OutputSchemaMinimal { t.Fatalf("output schema = %q, want %q", cfg.OutputSchema, OutputSchemaMinimal) } } func TestOutputSchemaAcceptsFull(t *testing.T) { t.Setenv(OutputSchemaEnv, "") dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") cfg, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, OutputSchema: OutputSchemaFull, PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, }) if err != nil { t.Fatalf("config failed: %v", err) } if cfg.OutputSchema != OutputSchemaFull { t.Fatalf("output schema = %q, want %q", cfg.OutputSchema, OutputSchemaFull) } } func TestOutputSchemaUsesEnvWhenFlagOmitted(t *testing.T) { t.Setenv(OutputSchemaEnv, OutputSchemaFull) dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") cfg, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, }) if err != nil { t.Fatalf("config failed: %v", err) } if cfg.OutputSchema != OutputSchemaFull { t.Fatalf("output schema = %q, want %q", cfg.OutputSchema, OutputSchemaFull) } } func TestOutputSchemaFlagOverridesEnv(t *testing.T) { t.Setenv(OutputSchemaEnv, OutputSchemaFull) dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") cfg, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, OutputSchema: OutputSchemaMinimal, PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, }) if err != nil { t.Fatalf("config failed: %v", err) } if cfg.OutputSchema != OutputSchemaMinimal { t.Fatalf("output schema = %q, want %q", cfg.OutputSchema, OutputSchemaMinimal) } } func TestOutputSchemaRejectsInvalidEnvValue(t *testing.T) { t.Setenv(OutputSchemaEnv, "compact") dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") _, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, }) if err == nil { t.Fatal("expected output schema error") } if !strings.Contains(err.Error(), "--output-schema must be one of") { t.Fatalf("unexpected error: %v", err) } } func TestOutputSchemaRejectsUnknownValue(t *testing.T) { t.Setenv(OutputSchemaEnv, "") dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") _, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, OutputSchema: "compact", PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, }) if err == nil { t.Fatal("expected output schema error") } if !strings.Contains(err.Error(), "--output-schema must be one of") { t.Fatalf("unexpected error: %v", err) } } func TestOutputSchemaRejectsLegacyValues(t *testing.T) { tests := []string{"default", "minimal", "seriatim"} for _, legacy := range tests { t.Run(legacy, func(t *testing.T) { t.Setenv(OutputSchemaEnv, "") dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") _, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, OutputSchema: legacy, PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, }) if err == nil { t.Fatal("expected output schema error") } if !strings.Contains(err.Error(), "--output-schema must be one of") { t.Fatalf("unexpected error: %v", err) } }) } } func TestOverlapWordRunGapDefaultsTo1(t *testing.T) { t.Setenv(OverlapWordRunGapEnv, "") dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") cfg, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, }) if err != nil { t.Fatalf("config failed: %v", err) } if cfg.OverlapWordRunGap != DefaultOverlapWordRunGap { t.Fatalf("gap = %f, want %f", cfg.OverlapWordRunGap, DefaultOverlapWordRunGap) } } func TestOverlapWordRunGapUsesValidEnvOverride(t *testing.T) { t.Setenv(OverlapWordRunGapEnv, "1.25") dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") cfg, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, }) if err != nil { t.Fatalf("config failed: %v", err) } if cfg.OverlapWordRunGap != 1.25 { t.Fatalf("gap = %f, want 1.25", cfg.OverlapWordRunGap) } } func TestOverlapWordRunGapRejectsInvalidEnvOverride(t *testing.T) { tests := []struct { name string value string want string }{ {name: "non-numeric", value: "fast", want: "must be a positive number"}, {name: "zero", value: "0", want: "must be positive"}, {name: "negative", value: "-0.1", want: "must be positive"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Setenv(OverlapWordRunGapEnv, test.value) dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") _, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, }) if err == nil { t.Fatal("expected error") } if !strings.Contains(err.Error(), test.want) { t.Fatalf("expected error to contain %q, got %v", test.want, err) } }) } } func TestWordRunReorderWindowDefaultsTo1(t *testing.T) { t.Setenv(WordRunReorderWindowEnv, "") dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") cfg, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, }) if err != nil { t.Fatalf("config failed: %v", err) } if cfg.WordRunReorderWindow != DefaultWordRunReorderWindow { t.Fatalf("window = %f, want %f", cfg.WordRunReorderWindow, DefaultWordRunReorderWindow) } } func TestWordRunReorderWindowUsesValidEnvOverride(t *testing.T) { t.Setenv(WordRunReorderWindowEnv, "0.2") dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") cfg, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, }) if err != nil { t.Fatalf("config failed: %v", err) } if cfg.WordRunReorderWindow != 0.2 { t.Fatalf("window = %f, want 0.2", cfg.WordRunReorderWindow) } } func TestWordRunReorderWindowRejectsInvalidEnvOverride(t *testing.T) { tests := []struct { name string value string want string }{ {name: "non-numeric", value: "fast", want: "must be a positive number"}, {name: "zero", value: "0", want: "must be positive"}, {name: "negative", value: "-0.1", want: "must be positive"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Setenv(WordRunReorderWindowEnv, test.value) dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") _, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, }) if err == nil { t.Fatal("expected error") } if !strings.Contains(err.Error(), test.want) { t.Fatalf("expected error to contain %q, got %v", test.want, err) } }) } } func TestBackchannelMaxDurationDefaultsTo2(t *testing.T) { t.Setenv(BackchannelMaxDurationEnv, "") dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") cfg, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, }) if err != nil { t.Fatalf("config failed: %v", err) } if cfg.BackchannelMaxDuration != DefaultBackchannelMaxDuration { t.Fatalf("backchannel max duration = %f, want %f", cfg.BackchannelMaxDuration, DefaultBackchannelMaxDuration) } } func TestBackchannelMaxDurationUsesValidEnvOverride(t *testing.T) { t.Setenv(BackchannelMaxDurationEnv, "1.5") dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") cfg, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, }) if err != nil { t.Fatalf("config failed: %v", err) } if cfg.BackchannelMaxDuration != 1.5 { t.Fatalf("backchannel max duration = %f, want 1.5", cfg.BackchannelMaxDuration) } } func TestBackchannelMaxDurationRejectsInvalidEnvOverride(t *testing.T) { assertPositiveFloatEnvValidation(t, BackchannelMaxDurationEnv) } func TestFillerMaxDurationDefaultsTo125(t *testing.T) { t.Setenv(FillerMaxDurationEnv, "") dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") cfg, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, }) if err != nil { t.Fatalf("config failed: %v", err) } if cfg.FillerMaxDuration != DefaultFillerMaxDuration { t.Fatalf("filler max duration = %f, want %f", cfg.FillerMaxDuration, DefaultFillerMaxDuration) } } func TestFillerMaxDurationUsesValidEnvOverride(t *testing.T) { t.Setenv(FillerMaxDurationEnv, "1.75") dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") cfg, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, }) if err != nil { t.Fatalf("config failed: %v", err) } if cfg.FillerMaxDuration != 1.75 { t.Fatalf("filler max duration = %f, want 1.75", cfg.FillerMaxDuration) } } func TestFillerMaxDurationRejectsInvalidEnvOverride(t *testing.T) { assertPositiveFloatEnvValidation(t, FillerMaxDurationEnv) } func TestCoalesceGapDefaultsTo3(t *testing.T) { dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") cfg, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, }) if err != nil { t.Fatalf("config failed: %v", err) } if cfg.CoalesceGap != DefaultCoalesceGap { t.Fatalf("coalesce gap = %f, want %f", cfg.CoalesceGap, DefaultCoalesceGap) } } func TestCoalesceGapUsesValidOverride(t *testing.T) { dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") cfg, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, CoalesceGap: "1.5", }) if err != nil { t.Fatalf("config failed: %v", err) } if cfg.CoalesceGap != 1.5 { t.Fatalf("coalesce gap = %f, want 1.5", cfg.CoalesceGap) } } func TestCoalesceGapAllowsZero(t *testing.T) { dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") cfg, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, CoalesceGap: "0", }) if err != nil { t.Fatalf("config failed: %v", err) } if cfg.CoalesceGap != 0 { t.Fatalf("coalesce gap = %f, want 0", cfg.CoalesceGap) } } func TestCoalesceGapRejectsInvalidOverride(t *testing.T) { tests := []struct { name string value string want string }{ {name: "non-numeric", value: "fast", want: "must be a non-negative number"}, {name: "negative", value: "-0.1", want: "must be non-negative"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") _, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, CoalesceGap: test.value, }) if err == nil { t.Fatal("expected error") } if !strings.Contains(err.Error(), test.want) { t.Fatalf("expected error to contain %q, got %v", test.want, err) } }) } } func TestNewTrimConfigRequiresInputAndOutput(t *testing.T) { dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "trimmed.json") _, err := NewTrimConfig(TrimOptions{ OutputFile: output, Keep: "1", }) if err == nil || !strings.Contains(err.Error(), "--input-file is required") { t.Fatalf("expected input-file required error, got %v", err) } _, err = NewTrimConfig(TrimOptions{ InputFile: input, Keep: "1", }) if err == nil || !strings.Contains(err.Error(), "--output-file is required") { t.Fatalf("expected output-file required error, got %v", err) } } func TestNewTrimConfigRequiresExactlyOneSelectorFlag(t *testing.T) { dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "trimmed.json") _, err := NewTrimConfig(TrimOptions{ InputFile: input, OutputFile: output, }) if err == nil || !strings.Contains(err.Error(), "exactly one of --keep or --remove is required") { t.Fatalf("expected missing selector error, got %v", err) } _, err = NewTrimConfig(TrimOptions{ InputFile: input, OutputFile: output, Keep: "1", Remove: "2", }) if err == nil || !strings.Contains(err.Error(), "mutually exclusive") { t.Fatalf("expected mutually exclusive selector error, got %v", err) } } func TestNewTrimConfigAcceptsOutputSchemaOverride(t *testing.T) { dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "trimmed.json") reportPath := filepath.Join(dir, "report.json") cfg, err := NewTrimConfig(TrimOptions{ InputFile: input, OutputFile: output, ReportFile: reportPath, Remove: "3-5", OutputSchema: OutputSchemaMinimal, AllowEmpty: true, }) if err != nil { t.Fatalf("config failed: %v", err) } if cfg.Mode != "remove" { t.Fatalf("mode = %q, want remove", cfg.Mode) } if cfg.Selector != "3-5" { t.Fatalf("selector = %q, want 3-5", cfg.Selector) } if cfg.OutputSchema != OutputSchemaMinimal { t.Fatalf("output schema = %q, want %q", cfg.OutputSchema, OutputSchemaMinimal) } if !cfg.AllowEmpty { t.Fatal("allow empty should be true") } if cfg.ReportFile != reportPath { t.Fatalf("report file = %q, want %q", cfg.ReportFile, reportPath) } } func TestNewTrimConfigRejectsInvalidOutputSchemaOverride(t *testing.T) { dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "trimmed.json") _, err := NewTrimConfig(TrimOptions{ InputFile: input, OutputFile: output, Keep: "1", OutputSchema: "compact", }) if err == nil { t.Fatal("expected output schema validation error") } if !strings.Contains(err.Error(), "--output-schema must be one of") { t.Fatalf("unexpected error: %v", err) } } func TestNewNormalizeConfigRequiresInputFile(t *testing.T) { dir := t.TempDir() output := filepath.Join(dir, "normalized.json") _, err := NewNormalizeConfig(NormalizeOptions{ OutputFile: output, OutputModules: DefaultOutputModules, }) if err == nil { t.Fatal("expected input-file required error") } if !strings.Contains(err.Error(), "--input-file is required") { t.Fatalf("unexpected error: %v", err) } } func TestNewNormalizeConfigRequiresOutputFile(t *testing.T) { dir := t.TempDir() input := writeTempFile(t, dir, "input.json") _, err := NewNormalizeConfig(NormalizeOptions{ InputFile: input, OutputModules: DefaultOutputModules, }) if err == nil { t.Fatal("expected output-file required error") } if !strings.Contains(err.Error(), "--output-file is required") { t.Fatalf("unexpected error: %v", err) } } func TestNewNormalizeConfigResolvesOutputSchemaDefaultAndEnv(t *testing.T) { dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "normalized.json") t.Setenv(OutputSchemaEnv, "") cfg, err := NewNormalizeConfig(NormalizeOptions{ InputFile: input, OutputFile: output, OutputModules: DefaultOutputModules, }) if err != nil { t.Fatalf("config failed: %v", err) } if cfg.OutputSchema != DefaultOutputSchema { t.Fatalf("output schema = %q, want %q", cfg.OutputSchema, DefaultOutputSchema) } t.Setenv(OutputSchemaEnv, OutputSchemaMinimal) cfg, err = NewNormalizeConfig(NormalizeOptions{ InputFile: input, OutputFile: output, OutputModules: DefaultOutputModules, }) if err != nil { t.Fatalf("config failed: %v", err) } if cfg.OutputSchema != OutputSchemaMinimal { t.Fatalf("output schema = %q, want %q", cfg.OutputSchema, OutputSchemaMinimal) } } func TestNewNormalizeConfigRejectsInvalidOutputSchema(t *testing.T) { dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "normalized.json") _, err := NewNormalizeConfig(NormalizeOptions{ InputFile: input, OutputFile: output, OutputSchema: "compact", OutputModules: DefaultOutputModules, }) if err == nil { t.Fatal("expected output schema error") } if !strings.Contains(err.Error(), "--output-schema must be one of") { t.Fatalf("unexpected error: %v", err) } } func TestNewNormalizeConfigRejectsUnknownOutputModule(t *testing.T) { dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "normalized.json") _, err := NewNormalizeConfig(NormalizeOptions{ InputFile: input, OutputFile: output, OutputModules: "json,yaml", }) if err == nil { t.Fatal("expected output module error") } if !strings.Contains(err.Error(), "unknown output module") { t.Fatalf("unexpected error: %v", err) } } func assertPositiveFloatEnvValidation(t *testing.T, envName string) { t.Helper() tests := []struct { name string value string want string }{ {name: "non-numeric", value: "fast", want: "must be a positive number"}, {name: "zero", value: "0", want: "must be positive"}, {name: "negative", value: "-0.1", want: "must be positive"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Setenv(envName, test.value) dir := t.TempDir() input := writeTempFile(t, dir, "input.json") output := filepath.Join(dir, "merged.json") _, err := NewMergeConfig(MergeOptions{ InputFiles: []string{input}, OutputFile: output, InputReader: DefaultInputReader, OutputModules: DefaultOutputModules, PreprocessingModules: DefaultPreprocessingModules, PostprocessingModules: DefaultPostprocessingModules, }) if err == nil { t.Fatal("expected error") } if !strings.Contains(err.Error(), test.want) { t.Fatalf("expected error to contain %q, got %v", test.want, err) } }) } } func writeTempFile(t *testing.T, dir string, name string) string { t.Helper() path := filepath.Join(dir, name) if err := os.WriteFile(path, []byte("{}\n"), 0o600); err != nil { t.Fatalf("write temp file: %v", err) } return path }