Update paths in sync.sh, master/main.go, and CLAUDE.md to reflect the directory rename. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
180 lines
6.0 KiB
TypeScript
180 lines
6.0 KiB
TypeScript
// Tests for types.ts
|
|
// Pure unit tests
|
|
|
|
import assert from "node:assert/strict";
|
|
import { describe, it } from "node:test";
|
|
import type { Request as ExpressRequest } from "express";
|
|
import { Session } from "./auth/types";
|
|
import { contentTypes } from "./content-types";
|
|
import { httpCodes } from "./http-codes";
|
|
import {
|
|
AuthenticationRequired,
|
|
AuthorizationDenied,
|
|
type Call,
|
|
isRedirect,
|
|
massageMethod,
|
|
methodParser,
|
|
type Permission,
|
|
type RedirectResult,
|
|
type Result,
|
|
requireAuth,
|
|
requirePermission,
|
|
} from "./types";
|
|
import { AuthenticatedUser, anonymousUser } from "./user";
|
|
|
|
// Helper to create a minimal mock Call
|
|
function createMockCall(overrides: Partial<Call> = {}): Call {
|
|
const defaultSession = new Session(null, anonymousUser);
|
|
return {
|
|
pattern: "/test",
|
|
path: "/test",
|
|
method: "GET",
|
|
parameters: {},
|
|
request: {} as ExpressRequest,
|
|
user: anonymousUser,
|
|
session: defaultSession,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("types", () => {
|
|
describe("methodParser", () => {
|
|
it("accepts valid HTTP methods", () => {
|
|
assert.equal(methodParser.parse("GET"), "GET");
|
|
assert.equal(methodParser.parse("POST"), "POST");
|
|
assert.equal(methodParser.parse("PUT"), "PUT");
|
|
assert.equal(methodParser.parse("PATCH"), "PATCH");
|
|
assert.equal(methodParser.parse("DELETE"), "DELETE");
|
|
});
|
|
|
|
it("rejects invalid methods", () => {
|
|
assert.throws(() => methodParser.parse("get"));
|
|
assert.throws(() => methodParser.parse("OPTIONS"));
|
|
assert.throws(() => methodParser.parse("HEAD"));
|
|
assert.throws(() => methodParser.parse(""));
|
|
});
|
|
});
|
|
|
|
describe("massageMethod", () => {
|
|
it("converts lowercase to uppercase", () => {
|
|
assert.equal(massageMethod("get"), "GET");
|
|
assert.equal(massageMethod("post"), "POST");
|
|
assert.equal(massageMethod("put"), "PUT");
|
|
assert.equal(massageMethod("patch"), "PATCH");
|
|
assert.equal(massageMethod("delete"), "DELETE");
|
|
});
|
|
|
|
it("handles mixed case", () => {
|
|
assert.equal(massageMethod("Get"), "GET");
|
|
assert.equal(massageMethod("pOsT"), "POST");
|
|
});
|
|
|
|
it("throws for invalid methods", () => {
|
|
assert.throws(() => massageMethod("options"));
|
|
assert.throws(() => massageMethod("head"));
|
|
});
|
|
});
|
|
|
|
describe("isRedirect", () => {
|
|
it("returns true for redirect results", () => {
|
|
const result: RedirectResult = {
|
|
code: httpCodes.redirection.Found,
|
|
contentType: contentTypes.text.html,
|
|
result: "",
|
|
redirect: "/other",
|
|
};
|
|
assert.equal(isRedirect(result), true);
|
|
});
|
|
|
|
it("returns false for non-redirect results", () => {
|
|
const result: Result = {
|
|
code: httpCodes.success.OK,
|
|
contentType: contentTypes.text.html,
|
|
result: "hello",
|
|
};
|
|
assert.equal(isRedirect(result), false);
|
|
});
|
|
});
|
|
|
|
describe("AuthenticationRequired", () => {
|
|
it("has correct name and message", () => {
|
|
const err = new AuthenticationRequired();
|
|
assert.equal(err.name, "AuthenticationRequired");
|
|
assert.equal(err.message, "Authentication required");
|
|
});
|
|
|
|
it("is an instance of Error", () => {
|
|
const err = new AuthenticationRequired();
|
|
assert.ok(err instanceof Error);
|
|
});
|
|
});
|
|
|
|
describe("AuthorizationDenied", () => {
|
|
it("has correct name and message", () => {
|
|
const err = new AuthorizationDenied();
|
|
assert.equal(err.name, "AuthorizationDenied");
|
|
assert.equal(err.message, "Authorization denied");
|
|
});
|
|
|
|
it("is an instance of Error", () => {
|
|
const err = new AuthorizationDenied();
|
|
assert.ok(err instanceof Error);
|
|
});
|
|
});
|
|
|
|
describe("requireAuth", () => {
|
|
it("returns user for authenticated call", () => {
|
|
const user = AuthenticatedUser.create("test@example.com");
|
|
const session = new Session(null, user);
|
|
const call = createMockCall({ user, session });
|
|
|
|
const result = requireAuth(call);
|
|
assert.equal(result, user);
|
|
});
|
|
|
|
it("throws AuthenticationRequired for anonymous user", () => {
|
|
const call = createMockCall({ user: anonymousUser });
|
|
|
|
assert.throws(() => requireAuth(call), AuthenticationRequired);
|
|
});
|
|
});
|
|
|
|
describe("requirePermission", () => {
|
|
it("returns user when they have the permission", () => {
|
|
const user = AuthenticatedUser.create("test@example.com", {
|
|
permissions: ["posts:create" as Permission],
|
|
});
|
|
const session = new Session(null, user);
|
|
const call = createMockCall({ user, session });
|
|
|
|
const result = requirePermission(
|
|
call,
|
|
"posts:create" as Permission,
|
|
);
|
|
assert.equal(result, user);
|
|
});
|
|
|
|
it("throws AuthenticationRequired for anonymous user", () => {
|
|
const call = createMockCall({ user: anonymousUser });
|
|
|
|
assert.throws(
|
|
() => requirePermission(call, "posts:create" as Permission),
|
|
AuthenticationRequired,
|
|
);
|
|
});
|
|
|
|
it("throws AuthorizationDenied when missing permission", () => {
|
|
const user = AuthenticatedUser.create("test@example.com", {
|
|
permissions: ["posts:read" as Permission],
|
|
});
|
|
const session = new Session(null, user);
|
|
const call = createMockCall({ user, session });
|
|
|
|
assert.throws(
|
|
() => requirePermission(call, "posts:create" as Permission),
|
|
AuthorizationDenied,
|
|
);
|
|
});
|
|
});
|
|
});
|