/* eslint-disable max-lines-per-function -- Test suites naturally have many cases */ import { beforeEach, describe, expect, it, vi } from "vitest"; import { Hono } from "hono"; vi.mock("../../src/services/logger.js", () => ({ logger: { log: vi.fn().mockResolvedValue(undefined), error: vi.fn().mockResolvedValue(undefined), }, })); describe("frontend route", () => { let loggerMock: { log: ReturnType; error: ReturnType }; beforeEach(async () => { vi.clearAllMocks(); const { logger } = await import("../../src/services/logger.js"); loggerMock = logger as typeof loggerMock; }); const makeApp = async () => { const { frontendRouter } = await import("../../src/routes/frontend.js"); const app = new Hono(); app.route("/frontend", frontendRouter); return app; }; const postLog = async (body: unknown, contentType = "application/json") => { const app = await makeApp(); return app.fetch(new Request("http://localhost/frontend/log", { method: "POST", headers: { "Content-Type": contentType }, body: typeof body === "string" ? body : JSON.stringify(body), })); }; const postError = async (body: unknown, contentType = "application/json") => { const app = await makeApp(); return app.fetch(new Request("http://localhost/frontend/error", { method: "POST", headers: { "Content-Type": contentType }, body: typeof body === "string" ? body : JSON.stringify(body), })); }; describe("POST /log", () => { it("returns 200 when level is debug and message is present", async () => { const res = await postLog({ level: "debug", message: "test debug" }); expect(res.status).toBe(200); const body = await res.json() as { ok: boolean }; expect(body.ok).toBe(true); }); it("returns 200 when level is info and message is present", async () => { const res = await postLog({ level: "info", message: "test info" }); expect(res.status).toBe(200); const body = await res.json() as { ok: boolean }; expect(body.ok).toBe(true); }); it("returns 200 when level is warn and message is present", async () => { const res = await postLog({ level: "warn", message: "test warn" }); expect(res.status).toBe(200); const body = await res.json() as { ok: boolean }; expect(body.ok).toBe(true); }); it("returns 400 when level is invalid", async () => { const res = await postLog({ level: "error", message: "test" }); expect(res.status).toBe(400); const body = await res.json() as { error: string }; expect(body.error).toBe("level and message are required"); }); it("returns 400 when level is missing", async () => { const res = await postLog({ message: "test" }); expect(res.status).toBe(400); }); it("returns 400 when message is missing", async () => { const res = await postLog({ level: "info" }); expect(res.status).toBe(400); }); it("returns 500 when request body is invalid JSON", async () => { const res = await postLog("not valid json at all", "application/json"); expect(res.status).toBe(500); const body = await res.json() as { error: string }; expect(body.error).toBe("Internal server error"); }); it("returns 500 and covers non-Error branch when logger throws a raw value", async () => { loggerMock.log.mockImplementationOnce(() => { throw "raw string error"; }); const res = await postLog({ level: "info", message: "test" }); expect(res.status).toBe(500); const body = await res.json() as { error: string }; expect(body.error).toBe("Internal server error"); }); }); describe("POST /error", () => { it("returns 200 with valid context and message", async () => { const res = await postError({ context: "SomeComponent", message: "Something went wrong" }); expect(res.status).toBe(200); const body = await res.json() as { ok: boolean }; expect(body.ok).toBe(true); }); it("returns 400 when context field is missing", async () => { const res = await postError({ message: "Something went wrong" }); expect(res.status).toBe(400); const body = await res.json() as { error: string }; expect(body.error).toBe("context and message are required"); }); it("returns 400 when message field is missing", async () => { const res = await postError({ context: "SomeComponent" }); expect(res.status).toBe(400); }); it("returns 500 when request body is invalid JSON", async () => { const res = await postError("not valid json at all", "application/json"); expect(res.status).toBe(500); const body = await res.json() as { error: string }; expect(body.error).toBe("Internal server error"); }); it("returns 500 and covers non-Error branch when logger throws a raw value", async () => { loggerMock.error.mockImplementationOnce(() => { throw "raw string error"; }); const res = await postError({ context: "SomeComponent", message: "Something went wrong" }); expect(res.status).toBe(500); const body = await res.json() as { error: string }; expect(body.error).toBe("Internal server error"); }); }); });