// Tests for taskService image sub-resource functions. Spec 0004 moved task // images out of the task payload and onto /api/tasks/{id}/images/... sub- // resource; these tests pin that public contract (method, URL, body, headers) // so a refactor cannot silently break the backend wire shape. // // Mocking strategy: vi.mock the apiClient module *before* importing // taskService so the service sees a vi.fn() instead of the real axios // instance. We assert against the mocked fns directly. import { describe, it, expect, vi, beforeEach } from 'vitest' vi.mock('@/api/index.js', () => ({ default: { get: vi.fn(), post: vi.fn(), patch: vi.fn(), delete: vi.fn(), }, })) import apiClient from '@/api/index.js' import { listTaskImages, createTaskImage, deleteTaskImage, getTaskImageContent, } from '@/api/taskService.js' beforeEach(() => { vi.clearAllMocks() }) describe('listTaskImages', () => { it('GETs /api/tasks/{taskId}/images/ and returns the results array', async () => { apiClient.get.mockResolvedValueOnce({ data: { count: 1, next: null, previous: null, results: [{ id: 42, mime_type: 'image/png', file_name: 'a.png' }], }, }) const out = await listTaskImages(7) expect(apiClient.get).toHaveBeenCalledTimes(1) expect(apiClient.get.mock.calls[0][0]).toBe('/api/tasks/7/images/') expect(out).toEqual([ { id: 42, mime_type: 'image/png', file_name: 'a.png' }, ]) }) it('walks pagination via fetchAllPages until next is null', async () => { apiClient.get .mockResolvedValueOnce({ data: { count: 3, next: '/api/tasks/7/images/?page=2', previous: null, results: [{ id: 1 }, { id: 2 }], }, }) .mockResolvedValueOnce({ data: { count: 3, next: null, previous: '/api/tasks/7/images/?page=1', results: [{ id: 3 }], }, }) const out = await listTaskImages(7) expect(apiClient.get).toHaveBeenCalledTimes(2) expect(apiClient.get.mock.calls[0][1]).toMatchObject({ params: { page: 1, page_size: 100 } }) expect(apiClient.get.mock.calls[1][1]).toMatchObject({ params: { page: 2, page_size: 100 } }) expect(out.map((i) => i.id)).toEqual([1, 2, 3]) }) }) describe('createTaskImage', () => { it('POSTs /api/tasks/{taskId}/images/ with mime_type/file_name/image_base64', async () => { apiClient.post.mockResolvedValueOnce({ data: { id: 99, task_id: 7, mime_type: 'image/jpeg', file_name: 'p.jpg', image_base64: 'BASE64', }, }) const out = await createTaskImage(7, { mime_type: 'image/jpeg', file_name: 'p.jpg', image_base64: 'BASE64', }) expect(apiClient.post).toHaveBeenCalledTimes(1) expect(apiClient.post.mock.calls[0][0]).toBe('/api/tasks/7/images/') expect(apiClient.post.mock.calls[0][1]).toEqual({ mime_type: 'image/jpeg', file_name: 'p.jpg', image_base64: 'BASE64', }) expect(out).toMatchObject({ id: 99, mime_type: 'image/jpeg' }) }) }) describe('deleteTaskImage', () => { it('DELETEs /api/tasks/{taskId}/images/{imageId}/', async () => { apiClient.delete.mockResolvedValueOnce({ data: '' }) await deleteTaskImage(7, 42) expect(apiClient.delete).toHaveBeenCalledTimes(1) expect(apiClient.delete.mock.calls[0][0]).toBe('/api/tasks/7/images/42/') }) }) describe('getTaskImageContent', () => { it('GETs /content/ as blob with Accept */* to avoid 406', async () => { const blob = new Blob(['x']) apiClient.get.mockResolvedValueOnce({ data: blob }) const out = await getTaskImageContent(7, 42) expect(apiClient.get).toHaveBeenCalledTimes(1) expect(apiClient.get.mock.calls[0][0]).toBe('/api/tasks/7/images/42/content/') expect(apiClient.get.mock.calls[0][1]).toMatchObject({ responseType: 'blob', headers: { Accept: '*/*' }, }) expect(out).toBe(blob) }) })