Files
whetstone_DSL/editor/tests/step1918_test.cpp
Bill 4d47e894c1 Sprint 278: LSP Proxy Hardening (steps 1918-1922)
- LSPMessageValidator: JSON-RPC 2.0 shape validation (method, id type, params type)
- LSPCapabilityNegotiator: per-source capability tracking; shouldRoute gates on declared caps
- LSPRequestThrottler: per-URI/method debounce; blocks rapid didChange floods
- LSPErrorRecovery: per-source health tracking; stale-cache fallback when crashed
- Sprint278IntegrationSummary: poly-parse (C++ + Haskell) end-to-end integration

26/26 tests passing (5+5+5+5+6). All new headers well under 600 lines.
LoRA recorded: sprint278-2026-03-01 (9 calls, 6437 tokens).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 20:28:31 -07:00

98 lines
3.4 KiB
C++

// Step 1918: LSPMessageValidator
// Validates JSON-RPC 2.0 message shape before routing.
//
// t1: valid request passes
// t2: missing jsonrpc field rejected
// t3: wrong jsonrpc version rejected
// t4: non-string method rejected; missing method rejected
// t5: bad id type rejected; bad params type rejected; notification/request detection
#include "LSPMessageValidator.h"
#include <iostream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
namespace ws = whetstone;
static int p=0,f=0;
#define T(n) { std::cout<<" "<<#n<<"... "; }
#define P() { std::cout<<"PASS\n"; ++p; }
#define F(m) { std::cout<<"FAIL: "<<m<<"\n"; ++f; }
#define C(c,m) if(!(c)){F(m);return;}
void t1(){
T(valid_request_passes);
ws::LSPMessageValidator v;
json msg = {{"jsonrpc","2.0"},{"id",1},{"method","textDocument/definition"},
{"params",{{"textDocument",{{"uri","file:///a.go"}}}}}};
auto r = v.validate(msg);
C(r.valid, "valid");
C(r.errorCode == 0, "no error code");
C(r.errorMessage.empty(),"no error message");
// notification (no id) also valid
json notif = {{"jsonrpc","2.0"},{"method","textDocument/didOpen"},{"params",json::object()}};
C(v.validate(notif).valid, "notification valid");
P();
}
void t2(){
T(missing_jsonrpc_rejected);
ws::LSPMessageValidator v;
json msg = {{"method","textDocument/hover"}};
auto r = v.validate(msg);
C(!r.valid, "invalid");
C(r.errorCode == -32600, "code -32600");
P();
}
void t3(){
T(wrong_jsonrpc_version_rejected);
ws::LSPMessageValidator v;
json msg = {{"jsonrpc","1.0"},{"method","textDocument/hover"}};
auto r = v.validate(msg);
C(!r.valid, "invalid");
C(r.errorCode == -32600, "code -32600");
P();
}
void t4(){
T(non_string_method_and_missing_method_rejected);
ws::LSPMessageValidator v;
json bad1 = {{"jsonrpc","2.0"},{"method",42}};
C(!v.validate(bad1).valid, "number method invalid");
json bad2 = {{"jsonrpc","2.0"},{"id",1}};
C(!v.validate(bad2).valid, "missing method invalid");
C(v.validate(bad2).errorCode == -32600, "code -32600");
P();
}
void t5(){
T(bad_id_bad_params_and_detection);
ws::LSPMessageValidator v;
// id as object: invalid
json badId = {{"jsonrpc","2.0"},{"method","x"},{"id",json::object()}};
C(!v.validate(badId).valid, "object id invalid");
// id as null: valid
json nullId = {{"jsonrpc","2.0"},{"method","x"},{"id",nullptr}};
C(v.validate(nullId).valid, "null id valid");
// params as string: invalid
json badParams = {{"jsonrpc","2.0"},{"method","x"},{"params","oops"}};
C(!v.validate(badParams).valid, "string params invalid");
C(v.validate(badParams).errorCode == -32602, "params error code -32602");
// isNotification / isRequest
json notif = {{"jsonrpc","2.0"},{"method","didOpen"}};
json req = {{"jsonrpc","2.0"},{"method","definition"},{"id",2}};
C( ws::LSPMessageValidator::isNotification(notif), "notif is notification");
C(!ws::LSPMessageValidator::isNotification(req), "req is not notification");
C( ws::LSPMessageValidator::isRequest(req), "req is request");
C(!ws::LSPMessageValidator::isRequest(notif), "notif is not request");
P();
}
int main(){
std::cout << "Step 1918: LSPMessageValidator\n";
t1(); t2(); t3(); t4(); t5();
std::cout << "\n" << p << "/" << (p+f) << " passed\n";
return f > 0 ? 1 : 0;
}