UNPKG

@jettoblack/image_mcp

Version:

MCP server for image summarization using OpenAI-compatible chat completion endpoints

302 lines (262 loc) 9.35 kB
// Integration tests for the complete image processing workflow import axios from 'axios'; import fs from 'fs-extra'; import path from 'path'; describe('Image Processing Integration Tests', () => { const BASE_URL = 'http://localhost:9293'; const TEST_IMAGE_PATH = path.join(__dirname, '../test_image.webp'); const TEST_DATA_URL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=='; const TEST_HTTP_URL = 'https://httpbin.org/image/jpeg'; // Public test image let mockServerAvailable = false; beforeAll(async () => { // Check if mock server is available try { await axios.get(`${BASE_URL}/v1/models`, { timeout: 1000 }); console.log('Mock server is available, running integration tests'); mockServerAvailable = true; } catch (error) { console.log('Mock server is not available, skipping integration tests'); mockServerAvailable = false; } }); afterAll(async () => { // Clean up any temporary files }); describe('Direct image processing endpoint', () => { it('should detect file path input type', async () => { if (!mockServerAvailable) { console.log('Skipping test - mock server not available'); return; } if (!(await fs.pathExists(TEST_IMAGE_PATH))) { console.log(`Skipping file path test - test image not found at ${TEST_IMAGE_PATH}`); return; } const response = await axios.post(`${BASE_URL}/v1/test/image-process`, { image_url: TEST_IMAGE_PATH }); expect(response.status).toBe(200); expect(response.data.success).toBe(true); expect(response.data.input_type).toBe('file_path'); }); it('should detect data URL input type', async () => { if (!mockServerAvailable) { console.log('Skipping test - mock server not available'); return; } const response = await axios.post(`${BASE_URL}/v1/test/image-process`, { image_url: TEST_DATA_URL }); expect(response.status).toBe(200); expect(response.data.success).toBe(true); expect(response.data.input_type).toBe('data_url'); }); it('should detect HTTP URL input type', async () => { if (!mockServerAvailable) { console.log('Skipping test - mock server not available'); return; } const response = await axios.post(`${BASE_URL}/v1/test/image-process`, { image_url: TEST_HTTP_URL }); expect(response.status).toBe(200); expect(response.data.success).toBe(true); expect(response.data.input_type).toBe('http_url'); }); it('should handle custom prompt', async () => { if (!mockServerAvailable) { console.log('Skipping test - mock server not available'); return; } const response = await axios.post(`${BASE_URL}/v1/test/image-process`, { image_url: TEST_DATA_URL, custom_prompt: 'What colors are in this image?' }); expect(response.status).toBe(200); expect(response.data.success).toBe(true); expect(response.data.custom_prompt).toBe('What colors are in this image?'); }); it('should reject missing image_url', async () => { if (!mockServerAvailable) { console.log('Skipping test - mock server not available'); return; } try { await axios.post(`${BASE_URL}/v1/test/image-process`, {}); fail('Expected request to be rejected'); } catch (error: any) { expect(error.response.status).toBe(400); expect(error.response.data.error.message).toBe('image_url parameter is required'); } }); }); describe('OpenAI API compatibility', () => { it('should list models', async () => { if (!mockServerAvailable) { console.log('Skipping test - mock server not available'); return; } const response = await axios.get(`${BASE_URL}/v1/models`); expect(response.status).toBe(200); expect(response.data.object).toBe('list'); expect(response.data.data).toHaveLength(2); expect(response.data.data[0].id).toBe('test-model-vision'); expect(response.data.data[1].id).toBe('gpt-4-vision-preview'); }); it('should handle text-only chat completion', async () => { if (!mockServerAvailable) { console.log('Skipping test - mock server not available'); return; } const response = await axios.post(`${BASE_URL}/v1/chat/completions`, { model: 'test-model-vision', messages: [ { role: 'user', content: 'Hello, how are you?' } ], stream: false }); expect(response.status).toBe(200); expect(response.data.object).toBe('chat.completion'); expect(response.data.choices).toHaveLength(1); expect(response.data.choices[0].message.content).toContain('text-only message'); expect(response.data.choices[0].message.role).toBe('assistant'); }); it('should handle image chat completion with data URL', async () => { if (!mockServerAvailable) { console.log('Skipping test - mock server not available'); return; } const response = await axios.post(`${BASE_URL}/v1/chat/completions`, { model: 'test-model-vision', messages: [ { role: 'user', content: [ { type: 'text', text: 'Describe this image' }, { type: 'image_url', image_url: { url: TEST_DATA_URL } } ] } ], stream: false }); expect(response.status).toBe(200); expect(response.data.object).toBe('chat.completion'); expect(response.data.choices).toHaveLength(1); expect(response.data.choices[0].message.content).toContain('test image analysis'); expect(response.data.choices[0].message.role).toBe('assistant'); }); it('should handle streaming chat completion with image', async () => { if (!mockServerAvailable) { console.log('Skipping test - mock server not available'); return; } const response = await axios.post(`${BASE_URL}/v1/chat/completions`, { model: 'test-model-vision', messages: [ { role: 'user', content: [ { type: 'text', text: 'Describe this image' }, { type: 'image_url', image_url: { url: TEST_DATA_URL } } ] } ], stream: true }, { responseType: 'stream' }); expect(response.status).toBe(200); expect(response.headers['content-type']).toContain('text/event-stream'); return new Promise<void>((resolve, reject) => { let content = ''; let chunkCount = 0; response.data.on('data', (chunk: Buffer) => { chunkCount++; const chunkStr = chunk.toString(); if (chunkStr.includes('data:')) { const jsonStr = chunkStr.replace('data: ', '').trim(); if (jsonStr && jsonStr !== '[DONE]') { try { const data = JSON.parse(jsonStr); if (data.choices && data.choices[0] && data.choices[0].delta) { content += data.choices[0].delta.content || ''; } } catch (e) { // Ignore JSON parse errors for non-JSON chunks } } } }); response.data.on('end', () => { expect(chunkCount).toBeGreaterThan(1); expect(content).toContain('test image analysis'); resolve(); }); response.data.on('error', reject); }); }); }); describe('Error handling', () => { it('should handle invalid endpoint', async () => { if (!mockServerAvailable) { console.log('Skipping test - mock server not available'); return; } try { await axios.get(`${BASE_URL}/v1/invalid-endpoint`); fail('Expected request to be rejected'); } catch (error: any) { expect(error.response.status).toBe(404); } }); it('should handle invalid image URL in chat completion', async () => { if (!mockServerAvailable) { console.log('Skipping test - mock server not available'); return; } const response = await axios.post(`${BASE_URL}/v1/chat/completions`, { model: 'test-model-vision', messages: [ { role: 'user', content: [ { type: 'text', text: 'Describe this image' }, { type: 'image_url', image_url: { url: 'invalid-url' } } ] } ], stream: false }); // The mock server should still respond with a test response even for invalid URLs expect(response.status).toBe(200); expect(response.data.choices).toHaveLength(1); }); }); });