diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 00d62ff..cf6121f 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -4639,4 +4639,13 @@ target_link_libraries(step639_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step640_test tests/step640_test.cpp) +target_include_directories(step640_test PRIVATE src) +target_link_libraries(step640_test PRIVATE + nlohmann_json::nlohmann_json + unofficial::tree-sitter::tree-sitter + tree_sitter_python tree_sitter_cpp tree_sitter_elisp + tree_sitter_javascript tree_sitter_typescript + tree_sitter_java tree_sitter_rust tree_sitter_go) + # Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies) diff --git a/editor/src/MqttBoilerplateGenerator.h b/editor/src/MqttBoilerplateGenerator.h new file mode 100644 index 0000000..2f24a4a --- /dev/null +++ b/editor/src/MqttBoilerplateGenerator.h @@ -0,0 +1,56 @@ +#pragma once +// Step 640: MQTT pub/sub boilerplate generator + +#include +#include + +struct MqttTopicSpec { + std::string topic; + std::string payloadType; + int qos = 1; +}; + +struct MqttBoilerplateOutput { + bool success = false; + std::string headerCode; + std::string sourceCode; + std::vector errors; +}; + +class MqttBoilerplateGenerator { +public: + static MqttBoilerplateOutput generate(const std::string& clientClass, + const std::vector& topics) { + MqttBoilerplateOutput out; + if (clientClass.empty()) { + out.errors.push_back("client_class_required"); + return out; + } + if (topics.empty()) { + out.errors.push_back("topics_required"); + return out; + } + + out.success = true; + out.headerCode = "#pragma once\n" + "#include \n" + "#include \n\n" + "class " + clientClass + " {\n" + "public:\n" + " bool connect(const std::string& uri);\n" + " void subscribeAll();\n" + " void reconnect();\n" + "};\n"; + + out.sourceCode = "bool " + clientClass + "::connect(const std::string& uri){ (void)uri; return true; }\n" + "void " + clientClass + "::reconnect(){}\n" + "void " + clientClass + "::subscribeAll(){\n"; + for (const auto& t : topics) { + out.sourceCode += " // topic: " + t.topic + " qos=" + std::to_string(t.qos) + " payload=" + t.payloadType + "\n"; + out.sourceCode += " // deserialize via nlohmann::json\n"; + } + out.sourceCode += "}\n"; + + return out; + } +}; diff --git a/editor/tests/step640_test.cpp b/editor/tests/step640_test.cpp new file mode 100644 index 0000000..9dd3e8e --- /dev/null +++ b/editor/tests/step640_test.cpp @@ -0,0 +1,123 @@ +// Step 640: MQTT pub/sub boilerplate generator (12 tests) + +#include "MqttBoilerplateGenerator.h" + +#include + +static int passed = 0, failed = 0; +#define TEST(name) { std::cout << " " << #name << "... "; } +#define PASS() { std::cout << "PASS\n"; ++passed; } +#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; } +#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } + +static std::vector defaultTopics() { + return { + {"borg/energy/context", "EnergyContext", 1}, + {"borg/jobs/pending", "PendingJob", 1}, + {"borg/registry/commit", "RegistryCommit", 2} + }; +} + +void test_generate_requires_client_class() { + TEST(generate_requires_client_class); + auto out = MqttBoilerplateGenerator::generate("", defaultTopics()); + CHECK(!out.success, "empty client class should fail"); + PASS(); +} + +void test_generate_requires_topics() { + TEST(generate_requires_topics); + auto out = MqttBoilerplateGenerator::generate("DroneMqttClient", {}); + CHECK(!out.success, "empty topic list should fail"); + PASS(); +} + +void test_generate_succeeds_with_valid_input() { + TEST(generate_succeeds_with_valid_input); + auto out = MqttBoilerplateGenerator::generate("DroneMqttClient", defaultTopics()); + CHECK(out.success, "generator should succeed"); + PASS(); +} + +void test_header_contains_client_class() { + TEST(header_contains_client_class); + auto out = MqttBoilerplateGenerator::generate("DroneMqttClient", defaultTopics()); + CHECK(out.headerCode.find("class DroneMqttClient") != std::string::npos, "class missing"); + PASS(); +} + +void test_header_contains_connect_method() { + TEST(header_contains_connect_method); + auto out = MqttBoilerplateGenerator::generate("DroneMqttClient", defaultTopics()); + CHECK(out.headerCode.find("bool connect") != std::string::npos, "connect missing"); + PASS(); +} + +void test_header_contains_subscribe_method() { + TEST(header_contains_subscribe_method); + auto out = MqttBoilerplateGenerator::generate("DroneMqttClient", defaultTopics()); + CHECK(out.headerCode.find("void subscribeAll") != std::string::npos, "subscribe missing"); + PASS(); +} + +void test_header_contains_reconnect_method() { + TEST(header_contains_reconnect_method); + auto out = MqttBoilerplateGenerator::generate("DroneMqttClient", defaultTopics()); + CHECK(out.headerCode.find("void reconnect") != std::string::npos, "reconnect missing"); + PASS(); +} + +void test_source_contains_energy_topic() { + TEST(source_contains_energy_topic); + auto out = MqttBoilerplateGenerator::generate("DroneMqttClient", defaultTopics()); + CHECK(out.sourceCode.find("borg/energy/context") != std::string::npos, "energy topic missing"); + PASS(); +} + +void test_source_contains_job_topic() { + TEST(source_contains_job_topic); + auto out = MqttBoilerplateGenerator::generate("DroneMqttClient", defaultTopics()); + CHECK(out.sourceCode.find("borg/jobs/pending") != std::string::npos, "job topic missing"); + PASS(); +} + +void test_source_contains_registry_topic() { + TEST(source_contains_registry_topic); + auto out = MqttBoilerplateGenerator::generate("DroneMqttClient", defaultTopics()); + CHECK(out.sourceCode.find("borg/registry/commit") != std::string::npos, "registry topic missing"); + PASS(); +} + +void test_source_contains_json_deserialization_note() { + TEST(source_contains_json_deserialization_note); + auto out = MqttBoilerplateGenerator::generate("DroneMqttClient", defaultTopics()); + CHECK(out.sourceCode.find("nlohmann::json") != std::string::npos, "json note missing"); + PASS(); +} + +void test_source_contains_qos_for_topics() { + TEST(source_contains_qos_for_topics); + auto out = MqttBoilerplateGenerator::generate("DroneMqttClient", defaultTopics()); + CHECK(out.sourceCode.find("qos=2") != std::string::npos, "qos marker missing"); + PASS(); +} + +int main() { + std::cout << "Step 640: MQTT pub/sub boilerplate generator\n"; + + test_generate_requires_client_class(); + test_generate_requires_topics(); + test_generate_succeeds_with_valid_input(); + test_header_contains_client_class(); + test_header_contains_connect_method(); + test_header_contains_subscribe_method(); + test_header_contains_reconnect_method(); + test_source_contains_energy_topic(); + test_source_contains_job_topic(); + test_source_contains_registry_topic(); + test_source_contains_json_deserialization_note(); + test_source_contains_qos_for_topics(); + + std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index aba4d2d..2166935 100644 --- a/progress.md +++ b/progress.md @@ -13013,3 +13013,34 @@ stubs, dependency wiring, and JSON tool-response shaping. - `editor/src/ProjectSkeletonGenerator.h` (`67` <= `600`) - `editor/tests/step639_test.cpp` within test-file size guidance (`121` lines) - Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md` + +### Step 640: MQTT pub/sub boilerplate generator +**Status:** PASS (12/12 tests) + +Adds MQTT boilerplate generation for typed pub/sub client scaffolding with +topic/QoS metadata, reconnect hooks, and nlohmann-json deserialization points +for generated handlers. + +**Files added:** +- `editor/src/MqttBoilerplateGenerator.h` - MQTT boilerplate module: + - validates client/topic input + - emits client header and source skeleton text + - embeds topic and QoS handling markers for generated code +- `editor/tests/step640_test.cpp` - 12 tests covering: + - required-input edge cases + - client API method emission + - expected topic/QoS and JSON parsing markers + +**Files modified:** +- `editor/CMakeLists.txt` - `step640_test` target + +**Verification run:** +- `cmake -S editor -B editor/build-native` - PASS +- `cmake --build editor/build-native --target step640_test step639_test` - PASS +- `./editor/build-native/step640_test` - PASS (12/12) +- `./editor/build-native/step639_test` - PASS (12/12) regression coverage + +**Architecture gate check:** +- `editor/src/MqttBoilerplateGenerator.h` (`56` <= `600`) +- `editor/tests/step640_test.cpp` within test-file size guidance (`123` lines) +- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`