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) } } // TestJSONProcessor_DateManipulation tests manipulating date strings in a JSON document func TestJSONProcessor_DateManipulation(t *testing.T) { content := `{ "events": [ { "name": "Conference", "date": "2023-06-15" }, { "name": "Workshop", "date": "2023-06-20" } ] }` expected := `{ "events": [ { "name": "Conference", "date": "2023-07-15" }, { "name": "Workshop", "date": "2023-07-20" } ] }` p := &JSONProcessor{} result, modCount, matchCount, err := p.ProcessContent(content, "$.events[*].date", ` local year, month, day = string.match(v, "(%d%d%d%d)-(%d%d)-(%d%d)") -- Postpone events by 1 month month = tonumber(month) + 1 if month > 12 then month = 1 year = tonumber(year) + 1 end v = string.format("%04d-%02d-%s", tonumber(year), month, day) `) 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) } // Parse results as JSON objects for deep comparison rather than string comparison var resultObj map[string]interface{} var expectedObj map[string]interface{} if err := json.Unmarshal([]byte(result), &resultObj); err != nil { t.Fatalf("Failed to parse result JSON: %v", err) } if err := json.Unmarshal([]byte(expected), &expectedObj); err != nil { t.Fatalf("Failed to parse expected JSON: %v", err) } // Get the events arrays resultEvents, ok := resultObj["events"].([]interface{}) if !ok || len(resultEvents) != 2 { t.Fatalf("Expected events array with 2 items in result") } expectedEvents, ok := expectedObj["events"].([]interface{}) if !ok || len(expectedEvents) != 2 { t.Fatalf("Expected events array with 2 items in expected") } // Check each event's date value for i := 0; i < 2; i++ { resultEvent, ok := resultEvents[i].(map[string]interface{}) if !ok { t.Fatalf("Expected event %d to be an object", i) } expectedEvent, ok := expectedEvents[i].(map[string]interface{}) if !ok { t.Fatalf("Expected expected event %d to be an object", i) } resultDate, ok := resultEvent["date"].(string) if !ok { t.Fatalf("Expected date in result event %d to be a string", i) } expectedDate, ok := expectedEvent["date"].(string) if !ok { t.Fatalf("Expected date in expected event %d to be a string", i) } if resultDate != expectedDate { t.Errorf("Event %d: expected date %s, got %s", i, expectedDate, resultDate) } } } // TestJSONProcessor_MathFunctions tests using math functions in JSON processing func TestJSONProcessor_MathFunctions(t *testing.T) { content := `{ "measurements": [ 3.14159, 2.71828, 1.41421 ] }` expected := `{ "measurements": [ 3, 3, 1 ] }` p := &JSONProcessor{} result, modCount, matchCount, err := p.ProcessContent(content, "$.measurements[*]", "v = round(v)") if err != nil { t.Fatalf("Error processing content: %v", err) } if matchCount != 3 { t.Errorf("Expected 3 matches, got %d", matchCount) } if modCount != 3 { t.Errorf("Expected 3 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_Error_InvalidJSON tests error handling for invalid JSON func TestJSONProcessor_Error_InvalidJSON(t *testing.T) { content := `{ "unclosed": "value" ` p := &JSONProcessor{} _, _, _, err := p.ProcessContent(content, "$.unclosed", "v='modified'") if err == nil { t.Errorf("Expected an error for invalid JSON, but got none") } } // TestJSONProcessor_Error_InvalidJSONPath tests error handling for invalid JSONPath func TestJSONProcessor_Error_InvalidJSONPath(t *testing.T) { content := `{ "element": "value" }` p := &JSONProcessor{} _, _, _, err := p.ProcessContent(content, "[invalid path]", "v='modified'") if err == nil { t.Errorf("Expected an error for invalid JSONPath, but got none") } } // TestJSONProcessor_Error_InvalidLua tests error handling for invalid Lua func TestJSONProcessor_Error_InvalidLua(t *testing.T) { content := `{ "element": 123 }` p := &JSONProcessor{} _, _, _, err := p.ProcessContent(content, "$.element", "v = invalid_function()") if err == nil { t.Errorf("Expected an error for invalid Lua, but got none") } } // TestJSONProcessor_Process_SpecialCharacters tests handling of special characters in JSON func TestJSONProcessor_Process_SpecialCharacters(t *testing.T) { content := `{ "data": [ "This & that", "a < b", "c > d", "Quote: \"Hello\"" ] }` p := &JSONProcessor{} result, modCount, matchCount, err := p.ProcessContent(content, "$.data[*]", "v = string.upper(v)") if err != nil { t.Fatalf("Error processing content: %v", err) } if matchCount != 4 { t.Errorf("Expected 4 matches, got %d", matchCount) } if modCount != 4 { t.Errorf("Expected 4 modifications, got %d", modCount) } // Parse the result to verify the content var resultObj map[string]interface{} if err := json.Unmarshal([]byte(result), &resultObj); err != nil { t.Fatalf("Failed to parse result JSON: %v", err) } data, ok := resultObj["data"].([]interface{}) if !ok || len(data) != 4 { t.Fatalf("Expected data array with 4 items") } expectedValues := []string{ "THIS & THAT", "A < B", "C > D", "QUOTE: \"HELLO\"" } for i, val := range data { strVal, ok := val.(string) if !ok { t.Errorf("Expected item %d to be a string", i) continue } if strVal != expectedValues[i] { t.Errorf("Item %d: expected %q, got %q", i, expectedValues[i], strVal) } } } // TestJSONProcessor_AggregateCalculation tests calculating aggregated values from multiple fields func TestJSONProcessor_AggregateCalculation(t *testing.T) { content := `{ "items": [ { "name": "Apple", "price": 1.99, "quantity": 10 }, { "name": "Carrot", "price": 0.99, "quantity": 5 } ] }` expected := `{ "items": [ { "name": "Apple", "price": 1.99, "quantity": 10, "total": 19.9 }, { "name": "Carrot", "price": 0.99, "quantity": 5, "total": 4.95 } ] }` p := &JSONProcessor{} result, modCount, matchCount, err := p.ProcessContent(content, "$.items[*]", ` -- Calculate total from price and quantity local price = v.price local quantity = v.quantity -- Add new total field v.total = price * quantity `) 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_DataAnonymization tests anonymizing sensitive data func TestJSONProcessor_DataAnonymization(t *testing.T) { content := `{ "contacts": [ { "name": "John Doe", "email": "john.doe@example.com", "phone": "123-456-7890" }, { "name": "Jane Smith", "email": "jane.smith@example.com", "phone": "456-789-0123" } ] }` p := &JSONProcessor{} // First pass: anonymize email addresses result, modCount1, matchCount1, err := p.ProcessContent(content, "$.contacts[*].email", ` -- Anonymize email v = string.gsub(v, "@.+", "@anon.com") local username = string.match(v, "(.+)@") v = string.gsub(username, "%.", "") .. "@anon.com" `) if err != nil { t.Fatalf("Error processing email content: %v", err) } // Second pass: anonymize phone numbers result, modCount2, matchCount2, err := p.ProcessContent(result, "$.contacts[*].phone", ` -- Mask phone numbers v = string.gsub(v, "%d%d%d%-%d%d%d%-%d%d%d%d", function(match) return string.sub(match, 1, 3) .. "-XXX-XXXX" end) `) if err != nil { t.Fatalf("Error processing phone content: %v", err) } // Total counts from both operations matchCount := matchCount1 + matchCount2 modCount := modCount1 + modCount2 if matchCount != 4 { t.Errorf("Expected 4 total matches, got %d", matchCount) } if modCount != 4 { t.Errorf("Expected 4 total modifications, got %d", modCount) } // Parse the resulting JSON for validating content var resultObj map[string]interface{} if err := json.Unmarshal([]byte(result), &resultObj); err != nil { t.Fatalf("Failed to parse result JSON: %v", err) } contacts, ok := resultObj["contacts"].([]interface{}) if !ok || len(contacts) != 2 { t.Fatalf("Expected contacts array with 2 items") } // Validate first contact contact1, ok := contacts[0].(map[string]interface{}) if !ok { t.Fatalf("Expected first contact to be an object") } if email1, ok := contact1["email"].(string); !ok || email1 != "johndoe@anon.com" { t.Errorf("First contact email should be johndoe@anon.com, got %v", contact1["email"]) } if phone1, ok := contact1["phone"].(string); !ok || phone1 != "123-XXX-XXXX" { t.Errorf("First contact phone should be 123-XXX-XXXX, got %v", contact1["phone"]) } // Validate second contact contact2, ok := contacts[1].(map[string]interface{}) if !ok { t.Fatalf("Expected second contact to be an object") } if email2, ok := contact2["email"].(string); !ok || email2 != "janesmith@anon.com" { t.Errorf("Second contact email should be janesmith@anon.com, got %v", contact2["email"]) } if phone2, ok := contact2["phone"].(string); !ok || phone2 != "456-XXX-XXXX" { t.Errorf("Second contact phone should be 456-XXX-XXXX, got %v", contact2["phone"]) } } // TestJSONProcessor_ChainedOperations tests sequential operations on the same data func TestJSONProcessor_ChainedOperations(t *testing.T) { content := `{ "product": { "name": "Widget", "price": 100 } }` expected := `{ "product": { "name": "Widget", "price": 103.5 } }` p := &JSONProcessor{} result, modCount, matchCount, err := p.ProcessContent(content, "$.product.price", ` -- When v is a numeric value, we can perform math operations directly local price = v -- Add 15% tax price = price * 1.15 -- Apply 10% discount price = price * 0.9 -- Round to 2 decimal places price = math.floor(price * 100 + 0.5) / 100 v = price `) 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_ComplexDataTransformation tests advanced JSON transformation func TestJSONProcessor_ComplexDataTransformation(t *testing.T) { content := `{ "store": { "name": "My Store", "inventory": [ { "id": 1, "name": "Laptop", "category": "electronics", "price": 999.99, "stock": 15, "features": ["16GB RAM", "512GB SSD", "15-inch display"] }, { "id": 2, "name": "Smartphone", "category": "electronics", "price": 499.99, "stock": 25, "features": ["6GB RAM", "128GB storage", "5G"] }, { "id": 3, "name": "T-Shirt", "category": "clothing", "price": 19.99, "stock": 100, "features": ["100% cotton", "M, L, XL sizes", "Red color"] }, { "id": 4, "name": "Headphones", "category": "electronics", "price": 149.99, "stock": 8, "features": ["Noise cancelling", "Bluetooth", "20hr battery"] } ] } }` expected := `{ "store": { "name": "My Store", "inventory_summary": { "electronics": { "count": 3, "total_value": 30924.77, "low_stock_items": [ { "id": 4, "name": "Headphones", "stock": 8 } ] }, "clothing": { "count": 1, "total_value": 1999.00, "low_stock_items": [] } }, "transformed_items": [ { "name": "Laptop", "price_with_tax": 1199.99, "in_stock": true }, { "name": "Smartphone", "price_with_tax": 599.99, "in_stock": true }, { "name": "T-Shirt", "price_with_tax": 23.99, "in_stock": true }, { "name": "Headphones", "price_with_tax": 179.99, "in_stock": true } ] } }` p := &JSONProcessor{} // First, create a complex transformation that: // 1. Summarizes inventory by category (count, total value, low stock alerts) // 2. Creates a simplified view of items with tax added result, modCount, matchCount, err := p.ProcessContent(content, "$", ` -- Get store data local store = v.store local inventory = store.inventory -- Remove the original inventory array, we'll replace it with our summaries store.inventory = nil -- Create summary by category local summary = {} local transformed = {} -- Group and analyze items by category for _, item in ipairs(inventory) do -- Prepare category data if not exists local category = item.category if not summary[category] then summary[category] = { count = 0, total_value = 0, low_stock_items = {} } end -- Update category counts summary[category].count = summary[category].count + 1 -- Calculate total value (price * stock) and add to category local item_value = item.price * item.stock summary[category].total_value = summary[category].total_value + item_value -- Check for low stock (less than 10) if item.stock < 10 then table.insert(summary[category].low_stock_items, { id = item.id, name = item.name, stock = item.stock }) end -- Create transformed view of the item with added tax table.insert(transformed, { name = item.name, price_with_tax = math.floor((item.price * 1.2) * 100 + 0.5) / 100, -- 20% tax, rounded to 2 decimals in_stock = item.stock > 0 }) end -- Format the total_value with two decimal places for category, data in pairs(summary) do data.total_value = math.floor(data.total_value * 100 + 0.5) / 100 end -- Add our new data structures to the store store.inventory_summary = summary store.transformed_items = transformed `) 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) } // Parse both results as JSON objects for deep comparison var resultObj map[string]interface{} var expectedObj map[string]interface{} if err := json.Unmarshal([]byte(result), &resultObj); err != nil { t.Fatalf("Failed to parse result JSON: %v", err) } if err := json.Unmarshal([]byte(expected), &expectedObj); err != nil { t.Fatalf("Failed to parse expected JSON: %v", err) } // Verify the structure and key counts resultStore, ok := resultObj["store"].(map[string]interface{}) if !ok { t.Fatalf("Expected 'store' object in result") } // Check that inventory is gone and replaced with our new structures if resultStore["inventory"] != nil { t.Errorf("Expected 'inventory' to be removed") } if resultStore["inventory_summary"] == nil { t.Errorf("Expected 'inventory_summary' to be added") } if resultStore["transformed_items"] == nil { t.Errorf("Expected 'transformed_items' to be added") } // Check that the transformed_items array has the correct length transformedItems, ok := resultStore["transformed_items"].([]interface{}) if !ok { t.Fatalf("Expected 'transformed_items' to be an array") } if len(transformedItems) != 4 { t.Errorf("Expected 'transformed_items' to have 4 items, got %d", len(transformedItems)) } // Check that the summary has entries for both electronics and clothing summary, ok := resultStore["inventory_summary"].(map[string]interface{}) if !ok { t.Fatalf("Expected 'inventory_summary' to be an object") } if summary["electronics"] == nil { t.Errorf("Expected 'electronics' category in summary") } if summary["clothing"] == nil { t.Errorf("Expected 'clothing' category in summary") } }