/** * ResourceModuleTest - Independent validation tests * * Tests the ResourceModule in isolation without requiring the full game. * Validates core functionality: inventory, crafting, state preservation. */ #include "../src/modules/core/ResourceModule.h" #include #include #include #include #include // Simple assertion helper #define TEST_ASSERT(condition, message) \ if (!(condition)) { \ std::cerr << "[FAILED] " << message << std::endl; \ return false; \ } else { \ std::cout << "[PASSED] " << message << std::endl; \ } // Mock TaskScheduler (not used in ResourceModule but required by interface) class MockTaskScheduler : public grove::ITaskScheduler { public: void scheduleTask(const std::string& taskType, std::unique_ptr taskData) override {} int hasCompletedTasks() const override { return 0; } std::unique_ptr getCompletedTask() override { return nullptr; } }; /** * Test 1: Add and remove resources */ bool test_add_remove_resources() { std::cout << "\n=== Test 1: Add/Remove Resources ===" << std::endl; // Create module and mock IO auto module = std::make_unique(); auto io = std::make_unique("ResourceModule"); MockTaskScheduler scheduler; // Create minimal config auto config = std::make_unique("config"); auto resources = std::make_unique("resources"); auto scrapMetal = std::make_unique("scrap_metal"); scrapMetal->setInt("maxStack", 100); scrapMetal->setDouble("weight", 1.5); scrapMetal->setInt("baseValue", 10); scrapMetal->setInt("lowThreshold", 15); resources->setChild("scrap_metal", std::move(scrapMetal)); config->setChild("resources", std::move(resources)); // Initialize module module->setConfiguration(*config, io.get(), &scheduler); // Subscribe to inventory_changed events io->subscribe("resource:inventory_changed"); // Test adding resource auto addRequest = std::make_unique("add_request"); addRequest->setString("resource_id", "scrap_metal"); addRequest->setInt("quantity", 50); io->publish("resource:add_request", std::move(addRequest)); // Process auto processInput = std::make_unique("input"); processInput->setDouble("deltaTime", 0.016); module->process(*processInput); // Check event was published TEST_ASSERT(io->hasMessages() > 0, "inventory_changed event published"); auto msg = io->pullMessage(); TEST_ASSERT(msg.topic == "resource:inventory_changed", "Correct topic"); TEST_ASSERT(msg.data->getString("resource_id", "") == "scrap_metal", "Correct resource"); TEST_ASSERT(msg.data->getInt("delta", 0) == 50, "Delta is 50"); TEST_ASSERT(msg.data->getInt("total", 0) == 50, "Total is 50"); // Test removing resource auto removeRequest = std::make_unique("remove_request"); removeRequest->setString("resource_id", "scrap_metal"); removeRequest->setInt("quantity", 20); io->publish("resource:remove_request", std::move(removeRequest)); module->process(*processInput); TEST_ASSERT(io->hasMessages() > 0, "inventory_changed event published"); msg = io->pullMessage(); TEST_ASSERT(msg.data->getInt("delta", 0) == -20, "Delta is -20"); TEST_ASSERT(msg.data->getInt("total", 0) == 30, "Total is 30"); return true; } /** * Test 2: Crafting system (1 input -> 1 output) */ bool test_crafting() { std::cout << "\n=== Test 2: Crafting System ===" << std::endl; auto module = std::make_unique(); auto io = std::make_unique("ResourceModule"); MockTaskScheduler scheduler; // Create config with resources and recipe auto config = std::make_unique("config"); // Resources auto resources = std::make_unique("resources"); auto scrapMetal = std::make_unique("scrap_metal"); scrapMetal->setInt("maxStack", 100); scrapMetal->setDouble("weight", 1.5); scrapMetal->setInt("baseValue", 10); resources->setChild("scrap_metal", std::move(scrapMetal)); auto repairKit = std::make_unique("repair_kit"); repairKit->setInt("maxStack", 20); repairKit->setDouble("weight", 2.0); repairKit->setInt("baseValue", 50); resources->setChild("repair_kit", std::move(repairKit)); config->setChild("resources", std::move(resources)); // Recipes auto recipes = std::make_unique("recipes"); auto repairKitRecipe = std::make_unique("repair_kit_basic"); repairKitRecipe->setDouble("craftTime", 1.0); // 1 second for test auto inputs = std::make_unique("inputs"); inputs->setInt("scrap_metal", 5); repairKitRecipe->setChild("inputs", std::move(inputs)); auto outputs = std::make_unique("outputs"); outputs->setInt("repair_kit", 1); repairKitRecipe->setChild("outputs", std::move(outputs)); recipes->setChild("repair_kit_basic", std::move(repairKitRecipe)); config->setChild("recipes", std::move(recipes)); // Initialize module->setConfiguration(*config, io.get(), &scheduler); io->subscribe("resource:*"); // Add scrap metal auto addRequest = std::make_unique("add_request"); addRequest->setString("resource_id", "scrap_metal"); addRequest->setInt("quantity", 10); io->publish("resource:add_request", std::move(addRequest)); auto processInput = std::make_unique("input"); processInput->setDouble("deltaTime", 0.016); module->process(*processInput); // Clear messages while (io->hasMessages() > 0) { io->pullMessage(); } // Start craft auto craftRequest = std::make_unique("craft_request"); craftRequest->setString("recipe_id", "repair_kit_basic"); io->publish("resource:craft_request", std::move(craftRequest)); module->process(*processInput); // Check craft_started event bool craftStartedFound = false; while (io->hasMessages() > 0) { auto msg = io->pullMessage(); if (msg.topic == "resource:craft_started") { craftStartedFound = true; TEST_ASSERT(msg.data->getString("recipe_id", "") == "repair_kit_basic", "Correct recipe"); } } TEST_ASSERT(craftStartedFound, "craft_started event published"); // Simulate crafting time (1 second = 1000ms / 16ms = ~63 frames) for (int i = 0; i < 70; ++i) { module->process(*processInput); } // Check craft_complete event bool craftCompleteFound = false; while (io->hasMessages() > 0) { auto msg = io->pullMessage(); if (msg.topic == "resource:craft_complete") { craftCompleteFound = true; TEST_ASSERT(msg.data->getString("recipe_id", "") == "repair_kit_basic", "Correct recipe"); TEST_ASSERT(msg.data->getInt("repair_kit", 0) == 1, "Output produced"); } } TEST_ASSERT(craftCompleteFound, "craft_complete event published"); return true; } /** * Test 3: Hot-reload state preservation */ bool test_state_preservation() { std::cout << "\n=== Test 3: State Preservation (Hot-Reload) ===" << std::endl; auto module1 = std::make_unique(); auto io = std::make_unique("ResourceModule"); MockTaskScheduler scheduler; // Create minimal config auto config = std::make_unique("config"); auto resources = std::make_unique("resources"); auto scrapMetal = std::make_unique("scrap_metal"); scrapMetal->setInt("maxStack", 100); scrapMetal->setDouble("weight", 1.5); scrapMetal->setInt("baseValue", 10); resources->setChild("scrap_metal", std::move(scrapMetal)); config->setChild("resources", std::move(resources)); // Initialize first module module1->setConfiguration(*config, io.get(), &scheduler); // Add some inventory auto addRequest = std::make_unique("add_request"); addRequest->setString("resource_id", "scrap_metal"); addRequest->setInt("quantity", 42); io->publish("resource:add_request", std::move(addRequest)); auto processInput = std::make_unique("input"); processInput->setDouble("deltaTime", 0.016); module1->process(*processInput); // Extract state auto state = module1->getState(); TEST_ASSERT(state != nullptr, "State extracted successfully"); // Verify state content TEST_ASSERT(state->hasChild("inventory"), "State has inventory"); auto inventoryNode = state->getChildReadOnly("inventory"); TEST_ASSERT(inventoryNode != nullptr, "Inventory node exists"); TEST_ASSERT(inventoryNode->getInt("scrap_metal", 0) == 42, "Inventory value preserved"); // Create new module (simulating hot-reload) auto module2 = std::make_unique(); module2->setConfiguration(*config, io.get(), &scheduler); // Restore state module2->setState(*state); // Query inventory to verify auto queryRequest = std::make_unique("query_request"); io->publish("resource:query_inventory", std::move(queryRequest)); module2->process(*processInput); // Check inventory report bool inventoryReportFound = false; while (io->hasMessages() > 0) { auto msg = io->pullMessage(); if (msg.topic == "resource:inventory_report") { inventoryReportFound = true; TEST_ASSERT(msg.data->getInt("scrap_metal", 0) == 42, "Inventory restored correctly"); } } TEST_ASSERT(inventoryReportFound, "inventory_report event published"); return true; } /** * Test 4: Config loading validation */ bool test_config_loading() { std::cout << "\n=== Test 4: Config Loading ===" << std::endl; auto module = std::make_unique(); auto io = std::make_unique("ResourceModule"); MockTaskScheduler scheduler; // Create full config auto config = std::make_unique("config"); // Multiple resources auto resources = std::make_unique("resources"); auto res1 = std::make_unique("resource_a"); res1->setInt("maxStack", 50); res1->setDouble("weight", 1.0); res1->setInt("baseValue", 5); resources->setChild("resource_a", std::move(res1)); auto res2 = std::make_unique("resource_b"); res2->setInt("maxStack", 100); res2->setDouble("weight", 2.0); res2->setInt("baseValue", 10); resources->setChild("resource_b", std::move(res2)); config->setChild("resources", std::move(resources)); // Multiple recipes auto recipes = std::make_unique("recipes"); auto recipe1 = std::make_unique("craft_a_to_b"); recipe1->setDouble("craftTime", 5.0); auto inputs1 = std::make_unique("inputs"); inputs1->setInt("resource_a", 2); recipe1->setChild("inputs", std::move(inputs1)); auto outputs1 = std::make_unique("outputs"); outputs1->setInt("resource_b", 1); recipe1->setChild("outputs", std::move(outputs1)); recipes->setChild("craft_a_to_b", std::move(recipe1)); config->setChild("recipes", std::move(recipes)); // Initialize module->setConfiguration(*config, io.get(), &scheduler); // Check health status reflects loaded config auto health = module->getHealthStatus(); TEST_ASSERT(health != nullptr, "Health status available"); TEST_ASSERT(health->getString("status", "") == "healthy", "Module healthy"); return true; } /** * Main test runner */ int main() { std::cout << "========================================" << std::endl; std::cout << "ResourceModule Independent Tests" << std::endl; std::cout << "========================================" << std::endl; int passed = 0; int total = 4; if (test_add_remove_resources()) passed++; if (test_crafting()) passed++; if (test_state_preservation()) passed++; if (test_config_loading()) passed++; std::cout << "\n========================================" << std::endl; std::cout << "Results: " << passed << "/" << total << " tests passed" << std::endl; std::cout << "========================================" << std::endl; return (passed == total) ? 0 : 1; }