import { afterEach, expect, test, vi } from 'vitest'; import { createDeployment, createSession, deleteSession, getSession, listDeployments, searchBranches, searchCommits, uninstallDeployment, } from './api'; afterEach(() => { vi.unstubAllGlobals(); }); test('lists deployments from the same-origin control plane', async () => { const fetchMock = vi.fn().mockResolvedValue( new Response( JSON.stringify({ deployments: [ { id: 'preview-1', branch: 'feature/demo', status: 'running', health: 'healthy', createdAt: '2026-08-15T00:00:00Z', updatedAt: '2026-08-15T00:01:00Z', }, ], }), { status: 200, headers: { 'Content-Type': 'application/json' } }, ), ); vi.stubGlobal('fetch', fetchMock); const deployments = await listDeployments(); expect(deployments).toHaveLength(1); expect(fetchMock).toHaveBeenCalledWith( '/api/preview-deployer/deployments', expect.objectContaining({ credentials: 'same-origin' }), ); }); test('uses an http-only session without browser token persistence', async () => { const fetchMock = vi .fn() .mockImplementation((_url: string, init: RequestInit) => Promise.resolve( new Response( init.method === 'DELETE' ? null : JSON.stringify({ authenticated: true }), { status: 200, headers: { 'Content-Type': 'application/json' } }, ), ), ); vi.stubGlobal('fetch', fetchMock); expect(await getSession()).toEqual({ authenticated: true }); await createSession('team-access-token'); await deleteSession(); expect(fetchMock).toHaveBeenNthCalledWith( 2, '/api/preview-deployer/session', expect.objectContaining({ method: 'POST', credentials: 'same-origin', body: JSON.stringify({ accessToken: 'team-access-token' }), }), ); expect(fetchMock).toHaveBeenNthCalledWith( 3, '/api/preview-deployer/session', expect.objectContaining({ method: 'DELETE' }), ); }); test('submits only branch and optional commit to fixed endpoints', async () => { const buildResponse = () => new Response( JSON.stringify({ id: 'preview-2', branch: 'master', status: 'queued', health: 'pending', createdAt: '2026-08-15T00:00:00Z', updatedAt: '2026-08-15T00:00:00Z', }), { status: 200, headers: { 'Content-Type': 'application/json' } }, ); const fetchMock = vi .fn() .mockImplementation(() => Promise.resolve(buildResponse())); vi.stubGlobal('fetch', fetchMock); await createDeployment({ branch: 'master', commitHash: 'aa5221abc' }); await uninstallDeployment('preview/2'); expect(fetchMock).toHaveBeenNthCalledWith( 1, '/api/preview-deployer/deployments', expect.objectContaining({ method: 'POST', body: JSON.stringify({ branch: 'master', commitHash: 'aa5221abc' }), }), ); expect(fetchMock).toHaveBeenNthCalledWith( 2, '/api/preview-deployer/deployments/preview%2F2/uninstall', expect.objectContaining({ method: 'POST' }), ); }); test('searches branches and branch-scoped commits with encoded queries', async () => { const fetchMock = vi .fn() .mockResolvedValueOnce( new Response( JSON.stringify({ items: [{ name: 'feature/search', commitHash: 'abcdef123456' }], }), { status: 200, headers: { 'Content-Type': 'application/json' } }, ), ) .mockResolvedValueOnce( new Response( JSON.stringify({ items: [ { commitHash: 'abcdef123456', shortHash: 'abcdef1', subject: '搜索提交', }, ], }), { status: 200, headers: { 'Content-Type': 'application/json' } }, ), ); vi.stubGlobal('fetch', fetchMock); expect(await searchBranches('feature/search')).toHaveLength(1); expect(await searchCommits('feature/search', 'abc def')).toHaveLength(1); expect(fetchMock).toHaveBeenNthCalledWith( 1, '/api/preview-deployer/refs/branches?q=feature%2Fsearch', expect.objectContaining({ credentials: 'same-origin' }), ); expect(fetchMock).toHaveBeenNthCalledWith( 2, '/api/preview-deployer/refs/commits?branch=feature%2Fsearch&q=abc%20def', expect.objectContaining({ credentials: 'same-origin' }), ); });