import { beforeEach, describe, expect, it, vi } from 'vitest'; // Set required env vars before any imports process.env.ROOT_ADMIN_USERNAME = 'admin'; process.env.ROOT_ADMIN_PASSWORD = 'admin-password'; const { mockPool, mockClient } = vi.hoisted(() => ({ mockPool: { connect: vi.fn(), }, mockClient: { query: vi.fn(), release: vi.fn(), }, })); vi.mock('../../src/infra/database/database.manager', () => ({ DatabaseManager: { getInstance: () => ({ getPool: () => mockPool, }), }, })); vi.mock('../../src/utils/logger', () => ({ default: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn(), }, })); vi.mock('bcryptjs', () => ({ default: { hash: vi.fn().mockResolvedValue('hashed-password'), }, })); vi.mock('../../src/infra/security/token.manager', () => ({ TokenManager: { getInstance: () => ({ generateAccessToken: vi.fn().mockReturnValue('test-access-token'), }), }, })); vi.mock('../../src/services/auth/auth-config.service', () => ({ AuthConfigService: { getInstance: () => ({ getAuthConfig: vi.fn().mockResolvedValue({ requireEmailVerification: true, verifyEmailMethod: 'code', passwordMinLength: 8, passwordRequireUppercase: false, passwordRequireLowercase: false, passwordRequireNumbers: false, passwordRequireSymbols: false, }), validateRedirectUrl: vi.fn().mockResolvedValue(true), }), }, })); vi.mock('../../src/services/auth/auth-otp.service', () => ({ AuthOTPService: { getInstance: () => ({ generateOTP: vi.fn().mockResolvedValue('123456'), }), }, OTPPurpose: { VERIFY_EMAIL: 'VERIFY_EMAIL' }, OTPType: { CODE: 'CODE' }, })); vi.mock('../../src/services/auth/oauth-config.service', () => ({ OAuthConfigService: { getInstance: () => ({}), }, })); vi.mock('../../src/services/auth/custom-oauth-config.service', () => ({ CustomOAuthConfigService: { getInstance: () => ({}), }, })); vi.mock('../../src/services/email/email.service', () => ({ EmailService: { getInstance: () => ({ sendMail: vi.fn().mockResolvedValue(undefined), }), }, })); // Mock all OAuth providers the constructor initializes const mockOAuthProvider = { getInstance: () => ({}) }; vi.mock('../../src/providers/oauth/google.oauth.provider', () => ({ GoogleOAuthProvider: mockOAuthProvider, })); vi.mock('../../src/providers/oauth/github.oauth.provider', () => ({ GitHubOAuthProvider: mockOAuthProvider, })); vi.mock('../../src/providers/oauth/discord.oauth.provider', () => ({ DiscordOAuthProvider: mockOAuthProvider, })); vi.mock('../../src/providers/oauth/facebook.oauth.provider', () => ({ FacebookOAuthProvider: mockOAuthProvider, })); vi.mock('../../src/providers/oauth/microsoft.oauth.provider', () => ({ MicrosoftOAuthProvider: mockOAuthProvider, })); vi.mock('../../src/providers/oauth/x.oauth.provider', () => ({ XOAuthProvider: mockOAuthProvider, })); vi.mock('../../src/providers/oauth/apple.oauth.provider', () => ({ AppleOAuthProvider: mockOAuthProvider, })); vi.mock('../../src/infra/config/app.config', () => { const c = { app: { jwtSecret: 'test-secret', name: 'test', }, cloud: { projectId: null, }, auth: { rootAdminUsername: 'admin@test.com', rootAdminPassword: 'admin-password', }, }; return { config: c, appConfig: c, getApiBaseUrl: () => 'http://localhost:3000', }; }); import { AuthService } from '../../src/services/auth/auth.service'; describe('AuthService.register – autoConfirm', () => { let authService: AuthService; beforeEach(() => { vi.clearAllMocks(); mockPool.connect.mockResolvedValue(mockClient); mockClient.query.mockResolvedValue({ rows: [] }); // Reset singleton to get fresh instance // eslint-disable-next-line @typescript-eslint/no-explicit-any (AuthService as any).instance = undefined; authService = AuthService.getInstance(); // Mock getUserById to return a user record after INSERT // eslint-disable-next-line @typescript-eslint/no-explicit-any vi.spyOn(authService as any, 'getUserById').mockResolvedValue({ id: 'test-user-id', email: 'test@example.com', profile: { name: 'Test' }, email_verified: false, created_at: new Date(), updated_at: new Date(), metadata: null, is_anonymous: false, }); }); it('sets email_verified=true when autoConfirm=true and isAdminCreation=true', async () => { const result = await authService.register( 'test@example.com', 'password123', 'Test', undefined, { isAdminCreation: true, autoConfirm: true } ); // Verify INSERT was called with email_verified=true (5th param) const insertCall = mockClient.query.mock.calls.find( (call: unknown[]) => typeof call[0] === 'string' && call[0].includes('INSERT INTO auth.users') ); expect(insertCall).toBeDefined(); expect(insertCall![1][4]).toBe(true); // Should not require email verification expect(result.requireEmailVerification).toBe(false); }); it('sets email_verified=false when autoConfirm=false and isAdminCreation=true', async () => { const result = await authService.register( 'test@example.com', 'password123', 'Test', undefined, { isAdminCreation: true, autoConfirm: false } ); const insertCall = mockClient.query.mock.calls.find( (call: unknown[]) => typeof call[0] === 'string' && call[0].includes('INSERT INTO auth.users') ); expect(insertCall).toBeDefined(); expect(insertCall![1][4]).toBe(false); expect(result.requireEmailVerification).toBe(true); }); it('ignores autoConfirm=true when isAdminCreation is false', async () => { await authService.register('test@example.com', 'password123', 'Test', undefined, { isAdminCreation: false, autoConfirm: true, }); const insertCall = mockClient.query.mock.calls.find( (call: unknown[]) => typeof call[0] === 'string' && call[0].includes('INSERT INTO auth.users') ); expect(insertCall).toBeDefined(); expect(insertCall![1][4]).toBe(false); }); it('preserves existing behavior when autoConfirm is omitted', async () => { const result = await authService.register( 'test@example.com', 'password123', 'Test', undefined, { isAdminCreation: true } ); const insertCall = mockClient.query.mock.calls.find( (call: unknown[]) => typeof call[0] === 'string' && call[0].includes('INSERT INTO auth.users') ); expect(insertCall).toBeDefined(); expect(insertCall![1][4]).toBe(false); expect(result.requireEmailVerification).toBe(true); }); });