// Tests for auth/service.ts // Uses InMemoryAuthStore - no database needed import assert from "node:assert/strict"; import { beforeEach, describe, it } from "node:test"; import { AuthService } from "./service"; import { InMemoryAuthStore } from "./store"; describe("AuthService", () => { let store: InMemoryAuthStore; let service: AuthService; beforeEach(() => { store = new InMemoryAuthStore(); service = new AuthService(store); }); describe("register", () => { it("creates a new user", async () => { const result = await service.register( "test@example.com", "password123", "Test User", ); assert.equal(result.success, true); if (result.success) { assert.equal(result.user.email, "test@example.com"); assert.equal(result.user.displayName, "Test User"); assert.ok(result.verificationToken.length > 0); } }); it("fails when email already registered", async () => { await service.register("test@example.com", "password123"); const result = await service.register( "test@example.com", "password456", ); assert.equal(result.success, false); if (!result.success) { assert.equal(result.error, "Email already registered"); } }); it("creates user without displayName", async () => { const result = await service.register( "test@example.com", "password123", ); assert.equal(result.success, true); if (result.success) { assert.equal(result.user.displayName, undefined); } }); }); describe("login", () => { beforeEach(async () => { // Create and verify a user const result = await service.register( "test@example.com", "password123", "Test User", ); if (result.success) { // Verify email to activate user await service.verifyEmail(result.verificationToken); } }); it("succeeds with correct credentials", async () => { const result = await service.login( "test@example.com", "password123", "cookie", ); assert.equal(result.success, true); if (result.success) { assert.ok(result.token.length > 0); assert.equal(result.user.email, "test@example.com"); } }); it("fails with wrong password", async () => { const result = await service.login( "test@example.com", "wrongpassword", "cookie", ); assert.equal(result.success, false); if (!result.success) { assert.equal(result.error, "Invalid credentials"); } }); it("fails with unknown email", async () => { const result = await service.login( "unknown@example.com", "password123", "cookie", ); assert.equal(result.success, false); if (!result.success) { assert.equal(result.error, "Invalid credentials"); } }); it("fails for inactive user", async () => { // Create a user but don't verify email (stays pending) await service.register("pending@example.com", "password123"); const result = await service.login( "pending@example.com", "password123", "cookie", ); assert.equal(result.success, false); if (!result.success) { assert.equal(result.error, "Account is not active"); } }); it("stores metadata", async () => { const result = await service.login( "test@example.com", "password123", "cookie", { userAgent: "TestAgent", ipAddress: "192.168.1.1" }, ); assert.equal(result.success, true); }); }); describe("validateToken", () => { let token: string; beforeEach(async () => { const regResult = await service.register( "test@example.com", "password123", ); if (regResult.success) { await service.verifyEmail(regResult.verificationToken); } const loginResult = await service.login( "test@example.com", "password123", "cookie", ); if (loginResult.success) { token = loginResult.token; } }); it("returns authenticated for valid token", async () => { const result = await service.validateToken(token); assert.equal(result.authenticated, true); if (result.authenticated) { assert.equal(result.user.email, "test@example.com"); assert.notEqual(result.session, null); } }); it("returns unauthenticated for invalid token", async () => { const result = await service.validateToken("invalid-token"); assert.equal(result.authenticated, false); assert.equal(result.user.isAnonymous(), true); assert.equal(result.session, null); }); }); describe("logout", () => { it("invalidates the session", async () => { const regResult = await service.register( "test@example.com", "password123", ); if (regResult.success) { await service.verifyEmail(regResult.verificationToken); } const loginResult = await service.login( "test@example.com", "password123", "cookie", ); assert.equal(loginResult.success, true); if (!loginResult.success) return; const token = loginResult.token; // Token should be valid before logout const beforeLogout = await service.validateToken(token); assert.equal(beforeLogout.authenticated, true); // Logout await service.logout(token); // Token should be invalid after logout const afterLogout = await service.validateToken(token); assert.equal(afterLogout.authenticated, false); }); }); describe("logoutAllSessions", () => { it("invalidates all user sessions", async () => { const regResult = await service.register( "test@example.com", "password123", ); if (regResult.success) { await service.verifyEmail(regResult.verificationToken); } // Create multiple sessions const login1 = await service.login( "test@example.com", "password123", "cookie", ); const login2 = await service.login( "test@example.com", "password123", "bearer", ); assert.equal(login1.success, true); assert.equal(login2.success, true); if (!login1.success || !login2.success) return; // Both should be valid const before1 = await service.validateToken(login1.token); const before2 = await service.validateToken(login2.token); assert.equal(before1.authenticated, true); assert.equal(before2.authenticated, true); // Logout all const user = await store.getUserByEmail("test@example.com"); const count = await service.logoutAllSessions(user!.id); assert.equal(count, 2); // Both should be invalid const after1 = await service.validateToken(login1.token); const after2 = await service.validateToken(login2.token); assert.equal(after1.authenticated, false); assert.equal(after2.authenticated, false); }); }); describe("verifyEmail", () => { it("activates user with valid token", async () => { const regResult = await service.register( "test@example.com", "password123", ); assert.equal(regResult.success, true); if (!regResult.success) return; const result = await service.verifyEmail( regResult.verificationToken, ); assert.equal(result.success, true); // User should now be active and can login const loginResult = await service.login( "test@example.com", "password123", "cookie", ); assert.equal(loginResult.success, true); }); it("fails with invalid token", async () => { const result = await service.verifyEmail("invalid-token"); assert.equal(result.success, false); if (!result.success) { assert.equal( result.error, "Invalid or expired verification token", ); } }); it("fails when token already used", async () => { const regResult = await service.register( "test@example.com", "password123", ); assert.equal(regResult.success, true); if (!regResult.success) return; // First verification succeeds const result1 = await service.verifyEmail( regResult.verificationToken, ); assert.equal(result1.success, true); // Second verification fails (token deleted) const result2 = await service.verifyEmail( regResult.verificationToken, ); assert.equal(result2.success, false); }); }); describe("createPasswordResetToken", () => { it("returns token for existing user", async () => { const regResult = await service.register( "test@example.com", "password123", ); assert.equal(regResult.success, true); const result = await service.createPasswordResetToken("test@example.com"); assert.notEqual(result, null); assert.ok(result!.token.length > 0); }); it("returns null for unknown email", async () => { const result = await service.createPasswordResetToken( "unknown@example.com", ); assert.equal(result, null); }); }); describe("resetPassword", () => { it("changes password with valid token", async () => { const regResult = await service.register( "test@example.com", "oldpassword", ); if (regResult.success) { await service.verifyEmail(regResult.verificationToken); } const resetToken = await service.createPasswordResetToken("test@example.com"); assert.notEqual(resetToken, null); const result = await service.resetPassword( resetToken!.token, "newpassword", ); assert.equal(result.success, true); // Old password should no longer work const loginOld = await service.login( "test@example.com", "oldpassword", "cookie", ); assert.equal(loginOld.success, false); // New password should work const loginNew = await service.login( "test@example.com", "newpassword", "cookie", ); assert.equal(loginNew.success, true); }); it("fails with invalid token", async () => { const result = await service.resetPassword( "invalid-token", "newpassword", ); assert.equal(result.success, false); if (!result.success) { assert.equal(result.error, "Invalid or expired reset token"); } }); it("invalidates all existing sessions", async () => { const regResult = await service.register( "test@example.com", "password123", ); if (regResult.success) { await service.verifyEmail(regResult.verificationToken); } // Create a session const loginResult = await service.login( "test@example.com", "password123", "cookie", ); assert.equal(loginResult.success, true); if (!loginResult.success) return; const sessionToken = loginResult.token; // Reset password const resetToken = await service.createPasswordResetToken("test@example.com"); await service.resetPassword(resetToken!.token, "newpassword"); // Old session should be invalid const validateResult = await service.validateToken(sessionToken); assert.equal(validateResult.authenticated, false); }); }); });