package processor import ( "encoding/json" "strings" "testing" ) // TestJSONProcessor_Process_NumericValues tests processing numeric JSON values func TestJSONProcessor_Process_NumericValues(t *testing.T) { // Test JSON with numeric price values we want to modify testJSON := `{ "catalog": { "books": [ { "id": "bk101", "author": "Gambardella, Matthew", "title": "JSON Developer's Guide", "genre": "Computer", "price": 44.95, "publish_date": "2000-10-01" }, { "id": "bk102", "author": "Ralls, Kim", "title": "Midnight Rain", "genre": "Fantasy", "price": 5.95, "publish_date": "2000-12-16" } ] } }` // Create a JSON processor processor := NewJSONProcessor(&TestLogger{T: t}) // Process the JSON content directly to double all prices jsonPathExpr := "$.catalog.books[*].price" modifiedJSON, modCount, matchCount, err := processor.ProcessContent(testJSON, jsonPathExpr, "v1 = v1 * 2", "*2") if err != nil { t.Fatalf("Failed to process JSON content: %v", err) } // Check that we found and modified the correct number of nodes if matchCount != 2 { t.Errorf("Expected to match 2 nodes, got %d", matchCount) } if modCount != 2 { t.Errorf("Expected to modify 2 nodes, got %d", modCount) } // Parse the JSON to check values more precisely var result map[string]interface{} if err := json.Unmarshal([]byte(modifiedJSON), &result); err != nil { t.Fatalf("Failed to parse modified JSON: %v", err) } // Navigate to the books array catalog, ok := result["catalog"].(map[string]interface{}) if !ok { t.Fatalf("No catalog object found in result") } books, ok := catalog["books"].([]interface{}) if !ok { t.Fatalf("No books array found in catalog") } // Check that both books have their prices doubled // Note: The JSON numbers might be parsed as float64 book1, ok := books[0].(map[string]interface{}) if !ok { t.Fatalf("First book is not an object") } price1, ok := book1["price"].(float64) if !ok { t.Fatalf("Price of first book is not a number") } if price1 != 89.9 { t.Errorf("Expected first book price to be 89.9, got %v", price1) } book2, ok := books[1].(map[string]interface{}) if !ok { t.Fatalf("Second book is not an object") } price2, ok := book2["price"].(float64) if !ok { t.Fatalf("Price of second book is not a number") } if price2 != 11.9 { t.Errorf("Expected second book price to be 11.9, got %v", price2) } } // TestJSONProcessor_Process_StringValues tests processing string JSON values func TestJSONProcessor_Process_StringValues(t *testing.T) { // Test JSON with string values we want to modify testJSON := `{ "config": { "settings": [ { "name": "maxUsers", "value": "100" }, { "name": "timeout", "value": "30" }, { "name": "retries", "value": "5" } ] } }` // Create a JSON processor processor := NewJSONProcessor(&TestLogger{T: t}) // Process the JSON content directly to double all numeric values jsonPathExpr := "$.config.settings[*].value" modifiedJSON, modCount, matchCount, err := processor.ProcessContent(testJSON, jsonPathExpr, "v1 = v1 * 2", "*2") if err != nil { t.Fatalf("Failed to process JSON content: %v", err) } // Check that we found and modified the correct number of nodes if matchCount != 3 { t.Errorf("Expected to match 3 nodes, got %d", matchCount) } if modCount != 3 { t.Errorf("Expected to modify 3 nodes, got %d", modCount) } // Check that the string values were doubled if !strings.Contains(modifiedJSON, `"value": "200"`) { t.Errorf("Modified content does not contain updated value 200") } if !strings.Contains(modifiedJSON, `"value": "60"`) { t.Errorf("Modified content does not contain updated value 60") } if !strings.Contains(modifiedJSON, `"value": "10"`) { t.Errorf("Modified content does not contain updated value 10") } // Verify the JSON is valid after modification var result map[string]interface{} if err := json.Unmarshal([]byte(modifiedJSON), &result); err != nil { t.Fatalf("Modified JSON is not valid: %v", err) } } // TestJSONProcessor_FindNodes tests the JSONPath implementation func TestJSONProcessor_FindNodes(t *testing.T) { // Test simple JSONPath functionality 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, }, { name: "Invalid path", jsonData: `{"name": "test"}`, path: "$.invalid[[", // Double bracket should cause an error expectLen: 0, expectErr: true, }, } processor := &JSONProcessor{Logger: &TestLogger{}} 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 := processor.findNodePaths(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) { testJSON := `{ "company": { "name": "ABC Corp", "departments": [ { "id": "dev", "name": "Development", "employees": [ {"id": 1, "name": "John Doe", "salary": 75000}, {"id": 2, "name": "Jane Smith", "salary": 82000} ] }, { "id": "sales", "name": "Sales", "employees": [ {"id": 3, "name": "Bob Johnson", "salary": 65000}, {"id": 4, "name": "Alice Brown", "salary": 68000} ] } ] } }` // Create a JSON processor processor := NewJSONProcessor(&TestLogger{T: t}) // Process the JSON to give everyone a 10% raise jsonPathExpr := "$.company.departments[*].employees[*].salary" modifiedJSON, modCount, matchCount, err := processor.ProcessContent(testJSON, jsonPathExpr, "v1 = v1 * 1.1", "10% raise") if err != nil { t.Fatalf("Failed to process JSON content: %v", err) } // Check counts if matchCount != 4 { t.Errorf("Expected to match 4 salary nodes, got %d", matchCount) } if modCount != 4 { t.Errorf("Expected to modify 4 nodes, got %d", modCount) } // Parse the result to verify changes var result map[string]interface{} if err := json.Unmarshal([]byte(modifiedJSON), &result); err != nil { t.Fatalf("Failed to parse modified JSON: %v", err) } // Get company > departments company := result["company"].(map[string]interface{}) departments := company["departments"].([]interface{}) // Check first department's first employee dept1 := departments[0].(map[string]interface{}) employees1 := dept1["employees"].([]interface{}) emp1 := employees1[0].(map[string]interface{}) salary1 := emp1["salary"].(float64) // Salary should be 75000 * 1.1 = 82500 if salary1 != 82500 { t.Errorf("Expected first employee salary to be 82500, got %v", salary1) } // Check second department's second employee dept2 := departments[1].(map[string]interface{}) employees2 := dept2["employees"].([]interface{}) emp4 := employees2[1].(map[string]interface{}) salary4 := emp4["salary"].(float64) // Salary should be 68000 * 1.1 = 74800 if salary4 != 74800 { t.Errorf("Expected fourth employee salary to be 74800, got %v", salary4) } } // TestJSONProcessor_ArrayManipulation tests modifying JSON arrays func TestJSONProcessor_ArrayManipulation(t *testing.T) { testJSON := `{ "dataPoints": [10, 20, 30, 40, 50] }` // Create a JSON processor processor := NewJSONProcessor(&TestLogger{T: t}) // Process the JSON to normalize values (divide by max value) jsonPathExpr := "$.dataPoints[*]" modifiedJSON, modCount, matchCount, err := processor.ProcessContent(testJSON, jsonPathExpr, "v1 = v1 / 50", "normalize") if err != nil { t.Fatalf("Failed to process JSON content: %v", err) } // Check counts if matchCount != 5 { t.Errorf("Expected to match 5 data points, got %d", matchCount) } if modCount != 5 { t.Errorf("Expected to modify 5 nodes, got %d", modCount) } // Parse the result to verify changes var result map[string]interface{} if err := json.Unmarshal([]byte(modifiedJSON), &result); err != nil { t.Fatalf("Failed to parse modified JSON: %v", err) } // Get the data points array dataPoints := result["dataPoints"].([]interface{}) // Check values (should be divided by 50) expectedValues := []float64{0.2, 0.4, 0.6, 0.8, 1.0} for i, val := range dataPoints { if val.(float64) != expectedValues[i] { t.Errorf("Expected dataPoints[%d] to be %v, got %v", i, expectedValues[i], val) } } } // TestJSONProcessor_ConditionalModification tests applying changes only to certain elements func TestJSONProcessor_ConditionalModification(t *testing.T) { testJSON := `{ "products": [ {"id": "p1", "name": "Laptop", "price": 999.99, "discount": 0}, {"id": "p2", "name": "Headphones", "price": 59.99, "discount": 0}, {"id": "p3", "name": "Mouse", "price": 29.99, "discount": 0} ] }` // Create a JSON processor processor := NewJSONProcessor(&TestLogger{T: t}) // Process: apply 10% discount to items over $50, 5% to others luaScript := ` -- Get the path to find the parent (product) local path = string.gsub(_PATH, ".discount$", "") -- Custom logic based on price local price = _PARENT.price if price > 50 then v1 = 0.1 -- 10% discount else v1 = 0.05 -- 5% discount end ` jsonPathExpr := "$.products[*].discount" modifiedJSON, modCount, matchCount, err := processor.ProcessContent(testJSON, jsonPathExpr, luaScript, "apply discounts") if err != nil { t.Fatalf("Failed to process JSON content: %v", err) } // Check counts if matchCount != 3 { t.Errorf("Expected to match 3 discount nodes, got %d", matchCount) } if modCount != 3 { t.Errorf("Expected to modify 3 nodes, got %d", modCount) } // Parse the result to verify changes var result map[string]interface{} if err := json.Unmarshal([]byte(modifiedJSON), &result); err != nil { t.Fatalf("Failed to parse modified JSON: %v", err) } // Get products array products := result["products"].([]interface{}) // Laptop and Headphones should have 10% discount laptop := products[0].(map[string]interface{}) headphones := products[1].(map[string]interface{}) mouse := products[2].(map[string]interface{}) if laptop["discount"].(float64) != 0.1 { t.Errorf("Expected laptop discount to be 0.1, got %v", laptop["discount"]) } if headphones["discount"].(float64) != 0.1 { t.Errorf("Expected headphones discount to be 0.1, got %v", headphones["discount"]) } if mouse["discount"].(float64) != 0.05 { t.Errorf("Expected mouse discount to be 0.05, got %v", mouse["discount"]) } } // TestJSONProcessor_ComplexScripts tests using more complex Lua scripts func TestJSONProcessor_ComplexScripts(t *testing.T) { testJSON := `{ "metrics": [ {"name": "CPU", "values": [45, 60, 75, 90, 80]}, {"name": "Memory", "values": [30, 40, 45, 50, 60]}, {"name": "Disk", "values": [20, 25, 30, 40, 50]} ] }` // Create a JSON processor processor := NewJSONProcessor(&TestLogger{T: t}) // Apply a moving average transformation luaScript := ` -- This script transforms an array using a moving average local values = {} local window = 3 -- window size -- Get all the values as a table for i = 1, 5 do local element = _VALUE[i] if element then values[i] = element end end -- Calculate moving averages local result = {} for i = 1, #values do local sum = 0 local count = 0 -- Sum the window for j = math.max(1, i-(window-1)/2), math.min(#values, i+(window-1)/2) do sum = sum + values[j] count = count + 1 end -- Set the average result[i] = sum / count end -- Update all values for i = 1, #result do _VALUE[i] = result[i] end ` jsonPathExpr := "$.metrics[*].values" modifiedJSON, modCount, matchCount, err := processor.ProcessContent(testJSON, jsonPathExpr, luaScript, "moving average") if err != nil { t.Fatalf("Failed to process JSON content: %v", err) } // Check counts if matchCount != 3 { t.Errorf("Expected to match 3 value arrays, got %d", matchCount) } if modCount != 3 { t.Errorf("Expected to modify 3 nodes, got %d", modCount) } // Parse and verify the values were smoothed var result map[string]interface{} if err := json.Unmarshal([]byte(modifiedJSON), &result); err != nil { t.Fatalf("Failed to parse modified JSON: %v", err) } // The modification logic would smooth out the values // We'll check that the JSON is valid at least metrics := result["metrics"].([]interface{}) if len(metrics) != 3 { t.Errorf("Expected 3 metrics, got %d", len(metrics)) } // Each metrics should have 5 values for i, metric := range metrics { m := metric.(map[string]interface{}) values := m["values"].([]interface{}) if len(values) != 5 { t.Errorf("Metric %d should have 5 values, got %d", i, len(values)) } } }