package main import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestJSONPatcher_Add(t *testing.T) { tests := []struct { name string doc map[string]interface{} patch PatchOperation expected map[string]interface{} wantErr bool }{ { name: "add simple field", doc: map[string]interface{}{"id": "test"}, patch: PatchOperation{ Op: "add", Path: "/content", Value: "test content", }, expected: map[string]interface{}{ "id": "test", "content": "test content", }, wantErr: false, }, { name: "add nested field", doc: map[string]interface{}{"id": "test", "meta": map[string]interface{}{}}, patch: PatchOperation{ Op: "add", Path: "/meta/priority", Value: "high", }, expected: map[string]interface{}{ "id": "test", "meta": map[string]interface{}{"priority": "high"}, }, wantErr: false, }, { name: "add to existing field (overwrite)", doc: map[string]interface{}{"id": "test", "content": "old"}, patch: PatchOperation{ Op: "add", Path: "/content", Value: "new", }, expected: map[string]interface{}{ "id": "test", "content": "new", }, wantErr: false, }, { name: "add array field", doc: map[string]interface{}{"id": "test"}, patch: PatchOperation{ Op: "add", Path: "/tags", Value: []interface{}{"tag1", "tag2"}, }, expected: map[string]interface{}{ "id": "test", "tags": []interface{}{"tag1", "tag2"}, }, wantErr: false, }, { name: "add complex object", doc: map[string]interface{}{"id": "test"}, patch: PatchOperation{ Op: "add", Path: "/metadata", Value: map[string]interface{}{ "created": "2025-01-01", "version": 1, }, }, expected: map[string]interface{}{ "id": "test", "metadata": map[string]interface{}{ "created": "2025-01-01", "version": 1, }, }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { patcher := &JSONPatcher{} result, err := patcher.ApplyPatches(tt.doc, []PatchOperation{tt.patch}) if tt.wantErr { assert.Error(t, err) } else { require.NoError(t, err) assert.Equal(t, tt.expected, result) } }) } } func TestJSONPatcher_Remove(t *testing.T) { tests := []struct { name string doc map[string]interface{} patch PatchOperation expected map[string]interface{} wantErr bool }{ { name: "remove simple field", doc: map[string]interface{}{ "id": "test", "content": "test content", }, patch: PatchOperation{ Op: "remove", Path: "/content", }, expected: map[string]interface{}{ "id": "test", }, wantErr: false, }, { name: "remove nested field", doc: map[string]interface{}{ "id": "test", "meta": map[string]interface{}{"priority": "high", "tags": []string{"tag1"}}, }, patch: PatchOperation{ Op: "remove", Path: "/meta/priority", }, expected: map[string]interface{}{ "id": "test", "meta": map[string]interface{}{"tags": []string{"tag1"}}, }, wantErr: false, }, { name: "remove non-existent field", doc: map[string]interface{}{ "id": "test", }, patch: PatchOperation{ Op: "remove", Path: "/nonexistent", }, expected: map[string]interface{}{ "id": "test", }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { patcher := &JSONPatcher{} result, err := patcher.ApplyPatches(tt.doc, []PatchOperation{tt.patch}) if tt.wantErr { assert.Error(t, err) } else { require.NoError(t, err) assert.Equal(t, tt.expected, result) } }) } } func TestJSONPatcher_Replace(t *testing.T) { tests := []struct { name string doc map[string]interface{} patch PatchOperation expected map[string]interface{} wantErr bool }{ { name: "replace simple field", doc: map[string]interface{}{ "id": "test", "content": "old content", }, patch: PatchOperation{ Op: "replace", Path: "/content", Value: "new content", }, expected: map[string]interface{}{ "id": "test", "content": "new content", }, wantErr: false, }, { name: "replace nested field", doc: map[string]interface{}{ "id": "test", "meta": map[string]interface{}{"priority": "low"}, }, patch: PatchOperation{ Op: "replace", Path: "/meta/priority", Value: "high", }, expected: map[string]interface{}{ "id": "test", "meta": map[string]interface{}{"priority": "high"}, }, wantErr: false, }, { name: "replace non-existent field", doc: map[string]interface{}{ "id": "test", }, patch: PatchOperation{ Op: "replace", Path: "/nonexistent", Value: "value", }, expected: map[string]interface{}{ "id": "test", }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { patcher := &JSONPatcher{} result, err := patcher.ApplyPatches(tt.doc, []PatchOperation{tt.patch}) if tt.wantErr { assert.Error(t, err) } else { require.NoError(t, err) assert.Equal(t, tt.expected, result) } }) } } func TestJSONPatcher_Test(t *testing.T) { tests := []struct { name string doc map[string]interface{} patch PatchOperation wantErr bool }{ { name: "test field success", doc: map[string]interface{}{ "id": "test", "content": "test content", }, patch: PatchOperation{ Op: "test", Path: "/content", Value: "test content", }, wantErr: false, }, { name: "test field failure", doc: map[string]interface{}{ "id": "test", "content": "test content", }, patch: PatchOperation{ Op: "test", Path: "/content", Value: "different content", }, wantErr: true, }, { name: "test non-existent field", doc: map[string]interface{}{ "id": "test", }, patch: PatchOperation{ Op: "test", Path: "/nonexistent", Value: "value", }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { patcher := &JSONPatcher{} _, err := patcher.ApplyPatches(tt.doc, []PatchOperation{tt.patch}) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) } }) } } func TestJSONPatcher_Move(t *testing.T) { tests := []struct { name string doc map[string]interface{} patch PatchOperation expected map[string]interface{} wantErr bool }{ { name: "move field", doc: map[string]interface{}{ "id": "test", "content": "test content", }, patch: PatchOperation{ Op: "move", From: "/content", Path: "/title", }, expected: map[string]interface{}{ "id": "test", "title": "test content", }, wantErr: false, }, { name: "move non-existent field", doc: map[string]interface{}{ "id": "test", }, patch: PatchOperation{ Op: "move", From: "/nonexistent", Path: "/title", }, expected: map[string]interface{}{ "id": "test", }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { patcher := &JSONPatcher{} result, err := patcher.ApplyPatches(tt.doc, []PatchOperation{tt.patch}) if tt.wantErr { assert.Error(t, err) } else { require.NoError(t, err) assert.Equal(t, tt.expected, result) } }) } } func TestJSONPatcher_Copy(t *testing.T) { tests := []struct { name string doc map[string]interface{} patch PatchOperation expected map[string]interface{} wantErr bool }{ { name: "copy field", doc: map[string]interface{}{ "id": "test", "content": "test content", }, patch: PatchOperation{ Op: "copy", From: "/content", Path: "/title", }, expected: map[string]interface{}{ "id": "test", "content": "test content", "title": "test content", }, wantErr: false, }, { name: "copy non-existent field", doc: map[string]interface{}{ "id": "test", }, patch: PatchOperation{ Op: "copy", From: "/nonexistent", Path: "/title", }, expected: map[string]interface{}{ "id": "test", }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { patcher := &JSONPatcher{} result, err := patcher.ApplyPatches(tt.doc, []PatchOperation{tt.patch}) if tt.wantErr { assert.Error(t, err) } else { require.NoError(t, err) assert.Equal(t, tt.expected, result) } }) } } func TestJSONPatcher_Complex(t *testing.T) { tests := []struct { name string doc map[string]interface{} patches []PatchOperation expected map[string]interface{} wantErr bool }{ { name: "multiple operations", doc: map[string]interface{}{"id": "test"}, patches: []PatchOperation{ {Op: "add", Path: "/content", Value: "test"}, {Op: "add", Path: "/priority", Value: "high"}, {Op: "replace", Path: "/content", Value: "updated test"}, }, expected: map[string]interface{}{ "id": "test", "content": "updated test", "priority": "high", }, wantErr: false, }, { name: "test then modify", doc: map[string]interface{}{"id": "test", "version": 1}, patches: []PatchOperation{ {Op: "test", Path: "/version", Value: 1}, {Op: "replace", Path: "/version", Value: 2}, }, expected: map[string]interface{}{ "id": "test", "version": 2, }, wantErr: false, }, { name: "failed test stops execution", doc: map[string]interface{}{"id": "test", "version": 1}, patches: []PatchOperation{ {Op: "test", Path: "/version", Value: 2}, // This should fail {Op: "replace", Path: "/version", Value: 3}, }, expected: map[string]interface{}{ "id": "test", "version": 1, }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { patcher := &JSONPatcher{} result, err := patcher.ApplyPatches(tt.doc, tt.patches) if tt.wantErr { assert.Error(t, err) } else { require.NoError(t, err) assert.Equal(t, tt.expected, result) } }) } } func TestJSONPatcher_ParsePath(t *testing.T) { tests := []struct { name string path string expected []string }{ { name: "empty path", path: "", expected: []string{}, }, { name: "root path", path: "/", expected: []string{""}, }, { name: "simple path", path: "/content", expected: []string{"content"}, }, { name: "nested path", path: "/meta/priority", expected: []string{"meta", "priority"}, }, { name: "path with escaped characters", path: "/content~1title", expected: []string{"content/title"}, }, { name: "path with escaped slash", path: "/content~0title", expected: []string{"content~title"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { patcher := &JSONPatcher{} result := patcher.parsePath(tt.path) assert.Equal(t, tt.expected, result) }) } } func TestJSONPatcher_InvalidOperations(t *testing.T) { tests := []struct { name string patch PatchOperation }{ { name: "invalid operation", patch: PatchOperation{ Op: "invalid", Path: "/content", }, }, { name: "invalid path format", patch: PatchOperation{ Op: "add", Path: "content", // missing leading slash }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { patcher := &JSONPatcher{} doc := map[string]interface{}{"id": "test"} _, err := patcher.ApplyPatches(doc, []PatchOperation{tt.patch}) assert.Error(t, err) }) } }