// Tests for auth/store.ts (InMemoryAuthStore) // Pure unit tests - no database needed import assert from "node:assert/strict"; import { after, before, beforeEach, describe, it } from "node:test"; import type { UserId } from "../user"; import { InMemoryAuthStore } from "./store"; import { hashToken } from "./token"; import type { TokenId } from "./types"; describe("InMemoryAuthStore", () => { let store: InMemoryAuthStore; beforeEach(() => { store = new InMemoryAuthStore(); }); describe("createUser", () => { it("creates a user with pending status", async () => { const user = await store.createUser({ email: "test@example.com", passwordHash: "hash123", displayName: "Test User", }); assert.equal(user.email, "test@example.com"); assert.equal(user.displayName, "Test User"); assert.equal(user.status, "pending"); }); it("creates a user without displayName", async () => { const user = await store.createUser({ email: "test@example.com", passwordHash: "hash123", }); assert.equal(user.email, "test@example.com"); assert.equal(user.displayName, undefined); }); it("generates a unique id", async () => { const user1 = await store.createUser({ email: "test1@example.com", passwordHash: "hash123", }); const user2 = await store.createUser({ email: "test2@example.com", passwordHash: "hash456", }); assert.notEqual(user1.id, user2.id); }); }); describe("getUserByEmail", () => { it("returns user when found", async () => { await store.createUser({ email: "test@example.com", passwordHash: "hash123", }); const user = await store.getUserByEmail("test@example.com"); assert.notEqual(user, null); assert.equal(user!.email, "test@example.com"); }); it("is case-insensitive", async () => { await store.createUser({ email: "Test@Example.COM", passwordHash: "hash123", }); const user = await store.getUserByEmail("test@example.com"); assert.notEqual(user, null); }); it("returns null when not found", async () => { const user = await store.getUserByEmail("notfound@example.com"); assert.equal(user, null); }); }); describe("getUserById", () => { it("returns user when found", async () => { const created = await store.createUser({ email: "test@example.com", passwordHash: "hash123", }); const user = await store.getUserById(created.id); assert.notEqual(user, null); assert.equal(user!.id, created.id); }); it("returns null when not found", async () => { const user = await store.getUserById("nonexistent" as UserId); assert.equal(user, null); }); }); describe("getUserPasswordHash", () => { it("returns hash when found", async () => { const user = await store.createUser({ email: "test@example.com", passwordHash: "hash123", }); const hash = await store.getUserPasswordHash(user.id); assert.equal(hash, "hash123"); }); it("returns null when not found", async () => { const hash = await store.getUserPasswordHash( "nonexistent" as UserId, ); assert.equal(hash, null); }); }); describe("setUserPassword", () => { it("updates password hash", async () => { const user = await store.createUser({ email: "test@example.com", passwordHash: "oldhash", }); await store.setUserPassword(user.id, "newhash"); const hash = await store.getUserPasswordHash(user.id); assert.equal(hash, "newhash"); }); }); describe("updateUserEmailVerified", () => { it("sets user status to active", async () => { const created = await store.createUser({ email: "test@example.com", passwordHash: "hash123", }); assert.equal(created.status, "pending"); await store.updateUserEmailVerified(created.id); const user = await store.getUserById(created.id); assert.equal(user!.status, "active"); }); }); describe("createSession", () => { it("creates a session with token", async () => { const user = await store.createUser({ email: "test@example.com", passwordHash: "hash123", }); const { token, session } = await store.createSession({ userId: user.id, tokenType: "session", authMethod: "cookie", expiresAt: new Date(Date.now() + 3600000), }); assert.ok(token.length > 0); assert.equal(session.userId, user.id); assert.equal(session.tokenType, "session"); assert.equal(session.authMethod, "cookie"); }); it("stores metadata", async () => { const user = await store.createUser({ email: "test@example.com", passwordHash: "hash123", }); const { session } = await store.createSession({ userId: user.id, tokenType: "session", authMethod: "cookie", expiresAt: new Date(Date.now() + 3600000), userAgent: "Mozilla/5.0", ipAddress: "127.0.0.1", }); assert.equal(session.userAgent, "Mozilla/5.0"); assert.equal(session.ipAddress, "127.0.0.1"); }); }); describe("getSession", () => { it("returns session when found and not expired", async () => { const user = await store.createUser({ email: "test@example.com", passwordHash: "hash123", }); const { token } = await store.createSession({ userId: user.id, tokenType: "session", authMethod: "cookie", expiresAt: new Date(Date.now() + 3600000), // 1 hour from now }); const tokenId = hashToken(token) as TokenId; const session = await store.getSession(tokenId); assert.notEqual(session, null); assert.equal(session!.userId, user.id); }); it("returns null for expired session", async () => { const user = await store.createUser({ email: "test@example.com", passwordHash: "hash123", }); const { token } = await store.createSession({ userId: user.id, tokenType: "session", authMethod: "cookie", expiresAt: new Date(Date.now() - 1000), // Expired 1 second ago }); const tokenId = hashToken(token) as TokenId; const session = await store.getSession(tokenId); assert.equal(session, null); }); it("returns null for nonexistent session", async () => { const session = await store.getSession("nonexistent" as TokenId); assert.equal(session, null); }); }); describe("deleteSession", () => { it("removes the session", async () => { const user = await store.createUser({ email: "test@example.com", passwordHash: "hash123", }); const { token } = await store.createSession({ userId: user.id, tokenType: "session", authMethod: "cookie", expiresAt: new Date(Date.now() + 3600000), }); const tokenId = hashToken(token) as TokenId; await store.deleteSession(tokenId); const session = await store.getSession(tokenId); assert.equal(session, null); }); }); describe("deleteUserSessions", () => { it("removes all sessions for user", async () => { const user = await store.createUser({ email: "test@example.com", passwordHash: "hash123", }); const { token: token1 } = await store.createSession({ userId: user.id, tokenType: "session", authMethod: "cookie", expiresAt: new Date(Date.now() + 3600000), }); const { token: token2 } = await store.createSession({ userId: user.id, tokenType: "session", authMethod: "bearer", expiresAt: new Date(Date.now() + 3600000), }); const count = await store.deleteUserSessions(user.id); assert.equal(count, 2); const session1 = await store.getSession( hashToken(token1) as TokenId, ); const session2 = await store.getSession( hashToken(token2) as TokenId, ); assert.equal(session1, null); assert.equal(session2, null); }); it("returns 0 when user has no sessions", async () => { const count = await store.deleteUserSessions( "nonexistent" as UserId, ); assert.equal(count, 0); }); }); describe("updateLastUsed", () => { it("updates lastUsedAt timestamp", async () => { const user = await store.createUser({ email: "test@example.com", passwordHash: "hash123", }); const { token } = await store.createSession({ userId: user.id, tokenType: "session", authMethod: "cookie", expiresAt: new Date(Date.now() + 3600000), }); const tokenId = hashToken(token) as TokenId; const beforeUpdate = await store.getSession(tokenId); assert.equal(beforeUpdate!.lastUsedAt, undefined); await store.updateLastUsed(tokenId); const afterUpdate = await store.getSession(tokenId); assert.ok(afterUpdate!.lastUsedAt instanceof Date); }); }); });