Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[M1_TR-206] Frontend for M1_TR-5 #19

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions server/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ lerna-debug.log*

# Keep environment variables out of version control
.env
/test
6 changes: 5 additions & 1 deletion server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
"test:e2e": "jest --config ./test/jest-e2e.json",
"seed": "ts-node src/models/seed.ts"
},
"dependencies": {
"@nestjs/common": "^10.0.0",
Expand All @@ -31,10 +32,13 @@
"rxjs": "^7.8.1"
},
"devDependencies": {
"@faker-js/faker": "^8.0.2",
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
"@nestjs/swagger": "^7.1.10",
"@nestjs/testing": "^10.0.0",
"@types/express": "^4.17.17",
"@types/faker": "^6.6.9",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
"@types/supertest": "^2.0.12",
Expand Down
10 changes: 10 additions & 0 deletions server/src/api/jobs/dto/job-list.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ApiProperty } from '@nestjs/swagger';
import { Job } from '@prisma/client';

export class JobListDto {
@ApiProperty()
jobs: Job[];

@ApiProperty()
count: number;
}
38 changes: 38 additions & 0 deletions server/src/api/jobs/dto/job-query.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ApiProperty } from '@nestjs/swagger';
import { Status, Tag } from '@prisma/client';
import { Transform } from 'class-transformer';
import { IsEnum, IsNumber, IsOptional } from 'class-validator';
import { IsDateRange } from '../../../utils/validators/dateRange.validator';

export class JobQueryDto {
@ApiProperty()
@IsNumber()
@Transform(({ value }) => parseInt(value))
page: number;

@ApiProperty({ required: false })
@IsOptional()
@IsNumber()
@Transform(({ value }) => parseInt(value))
perPage?: number;

@ApiProperty({ required: false })
@IsOptional()
@IsEnum(Tag)
tag: Tag;

@ApiProperty({ required: false })
@IsOptional()
@IsEnum(Status)
status: Status;

@ApiProperty({ required: false })
@IsOptional()
@IsDateRange()
startDate: Date;

@ApiProperty({ required: false })
@IsOptional()
@IsDateRange()
endDate: Date;
}
123 changes: 123 additions & 0 deletions server/src/api/jobs/jobs.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { Test, TestingModule } from '@nestjs/testing';
import { $Enums } from '@prisma/client';
import { JobsController } from './jobs.controller';
import { JobsService } from './jobs.service';

describe('JobsController', () => {
let jobController: JobsController;

const mockJobService = {
findAll: jest.fn()
};

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [JobsController],
providers: [
{
provide: JobsService,
useValue: mockJobService,
},
],
}).compile();

jobController = module.get<JobsController>(JobsController);
});

describe('findAll', () => {
it('should return a list of jobs with the total number of jobs', async () => {
const jobQuery = { page: 1, perPage: 2, tag: null, status: null, startDate: null, endDate: null };
const jobList = {
jobs: [
{
id: 1,
title: "Job A",
type: "Type A",
tags: [],
remarks: null,
customerId: 1,
paymentMethod: null,
userId: 1,
pipelinePhase: null,
createdAt: new Date("2023-09-07T09:38:42.296Z"),
updatedAt: new Date("2023-09-07T09:38:42.296Z"),
},
{
id: 2,
title: "Job B",
type: "Type B",
tags: [],
remarks: null,
customerId: 2,
paymentMethod: null,
userId: 2,
pipelinePhase: null,
createdAt: new Date("2023-09-07T09:38:42.296Z"),
updatedAt: new Date("2023-09-07T09:38:42.296Z"),
}
],
count: 2
};

jest.spyOn(mockJobService, 'findAll').mockResolvedValue(jobList);

const jobs = await jobController.findAll(jobQuery);

expect(mockJobService.findAll).toHaveBeenCalledWith(jobQuery);
expect(jobs).toEqual(jobList);
});
});

describe('filter', () => {
it('should return a filtered list of jobs with total number of filtered jobs', async () => {
const mockTags = [$Enums.Tag.TAG_A];
const mockStatus = $Enums.Status.APPROVED;
const jobQuery = { page: 1, perPage: 2, tag: mockTags[0], status: mockStatus, startDate: null, endDate: null };

const jobList = {
jobs: [
{
id: 1,
title: "Job A",
type: "Type A",
tags: mockTags,
remarks: null,
customerId: 1,
paymentMethod: null,
userId: 1,
pipelinePhase: null,
createdAt: new Date("2023-09-07T09:38:42.296Z"),
updatedAt: new Date("2023-09-07T09:38:42.296Z"),
estimation: {
status: mockStatus,
totalCost: 1000.00
}
},
{
id: 2,
title: "Job B",
type: "Type B",
tags: [],
remarks: null,
customerId: 2,
paymentMethod: null,
userId: 2,
pipelinePhase: null,
createdAt: new Date("2023-09-07T09:38:42.296Z"),
updatedAt: new Date("2023-09-07T09:38:42.296Z"),
}
],
count: 2
};

const filteredJobList = { jobs: jobList.jobs.slice(0, 1), count: 1}

jest.spyOn(mockJobService, 'findAll').mockResolvedValue(filteredJobList);

const jobs = await jobController.findAll(jobQuery);

expect(mockJobService.findAll).toHaveBeenCalledWith(jobQuery);
expect(jobs).toEqual(filteredJobList);
});
});
});
19 changes: 19 additions & 0 deletions server/src/api/jobs/jobs.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Controller, Get, Query, UsePipes, ValidationPipe } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { JobListDto } from './dto/job-list.dto';
import { JobQueryDto } from './dto/job-query.dto';
import { JobsService } from './jobs.service';

@ApiTags('jobs')
@Controller('jobs')
export class JobsController {
constructor(private readonly jobsService: JobsService) {}

@Get()
@UsePipes(new ValidationPipe({ transform: true }))
async findAll(@Query() jobQuery: JobQueryDto): Promise<JobListDto> {
const jobs = await this.jobsService.findAll(jobQuery);

return jobs;
}
}
10 changes: 10 additions & 0 deletions server/src/api/jobs/jobs.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { PrismaService } from 'src/database/connection.service';
import { JobsController } from './jobs.controller';
import { JobsService } from './jobs.service';

@Module({
controllers: [JobsController],
providers: [JobsService, PrismaService],
})
export class JobsModule {}
118 changes: 118 additions & 0 deletions server/src/api/jobs/jobs.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { Test, TestingModule } from '@nestjs/testing';
import { $Enums } from '@prisma/client';
import { PrismaService } from '../../database/connection.service';
import { JobsService } from './jobs.service';

describe('JobsService', () => {
let jobService: JobsService;

const mockPrismaService = {
job: {
findMany: jest.fn(),
count: jest.fn(),
},
$transaction: jest.fn()
};

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
JobsService,
{
provide: PrismaService,
useValue: mockPrismaService
}
],
}).compile();

jobService = module.get<JobsService>(JobsService);
});

describe('findAll', () => {
it('should be return a list of jobs with the total number of jobs', async () => {
const jobQuery = { page: 1, perPage: 2, tag: null, status: null, startDate: null, endDate: null };
const mockJobList = [
{
id: 1,
title: "Job A",
type: "Type A",
tags: [],
remarks: null,
customerId: 1,
paymentMethod: null,
userId: 1,
pipelinePhase: null,
createdAt: new Date("2023-09-07T09:38:42.296Z"),
updatedAt: new Date("2023-09-07T09:38:42.296Z"),
},
{
id: 2,
title: "Job B",
type: "Type B",
tags: [],
remarks: null,
customerId: 2,
paymentMethod: null,
userId: 2,
pipelinePhase: null,
createdAt: new Date("2023-09-07T09:38:42.296Z"),
updatedAt: new Date("2023-09-07T09:38:42.296Z"),
}
];
const mockJobCount = 2;

mockPrismaService.$transaction.mockResolvedValue([ mockJobList, mockJobCount ]);

const jobs = await jobService.findAll(jobQuery);

expect(jobs).toEqual({ jobs: mockJobList, count: mockJobCount });
});
});

describe('filter', () => {
it('should be return a filtered list of jobs with the total number of filtered jobs', async () => {
const mockTags = [$Enums.Tag.TAG_A];
const mockStatus = $Enums.Status.APPROVED;
const jobQuery = { page: 1, perPage: 2, tag: mockTags[0], status: mockStatus, startDate: null, endDate: null }
const mockJobList = [
{
id: 1,
title: "Job A",
type: "Type A",
tags: mockTags,
remarks: null,
customerId: 1,
paymentMethod: null,
userId: 1,
pipelinePhase: null,
createdAt: new Date("2023-09-07T09:38:42.296Z"),
updatedAt: new Date("2023-09-07T09:38:42.296Z"),
estimation: {
status: mockStatus,
totalCost: 1000.00
}
},
{
id: 2,
title: "Job B",
type: "Type B",
tags: [],
remarks: null,
customerId: 2,
paymentMethod: null,
userId: 2,
pipelinePhase: null,
createdAt: new Date("2023-09-07T09:38:42.296Z"),
updatedAt: new Date("2023-09-07T09:38:42.296Z"),
}
];
const mockJobCount = 1;

mockPrismaService.$transaction.mockResolvedValue([ mockJobList.slice(0, 1), mockJobCount ]);

const jobs = await jobService.findAll(jobQuery);

expect(jobs).toEqual({ jobs: mockJobList.slice(0, 1), count: mockJobCount });
});
});
});
Loading