/**
* Unit tests for OpenAIProvider
* Tests provider initialization, chat method, and error handling
*
* Requirements: 4.2, 12.2
*/
import { describe, it, beforeEach } from 'node:test';
import assert from 'node:assert';
import { OpenAIProvider } from '../../../src/services/OpenAIProvider.js';
const runProviderTests = process.env.RUN_PROVIDER_TESTS === 'true';
const runProviderNetworkTests = process.env.RUN_PROVIDER_NETWORK_TESTS === 'true';
describe('OpenAIProvider', {skip: !runProviderTests}, () => {
describe('Constructor', () => {
it('should initialize with API key', () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
});
assert.ok(provider);
});
it('should throw error when API key is missing', () => {
assert.throws(
() => {
new OpenAIProvider({
apiKey: '',
});
},
{
message: /OpenAI API key is required/,
}
);
});
it('should initialize with custom base URL', () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
baseURL: 'https://custom.openai.com/v1',
});
assert.ok(provider);
});
it('should initialize with custom model', () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
model: 'gpt-3.5-turbo',
});
assert.ok(provider);
});
it('should use default model when not specified', () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
});
assert.ok(provider);
});
it('should initialize with all configuration options', () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
baseURL: 'https://custom.openai.com/v1',
model: 'gpt-4-turbo',
});
assert.ok(provider);
});
});
describe('chat method', {skip: !runProviderNetworkTests}, () => {
it('should reject with error when API key is invalid', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-invalid-key',
});
await assert.rejects(
async () => {
await provider.chat([{ role: 'user', content: 'Hello' }]);
},
Error
);
});
it('should handle empty messages array', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
});
await assert.rejects(
async () => {
await provider.chat([]);
},
Error
);
});
it('should handle system messages', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
});
// This will fail with invalid API key, but tests message handling
await assert.rejects(
async () => {
await provider.chat([
{ role: 'system', content: 'You are helpful' },
{ role: 'user', content: 'Hello' },
]);
},
Error
);
});
it('should handle conversation history', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
});
await assert.rejects(
async () => {
await provider.chat([
{ role: 'user', content: 'What is 2+2?' },
{ role: 'assistant', content: '4' },
{ role: 'user', content: 'What is 3+3?' },
]);
},
Error
);
});
it('should pass temperature option', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
});
await assert.rejects(
async () => {
await provider.chat(
[{ role: 'user', content: 'Hello' }],
{ temperature: 0.7 }
);
},
Error
);
});
it('should pass maxTokens option', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
});
await assert.rejects(
async () => {
await provider.chat(
[{ role: 'user', content: 'Hello' }],
{ maxTokens: 1000 }
);
},
Error
);
});
it('should pass model option', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
});
await assert.rejects(
async () => {
await provider.chat(
[{ role: 'user', content: 'Hello' }],
{ model: 'gpt-3.5-turbo' }
);
},
Error
);
});
it('should pass all chat options', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
});
await assert.rejects(
async () => {
await provider.chat(
[{ role: 'user', content: 'Hello' }],
{
temperature: 0.8,
maxTokens: 2000,
model: 'gpt-4',
}
);
},
Error
);
});
});
describe('analyzeImage method', {skip: !runProviderNetworkTests}, () => {
it('should reject with error when API key is invalid', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-invalid-key',
});
await assert.rejects(
async () => {
await provider.analyzeImage('test.png', 'Describe this image', true);
},
Error
);
});
it('should handle file path input', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
});
// This will fail because file doesn't exist, but tests the flow
await assert.rejects(
async () => {
await provider.analyzeImage('nonexistent.png', 'Describe', true);
},
Error
);
});
it('should handle base64 input', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
});
const base64Image = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
await assert.rejects(
async () => {
await provider.analyzeImage(base64Image, 'Describe', false);
},
Error
);
});
it('should handle data URL input', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
});
const dataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
await assert.rejects(
async () => {
await provider.analyzeImage(dataUrl, 'Describe', false);
},
Error
);
});
it('should handle HTTP URL input', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
});
await assert.rejects(
async () => {
await provider.analyzeImage('https://example.com/image.png', 'Describe', false);
},
Error
);
});
it('should handle HTTPS URL input', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
});
await assert.rejects(
async () => {
await provider.analyzeImage('https://example.com/image.png', 'Describe', false);
},
Error
);
});
it('should handle different image formats (PNG)', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
});
await assert.rejects(
async () => {
await provider.analyzeImage('test.png', 'Describe', true);
},
Error
);
});
it('should handle different image formats (JPEG)', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
});
await assert.rejects(
async () => {
await provider.analyzeImage('test.jpg', 'Describe', true);
},
Error
);
});
it('should handle different image formats (GIF)', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
});
await assert.rejects(
async () => {
await provider.analyzeImage('test.gif', 'Describe', true);
},
Error
);
});
it('should handle different image formats (WEBP)', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
});
await assert.rejects(
async () => {
await provider.analyzeImage('test.webp', 'Describe', true);
},
Error
);
});
it('should handle empty prompt', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
});
await assert.rejects(
async () => {
await provider.analyzeImage('test.png', '', true);
},
Error
);
});
});
describe('Error handling', () => {
it('should format OpenAI API errors', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-invalid-key',
});
await assert.rejects(
async () => {
await provider.chat([{ role: 'user', content: 'Hello' }]);
},
(error: any) => {
// Should be an Error object
assert.ok(error instanceof Error);
return true;
}
);
});
it('should preserve status code in errors', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-invalid-key',
});
try {
await provider.chat([{ role: 'user', content: 'Hello' }]);
assert.fail('Should have thrown an error');
} catch (error: any) {
// Error should have status property for retry logic
assert.ok(error instanceof Error);
}
});
it('should handle network errors', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
baseURL: 'https://invalid-domain-that-does-not-exist.com',
});
await assert.rejects(
async () => {
await provider.chat([{ role: 'user', content: 'Hello' }]);
},
Error
);
});
it('should handle timeout errors', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
});
// This will timeout or fail with invalid key
await assert.rejects(
async () => {
await provider.chat([{ role: 'user', content: 'Hello' }]);
},
Error
);
});
it('should handle malformed responses', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
});
await assert.rejects(
async () => {
await provider.chat([{ role: 'user', content: 'Hello' }]);
},
Error
);
});
});
describe('MIME type detection', () => {
it('should detect PNG MIME type', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
});
// Test by attempting to analyze (will fail but tests MIME detection)
await assert.rejects(
async () => {
await provider.analyzeImage('test.png', 'Describe', true);
},
Error
);
});
it('should detect JPEG MIME type', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
});
await assert.rejects(
async () => {
await provider.analyzeImage('test.jpeg', 'Describe', true);
},
Error
);
});
it('should default to PNG for unknown extensions', async () => {
const provider = new OpenAIProvider({
apiKey: 'sk-test-key',
});
await assert.rejects(
async () => {
await provider.analyzeImage('test.unknown', 'Describe', true);
},
Error
);
});
});
});