Files
BigChef/processor/json_test.go
2025-03-24 18:48:43 +01:00

984 lines
21 KiB
Go

package processor
import (
"encoding/json"
"strings"
"testing"
"github.com/PaesslerAG/jsonpath"
)
// findMatchingPaths 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[*].price", "v=v*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_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.*", "v=v*2")
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)
}
// 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",
"title": "Learn Go in 24 Hours",
"price": 13.188
},
{
"category": "fiction",
"title": "The Go Developer",
"price": 10.788
}
]
}
}`
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": [
{
"name": "Basic Widget",
"price": 8.991,
"discount": 0.1
},
{
"name": "Premium Widget",
"price": 18.9905,
"discount": 0.05
}
]
}`
p := &JSONProcessor{}
result, modCount, matchCount, err := p.ProcessContent(content, "$.products[*]", "v.price = v.price * (1 - v.discount)")
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)
}
if result != expected {
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": {
"name": "John",
"age": 30,
"email": "john@example.com"
}
}`
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": {
"name": "John",
"email": "john@example.com"
}
}`
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": [
{
"name": "Product A",
"price": 9.891,
"inStock": true
},
{
"name": "Product B",
"price": 5.99,
"inStock": false
},
{
"name": "Product C",
"price": 14.391,
"inStock": true
}
]
}`
p := &JSONProcessor{}
result, modCount, matchCount, err := p.ProcessContent(content, "$.products[*]", `
if v.inStock then
v.price = v.price * 0.9
end
`)
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": {
"frontend": {
"members": 12,
"projects": 5,
"status": "active"
},
"backend": {
"members": 8,
"projects": 3,
"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": {
"items": [
{
"product": "Widget A",
"quantity": 5,
"price": 10.0,
"total": 50.0,
"discounted_total": 45.0
},
{
"product": "Widget B",
"quantity": 3,
"price": 15.0,
"total": 45.0,
"discounted_total": 40.5
}
],
"customer": {
"name": "John Smith",
"tier": "gold"
},
"summary": {
"total_items": 8,
"subtotal": 95.0,
"discount": 9.5,
"total": 85.5
}
}
}`
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": [
{
"id": 1,
"name": "Alice",
"age": 25
}
],
"managers": [
{
"id": 2,
"name": "Bob",
"age": 30
}
]
}
}`
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)
}
}