@ronkovic/aad 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ronkovic/aad",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Autonomous Agent Development Orchestrator - Multi-agent TDD pipeline powered by Claude",
5
5
  "module": "src/main.ts",
6
6
  "type": "module",
@@ -350,4 +350,79 @@ describe("ClaudeSdkAdapter", () => {
350
350
  expect(response.exitCode).toBe(0);
351
351
  expect(response.result).toBe("Final result");
352
352
  });
353
+
354
+ test("call() - ANTHROPIC_API_KEY が env に渡される", async () => {
355
+ const originalKey = process.env.ANTHROPIC_API_KEY;
356
+ process.env.ANTHROPIC_API_KEY = "sk-ant-test-key";
357
+ try {
358
+ await adapter.call({ prompt: "Test" });
359
+ const callArgs = mockQuery.mock.calls[0] as unknown as [
360
+ { prompt: string; options: { env?: Record<string, string> } }
361
+ ];
362
+ expect(callArgs[0]!.options.env?.ANTHROPIC_API_KEY).toBe("sk-ant-test-key");
363
+ } finally {
364
+ if (originalKey) process.env.ANTHROPIC_API_KEY = originalKey;
365
+ else delete process.env.ANTHROPIC_API_KEY;
366
+ }
367
+ });
368
+
369
+ test("call() - CLAUDE_CODE_OAUTH_TOKEN が env に渡される", async () => {
370
+ const originalToken = process.env.CLAUDE_CODE_OAUTH_TOKEN;
371
+ process.env.CLAUDE_CODE_OAUTH_TOKEN = "oauth-test-token";
372
+ try {
373
+ await adapter.call({ prompt: "Test" });
374
+ const callArgs = mockQuery.mock.calls[0] as unknown as [
375
+ { prompt: string; options: { env?: Record<string, string> } }
376
+ ];
377
+ expect(callArgs[0]!.options.env?.CLAUDE_CODE_OAUTH_TOKEN).toBe("oauth-test-token");
378
+ } finally {
379
+ if (originalToken) process.env.CLAUDE_CODE_OAUTH_TOKEN = originalToken;
380
+ else delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
381
+ }
382
+ });
383
+
384
+ test("call() - タイムアウトで AbortError が発生する", async () => {
385
+ mockQuery.mockImplementationOnce(
386
+ (params: { prompt: string; options?: unknown }) => {
387
+ const abortController = (params.options as { abortController?: AbortController })?.abortController;
388
+ return (async function* () {
389
+ // AbortControllerのabortを監視
390
+ if (abortController) {
391
+ await new Promise((_, reject) => {
392
+ abortController.signal.addEventListener("abort", () => {
393
+ reject(new Error("The operation was aborted"));
394
+ });
395
+ // 長時間待機
396
+ setTimeout(() => {}, 5000);
397
+ });
398
+ }
399
+ yield { type: "result", subtype: "success", result: "late" } as MockSDKMessage;
400
+ })() as AsyncGenerator<MockSDKMessage, void, unknown>;
401
+ }
402
+ );
403
+
404
+ await expect(adapter.call({ prompt: "Test", timeout: 50 })).rejects.toThrow();
405
+ }, 10_000);
406
+
407
+ test("call() - effortLevel がレスポンスに反映される", async () => {
408
+ const response = await adapter.call({ prompt: "Test", effortLevel: "low" });
409
+ expect(response.effortLevel).toBe("low");
410
+ });
411
+
412
+ test("call() - model override がレスポンスに反映される", async () => {
413
+ mockQuery.mockReturnValueOnce(
414
+ (async function* () {
415
+ yield {
416
+ type: "assistant",
417
+ message: {
418
+ model: "claude-haiku-4-5-20251001",
419
+ content: [{ type: "text", text: "Response" }],
420
+ },
421
+ } as MockSDKMessage;
422
+ yield { type: "result", subtype: "success", result: "Response" } as MockSDKMessage;
423
+ })() as AsyncGenerator<MockSDKMessage, void, unknown>
424
+ );
425
+ const response = await adapter.call({ prompt: "Test", model: "claude-haiku-4-5-20251001" });
426
+ expect(response.model).toContain("haiku");
427
+ });
353
428
  });
@@ -75,3 +75,22 @@ describe("buildInstallCommand", () => {
75
75
  });
76
76
  });
77
77
  });
78
+
79
+ describe("resolveAvailableCommand", () => {
80
+ test("returns command if binary exists in PATH", async () => {
81
+ // Import the function
82
+ const { resolveAvailableCommand } = await import("../dependency-installer");
83
+
84
+ // bun should exist in PATH during tests
85
+ const result = await resolveAvailableCommand("bun");
86
+ expect(result).toBe("bun");
87
+ });
88
+
89
+ test("falls back to npx if binary does not exist in PATH", async () => {
90
+ const { resolveAvailableCommand } = await import("../dependency-installer");
91
+
92
+ // Use a non-existent binary name
93
+ const result = await resolveAvailableCommand("nonexistent-binary-xyz123");
94
+ expect(result).toBe("npx");
95
+ });
96
+ });
@@ -186,4 +186,29 @@ describe("resolveTemplateDir", () => {
186
186
  expect(result).toContain("templates");
187
187
  expect(result).not.toContain("sandbox/templates");
188
188
  });
189
+
190
+ test("checks multiple fallback paths for npm distribution", async () => {
191
+ // Create test directory structure mimicking npm installation
192
+ const npmTestDir = join(testDir, "npm-install-test");
193
+ const fakeProjectRoot = join(npmTestDir, "user-project");
194
+ const packageRoot = join(npmTestDir, "node_modules", "aad");
195
+ const npmTemplateDir = join(packageRoot, "templates");
196
+
197
+ await mkdir(fakeProjectRoot, { recursive: true });
198
+ await mkdir(npmTemplateDir, { recursive: true });
199
+ await writeFile(join(npmTemplateDir, "CLAUDE.md"), "NPM template");
200
+
201
+ // Verify the actual project has templates/ at the root for npm distribution
202
+ // src/modules/git-workspace/__tests__/ -> ../../../../templates
203
+ const { existsSync } = await import("node:fs");
204
+ const actualProjectRoot = join(import.meta.dir, "..", "..", "..", "..");
205
+ const actualNpmTemplate = join(actualProjectRoot, "templates");
206
+
207
+ expect(existsSync(actualNpmTemplate)).toBe(true);
208
+
209
+ // Verify resolveTemplateDir returns templates/ when .aad/templates doesn't exist
210
+ const result = resolveTemplateDir(fakeProjectRoot);
211
+ // Should fallback to package templates/ since no local override exists
212
+ expect(result).toContain("templates");
213
+ });
189
214
  });
@@ -109,6 +109,89 @@ describe("WorktreeManager", () => {
109
109
  worktreeManager.createParentWorktree(runId, parentBranch)
110
110
  ).rejects.toThrow(GitWorkspaceError);
111
111
  });
112
+
113
+ test("cleans up stale parent worktree before creating new one", async () => {
114
+ const runId = createRunId("run-003");
115
+ const parentBranch = "main";
116
+ const featureBranch = "feat/run-003/parent";
117
+ const calls: string[][] = [];
118
+
119
+ mockGitOps.gitExec = mock(async (args: string[]) => {
120
+ calls.push(args);
121
+ return { stdout: "", stderr: "", exitCode: 0 };
122
+ });
123
+
124
+ await worktreeManager.createParentWorktree(runId, parentBranch, featureBranch);
125
+
126
+ // Should call worktree remove before worktree add
127
+ const removeIdx = calls.findIndex(c => c[0] === "worktree" && c[1] === "remove");
128
+ const addIdx = calls.findIndex(c => c[0] === "worktree" && c[1] === "add");
129
+ expect(removeIdx).toBeGreaterThanOrEqual(0);
130
+ expect(addIdx).toBeGreaterThan(removeIdx);
131
+ expect(calls[removeIdx]).toEqual(["worktree", "remove", "/test/worktrees/parent-run-003", "--force"]);
132
+ });
133
+
134
+ test("deletes stale parent branch before creating new one", async () => {
135
+ const runId = createRunId("run-004");
136
+ const parentBranch = "main";
137
+ const featureBranch = "feat/run-004/parent";
138
+ const calls: string[][] = [];
139
+
140
+ mockGitOps.gitExec = mock(async (args: string[]) => {
141
+ calls.push(args);
142
+ return { stdout: "", stderr: "", exitCode: 0 };
143
+ });
144
+
145
+ await worktreeManager.createParentWorktree(runId, parentBranch, featureBranch);
146
+
147
+ const branchDeleteCall = calls.find(c => c[0] === "branch" && c[1] === "-D");
148
+ expect(branchDeleteCall).toBeDefined();
149
+ expect(branchDeleteCall![2]).toBe(featureBranch);
150
+
151
+ // Branch delete should happen before worktree add
152
+ const branchIdx = calls.indexOf(branchDeleteCall!);
153
+ const addIdx = calls.findIndex(c => c[0] === "worktree" && c[1] === "add");
154
+ expect(branchIdx).toBeLessThan(addIdx);
155
+ });
156
+
157
+ test("handles gracefully when no stale parent worktree exists", async () => {
158
+ const runId = createRunId("run-005");
159
+ const parentBranch = "main";
160
+ const featureBranch = "feat/run-005/parent";
161
+
162
+ // Simulate: worktree remove fails, rm succeeds, prune succeeds, branch -D fails, add succeeds
163
+ mockGitOps.gitExec = mock(async (args: string[]) => {
164
+ if (args[1] === "remove") throw new Error("not a worktree");
165
+ if (args[0] === "branch" && args[1] === "-D") throw new Error("branch not found");
166
+ return { stdout: "", stderr: "", exitCode: 0 };
167
+ });
168
+
169
+ // Should not throw — cleanup errors are swallowed
170
+ const result = await worktreeManager.createParentWorktree(runId, parentBranch, featureBranch);
171
+ expect(result.worktreePath).toBe("/test/worktrees/parent-run-005");
172
+ expect(result.branch).toBe(featureBranch);
173
+ });
174
+
175
+ test("calls git worktree prune during parent cleanup", async () => {
176
+ const runId = createRunId("run-006");
177
+ const parentBranch = "main";
178
+ const calls: string[][] = [];
179
+
180
+ mockGitOps.gitExec = mock(async (args: string[]) => {
181
+ calls.push(args);
182
+ return { stdout: "", stderr: "", exitCode: 0 };
183
+ });
184
+
185
+ await worktreeManager.createParentWorktree(runId, parentBranch);
186
+
187
+ const pruneCall = calls.find(c => c[0] === "worktree" && c[1] === "prune");
188
+ expect(pruneCall).toBeDefined();
189
+
190
+ // Prune should happen before worktree add
191
+ const pruneIdx = calls.indexOf(pruneCall!);
192
+ const addIdx = calls.findIndex(c => c[0] === "worktree" && c[1] === "add");
193
+ expect(pruneIdx).toBeLessThan(addIdx);
194
+ });
112
195
  });
113
196
 
114
197
  describe("removeWorktree", () => {
@@ -5,6 +5,10 @@
5
5
 
6
6
  import type { WorkspaceInfo } from "../../shared/types";
7
7
  import type { Logger } from "pino";
8
+ import { resolveAvailableCommand } from "../../shared/utils";
9
+
10
+ // Re-export for external use
11
+ export { resolveAvailableCommand };
8
12
 
9
13
  export interface InstallResult {
10
14
  success: boolean;
@@ -16,7 +16,8 @@ const PROJECT_CONTEXT_HEADER = "# プロジェクト固有コンテキスト\n\n
16
16
  /**
17
17
  * Resolve template directory path:
18
18
  * 1. Project local override: {projectRoot}/.aad/templates/ (if exists)
19
- * 2. Package bundled: {packageRoot}/templates/
19
+ * 2. Package bundled (dev): {packageRoot}/.aad/templates/ (if exists)
20
+ * 3. Package bundled (npm): {packageRoot}/templates/ (for npm distribution)
20
21
  */
21
22
  export function resolveTemplateDir(projectRoot: string): string {
22
23
  const localTemplateDir = join(projectRoot, ".aad", "templates");
@@ -27,9 +28,17 @@ export function resolveTemplateDir(projectRoot: string): string {
27
28
  }
28
29
 
29
30
  // Fallback to package bundled templates
30
- // src/modules/git-workspace/ -> ../../../.aad/templates
31
+ // src/modules/git-workspace/ -> ../../../
31
32
  const packageRoot = join(import.meta.dir, "..", "..", "..");
32
- return join(packageRoot, ".aad", "templates");
33
+
34
+ // Try .aad/templates first (dev environment)
35
+ const devTemplateDir = join(packageRoot, ".aad", "templates");
36
+ if (existsSync(devTemplateDir)) {
37
+ return devTemplateDir;
38
+ }
39
+
40
+ // Fallback to templates/ (npm distribution)
41
+ return join(packageRoot, "templates");
33
42
  }
34
43
 
35
44
  /**
@@ -127,6 +127,9 @@ export class WorktreeManager {
127
127
  try {
128
128
  await this.fsOps.mkdir(this.worktreeBase, { recursive: true });
129
129
 
130
+ // Clean up stale worktree/branch from previous runs
131
+ await this.cleanupStaleWorktree(worktreePath, branch);
132
+
130
133
  // Create worktree with a new branch based on parentBranch
131
134
  // e.g. git worktree add -b feat/auth-feature/parent <path> main
132
135
  await this.gitOps.gitExec(
@@ -212,6 +212,53 @@ describe("buildTestCommand", () => {
212
212
  // After fallback implementation, unknown should return npm test
213
213
  expect(buildTestCommand(workspace)).toEqual(["npm", "test"]);
214
214
  });
215
+
216
+ test("builds vitest with fallback when yarn is unavailable", async () => {
217
+ const workspace: WorkspaceInfo = {
218
+ path: "/path",
219
+ language: "typescript",
220
+ packageManager: "yarn",
221
+ framework: "vite",
222
+ testFramework: "vitest",
223
+ };
224
+
225
+ // Mock Bun.which to simulate yarn not available
226
+ const originalWhich = Bun.which;
227
+ Bun.which = (cmd: string) => {
228
+ if (cmd === "yarn") return null;
229
+ return originalWhich(cmd);
230
+ };
231
+
232
+ try {
233
+ const result = await (await import("../phases/tester-verify")).buildTestCommandWithFallback(workspace);
234
+ expect(result).toEqual(["npx", "yarn", "test"]);
235
+ } finally {
236
+ Bun.which = originalWhich;
237
+ }
238
+ });
239
+
240
+ test("builds jest with fallback when pnpm is unavailable", async () => {
241
+ const workspace: WorkspaceInfo = {
242
+ path: "/path",
243
+ language: "javascript",
244
+ packageManager: "pnpm",
245
+ framework: "react",
246
+ testFramework: "jest",
247
+ };
248
+
249
+ const originalWhich = Bun.which;
250
+ Bun.which = (cmd: string) => {
251
+ if (cmd === "pnpm") return null;
252
+ return originalWhich(cmd);
253
+ };
254
+
255
+ try {
256
+ const result = await (await import("../phases/tester-verify")).buildTestCommandWithFallback(workspace);
257
+ expect(result).toEqual(["npx", "pnpm", "test"]);
258
+ } finally {
259
+ Bun.which = originalWhich;
260
+ }
261
+ });
215
262
  });
216
263
 
217
264
  describe("runTests", () => {
@@ -1,5 +1,6 @@
1
1
  import type { WorkspaceInfo } from "@aad/shared/types";
2
2
  import { TestRunnerError } from "@aad/shared/errors";
3
+ import { resolveAvailableCommand } from "@aad/shared/utils";
3
4
 
4
5
  export interface ProcessResult {
5
6
  exitCode: number;
@@ -21,6 +22,95 @@ export interface TestResult {
21
22
  error?: string;
22
23
  }
23
24
 
25
+ /**
26
+ * Build test command with fallback support for unavailable package managers
27
+ */
28
+ export async function buildTestCommandWithFallback(workspace: WorkspaceInfo): Promise<string[]> {
29
+ const { testFramework, packageManager } = workspace;
30
+
31
+ switch (testFramework) {
32
+ case "bun-test":
33
+ return ["bun", "test"];
34
+
35
+ case "vitest": {
36
+ if (packageManager === "npm") return ["npm", "run", "test"];
37
+ if (packageManager === "yarn") {
38
+ const cmd = await resolveAvailableCommand("yarn");
39
+ return cmd === "npx" ? ["npx", "yarn", "test"] : ["yarn", "test"];
40
+ }
41
+ if (packageManager === "pnpm") {
42
+ const cmd = await resolveAvailableCommand("pnpm");
43
+ return cmd === "npx" ? ["npx", "pnpm", "test"] : ["pnpm", "test"];
44
+ }
45
+ return ["npx", "vitest", "run"];
46
+ }
47
+
48
+ case "jest": {
49
+ if (packageManager === "npm") return ["npm", "test"];
50
+ if (packageManager === "yarn") {
51
+ const cmd = await resolveAvailableCommand("yarn");
52
+ return cmd === "npx" ? ["npx", "yarn", "test"] : ["yarn", "test"];
53
+ }
54
+ if (packageManager === "pnpm") {
55
+ const cmd = await resolveAvailableCommand("pnpm");
56
+ return cmd === "npx" ? ["npx", "pnpm", "test"] : ["pnpm", "test"];
57
+ }
58
+ return ["npx", "jest"];
59
+ }
60
+
61
+ case "mocha": {
62
+ if (packageManager === "npm") return ["npm", "test"];
63
+ if (packageManager === "yarn") {
64
+ const cmd = await resolveAvailableCommand("yarn");
65
+ return cmd === "npx" ? ["npx", "yarn", "test"] : ["yarn", "test"];
66
+ }
67
+ return ["npx", "mocha"];
68
+ }
69
+
70
+ case "pytest": {
71
+ if (packageManager === "uv") return ["uv", "run", "pytest", "-v"];
72
+ if (packageManager === "poetry") return ["poetry", "run", "pytest", "-v"];
73
+ return ["pytest", "-v"];
74
+ }
75
+
76
+ case "go-test":
77
+ return ["go", "test", "./..."];
78
+
79
+ case "cargo":
80
+ return ["cargo", "test"];
81
+
82
+ case "maven":
83
+ return ["mvn", "test"];
84
+
85
+ case "gradle":
86
+ return ["./gradlew", "test"];
87
+
88
+ case "playwright":
89
+ return ["npx", "playwright", "test"];
90
+
91
+ case "terraform":
92
+ return ["terraform", "validate"];
93
+
94
+ case "unknown": {
95
+ if (packageManager === "bun") return ["bun", "test"];
96
+ if (packageManager === "npm") return ["npm", "test"];
97
+ if (packageManager === "yarn") return ["yarn", "test"];
98
+ if (packageManager === "pnpm") return ["pnpm", "test"];
99
+ if (packageManager === "uv") return ["uv", "run", "pytest", "-v"];
100
+ if (packageManager === "poetry") return ["poetry", "run", "pytest", "-v"];
101
+ return ["npm", "test"];
102
+ }
103
+
104
+ default: {
105
+ const exhaustive: never = testFramework;
106
+ throw new TestRunnerError(
107
+ `Unsupported test framework: ${exhaustive}`,
108
+ { testFramework }
109
+ );
110
+ }
111
+ }
112
+ }
113
+
24
114
  /**
25
115
  * Build test command based on detected test framework
26
116
  */
@@ -1,5 +1,5 @@
1
1
  import { describe, test, expect } from "bun:test";
2
- import { capitalize, toLowerCase, trim, reverse, toUpperCase } from "../utils";
2
+ import { capitalize, toLowerCase, trim, reverse, toUpperCase, resolveAvailableCommand } from "../utils";
3
3
 
4
4
  describe("capitalize", () => {
5
5
  test.each([
@@ -292,3 +292,23 @@ describe("toUpperCase", () => {
292
292
  expect(toUpperCase(input)).toBe(expected);
293
293
  });
294
294
  });
295
+
296
+ describe("resolveAvailableCommand", () => {
297
+ test("returns command if binary exists in PATH", async () => {
298
+ // bun should exist in PATH during tests
299
+ const result = await resolveAvailableCommand("bun");
300
+ expect(result).toBe("bun");
301
+ });
302
+
303
+ test("falls back to npx if binary does not exist in PATH", async () => {
304
+ // Use a non-existent binary name
305
+ const result = await resolveAvailableCommand("nonexistent-binary-xyz123");
306
+ expect(result).toBe("npx");
307
+ });
308
+
309
+ test("handles common package managers", async () => {
310
+ // npm should always be available if bun is available
311
+ const npmResult = await resolveAvailableCommand("npm");
312
+ expect(npmResult).toBe("npm");
313
+ });
314
+ });
@@ -1,5 +1,16 @@
1
1
  // Utility functions for shared functionality
2
2
 
3
+ /**
4
+ * コマンドのバイナリが $PATH に存在するか確認し、
5
+ * 存在しない場合は npx にフォールバックする
6
+ * @param command - The command to check
7
+ * @returns The command if available, otherwise "npx"
8
+ */
9
+ export async function resolveAvailableCommand(command: string): Promise<string> {
10
+ const available = await Bun.which(command);
11
+ return available ? command : "npx";
12
+ }
13
+
3
14
  /**
4
15
  * Capitalizes the first character of a string.
5
16
  * @param str - The string to capitalize
@@ -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
- });