@deepsql/mcp 0.14.0 → 0.17.0

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.
@@ -1,323 +0,0 @@
1
- "use strict";
2
-
3
- const test = require("node:test");
4
- const assert = require("node:assert/strict");
5
-
6
- // Mock api/client.request and the connection resolver before requiring the
7
- // command module so its captured references point at our stubs.
8
- const apiClientPath = require.resolve("../api/client");
9
- const realApiClient = require("../api/client");
10
- const connectionsPath = require.resolve("./_connections");
11
- const realConnections = require("./_connections");
12
- const sessionPath = require.resolve("./_session");
13
- const realSession = require("./_session");
14
-
15
- function withMocks({ request, resolveConnectionId, resolveSession }, fn) {
16
- delete require.cache[require.resolve("./index-recommendations")];
17
- if (request) {
18
- require.cache[apiClientPath] = {
19
- ...require.cache[apiClientPath],
20
- exports: { ...realApiClient, request },
21
- };
22
- }
23
- if (resolveConnectionId) {
24
- require.cache[connectionsPath] = {
25
- ...require.cache[connectionsPath],
26
- exports: { ...realConnections, resolveConnectionId },
27
- };
28
- }
29
- if (resolveSession) {
30
- require.cache[sessionPath] = {
31
- ...require.cache[sessionPath],
32
- exports: { ...realSession, resolveSession },
33
- };
34
- }
35
- try {
36
- const mod = require("./index-recommendations");
37
- return fn(mod);
38
- } finally {
39
- require.cache[apiClientPath].exports = realApiClient;
40
- require.cache[connectionsPath].exports = realConnections;
41
- require.cache[sessionPath].exports = realSession;
42
- delete require.cache[require.resolve("./index-recommendations")];
43
- }
44
- }
45
-
46
- function captured() {
47
- const out = { stdout: "", stderr: "" };
48
- const io = {
49
- stdout: { write: (s) => (out.stdout += s) },
50
- stderr: { write: (s) => (out.stderr += s) },
51
- };
52
- return { out, io };
53
- }
54
-
55
- const fakeSession = () => ({ baseUrl: "http://x", token: "t", defaultConnection: null });
56
- const fakeResolveConnId = async () => "conn-abc";
57
-
58
- // ─── `top` ─────────────────────────────────────────────────────────────────
59
-
60
- test("top prints workload-weighted summary lines with net-benefit + evidence", async () => {
61
- await withMocks(
62
- {
63
- resolveSession: fakeSession,
64
- resolveConnectionId: fakeResolveConnId,
65
- request: async (baseUrl, path, opts) => {
66
- assert.equal(path, "/index-recommendations/conn-abc/top");
67
- assert.deepEqual(opts.query, { limit: 5 });
68
- return [
69
- {
70
- id: "rec-1",
71
- tableName: "orders",
72
- columnNames: "customer_id,status",
73
- indexName: "idx_orders_customer_id_status",
74
- kind: "CREATE_INDEX",
75
- priority: "HIGH",
76
- occurrenceCount: 4,
77
- netBenefitMs: 4823000,
78
- workloadScoreMs: 4823000,
79
- writeCostScore: 142000,
80
- evidenceCount: 3,
81
- reason: "Workload-weighted composite from 3 contributing queries.",
82
- hypopgBeforeCost: 1000,
83
- hypopgAfterCost: 250,
84
- hypopgReductionPct: 75,
85
- topEvidence: [
86
- { calls: 4500, meanExecTimeMs: 850, totalExecTimeMs: 3825000, role: "WHERE_EQ" },
87
- ],
88
- },
89
- ];
90
- },
91
- },
92
- async (mod) => {
93
- const { out, io } = captured();
94
- await mod.run({ positional: ["top"], connection: "mylocalpg" }, io);
95
- assert.match(out.stdout, /\[CREATE\] orders\(customer_id,status\)/);
96
- assert.match(out.stdout, /seen 4×/);
97
- assert.match(out.stdout, /net=1\.3h saved/);
98
- assert.match(out.stdout, /HypoPG cost: 1000 → 250 \(−75\.0%\)/);
99
- assert.match(out.stdout, /id: rec-1/);
100
- assert.match(out.stdout, /top evidence: 4500 calls/);
101
- }
102
- );
103
- });
104
-
105
- test("top renders an empty-state message when no recommendations exist", async () => {
106
- await withMocks(
107
- {
108
- resolveSession: fakeSession,
109
- resolveConnectionId: fakeResolveConnId,
110
- request: async () => [],
111
- },
112
- async (mod) => {
113
- const { out, io } = captured();
114
- await mod.run({ positional: ["top"], connection: "mylocalpg" }, io);
115
- assert.match(out.stdout, /No pending index recommendations/);
116
- assert.match(out.stdout, /index-recommendations refresh/);
117
- }
118
- );
119
- });
120
-
121
- test("top respects --limit and clamps to the [1, 50] range", async () => {
122
- let captured_limit = null;
123
- await withMocks(
124
- {
125
- resolveSession: fakeSession,
126
- resolveConnectionId: fakeResolveConnId,
127
- request: async (_, __, opts) => {
128
- captured_limit = opts.query.limit;
129
- return [];
130
- },
131
- },
132
- async (mod) => {
133
- const { io } = captured();
134
- await mod.run({ positional: ["top"], connection: "c", limit: 999 }, io);
135
- assert.equal(captured_limit, 50);
136
- await mod.run({ positional: ["top"], connection: "c", limit: 0 }, io);
137
- assert.equal(captured_limit, 1);
138
- await mod.run({ positional: ["top"], connection: "c", limit: 10 }, io);
139
- assert.equal(captured_limit, 10);
140
- }
141
- );
142
- });
143
-
144
- test("top --json passes through the raw payload", async () => {
145
- await withMocks(
146
- {
147
- resolveSession: fakeSession,
148
- resolveConnectionId: fakeResolveConnId,
149
- request: async () => [{ id: "rec-1", tableName: "orders" }],
150
- },
151
- async (mod) => {
152
- const { out, io } = captured();
153
- await mod.run({ positional: ["top"], connection: "c", json: true }, io);
154
- const parsed = JSON.parse(out.stdout);
155
- assert.equal(parsed[0].id, "rec-1");
156
- }
157
- );
158
- });
159
-
160
- // ─── `apply` safety guard ──────────────────────────────────────────────────
161
-
162
- test("apply with mode=apply but no --confirm refuses up-front (no API call)", async () => {
163
- let called = false;
164
- await withMocks(
165
- {
166
- resolveSession: fakeSession,
167
- request: async () => {
168
- called = true;
169
- return {};
170
- },
171
- },
172
- async (mod) => {
173
- const { io } = captured();
174
- await assert.rejects(
175
- () => mod.run({ positional: ["apply", "rec-1"], mode: "apply" }, io),
176
- /Re-run with --confirm/
177
- );
178
- assert.equal(called, false, "API should not be hit without --confirm");
179
- }
180
- );
181
- });
182
-
183
- test("apply DRY_RUN does not require --confirm and surfaces planner-cost delta", async () => {
184
- await withMocks(
185
- {
186
- resolveSession: fakeSession,
187
- request: async (_, path, opts) => {
188
- assert.equal(path, "/index-recommendations/rec-1/apply");
189
- assert.equal(opts.method, "POST");
190
- assert.deepEqual(opts.query, { mode: "DRY_RUN", confirm: false, concurrent: true });
191
- return {
192
- recommendationId: "rec-1",
193
- executedDdl: "CREATE INDEX idx_o_status ON orders (status);",
194
- mode: "DRY_RUN",
195
- status: "OK",
196
- beforeCost: 1000,
197
- afterCost: 250,
198
- costReductionPct: 75,
199
- samples: [
200
- { fingerprint: "abc123def456", beforeCost: 1000, afterCost: 250 },
201
- ],
202
- message: "DRY_RUN complete — planner cost −75.0% (1000 → 250)",
203
- };
204
- },
205
- },
206
- async (mod) => {
207
- const { out, io } = captured();
208
- await mod.run({ positional: ["apply", "rec-1"], mode: "dry-run" }, io);
209
- assert.match(out.stdout, /\[DRY_RUN\] OK/);
210
- assert.match(out.stdout, /planner cost: 1000 → 250 \(−75\.0%\)/);
211
- assert.match(out.stdout, /fp=abc123def456 cost 1000 → 250/);
212
- }
213
- );
214
- });
215
-
216
- test("apply --no-concurrent passes concurrent=false to the backend", async () => {
217
- let captured_query = null;
218
- await withMocks(
219
- {
220
- resolveSession: fakeSession,
221
- request: async (_, __, opts) => {
222
- captured_query = opts.query;
223
- return { mode: "APPLY", status: "OK", executedDdl: "CREATE INDEX …" };
224
- },
225
- },
226
- async (mod) => {
227
- const { io } = captured();
228
- await mod.run(
229
- {
230
- positional: ["apply", "rec-1"],
231
- mode: "apply",
232
- confirm: true,
233
- noConcurrent: true,
234
- },
235
- io
236
- );
237
- assert.deepEqual(captured_query, { mode: "APPLY", confirm: true, concurrent: false });
238
- }
239
- );
240
- });
241
-
242
- test("apply rejects unknown modes early", async () => {
243
- await withMocks(
244
- {
245
- resolveSession: fakeSession,
246
- request: async () => {
247
- throw new Error("API should not be called");
248
- },
249
- },
250
- async (mod) => {
251
- const { io } = captured();
252
- await assert.rejects(
253
- () => mod.run({ positional: ["apply", "rec-1"], mode: "bogus" }, io),
254
- /Unknown mode/
255
- );
256
- }
257
- );
258
- });
259
-
260
- // ─── refresh / dismiss ─────────────────────────────────────────────────────
261
-
262
- test("refresh POSTs to /generate and surfaces the count", async () => {
263
- await withMocks(
264
- {
265
- resolveSession: fakeSession,
266
- resolveConnectionId: fakeResolveConnId,
267
- request: async (_, path, opts) => {
268
- assert.equal(path, "/index-recommendations/generate/conn-abc");
269
- assert.equal(opts.method, "POST");
270
- return { success: true, count: 12, message: "Generated 12 index recommendations" };
271
- },
272
- },
273
- async (mod) => {
274
- const { out, io } = captured();
275
- await mod.run({ positional: ["refresh"], connection: "mylocalpg" }, io);
276
- assert.match(out.stdout, /Refresh complete: 12 candidate/);
277
- }
278
- );
279
- });
280
-
281
- test("dismiss issues PUT /{id}/dismiss", async () => {
282
- let called_path = null;
283
- let called_method = null;
284
- await withMocks(
285
- {
286
- resolveSession: fakeSession,
287
- request: async (_, path, opts) => {
288
- called_path = path;
289
- called_method = opts.method;
290
- return {};
291
- },
292
- },
293
- async (mod) => {
294
- const { out, io } = captured();
295
- await mod.run({ positional: ["dismiss", "rec-9"] }, io);
296
- assert.equal(called_path, "/index-recommendations/rec-9/dismiss");
297
- assert.equal(called_method, "PUT");
298
- assert.match(out.stdout, /Dismissed recommendation rec-9/);
299
- }
300
- );
301
- });
302
-
303
- // ─── usage errors ──────────────────────────────────────────────────────────
304
-
305
- test("run with no subcommand prints usage error", async () => {
306
- await withMocks({ resolveSession: fakeSession }, async (mod) => {
307
- const { io } = captured();
308
- await assert.rejects(
309
- () => mod.run({ positional: [] }, io),
310
- /Usage: deepsql index-recommendations/
311
- );
312
- });
313
- });
314
-
315
- test("run with unknown subcommand throws clearly", async () => {
316
- await withMocks({ resolveSession: fakeSession }, async (mod) => {
317
- const { io } = captured();
318
- await assert.rejects(
319
- () => mod.run({ positional: ["bogus"] }, io),
320
- /Unknown index-recommendations subcommand: bogus/
321
- );
322
- });
323
- });