Remove some unused shit and write tests for coverage
This commit is contained in:
@@ -331,7 +331,14 @@ func convertValueToJSONString(value interface{}) string {
|
||||
// findArrayElementRemovalRange finds the exact byte range to remove for an array element
|
||||
func findArrayElementRemovalRange(content, arrayPath string, elementIndex int) (int, int) {
|
||||
// Get the array using gjson
|
||||
arrayResult := gjson.Get(content, arrayPath)
|
||||
var arrayResult gjson.Result
|
||||
if arrayPath == "" {
|
||||
// Root-level array
|
||||
arrayResult = gjson.Parse(content)
|
||||
} else {
|
||||
arrayResult = gjson.Get(content, arrayPath)
|
||||
}
|
||||
|
||||
if !arrayResult.Exists() || !arrayResult.IsArray() {
|
||||
return -1, -1
|
||||
}
|
||||
@@ -455,16 +462,9 @@ func findDeepChanges(basePath string, original, modified interface{}) map[string
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
// For primitive types, compare directly
|
||||
if !deepEqual(original, modified) {
|
||||
if basePath == "" {
|
||||
changes[""] = modified
|
||||
} else {
|
||||
changes[basePath] = modified
|
||||
}
|
||||
}
|
||||
}
|
||||
// Note: No default case needed - JSON data from unmarshaling is always
|
||||
// map[string]interface{} or []interface{} at the top level
|
||||
|
||||
return changes
|
||||
}
|
||||
@@ -531,112 +531,61 @@ func deepEqual(a, b interface{}) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// ToLuaTable converts a Go interface{} to a Lua table recursively
|
||||
// ToLuaTable converts a Go interface{} (map or array) to a Lua table
|
||||
// This should only be called with map[string]interface{} or []interface{} from JSON unmarshaling
|
||||
func ToLuaTable(L *lua.LState, data interface{}) (*lua.LTable, error) {
|
||||
toLuaTableLogger := jsonLogger.WithPrefix("ToLuaTable")
|
||||
toLuaTableLogger.Debug("Converting Go interface to Lua table")
|
||||
toLuaTableLogger.Trace("Input data type: %T", data)
|
||||
|
||||
switch v := data.(type) {
|
||||
case map[string]interface{}:
|
||||
toLuaTableLogger.Debug("Converting map to Lua table")
|
||||
table := L.CreateTable(0, len(v))
|
||||
for key, value := range v {
|
||||
luaValue, err := ToLuaValue(L, value)
|
||||
if err != nil {
|
||||
toLuaTableLogger.Error("Failed to convert map value for key %q: %v", key, err)
|
||||
return nil, err
|
||||
}
|
||||
table.RawSetString(key, luaValue)
|
||||
table.RawSetString(key, ToLuaValue(L, value))
|
||||
}
|
||||
return table, nil
|
||||
|
||||
case []interface{}:
|
||||
toLuaTableLogger.Debug("Converting slice to Lua table")
|
||||
table := L.CreateTable(len(v), 0)
|
||||
for i, value := range v {
|
||||
luaValue, err := ToLuaValue(L, value)
|
||||
if err != nil {
|
||||
toLuaTableLogger.Error("Failed to convert slice value at index %d: %v", i, err)
|
||||
return nil, err
|
||||
}
|
||||
table.RawSetInt(i+1, luaValue) // Lua arrays are 1-indexed
|
||||
table.RawSetInt(i+1, ToLuaValue(L, value)) // Lua arrays are 1-indexed
|
||||
}
|
||||
return table, nil
|
||||
|
||||
case string:
|
||||
toLuaTableLogger.Debug("Converting string to Lua string")
|
||||
return nil, fmt.Errorf("expected table or array, got string")
|
||||
|
||||
case float64:
|
||||
toLuaTableLogger.Debug("Converting float64 to Lua number")
|
||||
return nil, fmt.Errorf("expected table or array, got number")
|
||||
|
||||
case bool:
|
||||
toLuaTableLogger.Debug("Converting bool to Lua boolean")
|
||||
return nil, fmt.Errorf("expected table or array, got boolean")
|
||||
|
||||
case nil:
|
||||
toLuaTableLogger.Debug("Converting nil to Lua nil")
|
||||
return nil, fmt.Errorf("expected table or array, got nil")
|
||||
|
||||
default:
|
||||
toLuaTableLogger.Error("Unsupported type for Lua table conversion: %T", v)
|
||||
return nil, fmt.Errorf("unsupported type for Lua table conversion: %T", v)
|
||||
// This should only happen with invalid JSON (root-level primitives)
|
||||
return nil, fmt.Errorf("expected table or array, got %T", v)
|
||||
}
|
||||
}
|
||||
|
||||
// ToLuaValue converts a Go interface{} to a Lua value
|
||||
func ToLuaValue(L *lua.LState, data interface{}) (lua.LValue, error) {
|
||||
toLuaValueLogger := jsonLogger.WithPrefix("ToLuaValue")
|
||||
toLuaValueLogger.Debug("Converting Go interface to Lua value")
|
||||
toLuaValueLogger.Trace("Input data type: %T", data)
|
||||
|
||||
func ToLuaValue(L *lua.LState, data interface{}) lua.LValue {
|
||||
switch v := data.(type) {
|
||||
case map[string]interface{}:
|
||||
toLuaValueLogger.Debug("Converting map to Lua table")
|
||||
table := L.CreateTable(0, len(v))
|
||||
for key, value := range v {
|
||||
luaValue, err := ToLuaValue(L, value)
|
||||
if err != nil {
|
||||
toLuaValueLogger.Error("Failed to convert map value for key %q: %v", key, err)
|
||||
return lua.LNil, err
|
||||
}
|
||||
table.RawSetString(key, luaValue)
|
||||
table.RawSetString(key, ToLuaValue(L, value))
|
||||
}
|
||||
return table, nil
|
||||
return table
|
||||
|
||||
case []interface{}:
|
||||
toLuaValueLogger.Debug("Converting slice to Lua table")
|
||||
table := L.CreateTable(len(v), 0)
|
||||
for i, value := range v {
|
||||
luaValue, err := ToLuaValue(L, value)
|
||||
if err != nil {
|
||||
toLuaValueLogger.Error("Failed to convert slice value at index %d: %v", i, err)
|
||||
return lua.LNil, err
|
||||
}
|
||||
table.RawSetInt(i+1, luaValue) // Lua arrays are 1-indexed
|
||||
table.RawSetInt(i+1, ToLuaValue(L, value)) // Lua arrays are 1-indexed
|
||||
}
|
||||
return table, nil
|
||||
return table
|
||||
|
||||
case string:
|
||||
toLuaValueLogger.Debug("Converting string to Lua string")
|
||||
return lua.LString(v), nil
|
||||
return lua.LString(v)
|
||||
|
||||
case float64:
|
||||
toLuaValueLogger.Debug("Converting float64 to Lua number")
|
||||
return lua.LNumber(v), nil
|
||||
return lua.LNumber(v)
|
||||
|
||||
case bool:
|
||||
toLuaValueLogger.Debug("Converting bool to Lua boolean")
|
||||
return lua.LBool(v), nil
|
||||
return lua.LBool(v)
|
||||
|
||||
case nil:
|
||||
toLuaValueLogger.Debug("Converting nil to Lua nil")
|
||||
return lua.LNil, nil
|
||||
return lua.LNil
|
||||
|
||||
default:
|
||||
toLuaValueLogger.Error("Unsupported type for Lua value conversion: %T", v)
|
||||
return lua.LNil, fmt.Errorf("unsupported type for Lua value conversion: %T", v)
|
||||
// This should never happen with JSON-unmarshaled data
|
||||
return lua.LNil
|
||||
}
|
||||
}
|
||||
|
||||
283
processor/json_coverage_test.go
Normal file
283
processor/json_coverage_test.go
Normal file
@@ -0,0 +1,283 @@
|
||||
package processor
|
||||
|
||||
import (
|
||||
"cook/utils"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestJSONFloat tests line 298 - float formatting for non-integer floats
|
||||
func TestJSONFloatFormatting(t *testing.T) {
|
||||
jsonContent := `{
|
||||
"value": 10.5,
|
||||
"another": 3.14159
|
||||
}`
|
||||
|
||||
command := utils.ModifyCommand{
|
||||
Name: "test_float",
|
||||
JSON: true,
|
||||
Lua: `
|
||||
data.value = data.value * 2
|
||||
data.another = data.another * 10
|
||||
modified = true
|
||||
`,
|
||||
}
|
||||
|
||||
commands, err := ProcessJSON(jsonContent, command, "test.json")
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, commands)
|
||||
|
||||
result, _ := utils.ExecuteModifications(commands, jsonContent)
|
||||
assert.Contains(t, result, "21") // 10.5 * 2
|
||||
assert.Contains(t, result, "31.4159") // 3.14159 * 10
|
||||
}
|
||||
|
||||
// TestJSONNestedObjectAddition tests lines 303-320 - map[string]interface{} case
|
||||
func TestJSONNestedObjectAddition(t *testing.T) {
|
||||
jsonContent := `{
|
||||
"items": {}
|
||||
}`
|
||||
|
||||
command := utils.ModifyCommand{
|
||||
Name: "test_nested",
|
||||
JSON: true,
|
||||
Lua: `
|
||||
data.items.newObject = {
|
||||
name = "test",
|
||||
value = 42,
|
||||
enabled = true
|
||||
}
|
||||
modified = true
|
||||
`,
|
||||
}
|
||||
|
||||
commands, err := ProcessJSON(jsonContent, command, "test.json")
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, commands)
|
||||
|
||||
result, _ := utils.ExecuteModifications(commands, jsonContent)
|
||||
assert.Contains(t, result, `"newObject"`)
|
||||
assert.Contains(t, result, `"name"`)
|
||||
assert.Contains(t, result, `"test"`)
|
||||
assert.Contains(t, result, `"value"`)
|
||||
assert.Contains(t, result, "42")
|
||||
}
|
||||
|
||||
// TestJSONKeyWithQuotes tests line 315 - key escaping with quotes
|
||||
func TestJSONKeyWithQuotes(t *testing.T) {
|
||||
jsonContent := `{
|
||||
"data": {}
|
||||
}`
|
||||
|
||||
command := utils.ModifyCommand{
|
||||
Name: "test_key_quotes",
|
||||
JSON: true,
|
||||
Lua: `
|
||||
data.data["key-with-dash"] = "value1"
|
||||
data.data.normalKey = "value2"
|
||||
modified = true
|
||||
`,
|
||||
}
|
||||
|
||||
commands, err := ProcessJSON(jsonContent, command, "test.json")
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, commands)
|
||||
|
||||
result, _ := utils.ExecuteModifications(commands, jsonContent)
|
||||
assert.Contains(t, result, `"key-with-dash"`)
|
||||
assert.Contains(t, result, `"normalKey"`)
|
||||
}
|
||||
|
||||
// TestJSONArrayInValue tests lines 321-327 - default case with json.Marshal for arrays
|
||||
func TestJSONArrayInValue(t *testing.T) {
|
||||
jsonContent := `{
|
||||
"data": {}
|
||||
}`
|
||||
|
||||
command := utils.ModifyCommand{
|
||||
Name: "test_array_value",
|
||||
JSON: true,
|
||||
Lua: `
|
||||
data.data.items = {1, 2, 3, 4, 5}
|
||||
data.data.strings = {"a", "b", "c"}
|
||||
modified = true
|
||||
`,
|
||||
}
|
||||
|
||||
commands, err := ProcessJSON(jsonContent, command, "test.json")
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, commands)
|
||||
|
||||
result, _ := utils.ExecuteModifications(commands, jsonContent)
|
||||
assert.Contains(t, result, `"items"`)
|
||||
assert.Contains(t, result, `[1,2,3,4,5]`)
|
||||
assert.Contains(t, result, `"strings"`)
|
||||
assert.Contains(t, result, `["a","b","c"]`)
|
||||
}
|
||||
|
||||
// TestJSONRootArrayElementRemoval tests line 422 - removing from root-level array
|
||||
func TestJSONRootArrayElementRemoval(t *testing.T) {
|
||||
jsonContent := `[
|
||||
{"id": 1, "name": "first"},
|
||||
{"id": 2, "name": "second"},
|
||||
{"id": 3, "name": "third"}
|
||||
]`
|
||||
|
||||
command := utils.ModifyCommand{
|
||||
Name: "test_root_array_removal",
|
||||
JSON: true,
|
||||
Lua: `
|
||||
-- Remove the second element
|
||||
table.remove(data, 2)
|
||||
modified = true
|
||||
`,
|
||||
}
|
||||
|
||||
commands, err := ProcessJSON(jsonContent, command, "test.json")
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, commands)
|
||||
|
||||
result, _ := utils.ExecuteModifications(commands, jsonContent)
|
||||
assert.Contains(t, result, `"first"`)
|
||||
assert.Contains(t, result, `"third"`)
|
||||
assert.NotContains(t, result, `"second"`)
|
||||
}
|
||||
|
||||
// TestJSONRootArrayElementChange tests lines 434 and 450 - changing primitive values in root array
|
||||
func TestJSONRootArrayElementChange(t *testing.T) {
|
||||
jsonContent := `[10, 20, 30, 40, 50]`
|
||||
|
||||
command := utils.ModifyCommand{
|
||||
Name: "test_root_array_change",
|
||||
JSON: true,
|
||||
Lua: `
|
||||
-- Double all values
|
||||
for i = 1, #data do
|
||||
data[i] = data[i] * 2
|
||||
end
|
||||
modified = true
|
||||
`,
|
||||
}
|
||||
|
||||
commands, err := ProcessJSON(jsonContent, command, "test.json")
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, commands)
|
||||
|
||||
result, _ := utils.ExecuteModifications(commands, jsonContent)
|
||||
assert.Contains(t, result, "20")
|
||||
assert.Contains(t, result, "40")
|
||||
assert.Contains(t, result, "60")
|
||||
assert.Contains(t, result, "80")
|
||||
assert.Contains(t, result, "100")
|
||||
assert.NotContains(t, result, "10,")
|
||||
}
|
||||
|
||||
// TestJSONRootArrayStringElements tests deepEqual with strings in root array
|
||||
func TestJSONRootArrayStringElements(t *testing.T) {
|
||||
jsonContent := `["apple", "banana", "cherry"]`
|
||||
|
||||
command := utils.ModifyCommand{
|
||||
Name: "test_root_array_strings",
|
||||
JSON: true,
|
||||
Lua: `
|
||||
data[2] = "orange"
|
||||
modified = true
|
||||
`,
|
||||
}
|
||||
|
||||
commands, err := ProcessJSON(jsonContent, command, "test.json")
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, commands)
|
||||
|
||||
result, _ := utils.ExecuteModifications(commands, jsonContent)
|
||||
assert.Contains(t, result, `"apple"`)
|
||||
assert.Contains(t, result, `"orange"`)
|
||||
assert.Contains(t, result, `"cherry"`)
|
||||
assert.NotContains(t, result, `"banana"`)
|
||||
}
|
||||
|
||||
// TestJSONComplexNestedStructure tests multiple untested paths together
|
||||
func TestJSONComplexNestedStructure(t *testing.T) {
|
||||
jsonContent := `{
|
||||
"config": {
|
||||
"multiplier": 2.5
|
||||
}
|
||||
}`
|
||||
|
||||
command := utils.ModifyCommand{
|
||||
Name: "test_complex",
|
||||
JSON: true,
|
||||
Lua: `
|
||||
-- Add nested object with array
|
||||
data.config.settings = {
|
||||
enabled = true,
|
||||
values = {1.5, 2.5, 3.5},
|
||||
names = {"alpha", "beta"}
|
||||
}
|
||||
-- Change float
|
||||
data.config.multiplier = 7.777
|
||||
modified = true
|
||||
`,
|
||||
}
|
||||
|
||||
commands, err := ProcessJSON(jsonContent, command, "test.json")
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, commands)
|
||||
|
||||
result, _ := utils.ExecuteModifications(commands, jsonContent)
|
||||
assert.Contains(t, result, "7.777")
|
||||
assert.Contains(t, result, `"settings"`)
|
||||
assert.Contains(t, result, `"values"`)
|
||||
assert.Contains(t, result, `[1.5,2.5,3.5]`)
|
||||
}
|
||||
|
||||
// TestJSONRemoveFirstArrayElement tests line 358-365 - removing first element with comma handling
|
||||
func TestJSONRemoveFirstArrayElement(t *testing.T) {
|
||||
jsonContent := `{
|
||||
"items": [1, 2, 3, 4, 5]
|
||||
}`
|
||||
|
||||
command := utils.ModifyCommand{
|
||||
Name: "test_remove_first",
|
||||
JSON: true,
|
||||
Lua: `
|
||||
table.remove(data.items, 1)
|
||||
modified = true
|
||||
`,
|
||||
}
|
||||
|
||||
commands, err := ProcessJSON(jsonContent, command, "test.json")
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, commands)
|
||||
|
||||
result, _ := utils.ExecuteModifications(commands, jsonContent)
|
||||
assert.NotContains(t, result, "[1,")
|
||||
assert.Contains(t, result, "2")
|
||||
assert.Contains(t, result, "5")
|
||||
}
|
||||
|
||||
// TestJSONRemoveLastArrayElement tests line 366-374 - removing last element with comma handling
|
||||
func TestJSONRemoveLastArrayElement(t *testing.T) {
|
||||
jsonContent := `{
|
||||
"items": [1, 2, 3, 4, 5]
|
||||
}`
|
||||
|
||||
command := utils.ModifyCommand{
|
||||
Name: "test_remove_last",
|
||||
JSON: true,
|
||||
Lua: `
|
||||
table.remove(data.items, 5)
|
||||
modified = true
|
||||
`,
|
||||
}
|
||||
|
||||
commands, err := ProcessJSON(jsonContent, command, "test.json")
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, commands)
|
||||
|
||||
result, _ := utils.ExecuteModifications(commands, jsonContent)
|
||||
assert.Contains(t, result, "1")
|
||||
assert.Contains(t, result, "4")
|
||||
assert.NotContains(t, result, ", 5")
|
||||
}
|
||||
153
processor/json_deepequal_test.go
Normal file
153
processor/json_deepequal_test.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package processor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDeepEqual(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
a interface{}
|
||||
b interface{}
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "both nil",
|
||||
a: nil,
|
||||
b: nil,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "first nil",
|
||||
a: nil,
|
||||
b: "something",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "second nil",
|
||||
a: "something",
|
||||
b: nil,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "equal primitives",
|
||||
a: 42,
|
||||
b: 42,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "different primitives",
|
||||
a: 42,
|
||||
b: 43,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "equal strings",
|
||||
a: "hello",
|
||||
b: "hello",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "equal maps",
|
||||
a: map[string]interface{}{
|
||||
"key1": "value1",
|
||||
"key2": 42,
|
||||
},
|
||||
b: map[string]interface{}{
|
||||
"key1": "value1",
|
||||
"key2": 42,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "maps different lengths",
|
||||
a: map[string]interface{}{
|
||||
"key1": "value1",
|
||||
},
|
||||
b: map[string]interface{}{
|
||||
"key1": "value1",
|
||||
"key2": 42,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "maps different values",
|
||||
a: map[string]interface{}{
|
||||
"key1": "value1",
|
||||
},
|
||||
b: map[string]interface{}{
|
||||
"key1": "value2",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "map vs non-map",
|
||||
a: map[string]interface{}{
|
||||
"key1": "value1",
|
||||
},
|
||||
b: "not a map",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "equal arrays",
|
||||
a: []interface{}{1, 2, 3},
|
||||
b: []interface{}{1, 2, 3},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "arrays different lengths",
|
||||
a: []interface{}{1, 2},
|
||||
b: []interface{}{1, 2, 3},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "arrays different values",
|
||||
a: []interface{}{1, 2, 3},
|
||||
b: []interface{}{1, 2, 4},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "array vs non-array",
|
||||
a: []interface{}{1, 2, 3},
|
||||
b: "not an array",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "nested equal structures",
|
||||
a: map[string]interface{}{
|
||||
"outer": map[string]interface{}{
|
||||
"inner": []interface{}{1, 2, 3},
|
||||
},
|
||||
},
|
||||
b: map[string]interface{}{
|
||||
"outer": map[string]interface{}{
|
||||
"inner": []interface{}{1, 2, 3},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "nested different structures",
|
||||
a: map[string]interface{}{
|
||||
"outer": map[string]interface{}{
|
||||
"inner": []interface{}{1, 2, 3},
|
||||
},
|
||||
},
|
||||
b: map[string]interface{}{
|
||||
"outer": map[string]interface{}{
|
||||
"inner": []interface{}{1, 2, 4},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := deepEqual(tt.a, tt.b)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -88,8 +88,7 @@ func TestToLuaValue(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := ToLuaValue(L, tt.input)
|
||||
assert.NoError(t, err)
|
||||
result := ToLuaValue(L, tt.input)
|
||||
assert.Equal(t, tt.expected, result.String())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -485,6 +485,79 @@ function setAttr(element, attrName, value)
|
||||
element._attr[attrName] = tostring(value)
|
||||
end
|
||||
|
||||
--- Find first element with a specific tag name (searches direct children only)
|
||||
--- @param parent table The parent XML element
|
||||
--- @param tagName string The tag name to search for
|
||||
--- @return table|nil The first matching element or nil
|
||||
function findFirstElement(parent, tagName)
|
||||
if not parent._children then return nil end
|
||||
for _, child in ipairs(parent._children) do
|
||||
if child._tag == tagName then
|
||||
return child
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Add a child element to a parent
|
||||
--- @param parent table The parent XML element
|
||||
--- @param child table The child element to add
|
||||
function addChild(parent, child)
|
||||
if not parent._children then
|
||||
parent._children = {}
|
||||
end
|
||||
table.insert(parent._children, child)
|
||||
end
|
||||
|
||||
--- Remove all children with a specific tag name
|
||||
--- @param parent table The parent XML element
|
||||
--- @param tagName string The tag name to remove
|
||||
--- @return number Count of removed children
|
||||
function removeChildren(parent, tagName)
|
||||
if not parent._children then return 0 end
|
||||
local removed = 0
|
||||
local i = 1
|
||||
while i <= #parent._children do
|
||||
if parent._children[i]._tag == tagName then
|
||||
table.remove(parent._children, i)
|
||||
removed = removed + 1
|
||||
else
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
return removed
|
||||
end
|
||||
|
||||
--- Get all direct children with a specific tag name
|
||||
--- @param parent table The parent XML element
|
||||
--- @param tagName string The tag name to search for
|
||||
--- @return table Array of matching children
|
||||
function getChildren(parent, tagName)
|
||||
local results = {}
|
||||
if not parent._children then return results end
|
||||
for _, child in ipairs(parent._children) do
|
||||
if child._tag == tagName then
|
||||
table.insert(results, child)
|
||||
end
|
||||
end
|
||||
return results
|
||||
end
|
||||
|
||||
--- Count children with a specific tag name
|
||||
--- @param parent table The parent XML element
|
||||
--- @param tagName string The tag name to count
|
||||
--- @return number Count of matching children
|
||||
function countChildren(parent, tagName)
|
||||
if not parent._children then return 0 end
|
||||
local count = 0
|
||||
for _, child in ipairs(parent._children) do
|
||||
if child._tag == tagName then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- JSON HELPER FUNCTIONS
|
||||
-- ============================================================================
|
||||
|
||||
218
processor/processor_coverage_test.go
Normal file
218
processor/processor_coverage_test.go
Normal file
@@ -0,0 +1,218 @@
|
||||
package processor
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
// Test replaceVariables function
|
||||
func TestReplaceVariables(t *testing.T) {
|
||||
// Setup global variables
|
||||
globalVariables = map[string]interface{}{
|
||||
"multiplier": 2.5,
|
||||
"prefix": "TEST_",
|
||||
"enabled": true,
|
||||
"disabled": false,
|
||||
"count": 42,
|
||||
}
|
||||
defer func() {
|
||||
globalVariables = make(map[string]interface{})
|
||||
}()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "Replace numeric variable",
|
||||
input: "v1 * $multiplier",
|
||||
expected: "v1 * 2.5",
|
||||
},
|
||||
{
|
||||
name: "Replace string variable",
|
||||
input: `s1 = $prefix .. "value"`,
|
||||
expected: `s1 = "TEST_" .. "value"`,
|
||||
},
|
||||
{
|
||||
name: "Replace boolean true",
|
||||
input: "enabled = $enabled",
|
||||
expected: "enabled = true",
|
||||
},
|
||||
{
|
||||
name: "Replace boolean false",
|
||||
input: "disabled = $disabled",
|
||||
expected: "disabled = false",
|
||||
},
|
||||
{
|
||||
name: "Replace integer",
|
||||
input: "count = $count",
|
||||
expected: "count = 42",
|
||||
},
|
||||
{
|
||||
name: "Multiple replacements",
|
||||
input: "$count * $multiplier",
|
||||
expected: "42 * 2.5",
|
||||
},
|
||||
{
|
||||
name: "No variables",
|
||||
input: "v1 * 2",
|
||||
expected: "v1 * 2",
|
||||
},
|
||||
{
|
||||
name: "Undefined variable",
|
||||
input: "v1 * $undefined",
|
||||
expected: "v1 * $undefined",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := replaceVariables(tt.input)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Test SetVariables with all type cases
|
||||
func TestSetVariablesAllTypes(t *testing.T) {
|
||||
vars := map[string]interface{}{
|
||||
"int_val": 42,
|
||||
"int64_val": int64(100),
|
||||
"float32_val": float32(3.14),
|
||||
"float64_val": 2.718,
|
||||
"bool_true": true,
|
||||
"bool_false": false,
|
||||
"string_val": "hello",
|
||||
}
|
||||
|
||||
SetVariables(vars)
|
||||
|
||||
// Create Lua state to verify
|
||||
L, err := NewLuaState()
|
||||
assert.NoError(t, err)
|
||||
defer L.Close()
|
||||
|
||||
// Verify int64
|
||||
int64Val := L.GetGlobal("int64_val")
|
||||
assert.Equal(t, lua.LTNumber, int64Val.Type())
|
||||
assert.Equal(t, 100.0, float64(int64Val.(lua.LNumber)))
|
||||
|
||||
// Verify float32
|
||||
float32Val := L.GetGlobal("float32_val")
|
||||
assert.Equal(t, lua.LTNumber, float32Val.Type())
|
||||
assert.InDelta(t, 3.14, float64(float32Val.(lua.LNumber)), 0.01)
|
||||
|
||||
// Verify bool true
|
||||
boolTrue := L.GetGlobal("bool_true")
|
||||
assert.Equal(t, lua.LTBool, boolTrue.Type())
|
||||
assert.True(t, bool(boolTrue.(lua.LBool)))
|
||||
|
||||
// Verify bool false
|
||||
boolFalse := L.GetGlobal("bool_false")
|
||||
assert.Equal(t, lua.LTBool, boolFalse.Type())
|
||||
assert.False(t, bool(boolFalse.(lua.LBool)))
|
||||
|
||||
// Verify string
|
||||
stringVal := L.GetGlobal("string_val")
|
||||
assert.Equal(t, lua.LTString, stringVal.Type())
|
||||
assert.Equal(t, "hello", string(stringVal.(lua.LString)))
|
||||
}
|
||||
|
||||
// Test HTTP fetch with test server
|
||||
func TestFetchWithTestServer(t *testing.T) {
|
||||
// Create test HTTP server
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Verify request
|
||||
assert.Equal(t, "GET", r.Method)
|
||||
|
||||
// Send response
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`{"status": "success"}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
// Test fetch
|
||||
L := lua.NewState()
|
||||
defer L.Close()
|
||||
|
||||
L.SetGlobal("fetch", L.NewFunction(fetch))
|
||||
|
||||
script := `
|
||||
response = fetch("` + server.URL + `")
|
||||
assert(response ~= nil, "Expected response")
|
||||
assert(response.ok == true, "Expected ok to be true")
|
||||
assert(response.status == 200, "Expected status 200")
|
||||
assert(response.body == '{"status": "success"}', "Expected correct body")
|
||||
`
|
||||
|
||||
err := L.DoString(script)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestFetchWithTestServerPOST(t *testing.T) {
|
||||
// Create test HTTP server
|
||||
receivedBody := ""
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "POST", r.Method)
|
||||
assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
||||
|
||||
// Read body
|
||||
buf := make([]byte, 1024)
|
||||
n, _ := r.Body.Read(buf)
|
||||
receivedBody = string(buf[:n])
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Write([]byte(`{"created": true}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
L := lua.NewState()
|
||||
defer L.Close()
|
||||
|
||||
L.SetGlobal("fetch", L.NewFunction(fetch))
|
||||
|
||||
script := `
|
||||
local opts = {
|
||||
method = "POST",
|
||||
headers = {["Content-Type"] = "application/json"},
|
||||
body = '{"test": "data"}'
|
||||
}
|
||||
response = fetch("` + server.URL + `", opts)
|
||||
assert(response ~= nil, "Expected response")
|
||||
assert(response.ok == true, "Expected ok to be true")
|
||||
assert(response.status == 201, "Expected status 201")
|
||||
`
|
||||
|
||||
err := L.DoString(script)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `{"test": "data"}`, receivedBody)
|
||||
}
|
||||
|
||||
func TestFetchWithTestServer404(t *testing.T) {
|
||||
// Create test HTTP server that returns 404
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte(`{"error": "not found"}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
L := lua.NewState()
|
||||
defer L.Close()
|
||||
|
||||
L.SetGlobal("fetch", L.NewFunction(fetch))
|
||||
|
||||
script := `
|
||||
response = fetch("` + server.URL + `")
|
||||
assert(response ~= nil, "Expected response")
|
||||
assert(response.ok == false, "Expected ok to be false for 404")
|
||||
assert(response.status == 404, "Expected status 404")
|
||||
`
|
||||
|
||||
err := L.DoString(script)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
366
processor/processor_helper_test.go
Normal file
366
processor/processor_helper_test.go
Normal file
@@ -0,0 +1,366 @@
|
||||
package processor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
func TestSetVariables(t *testing.T) {
|
||||
// Test with various variable types
|
||||
vars := map[string]interface{}{
|
||||
"multiplier": 2.5,
|
||||
"prefix": "TEST_",
|
||||
"enabled": true,
|
||||
"count": 42,
|
||||
}
|
||||
|
||||
SetVariables(vars)
|
||||
|
||||
// Create a new Lua state to verify variables are set
|
||||
L, err := NewLuaState()
|
||||
assert.NoError(t, err)
|
||||
defer L.Close()
|
||||
|
||||
// Verify the variables are accessible
|
||||
multiplier := L.GetGlobal("multiplier")
|
||||
assert.Equal(t, lua.LTNumber, multiplier.Type())
|
||||
assert.Equal(t, 2.5, float64(multiplier.(lua.LNumber)))
|
||||
|
||||
prefix := L.GetGlobal("prefix")
|
||||
assert.Equal(t, lua.LTString, prefix.Type())
|
||||
assert.Equal(t, "TEST_", string(prefix.(lua.LString)))
|
||||
|
||||
enabled := L.GetGlobal("enabled")
|
||||
assert.Equal(t, lua.LTBool, enabled.Type())
|
||||
assert.True(t, bool(enabled.(lua.LBool)))
|
||||
|
||||
count := L.GetGlobal("count")
|
||||
assert.Equal(t, lua.LTNumber, count.Type())
|
||||
assert.Equal(t, 42.0, float64(count.(lua.LNumber)))
|
||||
}
|
||||
|
||||
func TestSetVariablesEmpty(t *testing.T) {
|
||||
// Test with empty map
|
||||
vars := map[string]interface{}{}
|
||||
SetVariables(vars)
|
||||
|
||||
// Should not panic
|
||||
L, err := NewLuaState()
|
||||
assert.NoError(t, err)
|
||||
defer L.Close()
|
||||
}
|
||||
|
||||
func TestSetVariablesNil(t *testing.T) {
|
||||
// Test with nil map
|
||||
SetVariables(nil)
|
||||
|
||||
// Should not panic
|
||||
L, err := NewLuaState()
|
||||
assert.NoError(t, err)
|
||||
defer L.Close()
|
||||
}
|
||||
|
||||
func TestGetLuaFunctionsHelp(t *testing.T) {
|
||||
help := GetLuaFunctionsHelp()
|
||||
|
||||
// Verify help is not empty
|
||||
assert.NotEmpty(t, help)
|
||||
|
||||
// Verify it contains documentation for key functions
|
||||
assert.Contains(t, help, "MATH FUNCTIONS")
|
||||
assert.Contains(t, help, "STRING FUNCTIONS")
|
||||
assert.Contains(t, help, "TABLE FUNCTIONS")
|
||||
assert.Contains(t, help, "XML HELPER FUNCTIONS")
|
||||
assert.Contains(t, help, "JSON HELPER FUNCTIONS")
|
||||
assert.Contains(t, help, "HTTP FUNCTIONS")
|
||||
assert.Contains(t, help, "REGEX FUNCTIONS")
|
||||
assert.Contains(t, help, "UTILITY FUNCTIONS")
|
||||
assert.Contains(t, help, "EXAMPLES")
|
||||
|
||||
// Verify specific functions are documented
|
||||
assert.Contains(t, help, "min(a, b)")
|
||||
assert.Contains(t, help, "max(a, b)")
|
||||
assert.Contains(t, help, "round(x, n)")
|
||||
assert.Contains(t, help, "fetch(url, options)")
|
||||
assert.Contains(t, help, "findElements(root, tagName)")
|
||||
assert.Contains(t, help, "visitJSON(data, callback)")
|
||||
assert.Contains(t, help, "re(pattern, input)")
|
||||
assert.Contains(t, help, "print(...)")
|
||||
}
|
||||
|
||||
func TestFetchFunction(t *testing.T) {
|
||||
L := lua.NewState()
|
||||
defer L.Close()
|
||||
|
||||
// Register the fetch function
|
||||
L.SetGlobal("fetch", L.NewFunction(fetch))
|
||||
|
||||
// Test 1: Missing URL should return nil and error
|
||||
err := L.DoString(`
|
||||
result, err = fetch("")
|
||||
assert(result == nil, "Expected nil result for empty URL")
|
||||
assert(err ~= nil, "Expected error for empty URL")
|
||||
`)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test 2: Invalid URL should return error
|
||||
err = L.DoString(`
|
||||
result, err = fetch("not-a-valid-url")
|
||||
assert(result == nil, "Expected nil result for invalid URL")
|
||||
assert(err ~= nil, "Expected error for invalid URL")
|
||||
`)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestFetchFunctionWithOptions(t *testing.T) {
|
||||
L := lua.NewState()
|
||||
defer L.Close()
|
||||
|
||||
// Register the fetch function
|
||||
L.SetGlobal("fetch", L.NewFunction(fetch))
|
||||
|
||||
// Test with options (should fail gracefully with invalid URL)
|
||||
err := L.DoString(`
|
||||
local opts = {
|
||||
method = "POST",
|
||||
headers = {["Content-Type"] = "application/json"},
|
||||
body = '{"test": "data"}'
|
||||
}
|
||||
result, err = fetch("http://invalid-domain-that-does-not-exist.local", opts)
|
||||
-- Should get error due to invalid domain
|
||||
assert(result == nil, "Expected nil result for invalid domain")
|
||||
assert(err ~= nil, "Expected error for invalid domain")
|
||||
`)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestPrependLuaAssignment(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "Simple assignment",
|
||||
input: "10",
|
||||
expected: "v1 = 10",
|
||||
},
|
||||
{
|
||||
name: "Expression",
|
||||
input: "v1 * 2",
|
||||
expected: "v1 = v1 * 2",
|
||||
},
|
||||
{
|
||||
name: "Assignment with equal sign",
|
||||
input: "= 5",
|
||||
expected: "v1 = 5",
|
||||
},
|
||||
{
|
||||
name: "Complex expression",
|
||||
input: "math.floor(v1 / 2)",
|
||||
expected: "v1 = math.floor(v1 / 2)",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := PrependLuaAssignment(tt.input)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestBuildJSONLuaScript(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
contains []string
|
||||
}{
|
||||
{
|
||||
name: "Simple JSON modification",
|
||||
input: "data.value = data.value * 2; modified = true",
|
||||
contains: []string{
|
||||
"data.value = data.value * 2",
|
||||
"modified = true",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Complex JSON script",
|
||||
input: "for i, item in ipairs(data.items) do item.price = item.price * 1.5 end; modified = true",
|
||||
contains: []string{
|
||||
"for i, item in ipairs(data.items)",
|
||||
"modified = true",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := BuildJSONLuaScript(tt.input)
|
||||
for _, substr := range tt.contains {
|
||||
assert.Contains(t, result, substr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrintToGo(t *testing.T) {
|
||||
L := lua.NewState()
|
||||
defer L.Close()
|
||||
|
||||
// Register the print function
|
||||
L.SetGlobal("print", L.NewFunction(printToGo))
|
||||
|
||||
// Test printing various types
|
||||
err := L.DoString(`
|
||||
print("Hello, World!")
|
||||
print(42)
|
||||
print(true)
|
||||
print(3.14)
|
||||
`)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestEvalRegex(t *testing.T) {
|
||||
L := lua.NewState()
|
||||
defer L.Close()
|
||||
|
||||
// Register the regex function
|
||||
L.SetGlobal("re", L.NewFunction(EvalRegex))
|
||||
|
||||
// Test 1: Simple match
|
||||
err := L.DoString(`
|
||||
matches = re("(\\d+)", "The answer is 42")
|
||||
assert(matches ~= nil, "Expected matches")
|
||||
assert(matches[1] == "42", "Expected full match to be 42")
|
||||
assert(matches[2] == "42", "Expected capture group to be 42")
|
||||
`)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test 2: No match
|
||||
err = L.DoString(`
|
||||
matches = re("(\\d+)", "No numbers here")
|
||||
assert(matches == nil, "Expected nil for no match")
|
||||
`)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test 3: Multiple capture groups
|
||||
err = L.DoString(`
|
||||
matches = re("(\\w+)\\s+(\\d+)", "item 123")
|
||||
assert(matches ~= nil, "Expected matches")
|
||||
assert(matches[1] == "item 123", "Expected full match")
|
||||
assert(matches[2] == "item", "Expected first capture group")
|
||||
assert(matches[3] == "123", "Expected second capture group")
|
||||
`)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestEstimatePatternComplexity(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pattern string
|
||||
minExpected int
|
||||
}{
|
||||
{
|
||||
name: "Simple literal",
|
||||
pattern: "hello",
|
||||
minExpected: 1,
|
||||
},
|
||||
{
|
||||
name: "With capture group",
|
||||
pattern: "(\\d+)",
|
||||
minExpected: 2,
|
||||
},
|
||||
{
|
||||
name: "Complex pattern",
|
||||
pattern: "(?P<name>\\w+)\\s+(?P<value>\\d+\\.\\d+)",
|
||||
minExpected: 3,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
complexity := estimatePatternComplexity(tt.pattern)
|
||||
assert.GreaterOrEqual(t, complexity, tt.minExpected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestParseNumeric(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected float64
|
||||
shouldOk bool
|
||||
}{
|
||||
{"Integer", "42", 42.0, true},
|
||||
{"Float", "3.14", 3.14, true},
|
||||
{"Negative", "-10", -10.0, true},
|
||||
{"Invalid", "not a number", 0, false},
|
||||
{"Empty", "", 0, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, ok := parseNumeric(tt.input)
|
||||
assert.Equal(t, tt.shouldOk, ok)
|
||||
if tt.shouldOk {
|
||||
assert.Equal(t, tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatNumeric(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input float64
|
||||
expected string
|
||||
}{
|
||||
{"Integer value", 42.0, "42"},
|
||||
{"Float value", 3.14, "3.14"},
|
||||
{"Negative integer", -10.0, "-10"},
|
||||
{"Negative float", -3.14, "-3.14"},
|
||||
{"Zero", 0.0, "0"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := formatNumeric(tt.input)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLuaHelperFunctionsDocumentation(t *testing.T) {
|
||||
help := GetLuaFunctionsHelp()
|
||||
|
||||
// All main function categories should be documented
|
||||
expectedCategories := []string{
|
||||
"MATH FUNCTIONS",
|
||||
"STRING FUNCTIONS",
|
||||
"XML HELPER FUNCTIONS",
|
||||
"JSON HELPER FUNCTIONS",
|
||||
}
|
||||
|
||||
for _, category := range expectedCategories {
|
||||
assert.Contains(t, help, category, "Help should contain category: %s", category)
|
||||
}
|
||||
|
||||
// Verify some key functions are mentioned
|
||||
keyFunctions := []string{
|
||||
"findElements",
|
||||
"visitElements",
|
||||
"visitJSON",
|
||||
"round",
|
||||
"fetch",
|
||||
}
|
||||
|
||||
for _, fn := range keyFunctions {
|
||||
assert.Contains(t, help, fn, "Help should mention function: %s", fn)
|
||||
}
|
||||
}
|
||||
87
processor/regex_coverage_test.go
Normal file
87
processor/regex_coverage_test.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package processor
|
||||
|
||||
import (
|
||||
"cook/utils"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Test named capture group fallback when value is not in Lua
|
||||
func TestNamedCaptureGroupFallback(t *testing.T) {
|
||||
pattern := `value = (?P<myvalue>\d+)`
|
||||
input := `value = 42`
|
||||
// Don't set myvalue in Lua, but do something else so we get a match
|
||||
lua := `v1 = v1 * 2 -- Set v1 but not myvalue, test fallback`
|
||||
|
||||
cmd := utils.ModifyCommand{
|
||||
Name: "test_fallback",
|
||||
Regex: pattern,
|
||||
Lua: lua,
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(pattern)
|
||||
matches := re.FindStringSubmatchIndex(input)
|
||||
assert.NotNil(t, matches)
|
||||
|
||||
replacements, err := ProcessRegex(input, cmd, "test.txt")
|
||||
|
||||
// Should not error
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Since only v1 is set, myvalue should keep original
|
||||
// Should have 1 replacement for v1
|
||||
if replacements != nil {
|
||||
assert.GreaterOrEqual(t, len(replacements), 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Test named capture groups with nil value in Lua
|
||||
func TestNamedCaptureGroupNilInLua(t *testing.T) {
|
||||
pattern := `value = (?P<num>\d+)`
|
||||
input := `value = 123`
|
||||
// Set num to nil explicitly, and also set v1 to get a modification
|
||||
lua := `v1 = v1 .. "_test"; num = nil -- v1 modified, num set to nil`
|
||||
|
||||
cmd := utils.ModifyCommand{
|
||||
Name: "test_nil",
|
||||
Regex: pattern,
|
||||
Lua: lua,
|
||||
}
|
||||
|
||||
replacements, err := ProcessRegex(input, cmd, "test.txt")
|
||||
|
||||
// Should not error
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Should have replacements for v1, num should fallback to original
|
||||
if replacements != nil {
|
||||
assert.GreaterOrEqual(t, len(replacements), 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Test multiple named capture groups with some undefined
|
||||
func TestMixedNamedCaptureGroups(t *testing.T) {
|
||||
pattern := `(?P<key>\w+) = (?P<value>\d+)`
|
||||
input := `count = 100`
|
||||
lua := `key = key .. "_modified" -- Only modify key, leave value undefined`
|
||||
|
||||
cmd := utils.ModifyCommand{
|
||||
Name: "test_mixed",
|
||||
Regex: pattern,
|
||||
Lua: lua,
|
||||
}
|
||||
|
||||
replacements, err := ProcessRegex(input, cmd, "test.txt")
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, replacements)
|
||||
|
||||
// Apply replacements
|
||||
result, _ := utils.ExecuteModifications(replacements, input)
|
||||
|
||||
// key should be modified, value should remain unchanged
|
||||
assert.Contains(t, result, "count_modified")
|
||||
assert.Contains(t, result, "100")
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package processor
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
logger "git.site.quack-lab.dev/dave/cylogger"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Only modify logger in test mode
|
||||
// This checks if we're running under 'go test'
|
||||
if os.Getenv("GO_TESTING") == "1" || os.Getenv("TESTING") == "1" {
|
||||
// Initialize logger with ERROR level for tests
|
||||
// to minimize noise in test output
|
||||
logger.Init(logger.LevelError)
|
||||
|
||||
// Optionally redirect logger output to discard
|
||||
// This prevents logger output from interfering with test output
|
||||
disableTestLogs := os.Getenv("ENABLE_TEST_LOGS") != "1"
|
||||
if disableTestLogs {
|
||||
// Create a new logger that writes to nowhere
|
||||
silentLogger := logger.New(io.Discard, "", 0)
|
||||
logger.Default = silentLogger
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ type XMLElement struct {
|
||||
|
||||
// XMLAttribute represents an attribute with its position in the source
|
||||
type XMLAttribute struct {
|
||||
Value string
|
||||
Value string
|
||||
ValueStart int64
|
||||
ValueEnd int64
|
||||
}
|
||||
@@ -57,7 +57,7 @@ func parseXMLWithPositions(content string) (*XMLElement, error) {
|
||||
// Find the actual start position of this element by searching for "<tagname"
|
||||
tagSearchPattern := "<" + t.Name.Local
|
||||
startPos := int64(strings.LastIndex(content[:offset], tagSearchPattern))
|
||||
|
||||
|
||||
element := &XMLElement{
|
||||
Tag: t.Name.Local,
|
||||
Attributes: make(map[string]XMLAttribute),
|
||||
@@ -69,7 +69,7 @@ func parseXMLWithPositions(content string) (*XMLElement, error) {
|
||||
if len(t.Attr) > 0 {
|
||||
tagEnd := offset
|
||||
tagSection := content[startPos:tagEnd]
|
||||
|
||||
|
||||
for _, attr := range t.Attr {
|
||||
// Find attribute in the tag section: attrname="value"
|
||||
attrPattern := attr.Name.Local + `="`
|
||||
@@ -102,7 +102,7 @@ func parseXMLWithPositions(content string) (*XMLElement, error) {
|
||||
if len(stack) > 0 && text != "" {
|
||||
current := stack[len(stack)-1]
|
||||
current.Text = text
|
||||
|
||||
|
||||
// The text content is between lastPos (after >) and offset (before </)
|
||||
// Search for the trimmed text within the raw content
|
||||
textInContent := content[lastPos:offset]
|
||||
@@ -127,34 +127,6 @@ func parseXMLWithPositions(content string) (*XMLElement, error) {
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// xmlElementToMap converts XMLElement to a map for comparison
|
||||
func xmlElementToMap(elem *XMLElement) map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
result["_tag"] = elem.Tag
|
||||
|
||||
if len(elem.Attributes) > 0 {
|
||||
attrs := make(map[string]interface{})
|
||||
for k, v := range elem.Attributes {
|
||||
attrs[k] = v.Value
|
||||
}
|
||||
result["_attr"] = attrs
|
||||
}
|
||||
|
||||
if elem.Text != "" {
|
||||
result["_text"] = elem.Text
|
||||
}
|
||||
|
||||
if len(elem.Children) > 0 {
|
||||
children := make([]interface{}, len(elem.Children))
|
||||
for i, child := range elem.Children {
|
||||
children[i] = xmlElementToMap(child)
|
||||
}
|
||||
result["_children"] = children
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// XMLChange represents a detected difference between original and modified XML structures
|
||||
type XMLChange struct {
|
||||
Type string // "text", "attribute", "add_element", "remove_element"
|
||||
@@ -276,22 +248,6 @@ func findXMLChanges(original, modified *XMLElement, path string) []XMLChange {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle completely new tag types
|
||||
for tag, modChildren := range modChildMap {
|
||||
if !processedTags[tag] {
|
||||
for i, child := range modChildren {
|
||||
childPath := fmt.Sprintf("%s/%s[%d]", path, tag, i)
|
||||
xmlText := serializeXMLElement(child, " ")
|
||||
changes = append(changes, XMLChange{
|
||||
Type: "add_element",
|
||||
Path: childPath,
|
||||
InsertText: xmlText,
|
||||
StartPos: original.EndPos - int64(len(original.Tag)+3),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changes
|
||||
}
|
||||
|
||||
@@ -395,14 +351,6 @@ func applyXMLChanges(changes []XMLChange) []utils.ReplaceCommand {
|
||||
return commands
|
||||
}
|
||||
|
||||
// modifyXMLElement applies modifications to an XMLElement based on a modification function
|
||||
func modifyXMLElement(elem *XMLElement, modifyFunc func(*XMLElement)) *XMLElement {
|
||||
// Deep copy the element
|
||||
copied := deepCopyXMLElement(elem)
|
||||
modifyFunc(copied)
|
||||
return copied
|
||||
}
|
||||
|
||||
// deepCopyXMLElement creates a deep copy of an XMLElement
|
||||
func deepCopyXMLElement(elem *XMLElement) *XMLElement {
|
||||
if elem == nil {
|
||||
@@ -410,12 +358,12 @@ func deepCopyXMLElement(elem *XMLElement) *XMLElement {
|
||||
}
|
||||
|
||||
copied := &XMLElement{
|
||||
Tag: elem.Tag,
|
||||
Text: elem.Text,
|
||||
StartPos: elem.StartPos,
|
||||
EndPos: elem.EndPos,
|
||||
TextStart: elem.TextStart,
|
||||
TextEnd: elem.TextEnd,
|
||||
Tag: elem.Tag,
|
||||
Text: elem.Text,
|
||||
StartPos: elem.StartPos,
|
||||
EndPos: elem.EndPos,
|
||||
TextStart: elem.TextStart,
|
||||
TextEnd: elem.TextEnd,
|
||||
Attributes: make(map[string]XMLAttribute),
|
||||
Children: make([]*XMLElement, len(elem.Children)),
|
||||
}
|
||||
@@ -534,10 +482,6 @@ func xmlElementToLuaTable(L *lua.LState, elem *XMLElement) *lua.LTable {
|
||||
table.RawSetString("_attr", attrs)
|
||||
}
|
||||
|
||||
if elem.Text != "" {
|
||||
table.RawSetString("_text", lua.LString(elem.Text))
|
||||
}
|
||||
|
||||
if len(elem.Children) > 0 {
|
||||
children := L.CreateTable(len(elem.Children), 0)
|
||||
for i, child := range elem.Children {
|
||||
@@ -551,11 +495,6 @@ func xmlElementToLuaTable(L *lua.LState, elem *XMLElement) *lua.LTable {
|
||||
|
||||
// luaTableToXMLElement applies Lua table modifications back to XMLElement
|
||||
func luaTableToXMLElement(L *lua.LState, table *lua.LTable, elem *XMLElement) {
|
||||
// Update text
|
||||
if textVal := table.RawGetString("_text"); textVal.Type() == lua.LTString {
|
||||
elem.Text = string(textVal.(lua.LString))
|
||||
}
|
||||
|
||||
// Update attributes
|
||||
if attrVal := table.RawGetString("_attr"); attrVal.Type() == lua.LTTable {
|
||||
attrTable := attrVal.(*lua.LTable)
|
||||
@@ -574,7 +513,7 @@ func luaTableToXMLElement(L *lua.LState, table *lua.LTable, elem *XMLElement) {
|
||||
if childrenVal := table.RawGetString("_children"); childrenVal.Type() == lua.LTTable {
|
||||
childrenTable := childrenVal.(*lua.LTable)
|
||||
newChildren := []*XMLElement{}
|
||||
|
||||
|
||||
// Iterate over array indices
|
||||
for i := 1; ; i++ {
|
||||
childVal := childrenTable.RawGetInt(i)
|
||||
|
||||
Reference in New Issue
Block a user