Files
stack/apps/api/src/auth/auth.service.spec.ts
Jason Woltje 6a038d093b feat(#4): Implement Authentik OIDC authentication with BetterAuth
- Integrated BetterAuth library for modern authentication
- Added Session, Account, and Verification database tables
- Created complete auth module with service, controller, guards, and decorators
- Implemented shared authentication types in @mosaic/shared package
- Added comprehensive test coverage (26 tests passing)
- Documented type sharing strategy for monorepo
- Updated environment configuration with OIDC and JWT settings

Key architectural decisions:
- BetterAuth over Passport.js for better TypeScript support
- Separation of User (DB entity) vs AuthUser (client-safe subset)
- Shared types package to prevent FE/BE drift
- Factory pattern for auth config to use shared Prisma instance

Ready for frontend integration (Issue #6).

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

Fixes #4
2026-01-28 17:26:34 -06:00

142 lines
3.7 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from "vitest";
import { Test, TestingModule } from "@nestjs/testing";
import { AuthService } from "./auth.service";
import { PrismaService } from "../prisma/prisma.service";
describe("AuthService", () => {
let service: AuthService;
let prisma: PrismaService;
const mockPrismaService = {
user: {
findUnique: vi.fn(),
},
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AuthService,
{
provide: PrismaService,
useValue: mockPrismaService,
},
],
}).compile();
service = module.get<AuthService>(AuthService);
prisma = module.get<PrismaService>(PrismaService);
vi.clearAllMocks();
});
describe("getAuth", () => {
it("should return BetterAuth instance", () => {
const auth = service.getAuth();
expect(auth).toBeDefined();
expect(auth.handler).toBeDefined();
});
});
describe("getUserById", () => {
const mockUser = {
id: "user-123",
email: "test@example.com",
name: "Test User",
authProviderId: "auth-123",
};
it("should get user by ID", async () => {
mockPrismaService.user.findUnique.mockResolvedValue(mockUser);
const result = await service.getUserById("user-123");
expect(result).toEqual(mockUser);
expect(mockPrismaService.user.findUnique).toHaveBeenCalledWith({
where: { id: "user-123" },
select: {
id: true,
email: true,
name: true,
authProviderId: true,
},
});
});
});
describe("getUserByEmail", () => {
const mockUser = {
id: "user-123",
email: "test@example.com",
name: "Test User",
authProviderId: "auth-123",
};
it("should get user by email", async () => {
mockPrismaService.user.findUnique.mockResolvedValue(mockUser);
const result = await service.getUserByEmail("test@example.com");
expect(result).toEqual(mockUser);
expect(mockPrismaService.user.findUnique).toHaveBeenCalledWith({
where: { email: "test@example.com" },
select: {
id: true,
email: true,
name: true,
authProviderId: true,
},
});
});
});
describe("verifySession", () => {
const mockSessionData = {
user: {
id: "user-123",
email: "test@example.com",
name: "Test User",
},
session: {
id: "session-123",
token: "test-token",
},
};
it("should return session data for valid token", async () => {
const auth = service.getAuth();
const mockGetSession = vi.fn().mockResolvedValue(mockSessionData);
auth.api = { getSession: mockGetSession } as any;
const result = await service.verifySession("valid-token");
expect(result).toEqual(mockSessionData);
expect(mockGetSession).toHaveBeenCalledWith({
headers: {
authorization: "Bearer valid-token",
},
});
});
it("should return null for invalid session", async () => {
const auth = service.getAuth();
const mockGetSession = vi.fn().mockResolvedValue(null);
auth.api = { getSession: mockGetSession } as any;
const result = await service.verifySession("invalid-token");
expect(result).toBeNull();
});
it("should return null and log error on verification failure", async () => {
const auth = service.getAuth();
const mockGetSession = vi.fn().mockRejectedValue(new Error("Verification failed"));
auth.api = { getSession: mockGetSession } as any;
const result = await service.verifySession("error-token");
expect(result).toBeNull();
});
});
});