diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..d4e48170 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @maxn990 @amywng diff --git a/apps/backend-e2e/src/apps/backend/apps/backend.spec.ts b/apps/backend-e2e/src/apps/backend/apps/backend.spec.ts deleted file mode 100644 index e8ac2a6c..00000000 --- a/apps/backend-e2e/src/apps/backend/apps/backend.spec.ts +++ /dev/null @@ -1,10 +0,0 @@ -import axios from 'axios'; - -describe('GET /api', () => { - it('should return a message', async () => { - const res = await axios.get(`/api`); - - expect(res.status).toBe(200); - expect(res.data).toEqual({ message: 'Hello API' }); - }); -}); diff --git a/apps/backend/src/app.module.ts b/apps/backend/src/app.module.ts index 54fb044e..20b29c4b 100644 --- a/apps/backend/src/app.module.ts +++ b/apps/backend/src/app.module.ts @@ -3,11 +3,16 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; -import { TaskModule } from './task/task.module'; import AppDataSource from './data-source'; +import { TaskModule } from './task/task.module'; +import { LabelModule } from './label/label.module'; @Module({ - imports: [TypeOrmModule.forRoot(AppDataSource.options), TaskModule], + imports: [ + TypeOrmModule.forRoot(AppDataSource.options), + TaskModule, + LabelModule, + ], controllers: [AppController], providers: [AppService], }) diff --git a/apps/backend/src/auth/auth.controller.spec.ts b/apps/backend/src/auth/auth.controller.spec.ts deleted file mode 100644 index 27a31e61..00000000 --- a/apps/backend/src/auth/auth.controller.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AuthController } from './auth.controller'; - -describe('AuthController', () => { - let controller: AuthController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [AuthController], - }).compile(); - - controller = module.get(AuthController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/apps/backend/src/auth/auth.service.spec.ts b/apps/backend/src/auth/auth.service.spec.ts deleted file mode 100644 index 800ab662..00000000 --- a/apps/backend/src/auth/auth.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AuthService } from './auth.service'; - -describe('AuthService', () => { - let service: AuthService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [AuthService], - }).compile(); - - service = module.get(AuthService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/apps/backend/src/data-source.ts b/apps/backend/src/data-source.ts index 4cd06624..8f9bd610 100644 --- a/apps/backend/src/data-source.ts +++ b/apps/backend/src/data-source.ts @@ -1,6 +1,7 @@ import { DataSource } from 'typeorm'; import { PluralNamingStrategy } from './strategies/plural-naming.strategy'; import { Task } from './task/types/task.entity'; +import { Label } from './label/types/label.entity'; import * as dotenv from 'dotenv'; dotenv.config(); @@ -12,7 +13,7 @@ const AppDataSource = new DataSource({ username: process.env.NX_DB_USERNAME, password: process.env.NX_DB_PASSWORD, database: process.env.NX_DB_DATABASE, - entities: [Task], + entities: [Task, Label], migrations: ['apps/backend/src/migrations/*.js'], // Setting synchronize: true shouldn't be used in production - otherwise you can lose production data synchronize: false, diff --git a/apps/backend/src/label/dtos/create-label.dto.ts b/apps/backend/src/label/dtos/create-label.dto.ts new file mode 100644 index 00000000..a3c47e9a --- /dev/null +++ b/apps/backend/src/label/dtos/create-label.dto.ts @@ -0,0 +1,12 @@ +import { IsString, IsHexColor } from 'class-validator'; + +export class CreateLabelDTO { + @IsString() + name: string; + + @IsString() + description: string; + + @IsHexColor() + color: string; +} diff --git a/apps/backend/src/label/label.controller.spec.ts b/apps/backend/src/label/label.controller.spec.ts new file mode 100644 index 00000000..fd362c0e --- /dev/null +++ b/apps/backend/src/label/label.controller.spec.ts @@ -0,0 +1,93 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { LabelsController } from './label.controller'; +import { LabelsService } from './label.service'; +import { CreateLabelDTO } from './dtos/create-label.dto'; +import { Label } from './types/label.entity'; + +// Mock implementation for LabelsService +export const mockLabelService: Partial = { + createLabel: jest.fn((labelDto: CreateLabelDTO) => + Promise.resolve(mockLabel), + ), + getAllLabels: jest.fn(() => Promise.resolve([mockLabel])), +}; + +export const mockLabelDto: CreateLabelDTO = { + name: 'Test Label', + description: 'Test Description', + color: '#000000', +}; + +export const mockLabel: Label = { + id: 1, + name: 'Test Label', + description: 'Test Description', + color: '#000000', + tasks: [], +}; + +export const mockLabel2: Label = { + id: 2, + name: 'Test Label 2', + description: 'Test Description 2', + color: '#802b2bff', + tasks: [], +}; + +describe('LabelController', () => { + let controller: LabelsController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [LabelsController], + providers: [ + { + provide: LabelsService, + useValue: mockLabelService, + }, + ], + }).compile(); + + controller = module.get(LabelsController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); + + /* Test for create new label */ + describe('POST /labels', () => { + it('should create a new label and return it', async () => { + jest.spyOn(mockLabelService, 'createLabel').mockResolvedValue(mockLabel); + + const res = await controller.createLabel(mockLabelDto); + + expect(res).toEqual(mockLabel); + expect(mockLabelService.createLabel).toHaveBeenCalledWith(mockLabelDto); + }); + }); + + /* Test for retrieve all labels */ + describe('GET /labels', () => { + it('should return an array of labels', async () => { + jest + .spyOn(mockLabelService, 'getAllLabels') + .mockResolvedValue([mockLabel]); + + const res = await controller.getAllLabels(); + + expect(res).toEqual([mockLabel]); + expect(mockLabelService.getAllLabels).toHaveBeenCalled(); + }); + it('should return an array of labels with 2 labels', async () => { + jest + .spyOn(mockLabelService, 'getAllLabels') + .mockResolvedValue([mockLabel, mockLabel2]); + + const res = await controller.getAllLabels(); + + expect(res).toEqual([mockLabel, mockLabel2]); + expect(mockLabelService.getAllLabels).toHaveBeenCalled(); + }); + }); +}); diff --git a/apps/backend/src/label/label.controller.ts b/apps/backend/src/label/label.controller.ts new file mode 100644 index 00000000..176cccac --- /dev/null +++ b/apps/backend/src/label/label.controller.ts @@ -0,0 +1,40 @@ +import { + Controller, + Get, + Post, + Body, + Patch, + Param, + Delete, + Query, +} from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { LabelsService } from './label.service'; +import { Label } from './types/label.entity'; +import { CreateLabelDTO } from './dtos/create-label.dto'; + +@ApiTags('labels') +@Controller('labels') +export class LabelsController { + constructor(private readonly labelsService: LabelsService) {} + + /** Creates a new label. + * @param LabelDto - The data transfer object containing label details. + * @returns The created label. + * @throws BadRequestException if the label name is not unique + * @throws BadRequestException if label name is not provided + * @throws BadRequestException if color is not provided or is not hexadecimal + */ + @Post('/label') + async createLabel(@Body() labelDto: CreateLabelDTO): Promise