Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123import { describe, expect, it, vi } from 'vitest';import { ClientResponseError } from './client';import { createRequestCache, httpStatusFor, parallel, toHttpError } from './load';
const cre = (status: number, error: string, message?: string): ClientResponseError => new ClientResponseError({ status, data: { error, message } });
describe('httpStatusFor', () => { it('passes through an in-band XRPC status', () => { expect(httpStatusFor(cre(404, 'RecordNotFound'))).toBe(404); });
it('prefers an in-band status over the error-name mapping', () => { // status 503 is in [400,599] so it wins even though the name maps to 404. expect(httpStatusFor(cre(503, 'RecordNotFound'))).toBe(503); });
it('maps the error name when the status is out of the 400-599 band', () => { expect(httpStatusFor(cre(200, 'RecordNotFound'))).toBe(404); expect(httpStatusFor(cre(200, 'InvalidRequest'))).toBe(400); expect(httpStatusFor(cre(200, 'UpstreamFailed'))).toBe(502); expect(httpStatusFor(cre(200, 'UpstreamGone'))).toBe(502); expect(httpStatusFor(cre(200, 'InvalidRecord'))).toBe(502); expect(httpStatusFor(cre(200, 'Overloaded'))).toBe(503); expect(httpStatusFor(cre(200, 'SomethingElse'))).toBe(500); });
it('maps any non-XRPC cause to 500', () => { expect(httpStatusFor(new Error('boom'))).toBe(500); expect(httpStatusFor('a string')).toBe(500); expect(httpStatusFor(undefined)).toBe(500); expect(httpStatusFor({ status: 404 })).toBe(500); });});
describe('toHttpError', () => { it('throws a SvelteKit error with the mapped status and the XRPC description', () => { let thrown: unknown; try { toHttpError(cre(404, 'RecordNotFound', 'no such repo')); } catch (e) { thrown = e; } expect(thrown).toMatchObject({ status: 404, body: { message: 'no such repo' } }); });
it('falls back to the error name when the description is absent', () => { let thrown: unknown; try { toHttpError(cre(200, 'Overloaded')); } catch (e) { thrown = e; } expect(thrown).toMatchObject({ status: 503, body: { message: 'Overloaded' } }); });
it('uses the fallback message + 500 for a non-XRPC cause', () => { let thrown: unknown; try { toHttpError(new Error('boom'), 'could not load'); } catch (e) { thrown = e; } expect(thrown).toMatchObject({ status: 500, body: { message: 'could not load' } }); });});
describe('parallel', () => { it('preserves key/value pairing regardless of settle order', async () => { // deferred across a few microtasks (no real timer) so it settles after `fast`. const slow = Promise.resolve() .then(() => Promise.resolve()) .then(() => 'slow'); const result = await parallel({ fast: Promise.resolve('fast'), slow }); expect(result).toEqual({ fast: 'fast', slow: 'slow' }); });
it('resolves to an empty object for no tasks', async () => { await expect(parallel({})).resolves.toEqual({}); });
it('rejects if any input promise rejects', async () => { await expect( parallel({ a: Promise.resolve(1), b: Promise.reject(new Error('boom')) }) ).rejects.toThrow('boom'); });});
describe('createRequestCache', () => { it('invokes the loader once per key and shares the value', async () => { const cache = createRequestCache(); const load = vi.fn(async () => 42);
const first = await cache.run('k', load); const second = await cache.run('k', load);
expect(first).toBe(42); expect(second).toBe(42); expect(load).toHaveBeenCalledTimes(1); });
it('shares one in-flight promise for concurrent same-key calls', () => { const cache = createRequestCache(); const load = vi.fn(async () => 'v');
const a = cache.run('k', load); const b = cache.run('k', load);
expect(a).toBe(b); expect(load).toHaveBeenCalledTimes(1); });
it('runs distinct keys independently', async () => { const cache = createRequestCache(); const load = vi.fn(async (key: string) => key.toUpperCase());
await cache.run('a', () => load('a')); await cache.run('b', () => load('b'));
expect(load).toHaveBeenCalledTimes(2); });});