mirror of
https://github.com/wahyd4/heygo.git
synced 2026-08-09 05:15:52 +10:00
158 lines
5.2 KiB
TypeScript
158 lines
5.2 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import {
|
|
ApiClientError,
|
|
buildPreviewUrl,
|
|
createPrivateLink,
|
|
deletePrivateLink,
|
|
listPrivateLinks,
|
|
listPublicLinks,
|
|
updatePrivateLink,
|
|
} from '../src/lib/api';
|
|
|
|
const originalFetch = globalThis.fetch;
|
|
|
|
function mockFetch(responder: (input: RequestInfo | URL, init?: RequestInit) => {
|
|
status?: number;
|
|
body?: unknown;
|
|
statusText?: string;
|
|
}) {
|
|
const calls: { path: string; init?: RequestInit }[] = [];
|
|
const fetchMock = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
const path = typeof input === 'string' ? input : new URL(input.toString()).pathname;
|
|
calls.push({ path, init });
|
|
const { status = 200, body = {}, statusText = 'OK' } = responder(input, init);
|
|
return new Response(JSON.stringify(body), {
|
|
status,
|
|
statusText,
|
|
headers: { 'content-type': 'application/json' },
|
|
});
|
|
});
|
|
globalThis.fetch = fetchMock as unknown as typeof fetch;
|
|
return {
|
|
calls,
|
|
fetchMock,
|
|
};
|
|
}
|
|
|
|
afterEach(() => {
|
|
globalThis.fetch = originalFetch;
|
|
});
|
|
|
|
describe('api client', () => {
|
|
it('listPrivateLinks sends GET with credentials to /api/links/private', async () => {
|
|
const { calls } = mockFetch(() => ({
|
|
body: { links: [{ id: 'link_1', alias: 'docs', scope: 'private', linkType: 'redirect' }] },
|
|
}));
|
|
|
|
const result = await listPrivateLinks();
|
|
|
|
expect(calls).toHaveLength(1);
|
|
expect(calls[0].path).toBe('/api/links/private');
|
|
expect(calls[0].init?.method).toBeUndefined();
|
|
expect(calls[0].init?.credentials).toBe('include');
|
|
expect(result.links).toHaveLength(1);
|
|
expect(result.links[0].alias).toBe('docs');
|
|
});
|
|
|
|
it('createPrivateLink POSTs a normalized JSON body', async () => {
|
|
const { calls } = mockFetch((_input, init) => ({
|
|
status: 201,
|
|
body: { link: { id: 'new', alias: 'docs', scope: 'private', linkType: 'redirect' } },
|
|
}));
|
|
|
|
const result = await createPrivateLink({
|
|
alias: 'Docs',
|
|
linkType: 'redirect',
|
|
targetUrl: 'https://example.com',
|
|
description: ' spaced ',
|
|
});
|
|
|
|
expect(calls[0].init?.method).toBe('POST');
|
|
expect(calls[0].init?.credentials).toBe('include');
|
|
const body = JSON.parse(calls[0].init?.body as string);
|
|
expect(body).toEqual({
|
|
alias: 'Docs',
|
|
linkType: 'redirect',
|
|
targetUrl: 'https://example.com',
|
|
contentMarkdown: null,
|
|
description: ' spaced ',
|
|
});
|
|
expect(result.link.id).toBe('new');
|
|
});
|
|
|
|
it('updatePrivateLink targets the link id with PATCH', async () => {
|
|
const { calls } = mockFetch(() => ({
|
|
body: { link: { id: 'link_1', alias: 'renamed', scope: 'private', linkType: 'custom' } },
|
|
}));
|
|
|
|
await updatePrivateLink('link_1', { alias: 'renamed', linkType: 'custom', contentMarkdown: '# Hi' });
|
|
|
|
expect(calls[0].path).toBe('/api/links/private/link_1');
|
|
expect(calls[0].init?.method).toBe('PATCH');
|
|
const body = JSON.parse(calls[0].init?.body as string);
|
|
expect(body.linkType).toBe('custom');
|
|
expect(body.targetUrl).toBeNull();
|
|
expect(body.contentMarkdown).toBe('# Hi');
|
|
});
|
|
|
|
it('deletePrivateLink issues a DELETE request', async () => {
|
|
const { calls } = mockFetch(() => ({ body: { ok: true } }));
|
|
|
|
const result = await deletePrivateLink('link_1');
|
|
|
|
expect(calls[0].path).toBe('/api/links/private/link_1');
|
|
expect(calls[0].init?.method).toBe('DELETE');
|
|
expect(result.ok).toBe(true);
|
|
});
|
|
|
|
it('listPublicLinks reads /api/links/public', async () => {
|
|
const { calls } = mockFetch(() => ({ body: { links: [] } }));
|
|
|
|
await listPublicLinks();
|
|
|
|
expect(calls[0].path).toBe('/api/links/public');
|
|
});
|
|
|
|
it('encodes link ids containing special path segments', async () => {
|
|
const { calls } = mockFetch(() => ({ body: { ok: true } }));
|
|
|
|
await deletePrivateLink('link/with/slash');
|
|
|
|
expect(calls[0].path).toBe('/api/links/private/link%2Fwith%2Fslash');
|
|
});
|
|
|
|
it('throws ApiClientError with the server error message on non-ok responses', async () => {
|
|
mockFetch(() => ({ status: 409, body: { error: 'Alias already exists' } }));
|
|
|
|
await expect(createPrivateLink({ alias: 'dup', linkType: 'redirect', targetUrl: 'https://x' }))
|
|
.rejects.toMatchObject({ name: 'ApiClientError', status: 409, message: 'Alias already exists' });
|
|
});
|
|
|
|
it('falls back to status text when the body has no error field', async () => {
|
|
mockFetch(() => ({ status: 500, body: {}, statusText: 'Internal Server Error' }));
|
|
|
|
await expect(listPrivateLinks()).rejects.toMatchObject({
|
|
name: 'ApiClientError',
|
|
status: 500,
|
|
message: '500 Internal Server Error',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('buildPreviewUrl', () => {
|
|
it('returns null for empty target URLs', () => {
|
|
expect(buildPreviewUrl(null)).toBeNull();
|
|
expect(buildPreviewUrl('')).toBeNull();
|
|
});
|
|
|
|
it('substitutes known template placeholders with encoded values', () => {
|
|
const url = 'https://example.com/search?q={query}&lang={lang}';
|
|
expect(buildPreviewUrl(url, { query: 'go links', lang: 'en' }))
|
|
.toBe('https://example.com/search?q=go%20links&lang=en');
|
|
});
|
|
|
|
it('replaces leftover placeholders with a readable token', () => {
|
|
expect(buildPreviewUrl('https://example.com/{region}/view')).toBe('https://example.com/example/view');
|
|
});
|
|
});
|