test: add vitest unit tests across packages

Cover shared zod schemas, extract traversal/refs/downloads, and tts filename/error handling. Document the test setup in the README and docs.
This commit is contained in:
2026-08-08 11:13:14 +08:00
parent 1de559c321
commit 92f11db415
15 changed files with 1185 additions and 3 deletions
+92
View File
@@ -0,0 +1,92 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
import type { AssetRef } from '@tts/shared';
import { downloadAll, downloadAsset, guessMimeType } from './download.js';
const ref = (url: string): AssetRef => ({
kind: 'image',
url,
ownerGuid: 'g1',
});
function blobResponse(): Response {
return new Response(new Blob(['data']), { status: 200 });
}
afterEach(() => {
vi.unstubAllGlobals();
});
describe('guessMimeType', () => {
it('maps common image extensions', () => {
expect(guessMimeType('https://example.com/a.png')).toBe('image/png');
expect(guessMimeType('https://example.com/a.jpg')).toBe('image/jpeg');
expect(guessMimeType('https://example.com/a.jpeg')).toBe('image/jpeg');
expect(guessMimeType('https://example.com/a.gif')).toBe('image/gif');
expect(guessMimeType('https://example.com/a.webp')).toBe('image/webp');
expect(guessMimeType('https://example.com/a.pdf')).toBe('application/pdf');
});
it('ignores query strings when reading the extension', () => {
expect(guessMimeType('https://example.com/a.png?v=2')).toBe('image/png');
});
it('falls back to octet-stream for unknown extensions', () => {
expect(guessMimeType('https://example.com/a.xyz')).toBe(
'application/octet-stream',
);
expect(guessMimeType('https://example.com/noext')).toBe(
'application/octet-stream',
);
});
});
describe('downloadAsset', () => {
it('returns a blob on success', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(blobResponse()));
const blob = await downloadAsset('https://example.com/a.png');
expect(blob).toBeInstanceOf(Blob);
});
it('throws on a non-OK response', async () => {
vi.stubGlobal(
'fetch',
vi.fn().mockResolvedValue(new Response(null, { status: 404 })),
);
await expect(downloadAsset('https://example.com/a.png')).rejects.toThrow(
'Failed to download asset (404)',
);
});
});
describe('downloadAll', () => {
it('downloads every ref and reports progress', async () => {
vi.stubGlobal('fetch', vi.fn().mockImplementation(() => blobResponse()));
const refs = [ref('https://example.com/1.png'), ref('https://example.com/2.png')];
const progress: number[] = [];
const results = await downloadAll(refs, {
concurrency: 2,
onProgress: (p) => progress.push(p.done),
});
expect(results).toHaveLength(2);
expect(progress).toEqual([1, 2]);
});
it('respects the concurrency limit', async () => {
let inFlight = 0;
let maxInFlight = 0;
const fetchMock = vi.fn().mockImplementation(async () => {
inFlight += 1;
maxInFlight = Math.max(maxInFlight, inFlight);
await new Promise((r) => setTimeout(r, 5));
inFlight -= 1;
return blobResponse();
});
vi.stubGlobal('fetch', fetchMock);
const refs = Array.from({ length: 6 }, (_, i) =>
ref(`https://example.com/${i}.png`),
);
await downloadAll(refs, { concurrency: 2 });
expect(maxInFlight).toBeLessThanOrEqual(2);
});
});