diff --git a/CMakeLists.txt b/CMakeLists.txt index 19ded68..22d601e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -789,6 +789,20 @@ target_link_libraries(json_config_schema_validation_test PRIVATE ) add_test(NAME json_config_schema_validation_test COMMAND json_config_schema_validation_test) +# Test: Workflow definition parser +add_executable(workflow_definition_parser_test + tests/workflow_definition_parser_test.cpp + src/services/impl/workflow_definition_parser.cpp + src/services/impl/json_config_document_parser.cpp +) +target_include_directories(workflow_definition_parser_test PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src") +target_link_libraries(workflow_definition_parser_test PRIVATE + GTest::gtest + GTest::gtest_main + rapidjson +) +add_test(NAME workflow_definition_parser_test COMMAND workflow_definition_parser_test) + # Test: Config compiler reference validation add_executable(config_compiler_reference_validation_test tests/config_compiler_reference_validation_test.cpp diff --git a/ROADMAP.md b/ROADMAP.md index 8e2eee7..0fd73b9 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -285,7 +285,7 @@ Option B: per-shader only - [~] Budget enforcement tests (GUI cache pruning + texture tracker covered; transient pool pending) - [~] Config-driven validation tour (checkpoint captures + image/ratio/luma/sample-point checks) - [ ] Smoke test: cube demo boots with config-first scene definition -- [ ] Workflow parser tests (template loading + invalid step diagnostics) +- [x] Workflow parser tests (template loading + invalid step diagnostics) ## Test Strategy (Solid Coverage Plan) ### Goals diff --git a/tests/workflow_definition_parser_test.cpp b/tests/workflow_definition_parser_test.cpp new file mode 100644 index 0000000..855348f --- /dev/null +++ b/tests/workflow_definition_parser_test.cpp @@ -0,0 +1,82 @@ +#include + +#include "services/impl/workflow_definition_parser.hpp" + +#include +#include +#include +#include + +namespace { + +struct TempFile { + explicit TempFile(const std::string& contents) { + const auto timestamp = std::chrono::steady_clock::now().time_since_epoch().count(); + path = std::filesystem::temp_directory_path() / + ("workflow_definition_parser_" + std::to_string(timestamp) + ".json"); + std::ofstream stream(path); + stream << contents; + } + + ~TempFile() { + std::error_code error; + std::filesystem::remove(path, error); + } + + std::filesystem::path path; +}; + +} // namespace + +TEST(WorkflowDefinitionParserTest, ParsesTemplateAndSteps) { + const std::string json = R"json({ + "template": "boot.default", + "steps": [ + { + "id": "load_config", + "plugin": "config.load", + "inputs": { "path": "config.path" }, + "outputs": { "document": "config.document" } + }, + { + "id": "validate_schema", + "plugin": "config.schema.validate", + "inputs": { "document": "config.document", "path": "config.path" } + } + ] +})json"; + + TempFile file(json); + sdl3cpp::services::impl::WorkflowDefinitionParser parser; + const auto workflow = parser.ParseFile(file.path); + + EXPECT_EQ(workflow.templateName, "boot.default"); + ASSERT_EQ(workflow.steps.size(), 2u); + EXPECT_EQ(workflow.steps[0].id, "load_config"); + EXPECT_EQ(workflow.steps[0].plugin, "config.load"); + EXPECT_EQ(workflow.steps[0].inputs.at("path"), "config.path"); + EXPECT_EQ(workflow.steps[0].outputs.at("document"), "config.document"); + EXPECT_EQ(workflow.steps[1].id, "validate_schema"); + EXPECT_EQ(workflow.steps[1].plugin, "config.schema.validate"); +} + +TEST(WorkflowDefinitionParserTest, FailsWhenPluginIsMissing) { + const std::string json = R"json({ + "template": "boot.invalid", + "steps": [ + { + "id": "load_config" + } + ] +})json"; + + TempFile file(json); + sdl3cpp::services::impl::WorkflowDefinitionParser parser; + try { + parser.ParseFile(file.path); + FAIL() << "Expected workflow parser to reject missing plugin"; + } catch (const std::runtime_error& error) { + const std::string message = error.what(); + EXPECT_NE(message.find("plugin"), std::string::npos); + } +}