Files
optima/api/tests/unit/errors.test.ts
T

267 lines
9.6 KiB
TypeScript

import { describe, test, expect } from "bun:test";
import GenericError from "../../src/Errors/GenericError";
import AuthenticationError from "../../src/Errors/AuthenticationError";
import AuthorizationError from "../../src/Errors/AuthorizationError";
import BodyError from "../../src/Errors/BodyError";
import InsufficientPermission from "../../src/Errors/InsufficientPermission";
import SessionError from "../../src/Errors/SessionError";
import SessionTokenError from "../../src/Errors/SessionTokenError";
import UserError from "../../src/Errors/UserError";
import RoleError from "../../src/Errors/RoleError";
import MissingBodyValue from "../../src/Errors/MissingBodyValue";
import ExpiredAccessTokenError from "../../src/Errors/ExpiredAccessTokenError";
import ExpiredRefreshTokenError from "../../src/Errors/ExpiredRefreshTokenError";
import PermissionsVerificationError from "../../src/Errors/PermissionsVerificationError";
// ---------------------------------------------------------------------------
// GenericError
// ---------------------------------------------------------------------------
describe("GenericError", () => {
test("sets name, message, status, and cause", () => {
const err = new GenericError({
name: "TestError",
message: "Something went wrong",
cause: "bad input",
status: 422,
});
expect(err).toBeInstanceOf(Error);
expect(err.name).toBe("TestError");
expect(err.message).toBe("Something went wrong");
expect(err.cause).toBe("bad input");
expect(err.status).toBe(422);
});
test("defaults status to 400", () => {
const err = new GenericError({ name: "X", message: "Y" });
expect(err.status).toBe(400);
});
test("cause is optional", () => {
const err = new GenericError({ name: "X", message: "Y" });
expect(err.cause).toBeUndefined();
});
});
// ---------------------------------------------------------------------------
// AuthenticationError
// ---------------------------------------------------------------------------
describe("AuthenticationError", () => {
test("sets correct name and message", () => {
const err = new AuthenticationError("Invalid credentials");
expect(err).toBeInstanceOf(Error);
expect(err.name).toBe("AuthenticationError");
expect(err.message).toBe("Invalid credentials");
});
test("accepts optional cause", () => {
const err = new AuthenticationError("Fail", "token expired");
expect(err.cause).toBe("token expired");
});
test("cause defaults to undefined", () => {
const err = new AuthenticationError("Fail");
expect(err.cause).toBeUndefined();
});
});
// ---------------------------------------------------------------------------
// AuthorizationError
// ---------------------------------------------------------------------------
describe("AuthorizationError", () => {
test("sets correct name and default status", () => {
const err = new AuthorizationError("Not authorized");
expect(err).toBeInstanceOf(Error);
expect(err.name).toBe("AuthorizationError");
expect(err.message).toBe("Not authorized");
expect(err.status).toBe(401);
});
test("allows custom status", () => {
const err = new AuthorizationError("Forbidden", "nope", 403);
expect(err.status).toBe(403);
expect(err.cause).toBe("nope");
});
});
// ---------------------------------------------------------------------------
// BodyError
// ---------------------------------------------------------------------------
describe("BodyError", () => {
test("sets name and message", () => {
const err = new BodyError("Body is invalid");
expect(err.name).toBe("BodyError");
expect(err.message).toBe("Body is invalid");
});
test("accepts optional cause", () => {
const err = new BodyError("Bad", "missing field");
expect(err.cause).toBe("missing field");
});
});
// ---------------------------------------------------------------------------
// InsufficientPermission
// ---------------------------------------------------------------------------
describe("InsufficientPermission", () => {
test("always has status 403", () => {
const err = new InsufficientPermission("Nope");
expect(err.name).toBe("InsufficientPermission");
expect(err.status).toBe(403);
expect(err.message).toBe("Nope");
});
test("accepts optional cause", () => {
const err = new InsufficientPermission("Nope", "missing role");
expect(err.cause).toBe("missing role");
});
});
// ---------------------------------------------------------------------------
// SessionError
// ---------------------------------------------------------------------------
describe("SessionError", () => {
test("sets name and message", () => {
const err = new SessionError("Invalid session");
expect(err.name).toBe("SessionError");
expect(err.message).toBe("Invalid session");
});
});
// ---------------------------------------------------------------------------
// SessionTokenError
// ---------------------------------------------------------------------------
describe("SessionTokenError", () => {
test("sets name and message", () => {
const err = new SessionTokenError("Token invalid");
expect(err.name).toBe("SessionTokenError");
expect(err.message).toBe("Token invalid");
});
test("accepts cause", () => {
const err = new SessionTokenError("Bad", "expired");
expect(err.cause).toBe("expired");
});
});
// ---------------------------------------------------------------------------
// UserError
// ---------------------------------------------------------------------------
describe("UserError", () => {
test("sets name and message", () => {
const err = new UserError("User not found");
expect(err.name).toBe("UserError");
expect(err.message).toBe("User not found");
});
});
// ---------------------------------------------------------------------------
// RoleError
// ---------------------------------------------------------------------------
describe("RoleError", () => {
test("sets name and message", () => {
const err = new RoleError("Role conflict");
expect(err.name).toBe("RoleError");
expect(err.message).toBe("Role conflict");
});
test("accepts cause", () => {
const err = new RoleError("Conflict", "moniker taken");
expect(err.cause).toBe("moniker taken");
});
});
// ---------------------------------------------------------------------------
// MissingBodyValue
// ---------------------------------------------------------------------------
describe("MissingBodyValue", () => {
test("formats message with value name", () => {
const err = new MissingBodyValue("email");
expect(err.name).toBe("MissingBodyValue");
expect(err.message).toBe("Value 'email' is missing from the body.");
expect(err.cause).toBe(
"A value that was required by the body of this request is missing.",
);
});
test("works with different value names", () => {
const err = new MissingBodyValue("password");
expect(err.message).toContain("password");
});
});
// ---------------------------------------------------------------------------
// ExpiredAccessTokenError
// ---------------------------------------------------------------------------
describe("ExpiredAccessTokenError", () => {
test("sets fixed name and message", () => {
const err = new ExpiredAccessTokenError();
expect(err.name).toBe("ExpiredAccessTokenError");
expect(err.message).toBe("The provided access token has expired.");
});
test("accepts optional cause", () => {
const err = new ExpiredAccessTokenError("jwt expired");
expect(err.cause).toBe("jwt expired");
});
});
// ---------------------------------------------------------------------------
// ExpiredRefreshTokenError
// ---------------------------------------------------------------------------
describe("ExpiredRefreshTokenError", () => {
test("sets fixed name and message", () => {
const err = new ExpiredRefreshTokenError();
expect(err.name).toBe("ExpiredRefreshTokenError");
expect(err.message).toBe("The provided refresh token has expired.");
});
test("accepts optional cause", () => {
const err = new ExpiredRefreshTokenError("jwt expired");
expect(err.cause).toBe("jwt expired");
});
});
// ---------------------------------------------------------------------------
// PermissionsVerificationError
// ---------------------------------------------------------------------------
describe("PermissionsVerificationError", () => {
test("sets name and message", () => {
const err = new PermissionsVerificationError("Cannot verify");
expect(err.name).toBe("PermissionsVerificationError");
expect(err.message).toBe("Cannot verify");
});
test("accepts cause", () => {
const err = new PermissionsVerificationError("Fail", "key mismatch");
expect(err.cause).toBe("key mismatch");
});
});
// ---------------------------------------------------------------------------
// Cross-cutting: all errors are instanceof Error
// ---------------------------------------------------------------------------
describe("All errors extend Error", () => {
const errors = [
new GenericError({ name: "G", message: "g" }),
new AuthenticationError("a"),
new AuthorizationError("a"),
new BodyError("b"),
new InsufficientPermission("i"),
new SessionError("s"),
new SessionTokenError("st"),
new UserError("u"),
new RoleError("r"),
new MissingBodyValue("v"),
new ExpiredAccessTokenError(),
new ExpiredRefreshTokenError(),
new PermissionsVerificationError("p"),
];
test.each(errors.map((e) => [e.constructor.name, e]))(
"%s is instanceof Error",
(_name, err) => {
expect(err).toBeInstanceOf(Error);
},
);
});