UNPKG

@lobehub/chat

Version:

Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.

269 lines (225 loc) • 7.49 kB
// @vitest-environment node import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { CreateImagePayload } from '../../types/image'; import { LobeBflAI } from './index'; // Mock the createBflImage function vi.mock('./createImage', () => ({ createBflImage: vi.fn(), })); // Mock the console.error to avoid polluting test output vi.spyOn(console, 'error').mockImplementation(() => {}); const bizErrorType = 'ProviderBizError'; const invalidErrorType = 'InvalidProviderAPIKey'; let instance: LobeBflAI; beforeEach(() => { vi.clearAllMocks(); instance = new LobeBflAI({ apiKey: 'test-api-key' }); }); afterEach(() => { vi.clearAllMocks(); }); describe('LobeBflAI', () => { describe('init', () => { it('should correctly initialize with an API key', () => { const instance = new LobeBflAI({ apiKey: 'test_api_key' }); expect(instance).toBeInstanceOf(LobeBflAI); }); it('should initialize with custom baseURL', () => { const customBaseURL = 'https://custom-api.bfl.ai'; const instance = new LobeBflAI({ apiKey: 'test_api_key', baseURL: customBaseURL, }); expect(instance).toBeInstanceOf(LobeBflAI); }); it('should throw InvalidProviderAPIKey if no apiKey is provided', () => { expect(() => { new LobeBflAI({}); }).toThrow(); }); it('should throw InvalidProviderAPIKey if apiKey is undefined', () => { expect(() => { new LobeBflAI({ apiKey: undefined }); }).toThrow(); }); }); describe('createImage', () => { let mockCreateBflImage: any; beforeEach(async () => { const { createBflImage } = await import('./createImage'); mockCreateBflImage = vi.mocked(createBflImage); }); it('should create image successfully with basic parameters', async () => { // Arrange const mockImageResponse = { imageUrl: 'https://example.com/generated-image.jpg', }; mockCreateBflImage.mockResolvedValue(mockImageResponse); const payload: CreateImagePayload = { model: 'flux-dev', params: { prompt: 'A beautiful landscape with mountains', width: 1024, height: 1024, }, }; // Act const result = await instance.createImage(payload); // Assert expect(mockCreateBflImage).toHaveBeenCalledWith(payload, { apiKey: 'test-api-key', baseURL: undefined, provider: 'bfl', }); expect(result).toEqual(mockImageResponse); }); it('should pass custom baseURL to createBflImage', async () => { // Arrange const customBaseURL = 'https://custom-api.bfl.ai'; const customInstance = new LobeBflAI({ apiKey: 'test-api-key', baseURL: customBaseURL, }); const mockImageResponse = { imageUrl: 'https://example.com/generated-image.jpg', }; mockCreateBflImage.mockResolvedValue(mockImageResponse); const payload: CreateImagePayload = { model: 'flux-pro', params: { prompt: 'Test image', }, }; // Act await customInstance.createImage(payload); // Assert expect(mockCreateBflImage).toHaveBeenCalledWith(payload, { apiKey: 'test-api-key', baseURL: customBaseURL, provider: 'bfl', }); }); describe('Error handling', () => { it('should throw InvalidProviderAPIKey on 401 error', async () => { // Arrange const apiError = new Error('Unauthorized') as Error & { status: number }; apiError.status = 401; mockCreateBflImage.mockRejectedValue(apiError); const payload: CreateImagePayload = { model: 'flux-dev', params: { prompt: 'Test image', }, }; // Act & Assert await expect(instance.createImage(payload)).rejects.toEqual({ error: { error: apiError }, errorType: invalidErrorType, }); }); it('should throw ProviderBizError on other errors', async () => { // Arrange const apiError = new Error('Some other error'); mockCreateBflImage.mockRejectedValue(apiError); const payload: CreateImagePayload = { model: 'flux-dev', params: { prompt: 'Test image', }, }; // Act & Assert await expect(instance.createImage(payload)).rejects.toEqual({ error: { error: apiError }, errorType: bizErrorType, }); }); it('should throw ProviderBizError on non-401 status errors', async () => { // Arrange const apiError = new Error('Server error') as Error & { status: number }; apiError.status = 500; mockCreateBflImage.mockRejectedValue(apiError); const payload: CreateImagePayload = { model: 'flux-dev', params: { prompt: 'Test image', }, }; // Act & Assert await expect(instance.createImage(payload)).rejects.toEqual({ error: { error: apiError }, errorType: bizErrorType, }); }); it('should throw ProviderBizError on errors without status property', async () => { // Arrange const apiError = new Error('Network error'); mockCreateBflImage.mockRejectedValue(apiError); const payload: CreateImagePayload = { model: 'flux-pro-1.1', params: { prompt: 'Test image', }, }; // Act & Assert await expect(instance.createImage(payload)).rejects.toEqual({ error: { error: apiError }, errorType: bizErrorType, }); }); }); describe('Edge cases', () => { it('should handle different model types', async () => { // Arrange const mockImageResponse = { imageUrl: 'https://example.com/generated-image.jpg', }; mockCreateBflImage.mockResolvedValue(mockImageResponse); const models = [ 'flux-dev', 'flux-pro', 'flux-pro-1.1', 'flux-pro-1.1-ultra', 'flux-kontext-pro', 'flux-kontext-max', ]; // Act & Assert for (const model of models) { const payload: CreateImagePayload = { model, params: { prompt: `Test image for ${model}`, }, }; await instance.createImage(payload); expect(mockCreateBflImage).toHaveBeenCalledWith(payload, { apiKey: 'test-api-key', baseURL: undefined, provider: 'bfl', }); } }); it('should handle empty params object', async () => { // Arrange const mockImageResponse = { imageUrl: 'https://example.com/generated-image.jpg', }; mockCreateBflImage.mockResolvedValue(mockImageResponse); const payload: CreateImagePayload = { model: 'flux-dev', params: { prompt: 'Empty params test', }, }; // Act const result = await instance.createImage(payload); // Assert expect(mockCreateBflImage).toHaveBeenCalledWith(payload, { apiKey: 'test-api-key', baseURL: undefined, provider: 'bfl', }); expect(result).toEqual(mockImageResponse); }); }); }); });