diff --git a/cmd/dry_run_test.go b/cmd/dry_run_test.go index eb8a7d1..ed1eb5c 100644 --- a/cmd/dry_run_test.go +++ b/cmd/dry_run_test.go @@ -16,6 +16,8 @@ package cmd import ( "bytes" + "encoding/json" + "io" "testing" "github.com/spf13/cobra" @@ -99,6 +101,61 @@ func TestDryRunDisplayPath(t *testing.T) { } } +func TestRenderOperationTextUsesClosure(t *testing.T) { + var stdout bytes.Buffer + cmd := renderOperationTestCommand(&stdout, "text") + + results := []jsonOperationResult{newJSONOperationResult(jsonStatusPlanned, "folder", nil, plannedMetadata("folder", "/Projects"))} + err := renderOperation(cmd, nil, results, nil, func(w io.Writer) error { + return writeDryRunLine(w, "create directory", "/Projects") + }) + if err != nil { + t.Fatalf("renderOperation error: %v", err) + } + + if got, want := stdout.String(), "Would create directory /Projects\n"; got != want { + t.Fatalf("text output = %q, want %q", got, want) + } +} + +func TestRenderOperationJSONRendersEnvelopeAndSkipsClosure(t *testing.T) { + var stdout bytes.Buffer + cmd := renderOperationTestCommand(&stdout, "json") + + input := mkdirInput{Path: "/Projects", DryRun: true} + results := []jsonOperationResult{newJSONOperationResult(jsonStatusPlanned, "folder", input, plannedMetadata("folder", "/Projects"))} + warnings := []jsonWarning{{Code: jsonWarningCodeSkippedSymlink, Message: "skipped symlink", Path: "/link"}} + + err := renderOperation(cmd, input, results, warnings, func(w io.Writer) error { + t.Fatalf("text closure called in JSON mode") + return nil + }) + if err != nil { + t.Fatalf("renderOperation error: %v", err) + } + + var got jsonOperationOutput + if err := json.Unmarshal(stdout.Bytes(), &got); err != nil { + t.Fatalf("decode output: %v (raw %s)", err, stdout.String()) + } + if !got.OK || got.SchemaVersion != jsonSchemaVersion || got.Command != "render-op" { + t.Fatalf("envelope = %+v, want ok/schema_version/command populated", got) + } + if len(got.Results) != 1 || got.Results[0].Status != jsonStatusPlanned || got.Results[0].Kind != "folder" { + t.Fatalf("results = %+v, want one planned folder result", got.Results) + } + if len(got.Warnings) != 1 || got.Warnings[0].Code != jsonWarningCodeSkippedSymlink { + t.Fatalf("warnings = %+v, want skipped_symlink passthrough", got.Warnings) + } +} + +func renderOperationTestCommand(stdout *bytes.Buffer, format string) *cobra.Command { + cmd := &cobra.Command{Use: "render-op"} + cmd.SetOut(stdout) + cmd.Flags().String(outputFlag, format, "") + return cmd +} + func TestPlannedMetadata(t *testing.T) { got := plannedMetadata("file", "/Reports/Old.PDF") if got.Type != "file" || got.PathDisplay != "/Reports/Old.PDF" || got.PathLower != "/reports/old.pdf" { diff --git a/cmd/put.go b/cmd/put.go index 06a9926..3015633 100644 --- a/cmd/put.go +++ b/cmd/put.go @@ -823,7 +823,7 @@ func plannedPutFolderResult(src, dst string) putResult { } func renderPlannedPutResults(cmd *cobra.Command, input putCommandInput, results []putResult, warnings []jsonWarning) error { - return commandOutput(cmd).Render(func(w io.Writer) error { + return renderOperation(cmd, input, putOperationResults(results), warnings, func(w io.Writer) error { for _, result := range results { if result.Kind == putKindFolder { if err := writeDryRunLine(w, "create directory", result.Input.Target); err != nil { @@ -836,7 +836,7 @@ func renderPlannedPutResults(cmd *cobra.Command, input putCommandInput, results } } return nil - }, newJSONCommandOperationOutput(cmd, input, putOperationResults(results), warnings)) + }) } // Keep traversal semantics aligned with putRecursiveInternal. Dry-run walks the