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:
@@ -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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user