@ronkovic/aad 0.4.0 → 0.5.1
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 +42 -4
- package/package.json +1 -1
- package/src/__tests__/e2e/cleanup-e2e.test.ts +186 -0
- package/src/__tests__/e2e/dashboard-api-e2e.test.ts +87 -0
- package/src/__tests__/e2e/pipeline-e2e.test.ts +10 -69
- package/src/__tests__/e2e/resume-e2e.test.ts +7 -11
- package/src/__tests__/e2e/retry-e2e.test.ts +285 -0
- package/src/__tests__/e2e/status-e2e.test.ts +227 -0
- package/src/__tests__/e2e/tdd-pipeline-e2e.test.ts +360 -0
- package/src/__tests__/helpers/index.ts +6 -0
- package/src/__tests__/helpers/mock-claude-provider.ts +53 -0
- package/src/__tests__/helpers/mock-logger.ts +36 -0
- package/src/__tests__/helpers/wait-helpers.ts +34 -0
- package/src/__tests__/integration/pipeline.test.ts +2 -0
- package/src/modules/claude-provider/__tests__/claude-sdk.adapter.test.ts +79 -0
- package/src/modules/claude-provider/__tests__/provider-registry.test.ts +2 -0
- package/src/modules/cli/__tests__/cleanup.test.ts +1 -0
- package/src/modules/cli/__tests__/resume.test.ts +3 -0
- package/src/modules/cli/__tests__/run.test.ts +36 -0
- package/src/modules/cli/__tests__/status.test.ts +1 -0
- package/src/modules/cli/app.ts +2 -0
- package/src/modules/cli/commands/resume.ts +11 -6
- package/src/modules/cli/commands/run.ts +14 -2
- package/src/modules/dashboard/ui/dashboard.html +640 -474
- package/src/modules/planning/__tests__/planning-service.test.ts +2 -0
- package/src/modules/process-manager/__tests__/process-manager.test.ts +2 -0
- package/src/modules/process-manager/process-manager.ts +2 -1
- package/src/modules/task-execution/__tests__/executor.test.ts +420 -10
- package/src/modules/task-execution/executor.ts +76 -0
- package/src/modules/task-queue/dispatcher.ts +46 -2
- package/src/shared/__tests__/config.test.ts +30 -0
- package/src/shared/__tests__/events.test.ts +42 -16
- package/src/shared/__tests__/shutdown-handler.test.ts +96 -0
- package/src/shared/config.ts +4 -0
- package/src/shared/events.ts +5 -0
- package/src/shared/memory-check.ts +2 -2
- package/src/shared/shutdown-handler.ts +12 -5
- package/src/shared/types.ts +12 -0
- package/src/modules/claude-provider/__tests__/claude-sdk-real-env.test.ts +0 -127
|
@@ -53,6 +53,7 @@ export class Dispatcher {
|
|
|
53
53
|
private taskMap: Map<string, Task> = new Map();
|
|
54
54
|
private initialized = false;
|
|
55
55
|
private runId?: import("@aad/shared/types").RunId;
|
|
56
|
+
private skippedCount = 0;
|
|
56
57
|
|
|
57
58
|
constructor(deps: DispatcherDeps) {
|
|
58
59
|
this.deps = deps;
|
|
@@ -112,6 +113,10 @@ export class Dispatcher {
|
|
|
112
113
|
// Listen to task:completed events
|
|
113
114
|
this.deps.eventBus.on("task:completed", (event) => {
|
|
114
115
|
if (event.type === "task:completed") {
|
|
116
|
+
// Track skipped tasks
|
|
117
|
+
if (event.result.skipped) {
|
|
118
|
+
this.skippedCount++;
|
|
119
|
+
}
|
|
115
120
|
void this.handleTaskCompleted(event.taskId);
|
|
116
121
|
}
|
|
117
122
|
});
|
|
@@ -337,14 +342,23 @@ export class Dispatcher {
|
|
|
337
342
|
// Emit run:completed if all tasks are done (FIX #1)
|
|
338
343
|
if (progress.pending === 0 && progress.running === 0) {
|
|
339
344
|
if (this.runId) {
|
|
345
|
+
const metrics = this.getRunMetrics();
|
|
346
|
+
|
|
340
347
|
this.deps.eventBus.emit({
|
|
341
348
|
type: "run:completed",
|
|
342
349
|
runId: this.runId,
|
|
343
350
|
});
|
|
344
351
|
|
|
345
352
|
this.deps.logger.info(
|
|
346
|
-
{
|
|
347
|
-
|
|
353
|
+
{
|
|
354
|
+
runId: this.runId,
|
|
355
|
+
completed: progress.completed,
|
|
356
|
+
failed: progress.failed,
|
|
357
|
+
skipped: metrics.skippedTasks,
|
|
358
|
+
totalDuration: metrics.totalDuration,
|
|
359
|
+
averageTaskDuration: Math.round(metrics.averageTaskDuration),
|
|
360
|
+
},
|
|
361
|
+
"Run completed with metrics"
|
|
348
362
|
);
|
|
349
363
|
}
|
|
350
364
|
}
|
|
@@ -424,4 +438,34 @@ export class Dispatcher {
|
|
|
424
438
|
return undefined;
|
|
425
439
|
}
|
|
426
440
|
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Calculate run metrics including skipped tasks
|
|
444
|
+
*/
|
|
445
|
+
getRunMetrics(): import("@aad/shared/types").RunMetrics {
|
|
446
|
+
const tasks = Array.from(this.taskMap.values());
|
|
447
|
+
const completedTasks = tasks.filter((t) => t.status === "completed");
|
|
448
|
+
const failedTasks = tasks.filter((t) => t.status === "failed");
|
|
449
|
+
|
|
450
|
+
const totalDuration = completedTasks.reduce((sum, task) => {
|
|
451
|
+
if (task.startTime && task.endTime) {
|
|
452
|
+
const start = new Date(task.startTime).getTime();
|
|
453
|
+
const end = new Date(task.endTime).getTime();
|
|
454
|
+
return sum + (end - start);
|
|
455
|
+
}
|
|
456
|
+
return sum;
|
|
457
|
+
}, 0);
|
|
458
|
+
|
|
459
|
+
const averageTaskDuration =
|
|
460
|
+
completedTasks.length > 0 ? totalDuration / completedTasks.length : 0;
|
|
461
|
+
|
|
462
|
+
return {
|
|
463
|
+
totalTasks: tasks.length,
|
|
464
|
+
completedTasks: completedTasks.length,
|
|
465
|
+
failedTasks: failedTasks.length,
|
|
466
|
+
skippedTasks: this.skippedCount,
|
|
467
|
+
totalDuration,
|
|
468
|
+
averageTaskDuration,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
427
471
|
}
|
|
@@ -201,4 +201,34 @@ describe("loadConfig", () => {
|
|
|
201
201
|
const config = loadConfig({});
|
|
202
202
|
expect(config.plugins).toBeUndefined();
|
|
203
203
|
});
|
|
204
|
+
|
|
205
|
+
test("loads skipCompleted from env (default true)", () => {
|
|
206
|
+
const config = loadConfig({});
|
|
207
|
+
expect(config.skipCompleted).toBe(true);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("loads skipCompleted from env (explicit false)", () => {
|
|
211
|
+
const config = loadConfig({ AAD_SKIP_COMPLETED: "0" });
|
|
212
|
+
expect(config.skipCompleted).toBe(false);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("loads skipCompleted from env (explicit true)", () => {
|
|
216
|
+
const config = loadConfig({ AAD_SKIP_COMPLETED: "1" });
|
|
217
|
+
expect(config.skipCompleted).toBe(true);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("loads strictTdd from env (default false)", () => {
|
|
221
|
+
const config = loadConfig({});
|
|
222
|
+
expect(config.strictTdd).toBe(false);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("loads strictTdd from env (explicit true)", () => {
|
|
226
|
+
const config = loadConfig({ AAD_STRICT_TDD: "1" });
|
|
227
|
+
expect(config.strictTdd).toBe(true);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("loads strictTdd from env (explicit false)", () => {
|
|
231
|
+
const config = loadConfig({ AAD_STRICT_TDD: "0" });
|
|
232
|
+
expect(config.strictTdd).toBe(false);
|
|
233
|
+
});
|
|
204
234
|
});
|
|
@@ -186,22 +186,31 @@ describe("EventBus", () => {
|
|
|
186
186
|
test("listener exception does not affect other listeners", () => {
|
|
187
187
|
let received: AADEvent | undefined;
|
|
188
188
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
189
|
+
// Mock console.error to suppress error output
|
|
190
|
+
const originalConsoleError = console.error;
|
|
191
|
+
console.error = () => {};
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
eventBus.on("worker:idle", () => {
|
|
195
|
+
throw new Error("Listener error");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
eventBus.on("worker:idle", (event) => {
|
|
199
|
+
received = event;
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const event: AADEvent = {
|
|
203
|
+
type: "worker:idle",
|
|
204
|
+
workerId: createWorkerId("w1"),
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
eventBus.emit(event);
|
|
208
|
+
|
|
209
|
+
expect(received).toEqual(event);
|
|
210
|
+
} finally {
|
|
211
|
+
// Restore console.error
|
|
212
|
+
console.error = originalConsoleError;
|
|
213
|
+
}
|
|
205
214
|
});
|
|
206
215
|
|
|
207
216
|
test("emits wildcard * for all events", () => {
|
|
@@ -493,4 +502,21 @@ describe("EventBus", () => {
|
|
|
493
502
|
|
|
494
503
|
expect(received).toEqual(event);
|
|
495
504
|
});
|
|
505
|
+
|
|
506
|
+
test("emits and receives execution:skipped event", () => {
|
|
507
|
+
let received: AADEvent | undefined;
|
|
508
|
+
eventBus.on("execution:skipped", (event) => {
|
|
509
|
+
received = event;
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
const event: AADEvent = {
|
|
513
|
+
type: "execution:skipped",
|
|
514
|
+
taskId: createTaskId("t1"),
|
|
515
|
+
reason: "Tests already pass",
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
eventBus.emit(event);
|
|
519
|
+
|
|
520
|
+
expect(received).toEqual(event);
|
|
521
|
+
});
|
|
496
522
|
});
|
|
@@ -135,4 +135,100 @@ describe("shutdown-handler", () => {
|
|
|
135
135
|
// exitFn called only once due to debounce
|
|
136
136
|
expect(exitFn).toHaveBeenCalledTimes(1);
|
|
137
137
|
});
|
|
138
|
+
|
|
139
|
+
test("displays resume command example when using fs persistence", async () => {
|
|
140
|
+
const tasks = [makeTask("t1", "running")];
|
|
141
|
+
const runState: RunState = {
|
|
142
|
+
runId,
|
|
143
|
+
parentBranch: "main",
|
|
144
|
+
totalTasks: 1,
|
|
145
|
+
pending: 0,
|
|
146
|
+
running: 1,
|
|
147
|
+
completed: 0,
|
|
148
|
+
failed: 0,
|
|
149
|
+
startTime: new Date().toISOString(),
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const exitFn = mock(() => {});
|
|
153
|
+
const stores = createMockStores(tasks, runState);
|
|
154
|
+
const logger = createMockLogger();
|
|
155
|
+
|
|
156
|
+
// Capture console output
|
|
157
|
+
const consoleLogSpy = mock(() => {});
|
|
158
|
+
const originalLog = console.log;
|
|
159
|
+
console.log = consoleLogSpy;
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
installShutdownHandler({
|
|
163
|
+
runId,
|
|
164
|
+
stores: { runStore: stores.runStore, taskStore: stores.taskStore },
|
|
165
|
+
logger,
|
|
166
|
+
exitFn,
|
|
167
|
+
persistMode: "fs",
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Simulate SIGINT
|
|
171
|
+
process.emit("SIGINT");
|
|
172
|
+
|
|
173
|
+
// Wait for async handler
|
|
174
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
175
|
+
|
|
176
|
+
// Verify resume command was logged
|
|
177
|
+
const calls = (consoleLogSpy as any).mock.calls as string[][];
|
|
178
|
+
const resumeLog = calls.find((args) =>
|
|
179
|
+
args.some((arg) => typeof arg === "string" && arg.includes("aad resume"))
|
|
180
|
+
);
|
|
181
|
+
expect(resumeLog).toBeDefined();
|
|
182
|
+
} finally {
|
|
183
|
+
console.log = originalLog;
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("does not display resume command when using memory persistence", async () => {
|
|
188
|
+
const tasks = [makeTask("t1", "running")];
|
|
189
|
+
const runState: RunState = {
|
|
190
|
+
runId,
|
|
191
|
+
parentBranch: "main",
|
|
192
|
+
totalTasks: 1,
|
|
193
|
+
pending: 0,
|
|
194
|
+
running: 1,
|
|
195
|
+
completed: 0,
|
|
196
|
+
failed: 0,
|
|
197
|
+
startTime: new Date().toISOString(),
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const exitFn = mock(() => {});
|
|
201
|
+
const stores = createMockStores(tasks, runState);
|
|
202
|
+
const logger = createMockLogger();
|
|
203
|
+
|
|
204
|
+
// Capture console output
|
|
205
|
+
const consoleLogSpy = mock(() => {});
|
|
206
|
+
const originalLog = console.log;
|
|
207
|
+
console.log = consoleLogSpy;
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
installShutdownHandler({
|
|
211
|
+
runId,
|
|
212
|
+
stores: { runStore: stores.runStore, taskStore: stores.taskStore },
|
|
213
|
+
logger,
|
|
214
|
+
exitFn,
|
|
215
|
+
persistMode: "memory",
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Simulate SIGINT
|
|
219
|
+
process.emit("SIGINT");
|
|
220
|
+
|
|
221
|
+
// Wait for async handler
|
|
222
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
223
|
+
|
|
224
|
+
// Verify resume command was NOT logged
|
|
225
|
+
const calls = (consoleLogSpy as any).mock.calls as string[][];
|
|
226
|
+
const resumeLog = calls.find((args) =>
|
|
227
|
+
args.some((arg) => typeof arg === "string" && arg.includes("aad resume"))
|
|
228
|
+
);
|
|
229
|
+
expect(resumeLog).toBeUndefined();
|
|
230
|
+
} finally {
|
|
231
|
+
console.log = originalLog;
|
|
232
|
+
}
|
|
233
|
+
});
|
|
138
234
|
});
|
package/src/shared/config.ts
CHANGED
|
@@ -25,6 +25,8 @@ const configSchema = z.object({
|
|
|
25
25
|
}),
|
|
26
26
|
debug: z.boolean(),
|
|
27
27
|
adaptiveEffort: z.boolean(),
|
|
28
|
+
skipCompleted: z.boolean(),
|
|
29
|
+
strictTdd: z.boolean(),
|
|
28
30
|
teams: z.object({
|
|
29
31
|
splitter: z.boolean(),
|
|
30
32
|
reviewer: z.boolean(),
|
|
@@ -101,6 +103,8 @@ export function loadConfig(env: Record<string, string | undefined> = process.env
|
|
|
101
103
|
},
|
|
102
104
|
debug: parseBoolOrDefault(env.DEBUG, false),
|
|
103
105
|
adaptiveEffort: parseBoolOrDefault(env.AAD_ADAPTIVE_EFFORT, false),
|
|
106
|
+
skipCompleted: parseBoolOrDefault(env.AAD_SKIP_COMPLETED, true),
|
|
107
|
+
strictTdd: parseBoolOrDefault(env.AAD_STRICT_TDD, false),
|
|
104
108
|
teams: {
|
|
105
109
|
splitter: parseBoolOrDefault(env.AAD_USE_TEAMS_FOR_SPLITTER, false),
|
|
106
110
|
reviewer: parseBoolOrDefault(env.AAD_USE_TEAMS_FOR_REVIEW, false),
|
package/src/shared/events.ts
CHANGED
|
@@ -122,6 +122,11 @@ export type AADEvent =
|
|
|
122
122
|
type: "execution:merge:conflict";
|
|
123
123
|
taskId: TaskId;
|
|
124
124
|
conflictedFiles: string[];
|
|
125
|
+
}
|
|
126
|
+
| {
|
|
127
|
+
type: "execution:skipped";
|
|
128
|
+
taskId: TaskId;
|
|
129
|
+
reason: string;
|
|
125
130
|
};
|
|
126
131
|
|
|
127
132
|
export type EventType = AADEvent["type"] | "*";
|
|
@@ -64,8 +64,8 @@ export async function getMemoryStatus(): Promise<MemoryStatus> {
|
|
|
64
64
|
const purgablePages = parseInt(purgableMatch?.[1] ?? "0", 10);
|
|
65
65
|
|
|
66
66
|
// Calculate memory in GB
|
|
67
|
-
const bytesToGB = (bytes: number) => bytes / (1024 * 1024 * 1024);
|
|
68
|
-
const pagesToGB = (pages: number) => bytesToGB(pages * pageSize);
|
|
67
|
+
const bytesToGB = (bytes: number): number => bytes / (1024 * 1024 * 1024);
|
|
68
|
+
const pagesToGB = (pages: number): number => bytesToGB(pages * pageSize);
|
|
69
69
|
|
|
70
70
|
// Available = free + inactive + speculative + purgeable
|
|
71
71
|
// macOS reclaims inactive/purgeable pages on demand — they are effectively free
|
|
@@ -15,6 +15,7 @@ export interface ShutdownHandlerOptions {
|
|
|
15
15
|
};
|
|
16
16
|
logger: pino.Logger;
|
|
17
17
|
exitFn?: (code: number) => void;
|
|
18
|
+
persistMode?: "memory" | "fs";
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
let _shuttingDown = false;
|
|
@@ -56,7 +57,7 @@ export function installShutdownHandler(options: ShutdownHandlerOptions): void {
|
|
|
56
57
|
const { runId, stores, logger, exitFn = (code: number) => process.exit(code) } = options;
|
|
57
58
|
let saving = false;
|
|
58
59
|
|
|
59
|
-
const handleShutdown = async (reason: string) => {
|
|
60
|
+
const handleShutdown = async (reason: string): Promise<void> => {
|
|
60
61
|
const now = Date.now();
|
|
61
62
|
// Debounce: ignore signals within 1s
|
|
62
63
|
if (now - _lastSignalTime < 1000) return;
|
|
@@ -95,7 +96,13 @@ export function installShutdownHandler(options: ShutdownHandlerOptions): void {
|
|
|
95
96
|
});
|
|
96
97
|
}
|
|
97
98
|
|
|
98
|
-
logger.info({ runId, pending, completed, failed }, "State saved.
|
|
99
|
+
logger.info({ runId, pending, completed, failed }, "State saved.");
|
|
100
|
+
|
|
101
|
+
// Display resume command example when using fs persistence
|
|
102
|
+
if (options.persistMode === "fs") {
|
|
103
|
+
console.log(`\n⚠️ Run interrupted. To resume:\n`);
|
|
104
|
+
console.log(` aad resume ${runId}\n`);
|
|
105
|
+
}
|
|
99
106
|
} catch (err) {
|
|
100
107
|
logger.error({ err }, "Failed to save state during shutdown");
|
|
101
108
|
}
|
|
@@ -103,9 +110,9 @@ export function installShutdownHandler(options: ShutdownHandlerOptions): void {
|
|
|
103
110
|
exitFn(1);
|
|
104
111
|
};
|
|
105
112
|
|
|
106
|
-
const onSigterm = () => { void handleShutdown("SIGTERM"); };
|
|
107
|
-
const onSigint = () => { void handleShutdown("SIGINT"); };
|
|
108
|
-
const onUncaught = (err: Error) => { void handleShutdown(`uncaughtException: ${err.message}`); };
|
|
113
|
+
const onSigterm = (): void => { void handleShutdown("SIGTERM"); };
|
|
114
|
+
const onSigint = (): void => { void handleShutdown("SIGINT"); };
|
|
115
|
+
const onUncaught = (err: Error): void => { void handleShutdown(`uncaughtException: ${err.message}`); };
|
|
109
116
|
|
|
110
117
|
process.on("SIGTERM", onSigterm);
|
|
111
118
|
process.on("SIGINT", onSigint);
|
package/src/shared/types.ts
CHANGED
|
@@ -54,6 +54,8 @@ export interface TaskExecutionResult {
|
|
|
54
54
|
duration: number;
|
|
55
55
|
output?: string;
|
|
56
56
|
error?: string;
|
|
57
|
+
skipped?: boolean;
|
|
58
|
+
phasesExecuted?: string[];
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
// RepoName branded type
|
|
@@ -110,6 +112,16 @@ export interface RunState {
|
|
|
110
112
|
endTime?: string;
|
|
111
113
|
}
|
|
112
114
|
|
|
115
|
+
// Run Metrics
|
|
116
|
+
export interface RunMetrics {
|
|
117
|
+
totalTasks: number;
|
|
118
|
+
completedTasks: number;
|
|
119
|
+
failedTasks: number;
|
|
120
|
+
skippedTasks: number;
|
|
121
|
+
totalDuration: number;
|
|
122
|
+
averageTaskDuration: number;
|
|
123
|
+
}
|
|
124
|
+
|
|
113
125
|
// Task Plan
|
|
114
126
|
export interface TaskPlan {
|
|
115
127
|
runId: RunId;
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from "bun:test";
|
|
2
|
-
import { ClaudeSdkAdapter } from "../claude-sdk.adapter";
|
|
3
|
-
import { loadConfig } from "../../../shared/config";
|
|
4
|
-
import pino from "pino";
|
|
5
|
-
import { ClaudeProviderError } from "../../../shared/errors";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* 実環境バリデーションテスト
|
|
9
|
-
* ANTHROPIC_API_KEY または CLAUDE_CODE_OAUTH_TOKEN が設定されている場合のみ実行
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
const hasApiKey = !!process.env.ANTHROPIC_API_KEY;
|
|
13
|
-
const hasOAuth = !!process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
14
|
-
const hasAuth = hasApiKey || hasOAuth;
|
|
15
|
-
|
|
16
|
-
const logger = pino({ level: "silent" });
|
|
17
|
-
|
|
18
|
-
function createAdapter() {
|
|
19
|
-
const config = loadConfig();
|
|
20
|
-
return new ClaudeSdkAdapter(config, logger);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
describe.skipIf(!hasAuth)("ClaudeSdkAdapter - Real Environment", () => {
|
|
24
|
-
test("simple query returns valid response", async () => {
|
|
25
|
-
const adapter = createAdapter();
|
|
26
|
-
const response = await adapter.call({
|
|
27
|
-
prompt: "Reply with exactly: PING",
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
expect(response.result).toBeDefined();
|
|
31
|
-
expect(response.result.length).toBeGreaterThan(0);
|
|
32
|
-
expect(response.exitCode).toBe(0);
|
|
33
|
-
expect(response.model).toBeDefined();
|
|
34
|
-
expect(response.duration).toBeGreaterThan(0);
|
|
35
|
-
}, 30_000);
|
|
36
|
-
|
|
37
|
-
test("detects authentication method", () => {
|
|
38
|
-
if (hasApiKey) {
|
|
39
|
-
expect(process.env.ANTHROPIC_API_KEY).toBeDefined();
|
|
40
|
-
console.log("Auth: ANTHROPIC_API_KEY");
|
|
41
|
-
}
|
|
42
|
-
if (hasOAuth) {
|
|
43
|
-
expect(process.env.CLAUDE_CODE_OAUTH_TOKEN).toBeDefined();
|
|
44
|
-
console.log("Auth: CLAUDE_CODE_OAUTH_TOKEN");
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test("throws ClaudeProviderError on invalid auth", async () => {
|
|
49
|
-
const originalKey = process.env.ANTHROPIC_API_KEY;
|
|
50
|
-
const originalOAuth = process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
// 無効な認証情報に差し替え
|
|
54
|
-
process.env.ANTHROPIC_API_KEY = "sk-ant-invalid-key-for-testing";
|
|
55
|
-
delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
56
|
-
|
|
57
|
-
const adapter = createAdapter();
|
|
58
|
-
await expect(
|
|
59
|
-
adapter.call({ prompt: "test" })
|
|
60
|
-
).rejects.toThrow(ClaudeProviderError);
|
|
61
|
-
} finally {
|
|
62
|
-
// 復元
|
|
63
|
-
if (originalKey) process.env.ANTHROPIC_API_KEY = originalKey;
|
|
64
|
-
else delete process.env.ANTHROPIC_API_KEY;
|
|
65
|
-
if (originalOAuth) process.env.CLAUDE_CODE_OAUTH_TOKEN = originalOAuth;
|
|
66
|
-
else delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
67
|
-
}
|
|
68
|
-
}, 15_000);
|
|
69
|
-
|
|
70
|
-
test("respects timeout/abort", async () => {
|
|
71
|
-
const adapter = createAdapter();
|
|
72
|
-
|
|
73
|
-
await expect(
|
|
74
|
-
adapter.call({
|
|
75
|
-
prompt: "Write a very long essay about the history of computing.",
|
|
76
|
-
timeout: 100, // 100ms — 即タイムアウト
|
|
77
|
-
})
|
|
78
|
-
).rejects.toThrow();
|
|
79
|
-
}, 10_000);
|
|
80
|
-
|
|
81
|
-
test("passes effort level correctly", async () => {
|
|
82
|
-
const adapter = createAdapter();
|
|
83
|
-
const response = await adapter.call({
|
|
84
|
-
prompt: "Reply with exactly: OK",
|
|
85
|
-
effortLevel: "low",
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
expect(response.result).toBeDefined();
|
|
89
|
-
expect(response.exitCode).toBe(0);
|
|
90
|
-
expect(response.effortLevel).toBe("low");
|
|
91
|
-
}, 30_000);
|
|
92
|
-
|
|
93
|
-
test("respects model override", async () => {
|
|
94
|
-
const adapter = createAdapter();
|
|
95
|
-
const response = await adapter.call({
|
|
96
|
-
prompt: "Reply with exactly: HI",
|
|
97
|
-
model: "claude-haiku-4-5-20251001",
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
expect(response.result).toBeDefined();
|
|
101
|
-
expect(response.exitCode).toBe(0);
|
|
102
|
-
// model名はSDKが返す値に依存するが、haiku系であることを確認
|
|
103
|
-
expect(response.model).toContain("haiku");
|
|
104
|
-
}, 30_000);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
describe.skipIf(!hasOAuth)("ClaudeSdkAdapter - OAuth Authentication", () => {
|
|
108
|
-
test("authenticates with CLAUDE_CODE_OAUTH_TOKEN", async () => {
|
|
109
|
-
const originalKey = process.env.ANTHROPIC_API_KEY;
|
|
110
|
-
|
|
111
|
-
try {
|
|
112
|
-
// API Keyを一時的に除外してOAuthのみで認証
|
|
113
|
-
delete process.env.ANTHROPIC_API_KEY;
|
|
114
|
-
|
|
115
|
-
const adapter = createAdapter();
|
|
116
|
-
const response = await adapter.call({
|
|
117
|
-
prompt: "Reply with exactly: OAUTH_OK",
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
expect(response.result).toBeDefined();
|
|
121
|
-
expect(response.exitCode).toBe(0);
|
|
122
|
-
} finally {
|
|
123
|
-
if (originalKey) process.env.ANTHROPIC_API_KEY = originalKey;
|
|
124
|
-
else delete process.env.ANTHROPIC_API_KEY;
|
|
125
|
-
}
|
|
126
|
-
}, 30_000);
|
|
127
|
-
});
|