import { describe, expect, it, vi } from 'vitest'; import { createDataServiceOperations, DataServiceCloudClient, } from '@electron/services/data-service-client'; const projectId = '11111111-1111-4111-8111-111111111111'; function jsonResponse(value: unknown, init: ResponseInit = {}): Response { const headers = new Headers(init.headers); headers.set('content-type', 'application/json'); return new Response(JSON.stringify(value), { ...init, headers }); } function instance(project = projectId) { return { instance_id: '22222222-2222-4222-8222-222222222222', project_id: project, collections: [], usage: { document_count: 0, total_bytes: 0 }, limits: { max_collections: 20, max_documents: 5000, max_total_bytes: 20971520, max_document_bytes: 65536, list_default_limit: 50, list_max_limit: 100, list_max_data_bytes: 1048576, mutations_per_minute: 120, }, created_at: '2026-08-26T08:00:00Z', updated_at: '2026-08-26T08:00:00Z', }; } function document() { return { id: 'todo-1', data: { title: 'Ship P0', done: false }, revision: 7, created_at: '2026-08-26T08:00:00Z', updated_at: '2026-08-26T08:00:00Z', }; } describe('DataServiceCloudClient', () => { it('projects the control-plane route and direct DTO', async () => { const fetchImpl = vi.fn().mockResolvedValue( jsonResponse(instance(), { status: 200 }), ); const client = new DataServiceCloudClient({ fetchImpl, getAccessToken: vi.fn().mockResolvedValue('access-token'), apiBaseUrl: 'https://square.example/', }); const result = await client.configure(projectId, ['todos']); expect(result).toMatchObject({ success: true, status: 200, data: instance() }); expect(fetchImpl).toHaveBeenCalledWith( `https://square.example/api/data-service/v1/projects/${projectId}`, expect.objectContaining({ method: 'PUT', body: JSON.stringify({ collections: ['todos'] }), headers: expect.objectContaining({ Accept: 'application/json', Authorization: 'Bearer access-token', 'Content-Type': 'application/json', }), }), ); }); it('refreshes and replays exactly once after an authoritative 401', async () => { const fetchImpl = vi.fn() .mockResolvedValueOnce(new Response(null, { status: 401 })) .mockResolvedValueOnce(jsonResponse(instance(), { status: 200 })); const getAccessToken = vi.fn(async (options?: { forceRefresh?: boolean }) => ( options?.forceRefresh ? 'refreshed-token' : 'stale-token' )); const client = new DataServiceCloudClient({ fetchImpl, getAccessToken }); const result = await client.inspect(projectId); expect(result.success).toBe(true); expect(fetchImpl).toHaveBeenCalledTimes(2); expect(getAccessToken).toHaveBeenNthCalledWith(1, { fetchImpl }); expect(getAccessToken).toHaveBeenNthCalledWith(2, { fetchImpl, forceRefresh: true }); expect((fetchImpl.mock.calls[1][1] as RequestInit).headers).toEqual( expect.objectContaining({ Authorization: 'Bearer refreshed-token' }), ); }); it('does not replay an ambiguous transport failure', async () => { const fetchImpl = vi.fn().mockRejectedValue(new Error('socket closed')); const getAccessToken = vi.fn().mockResolvedValue('access-token'); const client = new DataServiceCloudClient({ fetchImpl, getAccessToken }); const result = await client.inspect(projectId); expect(result).toMatchObject({ success: false, status: 503, code: 'data_service_unavailable', retryable: true, data: null, }); expect(fetchImpl).toHaveBeenCalledTimes(1); expect(getAccessToken).toHaveBeenCalledTimes(1); }); it('keeps error fields safe while preserving accepted code and context', async () => { const fetchImpl = vi.fn().mockResolvedValue( jsonResponse({ detail: { code: 'quota_exceeded', message: 'owner secret must not cross the Host boundary', retryable: false, context: { resource: 'bytes', limit: 20971520, current: 20900000, attempted: 80000, owner_user_id: 'private', }, }, }, { status: 409 }), ); const client = new DataServiceCloudClient({ fetchImpl, getAccessToken: vi.fn().mockResolvedValue('access-token'), }); const result = await client.putDocument(projectId, { collection: 'todos', document_id: 'todo-1', data: { done: false }, }); expect(result).toEqual({ success: false, status: 409, code: 'quota_exceeded', error: 'Data Service quota exceeded', retryable: false, context: { resource: 'bytes', limit: 20971520, current: 20900000, attempted: 80000 }, data: null, }); }); it('normalizes an invalid success DTO', async () => { const client = new DataServiceCloudClient({ fetchImpl: vi.fn().mockResolvedValue(jsonResponse({ items: [] })), getAccessToken: vi.fn().mockResolvedValue('access-token'), }); const result = await client.listProjects(); expect(result).toEqual({ success: false, status: 502, code: 'upstream_invalid_response', error: 'Data Service returned an invalid response', retryable: false, data: null, }); }); it('rejects a control-plane response for a different project', async () => { const client = new DataServiceCloudClient({ fetchImpl: vi.fn().mockResolvedValue( jsonResponse(instance('33333333-3333-4333-8333-333333333333'), { status: 200 }), ), getAccessToken: vi.fn().mockResolvedValue('access-token'), }); const result = await client.inspect(projectId); expect(result).toMatchObject({ success: false, status: 502, code: 'upstream_invalid_response', data: null, }); }); it('rejects malformed known error fields', async () => { const client = new DataServiceCloudClient({ fetchImpl: vi.fn().mockResolvedValue( jsonResponse({ detail: { code: 'quota_exceeded', retryable: 'no' } }, { status: 409 }), ), getAccessToken: vi.fn().mockResolvedValue('access-token'), }); const result = await client.inspect(projectId); expect(result).toMatchObject({ success: false, status: 502, code: 'upstream_invalid_response', data: null, }); }); it('rejects a known error code paired with the wrong HTTP status', async () => { const client = new DataServiceCloudClient({ fetchImpl: vi.fn().mockResolvedValue( jsonResponse({ detail: { code: 'quota_exceeded' } }, { status: 400 }), ), getAccessToken: vi.fn().mockResolvedValue('access-token'), }); const result = await client.inspect(projectId); expect(result).toMatchObject({ success: false, status: 502, code: 'upstream_invalid_response', data: null, }); }); it('normalizes malformed client errors and all upstream 5xx responses safely', async () => { const fetchImpl = vi.fn() .mockResolvedValueOnce(new Response(null, { status: 404 })) .mockResolvedValueOnce(new Response('private upstream exception', { status: 500 })); const client = new DataServiceCloudClient({ fetchImpl, getAccessToken: vi.fn().mockResolvedValue('access-token'), }); const malformed = await client.inspect(projectId); const unavailable = await client.inspect(projectId); expect(malformed).toMatchObject({ success: false, status: 502, code: 'upstream_invalid_response', data: null }); expect(unavailable).toMatchObject({ success: false, status: 503, code: 'data_service_unavailable', retryable: true, data: null, }); }); it('projects data-plane paths, query parameters, and strong revision headers exactly', async () => { const fetchImpl = vi.fn() .mockResolvedValueOnce(jsonResponse(document(), { status: 200 })) .mockResolvedValueOnce(jsonResponse({ items: [document()], next_cursor: null, limit: 50 }, { status: 200 })) .mockResolvedValueOnce(jsonResponse(document(), { status: 200 })) .mockResolvedValueOnce(new Response(null, { status: 204 })); const client = new DataServiceCloudClient({ fetchImpl, getAccessToken: vi.fn().mockResolvedValue('access-token'), apiBaseUrl: 'https://square.example', }); await client.getDocument(projectId, 'todos', 'todo-1'); await client.listDocuments(projectId, 'todos', 50, 'cursor/next'); await client.putDocument(projectId, { collection: 'todos', document_id: 'todo-1', data: document().data, if_revision: 7, }); await client.deleteDocument(projectId, { collection: 'todos', document_id: 'todo-1', if_revision: 7 }); expect(fetchImpl.mock.calls.map(([url]) => url)).toEqual([ `https://square.example/api/data/v1/projects/${projectId}/collections/todos/documents/todo-1`, `https://square.example/api/data/v1/projects/${projectId}/collections/todos/documents?limit=50&cursor=cursor%2Fnext`, `https://square.example/api/data/v1/projects/${projectId}/collections/todos/documents/todo-1`, `https://square.example/api/data/v1/projects/${projectId}/collections/todos/documents/todo-1`, ]); expect((fetchImpl.mock.calls[2][1] as RequestInit).headers).toEqual( expect.objectContaining({ 'If-Match': '"7"' }), ); expect((fetchImpl.mock.calls[3][1] as RequestInit).headers).toEqual( expect.objectContaining({ 'If-Match': '"7"' }), ); }); it('preserves opaque cursor text when projecting the query', async () => { const listDocuments = vi.fn().mockResolvedValue( jsonResponse({ items: [], next_cursor: null, limit: 50 }, { status: 200 }), ); const client = new DataServiceCloudClient({ fetchImpl: listDocuments, getAccessToken: vi.fn().mockResolvedValue('access-token'), }); const result = await client.listDocuments(projectId, 'todos', 50, ' cursor '); expect(result).toMatchObject({ success: true, status: 200 }); expect(listDocuments.mock.calls[0][0]).toContain('cursor=+cursor+'); }); }); describe('DataServiceOperations', () => { it('derives active project identity for every operation except owner-wide listing', async () => { const client = { listProjects: vi.fn().mockResolvedValue({ success: true, status: 200, code: null, error: null, retryable: false, data: { items: [], total: 0, instance_limit: 20 } }), inspect: vi.fn().mockResolvedValue({ success: true, status: 200, code: null, error: null, retryable: false, data: instance() }), } as unknown as DataServiceCloudClient; const requireActiveRealProjectWithIdentity = vi.fn().mockResolvedValue({ project: {}, path: 'C:\\projects\\active', projectId, }); const operations = createDataServiceOperations({ projects: { requireActiveRealProjectWithIdentity }, client, }); await operations.listProjects(); expect(requireActiveRealProjectWithIdentity).not.toHaveBeenCalled(); const unconfirmed = await operations.removeProject({ confirmed: false as true }); expect(unconfirmed).toMatchObject({ success: false, status: 400, code: 'confirmation_required', data: null }); expect(requireActiveRealProjectWithIdentity).not.toHaveBeenCalled(); await operations.inspect(); expect(requireActiveRealProjectWithIdentity).toHaveBeenCalledTimes(1); expect(client.inspect).toHaveBeenCalledWith(projectId); }); });