mcp-rubber-duck 1.5.1 → 1.5.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/.releaserc.json +4 -0
- package/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/tests/approval.test.ts +440 -0
- package/tests/cache.test.ts +240 -0
- package/tests/config.test.ts +468 -0
- package/tests/consensus.test.ts +10 -0
- package/tests/conversation.test.ts +86 -0
- package/tests/duck-debate.test.ts +105 -1
- package/tests/duck-iterate.test.ts +30 -0
- package/tests/duck-judge.test.ts +93 -0
- package/tests/duck-vote.test.ts +46 -0
- package/tests/health.test.ts +129 -0
- package/tests/providers.test.ts +591 -0
- package/tests/safe-logger.test.ts +314 -0
- package/tests/tools/approve-mcp-request.test.ts +239 -0
- package/tests/tools/ask-duck.test.ts +159 -0
- package/tests/tools/chat-duck.test.ts +191 -0
- package/tests/tools/compare-ducks.test.ts +190 -0
- package/tests/tools/duck-council.test.ts +219 -0
- package/tests/tools/get-pending-approvals.test.ts +195 -0
- package/tests/tools/list-ducks.test.ts +144 -0
- package/tests/tools/list-models.test.ts +163 -0
- package/tests/tools/mcp-status.test.ts +330 -0
package/.releaserc.json
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [1.5.2](https://github.com/nesquikm/mcp-rubber-duck/compare/v1.5.1...v1.5.2) (2026-01-08)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **ci:** trigger patch releases for dependency updates ([b1c92c4](https://github.com/nesquikm/mcp-rubber-duck/commit/b1c92c4f56e4a8e67c307c28284b9f03256dc72f))
|
|
7
|
+
|
|
1
8
|
## [1.5.1](https://github.com/nesquikm/mcp-rubber-duck/compare/v1.5.0...v1.5.1) (2026-01-02)
|
|
2
9
|
|
|
3
10
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
import { describe, it, expect, jest, beforeEach, afterEach } from '@jest/globals';
|
|
2
|
+
import { ApprovalService } from '../src/services/approval.js';
|
|
3
|
+
|
|
4
|
+
// Mock logger to avoid console noise during tests
|
|
5
|
+
jest.mock('../src/utils/logger');
|
|
6
|
+
jest.mock('../src/utils/safe-logger');
|
|
7
|
+
|
|
8
|
+
describe('ApprovalService', () => {
|
|
9
|
+
let service: ApprovalService;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
jest.clearAllMocks();
|
|
13
|
+
jest.useFakeTimers();
|
|
14
|
+
service = new ApprovalService(60); // 60 second timeout for tests
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
service.shutdown();
|
|
19
|
+
jest.useRealTimers();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('createApprovalRequest', () => {
|
|
23
|
+
it('should create a pending approval request', () => {
|
|
24
|
+
const request = service.createApprovalRequest(
|
|
25
|
+
'TestDuck',
|
|
26
|
+
'filesystem',
|
|
27
|
+
'read_file',
|
|
28
|
+
{ path: '/tmp/test.txt' }
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
expect(request.id).toBeDefined();
|
|
32
|
+
expect(request.duckName).toBe('TestDuck');
|
|
33
|
+
expect(request.mcpServer).toBe('filesystem');
|
|
34
|
+
expect(request.toolName).toBe('read_file');
|
|
35
|
+
expect(request.status).toBe('pending');
|
|
36
|
+
expect(request.arguments).toEqual({ path: '/tmp/test.txt' });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should set expiration time based on timeout', () => {
|
|
40
|
+
const before = Date.now();
|
|
41
|
+
const request = service.createApprovalRequest(
|
|
42
|
+
'TestDuck',
|
|
43
|
+
'filesystem',
|
|
44
|
+
'read_file',
|
|
45
|
+
{}
|
|
46
|
+
);
|
|
47
|
+
const after = Date.now();
|
|
48
|
+
|
|
49
|
+
// 60 seconds timeout = 60000 ms
|
|
50
|
+
expect(request.expiresAt).toBeGreaterThanOrEqual(before + 60000);
|
|
51
|
+
expect(request.expiresAt).toBeLessThanOrEqual(after + 60000);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should generate unique IDs for each request', () => {
|
|
55
|
+
const request1 = service.createApprovalRequest('Duck1', 'server', 'tool', {});
|
|
56
|
+
const request2 = service.createApprovalRequest('Duck2', 'server', 'tool', {});
|
|
57
|
+
|
|
58
|
+
expect(request1.id).not.toBe(request2.id);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('getApprovalRequest', () => {
|
|
63
|
+
it('should return existing request', () => {
|
|
64
|
+
const created = service.createApprovalRequest('TestDuck', 'server', 'tool', {});
|
|
65
|
+
const retrieved = service.getApprovalRequest(created.id);
|
|
66
|
+
|
|
67
|
+
expect(retrieved).toBeDefined();
|
|
68
|
+
expect(retrieved?.id).toBe(created.id);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should return undefined for non-existent request', () => {
|
|
72
|
+
const retrieved = service.getApprovalRequest('non-existent-id');
|
|
73
|
+
|
|
74
|
+
expect(retrieved).toBeUndefined();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should mark expired requests when retrieved', () => {
|
|
78
|
+
const request = service.createApprovalRequest('TestDuck', 'server', 'tool', {});
|
|
79
|
+
|
|
80
|
+
// Advance time past expiration
|
|
81
|
+
jest.advanceTimersByTime(61000);
|
|
82
|
+
|
|
83
|
+
const retrieved = service.getApprovalRequest(request.id);
|
|
84
|
+
|
|
85
|
+
expect(retrieved?.status).toBe('expired');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('getApprovalStatus', () => {
|
|
90
|
+
it('should return status of existing request', () => {
|
|
91
|
+
const request = service.createApprovalRequest('TestDuck', 'server', 'tool', {});
|
|
92
|
+
|
|
93
|
+
expect(service.getApprovalStatus(request.id)).toBe('pending');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should return undefined for non-existent request', () => {
|
|
97
|
+
expect(service.getApprovalStatus('non-existent')).toBeUndefined();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('approveRequest', () => {
|
|
102
|
+
it('should approve pending request', () => {
|
|
103
|
+
const request = service.createApprovalRequest('TestDuck', 'server', 'tool', {});
|
|
104
|
+
|
|
105
|
+
const result = service.approveRequest(request.id);
|
|
106
|
+
|
|
107
|
+
expect(result).toBe(true);
|
|
108
|
+
expect(service.getApprovalStatus(request.id)).toBe('approved');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should set approvedBy field', () => {
|
|
112
|
+
const request = service.createApprovalRequest('TestDuck', 'server', 'tool', {});
|
|
113
|
+
|
|
114
|
+
service.approveRequest(request.id, 'admin');
|
|
115
|
+
|
|
116
|
+
const retrieved = service.getApprovalRequest(request.id);
|
|
117
|
+
expect(retrieved?.approvedBy).toBe('admin');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should default approvedBy to user', () => {
|
|
121
|
+
const request = service.createApprovalRequest('TestDuck', 'server', 'tool', {});
|
|
122
|
+
|
|
123
|
+
service.approveRequest(request.id);
|
|
124
|
+
|
|
125
|
+
const retrieved = service.getApprovalRequest(request.id);
|
|
126
|
+
expect(retrieved?.approvedBy).toBe('user');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should return false for non-existent request', () => {
|
|
130
|
+
const result = service.approveRequest('non-existent');
|
|
131
|
+
|
|
132
|
+
expect(result).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should return false for already approved request', () => {
|
|
136
|
+
const request = service.createApprovalRequest('TestDuck', 'server', 'tool', {});
|
|
137
|
+
service.approveRequest(request.id);
|
|
138
|
+
|
|
139
|
+
const result = service.approveRequest(request.id);
|
|
140
|
+
|
|
141
|
+
expect(result).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should return false for expired request', () => {
|
|
145
|
+
const request = service.createApprovalRequest('TestDuck', 'server', 'tool', {});
|
|
146
|
+
|
|
147
|
+
// Advance time past expiration
|
|
148
|
+
jest.advanceTimersByTime(61000);
|
|
149
|
+
|
|
150
|
+
const result = service.approveRequest(request.id);
|
|
151
|
+
|
|
152
|
+
expect(result).toBe(false);
|
|
153
|
+
expect(service.getApprovalStatus(request.id)).toBe('expired');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should detect expiration via Date.now check even if status is still pending', () => {
|
|
157
|
+
const request = service.createApprovalRequest('TestDuck', 'server', 'tool', {});
|
|
158
|
+
|
|
159
|
+
// Request is created with status 'pending'
|
|
160
|
+
expect(request.status).toBe('pending');
|
|
161
|
+
|
|
162
|
+
// Directly set expiresAt to be in the past (without triggering cleanup timer)
|
|
163
|
+
// This simulates the case where time has passed but cleanup hasn't run
|
|
164
|
+
request.expiresAt = Date.now() - 1000;
|
|
165
|
+
|
|
166
|
+
// Status is still 'pending' because cleanup timer hasn't run
|
|
167
|
+
expect(request.status).toBe('pending');
|
|
168
|
+
|
|
169
|
+
// approveRequest should detect expiration via Date.now() check
|
|
170
|
+
const result = service.approveRequest(request.id);
|
|
171
|
+
|
|
172
|
+
expect(result).toBe(false);
|
|
173
|
+
expect(request.status).toBe('expired');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should add tool to session approvals', () => {
|
|
177
|
+
const request = service.createApprovalRequest('TestDuck', 'filesystem', 'read_file', {});
|
|
178
|
+
|
|
179
|
+
service.approveRequest(request.id);
|
|
180
|
+
|
|
181
|
+
expect(service.isToolApprovedForSession('TestDuck', 'filesystem', 'read_file')).toBe(true);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe('denyRequest', () => {
|
|
186
|
+
it('should deny pending request', () => {
|
|
187
|
+
const request = service.createApprovalRequest('TestDuck', 'server', 'tool', {});
|
|
188
|
+
|
|
189
|
+
const result = service.denyRequest(request.id);
|
|
190
|
+
|
|
191
|
+
expect(result).toBe(true);
|
|
192
|
+
expect(service.getApprovalStatus(request.id)).toBe('denied');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should set denial reason when provided', () => {
|
|
196
|
+
const request = service.createApprovalRequest('TestDuck', 'server', 'tool', {});
|
|
197
|
+
|
|
198
|
+
service.denyRequest(request.id, 'Security concern');
|
|
199
|
+
|
|
200
|
+
const retrieved = service.getApprovalRequest(request.id);
|
|
201
|
+
expect(retrieved?.deniedReason).toBe('Security concern');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should return false for non-existent request', () => {
|
|
205
|
+
const result = service.denyRequest('non-existent');
|
|
206
|
+
|
|
207
|
+
expect(result).toBe(false);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should return false for already denied request', () => {
|
|
211
|
+
const request = service.createApprovalRequest('TestDuck', 'server', 'tool', {});
|
|
212
|
+
service.denyRequest(request.id);
|
|
213
|
+
|
|
214
|
+
const result = service.denyRequest(request.id);
|
|
215
|
+
|
|
216
|
+
expect(result).toBe(false);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should return false for approved request', () => {
|
|
220
|
+
const request = service.createApprovalRequest('TestDuck', 'server', 'tool', {});
|
|
221
|
+
service.approveRequest(request.id);
|
|
222
|
+
|
|
223
|
+
const result = service.denyRequest(request.id);
|
|
224
|
+
|
|
225
|
+
expect(result).toBe(false);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe('getPendingApprovals', () => {
|
|
230
|
+
it('should return only pending requests', () => {
|
|
231
|
+
service.createApprovalRequest('Duck1', 'server', 'tool', {});
|
|
232
|
+
const approved = service.createApprovalRequest('Duck2', 'server', 'tool', {});
|
|
233
|
+
service.approveRequest(approved.id);
|
|
234
|
+
const denied = service.createApprovalRequest('Duck3', 'server', 'tool', {});
|
|
235
|
+
service.denyRequest(denied.id);
|
|
236
|
+
|
|
237
|
+
const pending = service.getPendingApprovals();
|
|
238
|
+
|
|
239
|
+
expect(pending).toHaveLength(1);
|
|
240
|
+
expect(pending[0].duckName).toBe('Duck1');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('should return empty array when no pending requests', () => {
|
|
244
|
+
const pending = service.getPendingApprovals();
|
|
245
|
+
|
|
246
|
+
expect(pending).toHaveLength(0);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe('getAllApprovals', () => {
|
|
251
|
+
it('should return all requests', () => {
|
|
252
|
+
service.createApprovalRequest('Duck1', 'server', 'tool', {});
|
|
253
|
+
const approved = service.createApprovalRequest('Duck2', 'server', 'tool', {});
|
|
254
|
+
service.approveRequest(approved.id);
|
|
255
|
+
const denied = service.createApprovalRequest('Duck3', 'server', 'tool', {});
|
|
256
|
+
service.denyRequest(denied.id);
|
|
257
|
+
|
|
258
|
+
const all = service.getAllApprovals();
|
|
259
|
+
|
|
260
|
+
expect(all).toHaveLength(3);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe('getApprovalsByDuck', () => {
|
|
265
|
+
it('should filter requests by duck name', () => {
|
|
266
|
+
service.createApprovalRequest('Duck1', 'server', 'tool1', {});
|
|
267
|
+
service.createApprovalRequest('Duck1', 'server', 'tool2', {});
|
|
268
|
+
service.createApprovalRequest('Duck2', 'server', 'tool3', {});
|
|
269
|
+
|
|
270
|
+
const duck1Requests = service.getApprovalsByDuck('Duck1');
|
|
271
|
+
const duck2Requests = service.getApprovalsByDuck('Duck2');
|
|
272
|
+
const nonExistent = service.getApprovalsByDuck('NonExistent');
|
|
273
|
+
|
|
274
|
+
expect(duck1Requests).toHaveLength(2);
|
|
275
|
+
expect(duck2Requests).toHaveLength(1);
|
|
276
|
+
expect(nonExistent).toHaveLength(0);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe('cleanupExpired', () => {
|
|
281
|
+
it('should mark expired pending requests as expired', () => {
|
|
282
|
+
const request = service.createApprovalRequest('TestDuck', 'server', 'tool', {});
|
|
283
|
+
|
|
284
|
+
// Advance time past expiration
|
|
285
|
+
jest.advanceTimersByTime(61000);
|
|
286
|
+
|
|
287
|
+
const cleanedCount = service.cleanupExpired();
|
|
288
|
+
|
|
289
|
+
expect(cleanedCount).toBe(1);
|
|
290
|
+
expect(service.getApprovalStatus(request.id)).toBe('expired');
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should not affect already approved/denied requests', () => {
|
|
294
|
+
const approved = service.createApprovalRequest('Duck1', 'server', 'tool', {});
|
|
295
|
+
service.approveRequest(approved.id);
|
|
296
|
+
const denied = service.createApprovalRequest('Duck2', 'server', 'tool', {});
|
|
297
|
+
service.denyRequest(denied.id);
|
|
298
|
+
|
|
299
|
+
// Advance time past expiration
|
|
300
|
+
jest.advanceTimersByTime(61000);
|
|
301
|
+
|
|
302
|
+
const cleanedCount = service.cleanupExpired();
|
|
303
|
+
|
|
304
|
+
expect(cleanedCount).toBe(0);
|
|
305
|
+
expect(service.getApprovalStatus(approved.id)).toBe('approved');
|
|
306
|
+
expect(service.getApprovalStatus(denied.id)).toBe('denied');
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('should be called by cleanup timer', () => {
|
|
310
|
+
service.createApprovalRequest('TestDuck', 'server', 'tool', {});
|
|
311
|
+
|
|
312
|
+
// Advance time past cleanup interval (60 seconds + expiration)
|
|
313
|
+
jest.advanceTimersByTime(121000);
|
|
314
|
+
|
|
315
|
+
const pending = service.getPendingApprovals();
|
|
316
|
+
|
|
317
|
+
expect(pending).toHaveLength(0);
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
describe('session approvals', () => {
|
|
322
|
+
it('should track tool approvals for session', () => {
|
|
323
|
+
expect(service.isToolApprovedForSession('Duck', 'server', 'tool')).toBe(false);
|
|
324
|
+
|
|
325
|
+
service.markToolAsApprovedForSession('Duck', 'server', 'tool');
|
|
326
|
+
|
|
327
|
+
expect(service.isToolApprovedForSession('Duck', 'server', 'tool')).toBe(true);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('should differentiate between duck/server/tool combinations', () => {
|
|
331
|
+
service.markToolAsApprovedForSession('Duck1', 'server', 'tool');
|
|
332
|
+
|
|
333
|
+
expect(service.isToolApprovedForSession('Duck1', 'server', 'tool')).toBe(true);
|
|
334
|
+
expect(service.isToolApprovedForSession('Duck2', 'server', 'tool')).toBe(false);
|
|
335
|
+
expect(service.isToolApprovedForSession('Duck1', 'other', 'tool')).toBe(false);
|
|
336
|
+
expect(service.isToolApprovedForSession('Duck1', 'server', 'other')).toBe(false);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('should clear session approvals', () => {
|
|
340
|
+
service.markToolAsApprovedForSession('Duck1', 'server', 'tool1');
|
|
341
|
+
service.markToolAsApprovedForSession('Duck2', 'server', 'tool2');
|
|
342
|
+
|
|
343
|
+
service.clearSessionApprovals();
|
|
344
|
+
|
|
345
|
+
expect(service.isToolApprovedForSession('Duck1', 'server', 'tool1')).toBe(false);
|
|
346
|
+
expect(service.isToolApprovedForSession('Duck2', 'server', 'tool2')).toBe(false);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('should get all session approvals', () => {
|
|
350
|
+
service.markToolAsApprovedForSession('Duck1', 'server', 'tool1');
|
|
351
|
+
service.markToolAsApprovedForSession('Duck2', 'server', 'tool2');
|
|
352
|
+
|
|
353
|
+
const approvals = service.getSessionApprovals();
|
|
354
|
+
|
|
355
|
+
expect(approvals).toHaveLength(2);
|
|
356
|
+
expect(approvals).toContain('Duck1:server:tool1');
|
|
357
|
+
expect(approvals).toContain('Duck2:server:tool2');
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
describe('getStats', () => {
|
|
362
|
+
it('should return accurate statistics', () => {
|
|
363
|
+
service.createApprovalRequest('Duck1', 'server', 'tool', {});
|
|
364
|
+
const approved = service.createApprovalRequest('Duck2', 'server', 'tool', {});
|
|
365
|
+
service.approveRequest(approved.id);
|
|
366
|
+
const denied = service.createApprovalRequest('Duck3', 'server', 'tool', {});
|
|
367
|
+
service.denyRequest(denied.id);
|
|
368
|
+
const expiring = service.createApprovalRequest('Duck4', 'server', 'tool', {});
|
|
369
|
+
|
|
370
|
+
// Expire one request
|
|
371
|
+
jest.advanceTimersByTime(61000);
|
|
372
|
+
service.getApprovalRequest(expiring.id); // Trigger expiration check
|
|
373
|
+
|
|
374
|
+
const stats = service.getStats();
|
|
375
|
+
|
|
376
|
+
expect(stats.total).toBe(4);
|
|
377
|
+
expect(stats.pending).toBe(0); // The first one also expired since time advanced
|
|
378
|
+
expect(stats.approved).toBe(1);
|
|
379
|
+
expect(stats.denied).toBe(1);
|
|
380
|
+
expect(stats.expired).toBe(2);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it('should return zeros when no requests exist', () => {
|
|
384
|
+
const stats = service.getStats();
|
|
385
|
+
|
|
386
|
+
expect(stats.total).toBe(0);
|
|
387
|
+
expect(stats.pending).toBe(0);
|
|
388
|
+
expect(stats.approved).toBe(0);
|
|
389
|
+
expect(stats.denied).toBe(0);
|
|
390
|
+
expect(stats.expired).toBe(0);
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
describe('shutdown', () => {
|
|
395
|
+
it('should stop cleanup timer', () => {
|
|
396
|
+
service.shutdown();
|
|
397
|
+
|
|
398
|
+
// Should not throw when advancing timers after shutdown
|
|
399
|
+
expect(() => jest.advanceTimersByTime(120000)).not.toThrow();
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it('should be safe to call multiple times', () => {
|
|
403
|
+
service.shutdown();
|
|
404
|
+
expect(() => service.shutdown()).not.toThrow();
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
describe('custom timeout', () => {
|
|
409
|
+
it('should use custom timeout value', () => {
|
|
410
|
+
const customService = new ApprovalService(30); // 30 seconds
|
|
411
|
+
|
|
412
|
+
const request = customService.createApprovalRequest('Duck', 'server', 'tool', {});
|
|
413
|
+
|
|
414
|
+
// After 25 seconds, should still be pending
|
|
415
|
+
jest.advanceTimersByTime(25000);
|
|
416
|
+
expect(customService.getApprovalStatus(request.id)).toBe('pending');
|
|
417
|
+
|
|
418
|
+
// After 31 seconds total, should be expired
|
|
419
|
+
jest.advanceTimersByTime(6000);
|
|
420
|
+
expect(customService.getApprovalStatus(request.id)).toBe('expired');
|
|
421
|
+
|
|
422
|
+
customService.shutdown();
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it('should use default timeout when not specified', () => {
|
|
426
|
+
const defaultService = new ApprovalService();
|
|
427
|
+
|
|
428
|
+
const request = defaultService.createApprovalRequest('Duck', 'server', 'tool', {});
|
|
429
|
+
|
|
430
|
+
// Default is 300 seconds (5 minutes)
|
|
431
|
+
jest.advanceTimersByTime(299000);
|
|
432
|
+
expect(defaultService.getApprovalStatus(request.id)).toBe('pending');
|
|
433
|
+
|
|
434
|
+
jest.advanceTimersByTime(2000);
|
|
435
|
+
expect(defaultService.getApprovalStatus(request.id)).toBe('expired');
|
|
436
|
+
|
|
437
|
+
defaultService.shutdown();
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
});
|