import { createWorkflow, deleteWorkflow, listWorkflows } from '../../src/workflows/client.js'; import type { WorkflowBody } from '../../src/workflows/types.js'; vi.mock('../../src/keyring.js', () => ({ getAccessToken: vi.fn().mockResolvedValue(null), getRefreshToken: vi.fn().mockResolvedValue(null), storeCredentials: vi.fn().mockResolvedValue(undefined), })); const mockFetch = vi.fn(); global.fetch = mockFetch; beforeEach(() => { vi.stubEnv('MINTLIFY_SESSION_TOKEN', 'test-token'); vi.stubEnv('MINTLIFY_API_URL', 'http://test-server:5000'); mockFetch.mockReset(); }); afterEach(() => { vi.unstubAllEnvs(); }); function mockOk(data: unknown, status = 200) { mockFetch.mockResolvedValueOnce({ ok: true, status, json: () => Promise.resolve(data), }); } function mockError(status: number, body: string) { mockFetch.mockResolvedValueOnce({ ok: false, status, statusText: 'Bad Request', text: () => Promise.resolve(body), }); } function calledUrl(): string { const arg = mockFetch.mock.calls[0]![0]; return typeof arg === 'string' ? arg : String(arg); } function calledInit(): RequestInit { return mockFetch.mock.calls[0]![1] as RequestInit; } const body: WorkflowBody = { name: 'My Workflow', on: { cron: '0 6 * * *' }, prompt: 'Translate docs', }; describe('workflow client auth', () => { it('throws when no session token is set', async () => { vi.stubEnv('MINTLIFY_SESSION_TOKEN', ''); await expect(listWorkflows('docs')).rejects.toThrow('Not authenticated'); }); it('sends bearer token header', async () => { mockOk([]); await listWorkflows('docs'); expect(mockFetch).toHaveBeenCalledWith( expect.any(String), expect.objectContaining({ headers: expect.objectContaining({ Authorization: 'Bearer test-token' }), }) ); }); }); describe('createWorkflow', () => { it('POSTs the body to the subdomain-scoped URL', async () => { mockOk({ _id: 'wf123', name: 'My Workflow' }, 201); const result = await createWorkflow('docs', body); expect(new URL(calledUrl()).pathname).toBe('/api/cli/workflows/docs'); const init = calledInit(); expect(init.method).toBe('POST'); expect(init.body).toBe(JSON.stringify(body)); expect(init.headers).toMatchObject({ 'Content-Type': 'application/json' }); expect(result).toMatchObject({ _id: 'wf123' }); }); it('encodes the subdomain', async () => { mockOk({ _id: 'wf123' }, 201); await createWorkflow('my docs/sub', body); expect(new URL(calledUrl()).pathname).toBe('/api/cli/workflows/my%20docs%2Fsub'); }); it('throws on API error', async () => { mockError(400, 'Invalid request body'); await expect(createWorkflow('docs', body)).rejects.toThrow( 'API error (400): Invalid request body' ); }); }); describe('listWorkflows', () => { it('GETs the subdomain-scoped URL', async () => { mockOk([{ _id: 'wf1' }, { _id: 'wf2' }]); const result = await listWorkflows('docs'); expect(new URL(calledUrl()).pathname).toBe('/api/cli/workflows/docs'); const init = calledInit(); expect(init.method).toBeUndefined(); expect(result).toHaveLength(2); }); }); describe('deleteWorkflow', () => { it('DELETEs by workflow id', async () => { mockOk({ success: true }); await deleteWorkflow('docs', 'wf123'); expect(new URL(calledUrl()).pathname).toBe('/api/cli/workflows/docs/wf123'); expect(calledInit().method).toBe('DELETE'); }); it('encodes both segments', async () => { mockOk({ success: true }); await deleteWorkflow('my docs', 'wf 123'); expect(new URL(calledUrl()).pathname).toBe('/api/cli/workflows/my%20docs/wf%20123'); }); });