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