1 line
64 KiB
JSON
1 line
64 KiB
JSON
{"repo": "amiantos/writers-guild", "files": {"server/src/services/providers/__tests__/deepseek-provider.test.js": {"adaf2b2ed11b3333dd3ce04081c4899b4654df00": {"t": 1785746016.5392542, "n": 13036, "h": "eba96d1c71bb99ec", "c": "import { describe, it, expect, beforeEach, vi } from 'vitest';\nimport { DeepSeekProvider } from '../deepseek-provider.js';\n\ndescribe('DeepSeekProvider', () => {\n let provider;\n let mockFetch;\n\n beforeEach(() => {\n // Mock global fetch\n mockFetch = vi.fn();\n global.fetch = mockFetch;\n\n provider = new DeepSeekProvider({\n apiKey: 'test-api-key',\n model: 'deepseek-v4-flash',\n });\n });\n\n describe('Configuration', () => {\n it('should initialize with correct defaults', () => {\n const defaultProvider = new DeepSeekProvider({\n apiKey: 'test-key',\n });\n\n expect(defaultProvider.baseURL).toBe('https://api.deepseek.com/v1');\n expect(defaultProvider.model).toBe('deepseek-v4-flash');\n });\n\n it('should allow custom base URL', () => {\n const customProvider = new DeepSeekProvider({\n apiKey: 'test-key',\n baseURL: 'https://custom-api.com/v1',\n });\n\n expect(customProvider.baseURL).toBe('https://custom-api.com/v1');\n });\n\n it('should allow custom model', () => {\n const customProvider = new DeepSeekProvider({\n apiKey: 'test-key',\n model: 'deepseek-v4-pro',\n });\n\n expect(customProvider.model).toBe('deepseek-v4-pro');\n });\n });\n\n describe('Capabilities', () => {\n it('should return correct capabilities', () => {\n const capabilities = provider.getCapabilities();\n\n expect(capabilities.streaming).toBe(true);\n expect(capabilities.reasoning).toBe(true);\n expect(capabilities.visionAPI).toBe(false);\n expect(capabilities.maxContextWindow).toBe(1000000);\n });\n });\n\n describe('Validation', () => {\n it('should validate correct configuration', () => {\n const result = provider.validateConfig();\n expect(result.valid).toBe(true);\n });\n\n it('should reject missing API key', () => {\n const invalidProvider = new DeepSeekProvider({ apiKey: '' });\n const result = invalidProvider.validateConfig();\n\n expect(result.valid).toBe(false);\n expect(result.error).toBe('API key is required');\n });\n\n it('should reject whitespace-only API key', () => {\n const invalidProvider = new DeepSeekProvider({ apiKey: ' ' });\n const result = invalidProvider.validateConfig();\n\n expect(result.valid).toBe(false);\n });\n });\n\n describe('Thinking mode request body', () => {\n beforeEach(() => {\n mockFetch.mockResolvedValue({\n ok: true,\n json: async () => ({ choices: [{ message: { content: 'r' } }] }),\n });\n });\n\n it('sends thinking: disabled by default', async () => {\n await provider.generate('System', 'User');\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.thinking).toEqual({ type: 'disabled' });\n });\n\n it('sends thinking: enabled with high effort by default when thinking=true', async () => {\n await provider.generate('System', 'User', { thinking: true });\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.thinking).toEqual({ type: 'enabled', reasoning_effort: 'high' });\n });\n\n it('honors reasoningEffort=max', async () => {\n await provider.generate('System', 'User', { thinking: true, reasoningEffort: 'max' });\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.thinking).toEqual({ type: 'enabled', reasoning_effort: 'max' });\n });\n\n it('coerces unknown reasoningEffort values to high', async () => {\n await provider.generate('System', 'User', { thinking: true, reasoningEffort: 'low' });\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.thinking.reasoning_effort).toBe('high');\n });\n\n it('omits sampling params when thinking is enabled', async () => {\n await provider.generate('System', 'User', {\n thinking: true,\n temperature: 0.7,\n top_p: 0.9,\n frequency_penalty: 0.5,\n presence_penalty: 0.3,\n });\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.temperature).toBeUndefined();\n expect(body.top_p).toBeUndefined();\n expect(body.frequency_penalty).toBeUndefined();\n expect(body.presence_penalty).toBeUndefined();\n });\n\n it('includes sampling params when thinking is disabled', async () => {\n await provider.generate('System', 'User', {\n temperature: 0.7,\n top_p: 0.9,\n frequency_penalty: 0.5,\n presence_penalty: 0.3,\n });\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.temperature).toBe(0.7);\n expect(body.top_p).toBe(0.9);\n expect(body.frequency_penalty).toBe(0.5);\n expect(body.presence_penalty).toBe(0.3);\n });\n });\n\n describe('Generate (Non-streaming)', () => {\n it('should generate content successfully', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n choices: [\n {\n message: {\n content: 'Generated response',\n reasoning_content: 'Thinking process',\n },\n },\n ],\n usage: {\n prompt_tokens: 100,\n completion_tokens: 50,\n },\n }),\n });\n\n const result = await provider.generate('You are a helpful assistant', 'Hello, how are you?');\n\n expect(result.content).toBe('Generated response');\n expect(result.reasoning).toBe('Thinking process');\n expect(mockFetch).toHaveBeenCalledTimes(1);\n });\n\n it('should build correct API request', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n choices: [{ message: { content: 'Response' } }],\n }),\n });\n\n await provider.generate('System prompt', 'User prompt', {\n maxTokens: 2000,\n temperature: 0.7,\n });\n\n const callArgs = mockFetch.mock.calls[0];\n expect(callArgs[0]).toBe('https://api.deepseek.com/v1/chat/completions');\n\n const headers = callArgs[1].headers;\n expect(headers['Content-Type']).toBe('application/json');\n expect(headers['Authorization']).toBe('Bearer test-api-key');\n\n const requestBody = JSON.parse(callArgs[1].body);\n expect(requestBody.stream).toBe(false);\n expect(requestBody.model).toBe('deepseek-v4-flash');\n expect(requestBody.max_tokens).toBe(2000);\n expect(requestBody.temperature).toBe(0.7);\n });\n\n it('should throw error when API key is missing', async () => {\n const noKeyProvider = new DeepSeekProvider({ apiKey: '' });\n\n await expect(noKeyProvider.generate('System', 'User')).rejects.toThrow('API key not set');\n });\n\n it('should handle API errors with error message', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: false,\n statusText: 'Bad Request',\n json: async () => ({\n error: { message: 'Invalid request' },\n }),\n });\n\n await expect(provider.generate('System', 'User')).rejects.toThrow('Invalid request');\n });\n\n it('should handle API errors without error message', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: false,\n statusText: 'Internal Server Error',\n json: async () => ({}),\n });\n\n await expect(provider.generate('System', 'User')).rejects.toThrow(\n 'API request failed: Internal Server Error',\n );\n });\n\n it('should include optional sampling params when thinking is off', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n choices: [{ message: { content: 'Response' } }],\n }),\n });\n\n await provider.generate('System', 'User', {\n top_p: 0.9,\n frequency_penalty: 0.5,\n presence_penalty: 0.3,\n stop_sequences: ['STOP'],\n });\n\n const requestBody = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(requestBody.top_p).toBe(0.9);\n expect(requestBody.frequency_penalty).toBe(0.5);\n expect(requestBody.presence_penalty).toBe(0.3);\n expect(requestBody.stop).toEqual(['STOP']);\n });\n\n it('should pass abort signal to fetch', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n choices: [{ message: { content: 'Response' } }],\n }),\n });\n\n const controller = new AbortController();\n await provider.generate('System', 'User', { signal: controller.signal });\n\n expect(mockFetch.mock.calls[0][1].signal).toBe(controller.signal);\n });\n });\n\n describe('Generate Streaming', () => {\n it('should throw error when API key is missing', async () => {\n const noKeyProvider = new DeepSeekProvider({ apiKey: '' });\n\n await expect(noKeyProvider.generateStreaming('System', 'User')).rejects.toThrow(\n 'API key not set',\n );\n });\n\n it('should return stream object with abort function', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n body: {\n getReader: () => ({\n read: async () => ({ done: true }),\n }),\n },\n });\n\n const result = await provider.generateStreaming('System', 'User');\n\n expect(result).toHaveProperty('stream');\n expect(result).toHaveProperty('abort');\n expect(result).toHaveProperty('metadata');\n expect(typeof result.abort).toBe('function');\n });\n\n it('should handle API errors', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: false,\n statusText: 'Server Error',\n json: async () => ({\n error: { message: 'Server error' },\n }),\n });\n\n await expect(provider.generateStreaming('System', 'User')).rejects.toThrow('Server error');\n });\n\n it('forwards thinking flag through streaming path', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n body: { getReader: () => ({ read: async () => ({ done: true }) }) },\n });\n\n await provider.generateStreaming('System', 'User', {\n thinking: true,\n reasoningEffort: 'max',\n });\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.stream).toBe(true);\n expect(body.thinking).toEqual({ type: 'enabled', reasoning_effort: 'max' });\n });\n });\n\n describe('Error Parsing', () => {\n it('should parse authentication errors', () => {\n const error = new Error('401 Unauthorized');\n const parsed = provider.parseError(error);\n\n expect(parsed.code).toBe('AUTH_ERROR');\n expect(parsed.message).toBe('Invalid API key');\n });\n\n it('should parse rate limit errors', () => {\n const error = new Error('429 rate limit exceeded');\n const parsed = provider.parseError(error);\n\n expect(parsed.code).toBe('RATE_LIMIT');\n expect(parsed.message).toContain('Rate limit');\n });\n\n it('should return generic error for unknown errors', () => {\n const error = new Error('Unknown error');\n const parsed = provider.parseError(error);\n\n expect(parsed.code).toBe('API_ERROR');\n });\n });\n\n describe('getAvailableModels', () => {\n it('should fetch and transform models', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n data: [\n { id: 'deepseek-v4-flash', created: 1234567890, owned_by: 'deepseek' },\n { id: 'deepseek-v4-pro', created: 1234567891, owned_by: 'deepseek' },\n ],\n }),\n });\n\n const models = await provider.getAvailableModels();\n\n expect(models).toHaveLength(2);\n expect(models[0].id).toBe('deepseek-v4-flash');\n expect(models[0].contextLength).toBe(1000000);\n });\n\n it('should return empty array on error', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: false,\n statusText: 'Server Error',\n });\n\n const models = await provider.getAvailableModels();\n expect(models).toEqual([]);\n });\n });\n\n describe('getModelDescription', () => {\n it('should return description for deepseek-v4-flash', () => {\n const description = provider.getModelDescription('deepseek-v4-flash');\n expect(description).toContain('Flash');\n });\n\n it('should return description for deepseek-v4-pro', () => {\n const description = provider.getModelDescription('deepseek-v4-pro');\n expect(description).toContain('Pro');\n });\n\n it('should mark deepseek-chat as deprecated', () => {\n const description = provider.getModelDescription('deepseek-chat');\n expect(description.toLowerCase()).toContain('deprecated');\n });\n\n it('should mark deepseek-reasoner as deprecated', () => {\n const description = provider.getModelDescription('deepseek-reasoner');\n expect(description.toLowerCase()).toContain('deprecated');\n });\n\n it('should return empty string for unknown model', () => {\n const description = provider.getModelDescription('unknown-model');\n expect(description).toBe('');\n });\n });\n});\n"}, "af74f2a9a751a83d6a1a68a2872c8820d6de72ac": {"t": 1785758804.6037772, "n": 13036, "h": "eba96d1c71bb99ec", "c": "import { describe, it, expect, beforeEach, vi } from 'vitest';\nimport { DeepSeekProvider } from '../deepseek-provider.js';\n\ndescribe('DeepSeekProvider', () => {\n let provider;\n let mockFetch;\n\n beforeEach(() => {\n // Mock global fetch\n mockFetch = vi.fn();\n global.fetch = mockFetch;\n\n provider = new DeepSeekProvider({\n apiKey: 'test-api-key',\n model: 'deepseek-v4-flash',\n });\n });\n\n describe('Configuration', () => {\n it('should initialize with correct defaults', () => {\n const defaultProvider = new DeepSeekProvider({\n apiKey: 'test-key',\n });\n\n expect(defaultProvider.baseURL).toBe('https://api.deepseek.com/v1');\n expect(defaultProvider.model).toBe('deepseek-v4-flash');\n });\n\n it('should allow custom base URL', () => {\n const customProvider = new DeepSeekProvider({\n apiKey: 'test-key',\n baseURL: 'https://custom-api.com/v1',\n });\n\n expect(customProvider.baseURL).toBe('https://custom-api.com/v1');\n });\n\n it('should allow custom model', () => {\n const customProvider = new DeepSeekProvider({\n apiKey: 'test-key',\n model: 'deepseek-v4-pro',\n });\n\n expect(customProvider.model).toBe('deepseek-v4-pro');\n });\n });\n\n describe('Capabilities', () => {\n it('should return correct capabilities', () => {\n const capabilities = provider.getCapabilities();\n\n expect(capabilities.streaming).toBe(true);\n expect(capabilities.reasoning).toBe(true);\n expect(capabilities.visionAPI).toBe(false);\n expect(capabilities.maxContextWindow).toBe(1000000);\n });\n });\n\n describe('Validation', () => {\n it('should validate correct configuration', () => {\n const result = provider.validateConfig();\n expect(result.valid).toBe(true);\n });\n\n it('should reject missing API key', () => {\n const invalidProvider = new DeepSeekProvider({ apiKey: '' });\n const result = invalidProvider.validateConfig();\n\n expect(result.valid).toBe(false);\n expect(result.error).toBe('API key is required');\n });\n\n it('should reject whitespace-only API key', () => {\n const invalidProvider = new DeepSeekProvider({ apiKey: ' ' });\n const result = invalidProvider.validateConfig();\n\n expect(result.valid).toBe(false);\n });\n });\n\n describe('Thinking mode request body', () => {\n beforeEach(() => {\n mockFetch.mockResolvedValue({\n ok: true,\n json: async () => ({ choices: [{ message: { content: 'r' } }] }),\n });\n });\n\n it('sends thinking: disabled by default', async () => {\n await provider.generate('System', 'User');\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.thinking).toEqual({ type: 'disabled' });\n });\n\n it('sends thinking: enabled with high effort by default when thinking=true', async () => {\n await provider.generate('System', 'User', { thinking: true });\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.thinking).toEqual({ type: 'enabled', reasoning_effort: 'high' });\n });\n\n it('honors reasoningEffort=max', async () => {\n await provider.generate('System', 'User', { thinking: true, reasoningEffort: 'max' });\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.thinking).toEqual({ type: 'enabled', reasoning_effort: 'max' });\n });\n\n it('coerces unknown reasoningEffort values to high', async () => {\n await provider.generate('System', 'User', { thinking: true, reasoningEffort: 'low' });\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.thinking.reasoning_effort).toBe('high');\n });\n\n it('omits sampling params when thinking is enabled', async () => {\n await provider.generate('System', 'User', {\n thinking: true,\n temperature: 0.7,\n top_p: 0.9,\n frequency_penalty: 0.5,\n presence_penalty: 0.3,\n });\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.temperature).toBeUndefined();\n expect(body.top_p).toBeUndefined();\n expect(body.frequency_penalty).toBeUndefined();\n expect(body.presence_penalty).toBeUndefined();\n });\n\n it('includes sampling params when thinking is disabled', async () => {\n await provider.generate('System', 'User', {\n temperature: 0.7,\n top_p: 0.9,\n frequency_penalty: 0.5,\n presence_penalty: 0.3,\n });\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.temperature).toBe(0.7);\n expect(body.top_p).toBe(0.9);\n expect(body.frequency_penalty).toBe(0.5);\n expect(body.presence_penalty).toBe(0.3);\n });\n });\n\n describe('Generate (Non-streaming)', () => {\n it('should generate content successfully', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n choices: [\n {\n message: {\n content: 'Generated response',\n reasoning_content: 'Thinking process',\n },\n },\n ],\n usage: {\n prompt_tokens: 100,\n completion_tokens: 50,\n },\n }),\n });\n\n const result = await provider.generate('You are a helpful assistant', 'Hello, how are you?');\n\n expect(result.content).toBe('Generated response');\n expect(result.reasoning).toBe('Thinking process');\n expect(mockFetch).toHaveBeenCalledTimes(1);\n });\n\n it('should build correct API request', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n choices: [{ message: { content: 'Response' } }],\n }),\n });\n\n await provider.generate('System prompt', 'User prompt', {\n maxTokens: 2000,\n temperature: 0.7,\n });\n\n const callArgs = mockFetch.mock.calls[0];\n expect(callArgs[0]).toBe('https://api.deepseek.com/v1/chat/completions');\n\n const headers = callArgs[1].headers;\n expect(headers['Content-Type']).toBe('application/json');\n expect(headers['Authorization']).toBe('Bearer test-api-key');\n\n const requestBody = JSON.parse(callArgs[1].body);\n expect(requestBody.stream).toBe(false);\n expect(requestBody.model).toBe('deepseek-v4-flash');\n expect(requestBody.max_tokens).toBe(2000);\n expect(requestBody.temperature).toBe(0.7);\n });\n\n it('should throw error when API key is missing', async () => {\n const noKeyProvider = new DeepSeekProvider({ apiKey: '' });\n\n await expect(noKeyProvider.generate('System', 'User')).rejects.toThrow('API key not set');\n });\n\n it('should handle API errors with error message', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: false,\n statusText: 'Bad Request',\n json: async () => ({\n error: { message: 'Invalid request' },\n }),\n });\n\n await expect(provider.generate('System', 'User')).rejects.toThrow('Invalid request');\n });\n\n it('should handle API errors without error message', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: false,\n statusText: 'Internal Server Error',\n json: async () => ({}),\n });\n\n await expect(provider.generate('System', 'User')).rejects.toThrow(\n 'API request failed: Internal Server Error',\n );\n });\n\n it('should include optional sampling params when thinking is off', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n choices: [{ message: { content: 'Response' } }],\n }),\n });\n\n await provider.generate('System', 'User', {\n top_p: 0.9,\n frequency_penalty: 0.5,\n presence_penalty: 0.3,\n stop_sequences: ['STOP'],\n });\n\n const requestBody = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(requestBody.top_p).toBe(0.9);\n expect(requestBody.frequency_penalty).toBe(0.5);\n expect(requestBody.presence_penalty).toBe(0.3);\n expect(requestBody.stop).toEqual(['STOP']);\n });\n\n it('should pass abort signal to fetch', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n choices: [{ message: { content: 'Response' } }],\n }),\n });\n\n const controller = new AbortController();\n await provider.generate('System', 'User', { signal: controller.signal });\n\n expect(mockFetch.mock.calls[0][1].signal).toBe(controller.signal);\n });\n });\n\n describe('Generate Streaming', () => {\n it('should throw error when API key is missing', async () => {\n const noKeyProvider = new DeepSeekProvider({ apiKey: '' });\n\n await expect(noKeyProvider.generateStreaming('System', 'User')).rejects.toThrow(\n 'API key not set',\n );\n });\n\n it('should return stream object with abort function', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n body: {\n getReader: () => ({\n read: async () => ({ done: true }),\n }),\n },\n });\n\n const result = await provider.generateStreaming('System', 'User');\n\n expect(result).toHaveProperty('stream');\n expect(result).toHaveProperty('abort');\n expect(result).toHaveProperty('metadata');\n expect(typeof result.abort).toBe('function');\n });\n\n it('should handle API errors', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: false,\n statusText: 'Server Error',\n json: async () => ({\n error: { message: 'Server error' },\n }),\n });\n\n await expect(provider.generateStreaming('System', 'User')).rejects.toThrow('Server error');\n });\n\n it('forwards thinking flag through streaming path', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n body: { getReader: () => ({ read: async () => ({ done: true }) }) },\n });\n\n await provider.generateStreaming('System', 'User', {\n thinking: true,\n reasoningEffort: 'max',\n });\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.stream).toBe(true);\n expect(body.thinking).toEqual({ type: 'enabled', reasoning_effort: 'max' });\n });\n });\n\n describe('Error Parsing', () => {\n it('should parse authentication errors', () => {\n const error = new Error('401 Unauthorized');\n const parsed = provider.parseError(error);\n\n expect(parsed.code).toBe('AUTH_ERROR');\n expect(parsed.message).toBe('Invalid API key');\n });\n\n it('should parse rate limit errors', () => {\n const error = new Error('429 rate limit exceeded');\n const parsed = provider.parseError(error);\n\n expect(parsed.code).toBe('RATE_LIMIT');\n expect(parsed.message).toContain('Rate limit');\n });\n\n it('should return generic error for unknown errors', () => {\n const error = new Error('Unknown error');\n const parsed = provider.parseError(error);\n\n expect(parsed.code).toBe('API_ERROR');\n });\n });\n\n describe('getAvailableModels', () => {\n it('should fetch and transform models', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n data: [\n { id: 'deepseek-v4-flash', created: 1234567890, owned_by: 'deepseek' },\n { id: 'deepseek-v4-pro', created: 1234567891, owned_by: 'deepseek' },\n ],\n }),\n });\n\n const models = await provider.getAvailableModels();\n\n expect(models).toHaveLength(2);\n expect(models[0].id).toBe('deepseek-v4-flash');\n expect(models[0].contextLength).toBe(1000000);\n });\n\n it('should return empty array on error', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: false,\n statusText: 'Server Error',\n });\n\n const models = await provider.getAvailableModels();\n expect(models).toEqual([]);\n });\n });\n\n describe('getModelDescription', () => {\n it('should return description for deepseek-v4-flash', () => {\n const description = provider.getModelDescription('deepseek-v4-flash');\n expect(description).toContain('Flash');\n });\n\n it('should return description for deepseek-v4-pro', () => {\n const description = provider.getModelDescription('deepseek-v4-pro');\n expect(description).toContain('Pro');\n });\n\n it('should mark deepseek-chat as deprecated', () => {\n const description = provider.getModelDescription('deepseek-chat');\n expect(description.toLowerCase()).toContain('deprecated');\n });\n\n it('should mark deepseek-reasoner as deprecated', () => {\n const description = provider.getModelDescription('deepseek-reasoner');\n expect(description.toLowerCase()).toContain('deprecated');\n });\n\n it('should return empty string for unknown model', () => {\n const description = provider.getModelDescription('unknown-model');\n expect(description).toBe('');\n });\n });\n});\n"}, "d68b09b832d1c48ba520833ad76e86c4a7f29798": {"t": 1785758806.0504093, "n": 12999, "h": "2c65eefb7798e04a", "c": "import { describe, it, expect, beforeEach, vi } from 'vitest';\nimport { DeepSeekProvider } from '../deepseek-provider.js';\n\ndescribe('DeepSeekProvider', () => {\n let provider;\n let mockFetch;\n\n beforeEach(() => {\n // Mock global fetch\n mockFetch = vi.fn();\n global.fetch = mockFetch;\n\n provider = new DeepSeekProvider({\n apiKey: 'test-api-key',\n model: 'deepseek-v4-flash'\n });\n });\n\n describe('Configuration', () => {\n it('should initialize with correct defaults', () => {\n const defaultProvider = new DeepSeekProvider({\n apiKey: 'test-key'\n });\n\n expect(defaultProvider.baseURL).toBe('https://api.deepseek.com/v1');\n expect(defaultProvider.model).toBe('deepseek-v4-flash');\n });\n\n it('should allow custom base URL', () => {\n const customProvider = new DeepSeekProvider({\n apiKey: 'test-key',\n baseURL: 'https://custom-api.com/v1'\n });\n\n expect(customProvider.baseURL).toBe('https://custom-api.com/v1');\n });\n\n it('should allow custom model', () => {\n const customProvider = new DeepSeekProvider({\n apiKey: 'test-key',\n model: 'deepseek-v4-pro'\n });\n\n expect(customProvider.model).toBe('deepseek-v4-pro');\n });\n });\n\n describe('Capabilities', () => {\n it('should return correct capabilities', () => {\n const capabilities = provider.getCapabilities();\n\n expect(capabilities.streaming).toBe(true);\n expect(capabilities.reasoning).toBe(true);\n expect(capabilities.visionAPI).toBe(false);\n expect(capabilities.maxContextWindow).toBe(1000000);\n });\n });\n\n describe('Validation', () => {\n it('should validate correct configuration', () => {\n const result = provider.validateConfig();\n expect(result.valid).toBe(true);\n });\n\n it('should reject missing API key', () => {\n const invalidProvider = new DeepSeekProvider({ apiKey: '' });\n const result = invalidProvider.validateConfig();\n\n expect(result.valid).toBe(false);\n expect(result.error).toBe('API key is required');\n });\n\n it('should reject whitespace-only API key', () => {\n const invalidProvider = new DeepSeekProvider({ apiKey: ' ' });\n const result = invalidProvider.validateConfig();\n\n expect(result.valid).toBe(false);\n });\n });\n\n describe('Thinking mode request body', () => {\n beforeEach(() => {\n mockFetch.mockResolvedValue({\n ok: true,\n json: async () => ({ choices: [{ message: { content: 'r' } }] })\n });\n });\n\n it('sends thinking: disabled by default', async () => {\n await provider.generate('System', 'User');\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.thinking).toEqual({ type: 'disabled' });\n });\n\n it('sends thinking: enabled with high effort by default when thinking=true', async () => {\n await provider.generate('System', 'User', { thinking: true });\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.thinking).toEqual({ type: 'enabled', reasoning_effort: 'high' });\n });\n\n it('honors reasoningEffort=max', async () => {\n await provider.generate('System', 'User', { thinking: true, reasoningEffort: 'max' });\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.thinking).toEqual({ type: 'enabled', reasoning_effort: 'max' });\n });\n\n it('coerces unknown reasoningEffort values to high', async () => {\n await provider.generate('System', 'User', { thinking: true, reasoningEffort: 'low' });\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.thinking.reasoning_effort).toBe('high');\n });\n\n it('omits sampling params when thinking is enabled', async () => {\n await provider.generate('System', 'User', {\n thinking: true,\n temperature: 0.7,\n top_p: 0.9,\n frequency_penalty: 0.5,\n presence_penalty: 0.3\n });\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.temperature).toBeUndefined();\n expect(body.top_p).toBeUndefined();\n expect(body.frequency_penalty).toBeUndefined();\n expect(body.presence_penalty).toBeUndefined();\n });\n\n it('includes sampling params when thinking is disabled', async () => {\n await provider.generate('System', 'User', {\n temperature: 0.7,\n top_p: 0.9,\n frequency_penalty: 0.5,\n presence_penalty: 0.3\n });\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.temperature).toBe(0.7);\n expect(body.top_p).toBe(0.9);\n expect(body.frequency_penalty).toBe(0.5);\n expect(body.presence_penalty).toBe(0.3);\n });\n });\n\n describe('Generate (Non-streaming)', () => {\n it('should generate content successfully', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n choices: [\n {\n message: {\n content: 'Generated response',\n reasoning_content: 'Thinking process'\n }\n }\n ],\n usage: {\n prompt_tokens: 100,\n completion_tokens: 50\n }\n })\n });\n\n const result = await provider.generate('You are a helpful assistant', 'Hello, how are you?');\n\n expect(result.content).toBe('Generated response');\n expect(result.reasoning).toBe('Thinking process');\n expect(mockFetch).toHaveBeenCalledTimes(1);\n });\n\n it('should build correct API request', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n choices: [{ message: { content: 'Response' } }]\n })\n });\n\n await provider.generate('System prompt', 'User prompt', {\n maxTokens: 2000,\n temperature: 0.7\n });\n\n const callArgs = mockFetch.mock.calls[0];\n expect(callArgs[0]).toBe('https://api.deepseek.com/v1/chat/completions');\n\n const headers = callArgs[1].headers;\n expect(headers['Content-Type']).toBe('application/json');\n expect(headers['Authorization']).toBe('Bearer test-api-key');\n\n const requestBody = JSON.parse(callArgs[1].body);\n expect(requestBody.stream).toBe(false);\n expect(requestBody.model).toBe('deepseek-v4-flash');\n expect(requestBody.max_tokens).toBe(2000);\n expect(requestBody.temperature).toBe(0.7);\n });\n\n it('should throw error when API key is missing', async () => {\n const noKeyProvider = new DeepSeekProvider({ apiKey: '' });\n\n await expect(noKeyProvider.generate('System', 'User')).rejects.toThrow('API key not set');\n });\n\n it('should handle API errors with error message', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: false,\n statusText: 'Bad Request',\n json: async () => ({\n error: { message: 'Invalid request' }\n })\n });\n\n await expect(provider.generate('System', 'User')).rejects.toThrow('Invalid request');\n });\n\n it('should handle API errors without error message', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: false,\n statusText: 'Internal Server Error',\n json: async () => ({})\n });\n\n await expect(provider.generate('System', 'User')).rejects.toThrow(\n 'API request failed: Internal Server Error'\n );\n });\n\n it('should include optional sampling params when thinking is off', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n choices: [{ message: { content: 'Response' } }]\n })\n });\n\n await provider.generate('System', 'User', {\n top_p: 0.9,\n frequency_penalty: 0.5,\n presence_penalty: 0.3,\n stop_sequences: ['STOP']\n });\n\n const requestBody = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(requestBody.top_p).toBe(0.9);\n expect(requestBody.frequency_penalty).toBe(0.5);\n expect(requestBody.presence_penalty).toBe(0.3);\n expect(requestBody.stop).toEqual(['STOP']);\n });\n\n it('should pass abort signal to fetch', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n choices: [{ message: { content: 'Response' } }]\n })\n });\n\n const controller = new AbortController();\n await provider.generate('System', 'User', { signal: controller.signal });\n\n expect(mockFetch.mock.calls[0][1].signal).toBe(controller.signal);\n });\n });\n\n describe('Generate Streaming', () => {\n it('should throw error when API key is missing', async () => {\n const noKeyProvider = new DeepSeekProvider({ apiKey: '' });\n\n await expect(noKeyProvider.generateStreaming('System', 'User')).rejects.toThrow(\n 'API key not set'\n );\n });\n\n it('should return stream object with abort function', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n body: {\n getReader: () => ({\n read: async () => ({ done: true })\n })\n }\n });\n\n const result = await provider.generateStreaming('System', 'User');\n\n expect(result).toHaveProperty('stream');\n expect(result).toHaveProperty('abort');\n expect(result).toHaveProperty('metadata');\n expect(typeof result.abort).toBe('function');\n });\n\n it('should handle API errors', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: false,\n statusText: 'Server Error',\n json: async () => ({\n error: { message: 'Server error' }\n })\n });\n\n await expect(provider.generateStreaming('System', 'User')).rejects.toThrow('Server error');\n });\n\n it('forwards thinking flag through streaming path', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n body: { getReader: () => ({ read: async () => ({ done: true }) }) }\n });\n\n await provider.generateStreaming('System', 'User', {\n thinking: true,\n reasoningEffort: 'max'\n });\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.stream).toBe(true);\n expect(body.thinking).toEqual({ type: 'enabled', reasoning_effort: 'max' });\n });\n });\n\n describe('Error Parsing', () => {\n it('should parse authentication errors', () => {\n const error = new Error('401 Unauthorized');\n const parsed = provider.parseError(error);\n\n expect(parsed.code).toBe('AUTH_ERROR');\n expect(parsed.message).toBe('Invalid API key');\n });\n\n it('should parse rate limit errors', () => {\n const error = new Error('429 rate limit exceeded');\n const parsed = provider.parseError(error);\n\n expect(parsed.code).toBe('RATE_LIMIT');\n expect(parsed.message).toContain('Rate limit');\n });\n\n it('should return generic error for unknown errors', () => {\n const error = new Error('Unknown error');\n const parsed = provider.parseError(error);\n\n expect(parsed.code).toBe('API_ERROR');\n });\n });\n\n describe('getAvailableModels', () => {\n it('should fetch and transform models', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n data: [\n { id: 'deepseek-v4-flash', created: 1234567890, owned_by: 'deepseek' },\n { id: 'deepseek-v4-pro', created: 1234567891, owned_by: 'deepseek' }\n ]\n })\n });\n\n const models = await provider.getAvailableModels();\n\n expect(models).toHaveLength(2);\n expect(models[0].id).toBe('deepseek-v4-flash');\n expect(models[0].contextLength).toBe(1000000);\n });\n\n it('should return empty array on error', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: false,\n statusText: 'Server Error'\n });\n\n const models = await provider.getAvailableModels();\n expect(models).toEqual([]);\n });\n });\n\n describe('getModelDescription', () => {\n it('should return description for deepseek-v4-flash', () => {\n const description = provider.getModelDescription('deepseek-v4-flash');\n expect(description).toContain('Flash');\n });\n\n it('should return description for deepseek-v4-pro', () => {\n const description = provider.getModelDescription('deepseek-v4-pro');\n expect(description).toContain('Pro');\n });\n\n it('should mark deepseek-chat as deprecated', () => {\n const description = provider.getModelDescription('deepseek-chat');\n expect(description.toLowerCase()).toContain('deprecated');\n });\n\n it('should mark deepseek-reasoner as deprecated', () => {\n const description = provider.getModelDescription('deepseek-reasoner');\n expect(description.toLowerCase()).toContain('deprecated');\n });\n\n it('should return empty string for unknown model', () => {\n const description = provider.getModelDescription('unknown-model');\n expect(description).toBe('');\n });\n });\n});\n"}, "2067c7a3a7e856d9ad8023cd163eab133ec5df11": {"t": 1785758807.2634325, "n": 13049, "h": "c51a88f65bfdf999", "c": "import { describe, it, expect, beforeEach, vi } from 'vitest';\nimport { DeepSeekProvider } from '../deepseek-provider.js';\n\ndescribe('DeepSeekProvider', () => {\n let provider;\n let mockFetch;\n\n beforeEach(() => {\n // Mock global fetch\n mockFetch = vi.fn();\n global.fetch = mockFetch;\n\n provider = new DeepSeekProvider({\n apiKey: 'test-api-key',\n model: 'deepseek-v4-flash'\n });\n });\n\n describe('Configuration', () => {\n it('should initialize with correct defaults', () => {\n const defaultProvider = new DeepSeekProvider({\n apiKey: 'test-key'\n });\n\n expect(defaultProvider.baseURL).toBe('https://api.deepseek.com/v1');\n expect(defaultProvider.model).toBe('deepseek-v4-flash');\n });\n\n it('should allow custom base URL', () => {\n const customProvider = new DeepSeekProvider({\n apiKey: 'test-key',\n baseURL: 'https://custom-api.com/v1'\n });\n\n expect(customProvider.baseURL).toBe('https://custom-api.com/v1');\n });\n\n it('should allow custom model', () => {\n const customProvider = new DeepSeekProvider({\n apiKey: 'test-key',\n model: 'deepseek-v4-pro'\n });\n\n expect(customProvider.model).toBe('deepseek-v4-pro');\n });\n });\n\n describe('Capabilities', () => {\n it('should return correct capabilities', () => {\n const capabilities = provider.getCapabilities();\n\n expect(capabilities.streaming).toBe(true);\n expect(capabilities.reasoning).toBe(true);\n expect(capabilities.visionAPI).toBe(false);\n expect(capabilities.maxContextWindow).toBe(1000000);\n });\n });\n\n describe('Validation', () => {\n it('should validate correct configuration', () => {\n const result = provider.validateConfig();\n expect(result.valid).toBe(true);\n });\n\n it('should reject missing API key', () => {\n const invalidProvider = new DeepSeekProvider({ apiKey: '' });\n const result = invalidProvider.validateConfig();\n\n expect(result.valid).toBe(false);\n expect(result.error).toBe('API key is required');\n });\n\n it('should reject whitespace-only API key', () => {\n const invalidProvider = new DeepSeekProvider({ apiKey: ' ' });\n const result = invalidProvider.validateConfig();\n\n expect(result.valid).toBe(false);\n });\n });\n\n describe('Thinking mode request body', () => {\n beforeEach(() => {\n mockFetch.mockResolvedValue({\n ok: true,\n json: async () => ({ choices: [{ message: { content: 'r' } }] })\n });\n });\n\n it('sends thinking: disabled by default', async () => {\n await provider.generate('System', 'User');\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.thinking).toEqual({ type: 'disabled' });\n });\n\n it('sends thinking: enabled with high effort by default when thinking=true', async () => {\n await provider.generate('System', 'User', { thinking: true });\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.thinking).toEqual({ type: 'enabled', reasoning_effort: 'high' });\n });\n\n it('honors reasoningEffort=max', async () => {\n await provider.generate('System', 'User', { thinking: true, reasoningEffort: 'max' });\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.thinking).toEqual({ type: 'enabled', reasoning_effort: 'max' });\n });\n\n it('coerces unknown reasoningEffort values to high', async () => {\n await provider.generate('System', 'User', { thinking: true, reasoningEffort: 'low' });\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.thinking.reasoning_effort).toBe('high');\n });\n\n it('omits sampling params when thinking is enabled', async () => {\n await provider.generate('System', 'User', {\n thinking: true,\n temperature: 0.7,\n top_p: 0.9,\n frequency_penalty: 0.5,\n presence_penalty: 0.3\n });\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.temperature).toBeUndefined();\n expect(body.top_p).toBeUndefined();\n expect(body.frequency_penalty).toBeUndefined();\n expect(body.presence_penalty).toBeUndefined();\n });\n\n it('includes sampling params when thinking is disabled', async () => {\n await provider.generate('System', 'User', {\n temperature: 0.7,\n top_p: 0.9,\n frequency_penalty: 0.5,\n presence_penalty: 0.3\n });\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.temperature).toBe(0.7);\n expect(body.top_p).toBe(0.9);\n expect(body.frequency_penalty).toBe(0.5);\n expect(body.presence_penalty).toBe(0.3);\n });\n });\n\n describe('Generate (Non-streaming)', () => {\n it('should generate content successfully', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n choices: [\n {\n message: {\n content: 'Generated response',\n reasoning_content: 'Thinking process'\n }\n }\n ],\n usage: {\n prompt_tokens: 100,\n completion_tokens: 50\n }\n })\n });\n\n const result = await provider.generate(\n 'You are a helpful assistant',\n 'Hello, how are you?'\n );\n\n expect(result.content).toBe('Generated response');\n expect(result.reasoning).toBe('Thinking process');\n expect(mockFetch).toHaveBeenCalledTimes(1);\n });\n\n it('should build correct API request', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n choices: [{ message: { content: 'Response' } }]\n })\n });\n\n await provider.generate('System prompt', 'User prompt', {\n maxTokens: 2000,\n temperature: 0.7\n });\n\n const callArgs = mockFetch.mock.calls[0];\n expect(callArgs[0]).toBe('https://api.deepseek.com/v1/chat/completions');\n\n const headers = callArgs[1].headers;\n expect(headers['Content-Type']).toBe('application/json');\n expect(headers['Authorization']).toBe('Bearer test-api-key');\n\n const requestBody = JSON.parse(callArgs[1].body);\n expect(requestBody.stream).toBe(false);\n expect(requestBody.model).toBe('deepseek-v4-flash');\n expect(requestBody.max_tokens).toBe(2000);\n expect(requestBody.temperature).toBe(0.7);\n });\n\n it('should throw error when API key is missing', async () => {\n const noKeyProvider = new DeepSeekProvider({ apiKey: '' });\n\n await expect(\n noKeyProvider.generate('System', 'User')\n ).rejects.toThrow('API key not set');\n });\n\n it('should handle API errors with error message', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: false,\n statusText: 'Bad Request',\n json: async () => ({\n error: { message: 'Invalid request' }\n })\n });\n\n await expect(\n provider.generate('System', 'User')\n ).rejects.toThrow('Invalid request');\n });\n\n it('should handle API errors without error message', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: false,\n statusText: 'Internal Server Error',\n json: async () => ({})\n });\n\n await expect(\n provider.generate('System', 'User')\n ).rejects.toThrow('API request failed: Internal Server Error');\n });\n\n it('should include optional sampling params when thinking is off', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n choices: [{ message: { content: 'Response' } }]\n })\n });\n\n await provider.generate('System', 'User', {\n top_p: 0.9,\n frequency_penalty: 0.5,\n presence_penalty: 0.3,\n stop_sequences: ['STOP']\n });\n\n const requestBody = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(requestBody.top_p).toBe(0.9);\n expect(requestBody.frequency_penalty).toBe(0.5);\n expect(requestBody.presence_penalty).toBe(0.3);\n expect(requestBody.stop).toEqual(['STOP']);\n });\n\n it('should pass abort signal to fetch', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n choices: [{ message: { content: 'Response' } }]\n })\n });\n\n const controller = new AbortController();\n await provider.generate('System', 'User', { signal: controller.signal });\n\n expect(mockFetch.mock.calls[0][1].signal).toBe(controller.signal);\n });\n });\n\n describe('Generate Streaming', () => {\n it('should throw error when API key is missing', async () => {\n const noKeyProvider = new DeepSeekProvider({ apiKey: '' });\n\n await expect(\n noKeyProvider.generateStreaming('System', 'User')\n ).rejects.toThrow('API key not set');\n });\n\n it('should return stream object with abort function', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n body: {\n getReader: () => ({\n read: async () => ({ done: true })\n })\n }\n });\n\n const result = await provider.generateStreaming('System', 'User');\n\n expect(result).toHaveProperty('stream');\n expect(result).toHaveProperty('abort');\n expect(result).toHaveProperty('metadata');\n expect(typeof result.abort).toBe('function');\n });\n\n it('should handle API errors', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: false,\n statusText: 'Server Error',\n json: async () => ({\n error: { message: 'Server error' }\n })\n });\n\n await expect(\n provider.generateStreaming('System', 'User')\n ).rejects.toThrow('Server error');\n });\n\n it('forwards thinking flag through streaming path', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n body: { getReader: () => ({ read: async () => ({ done: true }) }) }\n });\n\n await provider.generateStreaming('System', 'User', { thinking: true, reasoningEffort: 'max' });\n\n const body = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(body.stream).toBe(true);\n expect(body.thinking).toEqual({ type: 'enabled', reasoning_effort: 'max' });\n });\n });\n\n describe('Error Parsing', () => {\n it('should parse authentication errors', () => {\n const error = new Error('401 Unauthorized');\n const parsed = provider.parseError(error);\n\n expect(parsed.code).toBe('AUTH_ERROR');\n expect(parsed.message).toBe('Invalid API key');\n });\n\n it('should parse rate limit errors', () => {\n const error = new Error('429 rate limit exceeded');\n const parsed = provider.parseError(error);\n\n expect(parsed.code).toBe('RATE_LIMIT');\n expect(parsed.message).toContain('Rate limit');\n });\n\n it('should return generic error for unknown errors', () => {\n const error = new Error('Unknown error');\n const parsed = provider.parseError(error);\n\n expect(parsed.code).toBe('API_ERROR');\n });\n });\n\n describe('getAvailableModels', () => {\n it('should fetch and transform models', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n data: [\n { id: 'deepseek-v4-flash', created: 1234567890, owned_by: 'deepseek' },\n { id: 'deepseek-v4-pro', created: 1234567891, owned_by: 'deepseek' }\n ]\n })\n });\n\n const models = await provider.getAvailableModels();\n\n expect(models).toHaveLength(2);\n expect(models[0].id).toBe('deepseek-v4-flash');\n expect(models[0].contextLength).toBe(1000000);\n });\n\n it('should return empty array on error', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: false,\n statusText: 'Server Error'\n });\n\n const models = await provider.getAvailableModels();\n expect(models).toEqual([]);\n });\n });\n\n describe('getModelDescription', () => {\n it('should return description for deepseek-v4-flash', () => {\n const description = provider.getModelDescription('deepseek-v4-flash');\n expect(description).toContain('Flash');\n });\n\n it('should return description for deepseek-v4-pro', () => {\n const description = provider.getModelDescription('deepseek-v4-pro');\n expect(description).toContain('Pro');\n });\n\n it('should mark deepseek-chat as deprecated', () => {\n const description = provider.getModelDescription('deepseek-chat');\n expect(description.toLowerCase()).toContain('deprecated');\n });\n\n it('should mark deepseek-reasoner as deprecated', () => {\n const description = provider.getModelDescription('deepseek-reasoner');\n expect(description.toLowerCase()).toContain('deprecated');\n });\n\n it('should return empty string for unknown model', () => {\n const description = provider.getModelDescription('unknown-model');\n expect(description).toBe('');\n });\n });\n});\n"}, "d2cbac9cd5c6b0ca5617260c95a7436d5f466930": {"t": 1785758809.3144615, "n": 10238, "h": "c41ba5e52aed7106", "c": "import { describe, it, expect, beforeEach, vi } from 'vitest';\nimport { DeepSeekProvider } from '../deepseek-provider.js';\n\ndescribe('DeepSeekProvider', () => {\n let provider;\n let mockFetch;\n\n beforeEach(() => {\n // Mock global fetch\n mockFetch = vi.fn();\n global.fetch = mockFetch;\n\n provider = new DeepSeekProvider({\n apiKey: 'test-api-key',\n model: 'deepseek-chat'\n });\n });\n\n describe('Configuration', () => {\n it('should initialize with correct defaults', () => {\n const defaultProvider = new DeepSeekProvider({\n apiKey: 'test-key'\n });\n\n expect(defaultProvider.baseURL).toBe('https://api.deepseek.com/v1');\n expect(defaultProvider.model).toBe('deepseek-reasoner');\n });\n\n it('should allow custom base URL', () => {\n const customProvider = new DeepSeekProvider({\n apiKey: 'test-key',\n baseURL: 'https://custom-api.com/v1'\n });\n\n expect(customProvider.baseURL).toBe('https://custom-api.com/v1');\n });\n\n it('should allow custom model', () => {\n const customProvider = new DeepSeekProvider({\n apiKey: 'test-key',\n model: 'deepseek-chat'\n });\n\n expect(customProvider.model).toBe('deepseek-chat');\n });\n });\n\n describe('Capabilities', () => {\n it('should return correct capabilities', () => {\n const capabilities = provider.getCapabilities();\n\n expect(capabilities.streaming).toBe(true);\n expect(capabilities.reasoning).toBe(true);\n expect(capabilities.visionAPI).toBe(false);\n expect(capabilities.maxContextWindow).toBe(128000);\n });\n });\n\n describe('Validation', () => {\n it('should validate correct configuration', () => {\n const result = provider.validateConfig();\n expect(result.valid).toBe(true);\n });\n\n it('should reject missing API key', () => {\n const invalidProvider = new DeepSeekProvider({ apiKey: '' });\n const result = invalidProvider.validateConfig();\n\n expect(result.valid).toBe(false);\n expect(result.error).toBe('API key is required');\n });\n\n it('should reject whitespace-only API key', () => {\n const invalidProvider = new DeepSeekProvider({ apiKey: ' ' });\n const result = invalidProvider.validateConfig();\n\n expect(result.valid).toBe(false);\n });\n });\n\n describe('isReasonerModel', () => {\n it('should return true for deepseek-reasoner', () => {\n const reasonerProvider = new DeepSeekProvider({\n apiKey: 'test-key',\n model: 'deepseek-reasoner'\n });\n\n expect(reasonerProvider.isReasonerModel()).toBe(true);\n });\n\n it('should return false for deepseek-chat', () => {\n const chatProvider = new DeepSeekProvider({\n apiKey: 'test-key',\n model: 'deepseek-chat'\n });\n\n expect(chatProvider.isReasonerModel()).toBe(false);\n });\n });\n\n describe('Generate (Non-streaming)', () => {\n it('should generate content successfully', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n choices: [\n {\n message: {\n content: 'Generated response',\n reasoning_content: 'Thinking process'\n }\n }\n ],\n usage: {\n prompt_tokens: 100,\n completion_tokens: 50\n }\n })\n });\n\n const result = await provider.generate(\n 'You are a helpful assistant',\n 'Hello, how are you?'\n );\n\n expect(result.content).toBe('Generated response');\n expect(result.reasoning).toBe('Thinking process');\n expect(mockFetch).toHaveBeenCalledTimes(1);\n });\n\n it('should build correct API request', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n choices: [{ message: { content: 'Response' } }]\n })\n });\n\n await provider.generate('System prompt', 'User prompt', {\n maxTokens: 2000,\n temperature: 0.7\n });\n\n const callArgs = mockFetch.mock.calls[0];\n expect(callArgs[0]).toBe('https://api.deepseek.com/v1/chat/completions');\n\n const headers = callArgs[1].headers;\n expect(headers['Content-Type']).toBe('application/json');\n expect(headers['Authorization']).toBe('Bearer test-api-key');\n\n const requestBody = JSON.parse(callArgs[1].body);\n expect(requestBody.stream).toBe(false);\n expect(requestBody.model).toBe('deepseek-chat');\n expect(requestBody.max_tokens).toBe(2000);\n expect(requestBody.temperature).toBe(0.7);\n });\n\n it('should throw error when API key is missing', async () => {\n const noKeyProvider = new DeepSeekProvider({ apiKey: '' });\n\n await expect(\n noKeyProvider.generate('System', 'User')\n ).rejects.toThrow('API key not set');\n });\n\n it('should handle API errors with error message', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: false,\n statusText: 'Bad Request',\n json: async () => ({\n error: { message: 'Invalid request' }\n })\n });\n\n await expect(\n provider.generate('System', 'User')\n ).rejects.toThrow('Invalid request');\n });\n\n it('should handle API errors without error message', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: false,\n statusText: 'Internal Server Error',\n json: async () => ({})\n });\n\n await expect(\n provider.generate('System', 'User')\n ).rejects.toThrow('API request failed: Internal Server Error');\n });\n\n it('should include optional parameters for non-reasoner model', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n choices: [{ message: { content: 'Response' } }]\n })\n });\n\n await provider.generate('System', 'User', {\n top_p: 0.9,\n frequency_penalty: 0.5,\n presence_penalty: 0.3,\n stop_sequences: ['STOP']\n });\n\n const requestBody = JSON.parse(mockFetch.mock.calls[0][1].body);\n expect(requestBody.top_p).toBe(0.9);\n expect(requestBody.frequency_penalty).toBe(0.5);\n expect(requestBody.presence_penalty).toBe(0.3);\n expect(requestBody.stop).toEqual(['STOP']);\n });\n\n it('should pass abort signal to fetch', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n choices: [{ message: { content: 'Response' } }]\n })\n });\n\n const controller = new AbortController();\n await provider.generate('System', 'User', { signal: controller.signal });\n\n expect(mockFetch.mock.calls[0][1].signal).toBe(controller.signal);\n });\n });\n\n describe('Generate Streaming', () => {\n it('should throw error when API key is missing', async () => {\n const noKeyProvider = new DeepSeekProvider({ apiKey: '' });\n\n await expect(\n noKeyProvider.generateStreaming('System', 'User')\n ).rejects.toThrow('API key not set');\n });\n\n it('should return stream object with abort function', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n body: {\n getReader: () => ({\n read: async () => ({ done: true })\n })\n }\n });\n\n const result = await provider.generateStreaming('System', 'User');\n\n expect(result).toHaveProperty('stream');\n expect(result).toHaveProperty('abort');\n expect(result).toHaveProperty('metadata');\n expect(typeof result.abort).toBe('function');\n });\n\n it('should handle API errors', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: false,\n statusText: 'Server Error',\n json: async () => ({\n error: { message: 'Server error' }\n })\n });\n\n await expect(\n provider.generateStreaming('System', 'User')\n ).rejects.toThrow('Server error');\n });\n });\n\n describe('Error Parsing', () => {\n it('should parse authentication errors', () => {\n const error = new Error('401 Unauthorized');\n const parsed = provider.parseError(error);\n\n expect(parsed.code).toBe('AUTH_ERROR');\n expect(parsed.message).toBe('Invalid API key');\n });\n\n it('should parse rate limit errors', () => {\n const error = new Error('429 rate limit exceeded');\n const parsed = provider.parseError(error);\n\n expect(parsed.code).toBe('RATE_LIMIT');\n expect(parsed.message).toContain('Rate limit');\n });\n\n it('should return generic error for unknown errors', () => {\n const error = new Error('Unknown error');\n const parsed = provider.parseError(error);\n\n expect(parsed.code).toBe('API_ERROR');\n });\n });\n\n describe('getAvailableModels', () => {\n it('should fetch and transform models', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n data: [\n { id: 'deepseek-chat', created: 1234567890, owned_by: 'deepseek' },\n { id: 'deepseek-reasoner', created: 1234567891, owned_by: 'deepseek' }\n ]\n })\n });\n\n const models = await provider.getAvailableModels();\n\n expect(models).toHaveLength(2);\n expect(models[0].id).toBe('deepseek-chat');\n expect(models[0].contextLength).toBe(64000);\n });\n\n it('should return empty array on error', async () => {\n mockFetch.mockResolvedValueOnce({\n ok: false,\n statusText: 'Server Error'\n });\n\n const models = await provider.getAvailableModels();\n expect(models).toEqual([]);\n });\n });\n\n describe('getModelDescription', () => {\n it('should return description for deepseek-chat', () => {\n const description = provider.getModelDescription('deepseek-chat');\n expect(description).toContain('conversation');\n });\n\n it('should return description for deepseek-reasoner', () => {\n const description = provider.getModelDescription('deepseek-reasoner');\n expect(description).toContain('reasoning');\n });\n\n it('should return empty string for unknown model', () => {\n const description = provider.getModelDescription('unknown-model');\n expect(description).toBe('');\n });\n });\n});\n"}}}} |