@ebowwa/claude-code-mcp 1.0.0 → 1.0.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.
Files changed (48) hide show
  1. package/dist/__tests__/advanced.test.d.ts +6 -0
  2. package/dist/__tests__/advanced.test.d.ts.map +1 -0
  3. package/dist/__tests__/advanced.test.js +354 -0
  4. package/dist/__tests__/advanced.test.js.map +1 -0
  5. package/dist/advanced.d.ts +109 -0
  6. package/dist/advanced.d.ts.map +1 -0
  7. package/dist/advanced.js +427 -0
  8. package/dist/advanced.js.map +1 -0
  9. package/dist/cli-wrapper.d.ts +202 -0
  10. package/dist/cli-wrapper.d.ts.map +1 -0
  11. package/dist/cli-wrapper.js +347 -0
  12. package/dist/cli-wrapper.js.map +1 -0
  13. package/dist/cli-wrapper.test.d.ts +12 -0
  14. package/dist/cli-wrapper.test.d.ts.map +1 -0
  15. package/dist/cli-wrapper.test.js +354 -0
  16. package/dist/cli-wrapper.test.js.map +1 -0
  17. package/dist/cli.d.ts +16 -0
  18. package/dist/cli.d.ts.map +1 -0
  19. package/dist/cli.js +354 -0
  20. package/dist/cli.js.map +1 -0
  21. package/dist/index.d.ts +10 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +561 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/integration.test.d.ts +12 -0
  26. package/dist/integration.test.d.ts.map +1 -0
  27. package/dist/integration.test.js +716 -0
  28. package/dist/integration.test.js.map +1 -0
  29. package/dist/queue.d.ts +87 -0
  30. package/dist/queue.d.ts.map +1 -0
  31. package/dist/queue.js +273 -0
  32. package/dist/queue.js.map +1 -0
  33. package/dist/teammates-integration.d.ts +128 -0
  34. package/dist/teammates-integration.d.ts.map +1 -0
  35. package/dist/teammates-integration.js +353 -0
  36. package/dist/teammates-integration.js.map +1 -0
  37. package/dist/test-config.d.ts +104 -0
  38. package/dist/test-config.d.ts.map +1 -0
  39. package/dist/test-config.js +439 -0
  40. package/dist/test-config.js.map +1 -0
  41. package/dist/tools.d.ts +97 -0
  42. package/dist/tools.d.ts.map +1 -0
  43. package/dist/tools.js +627 -0
  44. package/dist/tools.js.map +1 -0
  45. package/package.json +7 -1
  46. package/ARCHITECTURE.md +0 -1802
  47. package/DOCUMENTATION.md +0 -747
  48. package/TESTING.md +0 -318
@@ -0,0 +1,716 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Integration Tests with Mock Claude Binary
4
+ *
5
+ * Tests the full MCP server integration with:
6
+ * - Mock claude binary for predictable testing
7
+ * - Full tool call lifecycle
8
+ * - Session state management
9
+ * - Doppler integration simulation
10
+ */
11
+ import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
12
+ // Mock Claude binary script - creates a temporary executable
13
+ async function createMockClaude() {
14
+ const mockPath = `/tmp/mock-claude-${Date.now()}.sh`;
15
+ await Bun.write(mockPath, `#!/bin/bash
16
+ # Mock Claude binary for testing
17
+
18
+ echo "Mock Claude CLI v1.0.0" >&2
19
+
20
+ # Parse arguments
21
+ case "$1" in
22
+ --version)
23
+ echo "claude version 1.0.0"
24
+ exit 0
25
+ ;;
26
+ --help)
27
+ echo "Usage: claude [options]"
28
+ exit 0
29
+ ;;
30
+ --resume)
31
+ # Mock resume behavior
32
+ UUID="$2"
33
+ echo "Resuming session: $UUID" >&2
34
+ # Simulate session work
35
+ sleep 0.1
36
+ echo '{"status":"resumed","sessionId":"'"$UUID"'"}'
37
+ exit 0
38
+ ;;
39
+ *)
40
+ # Default: simulate a new session
41
+ echo "Starting new session..." >&2
42
+ sleep 0.1
43
+ echo '{"status":"started","sessionId":"mock-uuid-'"$(date +%s)"'"}'
44
+ exit 0
45
+ ;;
46
+ esac
47
+ `);
48
+ // Make executable
49
+ await Bun.spawn(['chmod', '+x', mockPath]).exited;
50
+ return mockPath;
51
+ }
52
+ describe('MCP Server Integration', () => {
53
+ let mockClaudePath;
54
+ let server;
55
+ beforeEach(async () => {
56
+ mockClaudePath = await createMockClaude();
57
+ // Mock MCP server
58
+ server = {
59
+ tools: new Map(),
60
+ sessions: new Map(),
61
+ registerTool(name, handler) {
62
+ this.tools.set(name, handler);
63
+ },
64
+ async callTool(name, args) {
65
+ const handler = this.tools.get(name);
66
+ if (!handler) {
67
+ throw new Error(`Tool not found: ${name}`);
68
+ }
69
+ return await handler.call(this, args);
70
+ }
71
+ };
72
+ });
73
+ afterEach(async () => {
74
+ // Cleanup mock binary
75
+ try {
76
+ await Bun.spawn(['rm', '-f', mockClaudePath]).exited;
77
+ }
78
+ catch { }
79
+ });
80
+ describe('start_session', () => {
81
+ it('should start a new session', async () => {
82
+ server.registerTool('start_session', async function (args) {
83
+ const { projectPath, message } = args;
84
+ // Mock spawning Claude
85
+ const sessionId = `test-session-${Date.now()}`;
86
+ const process = await Bun.spawn([
87
+ mockClaudePath,
88
+ projectPath || '/tmp/test'
89
+ ]);
90
+ this.sessions.set(sessionId, {
91
+ id: sessionId,
92
+ projectPath,
93
+ status: 'running',
94
+ pid: process.pid,
95
+ startTime: Date.now()
96
+ });
97
+ return {
98
+ content: [{
99
+ type: 'text',
100
+ text: JSON.stringify({
101
+ sessionId,
102
+ status: 'started',
103
+ pid: process.pid
104
+ })
105
+ }]
106
+ };
107
+ });
108
+ const result = await server.callTool('start_session', {
109
+ projectPath: '/tmp/test-project',
110
+ message: 'Hello Claude'
111
+ });
112
+ const data = JSON.parse(result.content[0].text);
113
+ expect(data.status).toBe('started');
114
+ expect(data.sessionId).toBeDefined();
115
+ expect(server.sessions.size).toBe(1);
116
+ });
117
+ it('should handle missing projectPath', async () => {
118
+ server.registerTool('start_session', async function (args) {
119
+ if (!args.projectPath) {
120
+ return {
121
+ content: [{
122
+ type: 'text',
123
+ text: JSON.stringify({
124
+ error: 'projectPath is required'
125
+ })
126
+ }],
127
+ isError: true
128
+ };
129
+ }
130
+ // ... rest of implementation
131
+ });
132
+ const result = await server.callTool('start_session', {
133
+ message: 'Hello'
134
+ });
135
+ expect(result.isError).toBe(true);
136
+ const data = JSON.parse(result.content[0].text);
137
+ expect(data.error).toContain('projectPath');
138
+ });
139
+ });
140
+ describe('resume_session', () => {
141
+ it('should resume an existing session', async () => {
142
+ server.registerTool('resume_session', async function (args) {
143
+ const { sessionId, message, options } = args;
144
+ // Check if useDoppler is enabled
145
+ const useDoppler = options?.useDoppler ?? true;
146
+ if (useDoppler) {
147
+ // Mock doppler-wrapped resume
148
+ const process = await Bun.spawn([
149
+ 'bash',
150
+ '-c',
151
+ `${mockClaudePath} --resume ${sessionId}`
152
+ ]);
153
+ return {
154
+ content: [{
155
+ type: 'text',
156
+ text: JSON.stringify({
157
+ sessionId,
158
+ status: 'resumed',
159
+ dopplerWrapped: true,
160
+ pid: process.pid
161
+ })
162
+ }]
163
+ };
164
+ }
165
+ // Direct resume without doppler
166
+ return {
167
+ content: [{
168
+ type: 'text',
169
+ text: JSON.stringify({
170
+ sessionId,
171
+ status: 'resumed',
172
+ dopplerWrapped: false
173
+ })
174
+ }]
175
+ };
176
+ });
177
+ const result = await server.callTool('resume_session', {
178
+ sessionId: 'test-uuid-123',
179
+ message: 'Continue working',
180
+ options: { useDoppler: true }
181
+ });
182
+ const data = JSON.parse(result.content[0].text);
183
+ expect(data.status).toBe('resumed');
184
+ expect(data.dopplerWrapped).toBe(true);
185
+ });
186
+ it('should handle invalid sessionId', async () => {
187
+ server.registerTool('resume_session', async function (args) {
188
+ const { sessionId } = args;
189
+ if (!sessionId || sessionId.length < 10) {
190
+ return {
191
+ content: [{
192
+ type: 'text',
193
+ text: JSON.stringify({
194
+ error: 'Invalid sessionId format'
195
+ })
196
+ }],
197
+ isError: true
198
+ };
199
+ }
200
+ // ... resume logic
201
+ });
202
+ const result = await server.callTool('resume_session', {
203
+ sessionId: 'short'
204
+ });
205
+ expect(result.isError).toBe(true);
206
+ });
207
+ });
208
+ describe('list_sessions', () => {
209
+ beforeEach(() => {
210
+ // Add some mock sessions
211
+ server.sessions.set('session-1', {
212
+ id: 'session-1',
213
+ projectPath: '/path/to/project1',
214
+ status: 'running',
215
+ startTime: Date.now() - 3600000
216
+ });
217
+ server.sessions.set('session-2', {
218
+ id: 'session-2',
219
+ projectPath: '/path/to/project2',
220
+ status: 'completed',
221
+ startTime: Date.now() - 7200000
222
+ });
223
+ });
224
+ it('should list all sessions', async () => {
225
+ server.registerTool('list_sessions', async function (args) {
226
+ const { status, limit } = args;
227
+ let sessions = Array.from(this.sessions.values());
228
+ if (status && status !== 'all') {
229
+ sessions = sessions.filter((s) => s.status === status);
230
+ }
231
+ if (limit) {
232
+ sessions = sessions.slice(0, limit);
233
+ }
234
+ return {
235
+ content: [{
236
+ type: 'text',
237
+ text: JSON.stringify({
238
+ sessions,
239
+ count: sessions.length
240
+ })
241
+ }]
242
+ };
243
+ });
244
+ const result = await server.callTool('list_sessions', {
245
+ status: 'all'
246
+ });
247
+ const data = JSON.parse(result.content[0].text);
248
+ expect(data.count).toBe(2);
249
+ expect(data.sessions).toHaveLength(2);
250
+ });
251
+ it('should filter by status', async () => {
252
+ server.registerTool('list_sessions', async function (args) {
253
+ const { status } = args;
254
+ let sessions = Array.from(this.sessions.values());
255
+ if (status && status !== 'all') {
256
+ sessions = sessions.filter((s) => s.status === status);
257
+ }
258
+ return {
259
+ content: [{
260
+ type: 'text',
261
+ text: JSON.stringify({ sessions })
262
+ }]
263
+ };
264
+ });
265
+ const result = await server.callTool('list_sessions', {
266
+ status: 'running'
267
+ });
268
+ const data = JSON.parse(result.content[0].text);
269
+ expect(data.sessions).toHaveLength(1);
270
+ expect(data.sessions[0].id).toBe('session-1');
271
+ });
272
+ it('should apply limit', async () => {
273
+ server.registerTool('list_sessions', async function (args) {
274
+ const { limit } = args;
275
+ let sessions = Array.from(this.sessions.values());
276
+ if (limit) {
277
+ sessions = sessions.slice(0, limit);
278
+ }
279
+ return {
280
+ content: [{
281
+ type: 'text',
282
+ text: JSON.stringify({ sessions })
283
+ }]
284
+ };
285
+ });
286
+ const result = await server.callTool('list_sessions', {
287
+ limit: 1
288
+ });
289
+ const data = JSON.parse(result.content[0].text);
290
+ expect(data.sessions).toHaveLength(1);
291
+ });
292
+ });
293
+ describe('kill_session', () => {
294
+ beforeEach(() => {
295
+ server.sessions.set('session-to-kill', {
296
+ id: 'session-to-kill',
297
+ status: 'running',
298
+ pid: 12345
299
+ });
300
+ });
301
+ it('should kill a running session', async () => {
302
+ server.registerTool('kill_session', async function (args) {
303
+ const { sessionId, force } = args;
304
+ const session = this.sessions.get(sessionId);
305
+ if (!session) {
306
+ return {
307
+ content: [{
308
+ type: 'text',
309
+ text: JSON.stringify({ error: 'Session not found' })
310
+ }],
311
+ isError: true
312
+ };
313
+ }
314
+ // Mock kill
315
+ session.status = 'killed';
316
+ session.killedAt = Date.now();
317
+ return {
318
+ content: [{
319
+ type: 'text',
320
+ text: JSON.stringify({
321
+ sessionId,
322
+ status: 'killed',
323
+ force: force || false
324
+ })
325
+ }]
326
+ };
327
+ });
328
+ const result = await server.callTool('kill_session', {
329
+ sessionId: 'session-to-kill'
330
+ });
331
+ const data = JSON.parse(result.content[0].text);
332
+ expect(data.status).toBe('killed');
333
+ });
334
+ it('should return error for non-existent session', async () => {
335
+ server.registerTool('kill_session', async function (args) {
336
+ const { sessionId } = args;
337
+ if (!this.sessions.has(sessionId)) {
338
+ return {
339
+ content: [{
340
+ type: 'text',
341
+ text: JSON.stringify({ error: 'Session not found' })
342
+ }],
343
+ isError: true
344
+ };
345
+ }
346
+ });
347
+ const result = await server.callTool('kill_session', {
348
+ sessionId: 'non-existent'
349
+ });
350
+ expect(result.isError).toBe(true);
351
+ });
352
+ });
353
+ describe('send_message', () => {
354
+ beforeEach(() => {
355
+ server.sessions.set('active-session', {
356
+ id: 'active-session',
357
+ status: 'running',
358
+ messages: []
359
+ });
360
+ });
361
+ it('should send message to active session', async () => {
362
+ server.registerTool('send_message', async function (args) {
363
+ const { sessionId, message } = args;
364
+ const session = this.sessions.get(sessionId);
365
+ if (!session) {
366
+ return {
367
+ content: [{
368
+ type: 'text',
369
+ text: JSON.stringify({ error: 'Session not found' })
370
+ }],
371
+ isError: true
372
+ };
373
+ }
374
+ if (session.status !== 'running') {
375
+ return {
376
+ content: [{
377
+ type: 'text',
378
+ text: JSON.stringify({ error: 'Session is not running' })
379
+ }],
380
+ isError: true
381
+ };
382
+ }
383
+ session.messages.push({
384
+ content: message,
385
+ timestamp: Date.now()
386
+ });
387
+ return {
388
+ content: [{
389
+ type: 'text',
390
+ text: JSON.stringify({
391
+ sessionId,
392
+ messageSent: true,
393
+ messageCount: session.messages.length
394
+ })
395
+ }]
396
+ };
397
+ });
398
+ const result = await server.callTool('send_message', {
399
+ sessionId: 'active-session',
400
+ message: 'What is the status?'
401
+ });
402
+ const data = JSON.parse(result.content[0].text);
403
+ expect(data.messageSent).toBe(true);
404
+ expect(data.messageCount).toBe(1);
405
+ });
406
+ it('should handle waitForResponse option', async () => {
407
+ server.registerTool('send_message', async function (args) {
408
+ const { sessionId, message, waitForResponse } = args;
409
+ const session = this.sessions.get(sessionId);
410
+ session.messages.push({ content: message, timestamp: Date.now() });
411
+ const response = {
412
+ sessionId,
413
+ messageSent: true
414
+ };
415
+ if (waitForResponse) {
416
+ // Mock waiting for response
417
+ await new Promise(resolve => setTimeout(resolve, 10));
418
+ response.response = {
419
+ content: 'Task completed successfully',
420
+ timestamp: Date.now()
421
+ };
422
+ }
423
+ return {
424
+ content: [{
425
+ type: 'text',
426
+ text: JSON.stringify(response)
427
+ }]
428
+ };
429
+ });
430
+ const result = await server.callTool('send_message', {
431
+ sessionId: 'active-session',
432
+ message: 'Check status',
433
+ waitForResponse: true
434
+ });
435
+ const data = JSON.parse(result.content[0].text);
436
+ expect(data.response).toBeDefined();
437
+ });
438
+ });
439
+ describe('sync_context', () => {
440
+ beforeEach(() => {
441
+ server.sessions.set('source-session', {
442
+ id: 'source-session',
443
+ context: {
444
+ files: ['/path/to/file1.ts'],
445
+ history: ['message1', 'message2']
446
+ }
447
+ });
448
+ server.sessions.set('target-session', {
449
+ id: 'target-session',
450
+ context: {}
451
+ });
452
+ });
453
+ it('should sync context between sessions', async () => {
454
+ server.registerTool('sync_context', async function (args) {
455
+ const { sourceSessionId, targetSessionId, contextType } = args;
456
+ const source = this.sessions.get(sourceSessionId);
457
+ const target = this.sessions.get(targetSessionId);
458
+ if (!source || !target) {
459
+ return {
460
+ content: [{
461
+ type: 'text',
462
+ text: JSON.stringify({ error: 'Session not found' })
463
+ }],
464
+ isError: true
465
+ };
466
+ }
467
+ // Sync based on contextType
468
+ if (contextType === 'all' || contextType === 'history') {
469
+ target.context.history = source.context.history;
470
+ }
471
+ if (contextType === 'all' || contextType === 'files') {
472
+ target.context.files = source.context.files;
473
+ }
474
+ return {
475
+ content: [{
476
+ type: 'text',
477
+ text: JSON.stringify({
478
+ synced: true,
479
+ targetSessionId,
480
+ contextTypes: [contextType]
481
+ })
482
+ }]
483
+ };
484
+ });
485
+ const result = await server.callTool('sync_context', {
486
+ sourceSessionId: 'source-session',
487
+ targetSessionId: 'target-session',
488
+ contextType: 'all'
489
+ });
490
+ const data = JSON.parse(result.content[0].text);
491
+ expect(data.synced).toBe(true);
492
+ const target = server.sessions.get('target-session');
493
+ expect(target.context).toEqual({
494
+ files: ['/path/to/file1.ts'],
495
+ history: ['message1', 'message2']
496
+ });
497
+ });
498
+ it('should apply direct context data', async () => {
499
+ server.registerTool('sync_context', async function (args) {
500
+ const { targetSessionId, contextData } = args;
501
+ const target = this.sessions.get(targetSessionId);
502
+ if (!target) {
503
+ return {
504
+ content: [{
505
+ type: 'text',
506
+ text: JSON.stringify({ error: 'Session not found' })
507
+ }],
508
+ isError: true
509
+ };
510
+ }
511
+ target.context = { ...target.context, ...contextData };
512
+ return {
513
+ content: [{
514
+ type: 'text',
515
+ text: JSON.stringify({
516
+ applied: true,
517
+ targetSessionId
518
+ })
519
+ }]
520
+ };
521
+ });
522
+ const result = await server.callTool('sync_context', {
523
+ targetSessionId: 'target-session',
524
+ contextData: {
525
+ files: ['/new/file.ts'],
526
+ customData: 'test'
527
+ }
528
+ });
529
+ const data = JSON.parse(result.content[0].text);
530
+ expect(data.applied).toBe(true);
531
+ });
532
+ });
533
+ describe('stream_output', () => {
534
+ beforeEach(() => {
535
+ server.sessions.set('streaming-session', {
536
+ id: 'streaming-session',
537
+ output: [
538
+ 'Line 1: Starting task...',
539
+ 'Line 2: Processing data...',
540
+ 'Line 3: Complete!'
541
+ ]
542
+ });
543
+ });
544
+ it('should stream session output', async () => {
545
+ server.registerTool('stream_output', async function (args) {
546
+ const { sessionId, lines } = args;
547
+ const session = this.sessions.get(sessionId);
548
+ if (!session) {
549
+ return {
550
+ content: [{
551
+ type: 'text',
552
+ text: JSON.stringify({ error: 'Session not found' })
553
+ }],
554
+ isError: true
555
+ };
556
+ }
557
+ let output = session.output || [];
558
+ if (lines && lines > 0) {
559
+ output = output.slice(-lines);
560
+ }
561
+ return {
562
+ content: [{
563
+ type: 'text',
564
+ text: JSON.stringify({
565
+ sessionId,
566
+ output,
567
+ lineCount: output.length
568
+ })
569
+ }]
570
+ };
571
+ });
572
+ const result = await server.callTool('stream_output', {
573
+ sessionId: 'streaming-session',
574
+ lines: 2
575
+ });
576
+ const data = JSON.parse(result.content[0].text);
577
+ expect(data.lineCount).toBe(2);
578
+ expect(data.output).toEqual([
579
+ 'Line 2: Processing data...',
580
+ 'Line 3: Complete!'
581
+ ]);
582
+ });
583
+ it('should return all output when lines is 0', async () => {
584
+ server.registerTool('stream_output', async function (args) {
585
+ const { sessionId, lines } = args;
586
+ const session = this.sessions.get(sessionId);
587
+ let output = session.output || [];
588
+ if (lines > 0) {
589
+ output = output.slice(-lines);
590
+ }
591
+ return {
592
+ content: [{
593
+ type: 'text',
594
+ text: JSON.stringify({ output })
595
+ }]
596
+ };
597
+ });
598
+ const result = await server.callTool('stream_output', {
599
+ sessionId: 'streaming-session',
600
+ lines: 0
601
+ });
602
+ const data = JSON.parse(result.content[0].text);
603
+ expect(data.output).toHaveLength(3);
604
+ });
605
+ });
606
+ describe('wait_for_completion', () => {
607
+ it('should wait for session completion', async () => {
608
+ let sessionCompleted = false;
609
+ server.registerTool('wait_for_completion', async function (args) {
610
+ const { sessionId, timeout, pollInterval } = args;
611
+ const startTime = Date.now();
612
+ // Simulate polling
613
+ while (Date.now() - startTime < (timeout || 300000)) {
614
+ await new Promise(resolve => setTimeout(resolve, pollInterval || 1000));
615
+ // Check if session completed (mock)
616
+ if (sessionCompleted) {
617
+ return {
618
+ content: [{
619
+ type: 'text',
620
+ text: JSON.stringify({
621
+ sessionId,
622
+ status: 'completed',
623
+ duration: Date.now() - startTime
624
+ })
625
+ }]
626
+ };
627
+ }
628
+ }
629
+ return {
630
+ content: [{
631
+ type: 'text',
632
+ text: JSON.stringify({
633
+ sessionId,
634
+ status: 'timeout'
635
+ })
636
+ }]
637
+ };
638
+ });
639
+ // Complete session after 50ms
640
+ setTimeout(() => { sessionCompleted = true; }, 50);
641
+ const result = await server.callTool('wait_for_completion', {
642
+ sessionId: 'waiting-session',
643
+ timeout: 5000,
644
+ pollInterval: 10
645
+ });
646
+ const data = JSON.parse(result.content[0].text);
647
+ expect(data.status).toBe('completed');
648
+ expect(data.duration).toBeGreaterThan(0);
649
+ });
650
+ it('should timeout after specified duration', async () => {
651
+ server.registerTool('wait_for_completion', async function (args) {
652
+ const { timeout } = args;
653
+ const startTime = Date.now();
654
+ // Simulate timeout
655
+ await new Promise(resolve => setTimeout(resolve, timeout || 100));
656
+ return {
657
+ content: [{
658
+ type: 'text',
659
+ text: JSON.stringify({
660
+ status: 'timeout',
661
+ duration: Date.now() - startTime
662
+ })
663
+ }]
664
+ };
665
+ });
666
+ const result = await server.callTool('wait_for_completion', {
667
+ sessionId: 'slow-session',
668
+ timeout: 50
669
+ });
670
+ const data = JSON.parse(result.content[0].text);
671
+ expect(data.status).toBe('timeout');
672
+ });
673
+ });
674
+ });
675
+ describe('Doppler Integration Tests', () => {
676
+ describe('Doppler Environment Setup', () => {
677
+ it('should verify DOPPLER_TOKEN is available', async () => {
678
+ // Mock environment check
679
+ const hasToken = process.env.DOPPLER_TOKEN !== undefined;
680
+ expect(typeof hasToken).toBe('boolean');
681
+ });
682
+ it('should construct doppler run command correctly', () => {
683
+ const buildDopplerCommand = (project, config, command) => {
684
+ return ['doppler', 'run', '--project', project, '--config', config, '--command', command];
685
+ };
686
+ const cmd = buildDopplerCommand('test-project', 'dev', 'claude --resume test-uuid');
687
+ expect(cmd).toContain('doppler');
688
+ expect(cmd).toContain('--project');
689
+ expect(cmd).toContain('test-project');
690
+ expect(cmd).toContain('--config');
691
+ expect(cmd).toContain('dev');
692
+ expect(cmd).toContain('--command');
693
+ });
694
+ });
695
+ describe('Rolling Keys with Doppler', () => {
696
+ it('should handle ANTHROPIC_API_KEYS array format', () => {
697
+ const mockKeys = '["key1","key2","key3"]';
698
+ const parsed = JSON.parse(mockKeys);
699
+ expect(Array.isArray(parsed)).toBe(true);
700
+ expect(parsed).toHaveLength(3);
701
+ });
702
+ it('should select first available key', () => {
703
+ const keys = ['key1', 'key2', 'key3'];
704
+ const selectedKey = keys[0];
705
+ expect(selectedKey).toBe('key1');
706
+ });
707
+ it('should fallback to ANTHROPIC_AUTH_TOKEN if API_KEY missing', () => {
708
+ const fallback = (apiKey, authToken) => {
709
+ return apiKey || authToken;
710
+ };
711
+ expect(fallback('', 'auth-token-value')).toBe('auth-token-value');
712
+ expect(fallback('api-key-value', 'auth-token-value')).toBe('api-key-value');
713
+ });
714
+ });
715
+ });
716
+ //# sourceMappingURL=integration.test.js.map