Tests for: - user.ts: User class, roles, permissions, status checks - util.ts: loadFile utility - handlers.ts: multiHandler - types.ts: methodParser, requireAuth, requirePermission - logging.ts: module structure - database.ts: connectionConfig, raw queries, PostgresAuthStore - auth/token.ts: generateToken, hashToken, parseAuthorizationHeader - auth/password.ts: hashPassword, verifyPassword (scrypt) - auth/types.ts: Zod parsers, Session class, tokenLifetimes - auth/store.ts: InMemoryAuthStore - auth/service.ts: AuthService (login, register, verify, reset) - basic/*.ts: route structure tests Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
254 lines
8.3 KiB
TypeScript
254 lines
8.3 KiB
TypeScript
// Tests for auth/types.ts
|
|
// Pure unit tests - no database needed
|
|
|
|
import assert from "node:assert/strict";
|
|
import { describe, it } from "node:test";
|
|
import { z } from "zod";
|
|
import { AuthenticatedUser, anonymousUser } from "../user";
|
|
import {
|
|
authMethodParser,
|
|
forgotPasswordInputParser,
|
|
loginInputParser,
|
|
registerInputParser,
|
|
resetPasswordInputParser,
|
|
Session,
|
|
sessionDataParser,
|
|
tokenLifetimes,
|
|
tokenTypeParser,
|
|
} from "./types";
|
|
|
|
describe("auth/types", () => {
|
|
describe("tokenTypeParser", () => {
|
|
it("accepts valid token types", () => {
|
|
assert.equal(tokenTypeParser.parse("session"), "session");
|
|
assert.equal(
|
|
tokenTypeParser.parse("password_reset"),
|
|
"password_reset",
|
|
);
|
|
assert.equal(tokenTypeParser.parse("email_verify"), "email_verify");
|
|
});
|
|
|
|
it("rejects invalid token types", () => {
|
|
assert.throws(() => tokenTypeParser.parse("invalid"));
|
|
});
|
|
});
|
|
|
|
describe("authMethodParser", () => {
|
|
it("accepts valid auth methods", () => {
|
|
assert.equal(authMethodParser.parse("cookie"), "cookie");
|
|
assert.equal(authMethodParser.parse("bearer"), "bearer");
|
|
});
|
|
|
|
it("rejects invalid auth methods", () => {
|
|
assert.throws(() => authMethodParser.parse("basic"));
|
|
});
|
|
});
|
|
|
|
describe("sessionDataParser", () => {
|
|
it("accepts valid session data", () => {
|
|
const data = {
|
|
tokenId: "abc123",
|
|
userId: "user-1",
|
|
tokenType: "session",
|
|
authMethod: "cookie",
|
|
createdAt: new Date(),
|
|
expiresAt: new Date(),
|
|
};
|
|
const result = sessionDataParser.parse(data);
|
|
assert.equal(result.tokenId, "abc123");
|
|
assert.equal(result.userId, "user-1");
|
|
});
|
|
|
|
it("coerces date strings to dates", () => {
|
|
const data = {
|
|
tokenId: "abc123",
|
|
userId: "user-1",
|
|
tokenType: "session",
|
|
authMethod: "cookie",
|
|
createdAt: "2025-01-01T00:00:00Z",
|
|
expiresAt: "2025-01-02T00:00:00Z",
|
|
};
|
|
const result = sessionDataParser.parse(data);
|
|
assert.ok(result.createdAt instanceof Date);
|
|
assert.ok(result.expiresAt instanceof Date);
|
|
});
|
|
|
|
it("accepts optional fields", () => {
|
|
const data = {
|
|
tokenId: "abc123",
|
|
userId: "user-1",
|
|
tokenType: "session",
|
|
authMethod: "cookie",
|
|
createdAt: new Date(),
|
|
expiresAt: new Date(),
|
|
lastUsedAt: new Date(),
|
|
userAgent: "Mozilla/5.0",
|
|
ipAddress: "127.0.0.1",
|
|
isUsed: true,
|
|
};
|
|
const result = sessionDataParser.parse(data);
|
|
assert.equal(result.userAgent, "Mozilla/5.0");
|
|
assert.equal(result.ipAddress, "127.0.0.1");
|
|
assert.equal(result.isUsed, true);
|
|
});
|
|
});
|
|
|
|
describe("loginInputParser", () => {
|
|
it("accepts valid login input", () => {
|
|
const result = loginInputParser.parse({
|
|
email: "test@example.com",
|
|
password: "secret",
|
|
});
|
|
assert.equal(result.email, "test@example.com");
|
|
assert.equal(result.password, "secret");
|
|
});
|
|
|
|
it("rejects invalid email", () => {
|
|
assert.throws(() =>
|
|
loginInputParser.parse({
|
|
email: "not-an-email",
|
|
password: "secret",
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("rejects empty password", () => {
|
|
assert.throws(() =>
|
|
loginInputParser.parse({
|
|
email: "test@example.com",
|
|
password: "",
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("registerInputParser", () => {
|
|
it("accepts valid registration input", () => {
|
|
const result = registerInputParser.parse({
|
|
email: "test@example.com",
|
|
password: "password123",
|
|
displayName: "Test User",
|
|
});
|
|
assert.equal(result.email, "test@example.com");
|
|
assert.equal(result.password, "password123");
|
|
assert.equal(result.displayName, "Test User");
|
|
});
|
|
|
|
it("accepts registration without displayName", () => {
|
|
const result = registerInputParser.parse({
|
|
email: "test@example.com",
|
|
password: "password123",
|
|
});
|
|
assert.equal(result.displayName, undefined);
|
|
});
|
|
|
|
it("rejects password shorter than 8 characters", () => {
|
|
assert.throws(() =>
|
|
registerInputParser.parse({
|
|
email: "test@example.com",
|
|
password: "short",
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("forgotPasswordInputParser", () => {
|
|
it("accepts valid email", () => {
|
|
const result = forgotPasswordInputParser.parse({
|
|
email: "test@example.com",
|
|
});
|
|
assert.equal(result.email, "test@example.com");
|
|
});
|
|
|
|
it("rejects invalid email", () => {
|
|
assert.throws(() =>
|
|
forgotPasswordInputParser.parse({
|
|
email: "invalid",
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("resetPasswordInputParser", () => {
|
|
it("accepts valid reset input", () => {
|
|
const result = resetPasswordInputParser.parse({
|
|
token: "abc123",
|
|
password: "newpassword",
|
|
});
|
|
assert.equal(result.token, "abc123");
|
|
assert.equal(result.password, "newpassword");
|
|
});
|
|
|
|
it("rejects empty token", () => {
|
|
assert.throws(() =>
|
|
resetPasswordInputParser.parse({
|
|
token: "",
|
|
password: "newpassword",
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("rejects password shorter than 8 characters", () => {
|
|
assert.throws(() =>
|
|
resetPasswordInputParser.parse({
|
|
token: "abc123",
|
|
password: "short",
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("tokenLifetimes", () => {
|
|
it("defines session lifetime", () => {
|
|
assert.ok(tokenLifetimes.session > 0);
|
|
// 30 days in ms
|
|
assert.equal(tokenLifetimes.session, 30 * 24 * 60 * 60 * 1000);
|
|
});
|
|
|
|
it("defines password_reset lifetime", () => {
|
|
assert.ok(tokenLifetimes.password_reset > 0);
|
|
// 1 hour in ms
|
|
assert.equal(tokenLifetimes.password_reset, 1 * 60 * 60 * 1000);
|
|
});
|
|
|
|
it("defines email_verify lifetime", () => {
|
|
assert.ok(tokenLifetimes.email_verify > 0);
|
|
// 24 hours in ms
|
|
assert.equal(tokenLifetimes.email_verify, 24 * 60 * 60 * 1000);
|
|
});
|
|
});
|
|
|
|
describe("Session", () => {
|
|
it("wraps authenticated session", () => {
|
|
const user = AuthenticatedUser.create("test@example.com", {
|
|
id: "user-1",
|
|
});
|
|
const sessionData = {
|
|
tokenId: "token-1",
|
|
userId: "user-1",
|
|
tokenType: "session" as const,
|
|
authMethod: "cookie" as const,
|
|
createdAt: new Date(),
|
|
expiresAt: new Date(),
|
|
};
|
|
const session = new Session(sessionData, user);
|
|
|
|
assert.equal(session.isAuthenticated(), true);
|
|
assert.equal(session.getUser(), user);
|
|
assert.equal(session.getData(), sessionData);
|
|
assert.equal(session.tokenId, "token-1");
|
|
assert.equal(session.userId, "user-1");
|
|
});
|
|
|
|
it("wraps anonymous session", () => {
|
|
const session = new Session(null, anonymousUser);
|
|
|
|
assert.equal(session.isAuthenticated(), false);
|
|
assert.equal(session.getUser(), anonymousUser);
|
|
assert.equal(session.getData(), null);
|
|
assert.equal(session.tokenId, undefined);
|
|
assert.equal(session.userId, undefined);
|
|
});
|
|
});
|
|
});
|