UNPKG

@accounter/server

Version:
199 lines (160 loc) • 6.32 kB
import { ManagementClient } from 'auth0'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { Auth0ManagementProvider } from '../auth0-management.provider.js'; import type { Environment } from '../../../../shared/types/index.js'; // Mock auth0 library vi.mock('auth0', () => { const users = { create: vi.fn(), delete: vi.fn(), update: vi.fn(), get: vi.fn(), listUsersByEmail: vi.fn(), }; const tickets = { changePassword: vi.fn(), }; const ManagementClient = vi.fn(function() { return { users, tickets, } }); return { ManagementClient }; }); describe('Auth0ManagementProvider', () => { let service: Auth0ManagementProvider; let mockClient: any; let mockEnv: Environment; beforeEach(() => { vi.clearAllMocks(); // Create mock environment mockEnv = { auth0: { domain: 'test-domain', clientId: 'test-client-id', clientSecret: 'test-client-secret', audience: 'test-audience', managementAudience: 'test-management-audience', }, general: { frontendUrl: 'http://localhost:3000', }, // minimal mock } as any; service = new Auth0ManagementProvider(mockEnv); // Get the instance of the mocked client mockClient = (ManagementClient as any).mock.results[0].value; }); afterEach(() => { vi.resetModules(); }); it('should initialize ManagementClient with correct config', () => { expect(ManagementClient).toHaveBeenCalledWith({ domain: 'test-domain', clientId: 'test-client-id', clientSecret: 'test-client-secret', audience: 'test-management-audience', }); }); it('should return existing user id if user already exists', async () => { const email = 'existing@example.com'; const existingUserId = 'auth0|existing'; mockClient.users.listUsersByEmail.mockResolvedValue([{ user_id: existingUserId }]); const userId = await service.createBlockedUser(email); expect(userId).toBe(existingUserId); expect(mockClient.users.create).not.toHaveBeenCalled(); }); it('should create a blocked user with temporary password', async () => { const email = 'test@example.com'; const mockUserId = 'auth0|123456'; mockClient.users.listUsersByEmail.mockResolvedValue([]); mockClient.users.create.mockResolvedValue({ user_id: mockUserId, }); const userId = await service.createBlockedUser(email); expect(userId).toBe(mockUserId); expect(mockClient.users.create).toHaveBeenCalledWith( expect.objectContaining({ connection: 'Username-Password-Authentication', email, blocked: true, email_verified: false, app_metadata: { registered_by: 'accounter', }, }), ); // Verify password complexity requirements const callArgs = mockClient.users.create.mock.calls[0][0]; const password = callArgs.password; expect(password.length).toBeGreaterThanOrEqual(8); expect(/[a-z]/.test(password)).toBe(true); expect(/[A-Z]/.test(password)).toBe(true); expect(/[0-9]/.test(password)).toBe(true); // eslint-disable-next-line no-useless-escape expect(/[!@#$%^&*()_+~`|}{[\]:;?><,./\-=]/.test(password)).toBe(true); }); it('should unblock a user', async () => { const userId = 'auth0|123456'; mockClient.users.update.mockResolvedValue({}); await service.unblockUser(userId); expect(mockClient.users.update).toHaveBeenCalledWith( userId, { blocked: false } ); }); it('should delete a user', async () => { const userId = 'auth0|123456'; mockClient.users.delete.mockResolvedValue({}); await service.deleteUser(userId); expect(mockClient.users.delete).toHaveBeenCalledWith(userId); }); it('should send password reset email (trigger ticket)', async () => { const userId = 'auth0|123456'; const mockTicket = 'https://auth0.com/ticket'; mockClient.tickets.changePassword.mockResolvedValue({ ticket: mockTicket }); const ticket = await service.sendPasswordResetEmail(userId); // Should return the ticket URL expect(ticket).toBe(mockTicket); // Should NOT assume verified before reset expect(mockClient.users.update).not.toHaveBeenCalledWith( { id: userId }, { email_verified: true } ); // Should trigger password change with mark_email_as_verified: true expect(mockClient.tickets.changePassword).toHaveBeenCalledWith( expect.objectContaining({ user_id: userId, mark_email_as_verified: true, result_url: 'http://localhost:3000/login', }) ); }); it('should throw error if creation fails', async () => { mockClient.users.listUsersByEmail.mockResolvedValue([]); mockClient.users.create.mockRejectedValue(new Error('Auth0 Error')); await expect(service.createBlockedUser('fail@test.com')).rejects.toThrow('Failed to create Auth0 user: Auth0 Error'); }); it('getUserProfileById returns email + name and caches the result', async () => { const userId = 'auth0|profile'; mockClient.users.get.mockResolvedValue({ email: 'ada@example.com', name: 'Ada Lovelace' }); const first = await service.getUserProfileById(userId); const second = await service.getUserProfileById(userId); expect(first).toEqual({ email: 'ada@example.com', name: 'Ada Lovelace' }); expect(second).toEqual({ email: 'ada@example.com', name: 'Ada Lovelace' }); // Second call is served from the in-memory cache, not a second Auth0 request. expect(mockClient.users.get).toHaveBeenCalledTimes(1); }); it('getUserProfileById returns null on failure and does not cache it', async () => { const userId = 'auth0|transient'; mockClient.users.get.mockRejectedValueOnce(new Error('boom')); const failed = await service.getUserProfileById(userId); expect(failed).toBeNull(); // A failed lookup is not cached, so a later call retries Auth0. mockClient.users.get.mockResolvedValueOnce({ email: 'x@example.com', name: null }); const retry = await service.getUserProfileById(userId); expect(retry).toEqual({ email: 'x@example.com', name: null }); expect(mockClient.users.get).toHaveBeenCalledTimes(2); }); });