package processor import ( "modify/utils" "regexp" "strings" "testing" ) // TestLogger implements the Logger interface for testing type TestLogger struct { T *testing.T // Reference to the test's *testing.T } func (l *TestLogger) Printf(format string, v ...interface{}) { if l.T != nil { l.T.Logf(format, v...) } } // Helper function to normalize whitespace for comparison func normalizeWhitespace(s string) string { // Replace all whitespace with a single space re := regexp.MustCompile(`\s+`) return re.ReplaceAllString(strings.TrimSpace(s), " ") } func ApiAdaptor(content string, regex string, lua string) (string, int, int, error) { command := utils.ModifyCommand{ Pattern: regex, Lua: lua, LogLevel: "TRACE", } commands, err := ProcessRegex(content, command) if err != nil { return "", 0, 0, err } result, modifications := utils.ExecuteModifications(commands, content) return result, modifications, len(commands), nil } func TestBuildLuaScript(t *testing.T) { cases := []struct { input string expected string }{ {"s1 .. '_suffix'", "v1 = s1 .. '_suffix'"}, {"v1 * 1.5", "v1 = v1 * 1.5"}, {"v1 + 10", "v1 = v1 + 10"}, {"v1 * 2", "v1 = v1 * 2"}, {"v1 * v2", "v1 = v1 * v2"}, {"v1 / v2", "v1 = v1 / v2"}, {"12", "v1 = 12"}, } for _, c := range cases { result := PrependLuaAssignment(c.input) if result != c.expected { t.Errorf("BuildLuaScript(%q): expected %q, got %q", c.input, c.expected, result) } } } func TestSimpleValueMultiplication(t *testing.T) { content := ` 100 ` expected := ` 150 ` result, mods, matches, err := ApiAdaptor(content, `(?s)(\d+)`, "v1 = v1*1.5") if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 1 { t.Errorf("Expected 1 match, got %d", matches) } if mods != 1 { t.Errorf("Expected 1 modification, got %d", mods) } if result != expected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } func TestShorthandNotation(t *testing.T) { content := ` 100 ` expected := ` 150 ` result, mods, matches, err := ApiAdaptor(content, `(?s)(\d+)`, "v1*1.5") if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 1 { t.Errorf("Expected 1 match, got %d", matches) } if mods != 1 { t.Errorf("Expected 1 modification, got %d", mods) } if result != expected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } func TestShorthandNotationFloats(t *testing.T) { content := ` 10.5 ` expected := ` 15.75 ` result, mods, matches, err := ApiAdaptor(content, `(?s)(\d+\.\d+)`, "v1*1.5") if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 1 { t.Errorf("Expected 1 match, got %d", matches) } if mods != 1 { t.Errorf("Expected 1 modification, got %d", mods) } if result != expected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } func TestArrayNotation(t *testing.T) { content := ` 10 20 30 ` expected := ` 20 40 60 ` result, mods, matches, err := ApiAdaptor(content, `(?s)(\d+)`, "v1*2") if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 3 { t.Errorf("Expected 3 matches, got %d", matches) } if mods != 3 { t.Errorf("Expected 3 modifications, got %d", mods) } if result != expected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } func TestMultipleNumericMatches(t *testing.T) { content := ` 50 100 200 ` expected := ` 100 200 400 ` result, mods, matches, err := ApiAdaptor(content, `(\d+)`, "v1*2") if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 3 { t.Errorf("Expected 3 matches, got %d", matches) } if mods != 3 { t.Errorf("Expected 3 modifications, got %d", mods) } if result != expected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } func TestMultipleStringMatches(t *testing.T) { content := ` John Mary ` expected := ` John_modified Mary_modified ` result, mods, matches, err := ApiAdaptor(content, `([A-Za-z]+)`, `s1 = s1 .. "_modified"`) if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 2 { t.Errorf("Expected 2 matches, got %d", matches) } if mods != 2 { t.Errorf("Expected 2 modifications, got %d", mods) } if result != expected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } func TestStringUpperCase(t *testing.T) { content := ` John Mary ` expected := ` JOHN MARY ` result, mods, matches, err := ApiAdaptor(content, `([A-Za-z]+)`, `s1 = string.upper(s1)`) if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 2 { t.Errorf("Expected 2 matches, got %d", matches) } if mods != 2 { t.Errorf("Expected 2 modifications, got %d", mods) } if result != expected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } func TestStringConcatenation(t *testing.T) { content := ` Apple Banana ` expected := ` Apple_fruit Banana_fruit ` result, mods, matches, err := ApiAdaptor(content, `([A-Za-z]+)`, `s1 = s1 .. "_fruit"`) if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 2 { t.Errorf("Expected 2 matches, got %d", matches) } if mods != 2 { t.Errorf("Expected 2 modifications, got %d", mods) } if result != expected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } // Added from main_test.go func TestDecimalValues(t *testing.T) { content := ` 10.5 2.5 ` expected := ` 26.25 2.5 ` regex := regexp.MustCompile(`(?s)([0-9.]+).*?([0-9.]+)`) luaExpr := BuildLuaScript("v1 = v1 * v2") result, _, _, err := ApiAdaptor(content, regex.String(), luaExpr) if err != nil { t.Fatalf("Error processing content: %v", err) } normalizedModified := normalizeWhitespace(result) normalizedExpected := normalizeWhitespace(expected) if normalizedModified != normalizedExpected { t.Fatalf("Expected modified content to be %q, but got %q", normalizedExpected, normalizedModified) } } // Added from main_test.go func TestLuaMathFunctions(t *testing.T) { content := ` 16 ` expected := ` 4 ` regex := regexp.MustCompile(`(?s)(\d+)`) luaExpr := BuildLuaScript("v1 = math.sqrt(v1)") modifiedContent, _, _, err := ApiAdaptor(content, regex.String(), luaExpr) if err != nil { t.Fatalf("Error processing content: %v", err) } normalizedModified := normalizeWhitespace(modifiedContent) normalizedExpected := normalizeWhitespace(expected) if normalizedModified != normalizedExpected { t.Fatalf("Expected modified content to be %q, but got %q", normalizedExpected, normalizedModified) } } // Added from main_test.go func TestDirectAssignment(t *testing.T) { content := ` 100 ` expected := ` 0 ` regex := regexp.MustCompile(`(?s)(\d+)`) luaExpr := BuildLuaScript("=0") modifiedContent, _, _, err := ApiAdaptor(content, regex.String(), luaExpr) if err != nil { t.Fatalf("Error processing content: %v", err) } normalizedModified := normalizeWhitespace(modifiedContent) normalizedExpected := normalizeWhitespace(expected) if normalizedModified != normalizedExpected { t.Fatalf("Expected modified content to be %q, but got %q", normalizedExpected, normalizedModified) } } // Added from main_test.go func TestStringAndNumericOperations(t *testing.T) { tests := []struct { name string input string regexPattern string luaExpression string expectedOutput string expectedMods int }{ { name: "Basic numeric multiplication", input: "42", regexPattern: "(\\d+)", luaExpression: "v1 = v1 * 2", expectedOutput: "84", expectedMods: 1, }, { name: "Basic string manipulation", input: "test", regexPattern: "(.*?)", luaExpression: "s1 = string.upper(s1)", expectedOutput: "TEST", expectedMods: 1, }, { name: "String concatenation", input: "abc123", regexPattern: "(.*?)", luaExpression: "s1 = s1 .. '_modified'", expectedOutput: "abc123_modified", expectedMods: 1, }, { name: "Numeric value from string using num()", input: "19.99", regexPattern: "(.*?)", luaExpression: "v1 = num(s1) * 1.2", expectedOutput: "23.987999999999996", expectedMods: 1, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Compile the regex pattern with multiline support pattern := "(?s)" + tt.regexPattern luaExpr := BuildLuaScript(tt.luaExpression) // Process with our function result, modCount, _, err := ApiAdaptor(tt.input, pattern, luaExpr) if err != nil { t.Fatalf("Process function failed: %v", err) } // Check results if result != tt.expectedOutput { t.Errorf("Expected output: %s, got: %s", tt.expectedOutput, result) } if modCount != tt.expectedMods { t.Errorf("Expected %d modifications, got %d", tt.expectedMods, modCount) } }) } } // Added from main_test.go func TestEdgeCases(t *testing.T) { tests := []struct { name string input string regexPattern string luaExpression string expectedOutput string expectedMods int }{ { name: "Empty capture group", input: "", regexPattern: "(.*?)", luaExpression: "s1 = 'filled'", expectedOutput: "filled", expectedMods: 1, }, { name: "Non-numeric string with numeric operation", input: "abc", regexPattern: "(.*?)", luaExpression: "v1 = v1 * 2", // This would fail if we didn't handle strings properly expectedOutput: "abc", // Should remain unchanged expectedMods: 0, // No modifications }, { name: "Invalid number conversion", input: "abc", regexPattern: "(.*?)", luaExpression: "v1 = num(s1) + 10", // num(s1) should return 0 expectedOutput: "10", expectedMods: 1, }, { name: "Multiline string", input: "Line 1\nLine 2", regexPattern: "(.*?)", luaExpression: "s1 = string.gsub(s1, '\\n', ' - ')", expectedOutput: "Line 1 - Line 2", expectedMods: 1, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Make sure the regex can match across multiple lines pattern := "(?s)" + tt.regexPattern luaExpr := BuildLuaScript(tt.luaExpression) // Process with our function result, modCount, _, err := ApiAdaptor(tt.input, pattern, luaExpr) if err != nil { t.Fatalf("Process function failed: %v", err) } // Check results if result != tt.expectedOutput { t.Errorf("Expected output: %s, got: %s", tt.expectedOutput, result) } if modCount != tt.expectedMods { t.Errorf("Expected %d modifications, got %d", tt.expectedMods, modCount) } }) } } func TestNamedCaptureGroups(t *testing.T) { content := ` 100 ` expected := ` 200 ` result, mods, matches, err := ApiAdaptor(content, `(?s)(?\d+)`, "amount = amount * 2") if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 1 { t.Errorf("Expected 1 match, got %d", matches) } if mods != 1 { t.Errorf("Expected 1 modification, got %d", mods) } if result != expected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } func TestNamedCaptureGroupsNum(t *testing.T) { content := ` 100 ` expected := ` 200 ` result, mods, matches, err := ApiAdaptor(content, `(?s)(?!num)`, "amount = amount * 2") if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 1 { t.Errorf("Expected 1 match, got %d", matches) } if mods != 1 { t.Errorf("Expected 1 modification, got %d", mods) } if result != expected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } func TestMultipleNamedCaptureGroups(t *testing.T) { content := ` Widget 15.99 10 ` expected := ` WIDGET 23.99 15 ` result, mods, matches, err := ApiAdaptor(content, `(?s)(?[^<]+).*?(?\d+\.\d+).*?(?\d+)`, `prodName = string.upper(prodName) prodPrice = round(prodPrice + 8, 2) prodQty = prodQty + 5`) if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 3 { t.Errorf("Expected 3 matches, got %d", matches) } if mods != 3 { t.Errorf("Expected 3 modifications, got %d", mods) } if result != expected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } func TestMixedIndexedAndNamedCaptures(t *testing.T) { content := ` 12345 value ` expected := ` 24690 VALUE ` result, mods, matches, err := ApiAdaptor(content, `(?s)(\d+).*?(?[^<]+)`, `v1 = v1 * 2 dataField = string.upper(dataField)`) if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 2 { t.Errorf("Expected 2 matches, got %d", matches) } if mods != 2 { t.Errorf("Expected 2 modifications, got %d", mods) } if result != expected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } func TestComplexNestedNamedCaptures(t *testing.T) { content := `
John Smith 32
john@example.com
` expected := `
JOHN SMITH (32) 32
john@example.com
` result, mods, matches, err := ApiAdaptor(content, `(?s)
.*?(?[^<]+).*?(?\d+)`, `fullName = string.upper(fullName) .. " (" .. age .. ")"`) if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 1 { t.Errorf("Expected 1 match, got %d", matches) } if mods != 1 { t.Errorf("Expected 1 modification, got %d", mods) } if result != expected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } func TestNamedCaptureWithVariableReadback(t *testing.T) { content := ` 100 200 ` expected := ` 150 300 ` result, mods, matches, err := ApiAdaptor(content, `(?s)(?\d+).*?(?\d+)`, `hp = hp * 1.5 mp = mp * 1.5`) if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 2 { t.Errorf("Expected 2 matches, got %d", matches) } if mods != 2 { t.Errorf("Expected 2 modifications, got %d", mods) } if result != expected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } func TestNamedCaptureWithSpecialCharsInName(t *testing.T) { content := `` expected := `` result, mods, matches, err := ApiAdaptor(content, `` expected := `` result, mods, matches, err := ApiAdaptor(content, `attr="(?.*?)"`, `value = value == "" and "default" or value`) if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 1 { t.Errorf("Expected 1 match, got %d", matches) } if mods != 1 { t.Errorf("Expected 1 modification, got %d", mods) } if result != expected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } func TestMultipleNamedCapturesInSameLine(t *testing.T) { content := `` expected := `` result, mods, matches, err := ApiAdaptor(content, `x="(?\d+)" y="(?\d+)" width="(?\d+)" height="(?\d+)"`, `x = x * 2 y = y * 2 w = w * 2 h = h * 2`) if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 4 { t.Errorf("Expected 4 matches, got %d", matches) } if mods != 4 { t.Errorf("Expected 4 modifications, got %d", mods) } if result != expected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } func TestConditionalNamedCapture(t *testing.T) { content := ` ` expected := ` ` result, mods, matches, err := ApiAdaptor(content, ` ` expected := ` ` result, mods, matches, err := ApiAdaptor(content, ` ` expected := ` ` result, mods, matches, err := ApiAdaptor(content, `', price, qty, price * qty)`) if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 1 { t.Errorf("Expected 1 match, got %d", matches) } if mods != 1 { t.Errorf("Expected 1 modification, got %d", mods) } result = normalizeWhitespace(result) expected = normalizeWhitespace(expected) if result != expected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } func TestNamedCaptureWithGlobals(t *testing.T) { content := `25` expected := `77` result, mods, matches, err := ApiAdaptor(content, `(?\d+)`, `if unit == "C" then value = value * 9/5 + 32 unit = "F" elseif unit == "F" then value = (value - 32) * 5/9 unit = "C" end`) if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 2 { t.Errorf("Expected 2 matches, got %d", matches) } if mods != 2 { t.Errorf("Expected 2 modifications, got %d", mods) } if result != expected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } func TestMixedDynamicAndNamedCaptures(t *testing.T) { content := ` ` expected := ` ` result, mods, matches, err := ApiAdaptor(content, ``, `-- Uppercase the name colorName = string.upper(colorName) -- Create hex color local hex = string.format("#%02X%02X%02X", tonumber(r), tonumber(g), tonumber(b)) -- Replace the entire match replacement = string.format('', r, g, b, colorName, hex)`) if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 2 { t.Errorf("Expected 2 matches, got %d", matches) } if mods != 2 { t.Errorf("Expected 2 modifications, got %d", mods) } result = normalizeWhitespace(result) expected = normalizeWhitespace(expected) if result != expected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } func TestNamedCapturesWithMultipleReferences(t *testing.T) { content := `Hello world` expected := `HELLO WORLD` result, mods, matches, err := ApiAdaptor(content, `(?[^<]+)`, `local uppercaseContent = string.upper(content) local contentLength = string.len(content) replacement = string.format('%s', contentLength, uppercaseContent)`) if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 1 { t.Errorf("Expected 1 match, got %d", matches) } if mods != 1 { t.Errorf("Expected 1 modification, got %d", mods) } if result != expected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } func TestNamedCaptureWithJsonData(t *testing.T) { content := `{"name":"John","age":30}` expected := `{"name":"JOHN","age":30}` result, mods, matches, err := ApiAdaptor(content, `(?\{.*?\})`, `-- Parse JSON (simplified, assumes valid JSON) local name = json:match('"name":"([^"]+)"') local upperName = string.upper(name) json = json:gsub('"name":"([^"]+)"', '"name":"' .. upperName .. '"')`) if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 1 { t.Errorf("Expected 1 match, got %d", matches) } if mods != 1 { t.Errorf("Expected 1 modification, got %d", mods) } if result != expected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } func TestNamedCaptureInXML(t *testing.T) { content := ` ABC-123 19.99 25 ` expected := ` ABC-123 23.99 20 ` result, mods, matches, err := ApiAdaptor(content, `(?s)(?\d+\.\d+).*?(?\d+)`, `-- Add 20% to price if USD if currency == "USD" then price = round(price * 1.20, 2) end -- Reduce stock by 5 stock = stock - 5`) if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 2 { t.Errorf("Expected 2 matches, got %d", matches) } if mods != 2 { t.Errorf("Expected 2 modifications, got %d", mods) } if result != expected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } func TestComprehensiveNamedCaptures(t *testing.T) { content := ` Widget A 19.99 15 Widget B 29.99 0 Widget C 39.99 5 ` expected := ` WIDGET A 15.99 15 Widget B 29.99 0 WIDGET C 39.99 5 ` result, mods, matches, err := ApiAdaptor(content, `(?s)]*>\s*(?[^<]+)\s*(?\d+\.\d+)\s*(?\d+)`, `-- Only process in-stock items if status == "in-stock" then -- Transform name to uppercase product_name = string.upper(product_name) -- Apply discount based on currency local discounted = true if currency == "USD" then price = round(price * 0.8, 2) -- 20% discount for USD elseif currency == "GBP" then price = round(price * 0.8, 2) -- 20% discount for GBP price = price + 8 -- Add shipping cost for GBP else discounted = false end -- Add discounted attribute replacement = string.format('\n\t\t\t%s\n\t\t\t%.2f\n\t\t\t%s', sku, status, tostring(discounted), product_name, currency, price, qty) else -- Add discounted attribute for out-of-stock items (always false) replacement = string.format('\n\t\t\t%s\n\t\t\t%s\n\t\t\t%s', sku, status, product_name, currency, price, qty) end`) if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 3 { t.Errorf("Expected 3 matches, got %d", matches) } if mods != 3 { t.Errorf("Expected 3 modifications, got %d", mods) } // 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) } } func TestVariousNamedCaptureFormats(t *testing.T) { content := ` ` expected := ` ` result, mods, matches, err := ApiAdaptor(content, ``, `-- Prefix the ID with "ID-" id_num = "ID-" .. id_num print(id_num) print(val) print(status) -- Double the value except for inactive status if not status or status ~= "inactive" then val = val * 2 end -- Convert status to uppercase if present and active if status and status == "active" then status = string.upper(status) end -- Build the replacement based on whether status exists if status then replacement = string.format('', id_num, val, status) else replacement = string.format('', id_num, val) end`) if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 3 { t.Errorf("Expected 3 matches, got %d", matches) } if mods != 3 { t.Errorf("Expected 3 modifications, got %d", mods) } normalizedResult := normalizeWhitespace(result) normalizedExpected := normalizeWhitespace(expected) if normalizedResult != normalizedExpected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } } func TestSimpleNamedCapture(t *testing.T) { content := `` expected := `` result, mods, matches, err := ApiAdaptor(content, `name="(?[^"]+)"`, `product_name = string.upper(product_name)`) if err != nil { t.Fatalf("Error processing content: %v", err) } if matches != 1 { t.Errorf("Expected 1 match, got %d", matches) } if mods != 1 { t.Errorf("Expected 1 modification, got %d", mods) } if result != expected { t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) } }