package processor import ( "encoding/json" "strings" "testing" "github.com/PaesslerAG/jsonpath" ) // fi ndMatchingPaths finds nodes in a JSON document that match the given JSONPath func findMatchingPaths(jsonDoc interface{}, path string) ([]interface{}, error) { // Use the existing jsonpath library to extract values result, err := jsonpath.Get(path, jsonDoc) if err != nil { return nil, err } // Convert the result to a slice var values []interface{} switch v := result.(type) { case []interface{}: values = v default: values = []interface{}{v} } return values, nil } // TestJSONProcessor_Process_NumericValues tests processing numeric JSON values func TestJSONProcessor_Process_NumericValues(t *testing.T) { content := `{ "books": [ { "title": "The Go Programming Language", "price": 44.95 }, { "title": "Go in Action", "price": 5.95 } ] }` expected := `{ "books": [ { "title": "The Go Programming Language", "price": 89.9 }, { "title": "Go in Action", "price": 11.9 } ] }` p := &JSONProcessor{} result, modCount, matchCount, err := p.ProcessContent(content, "$.books[*]", "v.price=v.price*2") if err != nil { t.Fatalf("Error processing content: %v", err) } if matchCount != 2 { t.Errorf("Expected 2 matches, got %d", matchCount) } if modCount != 2 { t.Errorf("Expected 2 modifications, got %d", modCount) } // Compare parsed JSON objects instead of formatted strings var resultObj map[string]interface{} if err := json.Unmarshal([]byte(result), &resultObj); err != nil { t.Fatalf("Failed to parse result JSON: %v", err) } var expectedObj map[string]interface{} if err := json.Unmarshal([]byte(expected), &expectedObj); err != nil { t.Fatalf("Failed to parse expected JSON: %v", err) } // Compare the first book's price resultBooks, ok := resultObj["books"].([]interface{}) if !ok || len(resultBooks) < 1 { t.Fatalf("Expected books array in result") } resultBook1, ok := resultBooks[0].(map[string]interface{}) if !ok { t.Fatalf("Expected first book to be an object") } resultPrice1, ok := resultBook1["price"].(float64) if !ok { t.Fatalf("Expected numeric price in first book") } if resultPrice1 != 89.9 { t.Errorf("Expected first book price to be 89.9, got %v", resultPrice1) } // Compare the second book's price resultBook2, ok := resultBooks[1].(map[string]interface{}) if !ok { t.Fatalf("Expected second book to be an object") } resultPrice2, ok := resultBook2["price"].(float64) if !ok { t.Fatalf("Expected numeric price in second book") } if resultPrice2 != 11.9 { t.Errorf("Expected second book price to be 11.9, got %v", resultPrice2) } } // TestJSONProcessor_Process_StringValues tests processing string JSON values func TestJSONProcessor_Process_StringValues(t *testing.T) { content := `{ "config": { "maxItems": "100", "itemTimeoutSecs": "30", "retryCount": "5" } }` p := &JSONProcessor{} result, modCount, matchCount, err := p.ProcessContent(content, "$.config", "for k,vi in pairs(v) do v[k]=vi*2 end") if err != nil { t.Fatalf("Error processing content: %v", err) } // Debug info t.Logf("Result: %s", result) t.Logf("Match count: %d, Mod count: %d", matchCount, modCount) if matchCount != 1 { t.Errorf("Expected 1 matches, got %d", matchCount) } if modCount != 1 { t.Errorf("Expected 1 modifications, got %d", modCount) } // Check that all expected values are in the result if !strings.Contains(result, `"maxItems": 200`) { t.Errorf("Result missing expected value: maxItems=200") } if !strings.Contains(result, `"itemTimeoutSecs": 60`) { t.Errorf("Result missing expected value: itemTimeoutSecs=60") } if !strings.Contains(result, `"retryCount": 10`) { t.Errorf("Result missing expected value: retryCount=10") } } // TestJSONProcessor_FindNodes tests the JSONPath implementation func TestJSONProcessor_FindNodes(t *testing.T) { // Test cases for JSONPath implementation testCases := []struct { name string jsonData string path string expectLen int expectErr bool }{ { name: "Root element", jsonData: `{"name": "root", "value": 100}`, path: "$", expectLen: 1, expectErr: false, }, { name: "Direct property", jsonData: `{"name": "test", "value": 100}`, path: "$.value", expectLen: 1, expectErr: false, }, { name: "Array access", jsonData: `{"items": [10, 20, 30]}`, path: "$.items[1]", expectLen: 1, expectErr: false, }, { name: "All array elements", jsonData: `{"items": [10, 20, 30]}`, path: "$.items[*]", expectLen: 3, expectErr: false, }, { name: "Nested property", jsonData: `{"user": {"name": "John", "age": 30}}`, path: "$.user.age", expectLen: 1, expectErr: false, }, { name: "Array of objects", jsonData: `{"users": [{"name": "John"}, {"name": "Jane"}]}`, path: "$.users[*].name", expectLen: 2, expectErr: false, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { // Parse the JSON data var jsonDoc interface{} if err := json.Unmarshal([]byte(tc.jsonData), &jsonDoc); err != nil { t.Fatalf("Failed to parse test JSON: %v", err) } // Find nodes with the given path nodes, err := findMatchingPaths(jsonDoc, tc.path) // Check error expectation if tc.expectErr && err == nil { t.Errorf("Expected error but got none") } if !tc.expectErr && err != nil { t.Errorf("Unexpected error: %v", err) } // Skip further checks if we expected an error if tc.expectErr { return } // Check the number of nodes found if len(nodes) != tc.expectLen { t.Errorf("Expected %d nodes, got %d", tc.expectLen, len(nodes)) } }) } } // TestJSONProcessor_NestedModifications tests modifying nested JSON objects func TestJSONProcessor_NestedModifications(t *testing.T) { content := `{ "store": { "book": [ { "category": "reference", "title": "Learn Go in 24 Hours", "price": 10.99 }, { "category": "fiction", "title": "The Go Developer", "price": 8.99 } ] } }` expected := `{ "store": { "book": [ { "category": "reference", "price": 13.188, "title": "Learn Go in 24 Hours" }, { "category": "fiction", "price": 10.788, "title": "The Go Developer" } ] } }` p := &JSONProcessor{} result, modCount, matchCount, err := p.ProcessContent(content, "$.store.book[*].price", "v=v*1.2") if err != nil { t.Fatalf("Error processing content: %v", err) } if matchCount != 2 { t.Errorf("Expected 2 matches, got %d", matchCount) } if modCount != 2 { t.Errorf("Expected 2 modifications, got %d", modCount) } // Normalize whitespace for comparison normalizedResult := normalizeWhitespace(result) normalizedExpected := normalizeWhitespace(expected) if normalizedResult != normalizedExpected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } // TestJSONProcessor_StringManipulation tests string manipulation func TestJSONProcessor_StringManipulation(t *testing.T) { content := `{ "users": [ { "name": "john", "role": "admin" }, { "name": "alice", "role": "user" } ] }` expected := `{ "users": [ { "name": "JOHN", "role": "admin" }, { "name": "ALICE", "role": "user" } ] }` p := &JSONProcessor{} result, modCount, matchCount, err := p.ProcessContent(content, "$.users[*].name", "v = string.upper(v)") if err != nil { t.Fatalf("Error processing content: %v", err) } if matchCount != 2 { t.Errorf("Expected 2 matches, got %d", matchCount) } if modCount != 2 { t.Errorf("Expected 2 modifications, got %d", modCount) } // Normalize whitespace for comparison normalizedResult := normalizeWhitespace(result) normalizedExpected := normalizeWhitespace(expected) if normalizedResult != normalizedExpected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } // TestJSONProcessor_ComplexScript tests using more complex Lua scripts func TestJSONProcessor_ComplexScript(t *testing.T) { content := `{ "products": [ { "name": "Basic Widget", "price": 9.99, "discount": 0.1 }, { "name": "Premium Widget", "price": 19.99, "discount": 0.05 } ] }` expected := `{ "products": [ { "discount": 0.1, "name": "Basic Widget", "price": 8.991 }, { "discount": 0.05, "name": "Premium Widget", "price": 18.9905 } ] }` p := &JSONProcessor{} result, modCount, matchCount, err := p.ProcessContent(content, "$.products[*]", "v.price = round(v.price * (1 - v.discount), 4)") if err != nil { t.Fatalf("Error processing content: %v", err) } if matchCount != 2 { t.Errorf("Expected 2 matches, got %d", matchCount) } if modCount != 2 { t.Errorf("Expected 2 modifications, got %d", modCount) } // Normalize whitespace for comparison normalizedResult := normalizeWhitespace(result) normalizedExpected := normalizeWhitespace(expected) if normalizedResult != normalizedExpected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } // TestJSONProcessor_SpecificItemUpdate tests updating a specific item in an array func TestJSONProcessor_SpecificItemUpdate(t *testing.T) { content := `{ "items": [ {"id": 1, "name": "Item 1", "stock": 10}, {"id": 2, "name": "Item 2", "stock": 5}, {"id": 3, "name": "Item 3", "stock": 0} ] }` expected := ` { "items": [ { "id": 1, "name": "Item 1", "stock": 10 }, { "id": 2, "name": "Item 2", "stock": 15 }, { "id": 3, "name": "Item 3", "stock": 0 } ] } ` p := &JSONProcessor{} result, modCount, matchCount, err := p.ProcessContent(content, "$.items[1].stock", "v=v+10") if err != nil { t.Fatalf("Error processing content: %v", err) } if matchCount != 1 { t.Errorf("Expected 1 match, got %d", matchCount) } if modCount != 1 { t.Errorf("Expected 1 modification, got %d", modCount) } // Normalize whitespace for comparison normalizedResult := normalizeWhitespace(result) normalizedExpected := normalizeWhitespace(expected) if normalizedResult != normalizedExpected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } // TestJSONProcessor_RootElementUpdate tests updating the root element func TestJSONProcessor_RootElementUpdate(t *testing.T) { content := `{"value": 100}` expected := `{ "value": 200 }` p := &JSONProcessor{} result, modCount, matchCount, err := p.ProcessContent(content, "$.value", "v=v*2") if err != nil { t.Fatalf("Error processing content: %v", err) } if matchCount != 1 { t.Errorf("Expected 1 match, got %d", matchCount) } if modCount != 1 { t.Errorf("Expected 1 modification, got %d", modCount) } // Normalize whitespace for comparison normalizedResult := normalizeWhitespace(result) normalizedExpected := normalizeWhitespace(expected) if normalizedResult != normalizedExpected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } // TestJSONProcessor_AddNewField tests adding a new field to a JSON object func TestJSONProcessor_AddNewField(t *testing.T) { content := `{ "user": { "name": "John", "age": 30 } }` expected := `{ "user": { "age": 30, "email": "john@example.com", "name": "John" } }` p := &JSONProcessor{} result, modCount, matchCount, err := p.ProcessContent(content, "$.user", "v.email = 'john@example.com'") if err != nil { t.Fatalf("Error processing content: %v", err) } if matchCount != 1 { t.Errorf("Expected 1 match, got %d", matchCount) } if modCount != 1 { t.Errorf("Expected 1 modification, got %d", modCount) } // Normalize whitespace for comparison normalizedResult := normalizeWhitespace(result) normalizedExpected := normalizeWhitespace(expected) if normalizedResult != normalizedExpected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } // TestJSONProcessor_RemoveField tests removing a field from a JSON object func TestJSONProcessor_RemoveField(t *testing.T) { content := `{ "user": { "name": "John", "age": 30, "email": "john@example.com" } }` expected := `{ "user": { "email": "john@example.com", "name": "John" } }` p := &JSONProcessor{} result, modCount, matchCount, err := p.ProcessContent(content, "$.user", "v.age = nil") if err != nil { t.Fatalf("Error processing content: %v", err) } if matchCount != 1 { t.Errorf("Expected 1 match, got %d", matchCount) } if modCount != 1 { t.Errorf("Expected 1 modification, got %d", modCount) } // Normalize whitespace for comparison normalizedResult := normalizeWhitespace(result) normalizedExpected := normalizeWhitespace(expected) if normalizedResult != normalizedExpected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } // TestJSONProcessor_ArrayManipulation tests adding and manipulating array elements func TestJSONProcessor_ArrayManipulation(t *testing.T) { content := `{ "tags": ["go", "json", "lua"] }` expected := ` { "tags": [ "GO", "JSON", "LUA", "testing" ] }` p := &JSONProcessor{} result, modCount, matchCount, err := p.ProcessContent(content, "$.tags", ` -- Convert existing tags to uppercase for i=1, #v do v[i] = string.upper(v[i]) end -- Add a new tag v[#v+1] = "testing" `) if err != nil { t.Fatalf("Error processing content: %v", err) } if matchCount != 1 { t.Errorf("Expected 1 match, got %d", matchCount) } if modCount != 1 { t.Errorf("Expected 1 modification, got %d", modCount) } // Normalize whitespace for comparison normalizedResult := normalizeWhitespace(result) normalizedExpected := normalizeWhitespace(expected) if normalizedResult != normalizedExpected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } // TestJSONProcessor_ConditionalModification tests conditionally modifying values func TestJSONProcessor_ConditionalModification(t *testing.T) { content := `{ "products": [ { "name": "Product A", "price": 10.99, "inStock": true }, { "name": "Product B", "price": 5.99, "inStock": false }, { "name": "Product C", "price": 15.99, "inStock": true } ] }` expected := `{ "products": [ { "inStock": true, "name": "Product A", "price": 9.891 }, { "inStock": false, "name": "Product B", "price": 5.99 }, { "inStock": true, "name": "Product C", "price": 14.391 } ] }` p := &JSONProcessor{} result, modCount, matchCount, err := p.ProcessContent(content, "$.products[*]", ` if not v.inStock then return false end v.price = v.price * 0.9 `) if err != nil { t.Fatalf("Error processing content: %v", err) } if matchCount != 3 { t.Errorf("Expected 3 matches, got %d", matchCount) } if modCount != 2 { t.Errorf("Expected 2 modifications, got %d", modCount) } // Normalize whitespace for comparison normalizedResult := normalizeWhitespace(result) normalizedExpected := normalizeWhitespace(expected) if normalizedResult != normalizedExpected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } // TestJSONProcessor_DeepNesting tests manipulating deeply nested JSON structures func TestJSONProcessor_DeepNesting(t *testing.T) { content := `{ "company": { "departments": { "engineering": { "teams": { "frontend": { "members": 12, "projects": 5 }, "backend": { "members": 8, "projects": 3 } } } } } }` expected := `{ "company": { "departments": { "engineering": { "teams": { "backend": { "members": 8, "projects": 3, "status": "active" }, "frontend": { "members": 12, "projects": 5, "status": "active" } } } } } }` p := &JSONProcessor{} result, modCount, matchCount, err := p.ProcessContent(content, "$.company.departments.engineering.teams.*", ` v.status = "active" `) if err != nil { t.Fatalf("Error processing content: %v", err) } if matchCount != 2 { t.Errorf("Expected 2 matches, got %d", matchCount) } if modCount != 2 { t.Errorf("Expected 2 modifications, got %d", modCount) } // Normalize whitespace for comparison normalizedResult := normalizeWhitespace(result) normalizedExpected := normalizeWhitespace(expected) if normalizedResult != normalizedExpected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } // TestJSONProcessor_ComplexTransformation tests a complex transformation involving // multiple fields and calculations func TestJSONProcessor_ComplexTransformation(t *testing.T) { content := `{ "order": { "items": [ { "product": "Widget A", "quantity": 5, "price": 10.0 }, { "product": "Widget B", "quantity": 3, "price": 15.0 } ], "customer": { "name": "John Smith", "tier": "gold" } } }` expected := `{ "order": { "customer": { "name": "John Smith", "tier": "gold" }, "items": [ { "discounted_total": 45, "price": 10, "product": "Widget A", "quantity": 5, "total": 50 }, { "discounted_total": 40.5, "price": 15, "product": "Widget B", "quantity": 3, "total": 45 } ], "summary": { "discount": 9.5, "subtotal": 95, "total": 85.5, "total_items": 8 } } }` p := &JSONProcessor{} result, modCount, matchCount, err := p.ProcessContent(content, "$.order", ` -- Calculate item totals and apply discounts local discount_rate = 0.1 -- 10% discount for gold tier local subtotal = 0 local total_items = 0 for i, item in ipairs(v.items) do -- Calculate item total item.total = item.quantity * item.price -- Apply discount item.discounted_total = item.total * (1 - discount_rate) -- Add to running totals subtotal = subtotal + item.total total_items = total_items + item.quantity end -- Add order summary v.summary = { total_items = total_items, subtotal = subtotal, discount = subtotal * discount_rate, total = subtotal * (1 - discount_rate) } `) if err != nil { t.Fatalf("Error processing content: %v", err) } if matchCount != 1 { t.Errorf("Expected 1 match, got %d", matchCount) } if modCount != 1 { t.Errorf("Expected 1 modification, got %d", modCount) } // Normalize whitespace for comparison normalizedResult := normalizeWhitespace(result) normalizedExpected := normalizeWhitespace(expected) if normalizedResult != normalizedExpected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } // TestJSONProcessor_HandlingNullValues tests handling of null values in JSON func TestJSONProcessor_HandlingNullValues(t *testing.T) { content := `{ "data": { "value1": null, "value2": 42, "value3": "hello" } }` expected := `{ "data": { "value1": 0, "value2": 42, "value3": "hello" } }` p := &JSONProcessor{} result, modCount, matchCount, err := p.ProcessContent(content, "$.data.value1", ` v = 0 `) if err != nil { t.Fatalf("Error processing content: %v", err) } if matchCount != 1 { t.Errorf("Expected 1 match, got %d", matchCount) } if modCount != 1 { t.Errorf("Expected 1 modification, got %d", modCount) } // Normalize whitespace for comparison normalizedResult := normalizeWhitespace(result) normalizedExpected := normalizeWhitespace(expected) if normalizedResult != normalizedExpected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } // TestJSONProcessor_RestructuringData tests completely restructuring JSON data func TestJSONProcessor_RestructuringData(t *testing.T) { content := `{ "people": [ { "id": 1, "name": "Alice", "attributes": { "age": 25, "role": "developer" } }, { "id": 2, "name": "Bob", "attributes": { "age": 30, "role": "manager" } } ] }` expected := `{ "people": { "developers": [ { "age": 25, "id": 1, "name": "Alice" } ], "managers": [ { "age": 30, "id": 2, "name": "Bob" } ] } }` p := &JSONProcessor{} result, modCount, matchCount, err := p.ProcessContent(content, "$", ` -- Restructure the data local old_people = v.people local new_structure = { developers = {}, managers = {} } for _, person in ipairs(old_people) do local role = person.attributes.role local new_person = { id = person.id, name = person.name, age = person.attributes.age } if role == "developer" then table.insert(new_structure.developers, new_person) elseif role == "manager" then table.insert(new_structure.managers, new_person) end end v.people = new_structure `) if err != nil { t.Fatalf("Error processing content: %v", err) } if matchCount != 1 { t.Errorf("Expected 1 match, got %d", matchCount) } if modCount != 1 { t.Errorf("Expected 1 modification, got %d", modCount) } // Normalize whitespace for comparison normalizedResult := normalizeWhitespace(result) normalizedExpected := normalizeWhitespace(expected) if normalizedResult != normalizedExpected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } // TestJSONProcessor_FilteringArrayElements tests filtering elements from an array func TestJSONProcessor_FilteringArrayElements(t *testing.T) { content := `{ "numbers": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }` expected := `{ "numbers": [ 2, 4, 6, 8, 10 ] }` p := &JSONProcessor{} result, modCount, matchCount, err := p.ProcessContent(content, "$.numbers", ` -- Filter to keep only even numbers local filtered = {} for _, num in ipairs(v) do if num % 2 == 0 then table.insert(filtered, num) end end v = filtered `) if err != nil { t.Fatalf("Error processing content: %v", err) } if matchCount != 1 { t.Errorf("Expected 1 match, got %d", matchCount) } if modCount != 1 { t.Errorf("Expected 1 modification, got %d", modCount) } // Normalize whitespace for comparison normalizedResult := normalizeWhitespace(result) normalizedExpected := normalizeWhitespace(expected) if normalizedResult != normalizedExpected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } // TestJSONProcessor_RootNodeModification tests modifying the root node directly func TestJSONProcessor_RootNodeModification(t *testing.T) { content := `{ "name": "original", "value": 100 }` expected := `{ "description": "This is a completely modified root", "name": "modified", "values": [ 1, 2, 3 ] }` p := &JSONProcessor{} result, modCount, matchCount, err := p.ProcessContent(content, "$", ` -- Completely replace the root node v = { name = "modified", description = "This is a completely modified root", values = {1, 2, 3} } `) if err != nil { t.Fatalf("Error processing content: %v", err) } if matchCount != 1 { t.Errorf("Expected 1 match, got %d", matchCount) } if modCount != 1 { t.Errorf("Expected 1 modification, got %d", modCount) } // Normalize whitespace for comparison normalizedResult := normalizeWhitespace(result) normalizedExpected := normalizeWhitespace(expected) if normalizedResult != normalizedExpected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } }