2025-10-21 10:42:59 +00:00
|
|
|
import { Test, TestingModule } from '@nestjs/testing';
|
2025-10-27 06:41:51 +00:00
|
|
|
import { AuthController } from './auth.controller';
|
2025-12-02 06:19:03 +00:00
|
|
|
import { AuthService } from './auth.service';
|
|
|
|
|
import { ConfigService } from '@nestjs/config';
|
|
|
|
|
import { JwtService } from '@nestjs/jwt';
|
|
|
|
|
import { UserRole } from './dto/auth.dto';
|
2025-10-21 10:42:59 +00:00
|
|
|
|
2025-10-27 06:41:51 +00:00
|
|
|
describe('AuthController', () => {
|
|
|
|
|
let controller: AuthController;
|
2025-12-02 06:19:03 +00:00
|
|
|
let authService: jest.Mocked<AuthService>;
|
|
|
|
|
let configService: jest.Mocked<ConfigService>;
|
|
|
|
|
|
|
|
|
|
const mockAuthService = {
|
|
|
|
|
registerUser: jest.fn(),
|
|
|
|
|
signIn: jest.fn(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const mockConfigService = {
|
|
|
|
|
get: jest.fn(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const mockJwtService = {
|
|
|
|
|
signAsync: jest.fn(),
|
|
|
|
|
verifyAsync: jest.fn(),
|
|
|
|
|
};
|
2025-10-21 10:42:59 +00:00
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
2025-12-02 06:19:03 +00:00
|
|
|
jest.clearAllMocks();
|
|
|
|
|
|
2025-10-21 10:42:59 +00:00
|
|
|
const module: TestingModule = await Test.createTestingModule({
|
2025-10-27 06:41:51 +00:00
|
|
|
controllers: [AuthController],
|
2025-12-02 06:19:03 +00:00
|
|
|
providers: [
|
|
|
|
|
{ provide: AuthService, useValue: mockAuthService },
|
|
|
|
|
{ provide: ConfigService, useValue: mockConfigService },
|
|
|
|
|
{ provide: JwtService, useValue: mockJwtService },
|
|
|
|
|
],
|
2025-10-21 10:42:59 +00:00
|
|
|
}).compile();
|
|
|
|
|
|
2025-10-27 06:41:51 +00:00
|
|
|
controller = module.get<AuthController>(AuthController);
|
2025-12-02 06:19:03 +00:00
|
|
|
authService = module.get(AuthService);
|
|
|
|
|
configService = module.get(ConfigService);
|
2025-10-21 10:42:59 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should be defined', () => {
|
|
|
|
|
expect(controller).toBeDefined();
|
|
|
|
|
});
|
2025-12-02 06:19:03 +00:00
|
|
|
|
|
|
|
|
describe('registerUser', () => {
|
|
|
|
|
const createUserDto = {
|
|
|
|
|
nama_lengkap: 'Test User',
|
|
|
|
|
username: 'testuser',
|
|
|
|
|
password: 'password123',
|
|
|
|
|
role: UserRole.User,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const expectedResponse = {
|
|
|
|
|
id: BigInt(1),
|
|
|
|
|
nama_lengkap: 'Test User',
|
|
|
|
|
username: 'testuser',
|
|
|
|
|
role: UserRole.User,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
it('should register a new user', async () => {
|
|
|
|
|
mockAuthService.registerUser.mockResolvedValue(expectedResponse);
|
|
|
|
|
|
|
|
|
|
const result = await controller.registerUser(createUserDto);
|
|
|
|
|
|
|
|
|
|
expect(result).toEqual(expectedResponse);
|
|
|
|
|
expect(mockAuthService.registerUser).toHaveBeenCalledWith(createUserDto);
|
|
|
|
|
expect(mockAuthService.registerUser).toHaveBeenCalledTimes(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should propagate service errors', async () => {
|
|
|
|
|
const error = new Error('Service error');
|
|
|
|
|
mockAuthService.registerUser.mockRejectedValue(error);
|
|
|
|
|
|
|
|
|
|
await expect(controller.registerUser(createUserDto)).rejects.toThrow(
|
|
|
|
|
'Service error',
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('login', () => {
|
|
|
|
|
const loginDto = {
|
|
|
|
|
username: 'testuser',
|
|
|
|
|
password: 'password123',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const mockSignInResponse = {
|
|
|
|
|
accessToken: 'jwt-token',
|
|
|
|
|
csrfToken: 'csrf-token',
|
|
|
|
|
user: {
|
|
|
|
|
id: BigInt(1),
|
|
|
|
|
username: 'testuser',
|
|
|
|
|
role: 'user',
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
it('should login user and set cookie in development mode', async () => {
|
|
|
|
|
mockAuthService.signIn.mockResolvedValue(mockSignInResponse);
|
2025-12-03 05:57:02 +00:00
|
|
|
mockConfigService.get.mockImplementation((key: string) => {
|
|
|
|
|
if (key === 'NODE_ENV') return 'development';
|
|
|
|
|
if (key === 'COOKIE_MAX_AGE') return '3600000';
|
|
|
|
|
return undefined;
|
|
|
|
|
});
|
2025-12-02 06:19:03 +00:00
|
|
|
|
|
|
|
|
const mockResponse = {
|
|
|
|
|
cookie: jest.fn(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const result = await controller.login(loginDto, mockResponse as any);
|
|
|
|
|
|
|
|
|
|
expect(result).toEqual({
|
|
|
|
|
user: mockSignInResponse.user,
|
|
|
|
|
csrfToken: mockSignInResponse.csrfToken,
|
|
|
|
|
});
|
|
|
|
|
expect(mockResponse.cookie).toHaveBeenCalledWith(
|
|
|
|
|
'access_token',
|
|
|
|
|
'jwt-token',
|
|
|
|
|
{
|
|
|
|
|
httpOnly: true,
|
|
|
|
|
secure: false, // development mode
|
|
|
|
|
sameSite: 'strict',
|
|
|
|
|
maxAge: 3600000,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should login user and set secure cookie in production mode', async () => {
|
|
|
|
|
mockAuthService.signIn.mockResolvedValue(mockSignInResponse);
|
2025-12-03 05:57:02 +00:00
|
|
|
mockConfigService.get.mockImplementation((key: string) => {
|
|
|
|
|
if (key === 'NODE_ENV') return 'production';
|
|
|
|
|
if (key === 'COOKIE_MAX_AGE') return '3600000';
|
|
|
|
|
return undefined;
|
|
|
|
|
});
|
2025-12-02 06:19:03 +00:00
|
|
|
|
|
|
|
|
const mockResponse = {
|
|
|
|
|
cookie: jest.fn(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await controller.login(loginDto, mockResponse as any);
|
|
|
|
|
|
|
|
|
|
expect(mockResponse.cookie).toHaveBeenCalledWith(
|
|
|
|
|
'access_token',
|
|
|
|
|
'jwt-token',
|
|
|
|
|
{
|
|
|
|
|
httpOnly: true,
|
|
|
|
|
secure: true, // production mode
|
|
|
|
|
sameSite: 'strict',
|
|
|
|
|
maxAge: 3600000,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should propagate authentication errors', async () => {
|
|
|
|
|
mockAuthService.signIn.mockRejectedValue(
|
|
|
|
|
new Error('Invalid credentials'),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const mockResponse = {
|
|
|
|
|
cookie: jest.fn(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await expect(
|
|
|
|
|
controller.login(loginDto, mockResponse as any),
|
|
|
|
|
).rejects.toThrow('Invalid credentials');
|
|
|
|
|
expect(mockResponse.cookie).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('logout', () => {
|
|
|
|
|
it('should clear access_token cookie in development mode', () => {
|
|
|
|
|
mockConfigService.get.mockReturnValue('development');
|
|
|
|
|
|
|
|
|
|
const mockResponse = {
|
|
|
|
|
clearCookie: jest.fn(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const result = controller.logout(mockResponse as any);
|
|
|
|
|
|
2025-12-04 04:10:10 +00:00
|
|
|
expect(result).toEqual({ message: 'Logout successful' });
|
2025-12-02 06:19:03 +00:00
|
|
|
expect(mockResponse.clearCookie).toHaveBeenCalledWith('access_token', {
|
|
|
|
|
httpOnly: true,
|
|
|
|
|
secure: false,
|
|
|
|
|
sameSite: 'strict',
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should clear access_token cookie with secure flag in production mode', () => {
|
|
|
|
|
mockConfigService.get.mockReturnValue('production');
|
|
|
|
|
|
|
|
|
|
const mockResponse = {
|
|
|
|
|
clearCookie: jest.fn(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const result = controller.logout(mockResponse as any);
|
|
|
|
|
|
2025-12-04 04:10:10 +00:00
|
|
|
expect(result).toEqual({ message: 'Logout successful' });
|
2025-12-02 06:19:03 +00:00
|
|
|
expect(mockResponse.clearCookie).toHaveBeenCalledWith('access_token', {
|
|
|
|
|
httpOnly: true,
|
|
|
|
|
secure: true,
|
|
|
|
|
sameSite: 'strict',
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
2025-10-21 10:42:59 +00:00
|
|
|
});
|