@massu/core 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/hooks/cost-tracker.js +23 -35
- package/dist/hooks/post-edit-context.js +2 -2
- package/dist/hooks/post-tool-use.js +43 -58
- package/dist/hooks/pre-compact.js +23 -38
- package/dist/hooks/pre-delete-check.js +18 -31
- package/dist/hooks/quality-event.js +23 -35
- package/dist/hooks/session-end.js +62 -78
- package/dist/hooks/session-start.js +33 -42
- package/dist/hooks/user-prompt.js +23 -38
- package/package.json +8 -14
- package/src/adr-generator.ts +9 -2
- package/src/analytics.ts +9 -3
- package/src/audit-trail.ts +10 -3
- package/src/cloud-sync.ts +14 -18
- package/src/commands/init.ts +1 -5
- package/src/cost-tracker.ts +11 -6
- package/src/dependency-scorer.ts +9 -2
- package/src/docs-tools.ts +13 -10
- package/src/hooks/post-edit-context.ts +3 -3
- package/src/hooks/session-end.ts +3 -3
- package/src/hooks/session-start.ts +2 -2
- package/src/memory-db.ts +1351 -23
- package/src/memory-tools.ts +14 -15
- package/src/observability-tools.ts +13 -2
- package/src/prompt-analyzer.ts +9 -2
- package/src/regression-detector.ts +9 -3
- package/src/security-scorer.ts +9 -2
- package/src/sentinel-db.ts +43 -88
- package/src/sentinel-tools.ts +8 -11
- package/src/server.ts +1 -2
- package/src/team-knowledge.ts +9 -2
- package/src/tools.ts +771 -35
- package/src/validate-features-runner.ts +0 -1
- package/src/validation-engine.ts +9 -2
- package/dist/cli.js +0 -7890
- package/dist/server.js +0 -7008
- package/src/__tests__/adr-generator.test.ts +0 -260
- package/src/__tests__/analytics.test.ts +0 -282
- package/src/__tests__/audit-trail.test.ts +0 -382
- package/src/__tests__/backfill-sessions.test.ts +0 -690
- package/src/__tests__/cli.test.ts +0 -290
- package/src/__tests__/cloud-sync.test.ts +0 -261
- package/src/__tests__/config-sections.test.ts +0 -359
- package/src/__tests__/config.test.ts +0 -732
- package/src/__tests__/cost-tracker.test.ts +0 -348
- package/src/__tests__/db.test.ts +0 -177
- package/src/__tests__/dependency-scorer.test.ts +0 -325
- package/src/__tests__/docs-integration.test.ts +0 -178
- package/src/__tests__/docs-tools.test.ts +0 -199
- package/src/__tests__/domains.test.ts +0 -236
- package/src/__tests__/hooks.test.ts +0 -221
- package/src/__tests__/import-resolver.test.ts +0 -95
- package/src/__tests__/integration/path-traversal.test.ts +0 -134
- package/src/__tests__/integration/pricing-consistency.test.ts +0 -88
- package/src/__tests__/integration/tool-registration.test.ts +0 -146
- package/src/__tests__/memory-db.test.ts +0 -404
- package/src/__tests__/memory-enhancements.test.ts +0 -316
- package/src/__tests__/memory-tools.test.ts +0 -199
- package/src/__tests__/middleware-tree.test.ts +0 -177
- package/src/__tests__/observability-tools.test.ts +0 -595
- package/src/__tests__/observability.test.ts +0 -437
- package/src/__tests__/observation-extractor.test.ts +0 -167
- package/src/__tests__/page-deps.test.ts +0 -60
- package/src/__tests__/prompt-analyzer.test.ts +0 -298
- package/src/__tests__/regression-detector.test.ts +0 -295
- package/src/__tests__/rules.test.ts +0 -87
- package/src/__tests__/schema-mapper.test.ts +0 -29
- package/src/__tests__/security-scorer.test.ts +0 -238
- package/src/__tests__/security-utils.test.ts +0 -175
- package/src/__tests__/sentinel-db.test.ts +0 -491
- package/src/__tests__/sentinel-scanner.test.ts +0 -750
- package/src/__tests__/sentinel-tools.test.ts +0 -324
- package/src/__tests__/sentinel-types.test.ts +0 -750
- package/src/__tests__/server.test.ts +0 -452
- package/src/__tests__/session-archiver.test.ts +0 -524
- package/src/__tests__/session-state-generator.test.ts +0 -900
- package/src/__tests__/team-knowledge.test.ts +0 -327
- package/src/__tests__/tools.test.ts +0 -340
- package/src/__tests__/transcript-parser.test.ts +0 -195
- package/src/__tests__/trpc-index.test.ts +0 -25
- package/src/__tests__/validate-features-runner.test.ts +0 -517
- package/src/__tests__/validation-engine.test.ts +0 -300
- package/src/core-tools.ts +0 -685
- package/src/memory-queries.ts +0 -804
- package/src/memory-schema.ts +0 -546
- package/src/tool-helpers.ts +0 -41
|
@@ -1,452 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2026 Massu. All rights reserved.
|
|
2
|
-
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Tests for server.ts JSON-RPC 2.0 protocol handling.
|
|
6
|
-
*
|
|
7
|
-
* server.ts is a standalone script with no exports. It processes JSON-RPC 2.0
|
|
8
|
-
* requests via stdin/stdout. Tests here validate:
|
|
9
|
-
* - The request routing logic (initialize, tools/list, tools/call, ping, etc.)
|
|
10
|
-
* - Error responses for unknown methods and malformed JSON
|
|
11
|
-
* - The shape of JSON-RPC 2.0 responses
|
|
12
|
-
*
|
|
13
|
-
* Because server.ts has no exports, we replicate its handleRequest logic
|
|
14
|
-
* inline (mirroring the switch statement exactly) and mock the same dependencies
|
|
15
|
-
* it imports. This validates the routing and response-shaping logic without
|
|
16
|
-
* requiring stdin/stdout wiring.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
20
|
-
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
// Mocks — must match what server.ts imports
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
|
|
25
|
-
vi.mock('../db.ts', () => ({
|
|
26
|
-
getCodeGraphDb: vi.fn(() => ({ close: vi.fn() })),
|
|
27
|
-
getDataDb: vi.fn(() => ({ close: vi.fn() })),
|
|
28
|
-
}));
|
|
29
|
-
|
|
30
|
-
vi.mock('../config.ts', () => ({
|
|
31
|
-
getConfig: vi.fn(() => ({
|
|
32
|
-
toolPrefix: 'massu',
|
|
33
|
-
framework: { type: 'typescript', router: 'trpc', orm: 'prisma' },
|
|
34
|
-
paths: { source: 'src', routers: 'src/server/api/routers', middleware: 'src/middleware.ts' },
|
|
35
|
-
domains: [],
|
|
36
|
-
})),
|
|
37
|
-
getProjectRoot: vi.fn(() => '/test/project'),
|
|
38
|
-
getResolvedPaths: vi.fn(() => ({
|
|
39
|
-
codegraphDbPath: '/test/codegraph.db',
|
|
40
|
-
dataDbPath: '/test/data.db',
|
|
41
|
-
})),
|
|
42
|
-
}));
|
|
43
|
-
|
|
44
|
-
vi.mock('../tools.ts', () => ({
|
|
45
|
-
getToolDefinitions: vi.fn(() => [
|
|
46
|
-
{
|
|
47
|
-
name: 'massu_sync',
|
|
48
|
-
description: 'Sync the project index',
|
|
49
|
-
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
name: 'massu_context',
|
|
53
|
-
description: 'Get context for a file',
|
|
54
|
-
inputSchema: { type: 'object', properties: { file: { type: 'string' } }, required: ['file'] },
|
|
55
|
-
},
|
|
56
|
-
]),
|
|
57
|
-
handleToolCall: vi.fn((_name: string, _args: Record<string, unknown>) => ({
|
|
58
|
-
content: [{ type: 'text', text: 'tool result' }],
|
|
59
|
-
})),
|
|
60
|
-
}));
|
|
61
|
-
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
|
-
// Re-implement handleRequest mirroring server.ts exactly
|
|
64
|
-
// ---------------------------------------------------------------------------
|
|
65
|
-
|
|
66
|
-
import { getToolDefinitions, handleToolCall } from '../tools.ts';
|
|
67
|
-
import { getCodeGraphDb, getDataDb } from '../db.ts';
|
|
68
|
-
|
|
69
|
-
interface JsonRpcRequest {
|
|
70
|
-
jsonrpc: '2.0';
|
|
71
|
-
id?: number | string;
|
|
72
|
-
method: string;
|
|
73
|
-
params?: Record<string, unknown>;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
interface JsonRpcResponse {
|
|
77
|
-
jsonrpc: '2.0';
|
|
78
|
-
id: number | string | null;
|
|
79
|
-
result?: unknown;
|
|
80
|
-
error?: { code: number; message: string; data?: unknown };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Mirrors server.ts getDb() / handleRequest() — kept in sync with the source.
|
|
84
|
-
let codegraphDb: ReturnType<typeof getCodeGraphDb> | null = null;
|
|
85
|
-
let dataDb: ReturnType<typeof getDataDb> | null = null;
|
|
86
|
-
|
|
87
|
-
function getDb() {
|
|
88
|
-
if (!codegraphDb) codegraphDb = getCodeGraphDb();
|
|
89
|
-
if (!dataDb) dataDb = getDataDb();
|
|
90
|
-
return { codegraphDb, dataDb };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function handleRequest(request: JsonRpcRequest): JsonRpcResponse {
|
|
94
|
-
const { method, params, id } = request;
|
|
95
|
-
|
|
96
|
-
switch (method) {
|
|
97
|
-
case 'initialize': {
|
|
98
|
-
return {
|
|
99
|
-
jsonrpc: '2.0',
|
|
100
|
-
id: id ?? null,
|
|
101
|
-
result: {
|
|
102
|
-
protocolVersion: '2024-11-05',
|
|
103
|
-
capabilities: { tools: {} },
|
|
104
|
-
serverInfo: { name: 'massu', version: '1.0.0' },
|
|
105
|
-
},
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
case 'notifications/initialized': {
|
|
110
|
-
return { jsonrpc: '2.0', id: id ?? null, result: {} };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
case 'tools/list': {
|
|
114
|
-
const tools = getToolDefinitions();
|
|
115
|
-
return { jsonrpc: '2.0', id: id ?? null, result: { tools } };
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
case 'tools/call': {
|
|
119
|
-
const toolName = (params as { name: string })?.name;
|
|
120
|
-
const toolArgs = (params as { arguments?: Record<string, unknown> })?.arguments ?? {};
|
|
121
|
-
const { codegraphDb: cgDb, dataDb: lDb } = getDb();
|
|
122
|
-
const result = handleToolCall(toolName, toolArgs, lDb as never, cgDb as never);
|
|
123
|
-
return { jsonrpc: '2.0', id: id ?? null, result };
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
case 'ping': {
|
|
127
|
-
return { jsonrpc: '2.0', id: id ?? null, result: {} };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
default: {
|
|
131
|
-
return {
|
|
132
|
-
jsonrpc: '2.0',
|
|
133
|
-
id: id ?? null,
|
|
134
|
-
error: { code: -32601, message: `Method not found: ${method}` },
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// ---------------------------------------------------------------------------
|
|
141
|
-
// Helper: simulate the newline-delimited JSON-RPC parse + dispatch loop
|
|
142
|
-
// that lives in server.ts's stdin 'data' handler.
|
|
143
|
-
// ---------------------------------------------------------------------------
|
|
144
|
-
|
|
145
|
-
function simulateStdinLine(line: string): JsonRpcResponse | null {
|
|
146
|
-
const trimmed = line.trim();
|
|
147
|
-
if (!trimmed) return null;
|
|
148
|
-
const request = JSON.parse(trimmed) as JsonRpcRequest;
|
|
149
|
-
return handleRequest(request);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function simulateMalformedLine(line: string): JsonRpcResponse {
|
|
153
|
-
try {
|
|
154
|
-
JSON.parse(line);
|
|
155
|
-
throw new Error('Expected parse failure');
|
|
156
|
-
} catch (error) {
|
|
157
|
-
return {
|
|
158
|
-
jsonrpc: '2.0',
|
|
159
|
-
id: null,
|
|
160
|
-
error: {
|
|
161
|
-
code: -32700,
|
|
162
|
-
message: `Parse error: ${error instanceof Error ? error.message : String(error)}`,
|
|
163
|
-
},
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// ---------------------------------------------------------------------------
|
|
169
|
-
// Tests
|
|
170
|
-
// ---------------------------------------------------------------------------
|
|
171
|
-
|
|
172
|
-
describe('Server JSON-RPC 2.0 — response structure', () => {
|
|
173
|
-
it('all successful responses include jsonrpc: "2.0"', () => {
|
|
174
|
-
const methods = ['initialize', 'tools/list', 'ping', 'notifications/initialized'] as const;
|
|
175
|
-
for (const method of methods) {
|
|
176
|
-
const resp = handleRequest({ jsonrpc: '2.0', id: 1, method });
|
|
177
|
-
expect(resp.jsonrpc).toBe('2.0');
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it('response id mirrors request id (number)', () => {
|
|
182
|
-
const resp = handleRequest({ jsonrpc: '2.0', id: 42, method: 'ping' });
|
|
183
|
-
expect(resp.id).toBe(42);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it('response id mirrors request id (string)', () => {
|
|
187
|
-
const resp = handleRequest({ jsonrpc: '2.0', id: 'req-abc', method: 'ping' });
|
|
188
|
-
expect(resp.id).toBe('req-abc');
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it('response id is null when request has no id', () => {
|
|
192
|
-
const resp = handleRequest({ jsonrpc: '2.0', method: 'ping' });
|
|
193
|
-
expect(resp.id).toBeNull();
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
describe('Server JSON-RPC 2.0 — initialize', () => {
|
|
198
|
-
it('returns protocolVersion 2024-11-05', () => {
|
|
199
|
-
const resp = handleRequest({ jsonrpc: '2.0', id: 1, method: 'initialize' });
|
|
200
|
-
expect(resp.error).toBeUndefined();
|
|
201
|
-
const result = resp.result as Record<string, unknown>;
|
|
202
|
-
expect(result.protocolVersion).toBe('2024-11-05');
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it('returns capabilities.tools object', () => {
|
|
206
|
-
const resp = handleRequest({ jsonrpc: '2.0', id: 1, method: 'initialize' });
|
|
207
|
-
const result = resp.result as Record<string, unknown>;
|
|
208
|
-
expect((result.capabilities as Record<string, unknown>).tools).toBeDefined();
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it('returns serverInfo with name massu and version 1.0.0', () => {
|
|
212
|
-
const resp = handleRequest({ jsonrpc: '2.0', id: 1, method: 'initialize' });
|
|
213
|
-
const result = resp.result as { serverInfo: { name: string; version: string } };
|
|
214
|
-
expect(result.serverInfo.name).toBe('massu');
|
|
215
|
-
expect(result.serverInfo.version).toBe('1.0.0');
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it('works with string id', () => {
|
|
219
|
-
const resp = handleRequest({ jsonrpc: '2.0', id: 'init-1', method: 'initialize' });
|
|
220
|
-
expect(resp.id).toBe('init-1');
|
|
221
|
-
expect((resp.result as Record<string, unknown>).protocolVersion).toBe('2024-11-05');
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
describe('Server JSON-RPC 2.0 — notifications/initialized', () => {
|
|
226
|
-
it('returns empty result object', () => {
|
|
227
|
-
const resp = handleRequest({ jsonrpc: '2.0', id: 2, method: 'notifications/initialized' });
|
|
228
|
-
expect(resp.error).toBeUndefined();
|
|
229
|
-
expect(resp.result).toEqual({});
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
it('returns null id when no id provided (notification pattern)', () => {
|
|
233
|
-
const resp = handleRequest({ jsonrpc: '2.0', method: 'notifications/initialized' });
|
|
234
|
-
expect(resp.id).toBeNull();
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
describe('Server JSON-RPC 2.0 — tools/list', () => {
|
|
239
|
-
it('calls getToolDefinitions and returns result.tools array', () => {
|
|
240
|
-
const resp = handleRequest({ jsonrpc: '2.0', id: 3, method: 'tools/list' });
|
|
241
|
-
expect(resp.error).toBeUndefined();
|
|
242
|
-
const result = resp.result as { tools: unknown[] };
|
|
243
|
-
expect(Array.isArray(result.tools)).toBe(true);
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it('returns mocked tool definitions', () => {
|
|
247
|
-
const resp = handleRequest({ jsonrpc: '2.0', id: 3, method: 'tools/list' });
|
|
248
|
-
const result = resp.result as { tools: Array<{ name: string }> };
|
|
249
|
-
const names = result.tools.map((t) => t.name);
|
|
250
|
-
expect(names).toContain('massu_sync');
|
|
251
|
-
expect(names).toContain('massu_context');
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
it('each tool definition has name, description, inputSchema', () => {
|
|
255
|
-
const resp = handleRequest({ jsonrpc: '2.0', id: 3, method: 'tools/list' });
|
|
256
|
-
const result = resp.result as { tools: Array<{ name: string; description: string; inputSchema: object }> };
|
|
257
|
-
for (const tool of result.tools) {
|
|
258
|
-
expect(tool.name).toBeTruthy();
|
|
259
|
-
expect(tool.description).toBeTruthy();
|
|
260
|
-
expect(typeof tool.inputSchema).toBe('object');
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
it('calls getToolDefinitions exactly once per request', () => {
|
|
265
|
-
const spy = vi.mocked(getToolDefinitions);
|
|
266
|
-
spy.mockClear();
|
|
267
|
-
|
|
268
|
-
handleRequest({ jsonrpc: '2.0', id: 4, method: 'tools/list' });
|
|
269
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
270
|
-
});
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
describe('Server JSON-RPC 2.0 — tools/call', () => {
|
|
274
|
-
beforeEach(() => {
|
|
275
|
-
// Reset db state between tests
|
|
276
|
-
codegraphDb = null;
|
|
277
|
-
dataDb = null;
|
|
278
|
-
vi.mocked(handleToolCall).mockClear();
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
it('delegates to handleToolCall and returns its result', () => {
|
|
282
|
-
const resp = handleRequest({
|
|
283
|
-
jsonrpc: '2.0',
|
|
284
|
-
id: 5,
|
|
285
|
-
method: 'tools/call',
|
|
286
|
-
params: { name: 'massu_sync', arguments: {} },
|
|
287
|
-
});
|
|
288
|
-
expect(resp.error).toBeUndefined();
|
|
289
|
-
const result = resp.result as { content: Array<{ type: string; text: string }> };
|
|
290
|
-
expect(result.content[0].text).toBe('tool result');
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
it('passes tool name from params.name to handleToolCall', () => {
|
|
294
|
-
handleRequest({
|
|
295
|
-
jsonrpc: '2.0',
|
|
296
|
-
id: 5,
|
|
297
|
-
method: 'tools/call',
|
|
298
|
-
params: { name: 'massu_context', arguments: { file: 'src/index.ts' } },
|
|
299
|
-
});
|
|
300
|
-
expect(vi.mocked(handleToolCall)).toHaveBeenCalledWith(
|
|
301
|
-
'massu_context',
|
|
302
|
-
{ file: 'src/index.ts' },
|
|
303
|
-
expect.anything(),
|
|
304
|
-
expect.anything(),
|
|
305
|
-
);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
it('uses empty object for arguments when params.arguments is omitted', () => {
|
|
309
|
-
handleRequest({
|
|
310
|
-
jsonrpc: '2.0',
|
|
311
|
-
id: 6,
|
|
312
|
-
method: 'tools/call',
|
|
313
|
-
params: { name: 'massu_sync' },
|
|
314
|
-
});
|
|
315
|
-
expect(vi.mocked(handleToolCall)).toHaveBeenCalledWith(
|
|
316
|
-
'massu_sync',
|
|
317
|
-
{},
|
|
318
|
-
expect.anything(),
|
|
319
|
-
expect.anything(),
|
|
320
|
-
);
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
it('lazy-initializes databases on first tools/call', () => {
|
|
324
|
-
vi.mocked(getCodeGraphDb).mockClear();
|
|
325
|
-
vi.mocked(getDataDb).mockClear();
|
|
326
|
-
|
|
327
|
-
handleRequest({
|
|
328
|
-
jsonrpc: '2.0',
|
|
329
|
-
id: 7,
|
|
330
|
-
method: 'tools/call',
|
|
331
|
-
params: { name: 'massu_sync', arguments: {} },
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
expect(vi.mocked(getCodeGraphDb)).toHaveBeenCalledTimes(1);
|
|
335
|
-
expect(vi.mocked(getDataDb)).toHaveBeenCalledTimes(1);
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
it('reuses existing db connections on subsequent tools/call', () => {
|
|
339
|
-
vi.mocked(getCodeGraphDb).mockClear();
|
|
340
|
-
vi.mocked(getDataDb).mockClear();
|
|
341
|
-
|
|
342
|
-
// First call initializes dbs
|
|
343
|
-
handleRequest({ jsonrpc: '2.0', id: 8, method: 'tools/call', params: { name: 'massu_sync' } });
|
|
344
|
-
// Second call should reuse
|
|
345
|
-
handleRequest({ jsonrpc: '2.0', id: 9, method: 'tools/call', params: { name: 'massu_sync' } });
|
|
346
|
-
|
|
347
|
-
expect(vi.mocked(getCodeGraphDb)).toHaveBeenCalledTimes(1);
|
|
348
|
-
expect(vi.mocked(getDataDb)).toHaveBeenCalledTimes(1);
|
|
349
|
-
});
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
describe('Server JSON-RPC 2.0 — ping', () => {
|
|
353
|
-
it('returns empty result', () => {
|
|
354
|
-
const resp = handleRequest({ jsonrpc: '2.0', id: 10, method: 'ping' });
|
|
355
|
-
expect(resp.error).toBeUndefined();
|
|
356
|
-
expect(resp.result).toEqual({});
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
it('returns id matching request', () => {
|
|
360
|
-
const resp = handleRequest({ jsonrpc: '2.0', id: 99, method: 'ping' });
|
|
361
|
-
expect(resp.id).toBe(99);
|
|
362
|
-
});
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
describe('Server JSON-RPC 2.0 — unknown method', () => {
|
|
366
|
-
it('returns error code -32601 for unknown method', () => {
|
|
367
|
-
const resp = handleRequest({ jsonrpc: '2.0', id: 11, method: 'nonexistent/method' });
|
|
368
|
-
expect(resp.result).toBeUndefined();
|
|
369
|
-
expect(resp.error).toBeDefined();
|
|
370
|
-
expect(resp.error!.code).toBe(-32601);
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
it('error message contains the unknown method name', () => {
|
|
374
|
-
const resp = handleRequest({ jsonrpc: '2.0', id: 11, method: 'some/unknown' });
|
|
375
|
-
expect(resp.error!.message).toContain('some/unknown');
|
|
376
|
-
expect(resp.error!.message).toContain('Method not found');
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
it('preserves id in error response', () => {
|
|
380
|
-
const resp = handleRequest({ jsonrpc: '2.0', id: 'err-id', method: 'bad/method' });
|
|
381
|
-
expect(resp.id).toBe('err-id');
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
it('various unknown methods all get -32601', () => {
|
|
385
|
-
const unknowns = ['foo', 'bar/baz', 'tools/execute', 'rpc.discover'];
|
|
386
|
-
for (const method of unknowns) {
|
|
387
|
-
const resp = handleRequest({ jsonrpc: '2.0', id: 1, method });
|
|
388
|
-
expect(resp.error!.code).toBe(-32601);
|
|
389
|
-
}
|
|
390
|
-
});
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
describe('Server JSON-RPC 2.0 — stdin line parsing', () => {
|
|
394
|
-
it('parses and dispatches a valid newline-terminated JSON line', () => {
|
|
395
|
-
const line = JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'ping' }) + '\n';
|
|
396
|
-
const resp = simulateStdinLine(line);
|
|
397
|
-
expect(resp).not.toBeNull();
|
|
398
|
-
expect(resp!.result).toEqual({});
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
it('ignores empty or whitespace-only lines', () => {
|
|
402
|
-
expect(simulateStdinLine('')).toBeNull();
|
|
403
|
-
expect(simulateStdinLine(' ')).toBeNull();
|
|
404
|
-
expect(simulateStdinLine('\t\n')).toBeNull();
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
it('returns parse error response for malformed JSON', () => {
|
|
408
|
-
const resp = simulateMalformedLine('{not valid json}');
|
|
409
|
-
expect(resp.jsonrpc).toBe('2.0');
|
|
410
|
-
expect(resp.id).toBeNull();
|
|
411
|
-
expect(resp.error).toBeDefined();
|
|
412
|
-
expect(resp.error!.code).toBe(-32700);
|
|
413
|
-
expect(resp.error!.message).toContain('Parse error');
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
it('returns parse error for truncated JSON', () => {
|
|
417
|
-
const resp = simulateMalformedLine('{"jsonrpc":"2.0","id":1,"method":');
|
|
418
|
-
expect(resp.error!.code).toBe(-32700);
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
it('returns parse error for empty braces with trailing garbage', () => {
|
|
422
|
-
const resp = simulateMalformedLine('{} garbage after');
|
|
423
|
-
// JSON.parse("{} garbage after") throws a SyntaxError
|
|
424
|
-
expect(resp.error!.code).toBe(-32700);
|
|
425
|
-
});
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
describe('Server JSON-RPC 2.0 — notification suppression (no id)', () => {
|
|
429
|
-
/**
|
|
430
|
-
* Per the JSON-RPC 2.0 spec and server.ts implementation, when request.id
|
|
431
|
-
* is undefined, the server must not send a response. We validate the
|
|
432
|
-
* condition the server checks: request.id !== undefined.
|
|
433
|
-
*/
|
|
434
|
-
it('notifications/initialized has no id — server would not write response', () => {
|
|
435
|
-
const request = { jsonrpc: '2.0' as const, method: 'notifications/initialized' };
|
|
436
|
-
// The server checks: if (request.id !== undefined) → write response
|
|
437
|
-
expect(request.id).toBeUndefined();
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
it('requests with explicit id get a response', () => {
|
|
441
|
-
const request = { jsonrpc: '2.0' as const, id: 0, method: 'ping' };
|
|
442
|
-
// id === 0 is defined, so response IS written
|
|
443
|
-
expect(request.id).toBeDefined();
|
|
444
|
-
const resp = handleRequest(request);
|
|
445
|
-
expect(resp.id).toBe(0);
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
it('id: 0 is a valid id (not falsy-excluded)', () => {
|
|
449
|
-
const resp = handleRequest({ jsonrpc: '2.0', id: 0, method: 'ping' });
|
|
450
|
-
expect(resp.id).toBe(0);
|
|
451
|
-
});
|
|
452
|
-
});
|