Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 29s
CI / tests-integration (push) Successful in 1m40s
CI / tests-unit (push) Successful in 1m53s
CI / tests-ui (push) Successful in 2m35s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 1m59s
A fallback render drops the requested effect and is only a degraded stand-in, so writing it to the 30 day disk cache kept serving the worse image long after the local renderer recovered. Cache primary renders only and let the next request pick up the real render.
145 lines
4.1 KiB
TypeScript
145 lines
4.1 KiB
TypeScript
// @ts-nocheck
|
|
import { mkdtempSync, readdirSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { getAvatarUrl } from "./imager";
|
|
import {
|
|
FALLBACK_TIMEOUT_MS,
|
|
fetchAvatarImage,
|
|
PRIMARY_TIMEOUT_MS,
|
|
} from "./imager-upstream";
|
|
|
|
afterEach(() => {
|
|
vi.unstubAllEnvs();
|
|
});
|
|
|
|
describe("getAvatarUrl", () => {
|
|
beforeEach(() => {
|
|
vi.stubEnv("NEXT_PUBLIC_IMAGER_URL", "https://img.example.com");
|
|
});
|
|
|
|
it("builds a query string with defaults (omits default params)", () => {
|
|
const url = getAvatarUrl("hd-180-1");
|
|
expect(url.startsWith("/api/imaging/avatar?")).toBe(true);
|
|
expect(url).toContain("figure=hd-180-1");
|
|
expect(url).not.toContain("direction=2");
|
|
expect(url).not.toContain("head_direction=3");
|
|
expect(url).not.toContain("size=m");
|
|
expect(url).toContain("img_format=apng");
|
|
expect(url).toContain("effect=14");
|
|
});
|
|
|
|
it("honors custom options", () => {
|
|
const url = getAvatarUrl("hd-180-1", {
|
|
size: "l",
|
|
direction: 1,
|
|
headDirection: 2,
|
|
headOnly: true,
|
|
gesture: "sml",
|
|
action: "sit",
|
|
});
|
|
expect(url).toContain("size=l");
|
|
expect(url).toContain("direction=1");
|
|
expect(url).toContain("head_direction=2");
|
|
expect(url).toContain("headonly=1");
|
|
expect(url).toContain("gesture=sml");
|
|
expect(url).toContain("action=sit");
|
|
});
|
|
|
|
it("escapes figure values in the query string", () => {
|
|
const url = getAvatarUrl("hd-180-1&x=1");
|
|
expect(url).not.toContain("&x=1");
|
|
});
|
|
|
|
it("allows custom img_format", () => {
|
|
const url = getAvatarUrl("hd-180-1", { img_format: "png" });
|
|
expect(url).toContain("img_format=png");
|
|
});
|
|
|
|
it("allows custom effect", () => {
|
|
const url = getAvatarUrl("hd-180-1", { effect: 0 });
|
|
expect(url).toContain("effect=0");
|
|
});
|
|
|
|
it("uses the local runtime proxy when no env var is set", () => {
|
|
vi.stubEnv("NEXT_PUBLIC_IMAGER_URL", "");
|
|
const url = getAvatarUrl("hd-180-1");
|
|
expect(url.startsWith("/api/imaging/avatar?")).toBe(true);
|
|
expect(url).toContain("img_format=apng");
|
|
expect(url).toContain("effect=14");
|
|
});
|
|
});
|
|
|
|
describe("upstream render budgets", () => {
|
|
it("keeps enough headroom for APNG effect renders", () => {
|
|
expect(PRIMARY_TIMEOUT_MS).toBeGreaterThanOrEqual(8_000);
|
|
});
|
|
|
|
it("does not stack a long fallback wait on a slow primary", () => {
|
|
expect(FALLBACK_TIMEOUT_MS).toBeLessThan(PRIMARY_TIMEOUT_MS);
|
|
});
|
|
});
|
|
|
|
describe("fetchAvatarImage caching", () => {
|
|
let cacheRoot = "";
|
|
|
|
beforeEach(() => {
|
|
cacheRoot = mkdtempSync(join(tmpdir(), "imager-cache-"));
|
|
vi.stubEnv("IMAGING_CACHE_ROOT", cacheRoot);
|
|
vi.stubEnv("IMAGER_URL", "http://primary.invalid/avatarimage");
|
|
vi.stubEnv("IMAGING_UPSTREAM_URL", "http://primary.invalid/avatarimage");
|
|
vi.stubEnv("APP_URL", "https://cms.example");
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.unstubAllGlobals();
|
|
rmSync(cacheRoot, { recursive: true, force: true });
|
|
});
|
|
|
|
const params = () =>
|
|
new URLSearchParams({
|
|
figure: "hd-180-1",
|
|
direction: "2",
|
|
head_direction: "3",
|
|
size: "m",
|
|
img_format: "png",
|
|
});
|
|
|
|
it("persists renders from the primary renderer", async () => {
|
|
vi.stubGlobal(
|
|
"fetch",
|
|
vi.fn(
|
|
async () =>
|
|
new Response(new Uint8Array([1, 2, 3]), {
|
|
headers: { "content-type": "image/png" },
|
|
}),
|
|
),
|
|
);
|
|
|
|
const first = await fetchAvatarImage("https://cms.example", params());
|
|
expect(first.source).toBe("primary");
|
|
expect(readdirSync(join(cacheRoot, "avatars")).length).toBe(2);
|
|
|
|
const second = await fetchAvatarImage("https://cms.example", params());
|
|
expect(second.source).toBe("cache");
|
|
});
|
|
|
|
it("never persists a fallback render", async () => {
|
|
vi.stubGlobal(
|
|
"fetch",
|
|
vi.fn(async (url: string | URL) =>
|
|
String(url).includes("primary.invalid")
|
|
? new Response(null, { status: 500 })
|
|
: new Response(new Uint8Array([4, 5, 6]), {
|
|
headers: { "content-type": "image/png" },
|
|
}),
|
|
),
|
|
);
|
|
|
|
const result = await fetchAvatarImage("https://cms.example", params());
|
|
expect(result.source).toBe("fallback");
|
|
expect(() => readdirSync(join(cacheRoot, "avatars"))).toThrow();
|
|
});
|
|
});
|