package processor import ( "cook/utils" "testing" ) func TestSurgicalJSONEditing(t *testing.T) { tests := []struct { name string content string luaCode string expected string skip bool }{ { name: "Modify single field", content: `{ "name": "test", "value": 42, "description": "original" }`, luaCode: ` data.value = 84 modified = true `, expected: `{ "name": "test", "value": 84, "description": "original" }`, }, { name: "Add new field", content: `{ "name": "test", "value": 42 }`, luaCode: ` data.newField = "added" modified = true `, expected: `{ "name": "test", "value": 42 ,"newField":"added"}`, }, { name: "Modify nested field", content: `{ "config": { "settings": { "enabled": false, "timeout": 30 } } }`, luaCode: ` data.config.settings.enabled = true data.config.settings.timeout = 60 modified = true `, expected: `{ "config": { "settings": { "enabled": true, "timeout": 60 } } }`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.skip { t.Skip("Skipping test due to surgical approach not handling this case yet") } command := utils.ModifyCommand{ Name: "test", Lua: tt.luaCode, } commands, err := ProcessJSON(tt.content, command, "test.json") if err != nil { t.Fatalf("ProcessJSON failed: %v", err) } if len(commands) == 0 { t.Fatal("Expected at least one command") } // Apply the commands result := tt.content for _, cmd := range commands { result = result[:cmd.From] + cmd.With + result[cmd.To:] } // Check the actual result matches expected if result != tt.expected { t.Errorf("Expected:\n%s\n\nGot:\n%s", tt.expected, result) } }) } } func TestSurgicalJSONPreservesFormatting(t *testing.T) { // Test that surgical editing preserves the original formatting structure content := `{ "Defaults": { "Behaviour": "None", "Description": "", "DisplayName": "", "FlavorText": "", "Icon": "None", "MaxStack": 1, "Override_Glow_Icon": "None", "Weight": 0, "bAllowZeroWeight": false }, "RowStruct": "/Script/Icarus.ItemableData", "Rows": [ { "Description": "NSLOCTEXT(\"D_Itemable\", \"Item_Fiber-Description\", \"A bundle of soft fiber, highly useful.\")", "DisplayName": "NSLOCTEXT(\"D_Itemable\", \"Item_Fiber-DisplayName\", \"Fiber\")", "FlavorText": "NSLOCTEXT(\"D_Itemable\", \"Item_Fiber-FlavorText\", \"Fiber is collected from bast, the strong inner bark of certain flowering plants.\")", "Icon": "/Game/Assets/2DArt/UI/Items/Item_Icons/Resources/ITEM_Fibre.ITEM_Fibre", "MaxStack": 1000000, "Name": "Item_Fiber", "Weight": 10 } ] }` expected := `{ "Defaults": { "Behaviour": "None", "Description": "", "DisplayName": "", "FlavorText": "", "Icon": "None", "MaxStack": 1, "Override_Glow_Icon": "None", "Weight": 0, "bAllowZeroWeight": false }, "RowStruct": "/Script/Icarus.ItemableData", "Rows": [ { "Description": "NSLOCTEXT(\"D_Itemable\", \"Item_Fiber-Description\", \"A bundle of soft fiber, highly useful.\")", "DisplayName": "NSLOCTEXT(\"D_Itemable\", \"Item_Fiber-DisplayName\", \"Fiber\")", "FlavorText": "NSLOCTEXT(\"D_Itemable\", \"Item_Fiber-FlavorText\", \"Fiber is collected from bast, the strong inner bark of certain flowering plants.\")", "Icon": "/Game/Assets/2DArt/UI/Items/Item_Icons/Resources/ITEM_Fibre.ITEM_Fibre", "MaxStack": 1000000, "Name": "Item_Fiber", "Weight": 500 } ] }` command := utils.ModifyCommand{ Name: "test", Lua: ` -- Modify the weight of the first item data.Rows[1].Weight = 500 modified = true `, } commands, err := ProcessJSON(content, command, "test.json") if err != nil { t.Fatalf("ProcessJSON failed: %v", err) } if len(commands) == 0 { t.Fatal("Expected at least one command") } // Apply the commands result := content for _, cmd := range commands { result = result[:cmd.From] + cmd.With + result[cmd.To:] } // Check that the result matches expected (preserves formatting and changes weight) if result != expected { t.Errorf("Expected:\n%s\n\nGot:\n%s", expected, result) } } func TestSurgicalJSONPreservesFormatting2(t *testing.T) { // Test that surgical editing preserves the original formatting structure content := ` { "RowStruct": "/Script/Icarus.ProcessorRecipe", "Defaults": { "bForceDisableRecipe": false, "Requirement": { "RowName": "None", "DataTableName": "D_Talents" }, "SessionRequirement": { "RowName": "None", "DataTableName": "D_CharacterFlags" }, "CharacterRequirement": { "RowName": "None", "DataTableName": "D_CharacterFlags" }, "RequiredMillijoules": 2500, "RecipeSets": [], "ResourceCostMultipliers": [], "Inputs": [ { "Element": { "RowName": "None", "DataTableName": "D_ItemsStatic" }, "Count": 1, "DynamicProperties": [] } ], "Container": { "Value": "None" }, "ResourceInputs": [], "bSelectOutputItemRandomly": false, "bContainsContainer": false, "ItemIconOverride": { "ItemStaticData": { "RowName": "None", "DataTableName": "D_ItemsStatic" }, "ItemDynamicData": [], "ItemCustomStats": [], "CustomProperties": { "StaticWorldStats": [], "StaticWorldHeldStats": [], "Stats": [], "Alterations": [], "LivingItemSlots": [] }, "DatabaseGUID": "", "ItemOwnerLookupId": -1, "RuntimeTags": { "GameplayTags": [] } }, "Outputs": [ { "Element": { "RowName": "None", "DataTableName": "D_ItemTemplate" }, "Count": 1, "DynamicProperties": [] } ], "ResourceOutputs": [], "Refundable": "Inherit", "ExperienceMultiplier": 1, "Audio": { "RowName": "None", "DataTableName": "D_CraftingAudioData" } }, "Rows": [ { "Name": "Biofuel1", "RecipeSets": [ { "RowName": "Composter", "DataTableName": "D_RecipeSets" } ], "Inputs": [ { "Element": { "RowName": "Raw_Meat", "DataTableName": "D_ItemsStatic" }, "Count": 2, "DynamicProperties": [] }, { "Element": { "RowName": "Tree_Sap", "DataTableName": "D_ItemsStatic" }, "Count": 1, "DynamicProperties": [] } ], "Outputs": [], "Audio": { "RowName": "Composter" }, "ResourceOutputs": [ { "Type": { "Value": "Biofuel" }, "RequiredUnits": 100 } ] } ] } ` expected := ` { "RowStruct": "/Script/Icarus.ProcessorRecipe", "Defaults": { "bForceDisableRecipe": false, "Requirement": { "RowName": "None", "DataTableName": "D_Talents" }, "SessionRequirement": { "RowName": "None", "DataTableName": "D_CharacterFlags" }, "CharacterRequirement": { "RowName": "None", "DataTableName": "D_CharacterFlags" }, "RequiredMillijoules": 2500, "RecipeSets": [], "ResourceCostMultipliers": [], "Inputs": [ { "Element": { "RowName": "None", "DataTableName": "D_ItemsStatic" }, "Count": 1, "DynamicProperties": [] } ], "Container": { "Value": "None" }, "ResourceInputs": [], "bSelectOutputItemRandomly": false, "bContainsContainer": false, "ItemIconOverride": { "ItemStaticData": { "RowName": "None", "DataTableName": "D_ItemsStatic" }, "ItemDynamicData": [], "ItemCustomStats": [], "CustomProperties": { "StaticWorldStats": [], "StaticWorldHeldStats": [], "Stats": [], "Alterations": [], "LivingItemSlots": [] }, "DatabaseGUID": "", "ItemOwnerLookupId": -1, "RuntimeTags": { "GameplayTags": [] } }, "Outputs": [ { "Element": { "RowName": "None", "DataTableName": "D_ItemTemplate" }, "Count": 1, "DynamicProperties": [] } ], "ResourceOutputs": [], "Refundable": "Inherit", "ExperienceMultiplier": 1, "Audio": { "RowName": "None", "DataTableName": "D_CraftingAudioData" } }, "Rows": [ { "Name": "Biofuel1", "RecipeSets": [ { "RowName": "Composter", "DataTableName": "D_RecipeSets" } ], "Inputs": [ { "Element": { "RowName": "Raw_Meat", "DataTableName": "D_ItemsStatic" }, "Count": 2, "DynamicProperties": [] } ], "Outputs": [], "Audio": { "RowName": "Composter" }, "ResourceOutputs": [ { "Type": { "Value": "Biofuel" }, "RequiredUnits": 100 } ] } ] } ` command := utils.ModifyCommand{ Name: "test", Lua: ` -- Define regex patterns for matching recipe names local function matchesPattern(name, pattern) local matches = re(pattern, name) -- Check if matches table has any content (index 0 or 1 should exist if there's a match) return matches and (matches[0] or matches[1]) end -- Selection pattern for recipes that get multiplied local selectionPattern = "(?-s)(Bulk_)?(Pistol|Rifle).*?Round.*?|(Carbon|Composite)_Paste.*|(Gold|Copper)_Wire|(Ironw|Copper)_Nail|(Platinum|Steel|Cold_Steel|Titanium)_Ingot|.*?Shotgun_Shell.*?|.*_Arrow|.*_Bolt|.*_Fertilizer_?\\d*|.*_Grenade|.*_Pill|.*_Tonic|Aluminum|Ammo_Casing|Animal_Fat|Carbon_Fiber|Composites|Concrete_Mix|Cured_Leather_?\\d?|Electronics|Epoxy_?\\d?|Glass\\d?|Gunpowder\\w*|Health_.*|Titanium_Plate|Organic_Resin|Platinum_Sheath|Refined_[a-zA-Z]+|Rope|Shotgun_Casing|Steel_Bloom\\d?|Tree_Sap\\w*" -- Ingot pattern for recipes that get count set to 1 local ingotPattern = "(?-s)(Platinum|Steel|Cold_Steel|Titanium)_Ingot|Aluminum|Refined_[a-zA-Z]+|Glass\\d?" local factor = 16 local bonus = 0.5 for _, row in ipairs(data.Rows) do local recipeName = row.Name -- Special case: Biofuel recipes - remove Tree_Sap input if string.find(recipeName, "Biofuel") then if row.Inputs then for i = #row.Inputs, 1, -1 do local input = row.Inputs[i] if input.Element and input.Element.RowName and string.find(input.Element.RowName, "Tree_Sap") then table.remove(row.Inputs, i) print("Removing input 'Tree_Sap' from processor recipe '" .. recipeName .. "'") end end end end -- Ingot recipes: set input and output counts to 1 if matchesPattern(recipeName, ingotPattern) then if row.Inputs then for _, input in ipairs(row.Inputs) do input.Count = 1 end end if row.Outputs then for _, output in ipairs(row.Outputs) do output.Count = 1 end end end -- Selected recipes: multiply inputs by factor, outputs by factor * (1 + bonus) if matchesPattern(recipeName, selectionPattern) then if row.Inputs then for _, input in ipairs(row.Inputs) do local oldCount = input.Count input.Count = input.Count * factor print("Recipe " .. recipeName .. " Input.Count: " .. oldCount .. " -> " .. input.Count) end end if row.Outputs then for _, output in ipairs(row.Outputs) do local oldCount = output.Count output.Count = math.floor(output.Count * factor * (1 + bonus)) print("Recipe " .. recipeName .. " Output.Count: " .. oldCount .. " -> " .. output.Count) end end end end `, } commands, err := ProcessJSON(content, command, "test.json") if err != nil { t.Fatalf("ProcessJSON failed: %v", err) } if len(commands) == 0 { t.Fatal("Expected at least one command") } // Apply the commands result := content for _, cmd := range commands { result = result[:cmd.From] + cmd.With + result[cmd.To:] } // Check that the result matches expected (preserves formatting and changes weight) if result != expected { t.Errorf("Expected:\n%s\n\nGot:\n%s", expected, result) } } func TestRetardedJSONEditing(t *testing.T) { original := `{ "RowStruct": "/Script/Icarus.ItemableData", "Defaults": { "Behaviour": "None", "DisplayName": "", "Icon": "None", "Override_Glow_Icon": "None", "Description": "", "FlavorText": "", "Weight": 0, "bAllowZeroWeight": false, "MaxStack": 1 }, "Rows": [ { "DisplayName": "NSLOCTEXT(\"D_Itemable\", \"Item_Fiber-DisplayName\", \"Fiber\")", "Icon": "/Game/Assets/2DArt/UI/Items/Item_Icons/Resources/ITEM_Fibre.ITEM_Fibre", "Description": "NSLOCTEXT(\"D_Itemable\", \"Item_Fiber-Description\", \"A bundle of soft fiber, highly useful.\")", "FlavorText": "NSLOCTEXT(\"D_Itemable\", \"Item_Fiber-FlavorText\", \"Fiber is collected from bast, the strong inner bark of certain flowering plants.\")", "Weight": 10, "MaxStack": 200, "Name": "Item_Fiber" } ] }` expected := `{ "RowStruct": "/Script/Icarus.ItemableData", "Defaults": { "Behaviour": "None", "DisplayName": "", "Icon": "None", "Override_Glow_Icon": "None", "Description": "", "FlavorText": "", "Weight": 0, "bAllowZeroWeight": false, "MaxStack": 1 }, "Rows": [ { "DisplayName": "NSLOCTEXT(\"D_Itemable\", \"Item_Fiber-DisplayName\", \"Fiber\")", "Icon": "/Game/Assets/2DArt/UI/Items/Item_Icons/Resources/ITEM_Fibre.ITEM_Fibre", "Description": "NSLOCTEXT(\"D_Itemable\", \"Item_Fiber-Description\", \"A bundle of soft fiber, highly useful.\")", "FlavorText": "NSLOCTEXT(\"D_Itemable\", \"Item_Fiber-FlavorText\", \"Fiber is collected from bast, the strong inner bark of certain flowering plants.\")", "Weight": 10, "MaxStack": 1000000, "Name": "Item_Fiber" } ] }` command := utils.ModifyCommand{ Name: "test", Lua: ` for _, row in ipairs(data.Rows) do if row.MaxStack then if string.find(row.Name, "Carrot") or string.find(row.Name, "Potato") then row.MaxStack = 25 else row.MaxStack = row.MaxStack * 10000 if row.MaxStack > 1000000 then row.MaxStack = 1000000 end end end end `, } commands, err := ProcessJSON(original, command, "test.json") if err != nil { t.Fatalf("ProcessJSON failed: %v", err) } if len(commands) == 0 { t.Fatal("Expected at least one command") } // Apply the commands result := original for _, cmd := range commands { result = result[:cmd.From] + cmd.With + result[cmd.To:] } // Check that the weight was changed if result != expected { t.Errorf("Expected:\n%s\nGot:\n%s", expected, result) } }