| 1 | import type { AiAvailabilityService } from '../../../ai-availability' |
| 2 | import { AppException, ResponseCode } from '@yikart/common' |
| 3 | import { beforeEach, describe, expect, it, vi } from 'vitest' |
| 4 | import { RelayConfig } from './relay.config' |
| 5 | import { RelayLibService } from './relay.service' |
| 6 | |
| 7 | vi.mock('../../../ai-availability', () => ({ |
| 8 | AiAvailabilityService: class AiAvailabilityService {}, |
| 9 | })) |
| 10 | |
| 11 | describe('relayLibService', () => { |
| 12 | let service: RelayLibService |
| 13 | let httpClient: { |
| 14 | post: ReturnType<typeof vi.fn> |
| 15 | get: ReturnType<typeof vi.fn> |
| 16 | } |
| 17 | |
| 18 | beforeEach(() => { |
| 19 | service = new RelayLibService( |
| 20 | { url: 'https://relay.example.com', apiKey: 'relay-key', timeout: 1000 } as RelayConfig, |
| 21 | { execute: vi.fn((_context, fn) => fn()) } as unknown as AiAvailabilityService, |
| 22 | ) |
| 23 | httpClient = { |
| 24 | post: vi.fn(), |
| 25 | get: vi.fn(), |
| 26 | } |
| 27 | Object.assign(service as unknown as { httpClient: typeof httpClient }, { httpClient }) |
| 28 | }) |
| 29 | |
| 30 | function normalizeResponse(data: unknown) { |
| 31 | return (service as unknown as { |
| 32 | normalizeResponse: (response: { data: unknown }) => { data: unknown } |
| 33 | }).normalizeResponse({ data }).data |
| 34 | } |
| 35 | |
| 36 | it('returns relay video task response when creating relay video tasks', async () => { |
| 37 | httpClient.post.mockResolvedValue({ |
| 38 | data: { id: 'remote-task-1', status: 'generating' }, |
| 39 | }) |
| 40 | |
| 41 | await expect(service.createVideo({ |
| 42 | model: 'relay-video-model', |
| 43 | prompt: 'video', |
| 44 | })).resolves.toEqual({ id: 'remote-task-1', status: 'generating' }) |
| 45 | }) |
| 46 | |
| 47 | it('unwraps common and legacy data-only responses in response interceptor', () => { |
| 48 | expect(normalizeResponse({ |
| 49 | code: ResponseCode.Success, |
| 50 | message: 'ok', |
| 51 | data: { id: 'remote-task-1', status: 'generating' }, |
| 52 | })).toEqual({ id: 'remote-task-1', status: 'generating' }) |
| 53 | expect(normalizeResponse({ |
| 54 | data: { |
| 55 | id: 'remote-task-2', |
| 56 | status: 'generating', |
| 57 | }, |
| 58 | })).toEqual({ id: 'remote-task-2', status: 'generating' }) |
| 59 | }) |
| 60 | |
| 61 | it('returns relay video task status when polling relay video tasks', async () => { |
| 62 | httpClient.get.mockResolvedValue({ |
| 63 | data: { id: 'remote-task-1', status: 'success', videoUrl: 'videos/out.mp4' }, |
| 64 | }) |
| 65 | |
| 66 | await expect(service.getVideo('remote-task-1')).resolves.toEqual({ |
| 67 | id: 'remote-task-1', |
| 68 | status: 'success', |
| 69 | videoUrl: 'videos/out.mp4', |
| 70 | }) |
| 71 | }) |
| 72 | |
| 73 | it('throws AppException with original response code when common response code is not success', () => { |
| 74 | expect(() => normalizeResponse({ |
| 75 | code: ResponseCode.InvalidModel, |
| 76 | message: 'upstream rejected image url', |
| 77 | data: null, |
| 78 | })).toThrow(AppException) |
| 79 | |
| 80 | try { |
| 81 | normalizeResponse({ |
| 82 | code: ResponseCode.InvalidModel, |
| 83 | message: 'upstream rejected image url', |
| 84 | data: null, |
| 85 | }) |
| 86 | } |
| 87 | catch (error) { |
| 88 | expect(error).toBeInstanceOf(AppException) |
| 89 | expect((error as AppException).code).toBe(ResponseCode.InvalidModel) |
| 90 | } |
| 91 | }) |
| 92 | |
| 93 | it('throws AiCallFailed when relay video submit response has no id', async () => { |
| 94 | httpClient.post.mockResolvedValue({ |
| 95 | data: { status: 'failed', error: 'invalid image url' }, |
| 96 | }) |
| 97 | |
| 98 | await expect(service.createVideo({ |
| 99 | model: 'relay-video-model', |
| 100 | prompt: 'video', |
| 101 | })).rejects.toMatchObject({ code: ResponseCode.AiCallFailed }) |
| 102 | }) |
| 103 | }) |
| 104 |