import { afterEach, describe, expect, it, vi } from 'vitest'; import { fetchModFileFromUrl, fetchModFromUrl, getFileName } from './index.js'; import { ItemNotFoundError, NoFileError, SteamApiError, TtsError } from './errors.js'; afterEach(() => { vi.unstubAllGlobals(); }); // A minimal valid BSON document: { GameMode: 'Tabletop' }. const bsonBytes = new Uint8Array([ 28, 0, 0, 0, 2, 71, 97, 109, 101, 77, 111, 100, 101, 0, 9, 0, 0, 0, 84, 97, 98, 108, 101, 116, 111, 112, 0, 0, ]); function bsonResponse(): Response { return new Response(bsonBytes); } describe('fetchModFromUrl', () => { it('downloads and deserializes a save from a URL', async () => { vi.stubGlobal('fetch', vi.fn().mockResolvedValue(bsonResponse())); const mod = await fetchModFromUrl('https://example.com/save.json'); expect(mod.GameMode).toBe('Tabletop'); }); it('throws on a failed download', async () => { vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(null, { status: 404 }))); await expect(fetchModFromUrl('https://example.com/save.json')).rejects.toThrow( 'Failed to download save file (404)', ); }); }); describe('fetchModFileFromUrl', () => { it('returns bytes and a filename derived from the URL', async () => { vi.stubGlobal('fetch', vi.fn().mockResolvedValue(bsonResponse())); const { data, filename } = await fetchModFileFromUrl( 'https://example.com/files/mod.json', ); expect(new Uint8Array(data)).toEqual(bsonBytes); expect(filename).toBe('mod.json'); }); }); describe('getFileName', () => { it('parses a quoted content-disposition filename', () => { expect( getFileName('https://example.com/save', 'attachment; filename="mod.json"'), ).toBe('mod.json'); }); it('parses an unquoted filename', () => { expect( getFileName('https://example.com/save', 'attachment; filename=mod.json'), ).toBe('mod.json'); }); it('falls back to the URL path when there is no disposition', () => { expect(getFileName('https://example.com/files/mod.json', null)).toBe( 'mod.json', ); }); it('falls back to a default when the URL has no path', () => { expect(getFileName('https://example.com', null)).toBe('save.json'); }); }); describe('error classes', () => { it('ItemNotFoundError carries a 404 status', () => { const err = new ItemNotFoundError('123'); expect(err).toBeInstanceOf(TtsError); expect(err.status).toBe(404); expect(err.message).toContain('123'); }); it('NoFileError carries a 404 status', () => { const err = new NoFileError('123'); expect(err.status).toBe(404); expect(err.message).toContain('123'); }); it('SteamApiError defaults to a 502 status', () => { const err = new SteamApiError('boom'); expect(err.status).toBe(502); }); it('TtsError defaults to a 500 status', () => { const err = new TtsError('boom'); expect(err.status).toBe(500); }); });