diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index bf0779c..ebcd7d4 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -4369,4 +4369,13 @@ target_link_libraries(step609_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step610_test tests/step610_test.cpp) +target_include_directories(step610_test PRIVATE src) +target_link_libraries(step610_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/ServiceDependencyRiskMap.h b/editor/src/ServiceDependencyRiskMap.h new file mode 100644 index 0000000..ada63ca --- /dev/null +++ b/editor/src/ServiceDependencyRiskMap.h @@ -0,0 +1,78 @@ +#pragma once +// Step 610: Service Dependency Risk Map + +#include +#include +#include + +#include "ValidationErrorUtil.h" + +enum class DependencyRisk { + Low, + Medium, + High +}; + +struct DependencyEdge { + std::string edgeId; + std::string sourceService; + std::string targetService; + DependencyRisk risk = DependencyRisk::Low; +}; + +class ServiceDependencyRiskMap { +public: + bool addEdge(const DependencyEdge& edge, std::string* error) { + if (!error) return false; + error->clear(); + if (edge.edgeId.empty()) return failWith(error, "edge_id_missing"); + if (edge.sourceService.empty()) return failWith(error, "source_service_missing"); + if (edge.targetService.empty()) return failWith(error, "target_service_missing"); + if (edges_.count(edge.edgeId) != 0) return failWith(error, "edge_duplicate"); + edges_[edge.edgeId] = edge; + order_.push_back(edge.edgeId); + return true; + } + + bool updateRisk(const std::string& edgeId, DependencyRisk risk, std::string* error) { + if (!error) return false; + error->clear(); + auto it = edges_.find(edgeId); + if (it == edges_.end()) return failWith(error, "edge_missing"); + it->second.risk = risk; + return true; + } + + int highRiskInboundCount(const std::string& targetService) const { + int count = 0; + for (const auto& id : order_) { + const auto& edge = edges_.at(id); + if (edge.targetService == targetService && edge.risk == DependencyRisk::High) ++count; + } + return count; + } + + int highRiskOutboundCount(const std::string& sourceService) const { + int count = 0; + for (const auto& id : order_) { + const auto& edge = edges_.at(id); + if (edge.sourceService == sourceService && edge.risk == DependencyRisk::High) ++count; + } + return count; + } + + std::vector byService(const std::string& serviceId) const { + std::vector out; + for (const auto& id : order_) { + const auto& edge = edges_.at(id); + if (serviceId.empty() || edge.sourceService == serviceId || edge.targetService == serviceId) { + out.push_back(edge); + } + } + return out; + } + +private: + std::map edges_; + std::vector order_; +}; diff --git a/editor/tests/step610_test.cpp b/editor/tests/step610_test.cpp new file mode 100644 index 0000000..a44c57e --- /dev/null +++ b/editor/tests/step610_test.cpp @@ -0,0 +1,154 @@ +// Step 610: Service Dependency Risk Map (12 tests) + +#include "ServiceDependencyRiskMap.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; } else {} + +static DependencyEdge edge(const std::string& id, + const std::string& src, + const std::string& dst, + DependencyRisk risk = DependencyRisk::Low) { + return {id, src, dst, risk}; +} + +void test_add_edge_success() { + TEST(add_edge_success); + ServiceDependencyRiskMap map; + std::string error; + CHECK(map.addEdge(edge("e1", "svc-a", "svc-b"), &error), "add should succeed"); + CHECK(map.byService("svc-a").size() == 1, "service count mismatch"); + PASS(); +} + +void test_add_edge_rejects_missing_edge_id() { + TEST(add_edge_rejects_missing_edge_id); + ServiceDependencyRiskMap map; + std::string error; + CHECK(!map.addEdge(edge("", "svc-a", "svc-b"), &error), "add should fail"); + CHECK(error == "edge_id_missing", "wrong error"); + PASS(); +} + +void test_add_edge_rejects_missing_source() { + TEST(add_edge_rejects_missing_source); + ServiceDependencyRiskMap map; + std::string error; + CHECK(!map.addEdge(edge("e1", "", "svc-b"), &error), "add should fail"); + CHECK(error == "source_service_missing", "wrong error"); + PASS(); +} + +void test_add_edge_rejects_missing_target() { + TEST(add_edge_rejects_missing_target); + ServiceDependencyRiskMap map; + std::string error; + CHECK(!map.addEdge(edge("e1", "svc-a", ""), &error), "add should fail"); + CHECK(error == "target_service_missing", "wrong error"); + PASS(); +} + +void test_add_edge_rejects_duplicate() { + TEST(add_edge_rejects_duplicate); + ServiceDependencyRiskMap map; + std::string error; + CHECK(map.addEdge(edge("e1", "svc-a", "svc-b"), &error), "first add failed"); + CHECK(!map.addEdge(edge("e1", "svc-a", "svc-c"), &error), "duplicate should fail"); + CHECK(error == "edge_duplicate", "wrong error"); + PASS(); +} + +void test_update_risk_success() { + TEST(update_risk_success); + ServiceDependencyRiskMap map; + std::string error; + CHECK(map.addEdge(edge("e1", "svc-a", "svc-b"), &error), "add failed"); + CHECK(map.updateRisk("e1", DependencyRisk::High, &error), "update should succeed"); + CHECK(map.highRiskOutboundCount("svc-a") == 1, "outbound count mismatch"); + PASS(); +} + +void test_update_risk_rejects_missing_edge() { + TEST(update_risk_rejects_missing_edge); + ServiceDependencyRiskMap map; + std::string error; + CHECK(!map.updateRisk("missing", DependencyRisk::High, &error), "update should fail"); + CHECK(error == "edge_missing", "wrong error"); + PASS(); +} + +void test_high_risk_inbound_count() { + TEST(high_risk_inbound_count); + ServiceDependencyRiskMap map; + std::string error; + CHECK(map.addEdge(edge("e1", "svc-a", "svc-b", DependencyRisk::High), &error), "add e1 failed"); + CHECK(map.addEdge(edge("e2", "svc-c", "svc-b", DependencyRisk::Medium), &error), "add e2 failed"); + CHECK(map.addEdge(edge("e3", "svc-d", "svc-b", DependencyRisk::High), &error), "add e3 failed"); + CHECK(map.highRiskInboundCount("svc-b") == 2, "inbound high-risk count mismatch"); + PASS(); +} + +void test_high_risk_outbound_count() { + TEST(high_risk_outbound_count); + ServiceDependencyRiskMap map; + std::string error; + CHECK(map.addEdge(edge("e1", "svc-a", "svc-b", DependencyRisk::High), &error), "add e1 failed"); + CHECK(map.addEdge(edge("e2", "svc-a", "svc-c", DependencyRisk::Low), &error), "add e2 failed"); + CHECK(map.addEdge(edge("e3", "svc-a", "svc-d", DependencyRisk::High), &error), "add e3 failed"); + CHECK(map.highRiskOutboundCount("svc-a") == 2, "outbound high-risk count mismatch"); + PASS(); +} + +void test_by_service_filters_source_or_target() { + TEST(by_service_filters_source_or_target); + ServiceDependencyRiskMap map; + std::string error; + CHECK(map.addEdge(edge("e1", "svc-a", "svc-b"), &error), "add e1 failed"); + CHECK(map.addEdge(edge("e2", "svc-c", "svc-a"), &error), "add e2 failed"); + CHECK(map.addEdge(edge("e3", "svc-x", "svc-y"), &error), "add e3 failed"); + CHECK(map.byService("svc-a").size() == 2, "service filter mismatch"); + PASS(); +} + +void test_by_service_empty_returns_all() { + TEST(by_service_empty_returns_all); + ServiceDependencyRiskMap map; + std::string error; + CHECK(map.addEdge(edge("e1", "svc-a", "svc-b"), &error), "add e1 failed"); + CHECK(map.addEdge(edge("e2", "svc-c", "svc-d"), &error), "add e2 failed"); + CHECK(map.byService("").size() == 2, "empty filter should return all"); + PASS(); +} + +void test_counts_zero_for_unknown_service() { + TEST(counts_zero_for_unknown_service); + ServiceDependencyRiskMap map; + CHECK(map.highRiskInboundCount("missing") == 0, "inbound unknown should be zero"); + CHECK(map.highRiskOutboundCount("missing") == 0, "outbound unknown should be zero"); + PASS(); +} + +int main() { + std::cout << "Step 610: Service Dependency Risk Map\n"; + + test_add_edge_success(); // 1 + test_add_edge_rejects_missing_edge_id(); // 2 + test_add_edge_rejects_missing_source(); // 3 + test_add_edge_rejects_missing_target(); // 4 + test_add_edge_rejects_duplicate(); // 5 + test_update_risk_success(); // 6 + test_update_risk_rejects_missing_edge(); // 7 + test_high_risk_inbound_count(); // 8 + test_high_risk_outbound_count(); // 9 + test_by_service_filters_source_or_target(); // 10 + test_by_service_empty_returns_all(); // 11 + test_counts_zero_for_unknown_service(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index 655258e..fa8ad6c 100644 --- a/progress.md +++ b/progress.md @@ -11890,3 +11890,33 @@ and active-window listing. - `editor/src/ChangeFreezeCalendar.h` within header-size limit (`67` <= `600`) - `editor/tests/step609_test.cpp` within test-file size guidance (`149` lines) - Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md` + +### Step 610: Service Dependency Risk Map +**Status:** PASS (12/12 tests) + +Implements service dependency edge tracking with risk updates and high-risk +inbound/outbound rollups. + +**Files added:** +- `editor/src/ServiceDependencyRiskMap.h` - dependency risk module: + - dependency edge registration with validation/duplicate guards + - per-edge risk updates and missing-edge guards + - high-risk inbound/outbound counts and service filtering +- `editor/tests/step610_test.cpp` - 12 tests covering: + - edge add success/failure behavior + - risk update success/failure behavior + - high-risk rollup and service filter behavior + +**Files modified:** +- `editor/CMakeLists.txt` - `step610_test` target + +**Verification run:** +- `cmake -S editor -B editor/build-native` - PASS +- `cmake --build editor/build-native --target step610_test step609_test` - PASS +- `./editor/build-native/step610_test` - PASS (12/12) +- `./editor/build-native/step609_test` - PASS (12/12) regression coverage + +**Architecture gate check:** +- `editor/src/ServiceDependencyRiskMap.h` within header-size limit (`78` <= `600`) +- `editor/tests/step610_test.cpp` within test-file size guidance (`154` lines) +- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`