Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion internal/diff/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ type Diff struct {
Type DiffType `json:"type"`
Operation DiffOperation `json:"operation"` // create, alter, drop, replace
Path string `json:"path"`
Source DiffSource `json:"source,omitempty"`
Source DiffSource `json:"-"` // interface; not JSON-serializable (see #305)
}

type ddlDiff struct {
Expand Down
61 changes: 61 additions & 0 deletions internal/plan/plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,3 +260,64 @@ func TestPlanJSONLoadedSummary(t *testing.T) {
t.Error("Summary should not say \"No changes detected\" when there are changes")
}
}

func TestPlanDebugJSONRoundTrip(t *testing.T) {
// Issue #305: Plans generated with --debug produce JSON that cannot be
// deserialized by FromJSON() because the Diff.Source field is a Go interface
// (DiffSource) that json.Unmarshal cannot reconstruct.
oldSQL := `CREATE TABLE users (
id integer NOT NULL
);`

newSQL := `CREATE TABLE users (
id integer NOT NULL,
name text NOT NULL
);
CREATE TABLE posts (
id integer NOT NULL,
title text NOT NULL
);`

oldIR := parseSQL(t, oldSQL)
newIR := parseSQL(t, newSQL)
diffs := diff.GenerateMigration(oldIR, newIR, "public")

p := NewPlan(diffs)

// Serialize with debug mode (includes SourceDiffs; Diff.Source is excluded via json:"-")
debugJSON, err := p.ToJSONWithDebug(true)
if err != nil {
t.Fatalf("Failed to serialize plan with debug: %v", err)
}

// Deserialize - this should succeed
loaded, err := FromJSON([]byte(debugJSON))
if err != nil {
t.Fatalf("Failed to deserialize debug plan JSON: %v", err)
}

Comment on lines +294 to +298
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: consider asserting that debug mode actually emitted source_diffs (e.g., len(loaded.SourceDiffs) > 0). It makes the test more robust by ensuring ToJSONWithDebug(true) continues to include debug diffs, rather than the test passing if that output changes later.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added len(loaded.SourceDiffs) == 0 assertion to verify debug mode actually emits SourceDiffs.

// Verify debug mode actually included SourceDiffs
if len(loaded.SourceDiffs) == 0 {
t.Error("Debug plan should include SourceDiffs")
}

// Verify the loaded plan has valid groups and steps
if len(loaded.Groups) == 0 {
t.Error("Loaded plan should have at least one execution group")
}

// Re-serialize without debug and verify round-trip stability
normalJSON, err := loaded.ToJSON()
if err != nil {
t.Fatalf("Failed to re-serialize loaded plan: %v", err)
}

loaded2, err := FromJSON([]byte(normalJSON))
if err != nil {
t.Fatalf("Failed to deserialize re-serialized plan: %v", err)
}

if len(loaded2.Groups) != len(loaded.Groups) {
t.Errorf("Group count mismatch: got %d, want %d", len(loaded2.Groups), len(loaded.Groups))
}
}