octocode-cli 1.5.1 → 1.5.3
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 +28 -9
- package/out/chunks/chunk-375ZNU3A.js +317 -0
- package/out/chunks/{chunk-V64AXUWB.js → chunk-AYILFPX4.js} +1 -1
- package/out/chunks/chunk-BRHVQB64.js +10 -0
- package/out/chunks/{command-help-specs-JZXVSLZ5.js → chunk-KWJUBVXJ.js} +1 -1
- package/out/chunks/chunk-OMXWZJBJ.js +100 -0
- package/out/chunks/command-help-specs-RVJK6V6M.js +8 -0
- package/out/chunks/commands-JFDCC4Q7.js +8 -0
- package/out/chunks/{help-P7TCOYAJ.js → help-H3WGUJO7.js} +1 -1
- package/out/chunks/main-help-BUTDD4IR.js +10 -0
- package/out/chunks/{spinner-URV2OX6O.js → spinner-DXD4JQIU.js} +1 -1
- package/out/chunks/tool-command-L6PVHUNS.js +8 -0
- package/out/octocode-cli.js +1 -1
- package/package.json +2 -2
- package/skills/octocode-brainstorming/scripts/tavily-search.mjs +1 -10
- package/skills/octocode-chrome-devtools/scripts/cdp-runner.mjs +0 -2
- package/skills/octocode-chrome-devtools/scripts/cdp-sandbox.mjs +1 -5
- package/skills/octocode-chrome-devtools/scripts/cdp-template.mjs +1 -10
- package/skills/octocode-chrome-devtools/scripts/open-browser.mjs +0 -3
- package/skills/octocode-chrome-devtools/scripts/sourcemap-resolver.mjs +1 -4
- package/skills/octocode-chrome-devtools/scripts/undercover.mjs +0 -2
- package/skills/octocode-engineer/SKILL.md +7 -33
- package/skills/octocode-engineer/references/cli-reference.md +5 -11
- package/skills/octocode-engineer/references/externals.md +0 -4
- package/skills/octocode-engineer/references/quality-indicators.md +10 -50
- package/skills/octocode-engineer/references/tool-workflows.md +10 -13
- package/skills/octocode-engineer/src/analysis/dependency-summary.test.ts +2 -2
- package/skills/octocode-engineer/src/ast/metrics.test.ts +2 -2
- package/skills/octocode-engineer/src/ast/search-main.ts +2 -15
- package/skills/octocode-engineer/src/ast/search.ts +1 -7
- package/skills/octocode-engineer/src/ast/tree-sitter.test.ts +4 -4
- package/skills/octocode-engineer/src/ast/tree-sitter.ts +2 -2
- package/skills/octocode-engineer/src/collectors/chains.ts +3 -10
- package/skills/octocode-engineer/src/collectors/prototype-pollution.ts +3 -3
- package/skills/octocode-engineer/src/collectors/security.ts +3 -3
- package/skills/octocode-engineer/src/common/ensure-deps.ts +3 -20
- package/skills/octocode-engineer/src/detectors/code-quality.ts +0 -1
- package/skills/octocode-engineer/src/index.test.ts +1 -1
- package/skills/octocode-engineer/src/index.ts +1 -11
- package/skills/octocode-engineer/src/pipeline/affected.ts +2 -7
- package/skills/octocode-engineer/src/pipeline/baseline.ts +2 -8
- package/skills/octocode-engineer/src/pipeline/cache.ts +2 -2
- package/skills/octocode-engineer/src/pipeline/config-loader.test.ts +1 -2
- package/skills/octocode-engineer/src/pipeline/config-loader.ts +0 -9
- package/skills/octocode-engineer/src/pipeline/create-options.ts +2 -15
- package/skills/octocode-engineer/src/pipeline/reporters.ts +2 -12
- package/skills/octocode-engineer/src/reporting/summary-md.test.ts +2 -2
- package/skills/octocode-engineer/src/run.ts +1 -7
- package/skills/octocode-engineer/src/types/constants.ts +0 -5
- package/skills/octocode-engineer/src/types/core.ts +0 -5
- package/skills/octocode-news/eslint.config.mjs +1 -6
- package/skills/octocode-news/src/rss-core.ts +2 -4
- package/skills/octocode-research/ecosystem.config.cjs +0 -31
- package/skills/octocode-research/package.json +1 -1
- package/skills/octocode-research/scripts/server.js +272 -230
- package/skills/octocode-research/src/__tests__/integration/circuitBreaker.test.ts +0 -24
- package/skills/octocode-research/src/__tests__/integration/routes.test.ts +0 -10
- package/skills/octocode-research/src/__tests__/integration/serverLifecycle.test.ts +5 -22
- package/skills/octocode-research/src/__tests__/integration/toolsRoutes.test.ts +0 -27
- package/skills/octocode-research/src/__tests__/unit/circuitBreaker.test.ts +0 -29
- package/skills/octocode-research/src/__tests__/unit/errorHandler.test.ts +1 -12
- package/skills/octocode-research/src/__tests__/unit/httpPreprocess.test.ts +1 -9
- package/skills/octocode-research/src/__tests__/unit/logger.test.ts +0 -7
- package/skills/octocode-research/src/__tests__/unit/queryParser.test.ts +1 -7
- package/skills/octocode-research/src/__tests__/unit/responseBuilder.test.ts +0 -11
- package/skills/octocode-research/src/__tests__/unit/retry.test.ts +1 -18
- package/skills/octocode-research/src/__tests__/unit/schemas.test.ts +2 -25
- package/skills/octocode-research/src/index.ts +0 -27
- package/skills/octocode-research/src/mcpCache.ts +3 -18
- package/skills/octocode-research/src/middleware/errorHandler.ts +1 -4
- package/skills/octocode-research/src/middleware/logger.ts +1 -6
- package/skills/octocode-research/src/middleware/queryParser.ts +3 -22
- package/skills/octocode-research/src/routes/github.ts +0 -11
- package/skills/octocode-research/src/routes/local.ts +0 -12
- package/skills/octocode-research/src/routes/lsp.ts +1 -10
- package/skills/octocode-research/src/routes/package.ts +0 -2
- package/skills/octocode-research/src/routes/prompts.ts +2 -32
- package/skills/octocode-research/src/routes/tools.ts +12 -177
- package/skills/octocode-research/src/server-init.ts +0 -32
- package/skills/octocode-research/src/server.ts +8 -37
- package/skills/octocode-research/src/types/errorGuards.ts +0 -15
- package/skills/octocode-research/src/types/guards.ts +6 -27
- package/skills/octocode-research/src/types/mcp.ts +1 -8
- package/skills/octocode-research/src/types/responses.ts +0 -15
- package/skills/octocode-research/src/utils/asyncTimeout.ts +1 -63
- package/skills/octocode-research/src/utils/circuitBreaker.ts +43 -166
- package/skills/octocode-research/src/utils/colors.ts +3 -17
- package/skills/octocode-research/src/utils/errorQueue.ts +6 -28
- package/skills/octocode-research/src/utils/logger.ts +11 -93
- package/skills/octocode-research/src/utils/resilience.ts +22 -77
- package/skills/octocode-research/src/utils/responseBuilder.ts +26 -78
- package/skills/octocode-research/src/utils/responseFactory.ts +0 -7
- package/skills/octocode-research/src/utils/responseParser.ts +11 -59
- package/skills/octocode-research/src/utils/retry.ts +12 -75
- package/skills/octocode-research/src/utils/routeFactory.ts +10 -61
- package/skills/octocode-research/src/utils/url.ts +0 -5
- package/skills/octocode-research/src/validation/httpPreprocess.ts +15 -61
- package/skills/octocode-research/src/validation/schemas.ts +14 -101
- package/skills/octocode-research/src/validation/toolCallSchema.ts +5 -65
- package/skills/octocode-research/tsdown.config.ts +2 -7
- package/skills/octocode-search-skill/SKILL.md +9 -2
- package/skills/octocode-search-skill/references/discovery-surfaces.md +2 -1
- package/skills/octocode-slides/scripts/navbridge.js +1 -14
- package/skills/octocode-slides/scripts/presenter.js +2 -61
- package/skills/octocode-stats/scripts/build_dashboard.mjs +1 -19
- package/out/chunks/chunk-7476PETK.js +0 -309
- package/out/chunks/chunk-OQBJTZWK.js +0 -60
- package/out/chunks/chunk-UCZCF3BQ.js +0 -9
- package/out/chunks/commands-XBFPLHSQ.js +0 -8
- package/out/chunks/main-help-ULF5PAQY.js +0 -10
- package/out/chunks/tool-command-M6VA7P2F.js +0 -8
- package/skills/octocode-research/AGENTS.md +0 -349
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Integration tests for circuit breaker functionality.
|
|
3
|
-
*
|
|
4
|
-
* @module tests/integration/circuitBreaker
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
8
2
|
import {
|
|
9
3
|
withCircuitBreaker,
|
|
@@ -15,7 +9,6 @@ import {
|
|
|
15
9
|
|
|
16
10
|
describe('Circuit Breaker', () => {
|
|
17
11
|
beforeEach(() => {
|
|
18
|
-
// Reset all circuits before each test
|
|
19
12
|
resetCircuit('test');
|
|
20
13
|
resetCircuit('lsp');
|
|
21
14
|
resetCircuit('github');
|
|
@@ -45,7 +38,6 @@ describe('Circuit Breaker', () => {
|
|
|
45
38
|
|
|
46
39
|
describe('State Transitions', () => {
|
|
47
40
|
it('opens after reaching failure threshold', async () => {
|
|
48
|
-
// Configure test circuit with low threshold
|
|
49
41
|
configureCircuit('test', {
|
|
50
42
|
failureThreshold: 2,
|
|
51
43
|
successThreshold: 1,
|
|
@@ -54,17 +46,14 @@ describe('Circuit Breaker', () => {
|
|
|
54
46
|
|
|
55
47
|
const failingFn = () => Promise.reject(new Error('fail'));
|
|
56
48
|
|
|
57
|
-
// First failure
|
|
58
49
|
await withCircuitBreaker('test', failingFn).catch(() => {});
|
|
59
50
|
expect(getCircuitState('test').state).toBe('closed');
|
|
60
51
|
|
|
61
|
-
// Second failure - should open circuit
|
|
62
52
|
await withCircuitBreaker('test', failingFn).catch(() => {});
|
|
63
53
|
expect(getCircuitState('test').state).toBe('open');
|
|
64
54
|
});
|
|
65
55
|
|
|
66
56
|
it('rejects immediately when circuit is open', async () => {
|
|
67
|
-
// Force circuit open
|
|
68
57
|
configureCircuit('test', {
|
|
69
58
|
failureThreshold: 1,
|
|
70
59
|
successThreshold: 1,
|
|
@@ -73,7 +62,6 @@ describe('Circuit Breaker', () => {
|
|
|
73
62
|
|
|
74
63
|
await withCircuitBreaker('test', () => Promise.reject(new Error('fail'))).catch(() => {});
|
|
75
64
|
|
|
76
|
-
// Next call should be rejected immediately
|
|
77
65
|
await expect(
|
|
78
66
|
withCircuitBreaker('test', () => Promise.resolve('ok'))
|
|
79
67
|
).rejects.toThrow(CircuitOpenError);
|
|
@@ -88,14 +76,11 @@ describe('Circuit Breaker', () => {
|
|
|
88
76
|
resetTimeoutMs: 1000,
|
|
89
77
|
});
|
|
90
78
|
|
|
91
|
-
// Open the circuit
|
|
92
79
|
await withCircuitBreaker('test', () => Promise.reject(new Error())).catch(() => {});
|
|
93
80
|
expect(getCircuitState('test').state).toBe('open');
|
|
94
81
|
|
|
95
|
-
// Advance time past reset timeout
|
|
96
82
|
vi.advanceTimersByTime(1100);
|
|
97
83
|
|
|
98
|
-
// Next call should be allowed (half-open state)
|
|
99
84
|
const result = await withCircuitBreaker('test', () => Promise.resolve('recovered'));
|
|
100
85
|
expect(result).toBe('recovered');
|
|
101
86
|
expect(getCircuitState('test').state).toBe('closed');
|
|
@@ -112,13 +97,10 @@ describe('Circuit Breaker', () => {
|
|
|
112
97
|
resetTimeoutMs: 100,
|
|
113
98
|
});
|
|
114
99
|
|
|
115
|
-
// Open circuit
|
|
116
100
|
await withCircuitBreaker('test', () => Promise.reject(new Error())).catch(() => {});
|
|
117
101
|
|
|
118
|
-
// Wait for reset
|
|
119
102
|
vi.advanceTimersByTime(150);
|
|
120
103
|
|
|
121
|
-
// Successful call should close circuit
|
|
122
104
|
await withCircuitBreaker('test', () => Promise.resolve('ok'));
|
|
123
105
|
expect(getCircuitState('test').state).toBe('closed');
|
|
124
106
|
|
|
@@ -134,13 +116,10 @@ describe('Circuit Breaker', () => {
|
|
|
134
116
|
resetTimeoutMs: 100,
|
|
135
117
|
});
|
|
136
118
|
|
|
137
|
-
// Open circuit
|
|
138
119
|
await withCircuitBreaker('test', () => Promise.reject(new Error())).catch(() => {});
|
|
139
120
|
|
|
140
|
-
// Wait for reset
|
|
141
121
|
vi.advanceTimersByTime(150);
|
|
142
122
|
|
|
143
|
-
// Another failure should reopen circuit
|
|
144
123
|
await withCircuitBreaker('test', () => Promise.reject(new Error())).catch(() => {});
|
|
145
124
|
expect(getCircuitState('test').state).toBe('open');
|
|
146
125
|
|
|
@@ -189,15 +168,12 @@ describe('Circuit Breaker', () => {
|
|
|
189
168
|
resetTimeoutMs: 10000,
|
|
190
169
|
});
|
|
191
170
|
|
|
192
|
-
// Open circuit
|
|
193
171
|
await withCircuitBreaker('test', () => Promise.reject(new Error())).catch(() => {});
|
|
194
172
|
expect(getCircuitState('test').state).toBe('open');
|
|
195
173
|
|
|
196
|
-
// Reset
|
|
197
174
|
resetCircuit('test');
|
|
198
175
|
expect(getCircuitState('test').state).toBe('closed');
|
|
199
176
|
|
|
200
|
-
// Should allow requests again
|
|
201
177
|
const result = await withCircuitBreaker('test', () => Promise.resolve('ok'));
|
|
202
178
|
expect(result).toBe('ok');
|
|
203
179
|
});
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Integration tests for route validation and response handling.
|
|
3
|
-
*
|
|
4
|
-
* @module tests/integration/routes
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
8
2
|
|
|
9
3
|
import { describe, it, expect, vi } from 'vitest';
|
|
@@ -14,7 +8,6 @@ import { githubRoutes } from '../../routes/github.js';
|
|
|
14
8
|
import { lspRoutes } from '../../routes/lsp.js';
|
|
15
9
|
import { packageRoutes } from '../../routes/package.js';
|
|
16
10
|
|
|
17
|
-
// Mock the MCP cache
|
|
18
11
|
vi.mock('../../mcpCache.js', () => ({
|
|
19
12
|
getMcpContent: vi.fn().mockReturnValue({
|
|
20
13
|
tools: {},
|
|
@@ -27,7 +20,6 @@ vi.mock('../../mcpCache.js', () => ({
|
|
|
27
20
|
isMcpInitialized: vi.fn().mockReturnValue(true),
|
|
28
21
|
}));
|
|
29
22
|
|
|
30
|
-
// Mock the MCP tools
|
|
31
23
|
vi.mock('../../index.js', () => ({
|
|
32
24
|
localSearchCode: vi.fn().mockResolvedValue({
|
|
33
25
|
content: [{ type: 'text', text: 'results:\n - status: hasResults\n data:\n files: []\n totalMatches: 0' }],
|
|
@@ -70,11 +62,9 @@ vi.mock('../../index.js', () => ({
|
|
|
70
62
|
}),
|
|
71
63
|
}));
|
|
72
64
|
|
|
73
|
-
// Create app factory with routes (matching actual server.ts configuration)
|
|
74
65
|
function createApp(): any {
|
|
75
66
|
const app = express();
|
|
76
67
|
app.use(express.json());
|
|
77
|
-
// Routes are mounted at root, matching server.ts
|
|
78
68
|
app.use('/', localRoutes);
|
|
79
69
|
app.use('/', githubRoutes);
|
|
80
70
|
app.use('/', lspRoutes);
|
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Integration test: server lifecycle (startup, PID file, shutdown).
|
|
3
|
-
*
|
|
4
|
-
* All external calls (network, process spawning, fs) are mocked.
|
|
5
|
-
* Tests verify:
|
|
6
|
-
* 1. Server creates PID file on startup
|
|
7
|
-
* 2. Health endpoint reports ok after initialization
|
|
8
|
-
* 3. PID file is removed on graceful shutdown
|
|
9
|
-
* 4. server-init detects an already-running server (fast path)
|
|
10
|
-
* 5. server-init starts server when not running, waits for health
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
14
2
|
|
|
15
3
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
@@ -17,7 +5,6 @@ import request from 'supertest';
|
|
|
17
5
|
import { join } from 'path';
|
|
18
6
|
import { homedir } from 'os';
|
|
19
7
|
|
|
20
|
-
// ── Mocks (hoisted before any imports) ──────────────────────────────────────
|
|
21
8
|
|
|
22
9
|
vi.mock('../../mcpCache.js', () => ({
|
|
23
10
|
getMcpContent: vi.fn().mockReturnValue({
|
|
@@ -107,7 +94,6 @@ vi.mock('fs', async (importOriginal) => {
|
|
|
107
94
|
};
|
|
108
95
|
});
|
|
109
96
|
|
|
110
|
-
// ── Tests ───────────────────────────────────────────────────────────────────
|
|
111
97
|
|
|
112
98
|
import { createServer } from '../../server.js';
|
|
113
99
|
|
|
@@ -159,8 +145,7 @@ describe('Server Lifecycle', () => {
|
|
|
159
145
|
}) as any
|
|
160
146
|
);
|
|
161
147
|
|
|
162
|
-
|
|
163
|
-
// the PID_FILE export and fs mock instead
|
|
148
|
+
|
|
164
149
|
const OCTOCODE_DIR = process.env.OCTOCODE_HOME || join(homedir(), '.octocode');
|
|
165
150
|
const expectedPidFile = PID_FILE;
|
|
166
151
|
|
|
@@ -188,7 +173,6 @@ describe('Server Lifecycle', () => {
|
|
|
188
173
|
globalThis.fetch = mockFetch as any;
|
|
189
174
|
|
|
190
175
|
try {
|
|
191
|
-
// Simulate checkHealth logic from server-init.ts
|
|
192
176
|
const response = await globalThis.fetch('http://localhost:1987/health', {
|
|
193
177
|
signal: AbortSignal.timeout(5000),
|
|
194
178
|
});
|
|
@@ -218,7 +202,6 @@ describe('Server Lifecycle', () => {
|
|
|
218
202
|
globalThis.fetch = mockFetch as any;
|
|
219
203
|
|
|
220
204
|
try {
|
|
221
|
-
// Simulate waitForReady polling logic
|
|
222
205
|
let ready = false;
|
|
223
206
|
for (let i = 0; i < 5 && !ready; i++) {
|
|
224
207
|
try {
|
|
@@ -228,8 +211,8 @@ describe('Server Lifecycle', () => {
|
|
|
228
211
|
const body = await (res as any).json();
|
|
229
212
|
if (body.status === 'ok') ready = true;
|
|
230
213
|
} catch {
|
|
231
|
-
|
|
232
|
-
|
|
214
|
+
void 0;
|
|
215
|
+
}
|
|
233
216
|
}
|
|
234
217
|
|
|
235
218
|
expect(ready).toBe(true);
|
|
@@ -268,8 +251,8 @@ describe('Server Lifecycle', () => {
|
|
|
268
251
|
const body = await (res as any).json();
|
|
269
252
|
if (body.status === 'ok') ready = true;
|
|
270
253
|
} catch {
|
|
271
|
-
|
|
272
|
-
|
|
254
|
+
void 0;
|
|
255
|
+
}
|
|
273
256
|
}
|
|
274
257
|
|
|
275
258
|
expect(ready).toBe(true);
|
|
@@ -108,9 +108,6 @@ describe('Tools Routes', () => {
|
|
|
108
108
|
vi.clearAllMocks();
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
-
// =========================================================================
|
|
112
|
-
// GET /tools/list
|
|
113
|
-
// =========================================================================
|
|
114
111
|
describe('GET /tools/list', () => {
|
|
115
112
|
it('returns 200 with success true', async () => {
|
|
116
113
|
const res = await request(app).get('/tools/list');
|
|
@@ -149,9 +146,6 @@ describe('Tools Routes', () => {
|
|
|
149
146
|
});
|
|
150
147
|
});
|
|
151
148
|
|
|
152
|
-
// =========================================================================
|
|
153
|
-
// GET /tools/info
|
|
154
|
-
// =========================================================================
|
|
155
149
|
describe('GET /tools/info', () => {
|
|
156
150
|
it('returns all tools from MCP content', async () => {
|
|
157
151
|
const res = await request(app).get('/tools/info');
|
|
@@ -188,9 +182,6 @@ describe('Tools Routes', () => {
|
|
|
188
182
|
});
|
|
189
183
|
});
|
|
190
184
|
|
|
191
|
-
// =========================================================================
|
|
192
|
-
// GET /tools/info/:toolName
|
|
193
|
-
// =========================================================================
|
|
194
185
|
describe('GET /tools/info/:toolName', () => {
|
|
195
186
|
it('returns tool info for valid tool', async () => {
|
|
196
187
|
const res = await request(app).get('/tools/info/localSearchCode');
|
|
@@ -238,9 +229,6 @@ describe('Tools Routes', () => {
|
|
|
238
229
|
});
|
|
239
230
|
});
|
|
240
231
|
|
|
241
|
-
// =========================================================================
|
|
242
|
-
// GET /tools/metadata
|
|
243
|
-
// =========================================================================
|
|
244
232
|
describe('GET /tools/metadata', () => {
|
|
245
233
|
it('returns metadata summary', async () => {
|
|
246
234
|
const res = await request(app).get('/tools/metadata');
|
|
@@ -259,9 +247,6 @@ describe('Tools Routes', () => {
|
|
|
259
247
|
});
|
|
260
248
|
});
|
|
261
249
|
|
|
262
|
-
// =========================================================================
|
|
263
|
-
// GET /tools/schemas
|
|
264
|
-
// =========================================================================
|
|
265
250
|
describe('GET /tools/schemas', () => {
|
|
266
251
|
it('returns all tool schemas', async () => {
|
|
267
252
|
const res = await request(app).get('/tools/schemas');
|
|
@@ -288,9 +273,6 @@ describe('Tools Routes', () => {
|
|
|
288
273
|
});
|
|
289
274
|
});
|
|
290
275
|
|
|
291
|
-
// =========================================================================
|
|
292
|
-
// GET /tools/system
|
|
293
|
-
// =========================================================================
|
|
294
276
|
describe('GET /tools/system', () => {
|
|
295
277
|
it('returns system instructions', async () => {
|
|
296
278
|
const res = await request(app).get('/tools/system');
|
|
@@ -307,9 +289,6 @@ describe('Tools Routes', () => {
|
|
|
307
289
|
});
|
|
308
290
|
});
|
|
309
291
|
|
|
310
|
-
// =========================================================================
|
|
311
|
-
// GET /tools/initContext
|
|
312
|
-
// =========================================================================
|
|
313
292
|
describe('GET /tools/initContext', () => {
|
|
314
293
|
it('returns combined system prompt and schemas', async () => {
|
|
315
294
|
const res = await request(app).get('/tools/initContext');
|
|
@@ -339,9 +318,6 @@ describe('Tools Routes', () => {
|
|
|
339
318
|
});
|
|
340
319
|
});
|
|
341
320
|
|
|
342
|
-
// =========================================================================
|
|
343
|
-
// POST /tools/call/:toolName
|
|
344
|
-
// =========================================================================
|
|
345
321
|
describe('POST /tools/call/:toolName', () => {
|
|
346
322
|
const validLocalQuery = {
|
|
347
323
|
queries: [{
|
|
@@ -461,9 +437,6 @@ describe('Tools Routes', () => {
|
|
|
461
437
|
});
|
|
462
438
|
});
|
|
463
439
|
|
|
464
|
-
// =========================================================================
|
|
465
|
-
// Readiness (503 when not initialized)
|
|
466
|
-
// =========================================================================
|
|
467
440
|
describe('Readiness gate', () => {
|
|
468
441
|
it('returns 503 for /tools/list when not initialized', async () => {
|
|
469
442
|
const { isMcpInitialized } = await import('../../mcpCache.js');
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for circuit breaker utilities.
|
|
3
|
-
*
|
|
4
|
-
* @module tests/unit/circuitBreaker
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
8
2
|
import {
|
|
9
3
|
withCircuitBreaker,
|
|
@@ -16,7 +10,6 @@ import {
|
|
|
16
10
|
|
|
17
11
|
describe('withCircuitBreaker', () => {
|
|
18
12
|
beforeEach(() => {
|
|
19
|
-
// Reset test circuit before each test
|
|
20
13
|
resetCircuit('test');
|
|
21
14
|
vi.useFakeTimers();
|
|
22
15
|
});
|
|
@@ -39,7 +32,6 @@ describe('withCircuitBreaker', () => {
|
|
|
39
32
|
configureCircuit('test', { failureThreshold: 3 });
|
|
40
33
|
const operation = vi.fn().mockRejectedValue(new Error('fail'));
|
|
41
34
|
|
|
42
|
-
// First 3 failures should open the circuit
|
|
43
35
|
for (let i = 0; i < 3; i++) {
|
|
44
36
|
await expect(withCircuitBreaker('test', operation)).rejects.toThrow('fail');
|
|
45
37
|
}
|
|
@@ -52,10 +44,8 @@ describe('withCircuitBreaker', () => {
|
|
|
52
44
|
const operation = vi.fn().mockRejectedValue(new Error('fail'));
|
|
53
45
|
const fallback = vi.fn().mockReturnValue('fallback');
|
|
54
46
|
|
|
55
|
-
// Open the circuit
|
|
56
47
|
await expect(withCircuitBreaker('test', operation)).rejects.toThrow();
|
|
57
48
|
|
|
58
|
-
// Should use fallback
|
|
59
49
|
const result = await withCircuitBreaker('test', operation, fallback);
|
|
60
50
|
|
|
61
51
|
expect(result).toBe('fallback');
|
|
@@ -66,10 +56,8 @@ describe('withCircuitBreaker', () => {
|
|
|
66
56
|
configureCircuit('test', { failureThreshold: 1, resetTimeoutMs: 10000 });
|
|
67
57
|
const operation = vi.fn().mockRejectedValue(new Error('fail'));
|
|
68
58
|
|
|
69
|
-
// Open the circuit
|
|
70
59
|
await expect(withCircuitBreaker('test', operation)).rejects.toThrow();
|
|
71
60
|
|
|
72
|
-
// Should throw CircuitOpenError
|
|
73
61
|
await expect(withCircuitBreaker('test', operation)).rejects.toBeInstanceOf(
|
|
74
62
|
CircuitOpenError
|
|
75
63
|
);
|
|
@@ -82,14 +70,11 @@ describe('withCircuitBreaker', () => {
|
|
|
82
70
|
.mockRejectedValueOnce(new Error('fail'))
|
|
83
71
|
.mockResolvedValue('success');
|
|
84
72
|
|
|
85
|
-
// Open the circuit
|
|
86
73
|
await expect(withCircuitBreaker('test', operation)).rejects.toThrow();
|
|
87
74
|
expect(getCircuitState('test').state).toBe('open');
|
|
88
75
|
|
|
89
|
-
// Advance time past reset timeout
|
|
90
76
|
vi.advanceTimersByTime(1001);
|
|
91
77
|
|
|
92
|
-
// Next attempt should try (half-open)
|
|
93
78
|
const result = await withCircuitBreaker('test', operation);
|
|
94
79
|
expect(result).toBe('success');
|
|
95
80
|
});
|
|
@@ -104,17 +89,13 @@ describe('withCircuitBreaker', () => {
|
|
|
104
89
|
const failOp = vi.fn().mockRejectedValue(new Error('fail'));
|
|
105
90
|
const successOp = vi.fn().mockResolvedValue('success');
|
|
106
91
|
|
|
107
|
-
// Open the circuit
|
|
108
92
|
await expect(withCircuitBreaker('test', failOp)).rejects.toThrow();
|
|
109
93
|
|
|
110
|
-
// Advance time to half-open
|
|
111
94
|
vi.advanceTimersByTime(1001);
|
|
112
95
|
|
|
113
|
-
// First success - still half-open
|
|
114
96
|
await withCircuitBreaker('test', successOp);
|
|
115
97
|
expect(getCircuitState('test').state).toBe('half-open');
|
|
116
98
|
|
|
117
|
-
// Second success - should close
|
|
118
99
|
await withCircuitBreaker('test', successOp);
|
|
119
100
|
expect(getCircuitState('test').state).toBe('closed');
|
|
120
101
|
});
|
|
@@ -124,13 +105,10 @@ describe('withCircuitBreaker', () => {
|
|
|
124
105
|
|
|
125
106
|
const operation = vi.fn().mockRejectedValue(new Error('fail'));
|
|
126
107
|
|
|
127
|
-
// Open the circuit
|
|
128
108
|
await expect(withCircuitBreaker('test', operation)).rejects.toThrow();
|
|
129
109
|
|
|
130
|
-
// Advance time to half-open
|
|
131
110
|
vi.advanceTimersByTime(1001);
|
|
132
111
|
|
|
133
|
-
// Fail in half-open - should go back to open
|
|
134
112
|
await expect(withCircuitBreaker('test', operation)).rejects.toThrow();
|
|
135
113
|
expect(getCircuitState('test').state).toBe('open');
|
|
136
114
|
});
|
|
@@ -141,16 +119,13 @@ describe('withCircuitBreaker', () => {
|
|
|
141
119
|
const failOp = vi.fn().mockRejectedValue(new Error('fail'));
|
|
142
120
|
const successOp = vi.fn().mockResolvedValue('success');
|
|
143
121
|
|
|
144
|
-
// 2 failures
|
|
145
122
|
await expect(withCircuitBreaker('test', failOp)).rejects.toThrow();
|
|
146
123
|
await expect(withCircuitBreaker('test', failOp)).rejects.toThrow();
|
|
147
124
|
expect(getCircuitState('test').failures).toBe(2);
|
|
148
125
|
|
|
149
|
-
// 1 success resets failures
|
|
150
126
|
await withCircuitBreaker('test', successOp);
|
|
151
127
|
expect(getCircuitState('test').failures).toBe(0);
|
|
152
128
|
|
|
153
|
-
// Still closed
|
|
154
129
|
expect(getCircuitState('test').state).toBe('closed');
|
|
155
130
|
});
|
|
156
131
|
});
|
|
@@ -195,11 +170,9 @@ describe('resetCircuit', () => {
|
|
|
195
170
|
configureCircuit('test', { failureThreshold: 1 });
|
|
196
171
|
const operation = vi.fn().mockRejectedValue(new Error('fail'));
|
|
197
172
|
|
|
198
|
-
// Open the circuit
|
|
199
173
|
await expect(withCircuitBreaker('test', operation)).rejects.toThrow();
|
|
200
174
|
expect(getCircuitState('test').state).toBe('open');
|
|
201
175
|
|
|
202
|
-
// Reset
|
|
203
176
|
resetCircuit('test');
|
|
204
177
|
|
|
205
178
|
expect(getCircuitState('test').state).toBe('closed');
|
|
@@ -218,10 +191,8 @@ describe('getAllCircuitStates', () => {
|
|
|
218
191
|
const failOp = vi.fn().mockRejectedValue(new Error('fail'));
|
|
219
192
|
const successOp = vi.fn().mockResolvedValue('success');
|
|
220
193
|
|
|
221
|
-
// Open circuit-a
|
|
222
194
|
await expect(withCircuitBreaker('circuit-a', failOp)).rejects.toThrow();
|
|
223
195
|
|
|
224
|
-
// Keep circuit-b closed
|
|
225
196
|
await withCircuitBreaker('circuit-b', successOp);
|
|
226
197
|
|
|
227
198
|
const states = getAllCircuitStates();
|
|
@@ -1,28 +1,19 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for error handler middleware.
|
|
3
|
-
*
|
|
4
|
-
* @module tests/unit/errorHandler
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
8
2
|
import type { Request, Response, NextFunction } from 'express';
|
|
9
3
|
import { errorHandler, type ApiError } from '../../middleware/errorHandler.js';
|
|
10
|
-
import type { z } from 'zod
|
|
4
|
+
import type { z } from 'zod';
|
|
11
5
|
import { fireAndForgetWithTimeout } from '../../utils/asyncTimeout.js';
|
|
12
6
|
|
|
13
|
-
// Mock the logger
|
|
14
7
|
vi.mock('../../utils/logger.js', () => ({
|
|
15
8
|
logError: vi.fn(),
|
|
16
9
|
logWarn: vi.fn(),
|
|
17
10
|
sanitizeQueryParams: vi.fn((q) => q),
|
|
18
11
|
}));
|
|
19
12
|
|
|
20
|
-
// Mock the session error logging
|
|
21
13
|
vi.mock('../../index.js', () => ({
|
|
22
14
|
logSessionError: vi.fn().mockResolvedValue(undefined),
|
|
23
15
|
}));
|
|
24
16
|
|
|
25
|
-
// Mock the async timeout
|
|
26
17
|
vi.mock('../../utils/asyncTimeout.js', () => ({
|
|
27
18
|
fireAndForgetWithTimeout: vi.fn(),
|
|
28
19
|
}));
|
|
@@ -166,7 +157,6 @@ describe('errorHandler', () => {
|
|
|
166
157
|
|
|
167
158
|
errorHandler(error, mockReq as Request, mockRes as Response, mockNext);
|
|
168
159
|
|
|
169
|
-
// Verify fireAndForgetWithTimeout was called (tool name is passed to logSessionError)
|
|
170
160
|
expect(fireAndForgetWithTimeout).toHaveBeenCalled();
|
|
171
161
|
});
|
|
172
162
|
|
|
@@ -176,7 +166,6 @@ describe('errorHandler', () => {
|
|
|
176
166
|
|
|
177
167
|
errorHandler(error, mockReq as Request, mockRes as Response, mockNext);
|
|
178
168
|
|
|
179
|
-
// Tool name should be 'unknown' for non-tool paths
|
|
180
169
|
expect(fireAndForgetWithTimeout).toHaveBeenCalled();
|
|
181
170
|
});
|
|
182
171
|
});
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for HTTP preprocessing utilities.
|
|
3
|
-
*
|
|
4
|
-
* Tests type coercion, path safety, and array handling for HTTP query strings.
|
|
5
|
-
*
|
|
6
|
-
* @module tests/unit/httpPreprocess
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
1
|
import { describe, it, expect } from 'vitest';
|
|
10
2
|
import { toNumber, toBoolean, toArray, safePath, numericString, booleanString, stringArray } from '../../validation/httpPreprocess.js';
|
|
11
3
|
|
|
@@ -23,7 +15,7 @@ describe('toNumber', () => {
|
|
|
23
15
|
|
|
24
16
|
it('returns non-numeric strings as-is', () => {
|
|
25
17
|
expect(toNumber('abc')).toBe('abc');
|
|
26
|
-
expect(toNumber('12.5')).toBe('12.5');
|
|
18
|
+
expect(toNumber('12.5')).toBe('12.5');
|
|
27
19
|
expect(toNumber('12abc')).toBe('12abc');
|
|
28
20
|
});
|
|
29
21
|
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for logger utilities.
|
|
3
|
-
*
|
|
4
|
-
* @module tests/unit/logger
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import { describe, it, expect } from 'vitest';
|
|
8
2
|
import { sanitizeQueryParams } from '../../utils/logger.js';
|
|
9
3
|
|
|
@@ -125,7 +119,6 @@ describe('sanitizeQueryParams', () => {
|
|
|
125
119
|
};
|
|
126
120
|
const result = sanitizeQueryParams(query);
|
|
127
121
|
|
|
128
|
-
// Only top-level keys are checked
|
|
129
122
|
expect(result.config).toEqual({ token: 'nested' });
|
|
130
123
|
expect(result.path).toBe('/src');
|
|
131
124
|
});
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for query parser and validation utilities.
|
|
3
|
-
*
|
|
4
|
-
* @module tests/unit/queryParser
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import { describe, it, expect } from 'vitest';
|
|
8
2
|
import { validateToolCallBody, getValidationHints, MAX_QUERIES } from '../../validation/toolCallSchema.js';
|
|
9
|
-
import type { z } from 'zod
|
|
3
|
+
import type { z } from 'zod';
|
|
10
4
|
|
|
11
5
|
describe('validateToolCallBody', () => {
|
|
12
6
|
describe('valid inputs', () => {
|
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for response builder utilities.
|
|
3
|
-
*
|
|
4
|
-
* @module tests/unit/responseBuilder
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import { describe, it, expect, vi } from 'vitest';
|
|
8
2
|
import { ResearchResponse, detectLanguageFromPath } from '../../utils/responseBuilder.js';
|
|
9
3
|
|
|
10
|
-
// Mock octocode-mcp public module
|
|
11
4
|
vi.mock('octocode-mcp/public', () => ({
|
|
12
5
|
createRoleBasedResult: vi.fn((opts) => ({
|
|
13
6
|
content: [
|
|
@@ -169,7 +162,6 @@ describe('ResearchResponse.searchResults', () => {
|
|
|
169
162
|
});
|
|
170
163
|
|
|
171
164
|
it('handles non-string preview gracefully (no crash)', () => {
|
|
172
|
-
// preview could be a number or other non-string type from malformed data
|
|
173
165
|
const result = ResearchResponse.searchResults({
|
|
174
166
|
files: [
|
|
175
167
|
{ path: 'src/utils.ts', matches: 1, preview: 42 as unknown as string },
|
|
@@ -177,7 +169,6 @@ describe('ResearchResponse.searchResults', () => {
|
|
|
177
169
|
totalMatches: 1,
|
|
178
170
|
});
|
|
179
171
|
|
|
180
|
-
// Should not crash and should not include garbled output
|
|
181
172
|
expect(result.content[0].text).toContain('src/utils.ts');
|
|
182
173
|
expect(result.content[0].text).not.toContain('.slice');
|
|
183
174
|
});
|
|
@@ -355,7 +346,6 @@ describe('ResearchResponse.repoStructure', () => {
|
|
|
355
346
|
structure: { files, folders: [] },
|
|
356
347
|
});
|
|
357
348
|
|
|
358
|
-
// Only first 20 files shown
|
|
359
349
|
expect(result.content[0].text).not.toContain('file24.ts');
|
|
360
350
|
});
|
|
361
351
|
});
|
|
@@ -406,7 +396,6 @@ describe('ResearchResponse.packageSearch', () => {
|
|
|
406
396
|
registry: 'npm',
|
|
407
397
|
});
|
|
408
398
|
|
|
409
|
-
// Should use 'No description' fallback for non-string
|
|
410
399
|
expect(result.content[0].text).toContain('No description');
|
|
411
400
|
});
|
|
412
401
|
});
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for retry utilities.
|
|
3
|
-
*
|
|
4
|
-
* @module tests/unit/retry
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
8
2
|
import { withRetry, RETRY_CONFIGS } from '../../utils/retry.js';
|
|
9
3
|
|
|
@@ -34,10 +28,8 @@ describe('withRetry', () => {
|
|
|
34
28
|
|
|
35
29
|
const resultPromise = withRetry(operation, RETRY_CONFIGS.local);
|
|
36
30
|
|
|
37
|
-
// First call fails immediately
|
|
38
31
|
await vi.advanceTimersByTimeAsync(0);
|
|
39
32
|
|
|
40
|
-
// Wait for delay and retry
|
|
41
33
|
await vi.advanceTimersByTimeAsync(100);
|
|
42
34
|
|
|
43
35
|
const result = await resultPromise;
|
|
@@ -72,14 +64,11 @@ describe('withRetry', () => {
|
|
|
72
64
|
|
|
73
65
|
const resultPromise = withRetry(operation, config);
|
|
74
66
|
|
|
75
|
-
// First attempt fails
|
|
76
67
|
await vi.advanceTimersByTimeAsync(0);
|
|
77
68
|
|
|
78
|
-
// First retry after 100ms
|
|
79
69
|
await vi.advanceTimersByTimeAsync(100);
|
|
80
70
|
expect(operation).toHaveBeenCalledTimes(2);
|
|
81
71
|
|
|
82
|
-
// Second retry after 200ms (100 * 2)
|
|
83
72
|
await vi.advanceTimersByTimeAsync(200);
|
|
84
73
|
|
|
85
74
|
const result = await resultPromise;
|
|
@@ -97,20 +86,17 @@ describe('withRetry', () => {
|
|
|
97
86
|
const config = {
|
|
98
87
|
maxAttempts: 3,
|
|
99
88
|
initialDelayMs: 500,
|
|
100
|
-
maxDelayMs: 600,
|
|
89
|
+
maxDelayMs: 600,
|
|
101
90
|
backoffMultiplier: 2,
|
|
102
91
|
retryOn: (err: unknown) => (err as { code?: string })?.code === 'ETIMEDOUT',
|
|
103
92
|
};
|
|
104
93
|
|
|
105
94
|
const resultPromise = withRetry(operation, config);
|
|
106
95
|
|
|
107
|
-
// First attempt fails
|
|
108
96
|
await vi.advanceTimersByTimeAsync(0);
|
|
109
97
|
|
|
110
|
-
// First retry after 500ms
|
|
111
98
|
await vi.advanceTimersByTimeAsync(500);
|
|
112
99
|
|
|
113
|
-
// Second retry should be capped at 600ms (not 1000ms)
|
|
114
100
|
await vi.advanceTimersByTimeAsync(600);
|
|
115
101
|
|
|
116
102
|
const result = await resultPromise;
|
|
@@ -130,13 +116,10 @@ describe('withRetry', () => {
|
|
|
130
116
|
retryOn: () => true,
|
|
131
117
|
};
|
|
132
118
|
|
|
133
|
-
// Start the operation and immediately handle the promise
|
|
134
119
|
const resultPromise = withRetry(operation, config).catch((e) => e);
|
|
135
120
|
|
|
136
|
-
// Run all timers to completion
|
|
137
121
|
await vi.runAllTimersAsync();
|
|
138
122
|
|
|
139
|
-
// Now check the result
|
|
140
123
|
const result = await resultPromise;
|
|
141
124
|
expect(result).toBe(finalError);
|
|
142
125
|
expect(operation).toHaveBeenCalledTimes(2);
|