generated from nhcarrigan/template
d48b53eecd
- Add full test suite for frontend.ts (POST /log and POST /error) - Add error-path tests to all route handlers to cover catch blocks triggered by Prisma rejections - Add non-Error throw tests to cover the `new Error(String(error))` ternary false branch in middleware, services, and route catch handlers - Suppress unreachable outer catch in about.ts with v8 ignore (fetchReleases swallows all errors internally, making the outer catch genuinely dead code)
137 lines
5.2 KiB
TypeScript
137 lines
5.2 KiB
TypeScript
/* 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<typeof vi.fn>; error: ReturnType<typeof vi.fn> };
|
|
|
|
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");
|
|
});
|
|
});
|
|
});
|