366 lines
12 KiB
TypeScript
366 lines
12 KiB
TypeScript
/**
|
|
* Copyright 2025 LY Corporation
|
|
*
|
|
* LY Corporation licenses this file to you under the Apache License,
|
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at:
|
|
*
|
|
* https://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
import { faker } from '@faker-js/faker';
|
|
import {
|
|
BadRequestException,
|
|
InternalServerErrorException,
|
|
} from '@nestjs/common';
|
|
import { Test } from '@nestjs/testing';
|
|
import { DateTime } from 'luxon';
|
|
|
|
import { getMockProvider } from '@/test-utils/util-functions';
|
|
import { TenantService } from '../tenant/tenant.service';
|
|
import { UserDto } from '../user/dtos';
|
|
import { AuthController } from './auth.controller';
|
|
import { AuthService } from './auth.service';
|
|
import {
|
|
EmailUserSignUpRequestDto,
|
|
EmailVerificationCodeRequestDto,
|
|
EmailVerificationMailingRequestDto,
|
|
InvitationUserSignUpRequestDto,
|
|
OAuthUserSignUpRequestDto,
|
|
} from './dtos/requests';
|
|
|
|
const MockAuthService = {
|
|
sendEmailCode: jest.fn(),
|
|
verifyEmailCode: jest.fn(),
|
|
signUpEmailUser: jest.fn(),
|
|
signUpInvitationUser: jest.fn(),
|
|
signUpOAuthUser: jest.fn(),
|
|
signIn: jest.fn(),
|
|
signInByOAuth: jest.fn(),
|
|
refreshToken: jest.fn(),
|
|
getOAuthLoginURL: jest.fn(),
|
|
};
|
|
|
|
const MockTenantService = {
|
|
findOne: jest.fn(),
|
|
};
|
|
|
|
describe('AuthController', () => {
|
|
let authController: AuthController;
|
|
let authService: jest.Mocked<AuthService>;
|
|
let _tenantService: jest.Mocked<TenantService>;
|
|
|
|
beforeEach(async () => {
|
|
const module = await Test.createTestingModule({
|
|
providers: [
|
|
getMockProvider(AuthService, MockAuthService),
|
|
getMockProvider(TenantService, MockTenantService),
|
|
],
|
|
controllers: [AuthController],
|
|
}).compile();
|
|
|
|
authController = module.get(AuthController);
|
|
authService = module.get(AuthService);
|
|
_tenantService = module.get(TenantService);
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
it('should be defined', () => {
|
|
expect(authController).toBeDefined();
|
|
});
|
|
describe('sendCode', () => {
|
|
it('should send email verification code successfully', async () => {
|
|
const mockTimestamp = DateTime.utc().toISO();
|
|
const dto = new EmailVerificationMailingRequestDto();
|
|
dto.email = faker.internet.email();
|
|
|
|
authService.sendEmailCode.mockResolvedValue(mockTimestamp);
|
|
|
|
const result = await authController.sendCode(dto);
|
|
|
|
expect(authService.sendEmailCode).toHaveBeenCalledWith(dto);
|
|
expect(authService.sendEmailCode).toHaveBeenCalledTimes(1);
|
|
expect(result).toEqual({ expiredAt: mockTimestamp });
|
|
});
|
|
|
|
it('should handle sendEmailCode errors', async () => {
|
|
const dto = new EmailVerificationMailingRequestDto();
|
|
dto.email = faker.internet.email();
|
|
const error = new InternalServerErrorException(
|
|
'Email service unavailable',
|
|
);
|
|
|
|
authService.sendEmailCode.mockRejectedValue(error);
|
|
|
|
await expect(authController.sendCode(dto)).rejects.toThrow(
|
|
InternalServerErrorException,
|
|
);
|
|
expect(authService.sendEmailCode).toHaveBeenCalledWith(dto);
|
|
});
|
|
|
|
it('should handle invalid email format', async () => {
|
|
const dto = new EmailVerificationMailingRequestDto();
|
|
dto.email = 'invalid-email';
|
|
const error = new BadRequestException('Invalid email format');
|
|
|
|
authService.sendEmailCode.mockRejectedValue(error);
|
|
|
|
await expect(authController.sendCode(dto)).rejects.toThrow(
|
|
BadRequestException,
|
|
);
|
|
});
|
|
});
|
|
describe('verifyEmailCode', () => {
|
|
it('should verify email code successfully', async () => {
|
|
const dto = new EmailVerificationCodeRequestDto();
|
|
dto.code = faker.string.alphanumeric(6);
|
|
dto.email = faker.internet.email();
|
|
|
|
authService.verifyEmailCode.mockResolvedValue(undefined);
|
|
|
|
await authController.verifyEmailCode(dto);
|
|
|
|
expect(authService.verifyEmailCode).toHaveBeenCalledWith(dto);
|
|
expect(authService.verifyEmailCode).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('should handle invalid verification code', async () => {
|
|
const dto = new EmailVerificationCodeRequestDto();
|
|
dto.code = 'invalid-code';
|
|
dto.email = faker.internet.email();
|
|
const error = new BadRequestException('Invalid verification code');
|
|
|
|
authService.verifyEmailCode.mockRejectedValue(error);
|
|
|
|
await expect(authController.verifyEmailCode(dto)).rejects.toThrow(
|
|
BadRequestException,
|
|
);
|
|
expect(authService.verifyEmailCode).toHaveBeenCalledWith(dto);
|
|
});
|
|
|
|
it('should handle expired verification code', async () => {
|
|
const dto = new EmailVerificationCodeRequestDto();
|
|
dto.code = faker.string.alphanumeric(6);
|
|
dto.email = faker.internet.email();
|
|
const error = new BadRequestException('Verification code expired');
|
|
|
|
authService.verifyEmailCode.mockRejectedValue(error);
|
|
|
|
await expect(authController.verifyEmailCode(dto)).rejects.toThrow(
|
|
BadRequestException,
|
|
);
|
|
});
|
|
});
|
|
describe('signUpEmailUser', () => {
|
|
it('should sign up email user successfully', async () => {
|
|
const dto = new EmailUserSignUpRequestDto();
|
|
dto.email = faker.internet.email();
|
|
dto.password = faker.internet.password();
|
|
|
|
authService.signUpEmailUser.mockResolvedValue(undefined as any);
|
|
|
|
const result = await authController.signUpEmailUser(dto);
|
|
|
|
expect(authService.signUpEmailUser).toHaveBeenCalledWith(dto);
|
|
expect(authService.signUpEmailUser).toHaveBeenCalledTimes(1);
|
|
expect(result).toBeUndefined();
|
|
});
|
|
|
|
it('should handle email already exists error', async () => {
|
|
const dto = new EmailUserSignUpRequestDto();
|
|
dto.email = faker.internet.email();
|
|
dto.password = faker.internet.password();
|
|
const error = new BadRequestException('Email already exists');
|
|
|
|
authService.signUpEmailUser.mockRejectedValue(error);
|
|
|
|
await expect(authController.signUpEmailUser(dto)).rejects.toThrow(
|
|
BadRequestException,
|
|
);
|
|
expect(authService.signUpEmailUser).toHaveBeenCalledWith(dto);
|
|
});
|
|
|
|
it('should handle weak password error', async () => {
|
|
const dto = new EmailUserSignUpRequestDto();
|
|
dto.email = faker.internet.email();
|
|
dto.password = '123'; // Weak password
|
|
const error = new BadRequestException('Password is too weak');
|
|
|
|
authService.signUpEmailUser.mockRejectedValue(error);
|
|
|
|
await expect(authController.signUpEmailUser(dto)).rejects.toThrow(
|
|
BadRequestException,
|
|
);
|
|
});
|
|
});
|
|
describe('signUpInvitationUser', () => {
|
|
it('should sign up invitation user successfully', async () => {
|
|
const dto = new InvitationUserSignUpRequestDto();
|
|
dto.code = faker.string.alphanumeric(8);
|
|
dto.email = faker.internet.email();
|
|
dto.password = faker.internet.password();
|
|
|
|
authService.signUpInvitationUser.mockResolvedValue(undefined as any);
|
|
|
|
const result = await authController.signUpInvitationUser(dto);
|
|
|
|
expect(authService.signUpInvitationUser).toHaveBeenCalledWith(dto);
|
|
expect(authService.signUpInvitationUser).toHaveBeenCalledTimes(1);
|
|
expect(result).toBeUndefined();
|
|
});
|
|
|
|
it('should handle invalid invitation code', async () => {
|
|
const dto = new InvitationUserSignUpRequestDto();
|
|
dto.code = 'invalid-code';
|
|
dto.email = faker.internet.email();
|
|
dto.password = faker.internet.password();
|
|
const error = new BadRequestException('Invalid invitation code');
|
|
|
|
authService.signUpInvitationUser.mockRejectedValue(error);
|
|
|
|
await expect(authController.signUpInvitationUser(dto)).rejects.toThrow(
|
|
BadRequestException,
|
|
);
|
|
expect(authService.signUpInvitationUser).toHaveBeenCalledWith(dto);
|
|
});
|
|
|
|
it('should handle expired invitation', async () => {
|
|
const dto = new InvitationUserSignUpRequestDto();
|
|
dto.code = faker.string.alphanumeric(8);
|
|
dto.email = faker.internet.email();
|
|
dto.password = faker.internet.password();
|
|
const error = new BadRequestException('Invitation has expired');
|
|
|
|
authService.signUpInvitationUser.mockRejectedValue(error);
|
|
|
|
await expect(authController.signUpInvitationUser(dto)).rejects.toThrow(
|
|
BadRequestException,
|
|
);
|
|
});
|
|
});
|
|
describe('signInEmail', () => {
|
|
it('should sign in email user successfully', () => {
|
|
const user = new UserDto();
|
|
user.id = faker.number.int();
|
|
user.email = faker.internet.email();
|
|
user.name = faker.person.fullName();
|
|
const mockTokens = {
|
|
accessToken: faker.string.alphanumeric(32),
|
|
refreshToken: faker.string.alphanumeric(32),
|
|
};
|
|
|
|
authService.signIn.mockReturnValue(mockTokens as any);
|
|
|
|
const result = authController.signInEmail(user);
|
|
|
|
expect(authService.signIn).toHaveBeenCalledWith(user);
|
|
expect(authService.signIn).toHaveBeenCalledTimes(1);
|
|
expect(result).toEqual(mockTokens);
|
|
});
|
|
});
|
|
|
|
describe('refreshToken', () => {
|
|
it('should refresh token successfully', () => {
|
|
const user = new UserDto();
|
|
user.id = faker.number.int();
|
|
user.email = faker.internet.email();
|
|
user.name = faker.person.fullName();
|
|
const mockTokens = {
|
|
accessToken: faker.string.alphanumeric(32),
|
|
refreshToken: faker.string.alphanumeric(32),
|
|
};
|
|
|
|
authService.refreshToken.mockReturnValue(mockTokens as any);
|
|
|
|
const result = authController.refreshToken(user);
|
|
|
|
expect(authService.refreshToken).toHaveBeenCalledWith(user);
|
|
expect(authService.refreshToken).toHaveBeenCalledTimes(1);
|
|
expect(result).toEqual(mockTokens);
|
|
});
|
|
});
|
|
|
|
describe('signUpOAuthUser', () => {
|
|
it('should sign up OAuth user successfully', async () => {
|
|
const dto = new OAuthUserSignUpRequestDto();
|
|
dto.email = faker.internet.email();
|
|
dto.projectName = faker.company.name();
|
|
dto.roleName = faker.person.jobTitle();
|
|
|
|
authService.signUpOAuthUser.mockResolvedValue(undefined);
|
|
|
|
const result = await authController.signUpOAuthUser(dto);
|
|
|
|
expect(authService.signUpOAuthUser).toHaveBeenCalledWith(dto);
|
|
expect(authService.signUpOAuthUser).toHaveBeenCalledTimes(1);
|
|
expect(result).toBeUndefined();
|
|
});
|
|
|
|
it('should handle OAuth provider error', async () => {
|
|
const dto = new OAuthUserSignUpRequestDto();
|
|
dto.email = faker.internet.email();
|
|
dto.projectName = faker.company.name();
|
|
dto.roleName = faker.person.jobTitle();
|
|
const error = new InternalServerErrorException('OAuth provider error');
|
|
|
|
authService.signUpOAuthUser.mockRejectedValue(error);
|
|
|
|
await expect(authController.signUpOAuthUser(dto)).rejects.toThrow(
|
|
InternalServerErrorException,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('redirectToLoginURL', () => {
|
|
it('should return OAuth login URL', async () => {
|
|
const callbackUrl = faker.internet.url();
|
|
const mockUrl = faker.internet.url();
|
|
|
|
authService.getOAuthLoginURL.mockResolvedValue(mockUrl);
|
|
|
|
const result = await authController.redirectToLoginURL(callbackUrl);
|
|
|
|
expect(authService.getOAuthLoginURL).toHaveBeenCalledWith(callbackUrl);
|
|
expect(authService.getOAuthLoginURL).toHaveBeenCalledTimes(1);
|
|
expect(result).toEqual({ url: mockUrl });
|
|
});
|
|
});
|
|
|
|
describe('handleCallback', () => {
|
|
it('should handle OAuth callback successfully', async () => {
|
|
const query = { code: faker.string.alphanumeric(32) };
|
|
const mockTokens = {
|
|
accessToken: faker.string.alphanumeric(32),
|
|
refreshToken: faker.string.alphanumeric(32),
|
|
};
|
|
|
|
authService.signInByOAuth.mockResolvedValue(mockTokens);
|
|
|
|
const result = await authController.handleCallback(query);
|
|
|
|
expect(authService.signInByOAuth).toHaveBeenCalledWith(query.code);
|
|
expect(authService.signInByOAuth).toHaveBeenCalledTimes(1);
|
|
expect(result).toEqual(mockTokens);
|
|
});
|
|
|
|
it('should handle OAuth authentication failure', async () => {
|
|
const query = { code: 'invalid-code' };
|
|
const error = new BadRequestException('OAuth authentication failed');
|
|
|
|
authService.signInByOAuth.mockRejectedValue(error);
|
|
|
|
await expect(authController.handleCallback(query)).rejects.toThrow(
|
|
BadRequestException,
|
|
);
|
|
});
|
|
});
|
|
});
|