config.mode = 'fortress' local FILE_PATH_PATTERN = 'dfhack-config/orders/%s.json' local BACKUP_FILE_NAME = 'tmp-backup' local BACKUP_FILE_PATH = FILE_PATH_PATTERN:format(BACKUP_FILE_NAME) local TMP_FILE_NAME = 'tmp-test' local TMP_FILE_PATH = FILE_PATH_PATTERN:format(TMP_FILE_NAME) local function test_wrapper(test_fn) -- backup and clear active orders dfhack.run_command_silent{'orders', 'export', BACKUP_FILE_NAME} dfhack.run_command_silent{'orders', 'clear'} df.global.world.manager_order_next_id = 0 return dfhack.with_finalize( function() -- clear test orders, restore original orders, remove temp files dfhack.run_command_silent{'orders', 'clear'} df.global.world.manager_order_next_id = 0 dfhack.run_command_silent{'orders', 'import', BACKUP_FILE_NAME} df.global.world.manager_order_next_id = #df.global.world.manager_orders os.remove(BACKUP_FILE_PATH) os.remove(TMP_FILE_PATH) end, test_fn) end config.wrapper = test_wrapper -- returns export command result and exported file content function run_orders_export() local _, result = dfhack.run_command_silent{'orders', 'export', TMP_FILE_NAME} local f = io.open(TMP_FILE_PATH, 'r') return dfhack.with_finalize( function() f:close() end, function() return result, f:read('*all') end) end function run_orders_import(file_content) local f = io.open(TMP_FILE_PATH, 'w') f:write(file_content) f:close() return dfhack.run_command_silent{'orders', 'import', TMP_FILE_NAME} end local function normalize_whitespace(str) return str:gsub('%s+', ' '):trim() end function check_export_success(expected_file_content) local result, file_content = run_orders_export() expect.eq(result, CR_OK) -- ignore whitespace (otherwise the expected file content is impossible to -- format properly in this file) expect.eq(normalize_whitespace(expected_file_content), normalize_whitespace(file_content)) end function check_import_success(file_content, comment, num_expected_orders) local prev_num_orders = #df.global.world.manager_orders local output, result = run_orders_import(file_content) expect.eq(result, CR_OK, comment) expect.eq(prev_num_orders + num_expected_orders, #df.global.world.manager_orders, comment) end function check_import_fail(file_content, comment, prefix) comment = comment or '' local prev_num_orders = #df.global.world.manager_orders local output, result = run_orders_import(file_content) expect.eq(result, CR_FAILURE, ('%s: was successful'):format(comment)) if prefix then expect.true_(output:lower():startswith(prefix), ('%s: "%s" missing "%s"'):format(comment, output, prefix)) end expect.eq(prev_num_orders, #df.global.world.manager_orders, ('%s: number of manager orders changed'):format(comment)) end function test.import_empty() check_import_success('[]', 'empty input', 0) end function test.import_non_array() check_import_fail('{}', 'object', 'invalid') check_import_fail('null', 'null', 'invalid') check_import_fail('2', 'number', 'invalid') end function test.import_invalid_syntax() -- for https://github.com/DFHack/dfhack/pull/1770 check_import_fail('', 'empty') check_import_fail(']', 'missing opening bracket') check_import_fail([[ [ { "amount_left" : 0, "amount_total" : 0, "frequency" : "OneTime", "id" : 0, "is_active" : false, "is_validated" : true, "job" : "CustomReaction", "reaction" : "BRASS_MAKING" } ]], 'missing closing bracket') end function test.import_missing_fields() check_import_fail('[{}]', 'empty order', 'invalid') end function test.import_invalid_id() -- for https://github.com/DFHack/dfhack/issues/1893 check_import_fail([[ [ { "amount_left": 0, "amount_total": 0, "frequency": "OneTime", "id": "", "is_active": false, "is_validated": false, "item_category": [ "finished_goods" ], "job": "EncrustWithGems", "material": "INORGANIC:AMBER OPAL" } ] ]], 'string id instead of int', 'error') end function test.import_valid_and_invalid_orders() -- check_import_fail([[ -- [ -- { -- "amount_left" : 1, -- "amount_total" : 1, -- "frequency" : "OneTime", -- "id" : 0, -- "is_active" : false, -- "is_validated" : true, -- "job" : "ConstructTable", -- "material" : "INORGANIC:IRON" -- }, -- {} -- ] -- ]], 'empty order after valid order') check_import_fail([[ [ {}, { "amount_left" : 1, "amount_total" : 1, "frequency" : "OneTime", "id" : 0, "is_active" : false, "is_validated" : true, "job" : "ConstructTable", "material" : "INORGANIC:IRON" } ] ]], 'empty order before valid order') end function test.import_export_reaction_condition() local file_content = [[ [ { "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", "id" : 0, "is_active" : false, "is_validated" : false, "item_conditions" : [ { "condition" : "AtLeast", "contains" : [ "lye" ], "reaction_id" : "MAKE_SOAP_FROM_TALLOW", "value" : 5 } ], "job" : "CustomReaction", "reaction" : "MAKE_SOAP_FROM_TALLOW" } ] ]] check_import_success(file_content, 'valid reaction condition', 1) check_export_success(file_content) end local function get_last_order() return df.global.world.manager_orders[#df.global.world.manager_orders-1] end function test.import_invalid_reaction_conditions() check_import_success([[ [ { "amount_left" : 1, "amount_total" : 1, "frequency" : "OneTime", "id" : 0, "is_active" : false, "is_validated" : true, "item_conditions" : [ { "condition" : "AtLeast", "contains" : [ "lye" ], "reaction_id" : "MAKE_SOAP_FROM_TALLOW_xxx", "value" : 5 } ], "job" : "CustomReaction", "reaction" : "MAKE_SOAP_FROM_TALLOW" } ] ]], 'condition ignored for bad reaction id', 1) expect.eq(0, #get_last_order().item_conditions) check_import_success([[ [ { "amount_left" : 1, "amount_total" : 1, "frequency" : "OneTime", "id" : 0, "is_active" : false, "is_validated" : true, "item_conditions" : [ { "condition" : "AtLeast", "contains" : [ "lye_xxx" ], "reaction_id" : "MAKE_SOAP_FROM_TALLOW", "value" : 5 } ], "job" : "CustomReaction", "reaction" : "MAKE_SOAP_FROM_TALLOW" } ] ]], 'condition ignored for bad reagent name', 1) expect.eq(0, #get_last_order().item_conditions) end function test.list() local output, status = dfhack.run_command_silent('orders', 'list') expect.eq(CR_OK, status) expect.str_find(BACKUP_FILE_NAME:gsub('%-', '%%-'), output) end