317 lines
14 KiB
C++
317 lines
14 KiB
C++
// Step 422: Phase 18a Integration Tests (8 tests)
|
|
|
|
#include "MCPServer.h"
|
|
#include "MCPServerConfig.h"
|
|
#include "MCPWorkflowPrompts.h"
|
|
#include "HeadlessEditorState.h"
|
|
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
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 rpcResult(const json& result, int id = 1) {
|
|
return {{"jsonrpc", "2.0"}, {"id", id}, {"result", result}};
|
|
}
|
|
|
|
static json parseToolResult(const json& toolCallResponse) {
|
|
if (!toolCallResponse.contains("result")) return json::object();
|
|
auto content = toolCallResponse["result"].value("content", json::array());
|
|
if (!content.is_array() || content.empty()) return json::object();
|
|
std::string text = content[0].value("text", "{}");
|
|
try {
|
|
return json::parse(text);
|
|
} catch (...) {
|
|
return json::object();
|
|
}
|
|
}
|
|
|
|
static json callTool(MCPServer& server, const std::string& name,
|
|
const json& args = json::object()) {
|
|
auto resp = server.handleRequest({
|
|
{"jsonrpc", "2.0"},
|
|
{"id", 1},
|
|
{"method", "tools/call"},
|
|
{"params", {{"name", name}, {"arguments", args}}}
|
|
});
|
|
return parseToolResult(resp);
|
|
}
|
|
|
|
static std::string makeTempDir(const std::string& name) {
|
|
auto dir = std::filesystem::temp_directory_path() / ("whetstone_step422_" + name);
|
|
std::filesystem::create_directories(dir);
|
|
return dir.string();
|
|
}
|
|
|
|
static HeadlessEditorState makeStateWithReviewItem(const std::string& status = WI_REVIEW) {
|
|
HeadlessEditorState state;
|
|
state.setAgentRole("sess", AgentRole::Refactor);
|
|
state.workflow = WorkflowState("phase18a-integration");
|
|
|
|
WorkItem wi;
|
|
wi.id = "w-review-1";
|
|
wi.nodeId = "node1";
|
|
wi.nodeName = "processOrder";
|
|
wi.nodeType = "Function";
|
|
wi.bufferId = "orders.py";
|
|
wi.workerType = "llm";
|
|
wi.priority = "high";
|
|
wi.reviewRequired = true;
|
|
wi.status = status;
|
|
wi.result.generatedCode = "def process_order(x):\n return x\n";
|
|
wi.result.confidence = 0.85f;
|
|
wi.result.reasoning = "Generated from workflow blocker.";
|
|
state.workflow->queue.enqueue(wi);
|
|
return state;
|
|
}
|
|
|
|
void test_mcp_config_build_for_phase18a() {
|
|
TEST(mcp_config_build_for_phase18a);
|
|
json cfg = MCPServerConfig::buildMcpConfig("/bin/whetstone_mcp", "/repo", "cpp");
|
|
CHECK(cfg.contains("mcpServers"), "missing mcpServers");
|
|
CHECK(cfg["mcpServers"].contains("whetstone"), "missing whetstone server");
|
|
auto args = cfg["mcpServers"]["whetstone"]["args"].dump();
|
|
CHECK(args.find("--workspace") != std::string::npos, "missing workspace arg");
|
|
CHECK(args.find("--language") != std::string::npos, "missing language arg");
|
|
PASS();
|
|
}
|
|
|
|
void test_phase18a_tools_and_counts_present() {
|
|
TEST(phase18a_tools_and_counts_present);
|
|
MCPServer server;
|
|
CHECK((int)server.getTools().size() >= 75, "expected 75+ tools");
|
|
bool onboard = false, reviewQ = false, approve = false, progress = false;
|
|
for (const auto& t : server.getTools()) {
|
|
if (t.name == "whetstone_onboard_workspace") onboard = true;
|
|
if (t.name == "whetstone_get_review_queue") reviewQ = true;
|
|
if (t.name == "whetstone_approve_item") approve = true;
|
|
if (t.name == "whetstone_get_progress") progress = true;
|
|
}
|
|
CHECK(onboard && reviewQ && approve && progress, "missing required phase tools");
|
|
PASS();
|
|
}
|
|
|
|
void test_phase18a_workflow_prompt_templates_available() {
|
|
TEST(phase18a_workflow_prompt_templates_available);
|
|
auto templates = MCPWorkflowPrompts::templates();
|
|
bool architect = false, modernize = false, reviewPending = false;
|
|
for (const auto& t : templates) {
|
|
if (t.name == "architect_project") architect = true;
|
|
if (t.name == "modernize_module") modernize = true;
|
|
if (t.name == "review_pending") reviewPending = true;
|
|
}
|
|
CHECK(architect && modernize && reviewPending, "missing workflow prompts");
|
|
PASS();
|
|
}
|
|
|
|
void test_onboard_then_workflow_tool_sequence_mocked() {
|
|
TEST(onboard_then_workflow_tool_sequence_mocked);
|
|
MCPServer server;
|
|
std::vector<std::string> calls;
|
|
server.setRpcCallback([&](const json& req) {
|
|
std::string method = req.value("method", "");
|
|
calls.push_back(method);
|
|
if (method == "indexWorkspace") return rpcResult({{"root", "/repo"}, {"fileCount", 1}});
|
|
if (method == "workspaceList")
|
|
return rpcResult({{"files", json::array({{{"path", "src/main.cpp"}, {"isDir", false}}})}});
|
|
if (method == "openFile") return rpcResult(req["params"]);
|
|
if (method == "inferAnnotations") return rpcResult({{"count", 2}});
|
|
if (method == "saveAnnotatedAST") return rpcResult({{"success", true}});
|
|
if (method == "fileCreate") return rpcResult({{"created", true}});
|
|
if (method == "createSkeleton") return rpcResult({{"bufferId", "skel_demo"}});
|
|
if (method == "addSkeletonNode") return rpcResult({{"nodeId", "fn1"}});
|
|
if (method == "createWorkflow") return rpcResult({{"itemCount", 1}});
|
|
return rpcResult(json::object());
|
|
});
|
|
|
|
auto onboard = callTool(server, "whetstone_onboard_workspace");
|
|
auto skel = callTool(server, "whetstone_create_skeleton",
|
|
{{"name", "demo"}, {"language", "cpp"}});
|
|
auto add = callTool(server, "whetstone_add_skeleton_node",
|
|
{{"nodeType", "function"}, {"name", "process"},
|
|
{"automatability", "llm"}, {"contextWidth", "file"}});
|
|
auto wf = callTool(server, "whetstone_create_workflow", {{"projectName", "demo"}});;
|
|
|
|
CHECK(onboard.value("success", false), "onboard failed");
|
|
CHECK(skel.contains("bufferId"), "create skeleton failed");
|
|
CHECK(add.contains("nodeId"), "add node failed");
|
|
CHECK(wf.value("itemCount", 0) >= 1, "create workflow failed");
|
|
CHECK(!calls.empty() && calls.front() == "indexWorkspace", "expected onboarding start");
|
|
PASS();
|
|
}
|
|
|
|
void test_orchestrate_submit_review_progress_sequence_mocked() {
|
|
TEST(orchestrate_submit_review_progress_sequence_mocked);
|
|
MCPServer server;
|
|
bool reviewPending = false;
|
|
bool complete = false;
|
|
server.setRpcCallback([&](const json& req) {
|
|
std::string method = req.value("method", "");
|
|
if (method == "orchestrateRunDeterministic")
|
|
return rpcResult({{"blocked", 1}, {"processed", 0}});
|
|
if (method == "getBlockers")
|
|
return rpcResult({{"external", json::array({{{"itemId", "w1"}}})}});
|
|
if (method == "submitExternalResult") {
|
|
reviewPending = true;
|
|
return rpcResult({{"status", "submitted"}, {"reviewRequired", true}});
|
|
}
|
|
if (method == "getReviewQueue")
|
|
return rpcResult({{"items", reviewPending
|
|
? json::array({{{"itemId", "w1"}, {"status", "review"}}})
|
|
: json::array()}, {"count", reviewPending ? 1 : 0}});
|
|
if (method == "approveReviewItem") {
|
|
reviewPending = false;
|
|
complete = true;
|
|
return rpcResult({{"success", true}});
|
|
}
|
|
if (method == "getProgress")
|
|
return rpcResult({{"completionPercent", complete ? 100.0 : 60.0}});
|
|
return rpcResult(json::object());
|
|
});
|
|
|
|
callTool(server, "whetstone_orchestrate_run_deterministic");
|
|
auto blockers = callTool(server, "whetstone_get_blockers");
|
|
CHECK(blockers["external"].is_array() && !blockers["external"].empty(),
|
|
"expected blocker");
|
|
auto submit = callTool(server, "whetstone_submit_result", {
|
|
{"itemId", "w1"},
|
|
{"result", {{"generatedCode", "x=1"}, {"confidence", 0.9}, {"reasoning", "done"}}}
|
|
});
|
|
CHECK(submit.value("reviewRequired", false), "expected reviewRequired after submit");
|
|
auto q1 = callTool(server, "whetstone_get_review_queue");
|
|
CHECK(q1.value("count", 0) == 1, "expected one review item");
|
|
auto approve = callTool(server, "whetstone_approve_item", {{"itemId", "w1"}});
|
|
CHECK(approve.value("success", false), "approve failed");
|
|
auto progress = callTool(server, "whetstone_get_progress");
|
|
CHECK(progress.value("completionPercent", 0.0) >= 100.0, "expected completion");
|
|
PASS();
|
|
}
|
|
|
|
void test_full_phase18a_protocol_mocked_end_to_end() {
|
|
TEST(full_phase18a_protocol_mocked_end_to_end);
|
|
MCPServer server;
|
|
std::vector<std::string> methods;
|
|
bool reviewPending = false;
|
|
bool complete = false;
|
|
server.setRpcCallback([&](const json& req) {
|
|
std::string method = req.value("method", "");
|
|
methods.push_back(method);
|
|
if (method == "indexWorkspace") return rpcResult({{"root", "/repo"}, {"fileCount", 2}});
|
|
if (method == "workspaceList")
|
|
return rpcResult({{"files", json::array({
|
|
{{"path", "src/main.cpp"}, {"isDir", false}},
|
|
{{"path", "src/util.py"}, {"isDir", false}}
|
|
})}});
|
|
if (method == "openFile") return rpcResult(req["params"]);
|
|
if (method == "inferAnnotations") return rpcResult({{"count", 2}});
|
|
if (method == "saveAnnotatedAST") return rpcResult({{"success", true}});
|
|
if (method == "fileCreate") return rpcResult({{"created", true}});
|
|
if (method == "createSkeleton") return rpcResult({{"bufferId", "skel_demo"}});
|
|
if (method == "addSkeletonNode") return rpcResult({{"nodeId", "n1"}});
|
|
if (method == "createWorkflow") return rpcResult({{"itemCount", 1}});
|
|
if (method == "orchestrateRunDeterministic") return rpcResult({{"blocked", 1}});
|
|
if (method == "getBlockers")
|
|
return rpcResult({{"external", json::array({{{"itemId", "w1"}}})}});
|
|
if (method == "submitExternalResult") {
|
|
reviewPending = true;
|
|
return rpcResult({{"status", "submitted"}, {"reviewRequired", true}});
|
|
}
|
|
if (method == "getReviewQueue")
|
|
return rpcResult({{"items", reviewPending
|
|
? json::array({{{"itemId", "w1"}}})
|
|
: json::array()}, {"count", reviewPending ? 1 : 0}});
|
|
if (method == "approveReviewItem") {
|
|
reviewPending = false;
|
|
complete = true;
|
|
return rpcResult({{"success", true}});
|
|
}
|
|
if (method == "getProgress")
|
|
return rpcResult({{"completionPercent", complete ? 100.0 : 40.0}});
|
|
return rpcResult(json::object());
|
|
});
|
|
|
|
callTool(server, "whetstone_onboard_workspace");
|
|
callTool(server, "whetstone_create_skeleton", {{"name", "demo"}, {"language", "cpp"}});
|
|
callTool(server, "whetstone_add_skeleton_node",
|
|
{{"nodeType", "function"}, {"name", "process"}, {"automatability", "llm"}});
|
|
callTool(server, "whetstone_create_workflow", {{"projectName", "demo"}});
|
|
callTool(server, "whetstone_orchestrate_run_deterministic");
|
|
callTool(server, "whetstone_get_blockers");
|
|
callTool(server, "whetstone_submit_result",
|
|
{{"itemId", "w1"}, {"result", {{"generatedCode", "x=1"}, {"confidence", 0.91}}}});
|
|
callTool(server, "whetstone_get_review_queue");
|
|
callTool(server, "whetstone_approve_item", {{"itemId", "w1"}});
|
|
auto progress = callTool(server, "whetstone_get_progress");
|
|
|
|
CHECK(progress.value("completionPercent", 0.0) >= 100.0, "expected complete progress");
|
|
CHECK(!methods.empty() && methods.front() == "indexWorkspace", "flow should start with onboarding");
|
|
CHECK(std::find(methods.begin(), methods.end(), "approveReviewItem") != methods.end(),
|
|
"missing review approval phase");
|
|
PASS();
|
|
}
|
|
|
|
void test_headless_review_completion_via_mcp() {
|
|
TEST(headless_review_completion_via_mcp);
|
|
auto state = makeStateWithReviewItem();
|
|
MCPServer server;
|
|
server.setRpcCallback([&](const json& req) {
|
|
return state.processAgentRequest(req, "sess");
|
|
});
|
|
|
|
auto queue = callTool(server, "whetstone_get_review_queue");
|
|
CHECK(queue.value("count", 0) == 1, "expected one review item");
|
|
auto approve = callTool(server, "whetstone_approve_item",
|
|
{{"itemId", "w-review-1"}, {"feedback", "ok"}});
|
|
CHECK(approve.value("success", false), "approval failed");
|
|
auto queueAfter = callTool(server, "whetstone_get_review_queue");
|
|
CHECK(queueAfter.value("count", -1) == 0, "queue should be empty after approve");
|
|
PASS();
|
|
}
|
|
|
|
void test_headless_onboard_workspace_smoke() {
|
|
TEST(headless_onboard_workspace_smoke);
|
|
auto root = std::filesystem::path(makeTempDir("onboard"));
|
|
std::filesystem::create_directories(root / "src");
|
|
std::ofstream(root / "src" / "main.cpp") << "int main(){return 0;}\n";
|
|
|
|
HeadlessEditorState state;
|
|
state.setAgentRole("sess", AgentRole::Refactor);
|
|
state.workspaceRoot = root.string();
|
|
|
|
MCPServer server;
|
|
server.setRpcCallback([&](const json& req) {
|
|
return state.processAgentRequest(req, "sess");
|
|
});
|
|
|
|
auto res = callTool(server, "whetstone_onboard_workspace",
|
|
{{"root", root.string()}, {"maxFiles", 1}});
|
|
CHECK(res.value("success", false), "expected onboarding success");
|
|
CHECK(std::filesystem::exists(root / ".whetstone" / "README.md"),
|
|
"expected .whetstone bootstrap");
|
|
PASS();
|
|
}
|
|
|
|
int main() {
|
|
std::cout << "Step 422: Phase 18a Integration Tests\n";
|
|
|
|
test_mcp_config_build_for_phase18a(); // 1
|
|
test_phase18a_tools_and_counts_present(); // 2
|
|
test_phase18a_workflow_prompt_templates_available(); // 3
|
|
test_onboard_then_workflow_tool_sequence_mocked(); // 4
|
|
test_orchestrate_submit_review_progress_sequence_mocked();// 5
|
|
test_full_phase18a_protocol_mocked_end_to_end(); // 6
|
|
test_headless_review_completion_via_mcp(); // 7
|
|
test_headless_onboard_workspace_smoke(); // 8
|
|
|
|
std::cout << "\nResults: " << passed << "/" << (passed + failed)
|
|
<< " passed\n";
|
|
return failed == 0 ? 0 : 1;
|
|
}
|