test: add comprehensive unit tests for groups and auth modules
Added unit tests for `GroupsService`, `GroupsController`, `AuthService`, `AuthController`, and `JwtAuthGuard` to ensure functionality and coverage. Includes tests for entity operations, exception handling, and integration scenarios.
This commit is contained in:
parent
04aba190ed
commit
7eae25d5de
117
backend/src/modules/auth/controllers/auth.controller.spec.ts
Normal file
117
backend/src/modules/auth/controllers/auth.controller.spec.ts
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { UnauthorizedException } from '@nestjs/common';
|
||||||
|
import { AuthController } from './auth.controller';
|
||||||
|
import { AuthService } from '../services/auth.service';
|
||||||
|
|
||||||
|
describe('AuthController', () => {
|
||||||
|
let controller: AuthController;
|
||||||
|
let authService: AuthService;
|
||||||
|
let configService: ConfigService;
|
||||||
|
|
||||||
|
const mockAuthService = {
|
||||||
|
validateGithubUser: jest.fn(),
|
||||||
|
generateTokens: jest.fn(),
|
||||||
|
refreshTokens: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockConfigService = {
|
||||||
|
get: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [AuthController],
|
||||||
|
providers: [
|
||||||
|
{ provide: AuthService, useValue: mockAuthService },
|
||||||
|
{ provide: ConfigService, useValue: mockConfigService },
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
controller = module.get<AuthController>(AuthController);
|
||||||
|
authService = module.get<AuthService>(AuthService);
|
||||||
|
configService = module.get<ConfigService>(ConfigService);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(controller).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('githubAuth', () => {
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(controller.githubAuth).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('githubAuthCallback', () => {
|
||||||
|
it('should redirect to frontend with tokens', async () => {
|
||||||
|
const req = {
|
||||||
|
user: { id: 'user1', name: 'Test User' },
|
||||||
|
};
|
||||||
|
const res = {
|
||||||
|
redirect: jest.fn(),
|
||||||
|
};
|
||||||
|
const tokens = {
|
||||||
|
accessToken: 'access-token',
|
||||||
|
refreshToken: 'refresh-token',
|
||||||
|
};
|
||||||
|
const frontendUrl = 'http://localhost:3001';
|
||||||
|
const expectedRedirectUrl = `${frontendUrl}/auth/callback?accessToken=${tokens.accessToken}&refreshToken=${tokens.refreshToken}`;
|
||||||
|
|
||||||
|
mockAuthService.generateTokens.mockResolvedValue(tokens);
|
||||||
|
mockConfigService.get.mockReturnValue(frontendUrl);
|
||||||
|
|
||||||
|
await controller.githubAuthCallback(req as any, res as any);
|
||||||
|
|
||||||
|
expect(mockAuthService.generateTokens).toHaveBeenCalledWith('user1');
|
||||||
|
expect(mockConfigService.get).toHaveBeenCalledWith('FRONTEND_URL');
|
||||||
|
expect(res.redirect).toHaveBeenCalledWith(expectedRedirectUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw UnauthorizedException if user is not provided', async () => {
|
||||||
|
const req = {};
|
||||||
|
const res = {
|
||||||
|
redirect: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(controller.githubAuthCallback(req as any, res as any)).rejects.toThrow(
|
||||||
|
UnauthorizedException,
|
||||||
|
);
|
||||||
|
expect(res.redirect).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('refreshTokens', () => {
|
||||||
|
it('should refresh tokens', async () => {
|
||||||
|
const user = {
|
||||||
|
id: 'user1',
|
||||||
|
refreshToken: 'refresh-token',
|
||||||
|
};
|
||||||
|
const tokens = {
|
||||||
|
accessToken: 'new-access-token',
|
||||||
|
refreshToken: 'new-refresh-token',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockAuthService.refreshTokens.mockResolvedValue(tokens);
|
||||||
|
|
||||||
|
const result = await controller.refreshTokens(user);
|
||||||
|
|
||||||
|
expect(mockAuthService.refreshTokens).toHaveBeenCalledWith('user1', 'refresh-token');
|
||||||
|
expect(result).toEqual(tokens);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getProfile', () => {
|
||||||
|
it('should return user profile', () => {
|
||||||
|
const user = { id: 'user1', name: 'Test User' };
|
||||||
|
|
||||||
|
const result = controller.getProfile(user);
|
||||||
|
|
||||||
|
expect(result).toEqual(user);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
82
backend/src/modules/auth/guards/jwt-auth.guard.spec.ts
Normal file
82
backend/src/modules/auth/guards/jwt-auth.guard.spec.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { ExecutionContext, UnauthorizedException } from '@nestjs/common';
|
||||||
|
import { Reflector } from '@nestjs/core';
|
||||||
|
import { JwtAuthGuard } from './jwt-auth.guard';
|
||||||
|
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
|
||||||
|
|
||||||
|
describe('JwtAuthGuard', () => {
|
||||||
|
let guard: JwtAuthGuard;
|
||||||
|
let reflector: Reflector;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
reflector = new Reflector();
|
||||||
|
guard = new JwtAuthGuard(reflector);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('canActivate', () => {
|
||||||
|
it('should return true if the route is public', () => {
|
||||||
|
const context = {
|
||||||
|
getHandler: jest.fn(),
|
||||||
|
getClass: jest.fn(),
|
||||||
|
switchToHttp: jest.fn().mockReturnValue({
|
||||||
|
getRequest: jest.fn().mockReturnValue({}),
|
||||||
|
getResponse: jest.fn().mockReturnValue({}),
|
||||||
|
}),
|
||||||
|
} as unknown as ExecutionContext;
|
||||||
|
|
||||||
|
jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(true);
|
||||||
|
|
||||||
|
expect(guard.canActivate(context)).toBe(true);
|
||||||
|
expect(reflector.getAllAndOverride).toHaveBeenCalledWith(IS_PUBLIC_KEY, [
|
||||||
|
context.getHandler(),
|
||||||
|
context.getClass(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call super.canActivate if the route is not public', () => {
|
||||||
|
const context = {
|
||||||
|
getHandler: jest.fn(),
|
||||||
|
getClass: jest.fn(),
|
||||||
|
switchToHttp: jest.fn().mockReturnValue({
|
||||||
|
getRequest: jest.fn().mockReturnValue({}),
|
||||||
|
getResponse: jest.fn().mockReturnValue({}),
|
||||||
|
}),
|
||||||
|
} as unknown as ExecutionContext;
|
||||||
|
|
||||||
|
jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(false);
|
||||||
|
|
||||||
|
// Mock the AuthGuard's canActivate method
|
||||||
|
const canActivateSpy = jest.spyOn(guard, 'canActivate');
|
||||||
|
|
||||||
|
// We can't easily test the super.canActivate call directly,
|
||||||
|
// so we'll just verify our method was called with the right context
|
||||||
|
guard.canActivate(context);
|
||||||
|
|
||||||
|
expect(reflector.getAllAndOverride).toHaveBeenCalledWith(IS_PUBLIC_KEY, [
|
||||||
|
context.getHandler(),
|
||||||
|
context.getClass(),
|
||||||
|
]);
|
||||||
|
expect(canActivateSpy).toHaveBeenCalledWith(context);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('handleRequest', () => {
|
||||||
|
it('should return the user if no error and user exists', () => {
|
||||||
|
const user = { id: 'user1', name: 'Test User' };
|
||||||
|
|
||||||
|
const result = guard.handleRequest(null, user, null);
|
||||||
|
|
||||||
|
expect(result).toBe(user);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw the error if an error exists', () => {
|
||||||
|
const error = new Error('Test error');
|
||||||
|
|
||||||
|
expect(() => guard.handleRequest(error, null, null)).toThrow(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw UnauthorizedException if no error but user does not exist', () => {
|
||||||
|
expect(() => guard.handleRequest(null, null, null)).toThrow(UnauthorizedException);
|
||||||
|
expect(() => guard.handleRequest(null, null, null)).toThrow('Authentication required');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
208
backend/src/modules/auth/services/auth.service.spec.ts
Normal file
208
backend/src/modules/auth/services/auth.service.spec.ts
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { AuthService } from './auth.service';
|
||||||
|
import { UsersService } from '../../users/services/users.service';
|
||||||
|
import { UnauthorizedException } from '@nestjs/common';
|
||||||
|
|
||||||
|
describe('AuthService', () => {
|
||||||
|
let service: AuthService;
|
||||||
|
let usersService: UsersService;
|
||||||
|
let jwtService: JwtService;
|
||||||
|
let configService: ConfigService;
|
||||||
|
|
||||||
|
const mockUsersService = {
|
||||||
|
findByGithubId: jest.fn(),
|
||||||
|
findById: jest.fn(),
|
||||||
|
create: jest.fn(),
|
||||||
|
update: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockJwtService = {
|
||||||
|
signAsync: jest.fn(),
|
||||||
|
verifyAsync: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockConfigService = {
|
||||||
|
get: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
AuthService,
|
||||||
|
{ provide: UsersService, useValue: mockUsersService },
|
||||||
|
{ provide: JwtService, useValue: mockJwtService },
|
||||||
|
{ provide: ConfigService, useValue: mockConfigService },
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<AuthService>(AuthService);
|
||||||
|
usersService = module.get<UsersService>(UsersService);
|
||||||
|
jwtService = module.get<JwtService>(JwtService);
|
||||||
|
configService = module.get<ConfigService>(ConfigService);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validateGithubUser', () => {
|
||||||
|
it('should create a new user if one does not exist', async () => {
|
||||||
|
const githubId = 'github123';
|
||||||
|
const email = 'test@example.com';
|
||||||
|
const name = 'Test User';
|
||||||
|
const avatarUrl = 'https://example.com/avatar.jpg';
|
||||||
|
const newUser = { id: 'user1', githubId, name, avatar: avatarUrl };
|
||||||
|
|
||||||
|
mockUsersService.findByGithubId.mockResolvedValue(null);
|
||||||
|
mockUsersService.create.mockResolvedValue(newUser);
|
||||||
|
|
||||||
|
const result = await service.validateGithubUser(githubId, email, name, avatarUrl);
|
||||||
|
|
||||||
|
expect(mockUsersService.findByGithubId).toHaveBeenCalledWith(githubId);
|
||||||
|
expect(mockUsersService.create).toHaveBeenCalledWith({
|
||||||
|
githubId,
|
||||||
|
name,
|
||||||
|
avatar: avatarUrl,
|
||||||
|
metadata: { email },
|
||||||
|
});
|
||||||
|
expect(result).toEqual(newUser);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return an existing user if one exists', async () => {
|
||||||
|
const githubId = 'github123';
|
||||||
|
const email = 'test@example.com';
|
||||||
|
const name = 'Test User';
|
||||||
|
const avatarUrl = 'https://example.com/avatar.jpg';
|
||||||
|
const existingUser = { id: 'user1', githubId, name, avatar: avatarUrl };
|
||||||
|
|
||||||
|
mockUsersService.findByGithubId.mockResolvedValue(existingUser);
|
||||||
|
|
||||||
|
const result = await service.validateGithubUser(githubId, email, name, avatarUrl);
|
||||||
|
|
||||||
|
expect(mockUsersService.findByGithubId).toHaveBeenCalledWith(githubId);
|
||||||
|
expect(mockUsersService.create).not.toHaveBeenCalled();
|
||||||
|
expect(result).toEqual(existingUser);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('generateTokens', () => {
|
||||||
|
it('should generate access and refresh tokens', async () => {
|
||||||
|
const userId = 'user1';
|
||||||
|
const accessToken = 'access-token';
|
||||||
|
const refreshToken = 'refresh-token';
|
||||||
|
const refreshExpiration = '7d';
|
||||||
|
const refreshSecret = 'refresh-secret';
|
||||||
|
|
||||||
|
mockJwtService.signAsync.mockResolvedValueOnce(accessToken);
|
||||||
|
mockJwtService.signAsync.mockResolvedValueOnce(refreshToken);
|
||||||
|
mockConfigService.get.mockReturnValueOnce(refreshExpiration);
|
||||||
|
mockConfigService.get.mockReturnValueOnce(refreshSecret);
|
||||||
|
|
||||||
|
const result = await service.generateTokens(userId);
|
||||||
|
|
||||||
|
expect(mockJwtService.signAsync).toHaveBeenCalledWith({ sub: userId });
|
||||||
|
expect(mockJwtService.signAsync).toHaveBeenCalledWith(
|
||||||
|
{ sub: userId, isRefreshToken: true },
|
||||||
|
{
|
||||||
|
expiresIn: refreshExpiration,
|
||||||
|
secret: refreshSecret,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(result).toEqual({
|
||||||
|
accessToken,
|
||||||
|
refreshToken,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('refreshTokens', () => {
|
||||||
|
it('should refresh tokens if refresh token is valid', async () => {
|
||||||
|
const userId = 'user1';
|
||||||
|
const refreshToken = 'valid-refresh-token';
|
||||||
|
const newAccessToken = 'new-access-token';
|
||||||
|
const newRefreshToken = 'new-refresh-token';
|
||||||
|
const payload = { sub: userId, isRefreshToken: true };
|
||||||
|
|
||||||
|
mockJwtService.verifyAsync.mockResolvedValue(payload);
|
||||||
|
mockJwtService.signAsync.mockResolvedValueOnce(newAccessToken);
|
||||||
|
mockJwtService.signAsync.mockResolvedValueOnce(newRefreshToken);
|
||||||
|
|
||||||
|
const result = await service.refreshTokens(userId, refreshToken);
|
||||||
|
|
||||||
|
expect(mockJwtService.verifyAsync).toHaveBeenCalledWith(refreshToken, {
|
||||||
|
secret: undefined,
|
||||||
|
});
|
||||||
|
expect(result).toEqual({
|
||||||
|
accessToken: newAccessToken,
|
||||||
|
refreshToken: newRefreshToken,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw UnauthorizedException if refresh token is invalid', async () => {
|
||||||
|
const userId = 'user1';
|
||||||
|
const refreshToken = 'invalid-refresh-token';
|
||||||
|
|
||||||
|
mockJwtService.verifyAsync.mockRejectedValue(new Error('Invalid token'));
|
||||||
|
|
||||||
|
await expect(service.refreshTokens(userId, refreshToken)).rejects.toThrow(
|
||||||
|
UnauthorizedException,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw UnauthorizedException if token is not a refresh token', async () => {
|
||||||
|
const userId = 'user1';
|
||||||
|
const refreshToken = 'not-a-refresh-token';
|
||||||
|
const payload = { sub: userId }; // Missing isRefreshToken: true
|
||||||
|
|
||||||
|
mockJwtService.verifyAsync.mockResolvedValue(payload);
|
||||||
|
|
||||||
|
await expect(service.refreshTokens(userId, refreshToken)).rejects.toThrow(
|
||||||
|
UnauthorizedException,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw UnauthorizedException if user ID does not match', async () => {
|
||||||
|
const userId = 'user1';
|
||||||
|
const refreshToken = 'wrong-user-token';
|
||||||
|
const payload = { sub: 'user2', isRefreshToken: true }; // Different user ID
|
||||||
|
|
||||||
|
mockJwtService.verifyAsync.mockResolvedValue(payload);
|
||||||
|
|
||||||
|
await expect(service.refreshTokens(userId, refreshToken)).rejects.toThrow(
|
||||||
|
UnauthorizedException,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validateJwtUser', () => {
|
||||||
|
it('should return user if user exists', async () => {
|
||||||
|
const userId = 'user1';
|
||||||
|
const user = { id: userId, name: 'Test User' };
|
||||||
|
const payload = { sub: userId };
|
||||||
|
|
||||||
|
mockUsersService.findById.mockResolvedValue(user);
|
||||||
|
|
||||||
|
const result = await service.validateJwtUser(payload);
|
||||||
|
|
||||||
|
expect(mockUsersService.findById).toHaveBeenCalledWith(userId);
|
||||||
|
expect(result).toEqual(user);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw UnauthorizedException if user does not exist', async () => {
|
||||||
|
const userId = 'nonexistent';
|
||||||
|
const payload = { sub: userId };
|
||||||
|
|
||||||
|
mockUsersService.findById.mockResolvedValue(null);
|
||||||
|
|
||||||
|
await expect(service.validateJwtUser(payload)).rejects.toThrow(
|
||||||
|
UnauthorizedException,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
194
backend/src/modules/groups/controllers/groups.controller.spec.ts
Normal file
194
backend/src/modules/groups/controllers/groups.controller.spec.ts
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { GroupsController } from './groups.controller';
|
||||||
|
import { GroupsService } from '../services/groups.service';
|
||||||
|
import { CreateGroupDto } from '../dto/create-group.dto';
|
||||||
|
import { UpdateGroupDto } from '../dto/update-group.dto';
|
||||||
|
|
||||||
|
describe('GroupsController', () => {
|
||||||
|
let controller: GroupsController;
|
||||||
|
let service: GroupsService;
|
||||||
|
|
||||||
|
// Mock data
|
||||||
|
const mockGroup = {
|
||||||
|
id: 'group1',
|
||||||
|
name: 'Test Group',
|
||||||
|
projectId: 'project1',
|
||||||
|
metadata: {},
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockPerson = {
|
||||||
|
id: 'person1',
|
||||||
|
name: 'Test Person',
|
||||||
|
projectId: 'project1',
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mock service
|
||||||
|
const mockGroupsService = {
|
||||||
|
create: jest.fn(),
|
||||||
|
findAll: jest.fn(),
|
||||||
|
findByProjectId: jest.fn(),
|
||||||
|
findById: jest.fn(),
|
||||||
|
update: jest.fn(),
|
||||||
|
remove: jest.fn(),
|
||||||
|
addPersonToGroup: jest.fn(),
|
||||||
|
removePersonFromGroup: jest.fn(),
|
||||||
|
getPersonsInGroup: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [GroupsController],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: GroupsService,
|
||||||
|
useValue: mockGroupsService,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
controller = module.get<GroupsController>(GroupsController);
|
||||||
|
service = module.get<GroupsService>(GroupsService);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(controller).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('create', () => {
|
||||||
|
it('should create a new group', async () => {
|
||||||
|
const createGroupDto: CreateGroupDto = {
|
||||||
|
name: 'Test Group',
|
||||||
|
projectId: 'project1',
|
||||||
|
metadata: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
mockGroupsService.create.mockResolvedValue(mockGroup);
|
||||||
|
|
||||||
|
const result = await controller.create(createGroupDto);
|
||||||
|
|
||||||
|
expect(mockGroupsService.create).toHaveBeenCalledWith(createGroupDto);
|
||||||
|
expect(result).toEqual(mockGroup);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findAll', () => {
|
||||||
|
it('should return all groups when no projectId is provided', async () => {
|
||||||
|
mockGroupsService.findAll.mockResolvedValue([mockGroup]);
|
||||||
|
|
||||||
|
const result = await controller.findAll();
|
||||||
|
|
||||||
|
expect(mockGroupsService.findAll).toHaveBeenCalled();
|
||||||
|
expect(mockGroupsService.findByProjectId).not.toHaveBeenCalled();
|
||||||
|
expect(result).toEqual([mockGroup]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return groups for a specific project when projectId is provided', async () => {
|
||||||
|
const projectId = 'project1';
|
||||||
|
mockGroupsService.findByProjectId.mockResolvedValue([mockGroup]);
|
||||||
|
|
||||||
|
const result = await controller.findAll(projectId);
|
||||||
|
|
||||||
|
expect(mockGroupsService.findByProjectId).toHaveBeenCalledWith(projectId);
|
||||||
|
expect(mockGroupsService.findAll).not.toHaveBeenCalled();
|
||||||
|
expect(result).toEqual([mockGroup]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findOne', () => {
|
||||||
|
it('should return a group by id', async () => {
|
||||||
|
const id = 'group1';
|
||||||
|
mockGroupsService.findById.mockResolvedValue(mockGroup);
|
||||||
|
|
||||||
|
const result = await controller.findOne(id);
|
||||||
|
|
||||||
|
expect(mockGroupsService.findById).toHaveBeenCalledWith(id);
|
||||||
|
expect(result).toEqual(mockGroup);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('update', () => {
|
||||||
|
it('should update a group', async () => {
|
||||||
|
const id = 'group1';
|
||||||
|
const updateGroupDto: UpdateGroupDto = {
|
||||||
|
name: 'Updated Group',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockGroupsService.update.mockResolvedValue({
|
||||||
|
...mockGroup,
|
||||||
|
name: 'Updated Group',
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await controller.update(id, updateGroupDto);
|
||||||
|
|
||||||
|
expect(mockGroupsService.update).toHaveBeenCalledWith(id, updateGroupDto);
|
||||||
|
expect(result).toEqual({
|
||||||
|
...mockGroup,
|
||||||
|
name: 'Updated Group',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('remove', () => {
|
||||||
|
it('should remove a group', async () => {
|
||||||
|
const id = 'group1';
|
||||||
|
mockGroupsService.remove.mockResolvedValue(mockGroup);
|
||||||
|
|
||||||
|
const result = await controller.remove(id);
|
||||||
|
|
||||||
|
expect(mockGroupsService.remove).toHaveBeenCalledWith(id);
|
||||||
|
expect(result).toEqual(mockGroup);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('addPersonToGroup', () => {
|
||||||
|
it('should add a person to a group', async () => {
|
||||||
|
const groupId = 'group1';
|
||||||
|
const personId = 'person1';
|
||||||
|
const mockRelation = { groupId, personId };
|
||||||
|
|
||||||
|
mockGroupsService.addPersonToGroup.mockResolvedValue(mockRelation);
|
||||||
|
|
||||||
|
const result = await controller.addPersonToGroup(groupId, personId);
|
||||||
|
|
||||||
|
expect(mockGroupsService.addPersonToGroup).toHaveBeenCalledWith(groupId, personId);
|
||||||
|
expect(result).toEqual(mockRelation);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('removePersonFromGroup', () => {
|
||||||
|
it('should remove a person from a group', async () => {
|
||||||
|
const groupId = 'group1';
|
||||||
|
const personId = 'person1';
|
||||||
|
const mockRelation = { groupId, personId };
|
||||||
|
|
||||||
|
mockGroupsService.removePersonFromGroup.mockResolvedValue(mockRelation);
|
||||||
|
|
||||||
|
const result = await controller.removePersonFromGroup(groupId, personId);
|
||||||
|
|
||||||
|
expect(mockGroupsService.removePersonFromGroup).toHaveBeenCalledWith(groupId, personId);
|
||||||
|
expect(result).toEqual(mockRelation);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getPersonsInGroup', () => {
|
||||||
|
it('should get all persons in a group', async () => {
|
||||||
|
const groupId = 'group1';
|
||||||
|
const mockPersons = [{ person: mockPerson }];
|
||||||
|
|
||||||
|
mockGroupsService.getPersonsInGroup.mockResolvedValue(mockPersons);
|
||||||
|
|
||||||
|
const result = await controller.getPersonsInGroup(groupId);
|
||||||
|
|
||||||
|
expect(mockGroupsService.getPersonsInGroup).toHaveBeenCalledWith(groupId);
|
||||||
|
expect(result).toEqual(mockPersons);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
317
backend/src/modules/groups/services/groups.service.spec.ts
Normal file
317
backend/src/modules/groups/services/groups.service.spec.ts
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { GroupsService } from './groups.service';
|
||||||
|
import { NotFoundException } from '@nestjs/common';
|
||||||
|
import { DRIZZLE } from '../../../database/database.module';
|
||||||
|
|
||||||
|
describe('GroupsService', () => {
|
||||||
|
let service: GroupsService;
|
||||||
|
let mockDb: any;
|
||||||
|
|
||||||
|
// Mock data
|
||||||
|
const mockGroup = {
|
||||||
|
id: 'group1',
|
||||||
|
name: 'Test Group',
|
||||||
|
projectId: 'project1',
|
||||||
|
metadata: {},
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockPerson = {
|
||||||
|
id: 'person1',
|
||||||
|
name: 'Test Person',
|
||||||
|
projectId: 'project1',
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockPersonToGroup = {
|
||||||
|
personId: 'person1',
|
||||||
|
groupId: 'group1',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mock database operations
|
||||||
|
const mockDbOperations = {
|
||||||
|
select: jest.fn().mockReturnThis(),
|
||||||
|
from: jest.fn().mockReturnThis(),
|
||||||
|
where: jest.fn().mockReturnThis(),
|
||||||
|
insert: jest.fn().mockReturnThis(),
|
||||||
|
values: jest.fn().mockReturnThis(),
|
||||||
|
update: jest.fn().mockReturnThis(),
|
||||||
|
set: jest.fn().mockReturnThis(),
|
||||||
|
delete: jest.fn().mockReturnThis(),
|
||||||
|
innerJoin: jest.fn().mockReturnThis(),
|
||||||
|
returning: jest.fn().mockImplementation(() => {
|
||||||
|
return [mockGroup];
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
mockDb = {
|
||||||
|
...mockDbOperations,
|
||||||
|
};
|
||||||
|
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
GroupsService,
|
||||||
|
{
|
||||||
|
provide: DRIZZLE,
|
||||||
|
useValue: mockDb,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<GroupsService>(GroupsService);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('create', () => {
|
||||||
|
it('should create a new group', async () => {
|
||||||
|
const createGroupDto = {
|
||||||
|
name: 'Test Group',
|
||||||
|
projectId: 'project1',
|
||||||
|
metadata: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await service.create(createGroupDto);
|
||||||
|
|
||||||
|
expect(mockDb.insert).toHaveBeenCalled();
|
||||||
|
expect(mockDb.values).toHaveBeenCalledWith({
|
||||||
|
...createGroupDto,
|
||||||
|
});
|
||||||
|
expect(result).toEqual(mockGroup);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findAll', () => {
|
||||||
|
it('should return all groups', async () => {
|
||||||
|
mockDb.select.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.from.mockImplementationOnce(() => [mockGroup]);
|
||||||
|
|
||||||
|
const result = await service.findAll();
|
||||||
|
|
||||||
|
expect(mockDb.select).toHaveBeenCalled();
|
||||||
|
expect(mockDb.from).toHaveBeenCalled();
|
||||||
|
expect(result).toEqual([mockGroup]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findByProjectId', () => {
|
||||||
|
it('should return groups for a specific project', async () => {
|
||||||
|
const projectId = 'project1';
|
||||||
|
mockDb.select.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.where.mockImplementationOnce(() => [mockGroup]);
|
||||||
|
|
||||||
|
const result = await service.findByProjectId(projectId);
|
||||||
|
|
||||||
|
expect(mockDb.select).toHaveBeenCalled();
|
||||||
|
expect(mockDb.from).toHaveBeenCalled();
|
||||||
|
expect(mockDb.where).toHaveBeenCalled();
|
||||||
|
expect(result).toEqual([mockGroup]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findById', () => {
|
||||||
|
it('should return a group by id', async () => {
|
||||||
|
const id = 'group1';
|
||||||
|
mockDb.select.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.where.mockImplementationOnce(() => [mockGroup]);
|
||||||
|
|
||||||
|
const result = await service.findById(id);
|
||||||
|
|
||||||
|
expect(mockDb.select).toHaveBeenCalled();
|
||||||
|
expect(mockDb.from).toHaveBeenCalled();
|
||||||
|
expect(mockDb.where).toHaveBeenCalled();
|
||||||
|
expect(result).toEqual(mockGroup);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw NotFoundException if group not found', async () => {
|
||||||
|
const id = 'nonexistent';
|
||||||
|
mockDb.select.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.where.mockImplementationOnce(() => [undefined]);
|
||||||
|
|
||||||
|
await expect(service.findById(id)).rejects.toThrow(NotFoundException);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('update', () => {
|
||||||
|
it('should update a group', async () => {
|
||||||
|
const id = 'group1';
|
||||||
|
const updateGroupDto = {
|
||||||
|
name: 'Updated Group',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await service.update(id, updateGroupDto);
|
||||||
|
|
||||||
|
expect(mockDb.update).toHaveBeenCalled();
|
||||||
|
expect(mockDb.set).toHaveBeenCalled();
|
||||||
|
expect(mockDb.where).toHaveBeenCalled();
|
||||||
|
expect(result).toEqual(mockGroup);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw NotFoundException if group not found', async () => {
|
||||||
|
const id = 'nonexistent';
|
||||||
|
const updateGroupDto = {
|
||||||
|
name: 'Updated Group',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockDb.update.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.set.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.where.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.returning.mockImplementationOnce(() => [undefined]);
|
||||||
|
|
||||||
|
await expect(service.update(id, updateGroupDto)).rejects.toThrow(NotFoundException);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('remove', () => {
|
||||||
|
it('should remove a group', async () => {
|
||||||
|
const id = 'group1';
|
||||||
|
|
||||||
|
const result = await service.remove(id);
|
||||||
|
|
||||||
|
expect(mockDb.delete).toHaveBeenCalled();
|
||||||
|
expect(mockDb.where).toHaveBeenCalled();
|
||||||
|
expect(result).toEqual(mockGroup);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw NotFoundException if group not found', async () => {
|
||||||
|
const id = 'nonexistent';
|
||||||
|
|
||||||
|
mockDb.delete.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.where.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.returning.mockImplementationOnce(() => [undefined]);
|
||||||
|
|
||||||
|
await expect(service.remove(id)).rejects.toThrow(NotFoundException);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('addPersonToGroup', () => {
|
||||||
|
it('should add a person to a group', async () => {
|
||||||
|
const groupId = 'group1';
|
||||||
|
const personId = 'person1';
|
||||||
|
|
||||||
|
// Mock findById to return the group
|
||||||
|
jest.spyOn(service, 'findById').mockResolvedValueOnce(mockGroup);
|
||||||
|
|
||||||
|
// Reset and setup mocks for this test
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
// Mock person lookup
|
||||||
|
mockDb.select.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.where.mockImplementationOnce(() => [[mockPerson]]);
|
||||||
|
|
||||||
|
// Mock relation lookup
|
||||||
|
mockDb.select.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.where.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.where.mockImplementationOnce(() => [undefined]);
|
||||||
|
|
||||||
|
// Mock relation creation
|
||||||
|
mockDb.insert.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.values.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.returning.mockImplementationOnce(() => [mockPersonToGroup]);
|
||||||
|
|
||||||
|
const result = await service.addPersonToGroup(groupId, personId);
|
||||||
|
|
||||||
|
expect(service.findById).toHaveBeenCalledWith(groupId);
|
||||||
|
expect(mockDb.select).toHaveBeenCalled();
|
||||||
|
expect(mockDb.from).toHaveBeenCalled();
|
||||||
|
expect(mockDb.insert).toHaveBeenCalled();
|
||||||
|
expect(mockDb.values).toHaveBeenCalledWith({
|
||||||
|
personId,
|
||||||
|
groupId,
|
||||||
|
});
|
||||||
|
expect(result).toEqual(mockPersonToGroup);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw NotFoundException if person not found', async () => {
|
||||||
|
const groupId = 'group1';
|
||||||
|
const personId = 'nonexistent';
|
||||||
|
|
||||||
|
// Mock findById to return the group
|
||||||
|
jest.spyOn(service, 'findById').mockResolvedValueOnce(mockGroup);
|
||||||
|
|
||||||
|
// Reset and setup mocks for this test
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
// Mock person lookup to return no person
|
||||||
|
mockDb.select.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.where.mockImplementationOnce(() => [undefined]);
|
||||||
|
|
||||||
|
await expect(service.addPersonToGroup(groupId, personId)).rejects.toThrow(NotFoundException);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('removePersonFromGroup', () => {
|
||||||
|
it('should remove a person from a group', async () => {
|
||||||
|
const groupId = 'group1';
|
||||||
|
const personId = 'person1';
|
||||||
|
|
||||||
|
// Reset and setup mocks for this test
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
mockDb.delete.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.where.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.where.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.returning.mockImplementationOnce(() => [mockPersonToGroup]);
|
||||||
|
|
||||||
|
const result = await service.removePersonFromGroup(groupId, personId);
|
||||||
|
|
||||||
|
expect(mockDb.delete).toHaveBeenCalled();
|
||||||
|
expect(mockDb.where).toHaveBeenCalled();
|
||||||
|
expect(result).toEqual(mockPersonToGroup);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw NotFoundException if relation not found', async () => {
|
||||||
|
const groupId = 'group1';
|
||||||
|
const personId = 'nonexistent';
|
||||||
|
|
||||||
|
// Reset and setup mocks for this test
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
mockDb.delete.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.where.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.where.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.returning.mockImplementationOnce(() => [undefined]);
|
||||||
|
|
||||||
|
await expect(service.removePersonFromGroup(groupId, personId)).rejects.toThrow(NotFoundException);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getPersonsInGroup', () => {
|
||||||
|
it('should get all persons in a group', async () => {
|
||||||
|
const groupId = 'group1';
|
||||||
|
const mockPersons = [{ person: mockPerson }];
|
||||||
|
|
||||||
|
// Mock findById to return the group
|
||||||
|
jest.spyOn(service, 'findById').mockResolvedValueOnce(mockGroup);
|
||||||
|
|
||||||
|
mockDb.select.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.innerJoin.mockImplementationOnce(() => mockDbOperations);
|
||||||
|
mockDbOperations.where.mockImplementationOnce(() => mockPersons);
|
||||||
|
|
||||||
|
const result = await service.getPersonsInGroup(groupId);
|
||||||
|
|
||||||
|
expect(service.findById).toHaveBeenCalledWith(groupId);
|
||||||
|
expect(mockDb.select).toHaveBeenCalled();
|
||||||
|
expect(mockDb.from).toHaveBeenCalled();
|
||||||
|
expect(mockDb.innerJoin).toHaveBeenCalled();
|
||||||
|
expect(mockDb.where).toHaveBeenCalled();
|
||||||
|
expect(result).toEqual(mockPersons);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user