mirror of
https://github.com/juherr/kill-the-news.git
synced 2026-06-21 14:23:48 +00:00
2b3f00f7e3
Centralise the KV key schema and all get/put access behind a FeedRepository class under src/domain/. Every feed/email/list/icon/websub/counter key was previously inlined across ~12 modules with two divergent storeEmail and addFeedToList implementations; the dead src/utils/storage.ts write path is removed and the email key convention unified on feed:<id>:<ts>. Behaviour-preserving: existing tests pass unchanged in logic, plus a new feed-repository.test.ts covering CRUD, key builders, list ops and counters. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
93 lines
3.2 KiB
TypeScript
93 lines
3.2 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import worker from "../index";
|
|
import { createMockEnv } from "../test/setup";
|
|
import type { Env } from "../types";
|
|
|
|
const iconKey = (domain: string) => `icon:${domain}`;
|
|
|
|
function req(path: string): Request {
|
|
return new Request(`https://test.getmynews.app${path}`);
|
|
}
|
|
|
|
const PNG = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 9, 8, 7]);
|
|
|
|
function toBase64(bytes: Uint8Array): string {
|
|
return btoa(String.fromCharCode(...bytes));
|
|
}
|
|
|
|
describe("project favicon", () => {
|
|
it("serves an SVG favicon at /favicon.svg", async () => {
|
|
const env = createMockEnv() as unknown as Env;
|
|
const res = await worker.fetch(req("/favicon.svg"), env);
|
|
expect(res.status).toBe(200);
|
|
expect(res.headers.get("Content-Type")).toMatch(/^image\/svg\+xml/);
|
|
expect(res.headers.get("Cache-Control")).toContain("max-age");
|
|
const body = await res.text();
|
|
expect(body).toContain("<svg");
|
|
});
|
|
|
|
it("serves the same icon at /favicon.ico", async () => {
|
|
const env = createMockEnv() as unknown as Env;
|
|
const res = await worker.fetch(req("/favicon.ico"), env);
|
|
expect(res.status).toBe(200);
|
|
expect(res.headers.get("Content-Type")).toMatch(/^image\/svg\+xml/);
|
|
const body = await res.text();
|
|
expect(body).toContain("<svg");
|
|
});
|
|
});
|
|
|
|
describe("per-feed favicon", () => {
|
|
it("serves the cached domain icon when available", async () => {
|
|
const env = createMockEnv() as unknown as Env;
|
|
await env.EMAIL_STORAGE.put(
|
|
"feed:abc:metadata",
|
|
JSON.stringify({ emails: [], iconDomain: "github.com" }),
|
|
);
|
|
await env.EMAIL_STORAGE.put(
|
|
iconKey("github.com"),
|
|
JSON.stringify({ data: toBase64(PNG), contentType: "image/png" }),
|
|
);
|
|
|
|
const res = await worker.fetch(req("/favicon/abc"), env);
|
|
expect(res.status).toBe(200);
|
|
expect(res.headers.get("Content-Type")).toBe("image/png");
|
|
expect(new Uint8Array(await res.arrayBuffer())).toEqual(PNG);
|
|
});
|
|
|
|
it("falls back to the project SVG when the feed has no icon domain", async () => {
|
|
const env = createMockEnv() as unknown as Env;
|
|
await env.EMAIL_STORAGE.put(
|
|
"feed:abc:metadata",
|
|
JSON.stringify({ emails: [] }),
|
|
);
|
|
|
|
const res = await worker.fetch(req("/favicon/abc"), env);
|
|
expect(res.status).toBe(200);
|
|
expect(res.headers.get("Content-Type")).toMatch(/^image\/svg\+xml/);
|
|
expect(await res.text()).toContain("<svg");
|
|
});
|
|
|
|
it("falls back to the project SVG for a negative cache entry", async () => {
|
|
const env = createMockEnv() as unknown as Env;
|
|
await env.EMAIL_STORAGE.put(
|
|
"feed:abc:metadata",
|
|
JSON.stringify({ emails: [], iconDomain: "nope.test" }),
|
|
);
|
|
await env.EMAIL_STORAGE.put(
|
|
iconKey("nope.test"),
|
|
JSON.stringify({ data: null, contentType: "" }),
|
|
);
|
|
|
|
const res = await worker.fetch(req("/favicon/abc"), env);
|
|
expect(res.status).toBe(200);
|
|
expect(res.headers.get("Content-Type")).toMatch(/^image\/svg\+xml/);
|
|
});
|
|
|
|
it("falls back to the project SVG for an unknown feed", async () => {
|
|
const env = createMockEnv() as unknown as Env;
|
|
const res = await worker.fetch(req("/favicon/missing"), env);
|
|
expect(res.status).toBe(200);
|
|
expect(res.headers.get("Content-Type")).toMatch(/^image\/svg\+xml/);
|
|
});
|
|
});
|