mirror of
https://github.com/juherr/kill-the-news.git
synced 2026-06-21 06:13:48 +00:00
feat: reader-rendering correctness + privacy hardening (P1·S batch)
Close the five open P1·S items from TODO.md: - X-Robots-Tag: noindex on rss/atom/entries/files + a /robots.txt - absolutize relative content URLs against the sender's site - promote lazy-loaded images (data-src → src, strip loading="lazy") - strip XML-illegal control chars from generated feeds (keep emoji) - plain-text feed <title> (strip HTML, decode entities) Sender-base derivation lives on the EmailAddress value object (siteBaseUrl) instead of a misplaced favicon helper. Bump to 0.2.1 and document the changes in README + CLAUDE.md. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import { describe, it, expect, beforeEach } from "vitest";
|
||||
import { Hono } from "hono";
|
||||
import { handle } from "./rss";
|
||||
import { createMockEnv } from "../test/setup";
|
||||
import { Env } from "../types";
|
||||
|
||||
describe("RSS Feed Route", () => {
|
||||
let testApp: Hono;
|
||||
let mockEnv: Env;
|
||||
|
||||
beforeEach(() => {
|
||||
mockEnv = createMockEnv() as unknown as Env;
|
||||
testApp = new Hono();
|
||||
testApp.get("/:feedId", handle);
|
||||
});
|
||||
|
||||
describe("unknown feed", () => {
|
||||
it("returns 404 when no metadata exists in KV", async () => {
|
||||
const res = await testApp.request("/nonexistent-feed", {}, mockEnv);
|
||||
expect(res.status).toBe(404);
|
||||
expect(await res.text()).toBe("Feed not found");
|
||||
});
|
||||
});
|
||||
|
||||
describe("valid feed with no emails", () => {
|
||||
beforeEach(async () => {
|
||||
await mockEnv.EMAIL_STORAGE.put(
|
||||
"feed:empty-feed:metadata",
|
||||
JSON.stringify({ emails: [] }),
|
||||
);
|
||||
});
|
||||
|
||||
it("returns 200 with application/rss+xml content type", async () => {
|
||||
const res = await testApp.request("/empty-feed", {}, mockEnv);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get("Content-Type")).toContain("application/rss+xml");
|
||||
});
|
||||
|
||||
it("includes Cache-Control header", async () => {
|
||||
const res = await testApp.request("/empty-feed", {}, mockEnv);
|
||||
expect(res.headers.get("Cache-Control")).toBe("max-age=1800");
|
||||
});
|
||||
|
||||
it("sets X-Robots-Tag: noindex", async () => {
|
||||
const res = await testApp.request("/empty-feed", {}, mockEnv);
|
||||
expect(res.headers.get("X-Robots-Tag")).toBe("noindex");
|
||||
});
|
||||
|
||||
it("Link header advertises hub and self for WebSub discovery", async () => {
|
||||
const res = await testApp.request("/empty-feed", {}, mockEnv);
|
||||
const link = res.headers.get("Link") ?? "";
|
||||
expect(link).toContain(`rel="hub"`);
|
||||
expect(link).toContain(`rel="self"`);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user