andy-stack_vaultkeeper-ai/__tests__/AIClasses/ClaudeConversationNamingAgent.test.ts
Andrew Beal 1c5834e858 Update AI model references to latest versions
Replace Claude Sonnet 4.6 with Sonnet 5 as default model, add Claude Fable 5, update OpenAI models to include versioned releases (GPT-5.5 Pro, dated variants), and update all references across enums, settings, tests, and documentation.
2026-07-06 19:37:41 +01:00

161 lines
5.7 KiB
TypeScript

import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import { ClaudeConversationNamingAgent } from '../../AIClasses/Claude/ClaudeConversationNamingAgent';
import { RegisterSingleton, DeregisterAllServices } from '../../Services/DependencyService';
import { Services } from '../../Services/Services';
import { AIProvider, AIProviderModel } from '../../Enums/ApiProvider';
import { Role } from '../../Enums/Role';
describe('ClaudeConversationNamingAgent', () => {
let service: ClaudeConversationNamingAgent;
let mockPlugin: any;
let mockSettingsService: any;
let mockAbortService: any;
let fetchMock: any;
beforeEach(() => {
mockPlugin = {};
RegisterSingleton(Services.VaultkeeperAIPlugin, mockPlugin);
// Mock SettingsService
mockSettingsService = {
settings: {
model: AIProviderModel.ClaudeSonnet_5,
apiKeys: {
claude: 'test-claude-key',
openai: 'test-openai-key',
gemini: 'test-gemini-key', mistral: 'test-mistral-key'
}
},
getApiKeyForProvider: vi.fn((provider: AIProvider) => {
if (provider === AIProvider.Claude) return 'test-claude-key';
if (provider === AIProvider.OpenAI) return 'test-openai-key';
if (provider === AIProvider.Gemini) return 'test-gemini-key';
return '';
}),
getApiKeyForCurrentProvider: vi.fn(() => 'test-claude-key')
};
RegisterSingleton(Services.SettingsService, mockSettingsService);
// Mock AbortService
mockAbortService = {
signal: vi.fn(() => new AbortController().signal),
abortableOperation: vi.fn((fn) => fn())
};
RegisterSingleton(Services.AbortService, mockAbortService);
// Mock global fetch
fetchMock = vi.fn();
global.fetch = fetchMock;
service = new ClaudeConversationNamingAgent();
});
afterEach(() => {
// Clear singleton registry to prevent memory leaks
DeregisterAllServices();
vi.restoreAllMocks();
});
describe('generateName', () => {
it('should make request with correct format', async () => {
fetchMock.mockResolvedValue({
ok: true,
json: async () => ({
content: [{ type: 'text', text: 'Test Conversation' }]
})
});
await service.generateName('User prompt');
expect(fetchMock).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
method: 'POST',
headers: {
'x-api-key': 'test-claude-key',
'anthropic-version': '2023-06-01',
'anthropic-dangerous-direct-browser-access': 'true',
'content-type': 'application/json'
},
body: expect.any(String)
})
);
const requestBody = JSON.parse(fetchMock.mock.calls[0][1].body);
expect(requestBody.model).toBe(AIProviderModel.ClaudeNamer);
expect(requestBody.max_tokens).toBe(100);
expect(requestBody.system).toBeDefined();
expect(requestBody.messages).toHaveLength(1);
expect(requestBody.messages[0].role).toBe(Role.User);
expect(requestBody.messages[0].content).toBe('User prompt');
});
it('should return generated name from response', async () => {
fetchMock.mockResolvedValue({
ok: true,
json: async () => ({
content: [{ type: 'text', text: 'Generated Name' }]
})
});
const result = await service.generateName('Test prompt');
expect(result).toBe('Generated Name');
});
it('should throw error on API error response', async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 401,
statusText: 'Unauthorized',
text: async () => 'Invalid API key'
});
await expect(service.generateName('Test'))
.rejects.toThrow('Claude API error: 401 Unauthorized - Invalid API key');
});
it('should throw error when response has no content', async () => {
fetchMock.mockResolvedValue({
ok: true,
json: async () => ({
content: []
})
});
await expect(service.generateName('Test'))
.rejects.toThrow('Failed to generate conversation name');
});
it('should pass abort signal to fetch', async () => {
fetchMock.mockResolvedValue({
ok: true,
json: async () => ({
content: [{ type: 'text', text: 'Name' }]
})
});
await service.generateName('Test');
expect(fetchMock).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
signal: expect.any(AbortSignal)
})
);
});
it('should handle malformed response structure', async () => {
fetchMock.mockResolvedValue({
ok: true,
json: async () => ({
// Missing content array
other: 'data'
})
});
await expect(service.generateName('Test'))
.rejects.toThrow('Failed to generate conversation name');
});
});
});