Files
elysium/apps/api/test/routes/about.spec.ts
T

98 lines
3.8 KiB
TypeScript

/**
* @file Tests for the about route, covering release fetching, caching, and the
* fallback to pending releases when the upstream request fails.
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/* eslint-disable max-lines-per-function -- Test suites naturally have many cases */
import { Hono } from "hono";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { pendingReleases } from "../../src/data/pendingReleases.js";
const makeApp = async(): Promise<Hono> => {
const { aboutRouter } = await import("../../src/routes/about.js");
const app = new Hono();
app.route("/about", aboutRouter);
return app;
};
describe("about route", () => {
const mockFetch = vi.fn();
beforeEach(() => {
vi.resetModules();
vi.stubGlobal("fetch", mockFetch);
});
afterEach(() => {
vi.unstubAllGlobals();
mockFetch.mockReset();
});
it("returns pending and fetched releases on a successful fetch", async() => {
expect.assertions(2);
const releases = [ { body: "notes", id: 1, name: "v1.0.0" } ];
mockFetch.mockResolvedValueOnce({ json: () => {
return Promise.resolve(releases);
}, ok: true });
const app = await makeApp();
const response = await app.fetch(new Request("http://localhost/about"));
expect(response.status, "should respond with 200").toBe(200);
const body: { releases: Array<unknown> } = await response.json();
expect(body.releases, "should merge pending and fetched releases").
toStrictEqual([ ...pendingReleases, ...releases ]);
});
it("returns only pending releases when fetch is not ok", async() => {
expect.assertions(2);
mockFetch.mockResolvedValueOnce({ ok: false });
const app = await makeApp();
const response = await app.fetch(new Request("http://localhost/about"));
expect(response.status, "should respond with 200").toBe(200);
const body: { releases: Array<unknown> } = await response.json();
expect(body.releases, "should fall back to pending releases").
toStrictEqual(pendingReleases);
});
it("returns only pending releases when fetch throws", async() => {
expect.assertions(2);
mockFetch.mockRejectedValueOnce(new Error("Network error"));
const app = await makeApp();
const response = await app.fetch(new Request("http://localhost/about"));
expect(response.status, "should respond with 200").toBe(200);
const body: { releases: Array<unknown> } = await response.json();
expect(body.releases, "should fall back to pending releases").
toStrictEqual(pendingReleases);
});
it("returns cached releases on second call within TTL", async() => {
expect.assertions(2);
const releases = [ { body: "notes", id: 1, name: "v1.0.0" } ];
mockFetch.mockResolvedValueOnce({ json: () => {
return Promise.resolve(releases);
}, ok: true });
const app = await makeApp();
// First call populates cache
await app.fetch(new Request("http://localhost/about"));
// Second call should use cache, not call fetch again
const response = await app.fetch(new Request("http://localhost/about"));
expect(response.status, "should respond with 200").toBe(200);
expect(mockFetch, "should not re-fetch within the TTL").
toHaveBeenCalledTimes(1);
});
it("includes apiVersion in response", async() => {
expect.assertions(2);
mockFetch.mockResolvedValueOnce({ json: () => {
return Promise.resolve([]);
}, ok: true });
const app = await makeApp();
const response = await app.fetch(new Request("http://localhost/about"));
expect(response.status, "should respond with 200").toBe(200);
const body: { apiVersion: string } = await response.json();
expect(typeof body.apiVersion, "should expose apiVersion as a string").
toBe("string");
});
});