test: add unit tests for tags module and global auth guard

Added comprehensive unit tests for `TagsService` and `TagsController`, covering CRUD operations, exception handling, and associations with projects and persons. Mocked `JwtAuthGuard` globally for testing purposes.
This commit is contained in:
Mathis H (Avnyr) 2025-05-15 19:48:44 +02:00
parent 7eae25d5de
commit 50583f9ccc
3 changed files with 531 additions and 0 deletions

View File

@ -3,6 +3,19 @@ import { Reflector } from '@nestjs/core';
import { JwtAuthGuard } from './jwt-auth.guard';
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
// Mock the AuthGuard
jest.mock('@nestjs/passport', () => {
return {
AuthGuard: jest.fn().mockImplementation(() => {
return class {
canActivate() {
return true;
}
};
}),
};
});
describe('JwtAuthGuard', () => {
let guard: JwtAuthGuard;
let reflector: Reflector;

View File

@ -0,0 +1,179 @@
import { Test, TestingModule } from '@nestjs/testing';
import { TagsController } from './tags.controller';
import { TagsService } from '../services/tags.service';
import { CreateTagDto } from '../dto/create-tag.dto';
import { UpdateTagDto } from '../dto/update-tag.dto';
import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard';
describe('TagsController', () => {
let controller: TagsController;
let service: TagsService;
// Mock data
const mockTag = {
id: 'tag1',
name: 'Test Tag',
description: 'Test Description',
color: '#FF0000',
type: 'PERSON',
createdAt: new Date(),
updatedAt: new Date(),
};
const mockPersonToTag = {
personId: 'person1',
tagId: 'tag1',
};
const mockProjectToTag = {
projectId: 'project1',
tagId: 'tag1',
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [TagsController],
providers: [
{
provide: TagsService,
useValue: {
create: jest.fn().mockResolvedValue(mockTag),
findAll: jest.fn().mockResolvedValue([mockTag]),
findByType: jest.fn().mockResolvedValue([mockTag]),
findById: jest.fn().mockResolvedValue(mockTag),
update: jest.fn().mockResolvedValue(mockTag),
remove: jest.fn().mockResolvedValue(mockTag),
addTagToPerson: jest.fn().mockResolvedValue(mockPersonToTag),
removeTagFromPerson: jest.fn().mockResolvedValue(mockPersonToTag),
getTagsForPerson: jest.fn().mockResolvedValue([{ tag: mockTag }]),
addTagToProject: jest.fn().mockResolvedValue(mockProjectToTag),
removeTagFromProject: jest.fn().mockResolvedValue(mockProjectToTag),
getTagsForProject: jest.fn().mockResolvedValue([{ tag: mockTag }]),
},
},
],
})
.overrideGuard(JwtAuthGuard)
.useValue({ canActivate: () => true })
.compile();
controller = module.get<TagsController>(TagsController);
service = module.get<TagsService>(TagsService);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
describe('create', () => {
it('should create a new tag', async () => {
const createTagDto: CreateTagDto = {
name: 'Test Tag',
color: '#FF0000',
type: 'PERSON',
};
expect(await controller.create(createTagDto)).toBe(mockTag);
expect(service.create).toHaveBeenCalledWith(createTagDto);
});
});
describe('findAll', () => {
it('should return all tags when no type is provided', async () => {
expect(await controller.findAll()).toEqual([mockTag]);
expect(service.findAll).toHaveBeenCalled();
});
it('should return tags filtered by type when type is provided', async () => {
const type = 'PERSON';
expect(await controller.findAll(type)).toEqual([mockTag]);
expect(service.findByType).toHaveBeenCalledWith(type);
});
});
describe('findOne', () => {
it('should return a tag by ID', async () => {
const id = 'tag1';
expect(await controller.findOne(id)).toBe(mockTag);
expect(service.findById).toHaveBeenCalledWith(id);
});
});
describe('update', () => {
it('should update a tag', async () => {
const id = 'tag1';
const updateTagDto: UpdateTagDto = {
name: 'Updated Tag',
};
expect(await controller.update(id, updateTagDto)).toBe(mockTag);
expect(service.update).toHaveBeenCalledWith(id, updateTagDto);
});
});
describe('remove', () => {
it('should delete a tag', async () => {
const id = 'tag1';
expect(await controller.remove(id)).toBe(mockTag);
expect(service.remove).toHaveBeenCalledWith(id);
});
});
describe('addTagToPerson', () => {
it('should add a tag to a person', async () => {
const personId = 'person1';
const tagId = 'tag1';
expect(await controller.addTagToPerson(personId, tagId)).toBe(mockPersonToTag);
expect(service.addTagToPerson).toHaveBeenCalledWith(tagId, personId);
});
});
describe('removeTagFromPerson', () => {
it('should remove a tag from a person', async () => {
const personId = 'person1';
const tagId = 'tag1';
expect(await controller.removeTagFromPerson(personId, tagId)).toBe(mockPersonToTag);
expect(service.removeTagFromPerson).toHaveBeenCalledWith(tagId, personId);
});
});
describe('getTagsForPerson', () => {
it('should get all tags for a person', async () => {
const personId = 'person1';
expect(await controller.getTagsForPerson(personId)).toEqual([{ tag: mockTag }]);
expect(service.getTagsForPerson).toHaveBeenCalledWith(personId);
});
});
describe('addTagToProject', () => {
it('should add a tag to a project', async () => {
const projectId = 'project1';
const tagId = 'tag1';
expect(await controller.addTagToProject(projectId, tagId)).toBe(mockProjectToTag);
expect(service.addTagToProject).toHaveBeenCalledWith(tagId, projectId);
});
});
describe('removeTagFromProject', () => {
it('should remove a tag from a project', async () => {
const projectId = 'project1';
const tagId = 'tag1';
expect(await controller.removeTagFromProject(projectId, tagId)).toBe(mockProjectToTag);
expect(service.removeTagFromProject).toHaveBeenCalledWith(tagId, projectId);
});
});
describe('getTagsForProject', () => {
it('should get all tags for a project', async () => {
const projectId = 'project1';
expect(await controller.getTagsForProject(projectId)).toEqual([{ tag: mockTag }]);
expect(service.getTagsForProject).toHaveBeenCalledWith(projectId);
});
});
});

View File

@ -0,0 +1,339 @@
import { Test, TestingModule } from '@nestjs/testing';
import { TagsService } from './tags.service';
import { NotFoundException } from '@nestjs/common';
import { DRIZZLE } from '../../../database/database.module';
describe('TagsService', () => {
let service: TagsService;
let mockDb: any;
// Mock data
const mockTag = {
id: 'tag1',
name: 'Test Tag',
description: 'Test Description',
color: '#FF0000',
type: 'PERSON',
createdAt: new Date(),
updatedAt: new Date(),
};
const mockPerson = {
id: 'person1',
name: 'Test Person',
projectId: 'project1',
createdAt: new Date(),
updatedAt: new Date(),
};
const mockProject = {
id: 'project1',
name: 'Test Project',
userId: 'user1',
createdAt: new Date(),
updatedAt: new Date(),
};
const mockPersonToTag = {
personId: 'person1',
tagId: 'tag1',
};
const mockProjectToTag = {
projectId: 'project1',
tagId: 'tag1',
};
// 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 [mockTag];
}),
};
beforeEach(async () => {
mockDb = {
...mockDbOperations,
};
const module: TestingModule = await Test.createTestingModule({
providers: [
TagsService,
{
provide: DRIZZLE,
useValue: mockDb,
},
],
}).compile();
service = module.get<TagsService>(TagsService);
});
afterEach(() => {
jest.clearAllMocks();
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('create', () => {
it('should create a new tag', async () => {
const createTagDto = {
name: 'Test Tag',
color: '#FF0000',
type: 'PERSON' as 'PERSON',
};
const result = await service.create(createTagDto);
expect(mockDb.insert).toHaveBeenCalled();
expect(mockDb.values).toHaveBeenCalledWith({
...createTagDto,
});
expect(result).toEqual(mockTag);
});
});
describe('findAll', () => {
it('should return all tags', async () => {
mockDb.select.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.from.mockImplementationOnce(() => [mockTag]);
const result = await service.findAll();
expect(mockDb.select).toHaveBeenCalled();
expect(mockDb.from).toHaveBeenCalled();
expect(result).toEqual([mockTag]);
});
});
describe('findByType', () => {
it('should return tags for a specific type', async () => {
const type = 'PERSON';
mockDb.select.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.where.mockImplementationOnce(() => [mockTag]);
const result = await service.findByType(type);
expect(mockDb.select).toHaveBeenCalled();
expect(mockDb.from).toHaveBeenCalled();
expect(mockDb.where).toHaveBeenCalled();
expect(result).toEqual([mockTag]);
});
});
describe('findById', () => {
it('should return a tag by ID', async () => {
const id = 'tag1';
mockDb.select.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.where.mockImplementationOnce(() => [mockTag]);
const result = await service.findById(id);
expect(mockDb.select).toHaveBeenCalled();
expect(mockDb.from).toHaveBeenCalled();
expect(mockDb.where).toHaveBeenCalled();
expect(result).toEqual(mockTag);
});
it('should throw NotFoundException if tag not found', async () => {
const id = 'nonexistent';
mockDb.select.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.where.mockImplementationOnce(() => []);
await expect(service.findById(id)).rejects.toThrow(NotFoundException);
});
});
describe('update', () => {
it('should update a tag', async () => {
const id = 'tag1';
const updateTagDto = {
name: 'Updated Tag',
};
const result = await service.update(id, updateTagDto);
expect(mockDb.update).toHaveBeenCalled();
expect(mockDb.set).toHaveBeenCalled();
expect(mockDb.where).toHaveBeenCalled();
expect(result).toEqual(mockTag);
});
it('should throw NotFoundException if tag not found', async () => {
const id = 'nonexistent';
const updateTagDto = {
name: 'Updated Tag',
};
mockDb.update.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.set.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.where.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.returning.mockImplementationOnce(() => []);
await expect(service.update(id, updateTagDto)).rejects.toThrow(NotFoundException);
});
});
describe('remove', () => {
it('should delete a tag', async () => {
const id = 'tag1';
const result = await service.remove(id);
expect(mockDb.delete).toHaveBeenCalled();
expect(mockDb.where).toHaveBeenCalled();
expect(result).toEqual(mockTag);
});
it('should throw NotFoundException if tag not found', async () => {
const id = 'nonexistent';
mockDb.delete.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.where.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.returning.mockImplementationOnce(() => []);
await expect(service.remove(id)).rejects.toThrow(NotFoundException);
});
});
describe('addTagToPerson', () => {
it('should add a tag to a person', async () => {
const tagId = 'tag1';
const personId = 'person1';
// Mock findById to return a PERSON tag
jest.spyOn(service, 'findById').mockResolvedValueOnce(mockTag);
// Mock person check
mockDb.select.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.where.mockImplementationOnce(() => [mockPerson]);
// Mock relation check
mockDb.select.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.where.mockImplementationOnce(() => []);
// Mock insert
mockDb.insert.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.values.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.returning.mockImplementationOnce(() => [mockPersonToTag]);
const result = await service.addTagToPerson(tagId, personId);
expect(service.findById).toHaveBeenCalledWith(tagId);
expect(mockDb.select).toHaveBeenCalled();
expect(mockDb.from).toHaveBeenCalled();
expect(mockDb.where).toHaveBeenCalled();
expect(mockDb.insert).toHaveBeenCalled();
expect(mockDb.values).toHaveBeenCalledWith({
personId,
tagId,
});
expect(result).toEqual(mockPersonToTag);
});
});
describe('getTagsForPerson', () => {
it('should get all tags for a person', async () => {
const personId = 'person1';
// Mock person check
mockDb.select.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.where.mockImplementationOnce(() => [mockPerson]);
// Mock get tags
mockDb.select.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.innerJoin.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.where.mockImplementationOnce(() => [{ tag: mockTag }]);
const result = await service.getTagsForPerson(personId);
expect(mockDb.select).toHaveBeenCalled();
expect(mockDb.from).toHaveBeenCalled();
expect(mockDb.innerJoin).toHaveBeenCalled();
expect(mockDb.where).toHaveBeenCalled();
expect(result).toEqual([{ tag: mockTag }]);
});
});
describe('addTagToProject', () => {
it('should add a tag to a project', async () => {
const tagId = 'tag1';
const projectId = 'project1';
// Mock findById to return a PROJECT tag
const projectTag = { ...mockTag, type: 'PROJECT' };
jest.spyOn(service, 'findById').mockResolvedValueOnce(projectTag);
// Mock project check
mockDb.select.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.where.mockImplementationOnce(() => [mockProject]);
// Mock relation check
mockDb.select.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.where.mockImplementationOnce(() => []);
// Mock insert
mockDb.insert.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.values.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.returning.mockImplementationOnce(() => [mockProjectToTag]);
const result = await service.addTagToProject(tagId, projectId);
expect(service.findById).toHaveBeenCalledWith(tagId);
expect(mockDb.select).toHaveBeenCalled();
expect(mockDb.from).toHaveBeenCalled();
expect(mockDb.where).toHaveBeenCalled();
expect(mockDb.insert).toHaveBeenCalled();
expect(mockDb.values).toHaveBeenCalledWith({
projectId,
tagId,
});
expect(result).toEqual(mockProjectToTag);
});
});
describe('getTagsForProject', () => {
it('should get all tags for a project', async () => {
const projectId = 'project1';
// Mock project check
mockDb.select.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.where.mockImplementationOnce(() => [mockProject]);
// Mock get tags
mockDb.select.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.innerJoin.mockImplementationOnce(() => mockDbOperations);
mockDbOperations.where.mockImplementationOnce(() => [{ tag: mockTag }]);
const result = await service.getTagsForProject(projectId);
expect(mockDb.select).toHaveBeenCalled();
expect(mockDb.from).toHaveBeenCalled();
expect(mockDb.innerJoin).toHaveBeenCalled();
expect(mockDb.where).toHaveBeenCalled();
expect(result).toEqual([{ tag: mockTag }]);
});
});
});