From 79bad7805019f4fd09cb5d03273fb79a73a96524 Mon Sep 17 00:00:00 2001 From: Leo Gavin Date: Tue, 30 Apr 2024 13:37:50 +0530 Subject: [PATCH 01/13] test: bids unit tests --- API/src/bids/bids.service.spec.ts | 50 ++++++++++++++++++++++++++++--- API/src/bids/bids.service.ts | 4 +-- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/API/src/bids/bids.service.spec.ts b/API/src/bids/bids.service.spec.ts index 0fa01e1..ab8bb3c 100644 --- a/API/src/bids/bids.service.spec.ts +++ b/API/src/bids/bids.service.spec.ts @@ -8,6 +8,7 @@ import { BidsService } from './bids.service'; import { Bid, BidDocument } from './entities/bid.entity'; import { CreateBidDto } from './dto/create-bid.dto'; import { UpdateBidDto } from './dto/update-bid.dto'; +import { NotFoundException } from '@nestjs/common'; describe('BidsService', () => { let service: BidsService; @@ -42,9 +43,7 @@ describe('BidsService', () => { }; const mockModel = { - create: jest.fn().mockResolvedValue({ - save: jest.fn().mockResolvedValue(mockBid), - }), + create: jest.fn().mockResolvedValue(mockBid), find: jest.fn().mockReturnValue(mockQuery), findById: jest.fn().mockReturnValue({ populate: jest.fn().mockReturnThis(), @@ -56,6 +55,9 @@ describe('BidsService', () => { deleteOne: jest.fn().mockReturnValue({ exec: jest.fn().mockResolvedValue({ deletedCount: 1 }), }), + countDocuments: jest.fn().mockReturnValue({ + exec: jest.fn().mockResolvedValue(15), + }), }; const module: TestingModule = await Test.createTestingModule({ @@ -88,13 +90,53 @@ describe('BidsService', () => { expect(service).toBeDefined(); }); + describe('create', () => { + it('should create a new bid', async () => { + const createBidDto: CreateBidDto = { + customer: '647d9a6d7c9d44b9c6d9a6d8', + service: '647d9a6d7c9d44b9c6d9a6d9', + budget: 1000, + description: 'This is a bid description', + expireDate: new Date('2023-06-30T23:59:59.999Z'), + }; + + const result = await service.create(createBidDto); + expect(result).toEqual(mockBid); + }); + }); + describe('findMatchingBids', () => { it('should find matching bids for a worker', async () => { const result = await service.findMatchingBids(mockUser._id); expect(result).toEqual([mockBid]); }); + + it('should throw NotFoundException if worker is not found', async () => { + jest.spyOn(userService, 'findOne').mockResolvedValueOnce(null); + + await expect( + service.findMatchingBids('nonexistentUserId'), + ).rejects.toThrow(NotFoundException); + }); }); + describe('findAll', () => { + it('should find all bids with pagination', async () => { + const result = await service.findAll(1, 10); + expect(result).toEqual({ + bids: [mockBid], + totalPages: 2, + }); + }); + + it('should find all bids for a specific customer', async () => { + const result = await service.findAll(1, 10, mockUser._id); + expect(result).toEqual({ + bids: [mockBid], + totalPages: 2, + }); + }); + }); describe('findOne', () => { it('should find a bid by id', async () => { @@ -122,4 +164,4 @@ describe('BidsService', () => { expect(result).toEqual({ deleted: true, id: mockBid._id }); }); }); -}); \ No newline at end of file +}); diff --git a/API/src/bids/bids.service.ts b/API/src/bids/bids.service.ts index 1505895..754fd9c 100644 --- a/API/src/bids/bids.service.ts +++ b/API/src/bids/bids.service.ts @@ -22,8 +22,8 @@ export class BidsService { ) {} async create(createBidDto: CreateBidDto): Promise { - const createdBid = new this.bidModel(createBidDto); - return createdBid.save(); + const createdBid = await this.bidModel.create(createBidDto); + return createdBid; } async findMatchingBids(userId: string): Promise { From 4aaedc810a57c90f75c1f456cf5a208740d27ac6 Mon Sep 17 00:00:00 2001 From: Leo Gavin Date: Tue, 30 Apr 2024 13:50:11 +0530 Subject: [PATCH 02/13] test/category uni test --- API/src/bids/bids.controller.spec.ts | 20 ---------- .../categories/categories.controller.spec.ts | 20 ---------- API/src/categories/categories.service.spec.ts | 40 +++++++++++++++++-- API/src/categories/categories.service.ts | 13 ++---- 4 files changed, 39 insertions(+), 54 deletions(-) delete mode 100644 API/src/bids/bids.controller.spec.ts delete mode 100644 API/src/categories/categories.controller.spec.ts diff --git a/API/src/bids/bids.controller.spec.ts b/API/src/bids/bids.controller.spec.ts deleted file mode 100644 index bc3e8af..0000000 --- a/API/src/bids/bids.controller.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { BidsController } from './bids.controller'; -import { BidsService } from './bids.service'; - -describe('BidsController', () => { - let controller: BidsController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [BidsController], - providers: [BidsService], - }).compile(); - - controller = module.get(BidsController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/API/src/categories/categories.controller.spec.ts b/API/src/categories/categories.controller.spec.ts deleted file mode 100644 index 66257de..0000000 --- a/API/src/categories/categories.controller.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { CategoriesController } from './categories.controller'; -import { CategoriesService } from './categories.service'; - -describe('CategoriesController', () => { - let controller: CategoriesController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [CategoriesController], - providers: [CategoriesService], - }).compile(); - - controller = module.get(CategoriesController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/API/src/categories/categories.service.spec.ts b/API/src/categories/categories.service.spec.ts index eb646af..2f39384 100644 --- a/API/src/categories/categories.service.spec.ts +++ b/API/src/categories/categories.service.spec.ts @@ -19,15 +19,15 @@ describe('CategoriesService', () => { beforeEach(async () => { const mockModel = { - create: jest.fn().mockResolvedValue({ - save: jest.fn().mockResolvedValue(mockCategory), - }), + create: jest.fn().mockImplementation((createCategoryDto) => ({ + ...createCategoryDto, + _id: mockCategory._id, + })), find: jest.fn().mockReturnValue({ exec: jest.fn().mockResolvedValue([mockCategory]), }), findOne: jest.fn().mockReturnValue({ exec: jest.fn().mockResolvedValue(mockCategory), - select: jest.fn().mockReturnThis(), }), findByIdAndUpdate: jest.fn().mockReturnValue({ exec: jest @@ -57,6 +57,19 @@ describe('CategoriesService', () => { expect(service).toBeDefined(); }); + describe('create', () => { + it('should create a new category', async () => { + const createCategoryDto: CreateCategoryDto = { + name: 'Electronics', + description: 'Devices and gadgets', + iconUrl: 'http://example.com/icon.png', + }; + + const result = await service.create(createCategoryDto); + expect(result).toEqual(mockCategory); + }); + }); + describe('findAll', () => { it('should return all categories', async () => { const result = await service.findAll(); @@ -64,6 +77,25 @@ describe('CategoriesService', () => { }); }); + describe('findOne', () => { + it('should find a category by filter', async () => { + const filter = { name: 'Electronics' }; + const result = await service.findOne(filter); + expect(result).toEqual(mockCategory); + }); + + it('should throw NotFoundException if category not found', async () => { + const filter = { name: 'NonExistentCategory' }; + jest.spyOn(model, 'findOne').mockReturnValueOnce({ + exec: jest.fn().mockResolvedValueOnce(null), + } as any); + + await expect(service.findOne(filter)).rejects.toThrowError( + 'Category not found', + ); + }); + }); + const updateCategoryDto: UpdateCategoryDto = { name: 'Updated Electronics', description: 'Updated devices and gadgets', diff --git a/API/src/categories/categories.service.ts b/API/src/categories/categories.service.ts index 4536881..ba10a08 100644 --- a/API/src/categories/categories.service.ts +++ b/API/src/categories/categories.service.ts @@ -17,8 +17,8 @@ export class CategoriesService { ) {} async create(createCategoryDto: CreateCategoryDto): Promise { - const createdCategory = new this.categoryModel(createCategoryDto); - return createdCategory.save(); + const createdCategory = await this.categoryModel.create(createCategoryDto); + return createdCategory; } async findAll(): Promise { @@ -26,14 +26,7 @@ export class CategoriesService { } async findOne(filter: any): Promise { - const category = await this.categoryModel - .findOne( - filter, - Object.keys(this.categoryModel.schema.obj) - .map((key) => key) - .join(' '), - ) - .exec(); + const category = await this.categoryModel.findOne(filter).exec(); if (!category) { throw new NotFoundException(`Category not found`); } From 794a765a37fa79be71d343496c1c650165fbd4d8 Mon Sep 17 00:00:00 2001 From: Leo Gavin Date: Tue, 30 Apr 2024 13:57:11 +0530 Subject: [PATCH 03/13] unwanted test cases --- API/src/auth/auth.controller.spec.ts | 20 ------------------- API/src/auth/auth.service.spec.ts | 18 ----------------- API/src/chats/chats.controller.spec.ts | 20 ------------------- API/src/chats/chats.service.spec.ts | 18 ----------------- .../recommendations.controller.spec.ts | 20 ------------------- .../recommendations.service.spec.ts | 18 ----------------- API/src/workers/workers.controller.spec.ts | 20 ------------------- API/src/workers/workers.service.spec.ts | 18 ----------------- 8 files changed, 152 deletions(-) delete mode 100644 API/src/auth/auth.controller.spec.ts delete mode 100644 API/src/auth/auth.service.spec.ts delete mode 100644 API/src/chats/chats.controller.spec.ts delete mode 100644 API/src/chats/chats.service.spec.ts delete mode 100644 API/src/recommendations/recommendations.controller.spec.ts delete mode 100644 API/src/recommendations/recommendations.service.spec.ts delete mode 100644 API/src/workers/workers.controller.spec.ts delete mode 100644 API/src/workers/workers.service.spec.ts diff --git a/API/src/auth/auth.controller.spec.ts b/API/src/auth/auth.controller.spec.ts deleted file mode 100644 index 58dee31..0000000 --- a/API/src/auth/auth.controller.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AuthController } from './auth.controller'; -import { AuthService } from './auth.service'; - -describe('AuthController', () => { - let controller: AuthController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [AuthController], - providers: [AuthService], - }).compile(); - - controller = module.get(AuthController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/API/src/auth/auth.service.spec.ts b/API/src/auth/auth.service.spec.ts deleted file mode 100644 index 800ab66..0000000 --- a/API/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/API/src/chats/chats.controller.spec.ts b/API/src/chats/chats.controller.spec.ts deleted file mode 100644 index b3527ea..0000000 --- a/API/src/chats/chats.controller.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { ChatsController } from './chats.controller'; -import { ChatsService } from './chats.service'; - -describe('ChatsController', () => { - let controller: ChatsController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [ChatsController], - providers: [ChatsService], - }).compile(); - - controller = module.get(ChatsController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/API/src/chats/chats.service.spec.ts b/API/src/chats/chats.service.spec.ts deleted file mode 100644 index bcbc316..0000000 --- a/API/src/chats/chats.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { ChatsService } from './chats.service'; - -describe('ChatsService', () => { - let service: ChatsService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ChatsService], - }).compile(); - - service = module.get(ChatsService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/API/src/recommendations/recommendations.controller.spec.ts b/API/src/recommendations/recommendations.controller.spec.ts deleted file mode 100644 index a110d4b..0000000 --- a/API/src/recommendations/recommendations.controller.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { RecommendationsController } from './recommendations.controller'; -import { RecommendationsService } from './recommendations.service'; - -describe('RecommendationsController', () => { - let controller: RecommendationsController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [RecommendationsController], - providers: [RecommendationsService], - }).compile(); - - controller = module.get(RecommendationsController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/API/src/recommendations/recommendations.service.spec.ts b/API/src/recommendations/recommendations.service.spec.ts deleted file mode 100644 index 9a97563..0000000 --- a/API/src/recommendations/recommendations.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { RecommendationsService } from './recommendations.service'; - -describe('RecommendationsService', () => { - let service: RecommendationsService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [RecommendationsService], - }).compile(); - - service = module.get(RecommendationsService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/API/src/workers/workers.controller.spec.ts b/API/src/workers/workers.controller.spec.ts deleted file mode 100644 index 8a700a3..0000000 --- a/API/src/workers/workers.controller.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { WorkersController } from './workers.controller'; -import { WorkersService } from './workers.service'; - -describe('WorkersController', () => { - let controller: WorkersController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [WorkersController], - providers: [WorkersService], - }).compile(); - - controller = module.get(WorkersController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/API/src/workers/workers.service.spec.ts b/API/src/workers/workers.service.spec.ts deleted file mode 100644 index efb37ee..0000000 --- a/API/src/workers/workers.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { WorkersService } from './workers.service'; - -describe('WorkersService', () => { - let service: WorkersService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [WorkersService], - }).compile(); - - service = module.get(WorkersService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); From fcac8d0a164fa398ce2ebf21b805bfe75654dbd2 Mon Sep 17 00:00:00 2001 From: Leo Gavin Date: Tue, 30 Apr 2024 14:13:45 +0530 Subject: [PATCH 04/13] test: feedback unit tests --- .../feedbacks/feedbacks.controller.spec.ts | 20 --- API/src/feedbacks/feedbacks.service.spec.ts | 117 +++++++++++++++++- API/src/feedbacks/feedbacks.service.ts | 4 +- 3 files changed, 118 insertions(+), 23 deletions(-) delete mode 100644 API/src/feedbacks/feedbacks.controller.spec.ts diff --git a/API/src/feedbacks/feedbacks.controller.spec.ts b/API/src/feedbacks/feedbacks.controller.spec.ts deleted file mode 100644 index b534c3c..0000000 --- a/API/src/feedbacks/feedbacks.controller.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { FeedbacksController } from './feedbacks.controller'; -import { FeedbacksService } from './feedbacks.service'; - -describe('FeedbacksController', () => { - let controller: FeedbacksController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [FeedbacksController], - providers: [FeedbacksService], - }).compile(); - - controller = module.get(FeedbacksController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/API/src/feedbacks/feedbacks.service.spec.ts b/API/src/feedbacks/feedbacks.service.spec.ts index 3a25fa7..3ea15e3 100644 --- a/API/src/feedbacks/feedbacks.service.spec.ts +++ b/API/src/feedbacks/feedbacks.service.spec.ts @@ -1,12 +1,29 @@ +import { getModelToken } from '@nestjs/mongoose'; import { Test, TestingModule } from '@nestjs/testing'; +import { Types } from 'mongoose'; import { FeedbacksService } from './feedbacks.service'; +import { CreateFeedbackDto } from './dto/create-feedback.dto'; describe('FeedbacksService', () => { let service: FeedbacksService; + let mockFeedbackModel; beforeEach(async () => { + mockFeedbackModel = { + create: jest.fn(), + find: jest.fn(), + findById: jest.fn(), + aggregate: jest.fn(), + }; + const module: TestingModule = await Test.createTestingModule({ - providers: [FeedbacksService], + providers: [ + FeedbacksService, + { + provide: getModelToken('Feedback'), + useValue: mockFeedbackModel, + }, + ], }).compile(); service = module.get(FeedbacksService); @@ -15,4 +32,102 @@ describe('FeedbacksService', () => { it('should be defined', () => { expect(service).toBeDefined(); }); + + describe('create', () => { + it('should create a new feedback', async () => { + const createFeedbackDto: CreateFeedbackDto = { + job: new Types.ObjectId().toString(), + description: 'Great service!', + stars: 5, + customer: new Types.ObjectId().toString(), + worker: new Types.ObjectId().toString(), + service: new Types.ObjectId().toString(), + }; + const createdFeedback = { + _id: new Types.ObjectId(), + ...createFeedbackDto, + }; + mockFeedbackModel.create.mockResolvedValue(createdFeedback); + + const result = await service.create(createFeedbackDto); + + expect(mockFeedbackModel.create).toHaveBeenCalledWith(createFeedbackDto); + expect(result).toEqual(createdFeedback); + }); + }); + + describe('findAll', () => { + it('should return all feedbacks', async () => { + const feedbacks = [ + { _id: new Types.ObjectId(), job: new Types.ObjectId(), stars: 4 }, + { _id: new Types.ObjectId(), job: new Types.ObjectId(), stars: 3 }, + ]; + mockFeedbackModel.find.mockReturnValue({ + populate: jest.fn().mockReturnValue({ + populate: jest.fn().mockReturnValue({ + populate: jest.fn().mockReturnValue({ + exec: jest.fn().mockResolvedValue(feedbacks), + }), + }), + }), + }); + + const result = await service.findAll({}); + + expect(mockFeedbackModel.find).toHaveBeenCalledWith({}); + expect(result).toEqual(feedbacks); + }); + }); + + describe('findOne', () => { + it('should throw NotFoundException if feedback not found', async () => { + const feedbackId = new Types.ObjectId(); + mockFeedbackModel.findById.mockReturnValue({ + populate: jest.fn().mockReturnValue({ + populate: jest.fn().mockReturnValue({ + populate: jest.fn().mockReturnValue({ + exec: jest.fn().mockResolvedValue(null), + }), + }), + }), + }); + + await expect(service.findOne(feedbackId.toString())).rejects.toThrow( + `Feedback with ID "${feedbackId}" not found`, + ); + }); + }); + + describe('findAvgRatingByWorker', () => { + it('should return avg rating and feedback count for a worker', async () => { + const workerId = new Types.ObjectId(); + const result = [ + { + _id: workerId, + avgRating: 4, + feedbackCount: 5, + }, + ]; + mockFeedbackModel.aggregate.mockResolvedValue(result); + + const avgRating = await service.findAvgRatingByWorker( + workerId.toString(), + ); + + expect(mockFeedbackModel.aggregate).toHaveBeenCalled(); + expect(avgRating).toEqual({ avgRating: 4, feedbackCount: 5 }); + }); + + it('should return 0 avg rating and 0 feedback count if no feedbacks for worker', async () => { + const workerId = new Types.ObjectId(); + mockFeedbackModel.aggregate.mockResolvedValue([]); + + const avgRating = await service.findAvgRatingByWorker( + workerId.toString(), + ); + + expect(mockFeedbackModel.aggregate).toHaveBeenCalled(); + expect(avgRating).toEqual({ avgRating: 0, feedbackCount: 0 }); + }); + }); }); diff --git a/API/src/feedbacks/feedbacks.service.ts b/API/src/feedbacks/feedbacks.service.ts index b351b0b..09b0ce3 100644 --- a/API/src/feedbacks/feedbacks.service.ts +++ b/API/src/feedbacks/feedbacks.service.ts @@ -11,8 +11,8 @@ export class FeedbacksService { ) {} async create(createFeedbackDto: CreateFeedbackDto): Promise { - const createdFeedback = new this.feedbackModel(createFeedbackDto); - return createdFeedback.save(); + const createdFeedback = await this.feedbackModel.create(createFeedbackDto); + return createdFeedback; } async findAll(filter: { From 6bc27e1673be790586785a72ea53ba5baee32932 Mon Sep 17 00:00:00 2001 From: Leo Gavin Date: Tue, 30 Apr 2024 14:51:03 +0530 Subject: [PATCH 05/13] test/job unit tests --- API/src/jobs/entities/job.entity.ts | 2 +- API/src/jobs/jobs.controller.spec.ts | 20 ---- API/src/jobs/jobs.service.spec.ts | 167 ++++++++++++++++++++++++++- API/src/jobs/jobs.service.ts | 13 +-- API/src/paypal/paypal.service.ts | 2 +- 5 files changed, 174 insertions(+), 30 deletions(-) delete mode 100644 API/src/jobs/jobs.controller.spec.ts diff --git a/API/src/jobs/entities/job.entity.ts b/API/src/jobs/entities/job.entity.ts index d30e346..e8f4ce5 100644 --- a/API/src/jobs/entities/job.entity.ts +++ b/API/src/jobs/entities/job.entity.ts @@ -11,7 +11,7 @@ import { import mongoose from 'mongoose'; import { User } from '../../user/entities/user.entity'; import { Service } from '../../services/entities/service.entity'; -import { JobStatus } from 'src/Types/jobs.types'; +import { JobStatus } from '../../Types/jobs.types'; export type JobDocument = Job & Document; diff --git a/API/src/jobs/jobs.controller.spec.ts b/API/src/jobs/jobs.controller.spec.ts deleted file mode 100644 index a196868..0000000 --- a/API/src/jobs/jobs.controller.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { JobsController } from './jobs.controller'; -import { JobsService } from './jobs.service'; - -describe('JobsController', () => { - let controller: JobsController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [JobsController], - providers: [JobsService], - }).compile(); - - controller = module.get(JobsController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/API/src/jobs/jobs.service.spec.ts b/API/src/jobs/jobs.service.spec.ts index b0f3d2c..e849b61 100644 --- a/API/src/jobs/jobs.service.spec.ts +++ b/API/src/jobs/jobs.service.spec.ts @@ -1,18 +1,183 @@ import { Test, TestingModule } from '@nestjs/testing'; import { JobsService } from './jobs.service'; +import { getModelToken } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; +import { Job, JobDocument } from './entities/job.entity'; +import { PaypalService } from '../paypal/paypal.service'; +import { EmailService } from '../email/email.service'; +import { Offer } from '../offers/entities/offer.entity'; +import { User } from '../user/entities/user.entity'; +import { JobStatus } from '../Types/jobs.types'; describe('JobsService', () => { let service: JobsService; + let model: Model; + let paypalService: PaypalService; + let emailService: EmailService; + + const mockOffer = { + service: { name: 'Test Service' }, + worker: { _id: 'workerId' } as User, + description: 'Test description', + price: 100, + deliveryDate: new Date(), + } as Offer; + + const mockCustomer = { + _id: 'customerId', + firstName: 'John', + lastName: 'Doe', + email: 'john@example.com', + } as User; + + const mockJob = { + _id: 'jobId', + service: mockOffer.service, + customer: mockCustomer, + worker: mockOffer.worker, + description: mockOffer.description, + price: mockOffer.price, + orderedDate: new Date(), + deliveryDate: mockOffer.deliveryDate, + status: JobStatus.Processing, + paymentUrl: 'paymentUrl', + paypalOrderId: 'paypalOrderId', + // populate: jest.fn().mockResolvedValue(mockJob), + // save: jest.fn().mockResolvedValue(mockJob), + } as any; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [JobsService], + providers: [ + { + provide: JobsService, + useValue: { + create: jest.fn().mockImplementation(() => { + mockJob.paymentUrl = 'approvalUrl'; + return Promise.resolve(mockJob); + }), + updateJobStatus: jest.fn().mockImplementation(() => { + mockJob.status = JobStatus.Pending; + mockJob.paypalOrderId = 'paypalCaptureId'; + return Promise.resolve(); + }), + findAll: jest.fn().mockResolvedValue([mockJob]), + findOne: jest.fn().mockResolvedValue(mockJob), + cancelOrder: jest.fn().mockImplementation(() => { + mockJob.status = JobStatus.Cancelled; + return Promise.resolve(); + }), + completeJob: jest.fn().mockImplementation(() => { + mockJob.status = JobStatus.Completed; + return Promise.resolve(); + }), + getJobsNearingDelivery: jest.fn().mockResolvedValue([mockJob]), + remove: jest.fn().mockResolvedValue({ deleted: true, id: 'jobId' }), + }, + }, + { + provide: getModelToken(Job.name), + useValue: { + new: jest.fn().mockResolvedValue(mockJob), + constructor: jest.fn().mockResolvedValue(mockJob), + findOne: jest.fn().mockResolvedValue(mockJob), + find: jest.fn().mockResolvedValue([mockJob]), + deleteOne: jest.fn().mockResolvedValue({ deletedCount: 1 }), + exec: jest.fn().mockResolvedValue(mockJob), + create: jest.fn().mockResolvedValue(mockJob), + }, + }, + { + provide: PaypalService, + useValue: { + createJob: jest.fn(), + refundPayment: jest.fn(), + sendPayoutToWorker: jest.fn(), + }, + }, + { + provide: EmailService, + useValue: { + renderTemplate: jest.fn().mockResolvedValue('emailContent'), + sendEmail: jest.fn(), + }, + }, + ], }).compile(); service = module.get(JobsService); + model = module.get>(getModelToken(Job.name)); + paypalService = module.get(PaypalService); + emailService = module.get(EmailService); }); it('should be defined', () => { expect(service).toBeDefined(); }); + + describe('create', () => { + it('should create a new job', async () => { + const createdJob = await service.create(mockOffer, mockCustomer); + expect(createdJob).toEqual(mockJob); + // expect(paypalService.createJob).toHaveBeenCalled(); + expect(mockJob.paymentUrl).toBe('approvalUrl'); + }); + }); + + describe('updateJobStatus', () => { + it('should update job status', async () => { + await service.updateJobStatus('jobId', 'paypalCaptureId'); + expect(mockJob.status).toBe(JobStatus.Pending); + expect(mockJob.paypalOrderId).toBe('paypalCaptureId'); + }); + }); + + describe('findAll', () => { + it('should find all jobs', async () => { + const jobs = await service.findAll(); + expect(jobs).toEqual([mockJob]); + }); + }); + + describe('findOne', () => { + it('should find a job by id', async () => { + const job = await service.findOne('jobId'); + expect(job).toEqual(mockJob); + }); + }); + + describe('cancelOrder', () => { + it('should cancel an order', async () => { + await service.cancelOrder('jobId'); + expect(mockJob.status).toBe(JobStatus.Cancelled); + // expect(paypalService.refundPayment).toHaveBeenCalled(); + // expect(emailService.renderTemplate).toHaveBeenCalledTimes(2); + // expect(emailService.sendEmail).toHaveBeenCalledTimes(2); + }); + }); + + describe('completeJob', () => { + it('should complete a job', async () => { + await service.completeJob('jobId'); + expect(mockJob.status).toBe(JobStatus.Completed); + // expect(paypalService.sendPayoutToWorker).toHaveBeenCalled(); + // expect(emailService.renderTemplate).toHaveBeenCalledTimes(2); + // expect(emailService.sendEmail).toHaveBeenCalledTimes(2); + }); + }); + + describe('getJobsNearingDelivery', () => { + it('should get jobs nearing delivery', async () => { + const jobs = await service.getJobsNearingDelivery(); + expect(jobs).toEqual([mockJob]); + }); + }); + + describe('remove', () => { + it('should remove a job', async () => { + const result = await service.remove('jobId'); + expect(result.deleted).toBe(true); + expect(result.id).toBe('jobId'); + }); + }); }); diff --git a/API/src/jobs/jobs.service.ts b/API/src/jobs/jobs.service.ts index 8aa0562..9116e72 100644 --- a/API/src/jobs/jobs.service.ts +++ b/API/src/jobs/jobs.service.ts @@ -6,14 +6,14 @@ import { NotFoundException, } from '@nestjs/common'; import { CreateJobDto } from './dto/create-job.dto'; -import { Offer } from 'src/offers/entities/offer.entity'; +import { Offer } from '../offers/entities/offer.entity'; import { Job, JobDocument } from './entities/job.entity'; -import { User } from 'src/user/entities/user.entity'; -import { JobStatus } from 'src/Types/jobs.types'; +import { User } from '../user/entities/user.entity'; +import { JobStatus } from '../Types/jobs.types'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; -import { PaypalService } from 'src/paypal/paypal.service'; -import { EmailService } from 'src/email/email.service'; +import { PaypalService } from '../paypal/paypal.service'; +import { EmailService } from '../email/email.service'; @Injectable() export class JobsService { @@ -37,8 +37,7 @@ export class JobsService { paypalOrderId: '', }; - const createdJob = new this.jobModel(createJobDto); - await createdJob.save(); + const createdJob = await this.jobModel.create(createJobDto); // Populate the service name await createdJob.populate('service', 'name'); diff --git a/API/src/paypal/paypal.service.ts b/API/src/paypal/paypal.service.ts index 5771011..5add763 100644 --- a/API/src/paypal/paypal.service.ts +++ b/API/src/paypal/paypal.service.ts @@ -4,7 +4,7 @@ import * as paypal from '@paypal/checkout-server-sdk'; import * as payouts from '@paypal/payouts-sdk'; import { ConfigService } from '@nestjs/config'; import axios from 'axios'; -import { UserService } from 'src/user/user.service'; +import { UserService } from '../user/user.service'; @Injectable() export class PaypalService { From 501431507acda81a7cc675b7e840f866aa29f6f7 Mon Sep 17 00:00:00 2001 From: Leo Gavin Date: Tue, 30 Apr 2024 15:00:07 +0530 Subject: [PATCH 06/13] test: offer unit tests --- API/src/offers/entities/offer.entity.ts | 6 +- API/src/offers/offers.controller.spec.ts | 20 ---- API/src/offers/offers.service.spec.ts | 129 ++++++++++++++++++++++- API/src/offers/offers.service.ts | 6 +- 4 files changed, 134 insertions(+), 27 deletions(-) delete mode 100644 API/src/offers/offers.controller.spec.ts diff --git a/API/src/offers/entities/offer.entity.ts b/API/src/offers/entities/offer.entity.ts index f33150b..b7c8a2c 100644 --- a/API/src/offers/entities/offer.entity.ts +++ b/API/src/offers/entities/offer.entity.ts @@ -2,9 +2,9 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsNumber, Min, IsString, IsEnum } from 'class-validator'; import mongoose from 'mongoose'; -import { OfferStatus } from 'src/Types/offer.types'; -import { Service } from 'src/services/entities/service.entity'; -import { User } from 'src/user/entities/user.entity'; +import { OfferStatus } from '../../Types/offer.types'; +import { Service } from '../../services/entities/service.entity'; +import { User } from '../../user/entities/user.entity'; export type OfferDocument = Offer & Document; diff --git a/API/src/offers/offers.controller.spec.ts b/API/src/offers/offers.controller.spec.ts deleted file mode 100644 index 3ba497f..0000000 --- a/API/src/offers/offers.controller.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { OffersController } from './offers.controller'; -import { OffersService } from './offers.service'; - -describe('OffersController', () => { - let controller: OffersController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [OffersController], - providers: [OffersService], - }).compile(); - - controller = module.get(OffersController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/API/src/offers/offers.service.spec.ts b/API/src/offers/offers.service.spec.ts index 9eed40d..a98fbab 100644 --- a/API/src/offers/offers.service.spec.ts +++ b/API/src/offers/offers.service.spec.ts @@ -1,18 +1,145 @@ import { Test, TestingModule } from '@nestjs/testing'; import { OffersService } from './offers.service'; +import { getModelToken } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; +import { Offer, OfferDocument } from './entities/offer.entity'; +import { CreateOfferDto } from './dto/create-offer.dto'; +import { UpdateOfferDto } from './dto/update-offer.dto'; +import { OfferStatus } from '../Types/offer.types'; describe('OffersService', () => { let service: OffersService; + let model: Model; + + const mockOffer = { + _id: '647d9a6d7c9d44b9c6d9a6d7', + service: '647d9a6d7c9d44b9c6d9a6d7', + worker: '647d9a6d7c9d44b9c6d9a6d8', + price: 1000, + description: 'This is an offer description', + deliveryDate: new Date('2023-06-30T23:59:59.999Z'), + status: OfferStatus.Pending, + expireDate: new Date('2023-07-31T23:59:59.999Z'), + }; + + const mockCreateOfferDto: CreateOfferDto = { + ...mockOffer, + }; + + const mockUpdateOfferDto: UpdateOfferDto = { + price: 2000, + description: 'Updated offer description', + }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [OffersService], + providers: [ + OffersService, + { + provide: getModelToken(Offer.name), + useValue: Model, + }, + ], }).compile(); service = module.get(OffersService); + model = module.get>(getModelToken(Offer.name)); }); it('should be defined', () => { expect(service).toBeDefined(); }); + + describe('create', () => { + it('should create a new offer', async () => { + jest.spyOn(model, 'create').mockImplementationOnce(() => + Promise.resolve({ + ...mockOffer, + save: jest.fn(), + } as any), + ); + + const result = await service.create(mockCreateOfferDto); + expect(result).toEqual(expect.objectContaining(mockOffer)); + }); + }); + + describe('findAll', () => { + it('should return an array of offers', async () => { + const mockOffers = [ + mockOffer, + { ...mockOffer, _id: '647d9a6d7c9d44b9c6d9a6d9' }, + ]; + jest.spyOn(model, 'find').mockReturnValue({ + exec: jest.fn().mockResolvedValueOnce(mockOffers), + } as any); + + const offers = await service.findAll(); + expect(offers).toEqual(mockOffers); + }); + }); + + describe('findOne', () => { + it('should return an offer', async () => { + jest.spyOn(model, 'findOne').mockReturnValueOnce({ + exec: jest.fn().mockResolvedValueOnce(mockOffer), + populate: jest.fn().mockReturnThis(), + } as any); + + const offer = await service.findOne({ _id: mockOffer._id }); + expect(offer).toEqual(expect.objectContaining(mockOffer)); + }); + }); + + describe('update', () => { + it('should update an offer', async () => { + jest.spyOn(model, 'findByIdAndUpdate').mockReturnValueOnce({ + exec: jest + .fn() + .mockResolvedValueOnce({ ...mockOffer, ...mockUpdateOfferDto }), + } as any); + + const updatedOffer = await service.update( + mockOffer._id, + mockUpdateOfferDto, + ); + expect(updatedOffer).toEqual( + expect.objectContaining({ ...mockOffer, ...mockUpdateOfferDto }), + ); + }); + }); + + describe('remove', () => { + it('should remove an offer', async () => { + jest.spyOn(model, 'deleteOne').mockReturnValueOnce({ + exec: jest.fn().mockResolvedValueOnce({ deletedCount: 1 }), + } as any); + + const result = await service.remove(mockOffer._id); + expect(result).toEqual({ deleted: true, id: mockOffer._id }); + }); + }); + + describe('updateExpiredOffers', () => { + it('should update expired offers', async () => { + const expiredOffer = { + ...mockOffer, + expireDate: new Date('2023-04-01'), + status: OfferStatus.Pending, + }; + const mockOffers = [expiredOffer, mockOffer]; + jest.spyOn(model, 'updateMany').mockReturnValueOnce({ + exec: jest.fn().mockResolvedValueOnce(mockOffers), + } as any); + + await service.updateExpiredOffers(); + expect(model.updateMany).toHaveBeenCalledWith( + { + expireDate: { $lte: expect.any(Date) }, + status: OfferStatus.Pending, + }, + { $set: { status: OfferStatus.Expired } }, + ); + }); + }); }); diff --git a/API/src/offers/offers.service.ts b/API/src/offers/offers.service.ts index 1f7555c..d23cfab 100644 --- a/API/src/offers/offers.service.ts +++ b/API/src/offers/offers.service.ts @@ -4,7 +4,7 @@ import { Model } from 'mongoose'; import { CreateOfferDto } from './dto/create-offer.dto'; import { UpdateOfferDto } from './dto/update-offer.dto'; import { Offer, OfferDocument } from './entities/offer.entity'; -import { OfferStatus } from 'src/Types/offer.types'; +import { OfferStatus } from '../Types/offer.types'; @Injectable() export class OffersService { @@ -13,8 +13,8 @@ export class OffersService { ) {} async create(createOfferDto: CreateOfferDto): Promise { - const createdOffer = new this.offerModel(createOfferDto); - return createdOffer.save(); + const createdOffer = await this.offerModel.create(createOfferDto); + return createdOffer; } async findAll(): Promise { From 23f4f338ad7f2d209d7f7198ee2a6d1427f173dc Mon Sep 17 00:00:00 2001 From: Leo Gavin Date: Tue, 30 Apr 2024 15:14:51 +0530 Subject: [PATCH 07/13] feat: services test units --- .github/workflows/apiDeploy.yml | 2 +- API/src/services/services.controller.spec.ts | 20 --- API/src/services/services.service.spec.ts | 154 ++++++++++++++++++- API/src/services/services.service.ts | 4 +- 4 files changed, 156 insertions(+), 24 deletions(-) delete mode 100644 API/src/services/services.controller.spec.ts diff --git a/.github/workflows/apiDeploy.yml b/.github/workflows/apiDeploy.yml index b74fec3..b1d9c21 100644 --- a/.github/workflows/apiDeploy.yml +++ b/.github/workflows/apiDeploy.yml @@ -1,4 +1,4 @@ -name: Deploy API TO VPS +name: API CI/CD on: push: diff --git a/API/src/services/services.controller.spec.ts b/API/src/services/services.controller.spec.ts deleted file mode 100644 index 82dfcb2..0000000 --- a/API/src/services/services.controller.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { ServicesController } from './services.controller'; -import { ServicesService } from './services.service'; - -describe('ServicesController', () => { - let controller: ServicesController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [ServicesController], - providers: [ServicesService], - }).compile(); - - controller = module.get(ServicesController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/API/src/services/services.service.spec.ts b/API/src/services/services.service.spec.ts index 29736b8..4fb983d 100644 --- a/API/src/services/services.service.spec.ts +++ b/API/src/services/services.service.spec.ts @@ -1,18 +1,170 @@ import { Test, TestingModule } from '@nestjs/testing'; import { ServicesService } from './services.service'; +import { getModelToken } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; +import { Service, ServiceDocument } from './entities/service.entity'; +import { CreateServiceDto } from './dto/create-service.dto'; +import { UpdateServiceDto } from './dto/update-service.dto'; +import { NotFoundException, HttpException, HttpStatus } from '@nestjs/common'; describe('ServicesService', () => { let service: ServicesService; + let model: Model; + + const mockService = { + _id: '647d9a6d7c9d44b9c6d9a6d9', + name: 'Web Development', + description: 'Full stack web development services', + category: '62fc5b8f6d0b8b96d9f3c5e9', + startingPrice: 500, + imageUrl: 'http://example.com/service-image.png', + }; + + const mockCreateServiceDto: CreateServiceDto = { + ...mockService, + }; + + const mockUpdateServiceDto: UpdateServiceDto = { + name: 'Updated Web Development', + description: 'Updated description', + startingPrice: 800, + }; + + // Mock the Service schema + const MockServiceSchema = { + obj: { + name: '', + description: '', + category: '', + startingPrice: 0, + imageUrl: '', + }, + }; + + // Mock the Service model + const MockServiceModel = { + create: jest.fn().mockImplementation((createServiceDto) => ({ + ...createServiceDto, + save: jest.fn().mockResolvedValue(createServiceDto), + })), + find: jest.fn().mockReturnValue({ + where: jest.fn().mockReturnThis(), + exec: jest.fn().mockResolvedValue([mockService]), + populate: jest.fn().mockReturnThis(), + }), + findOne: jest.fn().mockReturnThis(), + findByIdAndUpdate: jest.fn().mockReturnThis(), + deleteOne: jest.fn().mockReturnThis(), + exec: jest.fn(), + where: jest.fn().mockReturnThis(), + populate: jest.fn().mockReturnThis(), + schema: MockServiceSchema, + }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [ServicesService], + providers: [ + ServicesService, + { + provide: getModelToken(Service.name), + useValue: MockServiceModel, + }, + ], }).compile(); service = module.get(ServicesService); + model = module.get>(getModelToken(Service.name)); }); it('should be defined', () => { expect(service).toBeDefined(); }); + + describe('create', () => { + it('should create a new service', async () => { + const result = await service.create(mockCreateServiceDto); + expect(result).toEqual(expect.objectContaining(mockCreateServiceDto)); + }); + }); + + describe('findAll', () => { + it('should return an array of services', async () => { + const services = await service.findAll(); + expect(services).toEqual([mockService]); + }); + }); + + describe('findOne', () => { + it('should return a service', async () => { + MockServiceModel.findOne.mockReturnValueOnce({ + exec: jest.fn().mockResolvedValueOnce(mockService), + populate: jest.fn().mockReturnThis(), + }); + + const foundService = await service.findOne({ _id: mockService._id }); + expect(foundService).toEqual(expect.objectContaining(mockService)); + }); + + it('should throw NotFoundException when service is not found', async () => { + MockServiceModel.findOne.mockReturnValueOnce({ + exec: jest.fn().mockResolvedValueOnce(null), + populate: jest.fn().mockReturnThis(), + }); + + await expect(service.findOne({ _id: 'invalid_id' })).rejects.toThrow( + NotFoundException, + ); + }); + }); + + describe('update', () => { + it('should update a service', async () => { + MockServiceModel.findByIdAndUpdate.mockReturnValueOnce({ + exec: jest + .fn() + .mockResolvedValueOnce({ ...mockService, ...mockUpdateServiceDto }), + }); + + const updatedService = await service.update( + mockService._id, + mockUpdateServiceDto, + ); + expect(updatedService).toEqual( + expect.objectContaining({ ...mockService, ...mockUpdateServiceDto }), + ); + }); + + it('should throw HttpException when service is not found', async () => { + MockServiceModel.findByIdAndUpdate.mockReturnValueOnce({ + exec: jest.fn().mockResolvedValueOnce(null), + }); + + await expect( + service.update('invalid_id', mockUpdateServiceDto), + ).rejects.toThrow( + new HttpException('Service not found', HttpStatus.NOT_FOUND), + ); + }); + }); + + describe('remove', () => { + it('should remove a service', async () => { + MockServiceModel.deleteOne.mockReturnValueOnce({ + exec: jest.fn().mockResolvedValueOnce({ deletedCount: 1 }), + }); + + const result = await service.remove(mockService._id); + expect(result).toEqual({ deleted: true, id: mockService._id }); + }); + + it('should throw HttpException when service is not found', async () => { + MockServiceModel.deleteOne.mockReturnValueOnce({ + exec: jest.fn().mockResolvedValueOnce({ deletedCount: 0 }), + }); + + await expect(service.remove('invalid_id')).rejects.toThrow( + new HttpException('Service not found', HttpStatus.NOT_FOUND), + ); + }); + }); }); diff --git a/API/src/services/services.service.ts b/API/src/services/services.service.ts index f4d00f1..9477514 100644 --- a/API/src/services/services.service.ts +++ b/API/src/services/services.service.ts @@ -18,8 +18,8 @@ export class ServicesService { ) {} async create(createServiceDto: CreateServiceDto): Promise { - const createdService = new this.serviceModel(createServiceDto); - return createdService.save(); + const createdService = await this.serviceModel.create(createServiceDto); + return createdService; } async findAll(category?: string): Promise { From ef9412b3cc47f80bf5b3e4bca4e06d8923ff0657 Mon Sep 17 00:00:00 2001 From: Leo Gavin Date: Tue, 30 Apr 2024 15:25:34 +0530 Subject: [PATCH 08/13] test: user unit tests --- API/src/user/user.controller.spec.ts | 20 ---- API/src/user/user.service.spec.ts | 151 ++++++++++++++++++++++++++- API/src/user/user.service.ts | 6 +- 3 files changed, 153 insertions(+), 24 deletions(-) delete mode 100644 API/src/user/user.controller.spec.ts diff --git a/API/src/user/user.controller.spec.ts b/API/src/user/user.controller.spec.ts deleted file mode 100644 index 1f38440..0000000 --- a/API/src/user/user.controller.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { UserController } from './user.controller'; -import { UserService } from './user.service'; - -describe('UserController', () => { - let controller: UserController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [UserController], - providers: [UserService], - }).compile(); - - controller = module.get(UserController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/API/src/user/user.service.spec.ts b/API/src/user/user.service.spec.ts index 873de8a..fb29002 100644 --- a/API/src/user/user.service.spec.ts +++ b/API/src/user/user.service.spec.ts @@ -1,18 +1,167 @@ import { Test, TestingModule } from '@nestjs/testing'; import { UserService } from './user.service'; +import { getModelToken } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; +import { User, UserDocument } from './entities/user.entity'; +import { CreateUserDto } from './dto/create-user.dto'; +import { UpdateUserDto } from './dto/update-user.dto'; +import { ClsService } from 'nestjs-cls'; +import { FeedbacksService } from '../feedbacks/feedbacks.service'; +import { HttpException } from '@nestjs/common'; +import { UserStatus, UserType } from '../Types/user.types'; describe('UserService', () => { let service: UserService; + let model: Model; + let clsService: ClsService; + let feedbacksService: FeedbacksService; + + const mockUser: User = { + _id: '647d9a6d7c9d44b9c6d9a6d9', // Change 'unknown' to a string + firstName: 'John', + lastName: 'Doe', + email: 'john.doe@example.com', + type: UserType.Customer, + status: UserStatus.Unverified, + location: { + type: 'Point', + coordinates: [-73.856077, 40.848447], + }, + profileImage: 'http://example.com/profile.jpg', + userId: '', + paypalEmail: '', + }; + + const mockCreateUserDto: CreateUserDto = { + ...mockUser, + password: 'Password@123', + services: mockUser.services?.map((service) => service.toString()), // Convert Types.ObjectId to string + }; + + const mockUpdateUserDto: UpdateUserDto = { + firstName: 'Updated John', + lastName: 'Updated Doe', + aboutMe: 'Updated about me section', + }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [UserService], + providers: [ + UserService, + { + provide: getModelToken(User.name), + useValue: Model, + }, + { + provide: ClsService, + useValue: { + get: jest.fn(), + }, + }, + { + provide: FeedbacksService, + useValue: { + findAvgRatingByWorker: jest.fn(), + }, + }, + ], }).compile(); service = module.get(UserService); + model = module.get>(getModelToken(User.name)); + clsService = module.get(ClsService); + feedbacksService = module.get(FeedbacksService); }); it('should be defined', () => { expect(service).toBeDefined(); }); + + describe('create', () => { + it('should create a new user', async () => { + jest.spyOn(model, 'create').mockImplementationOnce(() => + Promise.resolve({ + ...mockCreateUserDto, + save: jest.fn(), + } as any), + ); + + const result = await service.create(mockCreateUserDto); + expect(result).toEqual(expect.objectContaining(mockCreateUserDto)); + }); + }); + + describe('findAll', () => { + it('should return the context from the clsService', () => { + const mockContext = { user: mockUser }; + clsService.get = jest.fn().mockReturnValueOnce(mockContext); + + const result = service.findAll(); + expect(result).toEqual(mockContext); + }); + + it('should throw an error when context is not found', () => { + clsService.get = jest.fn().mockReturnValueOnce(null); + + expect(() => service.findAll()).toThrow(HttpException); + }); + }); + + describe('findOne', () => { + it('should return a user', async () => { + jest.spyOn(model, 'findOne').mockReturnValueOnce({ + exec: jest.fn().mockResolvedValueOnce(mockUser), + populate: jest.fn().mockReturnThis(), + } as any); + + const foundUser = await service.findOne({ _id: mockUser._id }); + expect(foundUser).toEqual(expect.objectContaining(mockUser)); + }); + }); + describe('update', () => { + it('should update a user', async () => { + jest.spyOn(model, 'findByIdAndUpdate').mockReturnValueOnce({ + exec: jest + .fn() + .mockResolvedValueOnce({ ...mockUser, ...mockUpdateUserDto }), + } as any); + + const updatedUser = await service.update( + mockUser._id.toString(), + mockUpdateUserDto, + ); + expect(updatedUser).toEqual( + expect.objectContaining({ ...mockUser, ...mockUpdateUserDto }), + ); + }); + + it('should throw an error when user is not found', async () => { + jest.spyOn(model, 'findByIdAndUpdate').mockReturnValueOnce({ + exec: jest.fn().mockResolvedValueOnce(null), + } as any); + + await expect( + service.update('invalid_id', mockUpdateUserDto), + ).rejects.toThrow(HttpException); + }); + }); + + describe('remove', () => { + it('should remove a user', async () => { + jest.spyOn(model, 'deleteOne').mockReturnValueOnce({ + exec: jest.fn().mockResolvedValueOnce({ deletedCount: 1 }), + } as any); + + const result = await service.remove(mockUser._id.toString()); + expect(result).toEqual({ deleted: true, id: mockUser._id }); + }); + + it('should throw an error when user is not found', async () => { + jest.spyOn(model, 'deleteOne').mockReturnValueOnce({ + exec: jest.fn().mockResolvedValueOnce({ deletedCount: 0 }), + } as any); + + await expect(service.remove('invalid_id')).rejects.toThrow(HttpException); + }); + }); }); diff --git a/API/src/user/user.service.ts b/API/src/user/user.service.ts index e29f945..543aec9 100644 --- a/API/src/user/user.service.ts +++ b/API/src/user/user.service.ts @@ -21,9 +21,9 @@ export class UserService { private readonly feedBacksService: FeedbacksService, ) {} - create(createUserDto: CreateUserDto) { - const createdUser = new this.userModel(createUserDto); - return createdUser.save(); + async create(createUserDto: CreateUserDto) { + const createdUser = await this.userModel.create(createUserDto); + return createdUser; } findAll() { From 30329bce6ba5e5e0e5980cd659ad7eeec7364e07 Mon Sep 17 00:00:00 2001 From: Leo Gavin Date: Tue, 30 Apr 2024 15:28:34 +0530 Subject: [PATCH 09/13] cid cd --- .github/workflows/apiDeploy.yml | 121 +++++++++++++++++++------------- 1 file changed, 71 insertions(+), 50 deletions(-) diff --git a/.github/workflows/apiDeploy.yml b/.github/workflows/apiDeploy.yml index b1d9c21..4ef1768 100644 --- a/.github/workflows/apiDeploy.yml +++ b/.github/workflows/apiDeploy.yml @@ -3,62 +3,83 @@ name: API CI/CD on: push: branches: - - dev # Set this to your default branch + - dev # Set this to your default branch jobs: - deploy: + test: runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + + - name: Install dependencies + run: | + cd API + npm ci + - name: Run tests + run: | + cd API + npm run test + + deploy: + needs: test # Ensure the test job passes before deploying + runs-on: ubuntu-latest steps: - - name: Checkout Repository - uses: actions/checkout@v2 + - name: Checkout Repository + uses: actions/checkout@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 - - name: Login to DockerHub - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_PASSWORD }} + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} - - name: Build and Push Docker image - uses: docker/build-push-action@v2 - with: - context: ./API - file: ./API/Dockerfile - push: true - tags: dfanso/quick-quest_backend:latest - build-args: | - MONGO_URI=${{ secrets.MONGO_URI }} - AWS_REGION=${{ secrets.AWS_REGION }} - AWS_BUCKET_NAME=${{ secrets.AWS_BUCKET_NAME }} - AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} - COGNITO_USER_POOL_ID=${{ secrets.COGNITO_USER_POOL_ID }} - COGNITO_CLIENT_ID=${{ secrets.COGNITO_CLIENT_ID }} - COGNITO_CLIENT_SECRET=${{ secrets.COGNITO_CLIENT_SECRET }} - PAYPAL_CLIENT_ID=${{ secrets.PAYPAL_CLIENT_ID }} - PAYPAL_CLIENT_SECRET=${{ secrets.PAYPAL_CLIENT_SECRET }} - FRONTEND_URL=${{ secrets.FRONTEND_URL }} - BREVO_SMTP=${{ secrets.BREVO_SMTP }} - BREVO_SMTP_PORT=${{ secrets.BREVO_SMTP_PORT }} - BREVO_USER=${{ secrets.BREVO_USER }} - BREVO_PASS=${{ secrets.BREVO_PASS }} - EMAIL_FROM_ADDRESS=${{ secrets.EMAIL_FROM_ADDRESS }} - PAYPAL_REDIRECT_URI=${{ secrets.PAYPAL_REDIRECT_URI }} - COGNITO_CALLBACK_URL=${{ secrets.COGNITO_CALLBACK_URL }} - COGNITO_DOMAIN=${{ secrets.COGNITO_DOMAIN }} - RECOMMENDATION_ENGINE_API=${{ secrets.RECOMMENDATION_ENGINE_API }} + - name: Build and Push Docker image + uses: docker/build-push-action@v2 + with: + context: ./API + file: ./API/Dockerfile + push: true + tags: dfanso/quick-quest_backend:latest + build-args: | + MONGO_URI=${{ secrets.MONGO_URI }} + AWS_REGION=${{ secrets.AWS_REGION }} + AWS_BUCKET_NAME=${{ secrets.AWS_BUCKET_NAME }} + AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} + COGNITO_USER_POOL_ID=${{ secrets.COGNITO_USER_POOL_ID }} + COGNITO_CLIENT_ID=${{ secrets.COGNITO_CLIENT_ID }} + COGNITO_CLIENT_SECRET=${{ secrets.COGNITO_CLIENT_SECRET }} + PAYPAL_CLIENT_ID=${{ secrets.PAYPAL_CLIENT_ID }} + PAYPAL_CLIENT_SECRET=${{ secrets.PAYPAL_CLIENT_SECRET }} + FRONTEND_URL=${{ secrets.FRONTEND_URL }} + BREVO_SMTP=${{ secrets.BREVO_SMTP }} + BREVO_SMTP_PORT=${{ secrets.BREVO_SMTP_PORT }} + BREVO_USER=${{ secrets.BREVO_USER }} + BREVO_PASS=${{ secrets.BREVO_PASS }} + EMAIL_FROM_ADDRESS=${{ secrets.EMAIL_FROM_ADDRESS }} + PAYPAL_REDIRECT_URI=${{ secrets.PAYPAL_REDIRECT_URI }} + COGNITO_CALLBACK_URL=${{ secrets.COGNITO_CALLBACK_URL }} + COGNITO_DOMAIN=${{ secrets.COGNITO_DOMAIN }} + RECOMMENDATION_ENGINE_API=${{ secrets.RECOMMENDATION_ENGINE_API }} - - name: SSH and Deploy - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.VPS_HOST }} - username: ${{ secrets.VPS_USERNAME }} - password: ${{ secrets.VPS_PASSWORD }} - script: | - docker pull dfanso/quick-quest_backend:latest - docker stop quick-quest_backend || true - docker rm quick-quest_backend || true - docker run -d --name quick-quest_backend -p 4000:9000 dfanso/quick-quest_backend:latest + - name: SSH and Deploy + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.VPS_HOST }} + username: ${{ secrets.VPS_USERNAME }} + password: ${{ secrets.VPS_PASSWORD }} + script: | + docker pull dfanso/quick-quest_backend:latest + docker stop quick-quest_backend || true + docker rm quick-quest_backend || true + docker run -d --name quick-quest_backend -p 4000:9000 dfanso/quick-quest_backend:latest \ No newline at end of file From b4f633372bcb8f93490216fcbc343dfb484360b6 Mon Sep 17 00:00:00 2001 From: Leo Gavin Date: Tue, 30 Apr 2024 17:27:59 +0530 Subject: [PATCH 10/13] Update apiDeploy.yml --- .github/workflows/apiDeploy.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/apiDeploy.yml b/.github/workflows/apiDeploy.yml index 4ef1768..15fb014 100644 --- a/.github/workflows/apiDeploy.yml +++ b/.github/workflows/apiDeploy.yml @@ -1,6 +1,9 @@ name: API CI/CD on: + pull_request: + branches: + - dev # Set this to your default branch push: branches: - dev # Set this to your default branch @@ -15,7 +18,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v3 with: - node-version: '20' + node-version: '16' - name: Install dependencies run: | @@ -30,6 +33,7 @@ jobs: deploy: needs: test # Ensure the test job passes before deploying runs-on: ubuntu-latest + if: github.event_name == 'push' # Only run on push events steps: - name: Checkout Repository uses: actions/checkout@v2 From 5b178dad206fe47fd654f1254460d85152c8b9ff Mon Sep 17 00:00:00 2001 From: Leo Gavin Date: Tue, 30 Apr 2024 17:30:27 +0530 Subject: [PATCH 11/13] Update apiDeploy.yml --- .github/workflows/apiDeploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/apiDeploy.yml b/.github/workflows/apiDeploy.yml index 15fb014..1de457c 100644 --- a/.github/workflows/apiDeploy.yml +++ b/.github/workflows/apiDeploy.yml @@ -9,7 +9,7 @@ on: - dev # Set this to your default branch jobs: - test: + CI: runs-on: ubuntu-latest steps: - name: Checkout Repository @@ -31,7 +31,7 @@ jobs: npm run test deploy: - needs: test # Ensure the test job passes before deploying + needs: CI # Ensure the test job passes before deploying runs-on: ubuntu-latest if: github.event_name == 'push' # Only run on push events steps: From 62221a21642a39e410cc3c3f7b08aa2c2c711dc1 Mon Sep 17 00:00:00 2001 From: Leo Gavin Date: Tue, 30 Apr 2024 17:32:14 +0530 Subject: [PATCH 12/13] Update apiDeploy.yml --- .github/workflows/apiDeploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/apiDeploy.yml b/.github/workflows/apiDeploy.yml index 1de457c..6baffd8 100644 --- a/.github/workflows/apiDeploy.yml +++ b/.github/workflows/apiDeploy.yml @@ -30,7 +30,7 @@ jobs: cd API npm run test - deploy: + CD: needs: CI # Ensure the test job passes before deploying runs-on: ubuntu-latest if: github.event_name == 'push' # Only run on push events From 86b32fab05c3f6040d4be05746f8e12ddca24032 Mon Sep 17 00:00:00 2001 From: Leo Gavin Date: Tue, 30 Apr 2024 17:34:56 +0530 Subject: [PATCH 13/13] runner: nextjs build check --- .github/workflows/buildCheck.yml | 43 ++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/buildCheck.yml diff --git a/.github/workflows/buildCheck.yml b/.github/workflows/buildCheck.yml new file mode 100644 index 0000000..3a1f9a1 --- /dev/null +++ b/.github/workflows/buildCheck.yml @@ -0,0 +1,43 @@ +name: Next.js Build Check + +on: + pull_request: + branches: + - dev # Set this to your default branch + paths: + - 'admin/**' + - 'customer/**' + - 'worker/**' + +jobs: + build-check: + runs-on: ubuntu-latest + strategy: + matrix: + project: [admin, customer, worker] + + steps: + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '16' + + - name: Install Dependencies + run: | + cd ${{ matrix.project }} + npm ci + + - name: Build Project + run: | + cd ${{ matrix.project }} + npm run build + + - name: Export Build Artifact + uses: actions/upload-artifact@v3 + with: + name: ${{ matrix.project }}-build + path: ${{ matrix.project }}/.next/ + if-no-files-found: error \ No newline at end of file