mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 08:53:36 +00:00
Merge main into massive-terminal-upgrade
Resolves merge conflicts: - apps/server/src/routes/terminal/common.ts: Keep randomBytes import, use @automaker/utils for createLogger - apps/ui/eslint.config.mjs: Use main's explicit globals list with XMLHttpRequest and MediaQueryListEvent additions - apps/ui/src/components/views/terminal-view.tsx: Keep our terminal improvements (killAllSessions, beforeunload, better error handling) - apps/ui/src/config/terminal-themes.ts: Keep our search highlight colors for all themes - apps/ui/src/store/app-store.ts: Keep our terminal settings persistence improvements (merge function) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,23 +1,21 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { BaseProvider } from "@/providers/base-provider.js";
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { BaseProvider } from '@/providers/base-provider.js';
|
||||
import type {
|
||||
ProviderConfig,
|
||||
ExecuteOptions,
|
||||
ProviderMessage,
|
||||
InstallationStatus,
|
||||
ModelDefinition,
|
||||
} from "@/providers/types.js";
|
||||
} from '@automaker/types';
|
||||
|
||||
// Concrete implementation for testing the abstract class
|
||||
class TestProvider extends BaseProvider {
|
||||
getName(): string {
|
||||
return "test-provider";
|
||||
return 'test-provider';
|
||||
}
|
||||
|
||||
async *executeQuery(
|
||||
_options: ExecuteOptions
|
||||
): AsyncGenerator<ProviderMessage> {
|
||||
yield { type: "text", text: "test response" };
|
||||
async *executeQuery(_options: ExecuteOptions): AsyncGenerator<ProviderMessage> {
|
||||
yield { type: 'text', text: 'test response' };
|
||||
}
|
||||
|
||||
async detectInstallation(): Promise<InstallationStatus> {
|
||||
@@ -25,37 +23,35 @@ class TestProvider extends BaseProvider {
|
||||
}
|
||||
|
||||
getAvailableModels(): ModelDefinition[] {
|
||||
return [
|
||||
{ id: "test-model-1", name: "Test Model 1", description: "A test model" },
|
||||
];
|
||||
return [{ id: 'test-model-1', name: 'Test Model 1', description: 'A test model' }];
|
||||
}
|
||||
}
|
||||
|
||||
describe("base-provider.ts", () => {
|
||||
describe("constructor", () => {
|
||||
it("should initialize with empty config when none provided", () => {
|
||||
describe('base-provider.ts', () => {
|
||||
describe('constructor', () => {
|
||||
it('should initialize with empty config when none provided', () => {
|
||||
const provider = new TestProvider();
|
||||
expect(provider.getConfig()).toEqual({});
|
||||
});
|
||||
|
||||
it("should initialize with provided config", () => {
|
||||
it('should initialize with provided config', () => {
|
||||
const config: ProviderConfig = {
|
||||
apiKey: "test-key",
|
||||
baseUrl: "https://test.com",
|
||||
apiKey: 'test-key',
|
||||
baseUrl: 'https://test.com',
|
||||
};
|
||||
const provider = new TestProvider(config);
|
||||
expect(provider.getConfig()).toEqual(config);
|
||||
});
|
||||
|
||||
it("should call getName() during initialization", () => {
|
||||
it('should call getName() during initialization', () => {
|
||||
const provider = new TestProvider();
|
||||
expect(provider.getName()).toBe("test-provider");
|
||||
expect(provider.getName()).toBe('test-provider');
|
||||
});
|
||||
});
|
||||
|
||||
describe("validateConfig", () => {
|
||||
it("should return valid when config exists", () => {
|
||||
const provider = new TestProvider({ apiKey: "test" });
|
||||
describe('validateConfig', () => {
|
||||
it('should return valid when config exists', () => {
|
||||
const provider = new TestProvider({ apiKey: 'test' });
|
||||
const result = provider.validateConfig();
|
||||
|
||||
expect(result.valid).toBe(true);
|
||||
@@ -63,7 +59,7 @@ describe("base-provider.ts", () => {
|
||||
expect(result.warnings).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("should return invalid when config is undefined", () => {
|
||||
it('should return invalid when config is undefined', () => {
|
||||
// Create provider without config
|
||||
const provider = new TestProvider();
|
||||
// Manually set config to undefined to test edge case
|
||||
@@ -72,10 +68,10 @@ describe("base-provider.ts", () => {
|
||||
const result = provider.validateConfig();
|
||||
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.errors).toContain("Provider config is missing");
|
||||
expect(result.errors).toContain('Provider config is missing');
|
||||
});
|
||||
|
||||
it("should return valid for empty config object", () => {
|
||||
it('should return valid for empty config object', () => {
|
||||
const provider = new TestProvider({});
|
||||
const result = provider.validateConfig();
|
||||
|
||||
@@ -83,53 +79,53 @@ describe("base-provider.ts", () => {
|
||||
expect(result.errors).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("should include warnings array in result", () => {
|
||||
it('should include warnings array in result', () => {
|
||||
const provider = new TestProvider();
|
||||
const result = provider.validateConfig();
|
||||
|
||||
expect(result).toHaveProperty("warnings");
|
||||
expect(result).toHaveProperty('warnings');
|
||||
expect(Array.isArray(result.warnings)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("supportsFeature", () => {
|
||||
describe('supportsFeature', () => {
|
||||
it("should support 'tools' feature", () => {
|
||||
const provider = new TestProvider();
|
||||
expect(provider.supportsFeature("tools")).toBe(true);
|
||||
expect(provider.supportsFeature('tools')).toBe(true);
|
||||
});
|
||||
|
||||
it("should support 'text' feature", () => {
|
||||
const provider = new TestProvider();
|
||||
expect(provider.supportsFeature("text")).toBe(true);
|
||||
expect(provider.supportsFeature('text')).toBe(true);
|
||||
});
|
||||
|
||||
it("should not support unknown features", () => {
|
||||
it('should not support unknown features', () => {
|
||||
const provider = new TestProvider();
|
||||
expect(provider.supportsFeature("vision")).toBe(false);
|
||||
expect(provider.supportsFeature("mcp")).toBe(false);
|
||||
expect(provider.supportsFeature("unknown")).toBe(false);
|
||||
expect(provider.supportsFeature('vision')).toBe(false);
|
||||
expect(provider.supportsFeature('mcp')).toBe(false);
|
||||
expect(provider.supportsFeature('unknown')).toBe(false);
|
||||
});
|
||||
|
||||
it("should be case-sensitive", () => {
|
||||
it('should be case-sensitive', () => {
|
||||
const provider = new TestProvider();
|
||||
expect(provider.supportsFeature("TOOLS")).toBe(false);
|
||||
expect(provider.supportsFeature("Text")).toBe(false);
|
||||
expect(provider.supportsFeature('TOOLS')).toBe(false);
|
||||
expect(provider.supportsFeature('Text')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getConfig", () => {
|
||||
it("should return current config", () => {
|
||||
describe('getConfig', () => {
|
||||
it('should return current config', () => {
|
||||
const config: ProviderConfig = {
|
||||
apiKey: "test-key",
|
||||
model: "test-model",
|
||||
apiKey: 'test-key',
|
||||
model: 'test-model',
|
||||
};
|
||||
const provider = new TestProvider(config);
|
||||
|
||||
expect(provider.getConfig()).toEqual(config);
|
||||
});
|
||||
|
||||
it("should return same reference", () => {
|
||||
const config: ProviderConfig = { apiKey: "test" };
|
||||
it('should return same reference', () => {
|
||||
const config: ProviderConfig = { apiKey: 'test' };
|
||||
const provider = new TestProvider(config);
|
||||
|
||||
const retrieved1 = provider.getConfig();
|
||||
@@ -139,31 +135,31 @@ describe("base-provider.ts", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("setConfig", () => {
|
||||
it("should merge partial config with existing config", () => {
|
||||
const provider = new TestProvider({ apiKey: "original-key" });
|
||||
describe('setConfig', () => {
|
||||
it('should merge partial config with existing config', () => {
|
||||
const provider = new TestProvider({ apiKey: 'original-key' });
|
||||
|
||||
provider.setConfig({ model: "new-model" });
|
||||
provider.setConfig({ model: 'new-model' });
|
||||
|
||||
expect(provider.getConfig()).toEqual({
|
||||
apiKey: "original-key",
|
||||
model: "new-model",
|
||||
apiKey: 'original-key',
|
||||
model: 'new-model',
|
||||
});
|
||||
});
|
||||
|
||||
it("should override existing fields", () => {
|
||||
const provider = new TestProvider({ apiKey: "old-key", model: "old-model" });
|
||||
it('should override existing fields', () => {
|
||||
const provider = new TestProvider({ apiKey: 'old-key', model: 'old-model' });
|
||||
|
||||
provider.setConfig({ apiKey: "new-key" });
|
||||
provider.setConfig({ apiKey: 'new-key' });
|
||||
|
||||
expect(provider.getConfig()).toEqual({
|
||||
apiKey: "new-key",
|
||||
model: "old-model",
|
||||
apiKey: 'new-key',
|
||||
model: 'old-model',
|
||||
});
|
||||
});
|
||||
|
||||
it("should accept empty object", () => {
|
||||
const provider = new TestProvider({ apiKey: "test" });
|
||||
it('should accept empty object', () => {
|
||||
const provider = new TestProvider({ apiKey: 'test' });
|
||||
const originalConfig = provider.getConfig();
|
||||
|
||||
provider.setConfig({});
|
||||
@@ -171,68 +167,68 @@ describe("base-provider.ts", () => {
|
||||
expect(provider.getConfig()).toEqual(originalConfig);
|
||||
});
|
||||
|
||||
it("should handle multiple updates", () => {
|
||||
it('should handle multiple updates', () => {
|
||||
const provider = new TestProvider();
|
||||
|
||||
provider.setConfig({ apiKey: "key1" });
|
||||
provider.setConfig({ model: "model1" });
|
||||
provider.setConfig({ baseUrl: "https://test.com" });
|
||||
provider.setConfig({ apiKey: 'key1' });
|
||||
provider.setConfig({ model: 'model1' });
|
||||
provider.setConfig({ baseUrl: 'https://test.com' });
|
||||
|
||||
expect(provider.getConfig()).toEqual({
|
||||
apiKey: "key1",
|
||||
model: "model1",
|
||||
baseUrl: "https://test.com",
|
||||
apiKey: 'key1',
|
||||
model: 'model1',
|
||||
baseUrl: 'https://test.com',
|
||||
});
|
||||
});
|
||||
|
||||
it("should preserve other fields when updating one field", () => {
|
||||
it('should preserve other fields when updating one field', () => {
|
||||
const provider = new TestProvider({
|
||||
apiKey: "key",
|
||||
model: "model",
|
||||
baseUrl: "https://test.com",
|
||||
apiKey: 'key',
|
||||
model: 'model',
|
||||
baseUrl: 'https://test.com',
|
||||
});
|
||||
|
||||
provider.setConfig({ model: "new-model" });
|
||||
provider.setConfig({ model: 'new-model' });
|
||||
|
||||
expect(provider.getConfig()).toEqual({
|
||||
apiKey: "key",
|
||||
model: "new-model",
|
||||
baseUrl: "https://test.com",
|
||||
apiKey: 'key',
|
||||
model: 'new-model',
|
||||
baseUrl: 'https://test.com',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("abstract methods", () => {
|
||||
it("should require getName implementation", () => {
|
||||
describe('abstract methods', () => {
|
||||
it('should require getName implementation', () => {
|
||||
const provider = new TestProvider();
|
||||
expect(typeof provider.getName).toBe("function");
|
||||
expect(provider.getName()).toBe("test-provider");
|
||||
expect(typeof provider.getName).toBe('function');
|
||||
expect(provider.getName()).toBe('test-provider');
|
||||
});
|
||||
|
||||
it("should require executeQuery implementation", async () => {
|
||||
it('should require executeQuery implementation', async () => {
|
||||
const provider = new TestProvider();
|
||||
expect(typeof provider.executeQuery).toBe("function");
|
||||
expect(typeof provider.executeQuery).toBe('function');
|
||||
|
||||
const generator = provider.executeQuery({
|
||||
prompt: "test",
|
||||
projectDirectory: "/test",
|
||||
prompt: 'test',
|
||||
projectDirectory: '/test',
|
||||
});
|
||||
const result = await generator.next();
|
||||
|
||||
expect(result.value).toEqual({ type: "text", text: "test response" });
|
||||
expect(result.value).toEqual({ type: 'text', text: 'test response' });
|
||||
});
|
||||
|
||||
it("should require detectInstallation implementation", async () => {
|
||||
it('should require detectInstallation implementation', async () => {
|
||||
const provider = new TestProvider();
|
||||
expect(typeof provider.detectInstallation).toBe("function");
|
||||
expect(typeof provider.detectInstallation).toBe('function');
|
||||
|
||||
const status = await provider.detectInstallation();
|
||||
expect(status).toHaveProperty("installed");
|
||||
expect(status).toHaveProperty('installed');
|
||||
});
|
||||
|
||||
it("should require getAvailableModels implementation", () => {
|
||||
it('should require getAvailableModels implementation', () => {
|
||||
const provider = new TestProvider();
|
||||
expect(typeof provider.getAvailableModels).toBe("function");
|
||||
expect(typeof provider.getAvailableModels).toBe('function');
|
||||
|
||||
const models = provider.getAvailableModels();
|
||||
expect(Array.isArray(models)).toBe(true);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { ClaudeProvider } from "@/providers/claude-provider.js";
|
||||
import * as sdk from "@anthropic-ai/claude-agent-sdk";
|
||||
import { collectAsyncGenerator } from "../../utils/helpers.js";
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { ClaudeProvider } from '@/providers/claude-provider.js';
|
||||
import * as sdk from '@anthropic-ai/claude-agent-sdk';
|
||||
import { collectAsyncGenerator } from '../../utils/helpers.js';
|
||||
|
||||
vi.mock("@anthropic-ai/claude-agent-sdk");
|
||||
vi.mock('@anthropic-ai/claude-agent-sdk');
|
||||
|
||||
describe("claude-provider.ts", () => {
|
||||
describe('claude-provider.ts', () => {
|
||||
let provider: ClaudeProvider;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -14,17 +14,17 @@ describe("claude-provider.ts", () => {
|
||||
delete process.env.ANTHROPIC_API_KEY;
|
||||
});
|
||||
|
||||
describe("getName", () => {
|
||||
describe('getName', () => {
|
||||
it("should return 'claude' as provider name", () => {
|
||||
expect(provider.getName()).toBe("claude");
|
||||
expect(provider.getName()).toBe('claude');
|
||||
});
|
||||
});
|
||||
|
||||
describe("executeQuery", () => {
|
||||
it("should execute simple text query", async () => {
|
||||
describe('executeQuery', () => {
|
||||
it('should execute simple text query', async () => {
|
||||
const mockMessages = [
|
||||
{ type: "text", text: "Response 1" },
|
||||
{ type: "text", text: "Response 2" },
|
||||
{ type: 'text', text: 'Response 1' },
|
||||
{ type: 'text', text: 'Response 2' },
|
||||
];
|
||||
|
||||
vi.mocked(sdk.query).mockReturnValue(
|
||||
@@ -36,95 +36,86 @@ describe("claude-provider.ts", () => {
|
||||
);
|
||||
|
||||
const generator = provider.executeQuery({
|
||||
prompt: "Hello",
|
||||
cwd: "/test",
|
||||
prompt: 'Hello',
|
||||
cwd: '/test',
|
||||
});
|
||||
|
||||
const results = await collectAsyncGenerator(generator);
|
||||
|
||||
expect(results).toHaveLength(2);
|
||||
expect(results[0]).toEqual({ type: "text", text: "Response 1" });
|
||||
expect(results[1]).toEqual({ type: "text", text: "Response 2" });
|
||||
expect(results[0]).toEqual({ type: 'text', text: 'Response 1' });
|
||||
expect(results[1]).toEqual({ type: 'text', text: 'Response 2' });
|
||||
});
|
||||
|
||||
it("should pass correct options to SDK", async () => {
|
||||
it('should pass correct options to SDK', async () => {
|
||||
vi.mocked(sdk.query).mockReturnValue(
|
||||
(async function* () {
|
||||
yield { type: "text", text: "test" };
|
||||
yield { type: 'text', text: 'test' };
|
||||
})()
|
||||
);
|
||||
|
||||
const generator = provider.executeQuery({
|
||||
prompt: "Test prompt",
|
||||
model: "claude-opus-4-5-20251101",
|
||||
cwd: "/test/dir",
|
||||
systemPrompt: "You are helpful",
|
||||
prompt: 'Test prompt',
|
||||
model: 'claude-opus-4-5-20251101',
|
||||
cwd: '/test/dir',
|
||||
systemPrompt: 'You are helpful',
|
||||
maxTurns: 10,
|
||||
allowedTools: ["Read", "Write"],
|
||||
allowedTools: ['Read', 'Write'],
|
||||
});
|
||||
|
||||
await collectAsyncGenerator(generator);
|
||||
|
||||
expect(sdk.query).toHaveBeenCalledWith({
|
||||
prompt: "Test prompt",
|
||||
prompt: 'Test prompt',
|
||||
options: expect.objectContaining({
|
||||
model: "claude-opus-4-5-20251101",
|
||||
systemPrompt: "You are helpful",
|
||||
model: 'claude-opus-4-5-20251101',
|
||||
systemPrompt: 'You are helpful',
|
||||
maxTurns: 10,
|
||||
cwd: "/test/dir",
|
||||
allowedTools: ["Read", "Write"],
|
||||
permissionMode: "acceptEdits",
|
||||
cwd: '/test/dir',
|
||||
allowedTools: ['Read', 'Write'],
|
||||
permissionMode: 'acceptEdits',
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it("should use default allowed tools when not specified", async () => {
|
||||
it('should use default allowed tools when not specified', async () => {
|
||||
vi.mocked(sdk.query).mockReturnValue(
|
||||
(async function* () {
|
||||
yield { type: "text", text: "test" };
|
||||
yield { type: 'text', text: 'test' };
|
||||
})()
|
||||
);
|
||||
|
||||
const generator = provider.executeQuery({
|
||||
prompt: "Test",
|
||||
cwd: "/test",
|
||||
prompt: 'Test',
|
||||
cwd: '/test',
|
||||
});
|
||||
|
||||
await collectAsyncGenerator(generator);
|
||||
|
||||
expect(sdk.query).toHaveBeenCalledWith({
|
||||
prompt: "Test",
|
||||
prompt: 'Test',
|
||||
options: expect.objectContaining({
|
||||
allowedTools: [
|
||||
"Read",
|
||||
"Write",
|
||||
"Edit",
|
||||
"Glob",
|
||||
"Grep",
|
||||
"Bash",
|
||||
"WebSearch",
|
||||
"WebFetch",
|
||||
],
|
||||
allowedTools: ['Read', 'Write', 'Edit', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch'],
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it("should enable sandbox by default", async () => {
|
||||
it('should enable sandbox by default', async () => {
|
||||
vi.mocked(sdk.query).mockReturnValue(
|
||||
(async function* () {
|
||||
yield { type: "text", text: "test" };
|
||||
yield { type: 'text', text: 'test' };
|
||||
})()
|
||||
);
|
||||
|
||||
const generator = provider.executeQuery({
|
||||
prompt: "Test",
|
||||
cwd: "/test",
|
||||
prompt: 'Test',
|
||||
cwd: '/test',
|
||||
});
|
||||
|
||||
await collectAsyncGenerator(generator);
|
||||
|
||||
expect(sdk.query).toHaveBeenCalledWith({
|
||||
prompt: "Test",
|
||||
prompt: 'Test',
|
||||
options: expect.objectContaining({
|
||||
sandbox: {
|
||||
enabled: true,
|
||||
@@ -134,118 +125,142 @@ describe("claude-provider.ts", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should pass abortController if provided", async () => {
|
||||
it('should pass abortController if provided', async () => {
|
||||
vi.mocked(sdk.query).mockReturnValue(
|
||||
(async function* () {
|
||||
yield { type: "text", text: "test" };
|
||||
yield { type: 'text', text: 'test' };
|
||||
})()
|
||||
);
|
||||
|
||||
const abortController = new AbortController();
|
||||
|
||||
const generator = provider.executeQuery({
|
||||
prompt: "Test",
|
||||
cwd: "/test",
|
||||
prompt: 'Test',
|
||||
cwd: '/test',
|
||||
abortController,
|
||||
});
|
||||
|
||||
await collectAsyncGenerator(generator);
|
||||
|
||||
expect(sdk.query).toHaveBeenCalledWith({
|
||||
prompt: "Test",
|
||||
prompt: 'Test',
|
||||
options: expect.objectContaining({
|
||||
abortController,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle conversation history with sdkSessionId using resume option", async () => {
|
||||
it('should handle conversation history with sdkSessionId using resume option', async () => {
|
||||
vi.mocked(sdk.query).mockReturnValue(
|
||||
(async function* () {
|
||||
yield { type: "text", text: "test" };
|
||||
yield { type: 'text', text: 'test' };
|
||||
})()
|
||||
);
|
||||
|
||||
const conversationHistory = [
|
||||
{ role: "user" as const, content: "Previous message" },
|
||||
{ role: "assistant" as const, content: "Previous response" },
|
||||
{ role: 'user' as const, content: 'Previous message' },
|
||||
{ role: 'assistant' as const, content: 'Previous response' },
|
||||
];
|
||||
|
||||
const generator = provider.executeQuery({
|
||||
prompt: "Current message",
|
||||
cwd: "/test",
|
||||
prompt: 'Current message',
|
||||
cwd: '/test',
|
||||
conversationHistory,
|
||||
sdkSessionId: "test-session-id",
|
||||
sdkSessionId: 'test-session-id',
|
||||
});
|
||||
|
||||
await collectAsyncGenerator(generator);
|
||||
|
||||
// Should use resume option when sdkSessionId is provided with history
|
||||
expect(sdk.query).toHaveBeenCalledWith({
|
||||
prompt: "Current message",
|
||||
prompt: 'Current message',
|
||||
options: expect.objectContaining({
|
||||
resume: "test-session-id",
|
||||
resume: 'test-session-id',
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle array prompt (with images)", async () => {
|
||||
it('should handle array prompt (with images)', async () => {
|
||||
vi.mocked(sdk.query).mockReturnValue(
|
||||
(async function* () {
|
||||
yield { type: "text", text: "test" };
|
||||
yield { type: 'text', text: 'test' };
|
||||
})()
|
||||
);
|
||||
|
||||
const arrayPrompt = [
|
||||
{ type: "text", text: "Describe this" },
|
||||
{ type: "image", source: { type: "base64", data: "..." } },
|
||||
{ type: 'text', text: 'Describe this' },
|
||||
{ type: 'image', source: { type: 'base64', data: '...' } },
|
||||
];
|
||||
|
||||
const generator = provider.executeQuery({
|
||||
prompt: arrayPrompt as any,
|
||||
cwd: "/test",
|
||||
cwd: '/test',
|
||||
});
|
||||
|
||||
await collectAsyncGenerator(generator);
|
||||
|
||||
// Should pass an async generator as prompt for array inputs
|
||||
const callArgs = vi.mocked(sdk.query).mock.calls[0][0];
|
||||
expect(typeof callArgs.prompt).not.toBe("string");
|
||||
expect(typeof callArgs.prompt).not.toBe('string');
|
||||
});
|
||||
|
||||
it("should use maxTurns default of 20", async () => {
|
||||
it('should use maxTurns default of 20', async () => {
|
||||
vi.mocked(sdk.query).mockReturnValue(
|
||||
(async function* () {
|
||||
yield { type: "text", text: "test" };
|
||||
yield { type: 'text', text: 'test' };
|
||||
})()
|
||||
);
|
||||
|
||||
const generator = provider.executeQuery({
|
||||
prompt: "Test",
|
||||
cwd: "/test",
|
||||
prompt: 'Test',
|
||||
cwd: '/test',
|
||||
});
|
||||
|
||||
await collectAsyncGenerator(generator);
|
||||
|
||||
expect(sdk.query).toHaveBeenCalledWith({
|
||||
prompt: "Test",
|
||||
prompt: 'Test',
|
||||
options: expect.objectContaining({
|
||||
maxTurns: 20,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle errors during execution and rethrow', async () => {
|
||||
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const testError = new Error('SDK execution failed');
|
||||
|
||||
vi.mocked(sdk.query).mockReturnValue(
|
||||
(async function* () {
|
||||
throw testError;
|
||||
})()
|
||||
);
|
||||
|
||||
const generator = provider.executeQuery({
|
||||
prompt: 'Test',
|
||||
cwd: '/test',
|
||||
});
|
||||
|
||||
await expect(collectAsyncGenerator(generator)).rejects.toThrow('SDK execution failed');
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
'[ClaudeProvider] executeQuery() error during execution:',
|
||||
testError
|
||||
);
|
||||
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe("detectInstallation", () => {
|
||||
it("should return installed with SDK method", async () => {
|
||||
describe('detectInstallation', () => {
|
||||
it('should return installed with SDK method', async () => {
|
||||
const result = await provider.detectInstallation();
|
||||
|
||||
expect(result.installed).toBe(true);
|
||||
expect(result.method).toBe("sdk");
|
||||
expect(result.method).toBe('sdk');
|
||||
});
|
||||
|
||||
it("should detect ANTHROPIC_API_KEY", async () => {
|
||||
process.env.ANTHROPIC_API_KEY = "test-key";
|
||||
it('should detect ANTHROPIC_API_KEY', async () => {
|
||||
process.env.ANTHROPIC_API_KEY = 'test-key';
|
||||
|
||||
const result = await provider.detectInstallation();
|
||||
|
||||
@@ -253,7 +268,7 @@ describe("claude-provider.ts", () => {
|
||||
expect(result.authenticated).toBe(true);
|
||||
});
|
||||
|
||||
it("should return hasApiKey false when no keys present", async () => {
|
||||
it('should return hasApiKey false when no keys present', async () => {
|
||||
const result = await provider.detectInstallation();
|
||||
|
||||
expect(result.hasApiKey).toBe(false);
|
||||
@@ -261,54 +276,52 @@ describe("claude-provider.ts", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("getAvailableModels", () => {
|
||||
it("should return 4 Claude models", () => {
|
||||
describe('getAvailableModels', () => {
|
||||
it('should return 4 Claude models', () => {
|
||||
const models = provider.getAvailableModels();
|
||||
|
||||
expect(models).toHaveLength(4);
|
||||
});
|
||||
|
||||
it("should include Claude Opus 4.5", () => {
|
||||
it('should include Claude Opus 4.5', () => {
|
||||
const models = provider.getAvailableModels();
|
||||
|
||||
const opus = models.find((m) => m.id === "claude-opus-4-5-20251101");
|
||||
const opus = models.find((m) => m.id === 'claude-opus-4-5-20251101');
|
||||
expect(opus).toBeDefined();
|
||||
expect(opus?.name).toBe("Claude Opus 4.5");
|
||||
expect(opus?.provider).toBe("anthropic");
|
||||
expect(opus?.name).toBe('Claude Opus 4.5');
|
||||
expect(opus?.provider).toBe('anthropic');
|
||||
});
|
||||
|
||||
it("should include Claude Sonnet 4", () => {
|
||||
it('should include Claude Sonnet 4', () => {
|
||||
const models = provider.getAvailableModels();
|
||||
|
||||
const sonnet = models.find((m) => m.id === "claude-sonnet-4-20250514");
|
||||
const sonnet = models.find((m) => m.id === 'claude-sonnet-4-20250514');
|
||||
expect(sonnet).toBeDefined();
|
||||
expect(sonnet?.name).toBe("Claude Sonnet 4");
|
||||
expect(sonnet?.name).toBe('Claude Sonnet 4');
|
||||
});
|
||||
|
||||
it("should include Claude 3.5 Sonnet", () => {
|
||||
it('should include Claude 3.5 Sonnet', () => {
|
||||
const models = provider.getAvailableModels();
|
||||
|
||||
const sonnet35 = models.find(
|
||||
(m) => m.id === "claude-3-5-sonnet-20241022"
|
||||
);
|
||||
const sonnet35 = models.find((m) => m.id === 'claude-3-5-sonnet-20241022');
|
||||
expect(sonnet35).toBeDefined();
|
||||
});
|
||||
|
||||
it("should include Claude 3.5 Haiku", () => {
|
||||
it('should include Claude 3.5 Haiku', () => {
|
||||
const models = provider.getAvailableModels();
|
||||
|
||||
const haiku = models.find((m) => m.id === "claude-3-5-haiku-20241022");
|
||||
const haiku = models.find((m) => m.id === 'claude-3-5-haiku-20241022');
|
||||
expect(haiku).toBeDefined();
|
||||
});
|
||||
|
||||
it("should mark Opus as default", () => {
|
||||
it('should mark Opus as default', () => {
|
||||
const models = provider.getAvailableModels();
|
||||
|
||||
const opus = models.find((m) => m.id === "claude-opus-4-5-20251101");
|
||||
const opus = models.find((m) => m.id === 'claude-opus-4-5-20251101');
|
||||
expect(opus?.default).toBe(true);
|
||||
});
|
||||
|
||||
it("should all support vision and tools", () => {
|
||||
it('should all support vision and tools', () => {
|
||||
const models = provider.getAvailableModels();
|
||||
|
||||
models.forEach((model) => {
|
||||
@@ -317,7 +330,7 @@ describe("claude-provider.ts", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should have correct context windows", () => {
|
||||
it('should have correct context windows', () => {
|
||||
const models = provider.getAvailableModels();
|
||||
|
||||
models.forEach((model) => {
|
||||
@@ -325,7 +338,7 @@ describe("claude-provider.ts", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should have modelString field matching id", () => {
|
||||
it('should have modelString field matching id', () => {
|
||||
const models = provider.getAvailableModels();
|
||||
|
||||
models.forEach((model) => {
|
||||
@@ -334,38 +347,38 @@ describe("claude-provider.ts", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("supportsFeature", () => {
|
||||
describe('supportsFeature', () => {
|
||||
it("should support 'tools' feature", () => {
|
||||
expect(provider.supportsFeature("tools")).toBe(true);
|
||||
expect(provider.supportsFeature('tools')).toBe(true);
|
||||
});
|
||||
|
||||
it("should support 'text' feature", () => {
|
||||
expect(provider.supportsFeature("text")).toBe(true);
|
||||
expect(provider.supportsFeature('text')).toBe(true);
|
||||
});
|
||||
|
||||
it("should support 'vision' feature", () => {
|
||||
expect(provider.supportsFeature("vision")).toBe(true);
|
||||
expect(provider.supportsFeature('vision')).toBe(true);
|
||||
});
|
||||
|
||||
it("should support 'thinking' feature", () => {
|
||||
expect(provider.supportsFeature("thinking")).toBe(true);
|
||||
expect(provider.supportsFeature('thinking')).toBe(true);
|
||||
});
|
||||
|
||||
it("should not support 'mcp' feature", () => {
|
||||
expect(provider.supportsFeature("mcp")).toBe(false);
|
||||
expect(provider.supportsFeature('mcp')).toBe(false);
|
||||
});
|
||||
|
||||
it("should not support 'cli' feature", () => {
|
||||
expect(provider.supportsFeature("cli")).toBe(false);
|
||||
expect(provider.supportsFeature('cli')).toBe(false);
|
||||
});
|
||||
|
||||
it("should not support unknown features", () => {
|
||||
expect(provider.supportsFeature("unknown")).toBe(false);
|
||||
it('should not support unknown features', () => {
|
||||
expect(provider.supportsFeature('unknown')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("validateConfig", () => {
|
||||
it("should validate config from base class", () => {
|
||||
describe('validateConfig', () => {
|
||||
it('should validate config from base class', () => {
|
||||
const result = provider.validateConfig();
|
||||
|
||||
expect(result.valid).toBe(true);
|
||||
@@ -373,21 +386,21 @@ describe("claude-provider.ts", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("config management", () => {
|
||||
it("should get and set config", () => {
|
||||
provider.setConfig({ apiKey: "test-key" });
|
||||
describe('config management', () => {
|
||||
it('should get and set config', () => {
|
||||
provider.setConfig({ apiKey: 'test-key' });
|
||||
|
||||
const config = provider.getConfig();
|
||||
expect(config.apiKey).toBe("test-key");
|
||||
expect(config.apiKey).toBe('test-key');
|
||||
});
|
||||
|
||||
it("should merge config updates", () => {
|
||||
provider.setConfig({ apiKey: "key1" });
|
||||
provider.setConfig({ model: "model1" });
|
||||
it('should merge config updates', () => {
|
||||
provider.setConfig({ apiKey: 'key1' });
|
||||
provider.setConfig({ model: 'model1' });
|
||||
|
||||
const config = provider.getConfig();
|
||||
expect(config.apiKey).toBe("key1");
|
||||
expect(config.model).toBe("model1");
|
||||
expect(config.apiKey).toBe('key1');
|
||||
expect(config.model).toBe('model1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user