From a20d7e02ae49512dd6bccc3c9c7aaa4b9dcc6bd4 Mon Sep 17 00:00:00 2001 From: Dalton Burkhart Date: Thu, 9 Oct 2025 01:32:13 -0400 Subject: [PATCH 01/15] Setup initial test --- .../foodRequests/request.controller.spec.ts | 68 +++++++++++++++++++ .../src/foodRequests/request.controller.ts | 2 +- .../src/foodRequests/request.module.ts | 4 +- .../src/foodRequests/request.service.spec.ts | 58 ++++++++++++++++ apps/backend/src/pantries/pantries.entity.ts | 2 +- package.json | 1 + yarn.lock | 12 ++++ 7 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 apps/backend/src/foodRequests/request.controller.spec.ts create mode 100644 apps/backend/src/foodRequests/request.service.spec.ts diff --git a/apps/backend/src/foodRequests/request.controller.spec.ts b/apps/backend/src/foodRequests/request.controller.spec.ts new file mode 100644 index 00000000..52108d41 --- /dev/null +++ b/apps/backend/src/foodRequests/request.controller.spec.ts @@ -0,0 +1,68 @@ +import { RequestsService } from './request.service'; +import { RequestsController } from './request.controller'; +import { Test, TestingModule } from '@nestjs/testing'; +import { mock } from 'jest-mock-extended'; +import { AWSS3Service } from '../aws/aws-s3.service'; +import { OrdersService } from '../orders/order.service'; + +const mockRequestsService = mock(); +const mockOrdersService = mock(); +const mockAWSS3Service = mock(); + +describe('RequestsController', () => { + let controller: RequestsController; + + beforeEach(async () => { + mockRequestsService.findOne.mockReset(); + + const module: TestingModule = await Test.createTestingModule({ + controllers: [RequestsController], + providers: [ + { + provide: RequestsService, + useValue: mockRequestsService, + }, + { + provide: AWSS3Service, + useValue: mockOrdersService, + }, + { + provide: OrdersService, + useValue: mockAWSS3Service, + }, + ], + }).compile(); + + controller = module.get(RequestsController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); + + describe('GET /:requestId', () => { + it('should call requestsService.findOne and return a specific food request', async () => { + const foodRequest = { + requestId: 1, + pantryId: 1, + requestedSize: 'Medium (5-10 boxes)', + requestedItems: ['Canned Goods', 'Vegetables'], + additionalInformation: 'No onions, please.', + requestedAt: null, + dateReceived: null, + feedback: null, + photos: null, + order: null, + }; + const requestId = 1; + + mockRequestsService.findOne.mockResolvedValueOnce(foodRequest); + + // ✅ call with a number, since ParseIntPipe handles conversion in real controller + const result = await controller.getRequest(requestId); + + expect(result).toEqual(foodRequest); + expect(mockRequestsService.findOne).toHaveBeenCalledWith(requestId); + }); + }); +}); diff --git a/apps/backend/src/foodRequests/request.controller.ts b/apps/backend/src/foodRequests/request.controller.ts index 1b088999..6d70e8c3 100644 --- a/apps/backend/src/foodRequests/request.controller.ts +++ b/apps/backend/src/foodRequests/request.controller.ts @@ -19,7 +19,7 @@ import { Order } from '../orders/order.entity'; @Controller('requests') // @UseInterceptors() -export class FoodRequestsController { +export class RequestsController { constructor( private requestsService: RequestsService, private awsS3Service: AWSS3Service, diff --git a/apps/backend/src/foodRequests/request.module.ts b/apps/backend/src/foodRequests/request.module.ts index baf98bcd..85b9af73 100644 --- a/apps/backend/src/foodRequests/request.module.ts +++ b/apps/backend/src/foodRequests/request.module.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { FoodRequestsController } from './request.controller'; +import { RequestsController } from './request.controller'; import { FoodRequest } from './request.entity'; import { RequestsService } from './request.service'; import { JwtStrategy } from '../auth/jwt.strategy'; @@ -16,7 +16,7 @@ import { Order } from '../orders/order.entity'; MulterModule.register({ dest: './uploads' }), TypeOrmModule.forFeature([FoodRequest, Order]), ], - controllers: [FoodRequestsController], + controllers: [RequestsController], providers: [RequestsService, OrdersService, AuthService, JwtStrategy], }) export class RequestsModule {} diff --git a/apps/backend/src/foodRequests/request.service.spec.ts b/apps/backend/src/foodRequests/request.service.spec.ts new file mode 100644 index 00000000..9d4bfb81 --- /dev/null +++ b/apps/backend/src/foodRequests/request.service.spec.ts @@ -0,0 +1,58 @@ +import { Test } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Repository, SelectQueryBuilder } from 'typeorm'; +import { FoodRequest } from './request.entity'; +import { RequestsService } from './request.service'; +import { mock } from 'jest-mock-extended'; + +const mockRequestsRepository = mock>(); + +describe('OrdersService', () => { + let service: RequestsService; + + beforeAll(async () => { + // Reset the mock repository before compiling module + mockRequestsRepository.findOne.mockReset(); + + const module = await Test.createTestingModule({ + providers: [ + RequestsService, + { + provide: getRepositoryToken(FoodRequest), + useValue: mockRequestsRepository, + }, + ], + }).compile(); + + service = module.get(RequestsService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('findOne', () => { + it('should return a food request with the corresponding id', async () => { + const mockRequest = { + requestId: 1, + pantryId: 1, + requestedSize: 'Medium (5-10 boxes)', + requestedItems: ['Canned Goods', 'Vegetables'], + additionalInformation: 'No onions, please.', + requestedAt: null, + dateReceived: null, + feedback: null, + photos: null, + order: null, + }; + const requestId = 1; + mockRequestsRepository.findOne.mockResolvedValueOnce(mockRequest); + const result = await service.findOne(requestId); + expect(result).toEqual(mockRequest); + expect(mockRequestsRepository.findOne).toHaveBeenCalledWith({ + where: { requestId }, + relations: ['order'], + }); + }); + }); +}); diff --git a/apps/backend/src/pantries/pantries.entity.ts b/apps/backend/src/pantries/pantries.entity.ts index 354d0caf..1b4fd689 100644 --- a/apps/backend/src/pantries/pantries.entity.ts +++ b/apps/backend/src/pantries/pantries.entity.ts @@ -29,7 +29,7 @@ export class Pantry { reserveFoodForAllergic: boolean; @Column({ name: 'reservation_explanation', type: 'text' }) - reservationExplanation: Text; + reservationExplanation: string; @Column({ name: 'dedicated_allergy_friendly', type: 'varchar', length: 255 }) dedicatedAllergyFriendly: string; diff --git a/package.json b/package.json index 83e1b96d..a69ffe4a 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "framer-motion": "^11.5.6", "global": "^4.4.0", "google-libphonenumber": "^3.2.40", + "jest-mock-extended": "^4.0.0", "jwks-rsa": "^3.1.0", "mongodb": "^6.1.0", "multer": "^1.4.5-lts.1", diff --git a/yarn.lock b/yarn.lock index 7a038925..ca4c7ff5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10135,6 +10135,13 @@ jest-message-util@^29.7.0: slash "^3.0.0" stack-utils "^2.0.3" +jest-mock-extended@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jest-mock-extended/-/jest-mock-extended-4.0.0.tgz#fe8cfa686c7ada4be2e7f7a3eced794b1338c18b" + integrity sha512-7BZpfuvLam+/HC+NxifIi9b+5VXj/utUDMPUqrDJehGWVuXPtLS9Jqlob2mJLrI/pg2k1S8DMfKDvEB88QNjaQ== + dependencies: + ts-essentials "^10.0.2" + jest-mock@^29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz" @@ -13728,6 +13735,11 @@ ts-api-utils@^2.0.1: resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz" integrity sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w== +ts-essentials@^10.0.2: + version "10.1.1" + resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-10.1.1.tgz#4e1d29b7c9b33c1a2744482376634c4fafba5210" + integrity sha512-4aTB7KLHKmUvkjNj8V+EdnmuVTiECzn3K+zIbRthumvHu+j44x3w63xpfs0JL3NGIzGXqoQ7AV591xHO+XrOTw== + ts-jest@^29.1.0: version "29.1.1" resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz" From 57e7c8a625eb246b55c98e838cb39faaabc93c94 Mon Sep 17 00:00:00 2001 From: Dalton Burkhart Date: Sat, 11 Oct 2025 11:58:35 -0400 Subject: [PATCH 02/15] Finished tests for controller and service --- .../foodRequests/request.controller.spec.ts | 182 +++++++++++- .../src/foodRequests/request.controller.ts | 9 - .../src/foodRequests/request.service.spec.ts | 281 +++++++++++++++++- .../src/foodRequests/request.service.ts | 2 + 4 files changed, 463 insertions(+), 11 deletions(-) diff --git a/apps/backend/src/foodRequests/request.controller.spec.ts b/apps/backend/src/foodRequests/request.controller.spec.ts index 52108d41..8e580068 100644 --- a/apps/backend/src/foodRequests/request.controller.spec.ts +++ b/apps/backend/src/foodRequests/request.controller.spec.ts @@ -4,6 +4,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { mock } from 'jest-mock-extended'; import { AWSS3Service } from '../aws/aws-s3.service'; import { OrdersService } from '../orders/order.service'; +import { Readable } from 'stream'; const mockRequestsService = mock(); const mockOrdersService = mock(); @@ -14,6 +15,9 @@ describe('RequestsController', () => { beforeEach(async () => { mockRequestsService.findOne.mockReset(); + mockRequestsService.find.mockReset(); + mockRequestsService.create.mockReset(); + mockRequestsService.updateDeliveryDetails?.mockReset(); const module: TestingModule = await Test.createTestingModule({ controllers: [RequestsController], @@ -58,11 +62,187 @@ describe('RequestsController', () => { mockRequestsService.findOne.mockResolvedValueOnce(foodRequest); - // ✅ call with a number, since ParseIntPipe handles conversion in real controller const result = await controller.getRequest(requestId); expect(result).toEqual(foodRequest); expect(mockRequestsService.findOne).toHaveBeenCalledWith(requestId); }); }); + + describe('GET /get-all-requests/:pantryId', () => { + it('should call requestsService.find and return all food requests for a specific pantry', async () => { + const foodRequests = [ + { + requestId: 1, + pantryId: 1, + requestedSize: 'Medium (5-10 boxes)', + requestedItems: ['Canned Goods', 'Vegetables'], + additionalInformation: 'No onions, please.', + requestedAt: null, + dateReceived: null, + feedback: null, + photos: null, + order: null, + }, + { + requestId: 2, + pantryId: 1, + requestedSize: 'Large (10-20 boxes)', + requestedItems: ['Rice', 'Beans'], + additionalInformation: 'Gluten-free items only.', + requestedAt: null, + dateReceived: null, + feedback: null, + photos: null, + order: null, + }, + ]; + const pantryId = 1; + + mockRequestsService.find.mockResolvedValueOnce(foodRequests); + + const result = await controller.getAllPantryRequests(pantryId); + + expect(result).toEqual(foodRequests); + expect(mockRequestsService.find).toHaveBeenCalledWith(pantryId); + }); + }); + + describe('POST /create', () => { + it('should call requestsService.create and return the created food request', async () => { + const createBody = { + pantryId: 1, + requestedSize: 'Medium (5-10 boxes)', + requestedItems: ['Test item 1', 'Test item 2'], + additionalInformation: 'Test information.', + dateReceived: null, + feedback: null, + photos: null, + }; + + const createdRequest = { + requestId: 1, + ...createBody, + requestedAt: new Date(), + order: null, + }; + + mockRequestsService.create.mockResolvedValueOnce(createdRequest); + + const result = await controller.createRequest(createBody); + + expect(result).toEqual(createdRequest); + expect(mockRequestsService.create).toHaveBeenCalledWith( + createBody.pantryId, + createBody.requestedSize, + createBody.requestedItems, + createBody.additionalInformation, + createBody.dateReceived, + createBody.feedback, + createBody.photos, + ); + }); + }); + + describe('POST /confirm-delivery', () => { + it('should call awsService.upload and then call orderService.updateDeliveryDetails and then call requestsService.updateDeliveryDetails and return the updated food request', async () => { + const requestId = 1; + const updateBody = { + deliveryDate: new Date(), + feedback: 'Delivery was on time.', + }; + + const mockStream = new Readable(); + mockStream._read = () => {}; + + const photos: Express.Multer.File[] = [ + { + fieldname: 'photos', + originalname: 'photo1.jpg', + encoding: '7bit', + mimetype: 'image/jpeg', + buffer: Buffer.from('fake image content 1'), + size: 1234, + destination: '', + filename: '', + path: '', + stream: mockStream, + }, + { + fieldname: 'photos', + originalname: 'photo2.jpg', + encoding: '7bit', + mimetype: 'image/jpeg', + buffer: Buffer.from('fake image content 2'), + size: 5678, + destination: '', + filename: '', + path: '', + stream: mockStream, + }, + ]; + + const updatedPhotoUrls = [ + 'https://s3.amazonaws.com/bucket/photo1.jpg', + 'https://s3.amazonaws.com/bucket/photo2.jpg', + ]; + mockAWSS3Service.upload.mockResolvedValueOnce(updatedPhotoUrls); + + const photoResult = await mockAWSS3Service.upload(photos); + expect(photoResult).toEqual(updatedPhotoUrls); + expect(mockAWSS3Service.upload).toHaveBeenCalledWith(photos); + + // Mock the OrdersService.updateDeliveryDetails method + await mockOrdersService.updateStatus(requestId, 'deivered'); + expect(mockOrdersService.updateStatus).toHaveBeenCalledWith( + requestId, + 'deivered', + ); + + // Mock the RequestsService.updateDeliveryDetails method + const updatedRequest = { + requestId, + pantryId: 1, + requestedSize: 'Medium (5-10 boxes)', + requestedItems: ['Canned Goods', 'Vegetables'], + additionalInformation: 'No onions, please.', + requestedAt: new Date(), + dateReceived: updateBody.deliveryDate, + feedback: updateBody.feedback, + photos: updatedPhotoUrls, + order: null, + }; + + mockRequestsService.updateDeliveryDetails.mockResolvedValueOnce( + updatedRequest, + ); + + const requestResult = await mockRequestsService.updateDeliveryDetails( + requestId, + updateBody.deliveryDate, + updateBody.feedback, + updatedPhotoUrls, + ); + + expect(requestResult).toEqual(updatedRequest); + expect(mockRequestsService.updateDeliveryDetails).toHaveBeenCalledWith( + requestId, + updateBody.deliveryDate, + updateBody.feedback, + updatedPhotoUrls, + ); + }); + + it('should throw an error if the received date is not properly formatted', async () => { + const requestId = 1; + const updateBody = { + dateReceived: 'invalid-date', + feedback: 'Delivery was on time.', + }; + + await expect( + controller.confirmDelivery(requestId, updateBody, []), + ).rejects.toThrow(); + }); + }); }); diff --git a/apps/backend/src/foodRequests/request.controller.ts b/apps/backend/src/foodRequests/request.controller.ts index 6d70e8c3..bcba7821 100644 --- a/apps/backend/src/foodRequests/request.controller.ts +++ b/apps/backend/src/foodRequests/request.controller.ts @@ -15,7 +15,6 @@ import { AWSS3Service } from '../aws/aws-s3.service'; import { FilesInterceptor } from '@nestjs/platform-express'; import * as multer from 'multer'; import { OrdersService } from '../orders/order.service'; -import { Order } from '../orders/order.entity'; @Controller('requests') // @UseInterceptors() @@ -40,14 +39,6 @@ export class RequestsController { return this.requestsService.find(pantryId); } - @Get('get-order/:requestId') - async getOrderByRequestId( - @Param('requestId', ParseIntPipe) requestId: number, - ): Promise { - const request = await this.requestsService.findOne(requestId); - return request.order; - } - @Post('/create') @ApiBody({ description: 'Details for creating a food request', diff --git a/apps/backend/src/foodRequests/request.service.spec.ts b/apps/backend/src/foodRequests/request.service.spec.ts index 9d4bfb81..92bcfe8c 100644 --- a/apps/backend/src/foodRequests/request.service.spec.ts +++ b/apps/backend/src/foodRequests/request.service.spec.ts @@ -1,6 +1,6 @@ import { Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { Repository, SelectQueryBuilder } from 'typeorm'; +import { Repository } from 'typeorm'; import { FoodRequest } from './request.entity'; import { RequestsService } from './request.service'; import { mock } from 'jest-mock-extended'; @@ -13,6 +13,9 @@ describe('OrdersService', () => { beforeAll(async () => { // Reset the mock repository before compiling module mockRequestsRepository.findOne.mockReset(); + mockRequestsRepository.create.mockReset(); + mockRequestsRepository.save.mockReset(); + mockRequestsRepository.find.mockReset(); const module = await Test.createTestingModule({ providers: [ @@ -55,4 +58,280 @@ describe('OrdersService', () => { }); }); }); + + describe('create', () => { + it('should successfully create and return a new food request', async () => { + const mockRequest = { + requestId: 1, + pantryId: 1, + requestedSize: 'Medium (5-10 boxes)', + requestedItems: ['Canned Goods', 'Vegetables'], + additionalInformation: 'No onions, please.', + requestedAt: null, + dateReceived: null, + feedback: null, + photos: null, + order: null, + }; + + mockRequestsRepository.create.mockReturnValueOnce(mockRequest); + mockRequestsRepository.save.mockResolvedValueOnce(mockRequest); + + const result = await service.create( + mockRequest.pantryId, + mockRequest.requestedSize, + mockRequest.requestedItems, + mockRequest.additionalInformation, + mockRequest.dateReceived, + mockRequest.feedback, + mockRequest.photos, + ); + + expect(result).toEqual(mockRequest); + expect(mockRequestsRepository.create).toHaveBeenCalledWith({ + pantryId: mockRequest.pantryId, + requestedSize: mockRequest.requestedSize, + requestedItems: mockRequest.requestedItems, + additionalInformation: mockRequest.additionalInformation, + dateReceived: mockRequest.dateReceived, + feedback: mockRequest.feedback, + photos: mockRequest.photos, + }); + expect(mockRequestsRepository.save).toHaveBeenCalledWith(mockRequest); + }); + }); + + describe('find', () => { + it('should return all food requests for a specific pantry', async () => { + const mockRequests = [ + { + requestId: 1, + pantryId: 1, + requestedSize: 'Medium (5-10 boxes)', + requestedItems: ['Canned Goods', 'Vegetables'], + additionalInformation: 'No onions, please.', + requestedAt: null, + dateReceived: null, + feedback: null, + photos: null, + order: null, + }, + { + requestId: 2, + pantryId: 1, + requestedSize: 'Large (10-20 boxes)', + requestedItems: ['Rice', 'Beans'], + additionalInformation: 'Gluten-free items only.', + requestedAt: null, + dateReceived: null, + feedback: null, + photos: null, + order: null, + }, + ]; + const pantryId = 1; + mockRequestsRepository.find.mockResolvedValueOnce(mockRequests); + + const result = await service.find(pantryId); + + expect(result).toEqual(mockRequests); + expect(mockRequestsRepository.find).toHaveBeenCalledWith({ + where: { pantryId }, + relations: ['order'], + }); + }); + }); + + describe('updateDeliveryDetails', () => { + it('should update and return the food request with new delivery details', async () => { + const mockOrder = { + orderId: 1, + shippedBy: 1, + shippedAt: new Date(), + status: 'shipped', + trackingNumber: '123456789', + requestId: 1, + pantry: null, + foodManufacturer: null, + donation: null, + deliveredAt: null, + request: null, + allocations: [], + manufacturer: 1, + fulfillmentCenter: null, + foodItems: [], + createdAt: new Date(), + updatedAt: new Date(), + deletedAt: null, + }; + + const mockRequest = { + requestId: 1, + pantryId: 1, + requestedSize: 'Medium (5-10 boxes)', + requestedItems: ['Canned Goods', 'Vegetables'], + additionalInformation: 'No onions, please.', + requestedAt: null, + dateReceived: null, + feedback: null, + photos: null, + order: mockOrder, + }; + const requestId = 1; + const deliveryDate = new Date(); + const feedback = 'Good delivery!'; + const photos = ['photo1.jpg', 'photo2.jpg']; + + mockRequestsRepository.findOne.mockResolvedValueOnce(mockRequest); + mockRequestsRepository.save.mockResolvedValueOnce({ + ...mockRequest, + dateReceived: deliveryDate, + feedback, + photos, + order: { ...mockOrder, status: 'fulfilled' }, + }); + + const result = await service.updateDeliveryDetails( + requestId, + deliveryDate, + feedback, + photos, + ); + + expect(result).toEqual({ + ...mockRequest, + dateReceived: deliveryDate, + feedback, + photos, + order: { ...mockOrder, status: 'fulfilled' }, + }); + + expect(mockRequestsRepository.findOne).toHaveBeenCalledWith({ + where: { requestId }, + relations: ['order'], + }); + + expect(mockRequestsRepository.save).toHaveBeenCalledWith({ + ...mockRequest, + dateReceived: deliveryDate, + feedback, + photos, + order: { ...mockOrder, status: 'fulfilled' }, + }); + }); + + it('should throw an error if the request ID is invalid', async () => { + const requestId = 999; + const deliveryDate = new Date(); + const feedback = 'Good delivery!'; + const photos = ['photo1.jpg', 'photo2.jpg']; + + mockRequestsRepository.findOne.mockResolvedValueOnce(null); + + await expect( + service.updateDeliveryDetails( + requestId, + deliveryDate, + feedback, + photos, + ), + ).rejects.toThrow('Invalid request ID'); + + expect(mockRequestsRepository.findOne).toHaveBeenCalledWith({ + where: { requestId }, + relations: ['order'], + }); + }); + + it('should throw an error if there is no associated order', async () => { + const mockRequest = { + requestId: 1, + pantryId: 1, + requestedSize: 'Medium (5-10 boxes)', + requestedItems: ['Canned Goods', 'Vegetables'], + additionalInformation: 'No onions, please.', + requestedAt: null, + dateReceived: null, + feedback: null, + photos: null, + order: null, + }; + const requestId = 1; + const deliveryDate = new Date(); + const feedback = 'Good delivery!'; + const photos = ['photo1.jpg', 'photo2.jpg']; + + mockRequestsRepository.findOne.mockResolvedValueOnce(mockRequest); + + await expect( + service.updateDeliveryDetails( + requestId, + deliveryDate, + feedback, + photos, + ), + ).rejects.toThrow('No associated order found for this request'); + + expect(mockRequestsRepository.findOne).toHaveBeenCalledWith({ + where: { requestId }, + relations: ['order'], + }); + }); + + it('should throw an error if the order does not have a food manufacturer', async () => { + const mockOrder = { + orderId: 1, + shippedBy: null, + shippedAt: new Date(), + status: 'shipped', + trackingNumber: '123456789', + requestId: 1, + pantry: null, + foodManufacturer: null, + donation: null, + deliveredAt: null, + request: null, + allocations: [], + manufacturer: null, + fulfillmentCenter: null, + foodItems: [], + createdAt: new Date(), + updatedAt: new Date(), + deletedAt: null, + }; + + const mockRequest = { + requestId: 1, + pantryId: 1, + requestedSize: 'Medium (5-10 boxes)', + requestedItems: ['Canned Goods', 'Vegetables'], + additionalInformation: 'No onions, please.', + requestedAt: null, + dateReceived: null, + feedback: null, + photos: null, + order: mockOrder, + }; + const requestId = 1; + const deliveryDate = new Date(); + const feedback = 'Good delivery!'; + const photos = ['photo1.jpg', 'photo2.jpg']; + + mockRequestsRepository.findOne.mockResolvedValueOnce(mockRequest); + + await expect( + service.updateDeliveryDetails( + requestId, + deliveryDate, + feedback, + photos, + ), + ).rejects.toThrow('No associated food manufacturer found for this order'); + + expect(mockRequestsRepository.findOne).toHaveBeenCalledWith({ + where: { requestId }, + relations: ['order'], + }); + }); + }); }); diff --git a/apps/backend/src/foodRequests/request.service.ts b/apps/backend/src/foodRequests/request.service.ts index 4234f4c5..331bb3a7 100644 --- a/apps/backend/src/foodRequests/request.service.ts +++ b/apps/backend/src/foodRequests/request.service.ts @@ -37,6 +37,8 @@ export class RequestsService { feedback: string | undefined, photos: string[] | undefined, ): Promise { + validateId(pantryId, 'Pantry'); + const foodRequest = this.repo.create({ pantryId, requestedSize, From c9bbc853f9808c3f42d9154c89b6522325ee0e53 Mon Sep 17 00:00:00 2001 From: Dalton Burkhart Date: Sat, 11 Oct 2025 12:21:22 -0400 Subject: [PATCH 03/15] Removed endpoint from apiClient --- apps/frontend/src/api/apiClient.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/apps/frontend/src/api/apiClient.ts b/apps/frontend/src/api/apiClient.ts index ac07fe32..c8e01641 100644 --- a/apps/frontend/src/api/apiClient.ts +++ b/apps/frontend/src/api/apiClient.ts @@ -179,12 +179,6 @@ export class ApiClient { return this.axiosInstance.get(`api/orders/${orderId}`) as Promise; } - public async getOrderByRequestId(requestId: number): Promise { - return this.axiosInstance.get( - `api/requests/get-order/${requestId}`, - ) as Promise; - } - async getAllAllocationsByOrder(orderId: number): Promise { return this.axiosInstance .get(`api/allocations/${orderId}/get-all-allocations`) From 46c4d730692607816b71840b781bbf98ce08da51 Mon Sep 17 00:00:00 2001 From: Dalton Burkhart Date: Sat, 11 Oct 2025 19:21:14 -0400 Subject: [PATCH 04/15] Updated comment per review --- apps/backend/src/foodRequests/request.controller.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/backend/src/foodRequests/request.controller.spec.ts b/apps/backend/src/foodRequests/request.controller.spec.ts index 8e580068..8be6031e 100644 --- a/apps/backend/src/foodRequests/request.controller.spec.ts +++ b/apps/backend/src/foodRequests/request.controller.spec.ts @@ -192,7 +192,6 @@ describe('RequestsController', () => { expect(photoResult).toEqual(updatedPhotoUrls); expect(mockAWSS3Service.upload).toHaveBeenCalledWith(photos); - // Mock the OrdersService.updateDeliveryDetails method await mockOrdersService.updateStatus(requestId, 'deivered'); expect(mockOrdersService.updateStatus).toHaveBeenCalledWith( requestId, From b3550e4c4946ae21b6f6a44b7df325804aa7c785 Mon Sep 17 00:00:00 2001 From: Dalton Burkhart Date: Sun, 12 Oct 2025 14:39:49 -0400 Subject: [PATCH 05/15] Cleaned up tests --- .../src/foodRequests/request.service.spec.ts | 171 ++++++++---------- .../src/foodRequests/request.service.ts | 5 + .../src/utils/validation.utils.spec.ts | 22 +++ 3 files changed, 101 insertions(+), 97 deletions(-) create mode 100644 apps/backend/src/utils/validation.utils.spec.ts diff --git a/apps/backend/src/foodRequests/request.service.spec.ts b/apps/backend/src/foodRequests/request.service.spec.ts index 92bcfe8c..291d4dfb 100644 --- a/apps/backend/src/foodRequests/request.service.spec.ts +++ b/apps/backend/src/foodRequests/request.service.spec.ts @@ -7,6 +7,19 @@ import { mock } from 'jest-mock-extended'; const mockRequestsRepository = mock>(); +const mockRequest = { + requestId: 1, + pantryId: 1, + requestedSize: 'Medium (5-10 boxes)', + requestedItems: ['Canned Goods', 'Vegetables'], + additionalInformation: 'No onions, please.', + requestedAt: null, + dateReceived: null, + feedback: null, + photos: null, + order: null, +}; + describe('OrdersService', () => { let service: RequestsService; @@ -30,24 +43,19 @@ describe('OrdersService', () => { service = module.get(RequestsService); }); + beforeEach(() => { + mockRequestsRepository.findOne.mockReset(); + mockRequestsRepository.create.mockReset(); + mockRequestsRepository.save.mockReset(); + mockRequestsRepository.find.mockReset(); + }); + it('should be defined', () => { expect(service).toBeDefined(); }); describe('findOne', () => { it('should return a food request with the corresponding id', async () => { - const mockRequest = { - requestId: 1, - pantryId: 1, - requestedSize: 'Medium (5-10 boxes)', - requestedItems: ['Canned Goods', 'Vegetables'], - additionalInformation: 'No onions, please.', - requestedAt: null, - dateReceived: null, - feedback: null, - photos: null, - order: null, - }; const requestId = 1; mockRequestsRepository.findOne.mockResolvedValueOnce(mockRequest); const result = await service.findOne(requestId); @@ -61,21 +69,9 @@ describe('OrdersService', () => { describe('create', () => { it('should successfully create and return a new food request', async () => { - const mockRequest = { - requestId: 1, - pantryId: 1, - requestedSize: 'Medium (5-10 boxes)', - requestedItems: ['Canned Goods', 'Vegetables'], - additionalInformation: 'No onions, please.', - requestedAt: null, - dateReceived: null, - feedback: null, - photos: null, - order: null, - }; - mockRequestsRepository.create.mockReturnValueOnce(mockRequest); mockRequestsRepository.save.mockResolvedValueOnce(mockRequest); + mockRequestsRepository.find.mockResolvedValueOnce([mockRequest]); const result = await service.create( mockRequest.pantryId, @@ -99,17 +95,37 @@ describe('OrdersService', () => { }); expect(mockRequestsRepository.save).toHaveBeenCalledWith(mockRequest); }); + + it('should throw an error if the pantry ID does not exist', async () => { + const invalidPantryId = 999; + + await expect( + service.create( + invalidPantryId, + 'Medium (5-10 boxes)', + ['Canned Goods', 'Vegetables'], + 'Additional info', + null, + null, + null, + ), + ).rejects.toThrow(`Pantry ${invalidPantryId} not found`); + + expect(mockRequestsRepository.create).not.toHaveBeenCalled(); + expect(mockRequestsRepository.save).not.toHaveBeenCalled(); + }); }); describe('find', () => { it('should return all food requests for a specific pantry', async () => { const mockRequests = [ + mockRequest, { - requestId: 1, + requestId: 2, pantryId: 1, - requestedSize: 'Medium (5-10 boxes)', - requestedItems: ['Canned Goods', 'Vegetables'], - additionalInformation: 'No onions, please.', + requestedSize: 'Large (10-20 boxes)', + requestedItems: ['Rice', 'Beans'], + additionalInformation: 'Gluten-free items only.', requestedAt: null, dateReceived: null, feedback: null, @@ -117,11 +133,11 @@ describe('OrdersService', () => { order: null, }, { - requestId: 2, - pantryId: 1, - requestedSize: 'Large (10-20 boxes)', - requestedItems: ['Rice', 'Beans'], - additionalInformation: 'Gluten-free items only.', + requestId: 3, + pantryId: 2, + requestedSize: 'Small (1-5 boxes)', + requestedItems: ['Fruits', 'Snacks'], + additionalInformation: 'No nuts, please.', requestedAt: null, dateReceived: null, feedback: null, @@ -130,11 +146,13 @@ describe('OrdersService', () => { }, ]; const pantryId = 1; - mockRequestsRepository.find.mockResolvedValueOnce(mockRequests); + mockRequestsRepository.find.mockResolvedValueOnce( + mockRequests.slice(0, 2), + ); const result = await service.find(pantryId); - expect(result).toEqual(mockRequests); + expect(result).toEqual(mockRequests.slice(0, 2)); expect(mockRequestsRepository.find).toHaveBeenCalledWith({ where: { pantryId }, relations: ['order'], @@ -146,43 +164,29 @@ describe('OrdersService', () => { it('should update and return the food request with new delivery details', async () => { const mockOrder = { orderId: 1, - shippedBy: 1, - shippedAt: new Date(), - status: 'shipped', - trackingNumber: '123456789', - requestId: 1, pantry: null, + request: null, + requestId: 1, foodManufacturer: null, + shippedBy: 1, donation: null, - deliveredAt: null, - request: null, - allocations: [], - manufacturer: 1, - fulfillmentCenter: null, - foodItems: [], + status: 'shipped', createdAt: new Date(), - updatedAt: new Date(), - deletedAt: null, + shippedAt: new Date(), + deliveredAt: null, }; - const mockRequest = { - requestId: 1, - pantryId: 1, - requestedSize: 'Medium (5-10 boxes)', - requestedItems: ['Canned Goods', 'Vegetables'], - additionalInformation: 'No onions, please.', - requestedAt: null, - dateReceived: null, - feedback: null, - photos: null, + const mockRequest2 = { + ...mockRequest, order: mockOrder, }; + const requestId = 1; const deliveryDate = new Date(); const feedback = 'Good delivery!'; const photos = ['photo1.jpg', 'photo2.jpg']; - mockRequestsRepository.findOne.mockResolvedValueOnce(mockRequest); + mockRequestsRepository.findOne.mockResolvedValueOnce(mockRequest2); mockRequestsRepository.save.mockResolvedValueOnce({ ...mockRequest, dateReceived: deliveryDate, @@ -244,18 +248,6 @@ describe('OrdersService', () => { }); it('should throw an error if there is no associated order', async () => { - const mockRequest = { - requestId: 1, - pantryId: 1, - requestedSize: 'Medium (5-10 boxes)', - requestedItems: ['Canned Goods', 'Vegetables'], - additionalInformation: 'No onions, please.', - requestedAt: null, - dateReceived: null, - feedback: null, - photos: null, - order: null, - }; const requestId = 1; const deliveryDate = new Date(); const feedback = 'Good delivery!'; @@ -281,43 +273,28 @@ describe('OrdersService', () => { it('should throw an error if the order does not have a food manufacturer', async () => { const mockOrder = { orderId: 1, - shippedBy: null, - shippedAt: new Date(), - status: 'shipped', - trackingNumber: '123456789', - requestId: 1, pantry: null, + request: null, + requestId: 1, foodManufacturer: null, + shippedBy: null, donation: null, - deliveredAt: null, - request: null, - allocations: [], - manufacturer: null, - fulfillmentCenter: null, - foodItems: [], + status: 'shipped', createdAt: new Date(), - updatedAt: new Date(), - deletedAt: null, + shippedAt: new Date(), + deliveredAt: null, }; - - const mockRequest = { - requestId: 1, - pantryId: 1, - requestedSize: 'Medium (5-10 boxes)', - requestedItems: ['Canned Goods', 'Vegetables'], - additionalInformation: 'No onions, please.', - requestedAt: null, - dateReceived: null, - feedback: null, - photos: null, + const mockRequest2 = { + ...mockRequest, order: mockOrder, }; + const requestId = 1; const deliveryDate = new Date(); const feedback = 'Good delivery!'; const photos = ['photo1.jpg', 'photo2.jpg']; - mockRequestsRepository.findOne.mockResolvedValueOnce(mockRequest); + mockRequestsRepository.findOne.mockResolvedValueOnce(mockRequest2); await expect( service.updateDeliveryDetails( diff --git a/apps/backend/src/foodRequests/request.service.ts b/apps/backend/src/foodRequests/request.service.ts index 331bb3a7..89473eb5 100644 --- a/apps/backend/src/foodRequests/request.service.ts +++ b/apps/backend/src/foodRequests/request.service.ts @@ -39,6 +39,11 @@ export class RequestsService { ): Promise { validateId(pantryId, 'Pantry'); + const pantry = await this.find(pantryId); + if (!pantry || pantry.length === 0) { + throw new NotFoundException(`Pantry ${pantryId} not found`); + } + const foodRequest = this.repo.create({ pantryId, requestedSize, diff --git a/apps/backend/src/utils/validation.utils.spec.ts b/apps/backend/src/utils/validation.utils.spec.ts new file mode 100644 index 00000000..25ca615f --- /dev/null +++ b/apps/backend/src/utils/validation.utils.spec.ts @@ -0,0 +1,22 @@ +import { BadRequestException } from '@nestjs/common'; +import { validateId } from './validation.utils'; + +describe('validateId', () => { + it('should not throw an error for a valid ID', () => { + expect(() => validateId(5, 'User')).not.toThrow(); + }); + + it('should throw BadRequestException for ID < 1', () => { + expect(() => validateId(0, 'User')).toThrow(BadRequestException); + expect(() => validateId(0, 'User')).toThrow('Invalid User ID'); + }); + + it('should throw BadRequestException for undefined or null ID', () => { + expect(() => validateId(undefined as unknown as number, 'Pantry')).toThrow( + 'Invalid Pantry ID', + ); + expect(() => validateId(null as unknown as number, 'Pantry')).toThrow( + 'Invalid Pantry ID', + ); + }); +}); From e9dcc5c211e5891a2aa10362507da07d3b2cf637 Mon Sep 17 00:00:00 2001 From: Dalton Burkhart Date: Sun, 12 Oct 2025 14:43:11 -0400 Subject: [PATCH 06/15] Final commit: --- .../src/foodRequests/request.service.spec.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apps/backend/src/foodRequests/request.service.spec.ts b/apps/backend/src/foodRequests/request.service.spec.ts index 291d4dfb..2d5447c3 100644 --- a/apps/backend/src/foodRequests/request.service.spec.ts +++ b/apps/backend/src/foodRequests/request.service.spec.ts @@ -65,6 +65,21 @@ describe('OrdersService', () => { relations: ['order'], }); }); + + it('should throw an error if the request id is not found', async () => { + const requestId = 999; + + mockRequestsRepository.findOne.mockResolvedValueOnce(null); + + await expect(service.findOne(requestId)).rejects.toThrow( + `Request ${requestId} not found`, + ); + + expect(mockRequestsRepository.findOne).toHaveBeenCalledWith({ + where: { requestId }, + relations: ['order'], + }); + }); }); describe('create', () => { From 459fb3edc9a9c769f149160a58bb579b64f66da4 Mon Sep 17 00:00:00 2001 From: Dalton Burkhart Date: Sun, 12 Oct 2025 16:02:57 -0400 Subject: [PATCH 07/15] Final commit fr --- .../foodRequests/request.controller.spec.ts | 38 +++++++------------ .../src/foodRequests/request.service.ts | 2 +- .../src/utils/validation.utils.spec.ts | 4 +- 3 files changed, 17 insertions(+), 27 deletions(-) diff --git a/apps/backend/src/foodRequests/request.controller.spec.ts b/apps/backend/src/foodRequests/request.controller.spec.ts index 8be6031e..2292376c 100644 --- a/apps/backend/src/foodRequests/request.controller.spec.ts +++ b/apps/backend/src/foodRequests/request.controller.spec.ts @@ -10,6 +10,19 @@ const mockRequestsService = mock(); const mockOrdersService = mock(); const mockAWSS3Service = mock(); +const foodRequest = { + requestId: 1, + pantryId: 1, + requestedSize: 'Medium (5-10 boxes)', + requestedItems: ['Canned Goods', 'Vegetables'], + additionalInformation: 'No onions, please.', + requestedAt: null, + dateReceived: null, + feedback: null, + photos: null, + order: null, +}; + describe('RequestsController', () => { let controller: RequestsController; @@ -46,18 +59,6 @@ describe('RequestsController', () => { describe('GET /:requestId', () => { it('should call requestsService.findOne and return a specific food request', async () => { - const foodRequest = { - requestId: 1, - pantryId: 1, - requestedSize: 'Medium (5-10 boxes)', - requestedItems: ['Canned Goods', 'Vegetables'], - additionalInformation: 'No onions, please.', - requestedAt: null, - dateReceived: null, - feedback: null, - photos: null, - order: null, - }; const requestId = 1; mockRequestsService.findOne.mockResolvedValueOnce(foodRequest); @@ -72,18 +73,7 @@ describe('RequestsController', () => { describe('GET /get-all-requests/:pantryId', () => { it('should call requestsService.find and return all food requests for a specific pantry', async () => { const foodRequests = [ - { - requestId: 1, - pantryId: 1, - requestedSize: 'Medium (5-10 boxes)', - requestedItems: ['Canned Goods', 'Vegetables'], - additionalInformation: 'No onions, please.', - requestedAt: null, - dateReceived: null, - feedback: null, - photos: null, - order: null, - }, + foodRequest, { requestId: 2, pantryId: 1, diff --git a/apps/backend/src/foodRequests/request.service.ts b/apps/backend/src/foodRequests/request.service.ts index 89473eb5..0b54a6e0 100644 --- a/apps/backend/src/foodRequests/request.service.ts +++ b/apps/backend/src/foodRequests/request.service.ts @@ -40,7 +40,7 @@ export class RequestsService { validateId(pantryId, 'Pantry'); const pantry = await this.find(pantryId); - if (!pantry || pantry.length === 0) { + if (!pantry) { throw new NotFoundException(`Pantry ${pantryId} not found`); } diff --git a/apps/backend/src/utils/validation.utils.spec.ts b/apps/backend/src/utils/validation.utils.spec.ts index 25ca615f..af00515b 100644 --- a/apps/backend/src/utils/validation.utils.spec.ts +++ b/apps/backend/src/utils/validation.utils.spec.ts @@ -13,10 +13,10 @@ describe('validateId', () => { it('should throw BadRequestException for undefined or null ID', () => { expect(() => validateId(undefined as unknown as number, 'Pantry')).toThrow( - 'Invalid Pantry ID', + new BadRequestException('Invalid Pantry ID'), ); expect(() => validateId(null as unknown as number, 'Pantry')).toThrow( - 'Invalid Pantry ID', + new BadRequestException('Invalid Pantry ID'), ); }); }); From ce376a142cbac3633d2b59ab0919d8187fcc12e1 Mon Sep 17 00:00:00 2001 From: Dalton Burkhart Date: Mon, 13 Oct 2025 14:17:08 -0400 Subject: [PATCH 08/15] Final commit fr this time! --- .../backend/src/foodRequests/request.service.spec.ts | 12 ++++++++++++ apps/backend/src/foodRequests/request.service.ts | 4 +++- apps/backend/src/utils/validation.utils.spec.ts | 5 +++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/apps/backend/src/foodRequests/request.service.spec.ts b/apps/backend/src/foodRequests/request.service.spec.ts index 2d5447c3..0a03ffc6 100644 --- a/apps/backend/src/foodRequests/request.service.spec.ts +++ b/apps/backend/src/foodRequests/request.service.spec.ts @@ -4,8 +4,11 @@ import { Repository } from 'typeorm'; import { FoodRequest } from './request.entity'; import { RequestsService } from './request.service'; import { mock } from 'jest-mock-extended'; +import { get } from 'http'; +import { Pantry } from '../pantries/pantries.entity'; const mockRequestsRepository = mock>(); +const mockPantryRepository = mock>(); const mockRequest = { requestId: 1, @@ -29,6 +32,7 @@ describe('OrdersService', () => { mockRequestsRepository.create.mockReset(); mockRequestsRepository.save.mockReset(); mockRequestsRepository.find.mockReset(); + mockPantryRepository.findOneBy.mockReset(); const module = await Test.createTestingModule({ providers: [ @@ -37,6 +41,10 @@ describe('OrdersService', () => { provide: getRepositoryToken(FoodRequest), useValue: mockRequestsRepository, }, + { + provide: getRepositoryToken(Pantry), + useValue: mockPantryRepository, + }, ], }).compile(); @@ -48,6 +56,7 @@ describe('OrdersService', () => { mockRequestsRepository.create.mockReset(); mockRequestsRepository.save.mockReset(); mockRequestsRepository.find.mockReset(); + mockPantryRepository.findOneBy.mockReset(); }); it('should be defined', () => { @@ -84,6 +93,9 @@ describe('OrdersService', () => { describe('create', () => { it('should successfully create and return a new food request', async () => { + mockPantryRepository.findOneBy.mockResolvedValueOnce({ + pantryId: 1, + } as unknown as Pantry); mockRequestsRepository.create.mockReturnValueOnce(mockRequest); mockRequestsRepository.save.mockResolvedValueOnce(mockRequest); mockRequestsRepository.find.mockResolvedValueOnce([mockRequest]); diff --git a/apps/backend/src/foodRequests/request.service.ts b/apps/backend/src/foodRequests/request.service.ts index 0b54a6e0..d14ef53a 100644 --- a/apps/backend/src/foodRequests/request.service.ts +++ b/apps/backend/src/foodRequests/request.service.ts @@ -7,11 +7,13 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { FoodRequest } from './request.entity'; import { validateId } from '../utils/validation.utils'; +import { Pantry } from '../pantries/pantries.entity'; @Injectable() export class RequestsService { constructor( @InjectRepository(FoodRequest) private repo: Repository, + @InjectRepository(Pantry) private pantryRepo: Repository, ) {} async findOne(requestId: number): Promise { @@ -39,7 +41,7 @@ export class RequestsService { ): Promise { validateId(pantryId, 'Pantry'); - const pantry = await this.find(pantryId); + const pantry = await this.pantryRepo.findOneBy({ pantryId }); if (!pantry) { throw new NotFoundException(`Pantry ${pantryId} not found`); } diff --git a/apps/backend/src/utils/validation.utils.spec.ts b/apps/backend/src/utils/validation.utils.spec.ts index af00515b..cde1bad6 100644 --- a/apps/backend/src/utils/validation.utils.spec.ts +++ b/apps/backend/src/utils/validation.utils.spec.ts @@ -7,8 +7,9 @@ describe('validateId', () => { }); it('should throw BadRequestException for ID < 1', () => { - expect(() => validateId(0, 'User')).toThrow(BadRequestException); - expect(() => validateId(0, 'User')).toThrow('Invalid User ID'); + expect(() => validateId(0, 'User')).toThrow( + new BadRequestException('Invalid User ID'), + ); }); it('should throw BadRequestException for undefined or null ID', () => { From 97fdb60c79e8a561078227c37daba3046727c251 Mon Sep 17 00:00:00 2001 From: Dalton Burkhart Date: Wed, 26 Nov 2025 19:14:18 -0500 Subject: [PATCH 09/15] Final commit --- apps/backend/src/config/typeorm.ts | 2 +- .../donationItems/donationItems.controller.ts | 9 ++++--- .../src/donations/donations.controller.ts | 4 +-- apps/backend/src/donations/types.ts | 2 +- .../src/foodRequests/request.controller.ts | 9 ++++--- ...1763963056712-AllergyFriendlyToBoolType.ts | 20 +++++++------- apps/backend/src/orders/order.service.spec.ts | 8 +++++- .../src/pantries/pantries.controller.ts | 20 ++++++++------ apps/backend/src/pantries/pantries.entity.ts | 24 ++++++++++------- apps/backend/src/pantries/types.ts | 2 +- .../forms/pantryApplicationForm.tsx | 27 +++++++++++++------ .../src/components/forms/requestFormModal.tsx | 4 +-- apps/frontend/src/main.tsx | 2 +- apps/frontend/src/theme.ts | 13 ++++++--- apps/frontend/src/types/pantryEnums.ts | 4 +-- apps/frontend/src/types/types.ts | 11 ++++---- 16 files changed, 98 insertions(+), 63 deletions(-) diff --git a/apps/backend/src/config/typeorm.ts b/apps/backend/src/config/typeorm.ts index 0fae486a..64c3bf7d 100644 --- a/apps/backend/src/config/typeorm.ts +++ b/apps/backend/src/config/typeorm.ts @@ -56,7 +56,7 @@ const config = { UpdatePantriesTable1742739750279, UpdatePantryUserFields1731171000000, RemoveOrdersDonationId1761500262238, - AllergyFriendlyToBoolType1763963056712 + AllergyFriendlyToBoolType1763963056712, ], }; diff --git a/apps/backend/src/donationItems/donationItems.controller.ts b/apps/backend/src/donationItems/donationItems.controller.ts index 96381fc0..96be5673 100644 --- a/apps/backend/src/donationItems/donationItems.controller.ts +++ b/apps/backend/src/donationItems/donationItems.controller.ts @@ -38,8 +38,8 @@ export class DonationItemsController { status: { type: 'string', example: 'available' }, ozPerItem: { type: 'integer', example: 5 }, estimatedValue: { type: 'integer', example: 100 }, - foodType: { - type: 'string', + foodType: { + type: 'string', enum: Object.values(FoodType), example: FoodType.DAIRY_FREE_ALTERNATIVES, }, @@ -59,7 +59,10 @@ export class DonationItemsController { foodType: FoodType; }, ): Promise { - if (body.foodType && !Object.values(FoodType).includes(body.foodType as FoodType)) { + if ( + body.foodType && + !Object.values(FoodType).includes(body.foodType as FoodType) + ) { throw new BadRequestException('Invalid foodtype'); } return this.donationItemsService.create( diff --git a/apps/backend/src/donations/donations.controller.ts b/apps/backend/src/donations/donations.controller.ts index d748df66..6bcd2a7e 100644 --- a/apps/backend/src/donations/donations.controller.ts +++ b/apps/backend/src/donations/donations.controller.ts @@ -46,8 +46,8 @@ export class DonationsController { type: 'string', format: 'date-time', }, - status: { - type: 'string', + status: { + type: 'string', enum: Object.values(DonationStatus), example: DonationStatus.AVAILABLE, }, diff --git a/apps/backend/src/donations/types.ts b/apps/backend/src/donations/types.ts index 549ee6c6..16387987 100644 --- a/apps/backend/src/donations/types.ts +++ b/apps/backend/src/donations/types.ts @@ -2,4 +2,4 @@ export enum DonationStatus { AVAILABLE = 'available', FULFILLED = 'fulfilled', MATCHING = 'matching', -} \ No newline at end of file +} diff --git a/apps/backend/src/foodRequests/request.controller.ts b/apps/backend/src/foodRequests/request.controller.ts index 358107d4..1f449491 100644 --- a/apps/backend/src/foodRequests/request.controller.ts +++ b/apps/backend/src/foodRequests/request.controller.ts @@ -50,8 +50,8 @@ export class RequestsController { type: 'object', properties: { pantryId: { type: 'integer', example: 1 }, - requestedSize: { - type: 'string', + requestedSize: { + type: 'string', enum: Object.values(RequestSize), example: RequestSize.LARGE, }, @@ -158,7 +158,10 @@ export class RequestsController { ); const request = await this.requestsService.findOne(requestId); - await this.ordersService.updateStatus(request.order.orderId, OrderStatus.DELIVERED); + await this.ordersService.updateStatus( + request.order.orderId, + OrderStatus.DELIVERED, + ); return this.requestsService.updateDeliveryDetails( requestId, diff --git a/apps/backend/src/migrations/1763963056712-AllergyFriendlyToBoolType.ts b/apps/backend/src/migrations/1763963056712-AllergyFriendlyToBoolType.ts index 079362d6..3a14c4d4 100644 --- a/apps/backend/src/migrations/1763963056712-AllergyFriendlyToBoolType.ts +++ b/apps/backend/src/migrations/1763963056712-AllergyFriendlyToBoolType.ts @@ -1,21 +1,21 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; +import { MigrationInterface, QueryRunner } from 'typeorm'; -export class AllergyFriendlyToBoolType1763963056712 implements MigrationInterface { - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(` +export class AllergyFriendlyToBoolType1763963056712 + implements MigrationInterface +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` ALTER TABLE pantries ALTER COLUMN dedicated_allergy_friendly TYPE BOOLEAN USING (FALSE), ALTER COLUMN dedicated_allergy_friendly SET NOT NULL; `); - } + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(` + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` ALTER TABLE pantries ALTER COLUMN dedicated_allergy_friendly TYPE VARCHAR(255), ALTER COLUMN dedicated_allergy_friendly DROP NOT NULL; `); - } - + } } diff --git a/apps/backend/src/orders/order.service.spec.ts b/apps/backend/src/orders/order.service.spec.ts index 4c64ac10..b65e335b 100644 --- a/apps/backend/src/orders/order.service.spec.ts +++ b/apps/backend/src/orders/order.service.spec.ts @@ -6,7 +6,13 @@ import { OrdersService } from './order.service'; import { mock } from 'jest-mock-extended'; import { Pantry } from '../pantries/pantries.entity'; import { User } from '../users/user.entity'; -import { AllergensConfidence, ClientVisitFrequency, PantryStatus, RefrigeratedDonation, ServeAllergicChildren } from '../pantries/types'; +import { + AllergensConfidence, + ClientVisitFrequency, + PantryStatus, + RefrigeratedDonation, + ServeAllergicChildren, +} from '../pantries/types'; import { OrderStatus } from './types'; const mockOrdersRepository = mock>(); diff --git a/apps/backend/src/pantries/pantries.controller.ts b/apps/backend/src/pantries/pantries.controller.ts index 6cc5d5da..d4aad408 100644 --- a/apps/backend/src/pantries/pantries.controller.ts +++ b/apps/backend/src/pantries/pantries.controller.ts @@ -5,13 +5,20 @@ import { Param, ParseIntPipe, Post, - ValidationPipe + ValidationPipe, } from '@nestjs/common'; import { Pantry } from './pantries.entity'; import { PantriesService } from './pantries.service'; import { PantryApplicationDto } from './dtos/pantry-application.dto'; import { ApiBody } from '@nestjs/swagger'; -import { Activity, AllergensConfidence, ClientVisitFrequency, RefrigeratedDonation, ReserveFoodForAllergic, ServeAllergicChildren } from './types'; +import { + Activity, + AllergensConfidence, + ClientVisitFrequency, + RefrigeratedDonation, + ReserveFoodForAllergic, + ServeAllergicChildren, +} from './types'; @Controller('pantries') export class PantriesController { @@ -142,12 +149,9 @@ export class PantriesController { type: 'array', items: { type: 'string', - enum: Object.values(Activity) + enum: Object.values(Activity), }, - example: [ - Activity.COLLECT_FEEDBACK, - Activity.CREATE_LABELED_SHELF, - ], + example: [Activity.COLLECT_FEEDBACK, Activity.CREATE_LABELED_SHELF], }, activitiesComments: { type: 'string', @@ -190,7 +194,7 @@ export class PantriesController { }) @Post() async submitPantryApplication( - @Body(new ValidationPipe()) + @Body(new ValidationPipe()) pantryData: PantryApplicationDto, ): Promise { return this.pantriesService.addPantry(pantryData); diff --git a/apps/backend/src/pantries/pantries.entity.ts b/apps/backend/src/pantries/pantries.entity.ts index e3426c41..20ddacff 100644 --- a/apps/backend/src/pantries/pantries.entity.ts +++ b/apps/backend/src/pantries/pantries.entity.ts @@ -63,10 +63,11 @@ export class Pantry { }) refrigeratedDonation: RefrigeratedDonation; - @Column({ name: 'reserve_food_for_allergic', - type: 'enum', - enum: ReserveFoodForAllergic, - enumName: 'reserve_food_for_allergic_enum' + @Column({ + name: 'reserve_food_for_allergic', + type: 'enum', + enum: ReserveFoodForAllergic, + enumName: 'reserve_food_for_allergic_enum', }) reserveFoodForAllergic: string; @@ -114,7 +115,11 @@ export class Pantry { // cascade: ['insert'] means that when we create a new // pantry, the pantry user will automatically be added // to the User table - @OneToOne(() => User, { nullable: false, cascade: ['insert'], onDelete: 'CASCADE' }) + @OneToOne(() => User, { + nullable: false, + cascade: ['insert'], + onDelete: 'CASCADE', + }) @JoinColumn({ name: 'pantry_user_id', referencedColumnName: 'id', @@ -136,12 +141,13 @@ export class Pantry { }) dateApplied: Date; - @Column({ - name: 'activities', + @Column({ + name: 'activities', type: 'enum', - enum: Activity, + enum: Activity, enumName: 'activity_enum', - array: true }) + array: true, + }) activities: Activity[]; @Column({ name: 'activities_comments', type: 'text', nullable: true }) diff --git a/apps/backend/src/pantries/types.ts b/apps/backend/src/pantries/types.ts index f776991b..cdf8b671 100644 --- a/apps/backend/src/pantries/types.ts +++ b/apps/backend/src/pantries/types.ts @@ -33,7 +33,7 @@ export enum PantryStatus { export enum Activity { CREATE_LABELED_SHELF = 'Create labeled shelf', PROVIDE_EDUCATIONAL_PAMPHLETS = 'Provide educational pamphlets', - TRACK_DIETARY_NEEDS ='Spreadsheet to track dietary needs', + TRACK_DIETARY_NEEDS = 'Spreadsheet to track dietary needs', POST_RESOURCE_FLYERS = 'Post allergen-free resource flyers', SURVEY_CLIENTS = 'Survey clients to determine medical dietary needs', COLLECT_FEEDBACK = 'Collect feedback from allergen-avoidant clients', diff --git a/apps/frontend/src/components/forms/pantryApplicationForm.tsx b/apps/frontend/src/components/forms/pantryApplicationForm.tsx index 4c7325bf..70f49e97 100644 --- a/apps/frontend/src/components/forms/pantryApplicationForm.tsx +++ b/apps/frontend/src/components/forms/pantryApplicationForm.tsx @@ -515,12 +515,18 @@ export const submitPantryApplicationForm: ActionFunction = async ({ const pantryApplicationData = new Map(); const ActivityStorageMap: Record = { - 'Create a labeled, allergy-friendly shelf or shelves': Activity.CREATE_LABELED_SHELF, - 'Provide clients and staff/volunteers with educational pamphlets': Activity.PROVIDE_EDUCATIONAL_PAMPHLETS, - "Use a spreadsheet to track clients' medical dietary needs and distribution of SSF items per month": Activity.TRACK_DIETARY_NEEDS, - 'Post allergen-free resource flyers throughout pantry': Activity.POST_RESOURCE_FLYERS, - 'Survey your clients to determine their medical dietary needs': Activity.SURVEY_CLIENTS, - 'Collect feedback from allergen-avoidant clients on SSF foods': Activity.COLLECT_FEEDBACK, + 'Create a labeled, allergy-friendly shelf or shelves': + Activity.CREATE_LABELED_SHELF, + 'Provide clients and staff/volunteers with educational pamphlets': + Activity.PROVIDE_EDUCATIONAL_PAMPHLETS, + "Use a spreadsheet to track clients' medical dietary needs and distribution of SSF items per month": + Activity.TRACK_DIETARY_NEEDS, + 'Post allergen-free resource flyers throughout pantry': + Activity.POST_RESOURCE_FLYERS, + 'Survey your clients to determine their medical dietary needs': + Activity.SURVEY_CLIENTS, + 'Collect feedback from allergen-avoidant clients on SSF foods': + Activity.COLLECT_FEEDBACK, 'Something else': Activity.SOMETHING_ELSE, }; @@ -538,11 +544,16 @@ export const submitPantryApplicationForm: ActionFunction = async ({ form.delete('restrictions'); const selectedActivities = form.getAll('activities') as string[]; - const convertedActivities = selectedActivities.map((activity) => ActivityStorageMap[activity]); + const convertedActivities = selectedActivities.map( + (activity) => ActivityStorageMap[activity], + ); pantryApplicationData.set('activities', convertedActivities); form.delete('activities'); - pantryApplicationData.set('dedicatedAllergyFriendly', form.get('dedicatedAllergyFriendly')); + pantryApplicationData.set( + 'dedicatedAllergyFriendly', + form.get('dedicatedAllergyFriendly'), + ); form.delete('dedicatedAllergyFriendly'); // Handle all other questions diff --git a/apps/frontend/src/components/forms/requestFormModal.tsx b/apps/frontend/src/components/forms/requestFormModal.tsx index cb12dc51..9861327a 100644 --- a/apps/frontend/src/components/forms/requestFormModal.tsx +++ b/apps/frontend/src/components/forms/requestFormModal.tsx @@ -132,9 +132,7 @@ const FoodRequestFormModal: React.FC = ({ - - {option} - + {option} ))} diff --git a/apps/frontend/src/main.tsx b/apps/frontend/src/main.tsx index 10110223..7f571895 100644 --- a/apps/frontend/src/main.tsx +++ b/apps/frontend/src/main.tsx @@ -14,4 +14,4 @@ root.render( , -); \ No newline at end of file +); diff --git a/apps/frontend/src/theme.ts b/apps/frontend/src/theme.ts index c472e41f..c504bb84 100644 --- a/apps/frontend/src/theme.ts +++ b/apps/frontend/src/theme.ts @@ -1,4 +1,9 @@ -import { createSystem, defaultConfig, defineConfig, defineTextStyles } from '@chakra-ui/react'; +import { + createSystem, + defaultConfig, + defineConfig, + defineTextStyles, +} from '@chakra-ui/react'; const textStyles = defineTextStyles({ body: { @@ -10,7 +15,7 @@ const textStyles = defineTextStyles({ value: { fontFamily: 'instrument', fontSize: '32px', - fontWeight: '400' + fontWeight: '400', }, }, h2: { @@ -53,7 +58,7 @@ const customConfig = defineConfig({ colors: { white: { value: '#fff' }, black: { value: '#000' }, - blue: { + blue: { ssf: { value: '#2B5061' }, 100: { value: '#bee3f8' }, }, @@ -79,4 +84,4 @@ const customConfig = defineConfig({ }, }); -export const system = createSystem(defaultConfig, customConfig); \ No newline at end of file +export const system = createSystem(defaultConfig, customConfig); diff --git a/apps/frontend/src/types/pantryEnums.ts b/apps/frontend/src/types/pantryEnums.ts index e5f13a6f..cdf8b671 100644 --- a/apps/frontend/src/types/pantryEnums.ts +++ b/apps/frontend/src/types/pantryEnums.ts @@ -33,7 +33,7 @@ export enum PantryStatus { export enum Activity { CREATE_LABELED_SHELF = 'Create labeled shelf', PROVIDE_EDUCATIONAL_PAMPHLETS = 'Provide educational pamphlets', - TRACK_DIETARY_NEEDS ='Spreadsheet to track dietary needs', + TRACK_DIETARY_NEEDS = 'Spreadsheet to track dietary needs', POST_RESOURCE_FLYERS = 'Post allergen-free resource flyers', SURVEY_CLIENTS = 'Survey clients to determine medical dietary needs', COLLECT_FEEDBACK = 'Collect feedback from allergen-avoidant clients', @@ -44,4 +44,4 @@ export enum ReserveFoodForAllergic { YES = 'Yes', SOME = 'Some', NO = 'No', -} \ No newline at end of file +} diff --git a/apps/frontend/src/types/types.ts b/apps/frontend/src/types/types.ts index cb901272..619bdf4f 100644 --- a/apps/frontend/src/types/types.ts +++ b/apps/frontend/src/types/types.ts @@ -1,12 +1,12 @@ -import { - RefrigeratedDonation, - ReserveFoodForAllergic, - ClientVisitFrequency, +import { + RefrigeratedDonation, + ReserveFoodForAllergic, + ClientVisitFrequency, ServeAllergicChildren, AllergensConfidence, PantryStatus, Activity, -} from "./pantryEnums"; +} from './pantryEnums'; // Note: The API calls as currently written do not // return a pantry's SSF representative or pantry @@ -223,4 +223,3 @@ export enum DonationStatus { FULFILLED = 'fulfilled', MATCHING = 'matching', } - From 82158b3c0bf3e3e42e7aba2d809815394d2de74b Mon Sep 17 00:00:00 2001 From: Dalton Burkhart Date: Thu, 27 Nov 2025 02:13:21 -0500 Subject: [PATCH 10/15] Resolved comments --- .../foodRequests/request.controller.spec.ts | 135 +++++++----------- .../src/foodRequests/request.service.spec.ts | 51 +++---- yarn.lock | 23 ++- 3 files changed, 98 insertions(+), 111 deletions(-) diff --git a/apps/backend/src/foodRequests/request.controller.spec.ts b/apps/backend/src/foodRequests/request.controller.spec.ts index 2292376c..4a02a308 100644 --- a/apps/backend/src/foodRequests/request.controller.spec.ts +++ b/apps/backend/src/foodRequests/request.controller.spec.ts @@ -5,22 +5,17 @@ import { mock } from 'jest-mock-extended'; import { AWSS3Service } from '../aws/aws-s3.service'; import { OrdersService } from '../orders/order.service'; import { Readable } from 'stream'; +import { FoodRequest } from './request.entity'; +import { RequestSize } from './types'; +import { OrderStatus } from '../orders/types'; const mockRequestsService = mock(); const mockOrdersService = mock(); const mockAWSS3Service = mock(); -const foodRequest = { +const foodRequest: Partial = { requestId: 1, pantryId: 1, - requestedSize: 'Medium (5-10 boxes)', - requestedItems: ['Canned Goods', 'Vegetables'], - additionalInformation: 'No onions, please.', - requestedAt: null, - dateReceived: null, - feedback: null, - photos: null, - order: null, }; describe('RequestsController', () => { @@ -40,11 +35,11 @@ describe('RequestsController', () => { useValue: mockRequestsService, }, { - provide: AWSS3Service, + provide: OrdersService, useValue: mockOrdersService, }, { - provide: OrdersService, + provide: AWSS3Service, useValue: mockAWSS3Service, }, ], @@ -61,7 +56,7 @@ describe('RequestsController', () => { it('should call requestsService.findOne and return a specific food request', async () => { const requestId = 1; - mockRequestsService.findOne.mockResolvedValueOnce(foodRequest); + mockRequestsService.findOne.mockResolvedValueOnce(foodRequest as FoodRequest); const result = await controller.getRequest(requestId); @@ -72,24 +67,16 @@ describe('RequestsController', () => { describe('GET /get-all-requests/:pantryId', () => { it('should call requestsService.find and return all food requests for a specific pantry', async () => { - const foodRequests = [ + const foodRequests: Partial[] = [ foodRequest, { requestId: 2, pantryId: 1, - requestedSize: 'Large (10-20 boxes)', - requestedItems: ['Rice', 'Beans'], - additionalInformation: 'Gluten-free items only.', - requestedAt: null, - dateReceived: null, - feedback: null, - photos: null, - order: null, }, ]; const pantryId = 1; - mockRequestsService.find.mockResolvedValueOnce(foodRequests); + mockRequestsService.find.mockResolvedValueOnce(foodRequests as FoodRequest[]); const result = await controller.getAllPantryRequests(pantryId); @@ -100,9 +87,9 @@ describe('RequestsController', () => { describe('POST /create', () => { it('should call requestsService.create and return the created food request', async () => { - const createBody = { + const createBody: Partial = { pantryId: 1, - requestedSize: 'Medium (5-10 boxes)', + requestedSize: RequestSize.MEDIUM, requestedItems: ['Test item 1', 'Test item 2'], additionalInformation: 'Test information.', dateReceived: null, @@ -110,16 +97,16 @@ describe('RequestsController', () => { photos: null, }; - const createdRequest = { + const createdRequest: Partial = { requestId: 1, ...createBody, requestedAt: new Date(), order: null, }; - mockRequestsService.create.mockResolvedValueOnce(createdRequest); + mockRequestsService.create.mockResolvedValueOnce(createdRequest as FoodRequest); - const result = await controller.createRequest(createBody); + const result = await controller.createRequest(createBody as FoodRequest); expect(result).toEqual(createdRequest); expect(mockRequestsService.create).toHaveBeenCalledWith( @@ -134,14 +121,16 @@ describe('RequestsController', () => { }); }); - describe('POST /confirm-delivery', () => { - it('should call awsService.upload and then call orderService.updateDeliveryDetails and then call requestsService.updateDeliveryDetails and return the updated food request', async () => { + describe('POST /:requestId/confirm-delivery', () => { + it('should upload photos, update the order, then update the request', async () => { const requestId = 1; - const updateBody = { - deliveryDate: new Date(), - feedback: 'Delivery was on time.', + + const body = { + dateReceived: new Date().toISOString(), + feedback: 'Nice delivery!', }; + // Mock Photos const mockStream = new Readable(); mockStream._read = () => {}; @@ -151,8 +140,8 @@ describe('RequestsController', () => { originalname: 'photo1.jpg', encoding: '7bit', mimetype: 'image/jpeg', - buffer: Buffer.from('fake image content 1'), - size: 1234, + buffer: Buffer.from('image1'), + size: 1000, destination: '', filename: '', path: '', @@ -163,75 +152,61 @@ describe('RequestsController', () => { originalname: 'photo2.jpg', encoding: '7bit', mimetype: 'image/jpeg', - buffer: Buffer.from('fake image content 2'), - size: 5678, + buffer: Buffer.from('image2'), + size: 2000, destination: '', filename: '', path: '', stream: mockStream, - }, + } ]; - const updatedPhotoUrls = [ - 'https://s3.amazonaws.com/bucket/photo1.jpg', - 'https://s3.amazonaws.com/bucket/photo2.jpg', - ]; - mockAWSS3Service.upload.mockResolvedValueOnce(updatedPhotoUrls); + const uploadedUrls = ['https://fake-s3/photo1.jpg', 'https://fake-s3/photo2.jpg']; - const photoResult = await mockAWSS3Service.upload(photos); - expect(photoResult).toEqual(updatedPhotoUrls); - expect(mockAWSS3Service.upload).toHaveBeenCalledWith(photos); + // Mock AWS upload + mockAWSS3Service.upload.mockResolvedValue(uploadedUrls); - await mockOrdersService.updateStatus(requestId, 'deivered'); - expect(mockOrdersService.updateStatus).toHaveBeenCalledWith( + // Mock RequestsService.findOne + mockRequestsService.findOne.mockResolvedValue({ requestId, - 'deivered', - ); + pantryId: 1, + order: { orderId: 99 }, + } as FoodRequest); + + mockOrdersService.updateStatus.mockResolvedValue(); - // Mock the RequestsService.updateDeliveryDetails method const updatedRequest = { requestId, pantryId: 1, - requestedSize: 'Medium (5-10 boxes)', - requestedItems: ['Canned Goods', 'Vegetables'], - additionalInformation: 'No onions, please.', - requestedAt: new Date(), - dateReceived: updateBody.deliveryDate, - feedback: updateBody.feedback, - photos: updatedPhotoUrls, - order: null, + dateReceived: new Date(body.dateReceived), + feedback: body.feedback, + photos: uploadedUrls, }; - mockRequestsService.updateDeliveryDetails.mockResolvedValueOnce( - updatedRequest, - ); + mockRequestsService.updateDeliveryDetails.mockResolvedValue(updatedRequest as FoodRequest); - const requestResult = await mockRequestsService.updateDeliveryDetails( - requestId, - updateBody.deliveryDate, - updateBody.feedback, - updatedPhotoUrls, - ); + const result = await controller.confirmDelivery(requestId, body, photos); + + expect(mockAWSS3Service.upload).toHaveBeenCalledWith(photos); + + expect(mockRequestsService.findOne).toHaveBeenCalledWith(requestId); + + expect(mockOrdersService.updateStatus).toHaveBeenCalledWith(99, OrderStatus.DELIVERED); - expect(requestResult).toEqual(updatedRequest); expect(mockRequestsService.updateDeliveryDetails).toHaveBeenCalledWith( requestId, - updateBody.deliveryDate, - updateBody.feedback, - updatedPhotoUrls, + new Date(body.dateReceived), + body.feedback, + uploadedUrls, ); - }); - it('should throw an error if the received date is not properly formatted', async () => { - const requestId = 1; - const updateBody = { - dateReceived: 'invalid-date', - feedback: 'Delivery was on time.', - }; + expect(result).toEqual(updatedRequest); + }); + it('should throw an error for invalid date', async () => { await expect( - controller.confirmDelivery(requestId, updateBody, []), - ).rejects.toThrow(); + controller.confirmDelivery(1, { dateReceived: 'bad-date', feedback: '' }, []), + ).rejects.toThrow('Invalid date format for deliveryDate'); }); }); }); diff --git a/apps/backend/src/foodRequests/request.service.spec.ts b/apps/backend/src/foodRequests/request.service.spec.ts index 0a03ffc6..9b101ddf 100644 --- a/apps/backend/src/foodRequests/request.service.spec.ts +++ b/apps/backend/src/foodRequests/request.service.spec.ts @@ -4,16 +4,17 @@ import { Repository } from 'typeorm'; import { FoodRequest } from './request.entity'; import { RequestsService } from './request.service'; import { mock } from 'jest-mock-extended'; -import { get } from 'http'; import { Pantry } from '../pantries/pantries.entity'; +import { RequestSize } from './types'; +import { Order } from '../orders/order.entity'; +import { OrderStatus } from '../orders/types'; const mockRequestsRepository = mock>(); const mockPantryRepository = mock>(); -const mockRequest = { +const mockRequest: Partial = { requestId: 1, pantryId: 1, - requestedSize: 'Medium (5-10 boxes)', requestedItems: ['Canned Goods', 'Vegetables'], additionalInformation: 'No onions, please.', requestedAt: null, @@ -23,7 +24,7 @@ const mockRequest = { order: null, }; -describe('OrdersService', () => { +describe('RequestsService', () => { let service: RequestsService; beforeAll(async () => { @@ -66,7 +67,7 @@ describe('OrdersService', () => { describe('findOne', () => { it('should return a food request with the corresponding id', async () => { const requestId = 1; - mockRequestsRepository.findOne.mockResolvedValueOnce(mockRequest); + mockRequestsRepository.findOne.mockResolvedValueOnce(mockRequest as FoodRequest); const result = await service.findOne(requestId); expect(result).toEqual(mockRequest); expect(mockRequestsRepository.findOne).toHaveBeenCalledWith({ @@ -96,9 +97,9 @@ describe('OrdersService', () => { mockPantryRepository.findOneBy.mockResolvedValueOnce({ pantryId: 1, } as unknown as Pantry); - mockRequestsRepository.create.mockReturnValueOnce(mockRequest); - mockRequestsRepository.save.mockResolvedValueOnce(mockRequest); - mockRequestsRepository.find.mockResolvedValueOnce([mockRequest]); + mockRequestsRepository.create.mockReturnValueOnce(mockRequest as FoodRequest); + mockRequestsRepository.save.mockResolvedValueOnce(mockRequest as FoodRequest); + mockRequestsRepository.find.mockResolvedValueOnce([mockRequest as FoodRequest]); const result = await service.create( mockRequest.pantryId, @@ -129,7 +130,7 @@ describe('OrdersService', () => { await expect( service.create( invalidPantryId, - 'Medium (5-10 boxes)', + RequestSize.MEDIUM, ['Canned Goods', 'Vegetables'], 'Additional info', null, @@ -174,7 +175,7 @@ describe('OrdersService', () => { ]; const pantryId = 1; mockRequestsRepository.find.mockResolvedValueOnce( - mockRequests.slice(0, 2), + mockRequests.slice(0, 2) as FoodRequest[], ); const result = await service.find(pantryId); @@ -189,7 +190,7 @@ describe('OrdersService', () => { describe('updateDeliveryDetails', () => { it('should update and return the food request with new delivery details', async () => { - const mockOrder = { + const mockOrder: Partial = { orderId: 1, pantry: null, request: null, @@ -197,15 +198,15 @@ describe('OrdersService', () => { foodManufacturer: null, shippedBy: 1, donation: null, - status: 'shipped', + status: OrderStatus.SHIPPED, createdAt: new Date(), shippedAt: new Date(), deliveredAt: null, }; - const mockRequest2 = { + const mockRequest2: Partial = { ...mockRequest, - order: mockOrder, + order: mockOrder as Order, }; const requestId = 1; @@ -213,14 +214,14 @@ describe('OrdersService', () => { const feedback = 'Good delivery!'; const photos = ['photo1.jpg', 'photo2.jpg']; - mockRequestsRepository.findOne.mockResolvedValueOnce(mockRequest2); + mockRequestsRepository.findOne.mockResolvedValueOnce(mockRequest2 as FoodRequest); mockRequestsRepository.save.mockResolvedValueOnce({ ...mockRequest, dateReceived: deliveryDate, feedback, photos, - order: { ...mockOrder, status: 'fulfilled' }, - }); + order: { ...(mockOrder as Order), status: OrderStatus.DELIVERED } as Order, + } as FoodRequest); const result = await service.updateDeliveryDetails( requestId, @@ -234,7 +235,7 @@ describe('OrdersService', () => { dateReceived: deliveryDate, feedback, photos, - order: { ...mockOrder, status: 'fulfilled' }, + order: { ...mockOrder, status: 'delivered' }, }); expect(mockRequestsRepository.findOne).toHaveBeenCalledWith({ @@ -247,7 +248,7 @@ describe('OrdersService', () => { dateReceived: deliveryDate, feedback, photos, - order: { ...mockOrder, status: 'fulfilled' }, + order: { ...mockOrder, status: 'delivered' }, }); }); @@ -280,7 +281,7 @@ describe('OrdersService', () => { const feedback = 'Good delivery!'; const photos = ['photo1.jpg', 'photo2.jpg']; - mockRequestsRepository.findOne.mockResolvedValueOnce(mockRequest); + mockRequestsRepository.findOne.mockResolvedValueOnce(mockRequest as FoodRequest); await expect( service.updateDeliveryDetails( @@ -298,7 +299,7 @@ describe('OrdersService', () => { }); it('should throw an error if the order does not have a food manufacturer', async () => { - const mockOrder = { + const mockOrder: Partial = { orderId: 1, pantry: null, request: null, @@ -306,14 +307,14 @@ describe('OrdersService', () => { foodManufacturer: null, shippedBy: null, donation: null, - status: 'shipped', + status: OrderStatus.SHIPPED, createdAt: new Date(), shippedAt: new Date(), deliveredAt: null, }; - const mockRequest2 = { + const mockRequest2: Partial = { ...mockRequest, - order: mockOrder, + order: mockOrder as Order, }; const requestId = 1; @@ -321,7 +322,7 @@ describe('OrdersService', () => { const feedback = 'Good delivery!'; const photos = ['photo1.jpg', 'photo2.jpg']; - mockRequestsRepository.findOne.mockResolvedValueOnce(mockRequest2); + mockRequestsRepository.findOne.mockResolvedValueOnce(mockRequest2 as FoodRequest); await expect( service.updateDeliveryDetails( diff --git a/yarn.lock b/yarn.lock index fa7b60cd..54332615 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9674,6 +9674,22 @@ jest-message-util@^29.7.0: slash "^3.0.0" stack-utils "^2.0.3" +jest-mock-extended@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jest-mock-extended/-/jest-mock-extended-4.0.0.tgz#fe8cfa686c7ada4be2e7f7a3eced794b1338c18b" + integrity sha512-7BZpfuvLam+/HC+NxifIi9b+5VXj/utUDMPUqrDJehGWVuXPtLS9Jqlob2mJLrI/pg2k1S8DMfKDvEB88QNjaQ== + dependencies: + ts-essentials "^10.0.2" + +jest-mock@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.2.0.tgz#69f991614eeb4060189459d3584f710845bff45e" + integrity sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw== + dependencies: + "@jest/types" "30.2.0" + "@types/node" "*" + jest-util "30.2.0" + jest-mock@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" @@ -13396,11 +13412,6 @@ ts-essentials@^10.0.2: resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-10.1.1.tgz#4e1d29b7c9b33c1a2744482376634c4fafba5210" integrity sha512-4aTB7KLHKmUvkjNj8V+EdnmuVTiECzn3K+zIbRthumvHu+j44x3w63xpfs0JL3NGIzGXqoQ7AV591xHO+XrOTw== -ts-essentials@^10.0.2: - version "10.1.1" - resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-10.1.1.tgz#4e1d29b7c9b33c1a2744482376634c4fafba5210" - integrity sha512-4aTB7KLHKmUvkjNj8V+EdnmuVTiECzn3K+zIbRthumvHu+j44x3w63xpfs0JL3NGIzGXqoQ7AV591xHO+XrOTw== - ts-jest@^29.1.0: version "29.4.5" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.4.5.tgz#a6b0dc401e521515d5342234be87f1ca96390a6f" @@ -14359,4 +14370,4 @@ yocto-queue@^0.1.0: yocto-queue@^1.0.0: version "1.2.1" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.2.1.tgz#36d7c4739f775b3cbc28e6136e21aa057adec418" - integrity sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg== \ No newline at end of file + integrity sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg== From c3df1a761debc0a86eea3753610ae7338740cdd1 Mon Sep 17 00:00:00 2001 From: Dalton Burkhart Date: Thu, 27 Nov 2025 02:13:44 -0500 Subject: [PATCH 11/15] prettier --- .../foodRequests/request.controller.spec.ts | 34 ++++++++++++++----- .../src/foodRequests/request.service.spec.ts | 33 +++++++++++++----- 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/apps/backend/src/foodRequests/request.controller.spec.ts b/apps/backend/src/foodRequests/request.controller.spec.ts index 4a02a308..b0acb1da 100644 --- a/apps/backend/src/foodRequests/request.controller.spec.ts +++ b/apps/backend/src/foodRequests/request.controller.spec.ts @@ -56,7 +56,9 @@ describe('RequestsController', () => { it('should call requestsService.findOne and return a specific food request', async () => { const requestId = 1; - mockRequestsService.findOne.mockResolvedValueOnce(foodRequest as FoodRequest); + mockRequestsService.findOne.mockResolvedValueOnce( + foodRequest as FoodRequest, + ); const result = await controller.getRequest(requestId); @@ -76,7 +78,9 @@ describe('RequestsController', () => { ]; const pantryId = 1; - mockRequestsService.find.mockResolvedValueOnce(foodRequests as FoodRequest[]); + mockRequestsService.find.mockResolvedValueOnce( + foodRequests as FoodRequest[], + ); const result = await controller.getAllPantryRequests(pantryId); @@ -104,7 +108,9 @@ describe('RequestsController', () => { order: null, }; - mockRequestsService.create.mockResolvedValueOnce(createdRequest as FoodRequest); + mockRequestsService.create.mockResolvedValueOnce( + createdRequest as FoodRequest, + ); const result = await controller.createRequest(createBody as FoodRequest); @@ -158,10 +164,13 @@ describe('RequestsController', () => { filename: '', path: '', stream: mockStream, - } + }, ]; - const uploadedUrls = ['https://fake-s3/photo1.jpg', 'https://fake-s3/photo2.jpg']; + const uploadedUrls = [ + 'https://fake-s3/photo1.jpg', + 'https://fake-s3/photo2.jpg', + ]; // Mock AWS upload mockAWSS3Service.upload.mockResolvedValue(uploadedUrls); @@ -183,7 +192,9 @@ describe('RequestsController', () => { photos: uploadedUrls, }; - mockRequestsService.updateDeliveryDetails.mockResolvedValue(updatedRequest as FoodRequest); + mockRequestsService.updateDeliveryDetails.mockResolvedValue( + updatedRequest as FoodRequest, + ); const result = await controller.confirmDelivery(requestId, body, photos); @@ -191,7 +202,10 @@ describe('RequestsController', () => { expect(mockRequestsService.findOne).toHaveBeenCalledWith(requestId); - expect(mockOrdersService.updateStatus).toHaveBeenCalledWith(99, OrderStatus.DELIVERED); + expect(mockOrdersService.updateStatus).toHaveBeenCalledWith( + 99, + OrderStatus.DELIVERED, + ); expect(mockRequestsService.updateDeliveryDetails).toHaveBeenCalledWith( requestId, @@ -205,7 +219,11 @@ describe('RequestsController', () => { it('should throw an error for invalid date', async () => { await expect( - controller.confirmDelivery(1, { dateReceived: 'bad-date', feedback: '' }, []), + controller.confirmDelivery( + 1, + { dateReceived: 'bad-date', feedback: '' }, + [], + ), ).rejects.toThrow('Invalid date format for deliveryDate'); }); }); diff --git a/apps/backend/src/foodRequests/request.service.spec.ts b/apps/backend/src/foodRequests/request.service.spec.ts index 9b101ddf..9cbd826e 100644 --- a/apps/backend/src/foodRequests/request.service.spec.ts +++ b/apps/backend/src/foodRequests/request.service.spec.ts @@ -67,7 +67,9 @@ describe('RequestsService', () => { describe('findOne', () => { it('should return a food request with the corresponding id', async () => { const requestId = 1; - mockRequestsRepository.findOne.mockResolvedValueOnce(mockRequest as FoodRequest); + mockRequestsRepository.findOne.mockResolvedValueOnce( + mockRequest as FoodRequest, + ); const result = await service.findOne(requestId); expect(result).toEqual(mockRequest); expect(mockRequestsRepository.findOne).toHaveBeenCalledWith({ @@ -97,9 +99,15 @@ describe('RequestsService', () => { mockPantryRepository.findOneBy.mockResolvedValueOnce({ pantryId: 1, } as unknown as Pantry); - mockRequestsRepository.create.mockReturnValueOnce(mockRequest as FoodRequest); - mockRequestsRepository.save.mockResolvedValueOnce(mockRequest as FoodRequest); - mockRequestsRepository.find.mockResolvedValueOnce([mockRequest as FoodRequest]); + mockRequestsRepository.create.mockReturnValueOnce( + mockRequest as FoodRequest, + ); + mockRequestsRepository.save.mockResolvedValueOnce( + mockRequest as FoodRequest, + ); + mockRequestsRepository.find.mockResolvedValueOnce([ + mockRequest as FoodRequest, + ]); const result = await service.create( mockRequest.pantryId, @@ -214,13 +222,18 @@ describe('RequestsService', () => { const feedback = 'Good delivery!'; const photos = ['photo1.jpg', 'photo2.jpg']; - mockRequestsRepository.findOne.mockResolvedValueOnce(mockRequest2 as FoodRequest); + mockRequestsRepository.findOne.mockResolvedValueOnce( + mockRequest2 as FoodRequest, + ); mockRequestsRepository.save.mockResolvedValueOnce({ ...mockRequest, dateReceived: deliveryDate, feedback, photos, - order: { ...(mockOrder as Order), status: OrderStatus.DELIVERED } as Order, + order: { + ...(mockOrder as Order), + status: OrderStatus.DELIVERED, + } as Order, } as FoodRequest); const result = await service.updateDeliveryDetails( @@ -281,7 +294,9 @@ describe('RequestsService', () => { const feedback = 'Good delivery!'; const photos = ['photo1.jpg', 'photo2.jpg']; - mockRequestsRepository.findOne.mockResolvedValueOnce(mockRequest as FoodRequest); + mockRequestsRepository.findOne.mockResolvedValueOnce( + mockRequest as FoodRequest, + ); await expect( service.updateDeliveryDetails( @@ -322,7 +337,9 @@ describe('RequestsService', () => { const feedback = 'Good delivery!'; const photos = ['photo1.jpg', 'photo2.jpg']; - mockRequestsRepository.findOne.mockResolvedValueOnce(mockRequest2 as FoodRequest); + mockRequestsRepository.findOne.mockResolvedValueOnce( + mockRequest2 as FoodRequest, + ); await expect( service.updateDeliveryDetails( From d5442da658e8650db7c0984912813095765d0553 Mon Sep 17 00:00:00 2001 From: Dalton Burkhart Date: Sat, 6 Dec 2025 17:57:24 -0500 Subject: [PATCH 12/15] prettier --- .../pantries/dtos/pantry-application.dto.ts | 2 +- .../forms/pantryApplicationForm.tsx | 658 ++++++++++-------- .../src/components/forms/usPhoneInput.tsx | 5 +- apps/frontend/src/theme.ts | 6 +- 4 files changed, 394 insertions(+), 277 deletions(-) diff --git a/apps/backend/src/pantries/dtos/pantry-application.dto.ts b/apps/backend/src/pantries/dtos/pantry-application.dto.ts index 42510915..c7473b0f 100644 --- a/apps/backend/src/pantries/dtos/pantry-application.dto.ts +++ b/apps/backend/src/pantries/dtos/pantry-application.dto.ts @@ -52,7 +52,7 @@ export class PantryApplicationDto { @IsNotEmpty() @MaxLength(255) emailContactOther?: string; - + @IsOptional() @IsString() @IsNotEmpty() diff --git a/apps/frontend/src/components/forms/pantryApplicationForm.tsx b/apps/frontend/src/components/forms/pantryApplicationForm.tsx index 48b693e0..df185caa 100644 --- a/apps/frontend/src/components/forms/pantryApplicationForm.tsx +++ b/apps/frontend/src/components/forms/pantryApplicationForm.tsx @@ -67,18 +67,22 @@ const activityOptions = [ const PantryApplicationForm: React.FC = () => { const [contactPhone, setContactPhone] = useState(''); - const [secondaryContactPhone, setSecondaryContactPhone] = useState(''); + const [secondaryContactPhone, setSecondaryContactPhone] = + useState(''); const [activities, setActivities] = useState([]); const allergenClientsExactOption: string = 'I have an exact number'; const [allergenClients, setAllergenClients] = useState(); const [restrictions, setRestrictions] = useState([]); - const [reserveFoodForAllergic, setReserveFoodForAllergic] = useState(); - const [differentMailingAddress, setDifferentMailingAddress] = useState(); + const [reserveFoodForAllergic, setReserveFoodForAllergic] = + useState(); + const [differentMailingAddress, setDifferentMailingAddress] = useState< + boolean | null + >(); const [otherEmailContact, setOtherEmailContact] = useState(false); const sectionTitleStyles = { - fontFamily: "inter", + fontFamily: 'inter', fontWeight: '600', fontSize: 'md', color: 'gray.dark', @@ -86,16 +90,16 @@ const PantryApplicationForm: React.FC = () => { }; const sectionSubtitleStyles = { - fontFamily: "inter", + fontFamily: 'inter', fontWeight: '400', color: 'gray.light', mb: '2.25em', fontSize: 'sm', - } + }; const fieldHeaderStyles = { color: 'neutral.800', - fontFamily: "inter", + fontFamily: 'inter', fontSize: 'sm', fontWeight: '600', }; @@ -103,96 +107,121 @@ const PantryApplicationForm: React.FC = () => { return ( - + Partner Pantry Application - Thank you for your interest in partnering with Securing Safe Food (SSF) to help - serve clients with food allergies and other adverse reactions to foods. + Thank you for your interest in partnering with Securing Safe Food + (SSF) to help serve clients with food allergies and other adverse + reactions to foods. -
- + Pantry Application Form - + - This application helps us understand your pantry’s capacity and interest in - distributing allergen-friendly food. We’ll ask about your pantry’s current - practices, storage capabilities, and communication preferences. + This application helps us understand your pantry’s capacity and + interest in distributing allergen-friendly food. We’ll ask about + your pantry’s current practices, storage capabilities, and + communication preferences. - Please answer as accurately as possible. If you have any questions or need help, - don’t hesitate to contact the SSF team. + Please answer as accurately as possible. If you have any questions + or need help, don’t hesitate to contact the SSF team. - - - Primary Contact Information - + + Primary Contact Information First Name - + - + Last Name - + - + Phone Number - + Email Address - + - + - Is there someone at your pantry who can regularly check and respond to emails from SSF as needed?{' '} - + Is there someone at your pantry who can regularly check and + respond to emails from SSF as needed?{' '} + - setOtherEmailContact(e.value === 'Other')} + onValueChange={(e: { value: string }) => + setOtherEmailContact(e.value === 'Other') + } > {['Yes', 'No', 'Other'].map((value) => ( - - - + + - + {value} @@ -201,127 +230,151 @@ const PantryApplicationForm: React.FC = () => { - - + - - Secondary Contact Information - + Secondary Contact Information - - First Name - - + First Name + - - Last Name - - + Last Name + - - Phone Number - + Phone Number - - Email Address - - + Email Address + - - + + Food Shipment Address Please list your address for food shipments. - + Address Line 1 - + - + - - Address Line 2 - - + Address Line 2 + City/Town - + - + State/Region/Province - + - + Zip/Post Code - + - + - - Country - - + Country + - Does this address differ from your pantry's mailing address for documents?{' '} - + Does this address differ from your pantry's mailing address for + documents? - setDifferentMailingAddress(e.value === 'Yes')} + onValueChange={(e: { value: string }) => + setDifferentMailingAddress(e.value === 'Yes') + } name="differentMailingAddress" > {['Yes', 'No'].map((value) => ( - - - + + - + {value} @@ -331,28 +384,22 @@ const PantryApplicationForm: React.FC = () => { - Would your pantry be able to accept food deliveries - during standard business hours Mon-Fri?{' '} - + Would your pantry be able to accept food deliveries during + standard business hours Mon-Fri?{' '} + - + {['Yes', 'No'].map((value) => ( - - + - + {value} @@ -364,10 +411,13 @@ const PantryApplicationForm: React.FC = () => { Please note any delivery window restrictions. -