Files
whetstone_DSL/editor/tests/step59_test.cpp

301 lines
11 KiB
C++
Raw Normal View History

// Step 59 TDD Test: WebSocket agent endpoint
//
// Tests the WebSocketAgentServer with MockWebSocketTransport:
// 1. Server starts and reports running
// 2. Agent connects and receives a session ID
// 3. Agent sends JSON-RPC `ping`, receives `pong` with session ID
// 4. Multiple agents get unique session IDs
// 5. Agent disconnect is tracked (session marked inactive)
// 6. Session event callbacks fire on connect/disconnect
// 7. Invalid JSON produces a parse-error response
// 8. Unknown method produces method-not-found error
// 9. Custom request handler delegation works
// 10. listSessions returns all active agents
#include <iostream>
#include <string>
#include <cassert>
#include <vector>
#include <memory>
#include <nlohmann/json.hpp>
#include "WebSocketServer.h"
using json = nlohmann::json;
static bool contains(const std::string& haystack, const std::string& needle) {
return haystack.find(needle) != std::string::npos;
}
int main() {
int passed = 0;
int failed = 0;
// --- Test 1: Server starts and reports running ---
{
auto transport = std::make_unique<MockWebSocketTransport>();
auto* tp = transport.get(); // keep raw ptr for test helpers
WebSocketAgentServer server(std::move(transport));
assert(!server.isRunning() && "Server should not be running before start");
bool ok = server.start(9090);
assert(ok && "Server should start successfully");
assert(server.isRunning() && "Server should be running after start");
assert(server.getPort() == 9090 && "Port should match");
server.stop();
assert(!server.isRunning() && "Server should stop");
std::cout << "Test 1 PASS: Server starts and stops correctly" << std::endl;
++passed;
}
// --- Test 2: Agent connects and gets session ID ---
{
auto transport = std::make_unique<MockWebSocketTransport>();
auto* tp = transport.get();
WebSocketAgentServer server(std::move(transport));
server.start(9090);
std::string sid = tp->simulateConnect();
assert(!sid.empty() && "Session ID should not be empty");
const AgentSession* session = server.getSession(sid);
assert(session != nullptr && "Session should exist after connect");
assert(session->connected && "Session should be marked connected");
assert(session->sessionId == sid && "Session ID should match");
assert(server.getActiveSessionCount() == 1 && "Should have 1 active session");
std::cout << "Test 2 PASS: Agent connects and gets session ID" << std::endl;
++passed;
}
// --- Test 3: Agent sends ping, receives pong with session ID ---
{
auto transport = std::make_unique<MockWebSocketTransport>();
auto* tp = transport.get();
WebSocketAgentServer server(std::move(transport));
server.start(9090);
std::string sid = tp->simulateConnect();
json pingRequest = {
{"jsonrpc", "2.0"},
{"id", 1},
{"method", "ping"}
};
tp->simulateMessage(sid, pingRequest.dump());
std::string response = tp->getLastSentMessage(sid);
assert(!response.empty() && "Should receive a response");
json resp = json::parse(response);
assert(resp.contains("result") && "Response should have result");
assert(resp["result"]["message"] == "pong" && "Result message should be pong");
assert(resp["result"]["sessionId"] == sid && "Result should include session ID");
assert(resp["id"] == 1 && "Response ID should match request");
std::cout << "Test 3 PASS: ping returns pong with session ID" << std::endl;
++passed;
}
// --- Test 4: Multiple agents get unique session IDs ---
{
auto transport = std::make_unique<MockWebSocketTransport>();
auto* tp = transport.get();
WebSocketAgentServer server(std::move(transport));
server.start(9090);
std::string sid1 = tp->simulateConnect();
std::string sid2 = tp->simulateConnect();
std::string sid3 = tp->simulateConnect();
assert(sid1 != sid2 && "Session IDs should be unique");
assert(sid2 != sid3 && "Session IDs should be unique");
assert(sid1 != sid3 && "Session IDs should be unique");
assert(server.getActiveSessionCount() == 3 && "Should have 3 active sessions");
std::cout << "Test 4 PASS: Multiple agents get unique session IDs" << std::endl;
++passed;
}
// --- Test 5: Agent disconnect is tracked ---
{
auto transport = std::make_unique<MockWebSocketTransport>();
auto* tp = transport.get();
WebSocketAgentServer server(std::move(transport));
server.start(9090);
std::string sid = tp->simulateConnect();
assert(server.getActiveSessionCount() == 1);
tp->simulateDisconnect(sid);
const AgentSession* session = server.getSession(sid);
assert(session != nullptr && "Session should still exist");
assert(!session->connected && "Session should be marked disconnected");
assert(server.getActiveSessionCount() == 0 && "No active sessions after disconnect");
std::cout << "Test 5 PASS: Agent disconnect tracked correctly" << std::endl;
++passed;
}
// --- Test 6: Session event callbacks fire ---
{
auto transport = std::make_unique<MockWebSocketTransport>();
auto* tp = transport.get();
WebSocketAgentServer server(std::move(transport));
std::vector<std::string> events;
server.setSessionEventCallback(
[&events](const AgentSession& s, const std::string& event) {
events.push_back(s.sessionId + ":" + event);
});
server.start(9090);
std::string sid = tp->simulateConnect();
assert(events.size() == 1 && "Should have 1 event after connect");
assert(contains(events[0], "connected") && "Event should be 'connected'");
tp->simulateDisconnect(sid);
assert(events.size() == 2 && "Should have 2 events after disconnect");
assert(contains(events[1], "disconnected") && "Event should be 'disconnected'");
std::cout << "Test 6 PASS: Session event callbacks fire correctly" << std::endl;
++passed;
}
// --- Test 7: Invalid JSON produces parse-error response ---
{
auto transport = std::make_unique<MockWebSocketTransport>();
auto* tp = transport.get();
WebSocketAgentServer server(std::move(transport));
server.start(9090);
std::string sid = tp->simulateConnect();
tp->simulateMessage(sid, "this is not json!!!");
std::string response = tp->getLastSentMessage(sid);
json resp = json::parse(response);
assert(resp.contains("error") && "Should have error for invalid JSON");
assert(resp["error"]["code"] == -32700 && "Error code should be -32700 (Parse error)");
std::cout << "Test 7 PASS: Invalid JSON returns parse error" << std::endl;
++passed;
}
// --- Test 8: Unknown method produces method-not-found error ---
{
auto transport = std::make_unique<MockWebSocketTransport>();
auto* tp = transport.get();
WebSocketAgentServer server(std::move(transport));
server.start(9090);
std::string sid = tp->simulateConnect();
json req = {
{"jsonrpc", "2.0"},
{"id", 42},
{"method", "nonExistentMethod"}
};
tp->simulateMessage(sid, req.dump());
std::string response = tp->getLastSentMessage(sid);
json resp = json::parse(response);
assert(resp.contains("error") && "Should have error for unknown method");
assert(resp["error"]["code"] == -32601 && "Error code should be -32601 (Method not found)");
assert(resp["id"] == 42 && "Response ID should match request");
std::cout << "Test 8 PASS: Unknown method returns method-not-found error" << std::endl;
++passed;
}
// --- Test 9: Custom request handler delegation ---
{
auto transport = std::make_unique<MockWebSocketTransport>();
auto* tp = transport.get();
WebSocketAgentServer server(std::move(transport));
// Register a custom handler that knows about "getAST"
server.setRequestHandler([](const json& request, const std::string&) -> json {
std::string method = request.at("method").get<std::string>();
json response;
response["jsonrpc"] = "2.0";
response["id"] = request.contains("id") ? request["id"] : json(nullptr);
if (method == "getAST") {
response["result"] = {{"concept", "Module"}, {"name", "TestModule"}};
} else {
response["error"] = {{"code", -32601}, {"message", "Method not found"}};
}
return response;
});
server.start(9090);
std::string sid = tp->simulateConnect();
json req = {
{"jsonrpc", "2.0"},
{"id", 10},
{"method", "getAST"}
};
tp->simulateMessage(sid, req.dump());
std::string response = tp->getLastSentMessage(sid);
json resp = json::parse(response);
assert(resp.contains("result") && "Delegated handler should return result");
assert(resp["result"]["concept"] == "Module" && "Should get Module from handler");
assert(resp["id"] == 10 && "Response ID should match");
std::cout << "Test 9 PASS: Custom request handler delegation works" << std::endl;
++passed;
}
// --- Test 10: listSessions returns all active agents ---
{
auto transport = std::make_unique<MockWebSocketTransport>();
auto* tp = transport.get();
WebSocketAgentServer server(std::move(transport));
server.start(9090);
std::string sid1 = tp->simulateConnect();
std::string sid2 = tp->simulateConnect();
// Set agent name on sid1
json nameReq = {
{"jsonrpc", "2.0"},
{"id", 1},
{"method", "setAgentName"},
{"params", {{"name", "CodeAssistant"}}}
};
tp->simulateMessage(sid1, nameReq.dump());
// Disconnect sid2
tp->simulateDisconnect(sid2);
// Ask for session list from sid1
json listReq = {
{"jsonrpc", "2.0"},
{"id", 2},
{"method", "listSessions"}
};
tp->simulateMessage(sid1, listReq.dump());
std::string response = tp->getLastSentMessage(sid1);
json resp = json::parse(response);
assert(resp.contains("result") && "Should have result");
assert(resp["result"].is_array() && "Result should be an array");
// Only sid1 should be active (sid2 disconnected)
assert(resp["result"].size() == 1 && "Should have 1 active session");
assert(resp["result"][0]["agentName"] == "CodeAssistant" &&
"Agent name should be set");
std::cout << "Test 10 PASS: listSessions returns active agents" << std::endl;
++passed;
}
// --- Summary ---
std::cout << "\n=== Step 59 Results: " << passed << " passed, "
<< failed << " failed ===" << std::endl;
return failed > 0 ? 1 : 0;
}