feat: complete Phase 2 tech debt remediation

- Extract shared RSS/Atom fetch logic into feed-fetcher utility (P1-3)
- Split email-processor into validateEmail/storeEmail functions (P1-6)
- Add stateless HMAC-SHA256 CSRF protection to admin forms (P2-8)
- Fix Hono<{ Bindings: Env }> type safety across all routes (P3-13)
- Add entries.test.ts and files.test.ts with full coverage (P1-7)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Julien Herr
2026-05-22 09:46:55 +02:00
parent f2981eec31
commit 7d375693b9
15 changed files with 485 additions and 152 deletions
+16 -3
View File
@@ -3,17 +3,20 @@ import { Hono } from "hono";
import app from "./admin";
import { createMockEnv } from "../test/setup";
import { Env } from "../types";
import { generateCsrfToken } from "../utils/csrf";
describe("Admin Routes", () => {
let testApp: Hono;
let mockEnv: Env;
let csrfToken: string;
let request: (path: string, init?: RequestInit) => Promise<Response>;
let loginAndGetCookie: () => Promise<string>;
beforeEach(() => {
beforeEach(async () => {
mockEnv = createMockEnv() as unknown as Env;
testApp = new Hono();
testApp.route("/admin", app);
csrfToken = await generateCsrfToken("test-password");
request = (path, init = {}) =>
Promise.resolve(testApp.request(path, init, mockEnv));
loginAndGetCookie = async () => {
@@ -94,6 +97,7 @@ describe("Admin Routes", () => {
const res = await request("/admin", {
headers: {
Cookie: authCookie,
"X-CSRF-Token": csrfToken,
},
});
expect(res.status).toBe(200);
@@ -139,6 +143,7 @@ describe("Admin Routes", () => {
method: "POST",
headers: {
Cookie: authCookie,
"X-CSRF-Token": csrfToken,
},
body: formData,
});
@@ -175,6 +180,7 @@ describe("Admin Routes", () => {
method: "POST",
headers: {
Cookie: authCookie,
"X-CSRF-Token": csrfToken,
},
body: formData,
});
@@ -195,6 +201,7 @@ describe("Admin Routes", () => {
headers: {
Cookie: authCookie,
"Content-Type": "application/json",
"X-CSRF-Token": csrfToken,
},
body: JSON.stringify({ title: "", description: "desc" }),
});
@@ -241,6 +248,7 @@ describe("Admin Routes", () => {
method: "POST",
headers: {
Cookie: authCookie,
"X-CSRF-Token": csrfToken,
},
body: formData,
});
@@ -259,6 +267,7 @@ describe("Admin Routes", () => {
method: "POST",
headers: {
Cookie: authCookie,
"X-CSRF-Token": csrfToken,
},
});
@@ -291,6 +300,7 @@ describe("Admin Routes", () => {
method: "POST",
headers: {
Cookie: authCookie,
"X-CSRF-Token": csrfToken,
},
body: formData,
});
@@ -310,6 +320,7 @@ describe("Admin Routes", () => {
headers: {
Cookie: authCookie,
Accept: "application/json",
"X-CSRF-Token": csrfToken,
},
},
);
@@ -329,7 +340,7 @@ describe("Admin Routes", () => {
formData.append("description", "Test");
const createRes = await request("/admin/feeds/create", {
method: "POST",
headers: { Cookie: authCookie },
headers: { Cookie: authCookie, "X-CSRF-Token": csrfToken },
body: formData,
});
expect(createRes.status).toBe(302);
@@ -350,7 +361,7 @@ describe("Admin Routes", () => {
const bulkDeleteRes = await request("/admin/feeds/bulk-delete", {
method: "POST",
headers: { Cookie: authCookie },
headers: { Cookie: authCookie, "X-CSRF-Token": csrfToken },
body: bulkForm,
});
@@ -479,6 +490,7 @@ describe("Admin Routes", () => {
method: "POST",
headers: {
Cookie: authCookie,
"X-CSRF-Token": csrfToken,
},
body: formData,
});
@@ -528,6 +540,7 @@ describe("Admin Routes", () => {
headers: {
Cookie: authCookie,
Accept: "application/json",
"X-CSRF-Token": csrfToken,
},
},
);