fix: resolve type errors across test suite
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { Hono } from "hono";
|
||||
import { apiResponse } from "../../src/modules/api-utils/apiResponse";
|
||||
import { ZodError } from "zod";
|
||||
import GenericError from "../../src/Errors/GenericError";
|
||||
|
||||
/**
|
||||
* Tests the error-handling middleware registered in server.ts.
|
||||
* We replicate the onError logic on a fresh Hono instance to test
|
||||
* in isolation without importing all routes.
|
||||
*/
|
||||
function createAppWithErrorHandling() {
|
||||
const app = new Hono();
|
||||
|
||||
app.onError((err, ctx) => {
|
||||
const errClassName = err.constructor.name;
|
||||
|
||||
if (
|
||||
errClassName.toLowerCase().includes("prisma") ||
|
||||
err.message.toLowerCase().includes("prisma") ||
|
||||
err.name.toLowerCase().includes("prisma")
|
||||
) {
|
||||
return ctx.json(apiResponse.internalError(), 500);
|
||||
}
|
||||
|
||||
if (err instanceof ZodError || err.name === "ZodError") {
|
||||
const zodResp = apiResponse.zodError(err as ZodError);
|
||||
return ctx.json(zodResp, zodResp.status as any);
|
||||
}
|
||||
|
||||
const response = apiResponse.error(err);
|
||||
return ctx.json(response, response.status);
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
describe("Server error handling", () => {
|
||||
test("Prisma errors return 500 InternalServerError", async () => {
|
||||
const app = createAppWithErrorHandling();
|
||||
app.get("/test", () => {
|
||||
const err = new Error("prisma query failed");
|
||||
throw err;
|
||||
});
|
||||
const res = await app.request("/test");
|
||||
expect(res.status).toBe(500);
|
||||
const body: any = await res.json();
|
||||
expect(body.error).toBe("InternalServerError");
|
||||
expect(body.successful).toBe(false);
|
||||
});
|
||||
|
||||
test("Prisma errors detected by class name", async () => {
|
||||
const app = createAppWithErrorHandling();
|
||||
app.get("/test", () => {
|
||||
class PrismaClientError extends Error {
|
||||
constructor() {
|
||||
super("something");
|
||||
this.name = "PrismaClientError";
|
||||
}
|
||||
}
|
||||
throw new PrismaClientError();
|
||||
});
|
||||
const res = await app.request("/test");
|
||||
expect(res.status).toBe(500);
|
||||
});
|
||||
|
||||
test("ZodError returns 400 with error array", async () => {
|
||||
const app = createAppWithErrorHandling();
|
||||
app.get("/test", (c) => {
|
||||
// In Zod v4, we need to use z.parse to generate a proper ZodError
|
||||
const { z } = require("zod");
|
||||
const schema = z.object({ name: z.string() });
|
||||
schema.parse({}); // throws ZodError
|
||||
return c.text("unreachable");
|
||||
});
|
||||
const res = await app.request("/test");
|
||||
expect(res.status).toBe(400);
|
||||
const body: any = await res.json();
|
||||
expect(body.message).toBe("TypeError");
|
||||
expect(body.successful).toBe(false);
|
||||
expect(Array.isArray(body.error)).toBe(true);
|
||||
});
|
||||
|
||||
test("GenericError returns custom status", async () => {
|
||||
const app = createAppWithErrorHandling();
|
||||
app.get("/test", () => {
|
||||
throw new GenericError({
|
||||
name: "NotFound",
|
||||
message: "Resource not found",
|
||||
status: 404,
|
||||
});
|
||||
});
|
||||
const res = await app.request("/test");
|
||||
expect(res.status).toBe(404);
|
||||
const body: any = await res.json();
|
||||
expect(body.error).toBe("NotFound");
|
||||
expect(body.message).toBe("Resource not found");
|
||||
expect(body.successful).toBe(false);
|
||||
});
|
||||
|
||||
test("plain Error defaults to 400", async () => {
|
||||
const app = createAppWithErrorHandling();
|
||||
app.get("/test", () => {
|
||||
throw new Error("Unexpected error");
|
||||
});
|
||||
const res = await app.request("/test");
|
||||
expect(res.status).toBe(400);
|
||||
const body: any = await res.json();
|
||||
expect(body.successful).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,133 @@
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import app from "../../src/api/server";
|
||||
|
||||
describe("API Server — Integration", () => {
|
||||
// -------------------------------------------------------------------
|
||||
// Teapot route (no auth required)
|
||||
// -------------------------------------------------------------------
|
||||
describe("GET /v1/teapot", () => {
|
||||
test("returns 418 I'm not a teapot", async () => {
|
||||
const res = await app.request("/v1/teapot");
|
||||
expect(res.status).toBe(418);
|
||||
const body: any = await res.json();
|
||||
expect(body.status).toBe(418);
|
||||
expect(body.message).toBe("I'm not a teapot");
|
||||
expect(body.successful).toBe(true);
|
||||
});
|
||||
|
||||
test("returns JSON content type", async () => {
|
||||
const res = await app.request("/v1/teapot");
|
||||
expect(res.headers.get("content-type")).toContain("application/json");
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Not Found
|
||||
// -------------------------------------------------------------------
|
||||
describe("Not Found handling", () => {
|
||||
test("returns 404 for unknown routes", async () => {
|
||||
const res = await app.request("/v1/nonexistent");
|
||||
expect(res.status).toBe(404);
|
||||
const body: any = await res.json();
|
||||
expect(body.successful).toBe(false);
|
||||
expect(body.error).toBe("NotFound");
|
||||
});
|
||||
|
||||
test("includes method and path in message", async () => {
|
||||
const res = await app.request("/v1/some/random/path", {
|
||||
method: "POST",
|
||||
});
|
||||
const body: any = await res.json();
|
||||
expect(body.message).toContain("POST");
|
||||
expect(body.message).toContain("/v1/some/random/path");
|
||||
});
|
||||
|
||||
test("returns 404 for root path", async () => {
|
||||
const res = await app.request("/");
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// CORS
|
||||
// -------------------------------------------------------------------
|
||||
describe("CORS", () => {
|
||||
test("includes CORS headers", async () => {
|
||||
const res = await app.request("/v1/teapot", {
|
||||
headers: { Origin: "http://localhost:3000" },
|
||||
});
|
||||
// Hono's cors middleware should add access-control headers
|
||||
const acaoHeader = res.headers.get("access-control-allow-origin");
|
||||
expect(acaoHeader).toBeDefined();
|
||||
});
|
||||
|
||||
test("handles OPTIONS preflight", async () => {
|
||||
const res = await app.request("/v1/teapot", {
|
||||
method: "OPTIONS",
|
||||
headers: {
|
||||
Origin: "http://localhost:3000",
|
||||
"Access-Control-Request-Method": "GET",
|
||||
},
|
||||
});
|
||||
// Should not be 404
|
||||
expect(res.status).toBeLessThan(400);
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Auth-protected routes (should reject without auth)
|
||||
// -------------------------------------------------------------------
|
||||
describe("Protected routes require authorization", () => {
|
||||
const protectedRoutes = [
|
||||
{ method: "GET", path: "/v1/company/companies" },
|
||||
{ method: "GET", path: "/v1/company/count" },
|
||||
{ method: "GET", path: "/v1/credential/credentials/some-id" },
|
||||
{ method: "POST", path: "/v1/credential/credentials" },
|
||||
{ method: "GET", path: "/v1/role" },
|
||||
{ method: "POST", path: "/v1/role" },
|
||||
{ method: "GET", path: "/v1/user/users" },
|
||||
];
|
||||
|
||||
test.each(protectedRoutes)(
|
||||
"$method $path returns 401 without auth header",
|
||||
async ({ method, path }) => {
|
||||
const res = await app.request(path, { method });
|
||||
expect(res.status).toBe(401);
|
||||
const body: any = await res.json();
|
||||
expect(body.successful).toBe(false);
|
||||
},
|
||||
);
|
||||
|
||||
test.each(protectedRoutes)(
|
||||
"$method $path returns error with invalid auth header",
|
||||
async ({ method, path }) => {
|
||||
const res = await app.request(path, {
|
||||
method,
|
||||
headers: { Authorization: "invalid-format" },
|
||||
});
|
||||
const body: any = await res.json();
|
||||
expect(body.successful).toBe(false);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Error handling
|
||||
// -------------------------------------------------------------------
|
||||
describe("Error handling", () => {
|
||||
test("ZodError returns 400 with error details", async () => {
|
||||
// POST to credentials without proper body should trigger a Zod error
|
||||
const res = await app.request("/v1/credential/credentials", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer invalid.token.here",
|
||||
},
|
||||
});
|
||||
// Will get auth error first, which is expected
|
||||
const body: any = await res.json();
|
||||
expect(body.successful).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user