CI / check (push) Successful in 40s
CI / tests-imaging-browsers (push) Successful in 1m13s
CI / tests-integration (push) Successful in 2m18s
CI / tests-unit (push) Successful in 2m39s
CI / tests-ui (push) Successful in 3m7s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m15s
In production the edge proxy fronts /imaging to the imager container, so the CMS route only answers when reached directly. Asserting it in the shared spec failed against the public site in all three engines. Its behaviour is covered by src/lib/imaging-route.test.ts; the e2e spec now only checks the endpoints that are identical in both topologies (/api/imaging/avatar and the badge).
89 lines
3.1 KiB
TypeScript
89 lines
3.1 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
// The exact figure that regressed in production: the game client appends a
|
|
// trailing `.<n>` segment that the renderer ignores, but the previous
|
|
// validation regex rejected with a 400.
|
|
const FIGURE =
|
|
"hr-11782-40-40.hd-180-7-14.ch-11592-66.lg-10726-79-1408.sh-11764-1408.ha-11958-70-1408.wa-2007-0.0";
|
|
|
|
const avatarPath = (params: Record<string, string> = {}) =>
|
|
`/api/imaging/avatar?${new URLSearchParams({ figure: FIGURE, ...params })}`;
|
|
|
|
function countBytes(haystack: Buffer, needle: Buffer): number {
|
|
let count = 0;
|
|
for (
|
|
let index = haystack.indexOf(needle);
|
|
index !== -1;
|
|
index = haystack.indexOf(needle, index + needle.length)
|
|
) {
|
|
count += 1;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
test("renders an animated APNG for a figure with an effect", async ({
|
|
request,
|
|
}) => {
|
|
const response = await request.get(
|
|
avatarPath({ img_format: "apng", effect: "14" }),
|
|
);
|
|
expect(response.status()).toBe(200);
|
|
expect(response.headers()["content-type"]).toContain("image/");
|
|
// `unavailable` means the placeholder was served, i.e. no real render.
|
|
expect(response.headers()["x-imager-source"]).not.toBe("unavailable");
|
|
|
|
const body = await response.body();
|
|
expect(body.length).toBeGreaterThan(1000);
|
|
// APNG signature chunks: `acTL` announces the animation, `fcTL` marks frames.
|
|
expect(body.includes(Buffer.from("acTL"))).toBe(true);
|
|
expect(countBytes(body, Buffer.from("fcTL"))).toBeGreaterThan(1);
|
|
});
|
|
|
|
test("serves a static PNG when no animation is requested", async ({
|
|
request,
|
|
}) => {
|
|
const response = await request.get(avatarPath({ img_format: "png" }));
|
|
expect(response.status()).toBe(200);
|
|
expect(response.headers()["content-type"]).toContain("image/png");
|
|
const body = await response.body();
|
|
expect(body.subarray(1, 4).toString("ascii")).toBe("PNG");
|
|
});
|
|
|
|
test("answers conditional requests with 304", async ({ request }) => {
|
|
const first = await request.get(avatarPath({ img_format: "png" }));
|
|
expect(first.status()).toBe(200);
|
|
const etag = first.headers()["etag"];
|
|
expect(etag).toBeTruthy();
|
|
|
|
const second = await request.get(avatarPath({ img_format: "png" }), {
|
|
headers: { "if-none-match": etag as string },
|
|
});
|
|
expect(second.status()).toBe(304);
|
|
expect(second.headers()["etag"]).toBe(etag);
|
|
});
|
|
|
|
test("rejects a malformed figure with 400", async ({ request }) => {
|
|
const response = await request.get(avatarPath({ figure: "../bad" }));
|
|
expect(response.status()).toBe(400);
|
|
});
|
|
|
|
// NB: the sibling `/imaging` CMS route is intentionally fronted to the imager
|
|
// container by the edge proxy in production, so it is not asserted here — it
|
|
// would only pass when the CMS is reached directly. Its behaviour is covered by
|
|
// src/lib/imaging-route.test.ts.
|
|
|
|
test("the badge endpoint serves an image and revalidates", async ({
|
|
request,
|
|
}) => {
|
|
const first = await request.get("/api/imaging/badge?code=ADM");
|
|
expect(first.status()).toBe(200);
|
|
expect(first.headers()["content-type"]).toContain("image/");
|
|
const etag = first.headers()["etag"];
|
|
expect(etag).toBeTruthy();
|
|
|
|
const second = await request.get("/api/imaging/badge?code=ADM", {
|
|
headers: { "if-none-match": etag as string },
|
|
});
|
|
expect(second.status()).toBe(304);
|
|
});
|