This repository has no description
1import { describe, expect, it, vi } from "vitest";
2import { ClientResponseError } from "./client";
3import { createRequestCache, httpStatusFor, parallel, toHttpError } from "./load";
4
5const cre = (status: number, error: string, message?: string): ClientResponseError =>
6 new ClientResponseError({ status, data: { error, message } });
7
8describe("httpStatusFor", () => {
9 it("passes through an in-band XRPC status", () => {
10 expect(httpStatusFor(cre(404, "RecordNotFound"))).toBe(404);
11 });
12
13 it("prefers an in-band status over the error-name mapping", () => {
14 // status 503 is in [400,599] so it wins even though the name maps to 404.
15 expect(httpStatusFor(cre(503, "RecordNotFound"))).toBe(503);
16 });
17
18 it("maps the error name when the status is out of the 400-599 band", () => {
19 expect(httpStatusFor(cre(200, "RecordNotFound"))).toBe(404);
20 expect(httpStatusFor(cre(200, "InvalidRequest"))).toBe(400);
21 expect(httpStatusFor(cre(200, "UpstreamFailed"))).toBe(502);
22 expect(httpStatusFor(cre(200, "UpstreamGone"))).toBe(502);
23 expect(httpStatusFor(cre(200, "InvalidRecord"))).toBe(502);
24 expect(httpStatusFor(cre(200, "Overloaded"))).toBe(503);
25 expect(httpStatusFor(cre(200, "SomethingElse"))).toBe(500);
26 });
27
28 it("maps any non-XRPC cause to 500", () => {
29 expect(httpStatusFor(new Error("boom"))).toBe(500);
30 expect(httpStatusFor("a string")).toBe(500);
31 expect(httpStatusFor(undefined)).toBe(500);
32 expect(httpStatusFor({ status: 404 })).toBe(500);
33 });
34});
35
36describe("toHttpError", () => {
37 it("throws a SvelteKit error with the mapped status and the XRPC description", () => {
38 let thrown: unknown;
39 try {
40 toHttpError(cre(404, "RecordNotFound", "no such repo"));
41 } catch (e) {
42 thrown = e;
43 }
44 expect(thrown).toMatchObject({ status: 404, body: { message: "no such repo" } });
45 });
46
47 it("falls back to the error name when the description is absent", () => {
48 let thrown: unknown;
49 try {
50 toHttpError(cre(200, "Overloaded"));
51 } catch (e) {
52 thrown = e;
53 }
54 expect(thrown).toMatchObject({ status: 503, body: { message: "Overloaded" } });
55 });
56
57 it("uses the fallback message + 500 for a non-XRPC cause", () => {
58 let thrown: unknown;
59 try {
60 toHttpError(new Error("boom"), "could not load");
61 } catch (e) {
62 thrown = e;
63 }
64 expect(thrown).toMatchObject({ status: 500, body: { message: "could not load" } });
65 });
66});
67
68describe("parallel", () => {
69 it("preserves key/value pairing regardless of settle order", async () => {
70 // deferred across a few microtasks (no real timer) so it settles after `fast`.
71 const slow = Promise.resolve()
72 .then(() => Promise.resolve())
73 .then(() => "slow");
74 const result = await parallel({ fast: Promise.resolve("fast"), slow });
75 expect(result).toEqual({ fast: "fast", slow: "slow" });
76 });
77
78 it("resolves to an empty object for no tasks", async () => {
79 await expect(parallel({})).resolves.toEqual({});
80 });
81
82 it("rejects if any input promise rejects", async () => {
83 await expect(
84 parallel({ a: Promise.resolve(1), b: Promise.reject(new Error("boom")) })
85 ).rejects.toThrow("boom");
86 });
87});
88
89describe("createRequestCache", () => {
90 it("invokes the loader once per key and shares the value", async () => {
91 const cache = createRequestCache();
92 const load = vi.fn(async () => 42);
93
94 const first = await cache.run("k", load);
95 const second = await cache.run("k", load);
96
97 expect(first).toBe(42);
98 expect(second).toBe(42);
99 expect(load).toHaveBeenCalledTimes(1);
100 });
101
102 it("shares one in-flight promise for concurrent same-key calls", () => {
103 const cache = createRequestCache();
104 const load = vi.fn(async () => "v");
105
106 const a = cache.run("k", load);
107 const b = cache.run("k", load);
108
109 expect(a).toBe(b);
110 expect(load).toHaveBeenCalledTimes(1);
111 });
112
113 it("runs distinct keys independently", async () => {
114 const cache = createRequestCache();
115 const load = vi.fn(async (key: string) => key.toUpperCase());
116
117 await cache.run("a", () => load("a"));
118 await cache.run("b", () => load("b"));
119
120 expect(load).toHaveBeenCalledTimes(2);
121 });
122});