diff --git a/internal/app/commands.go b/internal/app/commands.go index 3dfe9ca..4f93d02 100644 --- a/internal/app/commands.go +++ b/internal/app/commands.go @@ -48,7 +48,7 @@ func executeWithDependencies(args []string, stdout, stderr io.Writer, deps comma case "clean": err = cleanWithDependencies(ctx, cmdArgs, stdout, deps) case "config": - err = Config(ctx, cmdArgs, stdout) + err = configWithDependencies(ctx, cmdArgs, stdout, deps) default: fmt.Fprintf(stderr, "unknown command: %q\n\n", cmd) printUsage(stderr) diff --git a/internal/app/config_commands.go b/internal/app/config_commands.go index e47702d..72c20be 100644 --- a/internal/app/config_commands.go +++ b/internal/app/config_commands.go @@ -29,6 +29,11 @@ type inspectionConfig struct { // Config dispatches read-only pipeline configuration commands. func Config(ctx context.Context, args []string, out io.Writer) error { + return configWithDependencies(ctx, args, out, commandDependencies{}) +} + +func configWithDependencies(ctx context.Context, args []string, out io.Writer, deps commandDependencies) error { + deps = normalizeCommandDependencies(deps) if len(args) == 0 { return fmt.Errorf("config: expected subcommand: validate|show|sources|diff") } @@ -38,11 +43,11 @@ func Config(ctx context.Context, args []string, out io.Writer) error { } switch args[0] { case "validate": - return ConfigValidate(ctx, args[1:], out) + return configValidateWithDependencies(ctx, args[1:], out, deps) case "show": - return ConfigShow(ctx, args[1:], out) + return configShowWithDependencies(ctx, args[1:], out, deps) case "sources": - return ConfigSources(ctx, args[1:], out) + return configSourcesWithDependencies(ctx, args[1:], out, deps) case "diff": return ConfigDiff(ctx, args[1:], out) default: @@ -52,7 +57,11 @@ func Config(ctx context.Context, args []string, out io.Writer) error { // ConfigValidate validates one fully resolved effective pipeline without // loading session state or constructing runtime collaborators. -func ConfigValidate(_ context.Context, args []string, out io.Writer) error { +func ConfigValidate(ctx context.Context, args []string, out io.Writer) error { + return configValidateWithDependencies(ctx, args, out, commandDependencies{}) +} + +func configValidateWithDependencies(_ context.Context, args []string, out io.Writer, deps commandDependencies) error { flags, err := parseInspectionFlags("config validate", args, out) if err != nil { if errors.Is(err, flag.ErrHelp) { @@ -60,7 +69,7 @@ func ConfigValidate(_ context.Context, args []string, out io.Writer) error { } return err } - resolved, err := resolveInspectionConfig(flags) + resolved, err := resolveInspectionConfigWithDependencies(flags, deps) if err != nil { return fmt.Errorf("config validate: %w", err) } @@ -71,7 +80,11 @@ func ConfigValidate(_ context.Context, args []string, out io.Writer) error { // ConfigShow validates and renders one fully resolved effective pipeline // without loading session state or constructing runtime collaborators. -func ConfigShow(_ context.Context, args []string, out io.Writer) error { +func ConfigShow(ctx context.Context, args []string, out io.Writer) error { + return configShowWithDependencies(ctx, args, out, commandDependencies{}) +} + +func configShowWithDependencies(_ context.Context, args []string, out io.Writer, deps commandDependencies) error { flags, err := parseInspectionFlags("config show", args, out) if err != nil { if errors.Is(err, flag.ErrHelp) { @@ -79,7 +92,7 @@ func ConfigShow(_ context.Context, args []string, out io.Writer) error { } return err } - resolved, err := resolveInspectionConfig(flags) + resolved, err := resolveInspectionConfigWithDependencies(flags, deps) if err != nil { return fmt.Errorf("config show: %w", err) } @@ -93,7 +106,11 @@ func ConfigShow(_ context.Context, args []string, out io.Writer) error { // ConfigSources validates and reports deterministic effective configuration // ownership without loading session state or runtime collaborators. -func ConfigSources(_ context.Context, args []string, out io.Writer) error { +func ConfigSources(ctx context.Context, args []string, out io.Writer) error { + return configSourcesWithDependencies(ctx, args, out, commandDependencies{}) +} + +func configSourcesWithDependencies(_ context.Context, args []string, out io.Writer, deps commandDependencies) error { flags, err := parseInspectionFlags("config sources", args, out) if err != nil { if errors.Is(err, flag.ErrHelp) { @@ -101,7 +118,7 @@ func ConfigSources(_ context.Context, args []string, out io.Writer) error { } return err } - resolved, err := resolveInspectionConfig(flags) + resolved, err := resolveInspectionConfigWithDependencies(flags, deps) if err != nil { return fmt.Errorf("config sources: %w", err) } @@ -150,8 +167,8 @@ func parseInspectionFlags(command string, args []string, out io.Writer) (inspect return flags, nil } -func resolveInspectionConfig(flags inspectionFlags) (*inspectionConfig, error) { - pipelinePath, pipeline, err := loadPipelineConfig(flags.pipelinePath, config.PipelineLoadOptions{Profile: flags.profile.pointer()}) +func resolveInspectionConfigWithDependencies(flags inspectionFlags, deps commandDependencies) (*inspectionConfig, error) { + pipelinePath, pipeline, err := loadPipelineConfigWithDependencies(flags.pipelinePath, config.PipelineLoadOptions{Profile: flags.profile.pointer()}, deps) if err != nil { return nil, err } diff --git a/internal/app/config_commands_test.go b/internal/app/config_commands_test.go index f803ce6..da1dd80 100644 --- a/internal/app/config_commands_test.go +++ b/internal/app/config_commands_test.go @@ -47,6 +47,38 @@ func TestConfigCommandHelpAndDispatch(t *testing.T) { } } +func TestExecuteConfigUsesInvocationPipelineLoader(t *testing.T) { + dir := t.TempDir() + pipelinePath := filepath.Join(dir, "pipeline.yml") + mustWriteTestFile(t, pipelinePath, "workspace:\n root: "+filepath.Join(dir, "workspace")+"\nwhisperx:\n transcribe_url: https://transcription.example.com/transcribe\n") + + for _, subcommand := range []string{"validate", "show", "sources"} { + t.Run(subcommand, func(t *testing.T) { + calls := 0 + deps := commandDependencies{ + loadPipelineConfig: func(path string, opts config.PipelineLoadOptions) (*config.PipelineConfig, error) { + calls++ + return config.LoadPipelineWithOptions(path, opts) + }, + } + var stdout, stderr bytes.Buffer + code := executeWithDependencies( + []string{"config", subcommand, "--config", pipelinePath}, + &stdout, + &stderr, + deps, + executePlan, + ) + if code != 0 { + t.Fatalf("exit code = %d, want 0; stdout=%q stderr=%q", code, stdout.String(), stderr.String()) + } + if calls != 1 { + t.Fatalf("pipeline loader calls = %d, want 1", calls) + } + }) + } +} + func TestConfigValidateAndShowPipelineOnlyAreSideEffectFree(t *testing.T) { dir := t.TempDir() workspaceRoot := filepath.Join(dir, "workspace-does-not-exist")