238 lines
8.5 KiB
C++
238 lines
8.5 KiB
C++
// Step 616: whetstone_queue_ready MCP Tool (12 tests)
|
|
|
|
#include "MCPServer.h"
|
|
|
|
#include <iostream>
|
|
#include <string>
|
|
|
|
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 json callTool(MCPServer& mcp, const std::string& name, const json& args) {
|
|
json req = {
|
|
{"jsonrpc", "2.0"},
|
|
{"id", 1},
|
|
{"method", "tools/call"},
|
|
{"params", {{"name", name}, {"arguments", args}}}
|
|
};
|
|
json resp = mcp.handleRequest(req);
|
|
std::string text = resp["result"]["content"][0].value("text", "{}");
|
|
return json::parse(text);
|
|
}
|
|
|
|
static std::string clearSpec() {
|
|
return
|
|
"## Goals\n"
|
|
"- Ship queue simulation\n"
|
|
"\n"
|
|
"## Constraints\n"
|
|
"- Keep APIs deterministic\n"
|
|
"\n"
|
|
"## Dependencies\n"
|
|
"- cmake\n"
|
|
"\n"
|
|
"## Acceptance Criteria\n"
|
|
"- queue has coverage checks\n";
|
|
}
|
|
|
|
static std::string riskySpec() {
|
|
return
|
|
"## Goals\n"
|
|
"- maybe improve workflow maybe maybe\n"
|
|
"\n"
|
|
"## Constraints\n"
|
|
"- Do not use dynamic allocation in hot path\n"
|
|
"- Use dynamic allocation in hot path for warmup\n"
|
|
"\n"
|
|
"## Dependencies\n"
|
|
"- maybe add another dependency\n"
|
|
"\n"
|
|
"## Acceptance Criteria\n"
|
|
"- maybe pass tests\n";
|
|
}
|
|
|
|
static json intakeThenGenerate(MCPServer& mcp, const std::string& markdown) {
|
|
json intake = callTool(mcp, "whetstone_architect_intake", {{"markdown", markdown}});
|
|
return {
|
|
{"intake", intake},
|
|
{"generated", callTool(mcp, "whetstone_generate_taskitems", {
|
|
{"normalizedRequirements", intake["normalizedRequirements"]},
|
|
{"conflicts", intake["conflicts"]}
|
|
})}
|
|
};
|
|
}
|
|
|
|
void test_tool_registered_accessor() {
|
|
TEST(tool_registered_accessor);
|
|
MCPServer mcp;
|
|
bool found = false;
|
|
for (const auto& tool : mcp.getTools()) {
|
|
if (tool.name == "whetstone_queue_ready") found = true;
|
|
}
|
|
CHECK(found, "tool should be registered");
|
|
PASS();
|
|
}
|
|
|
|
void test_tool_schema_requires_tasks() {
|
|
TEST(tool_schema_requires_tasks);
|
|
MCPServer mcp;
|
|
bool required = false;
|
|
for (const auto& tool : mcp.getTools()) {
|
|
if (tool.name != "whetstone_queue_ready") continue;
|
|
for (const auto& req : tool.inputSchema["required"]) {
|
|
if (req == "tasks") required = true;
|
|
}
|
|
}
|
|
CHECK(required, "tasks should be required");
|
|
PASS();
|
|
}
|
|
|
|
void test_success_with_generated_tasks_and_requirements() {
|
|
TEST(success_with_generated_tasks_and_requirements);
|
|
MCPServer mcp;
|
|
json flow = intakeThenGenerate(mcp, clearSpec());
|
|
json out = callTool(mcp, "whetstone_queue_ready", {
|
|
{"tasks", flow["generated"]["tasks"]},
|
|
{"normalizedRequirements", flow["intake"]["normalizedRequirements"]}
|
|
});
|
|
CHECK(out.value("success", false), "success should be true");
|
|
PASS();
|
|
}
|
|
|
|
void test_ready_count_positive_for_clear_spec() {
|
|
TEST(ready_count_positive_for_clear_spec);
|
|
MCPServer mcp;
|
|
json flow = intakeThenGenerate(mcp, clearSpec());
|
|
json out = callTool(mcp, "whetstone_queue_ready", {
|
|
{"tasks", flow["generated"]["tasks"]},
|
|
{"normalizedRequirements", flow["intake"]["normalizedRequirements"]}
|
|
});
|
|
CHECK(out.value("readyCount", 0) >= 1, "readyCount should be >= 1");
|
|
PASS();
|
|
}
|
|
|
|
void test_queue_json_contains_coverage_checks() {
|
|
TEST(queue_json_contains_coverage_checks);
|
|
MCPServer mcp;
|
|
json flow = intakeThenGenerate(mcp, clearSpec());
|
|
json out = callTool(mcp, "whetstone_queue_ready", {
|
|
{"tasks", flow["generated"]["tasks"]},
|
|
{"normalizedRequirements", flow["intake"]["normalizedRequirements"]}
|
|
});
|
|
CHECK(out["queueJson"].is_array() && !out["queueJson"].empty(), "queueJson should be populated");
|
|
CHECK(out["queueJson"][0].value("hasCoverage", false), "first queue item should have coverage");
|
|
CHECK(out["queueJson"][0]["checks"].is_array() && !out["queueJson"][0]["checks"].empty(),
|
|
"first queue item should include checks");
|
|
PASS();
|
|
}
|
|
|
|
void test_risky_spec_surfaces_escalations() {
|
|
TEST(risky_spec_surfaces_escalations);
|
|
MCPServer mcp;
|
|
json flow = intakeThenGenerate(mcp, riskySpec());
|
|
json out = callTool(mcp, "whetstone_queue_ready", {
|
|
{"tasks", flow["generated"]["tasks"]},
|
|
{"normalizedRequirements", flow["intake"]["normalizedRequirements"]}
|
|
});
|
|
CHECK(out.value("escalateCount", 0) >= 1, "escalateCount should be >= 1");
|
|
PASS();
|
|
}
|
|
|
|
void test_risky_spec_not_ready_due_to_blockers() {
|
|
TEST(risky_spec_not_ready_due_to_blockers);
|
|
MCPServer mcp;
|
|
json flow = intakeThenGenerate(mcp, riskySpec());
|
|
json out = callTool(mcp, "whetstone_queue_ready", {
|
|
{"tasks", flow["generated"]["tasks"]},
|
|
{"normalizedRequirements", flow["intake"]["normalizedRequirements"]}
|
|
});
|
|
bool hasEscalationBlocker = false;
|
|
for (const auto& blocker : out["blockers"]) {
|
|
if (blocker == "escalations_present") hasEscalationBlocker = true;
|
|
}
|
|
CHECK(!out.value("ready", true), "ready should be false");
|
|
CHECK(hasEscalationBlocker, "escalation blocker expected");
|
|
PASS();
|
|
}
|
|
|
|
void test_missing_tasks_errors() {
|
|
TEST(missing_tasks_errors);
|
|
MCPServer mcp;
|
|
json out = callTool(mcp, "whetstone_queue_ready", json::object());
|
|
CHECK(!out.value("success", true), "success should be false");
|
|
CHECK(out.value("error", "") == "tasks_missing", "wrong error");
|
|
PASS();
|
|
}
|
|
|
|
void test_tasks_must_be_array() {
|
|
TEST(tasks_must_be_array);
|
|
MCPServer mcp;
|
|
json out = callTool(mcp, "whetstone_queue_ready", {{"tasks", "bad"}});
|
|
CHECK(!out.value("success", true), "success should be false");
|
|
CHECK(out.value("error", "") == "tasks_not_array", "wrong error");
|
|
PASS();
|
|
}
|
|
|
|
void test_rejects_task_entries_missing_fields() {
|
|
TEST(rejects_task_entries_missing_fields);
|
|
MCPServer mcp;
|
|
json badTasks = json::array({{{"taskId", "task-1"}}});
|
|
json out = callTool(mcp, "whetstone_queue_ready", {{"tasks", badTasks}});
|
|
CHECK(!out.value("success", true), "success should be false");
|
|
CHECK(out.value("error", "") == "task_entry_missing_fields", "wrong error");
|
|
PASS();
|
|
}
|
|
|
|
void test_without_requirements_reports_acceptance_blocker() {
|
|
TEST(without_requirements_reports_acceptance_blocker);
|
|
MCPServer mcp;
|
|
json flow = intakeThenGenerate(mcp, clearSpec());
|
|
json out = callTool(mcp, "whetstone_queue_ready", {
|
|
{"tasks", flow["generated"]["tasks"]}
|
|
});
|
|
bool hasAcceptanceBlocker = false;
|
|
for (const auto& blocker : out["blockers"]) {
|
|
if (blocker == "acceptance_requirements_empty") hasAcceptanceBlocker = true;
|
|
}
|
|
CHECK(out.value("success", false), "success should be true");
|
|
CHECK(hasAcceptanceBlocker, "acceptance blocker expected");
|
|
CHECK(!out.value("ready", true), "ready should be false");
|
|
PASS();
|
|
}
|
|
|
|
void test_invalid_normalized_requirements_type_errors() {
|
|
TEST(invalid_normalized_requirements_type_errors);
|
|
MCPServer mcp;
|
|
json flow = intakeThenGenerate(mcp, clearSpec());
|
|
json out = callTool(mcp, "whetstone_queue_ready", {
|
|
{"tasks", flow["generated"]["tasks"]},
|
|
{"normalizedRequirements", "bad"}
|
|
});
|
|
CHECK(!out.value("success", true), "success should be false");
|
|
CHECK(out.value("error", "") == "normalized_requirements_not_array", "wrong error");
|
|
PASS();
|
|
}
|
|
|
|
int main() {
|
|
std::cout << "Step 616: whetstone_queue_ready MCP Tool\n";
|
|
|
|
test_tool_registered_accessor(); // 1
|
|
test_tool_schema_requires_tasks(); // 2
|
|
test_success_with_generated_tasks_and_requirements(); // 3
|
|
test_ready_count_positive_for_clear_spec(); // 4
|
|
test_queue_json_contains_coverage_checks(); // 5
|
|
test_risky_spec_surfaces_escalations(); // 6
|
|
test_risky_spec_not_ready_due_to_blockers(); // 7
|
|
test_missing_tasks_errors(); // 8
|
|
test_tasks_must_be_array(); // 9
|
|
test_rejects_task_entries_missing_fields(); // 10
|
|
test_without_requirements_reports_acceptance_blocker(); // 11
|
|
test_invalid_normalized_requirements_type_errors(); // 12
|
|
|
|
std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n";
|
|
return failed == 0 ? 0 : 1;
|
|
}
|