58 lines · 2.5 KB
import { describe, expect, spyOn, test } from "bun:test";
import { notFound, redirect } from "@tanstack/react-router";
import { clientSafeError } from "./client-safe-error";
import { HttpError } from "./errors";

describe("clientSafeError (DEPLOY-P8)", () => {
  test("unknown errors become a generic 500 and are logged", () => {
    const log = spyOn(console, "error").mockImplementation(() => undefined);
    const masked = clientSafeError(new Error("D1_ERROR: no such column secret_hash"));
    expect(masked).toBeInstanceOf(HttpError);
    expect(masked instanceof HttpError && masked.status).toBe(500);
    expect(masked instanceof HttpError && masked.message).toBe("Internal error");
    expect(log).toHaveBeenCalled();
    log.mockRestore();
  });

  test("5xx HttpErrors lose their internal message", () => {
    const log = spyOn(console, "error").mockImplementation(() => undefined);
    const result = clientSafeError(new HttpError(500, "D1 table x missing"));
    expect(result instanceof HttpError && [result.status, result.message]).toEqual([500, "Internal error"]);
    log.mockRestore();
  });

  test("deliberate errors and router control flow pass through", () => {
    const http = new HttpError(403, "Nope");
    expect(clientSafeError(http)).toBe(http);
    const nf = notFound();
    expect(clientSafeError(nf)).toBe(nf);
    const to = redirect({ to: "/" });
    expect(clientSafeError(to)).toBe(to);
  });

  test("validation failures become a 400 with the first issue's message", () => {
    const error = Object.assign(new Error("[...]"), { name: "ZodError", issues: [{ message: "Too long" }] });
    const result = clientSafeError(error);
    expect(result instanceof HttpError && [result.status, result.message]).toEqual([400, "Too long"]);
  });

  test("validator failures serialized into a plain Error message also become a 400", () => {
    const issues = [{ code: "invalid_value", path: ["acceptTerms"], message: "Accept the Terms to continue" }];