From 874c654911469473e0fb9e9ca383b3003d10e7bc Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Sun, 1 Feb 2026 19:39:52 -0500 Subject: [PATCH 01/15] add tracking link and shipping cost to order --- apps/backend/src/config/typeorm.ts | 2 ++ .../1769990652833-UpdateOrderEntity.ts | 19 ++++++++++++++++++ .../src/orders/order.controller.spec.ts | 20 +++++++++++++++++++ apps/backend/src/orders/order.controller.ts | 13 ++++++++++++ apps/backend/src/orders/order.entity.ts | 17 ++++++++++++++++ apps/backend/src/orders/order.service.ts | 18 +++++++++++++++++ 6 files changed, 89 insertions(+) create mode 100644 apps/backend/src/migrations/1769990652833-UpdateOrderEntity.ts diff --git a/apps/backend/src/config/typeorm.ts b/apps/backend/src/config/typeorm.ts index 823846739..9f4faf764 100644 --- a/apps/backend/src/config/typeorm.ts +++ b/apps/backend/src/config/typeorm.ts @@ -27,6 +27,7 @@ import { RemoveMultipleVolunteerTypes1764811878152 } from '../migrations/1764811 import { RemoveUnusedStatuses1764816885341 } from '../migrations/1764816885341-RemoveUnusedStatuses'; import { UpdatePantryFields1763762628431 } from '../migrations/1763762628431-UpdatePantryFields'; import { PopulateDummyData1768501812134 } from '../migrations/1768501812134-populateDummyData'; +import { UpdateOrderEntity1769990652833 } from '../migrations/1769990652833-UpdateOrderEntity'; const config = { type: 'postgres', @@ -67,6 +68,7 @@ const config = { RemoveMultipleVolunteerTypes1764811878152, RemoveUnusedStatuses1764816885341, PopulateDummyData1768501812134, + UpdateOrderEntity1769990652833, ], }; diff --git a/apps/backend/src/migrations/1769990652833-UpdateOrderEntity.ts b/apps/backend/src/migrations/1769990652833-UpdateOrderEntity.ts new file mode 100644 index 000000000..0360fc8a7 --- /dev/null +++ b/apps/backend/src/migrations/1769990652833-UpdateOrderEntity.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateOrderEntity1769990652833 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE orders + ADD COLUMN IF NOT EXISTS tracking_link VARCHAR(255), + ADD COLUMN IF NOT EXISTS shipping_cost NUMERIC(10,2); + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE orders + DROP COLUMN IF EXISTS tracking_link, + DROP COLUMN IF EXISTS shipping_cost; + `); + } +} diff --git a/apps/backend/src/orders/order.controller.spec.ts b/apps/backend/src/orders/order.controller.spec.ts index 4c06f83aa..a82889853 100644 --- a/apps/backend/src/orders/order.controller.spec.ts +++ b/apps/backend/src/orders/order.controller.spec.ts @@ -73,4 +73,24 @@ describe('OrdersController', () => { ).toHaveBeenCalledWith(orderId); }); }); + + describe('updateTrackingAndCost', () => { + it('should call ordersService.updateTrackingAndCost with correct parameters', async () => { + const orderId = 1; + const trackingLink = 'www.samplelink/samplelink'; + const shippingCost = '15.99'; + + await controller.updateTrackingAndCost( + orderId, + trackingLink, + shippingCost, + ); + + expect(mockOrdersService.updateTrackingAndCost).toHaveBeenCalledWith( + orderId, + trackingLink, + shippingCost, + ); + }); + }); }); diff --git a/apps/backend/src/orders/order.controller.ts b/apps/backend/src/orders/order.controller.ts index 870dc1eff..f4081fff3 100644 --- a/apps/backend/src/orders/order.controller.ts +++ b/apps/backend/src/orders/order.controller.ts @@ -99,4 +99,17 @@ export class OrdersController { } return this.ordersService.updateStatus(orderId, newStatus as OrderStatus); } + + @Patch('/:orderId/update-tracking-and-cost') + async updateTrackingAndCost( + @Param('orderId', ParseIntPipe) orderId: number, + @Body('trackingLink') trackingLink: string, + @Body('shippingCost') shippingCost: string, + ): Promise { + return this.ordersService.updateTrackingAndCost( + orderId, + trackingLink, + shippingCost, + ); + } } diff --git a/apps/backend/src/orders/order.entity.ts b/apps/backend/src/orders/order.entity.ts index 7c40fdb4b..6ef531a80 100644 --- a/apps/backend/src/orders/order.entity.ts +++ b/apps/backend/src/orders/order.entity.ts @@ -77,4 +77,21 @@ export class Order { @OneToMany(() => Allocation, (allocation) => allocation.order) allocations: Allocation[]; + + @Column({ + name: 'tracking_link', + type: 'varchar', + length: 255, + nullable: true, + }) + trackingLink?: string; + + @Column({ + name: 'shipping_cost', + type: 'numeric', + precision: 10, + scale: 2, + nullable: true, + }) + shippingCost?: string; } diff --git a/apps/backend/src/orders/order.service.ts b/apps/backend/src/orders/order.service.ts index 05f37bca8..a03fc5c62 100644 --- a/apps/backend/src/orders/order.service.ts +++ b/apps/backend/src/orders/order.service.ts @@ -147,4 +147,22 @@ export class OrdersService { return orders; } + + async updateTrackingAndCost( + orderId: number, + trackingLink: string, + shippingCost: string, + ) { + validateId(orderId, 'Order'); + + await this.repo + .createQueryBuilder() + .update(Order) + .set({ + trackingLink: trackingLink, + shippingCost: shippingCost, + }) + .where('order_id = :orderId', { orderId }) + .execute(); + } } From 700b1245a0ade1bc4d2421e21d19b84a7e556c91 Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Sun, 1 Feb 2026 23:09:08 -0500 Subject: [PATCH 02/15] add dummy data --- .../src/migrations/1769990652833-UpdateOrderEntity.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/backend/src/migrations/1769990652833-UpdateOrderEntity.ts b/apps/backend/src/migrations/1769990652833-UpdateOrderEntity.ts index 0360fc8a7..780483df5 100644 --- a/apps/backend/src/migrations/1769990652833-UpdateOrderEntity.ts +++ b/apps/backend/src/migrations/1769990652833-UpdateOrderEntity.ts @@ -6,6 +6,11 @@ export class UpdateOrderEntity1769990652833 implements MigrationInterface { ALTER TABLE orders ADD COLUMN IF NOT EXISTS tracking_link VARCHAR(255), ADD COLUMN IF NOT EXISTS shipping_cost NUMERIC(10,2); + + UPDATE orders + SET tracking_link = 'www.samplelink/samplelink', + shipping_cost = 20.00 + WHERE status = 'delivered' OR status = 'shipped' AND shipped_at IS NOT NULL; `); } From e23efe2b824957b6187d69ceb5339e0be8bee245 Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Tue, 3 Feb 2026 23:12:42 -0500 Subject: [PATCH 03/15] add dto and review comments --- apps/backend/src/orders/dtos/tracking-cost.dto.ts | 10 ++++++++++ apps/backend/src/orders/order.controller.spec.ts | 10 +++++----- apps/backend/src/orders/order.controller.ts | 9 +++++---- apps/backend/src/orders/order.entity.ts | 2 +- apps/backend/src/orders/order.service.ts | 8 ++++---- apps/frontend/src/types/types.ts | 2 ++ 6 files changed, 27 insertions(+), 14 deletions(-) create mode 100644 apps/backend/src/orders/dtos/tracking-cost.dto.ts diff --git a/apps/backend/src/orders/dtos/tracking-cost.dto.ts b/apps/backend/src/orders/dtos/tracking-cost.dto.ts new file mode 100644 index 000000000..58e817b4f --- /dev/null +++ b/apps/backend/src/orders/dtos/tracking-cost.dto.ts @@ -0,0 +1,10 @@ +import { IsUrl, IsNumber, Min } from "class-validator"; + +export class TrackingCostDto { + @IsUrl({}, { message: 'Tracking link must be a valid URL' }) + trackingLink: string; + + @IsNumber({ maxDecimalPlaces: 2, }, { message: 'Shipping cost must have at most 2 decimal places' }) + @Min(0, { message: 'Shipping cost cannot be negative' }) + shippingCost: number; +} \ No newline at end of file diff --git a/apps/backend/src/orders/order.controller.spec.ts b/apps/backend/src/orders/order.controller.spec.ts index a82889853..68be728e5 100644 --- a/apps/backend/src/orders/order.controller.spec.ts +++ b/apps/backend/src/orders/order.controller.spec.ts @@ -6,6 +6,7 @@ import { Order } from './order.entity'; import { Allocation } from '../allocations/allocations.entity'; import { mock } from 'jest-mock-extended'; import { OrderStatus } from './types'; +import { TrackingCostDto } from './dtos/tracking-cost.dto'; const mockOrdersService = mock(); const mockAllocationsService = mock(); @@ -78,18 +79,17 @@ describe('OrdersController', () => { it('should call ordersService.updateTrackingAndCost with correct parameters', async () => { const orderId = 1; const trackingLink = 'www.samplelink/samplelink'; - const shippingCost = '15.99'; + const shippingCost = 15.99; + const dto: TrackingCostDto = { trackingLink, shippingCost }; await controller.updateTrackingAndCost( orderId, - trackingLink, - shippingCost, + dto, ); expect(mockOrdersService.updateTrackingAndCost).toHaveBeenCalledWith( orderId, - trackingLink, - shippingCost, + dto, ); }); }); diff --git a/apps/backend/src/orders/order.controller.ts b/apps/backend/src/orders/order.controller.ts index f4081fff3..330680107 100644 --- a/apps/backend/src/orders/order.controller.ts +++ b/apps/backend/src/orders/order.controller.ts @@ -7,6 +7,7 @@ import { Body, Query, BadRequestException, + ValidationPipe, } from '@nestjs/common'; import { OrdersService } from './order.service'; import { Order } from './order.entity'; @@ -15,6 +16,7 @@ import { FoodManufacturer } from '../foodManufacturers/manufacturer.entity'; import { FoodRequest } from '../foodRequests/request.entity'; import { AllocationsService } from '../allocations/allocations.service'; import { OrderStatus } from './types'; +import { TrackingCostDto } from './dtos/tracking-cost.dto'; @Controller('orders') export class OrdersController { @@ -103,13 +105,12 @@ export class OrdersController { @Patch('/:orderId/update-tracking-and-cost') async updateTrackingAndCost( @Param('orderId', ParseIntPipe) orderId: number, - @Body('trackingLink') trackingLink: string, - @Body('shippingCost') shippingCost: string, + @Body(new ValidationPipe()) + dto: TrackingCostDto, ): Promise { return this.ordersService.updateTrackingAndCost( orderId, - trackingLink, - shippingCost, + dto, ); } } diff --git a/apps/backend/src/orders/order.entity.ts b/apps/backend/src/orders/order.entity.ts index 6ef531a80..1cb49f84c 100644 --- a/apps/backend/src/orders/order.entity.ts +++ b/apps/backend/src/orders/order.entity.ts @@ -93,5 +93,5 @@ export class Order { scale: 2, nullable: true, }) - shippingCost?: string; + shippingCost?: number; } diff --git a/apps/backend/src/orders/order.service.ts b/apps/backend/src/orders/order.service.ts index a03fc5c62..c4fad40cd 100644 --- a/apps/backend/src/orders/order.service.ts +++ b/apps/backend/src/orders/order.service.ts @@ -7,6 +7,7 @@ import { FoodManufacturer } from '../foodManufacturers/manufacturer.entity'; import { FoodRequest } from '../foodRequests/request.entity'; import { validateId } from '../utils/validation.utils'; import { OrderStatus } from './types'; +import { TrackingCostDto } from './dtos/tracking-cost.dto'; @Injectable() export class OrdersService { @@ -150,8 +151,7 @@ export class OrdersService { async updateTrackingAndCost( orderId: number, - trackingLink: string, - shippingCost: string, + dto: TrackingCostDto, ) { validateId(orderId, 'Order'); @@ -159,8 +159,8 @@ export class OrdersService { .createQueryBuilder() .update(Order) .set({ - trackingLink: trackingLink, - shippingCost: shippingCost, + trackingLink: dto.trackingLink, + shippingCost: dto.shippingCost, }) .where('order_id = :orderId', { orderId }) .execute(); diff --git a/apps/frontend/src/types/types.ts b/apps/frontend/src/types/types.ts index eb98e3a22..8aba145bd 100644 --- a/apps/frontend/src/types/types.ts +++ b/apps/frontend/src/types/types.ts @@ -198,6 +198,8 @@ export interface Order { createdAt: string; shippedAt: string | null; deliveredAt: string | null; + trackingLink: string | null; + shippingCost: number | null; } export interface OrderItemDetails { From bbf6f459b83cc229cf947d0faa13c15ef8441083 Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Tue, 3 Feb 2026 23:15:29 -0500 Subject: [PATCH 04/15] prettier --- apps/backend/src/orders/order.controller.spec.ts | 5 +---- apps/backend/src/orders/order.controller.ts | 5 +---- apps/backend/src/orders/order.service.ts | 5 +---- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/apps/backend/src/orders/order.controller.spec.ts b/apps/backend/src/orders/order.controller.spec.ts index 68be728e5..400e322b2 100644 --- a/apps/backend/src/orders/order.controller.spec.ts +++ b/apps/backend/src/orders/order.controller.spec.ts @@ -82,10 +82,7 @@ describe('OrdersController', () => { const shippingCost = 15.99; const dto: TrackingCostDto = { trackingLink, shippingCost }; - await controller.updateTrackingAndCost( - orderId, - dto, - ); + await controller.updateTrackingAndCost(orderId, dto); expect(mockOrdersService.updateTrackingAndCost).toHaveBeenCalledWith( orderId, diff --git a/apps/backend/src/orders/order.controller.ts b/apps/backend/src/orders/order.controller.ts index 330680107..994617b5e 100644 --- a/apps/backend/src/orders/order.controller.ts +++ b/apps/backend/src/orders/order.controller.ts @@ -108,9 +108,6 @@ export class OrdersController { @Body(new ValidationPipe()) dto: TrackingCostDto, ): Promise { - return this.ordersService.updateTrackingAndCost( - orderId, - dto, - ); + return this.ordersService.updateTrackingAndCost(orderId, dto); } } diff --git a/apps/backend/src/orders/order.service.ts b/apps/backend/src/orders/order.service.ts index c4fad40cd..369751084 100644 --- a/apps/backend/src/orders/order.service.ts +++ b/apps/backend/src/orders/order.service.ts @@ -149,10 +149,7 @@ export class OrdersService { return orders; } - async updateTrackingAndCost( - orderId: number, - dto: TrackingCostDto, - ) { + async updateTrackingAndCost(orderId: number, dto: TrackingCostDto) { validateId(orderId, 'Order'); await this.repo From dee5203bc2d95eef74f3c6fd99d2686171ed88b3 Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Fri, 6 Feb 2026 15:26:08 -0500 Subject: [PATCH 05/15] add migration --- apps/backend/src/config/migrations.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/backend/src/config/migrations.ts b/apps/backend/src/config/migrations.ts index 9573736c8..3a3b1f323 100644 --- a/apps/backend/src/config/migrations.ts +++ b/apps/backend/src/config/migrations.ts @@ -25,6 +25,7 @@ import { RemoveUnusedStatuses1764816885341 } from '../migrations/1764816885341-R import { UpdatePantryFields1763762628431 } from '../migrations/1763762628431-UpdatePantryFields'; import { PopulateDummyData1768501812134 } from '../migrations/1768501812134-populateDummyData'; import { RemovePantryFromOrders1769316004958 } from '../migrations/1769316004958-RemovePantryFromOrders'; +import { UpdateOrderEntity1769990652833 } from '../migrations/1769990652833-UpdateOrderEntity'; const schemaMigrations = [ User1725726359198, @@ -54,6 +55,7 @@ const schemaMigrations = [ RemoveUnusedStatuses1764816885341, PopulateDummyData1768501812134, RemovePantryFromOrders1769316004958, + UpdateOrderEntity1769990652833, ]; export default schemaMigrations; From 7c82a53a6f5ef898e2102305c6c4e03716e18cb0 Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Sat, 7 Feb 2026 20:48:53 -0500 Subject: [PATCH 06/15] review comments --- .../src/orders/dtos/tracking-cost.dto.ts | 15 +++++--- .../src/orders/order.controller.spec.ts | 8 ++--- apps/backend/src/orders/order.controller.ts | 6 ++-- apps/backend/src/orders/order.service.ts | 36 +++++++++++++------ 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/apps/backend/src/orders/dtos/tracking-cost.dto.ts b/apps/backend/src/orders/dtos/tracking-cost.dto.ts index 58e817b4f..1c29ce6eb 100644 --- a/apps/backend/src/orders/dtos/tracking-cost.dto.ts +++ b/apps/backend/src/orders/dtos/tracking-cost.dto.ts @@ -1,10 +1,15 @@ -import { IsUrl, IsNumber, Min } from "class-validator"; +import { IsUrl, IsNumber, Min, IsOptional } from 'class-validator'; export class TrackingCostDto { @IsUrl({}, { message: 'Tracking link must be a valid URL' }) - trackingLink: string; + @IsOptional() + trackingLink?: string; - @IsNumber({ maxDecimalPlaces: 2, }, { message: 'Shipping cost must have at most 2 decimal places' }) + @IsNumber( + { maxDecimalPlaces: 2 }, + { message: 'Shipping cost must have at most 2 decimal places' }, + ) @Min(0, { message: 'Shipping cost cannot be negative' }) - shippingCost: number; -} \ No newline at end of file + @IsOptional() + shippingCost?: number; +} diff --git a/apps/backend/src/orders/order.controller.spec.ts b/apps/backend/src/orders/order.controller.spec.ts index 5e1ced6fb..980fc6bab 100644 --- a/apps/backend/src/orders/order.controller.spec.ts +++ b/apps/backend/src/orders/order.controller.spec.ts @@ -102,16 +102,16 @@ describe('OrdersController', () => { }); }); - describe('updateTrackingAndCost', () => { - it('should call ordersService.updateTrackingAndCost with correct parameters', async () => { + describe('updateTrackingCostInfo', () => { + it('should call ordersService.updateTrackingCostInfo with correct parameters', async () => { const orderId = 1; const trackingLink = 'www.samplelink/samplelink'; const shippingCost = 15.99; const dto: TrackingCostDto = { trackingLink, shippingCost }; - await controller.updateTrackingAndCost(orderId, dto); + await controller.updateTrackingCostInfo(orderId, dto); - expect(mockOrdersService.updateTrackingAndCost).toHaveBeenCalledWith( + expect(mockOrdersService.updateTrackingCostInfo).toHaveBeenCalledWith( orderId, dto, ); diff --git a/apps/backend/src/orders/order.controller.ts b/apps/backend/src/orders/order.controller.ts index 994617b5e..1093bc297 100644 --- a/apps/backend/src/orders/order.controller.ts +++ b/apps/backend/src/orders/order.controller.ts @@ -102,12 +102,12 @@ export class OrdersController { return this.ordersService.updateStatus(orderId, newStatus as OrderStatus); } - @Patch('/:orderId/update-tracking-and-cost') - async updateTrackingAndCost( + @Patch('/:orderId/update-tracking-cost-info') + async updateTrackingCostInfo( @Param('orderId', ParseIntPipe) orderId: number, @Body(new ValidationPipe()) dto: TrackingCostDto, ): Promise { - return this.ordersService.updateTrackingAndCost(orderId, dto); + return this.ordersService.updateTrackingCostInfo(orderId, dto); } } diff --git a/apps/backend/src/orders/order.service.ts b/apps/backend/src/orders/order.service.ts index e7dc00c83..4d329a5c7 100644 --- a/apps/backend/src/orders/order.service.ts +++ b/apps/backend/src/orders/order.service.ts @@ -1,4 +1,8 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; +import { + BadRequestException, + Injectable, + NotFoundException, +} from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, In } from 'typeorm'; import { Order } from './order.entity'; @@ -161,17 +165,27 @@ export class OrdersService { return orders; } - async updateTrackingAndCost(orderId: number, dto: TrackingCostDto) { + async updateTrackingCostInfo(orderId: number, dto: TrackingCostDto) { validateId(orderId, 'Order'); + if (!dto.trackingLink && !dto.shippingCost) { + throw new BadRequestException( + 'At least one of tracking link or shipping cost must be provided', + ); + } - await this.repo - .createQueryBuilder() - .update(Order) - .set({ - trackingLink: dto.trackingLink, - shippingCost: dto.shippingCost, - }) - .where('order_id = :orderId', { orderId }) - .execute(); + const order = await this.repo.findOneBy({ orderId }); + if (!order) { + throw new NotFoundException(`Order ${orderId} not found`); + } + if (order.status !== OrderStatus.SHIPPED) { + throw new BadRequestException( + 'Can only update tracking info for shipped orders', + ); + } + + if (dto.trackingLink) order.trackingLink = dto.trackingLink; + if (dto.shippingCost) order.shippingCost = dto.shippingCost; + + await this.repo.save(order); } } From f8d23f3a0a245d4ec833c9737db3c1c1d1f7470c Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Sat, 7 Feb 2026 21:26:29 -0500 Subject: [PATCH 07/15] fix entity --- apps/backend/src/orders/order.entity.ts | 22 +++++++++++----------- apps/backend/src/orders/order.service.ts | 9 +++++++-- apps/frontend/src/types/types.ts | 11 ++++++----- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/apps/backend/src/orders/order.entity.ts b/apps/backend/src/orders/order.entity.ts index f3cfb2599..a00532943 100644 --- a/apps/backend/src/orders/order.entity.ts +++ b/apps/backend/src/orders/order.entity.ts @@ -15,27 +15,27 @@ import { Allocation } from '../allocations/allocations.entity'; @Entity('orders') export class Order { @PrimaryGeneratedColumn({ name: 'order_id' }) - orderId: number; + orderId!: number; @ManyToOne(() => FoodRequest, { nullable: false }) @JoinColumn({ name: 'request_id', referencedColumnName: 'requestId', }) - request: FoodRequest; + request!: FoodRequest; @Column({ name: 'request_id' }) - requestId: number; + requestId!: number; - @ManyToOne(() => FoodManufacturer, { nullable: false }) + @ManyToOne(() => FoodManufacturer, { nullable: true }) @JoinColumn({ name: 'shipped_by', referencedColumnName: 'foodManufacturerId', }) - foodManufacturer: FoodManufacturer; + foodManufacturer?: FoodManufacturer; @Column({ name: 'shipped_by', nullable: true }) - shippedBy: number; + shippedBy?: number; @Column({ name: 'status', @@ -44,31 +44,31 @@ export class Order { enum: OrderStatus, default: OrderStatus.PENDING, }) - status: OrderStatus; + status!: OrderStatus; @CreateDateColumn({ name: 'created_at', type: 'timestamp', default: () => 'NOW()', }) - createdAt: Date; + createdAt!: Date; @Column({ name: 'shipped_at', type: 'timestamp', nullable: true, }) - shippedAt: Date | null; + shippedAt?: Date; @Column({ name: 'delivered_at', type: 'timestamp', nullable: true, }) - deliveredAt: Date | null; + deliveredAt?: Date; @OneToMany(() => Allocation, (allocation) => allocation.order) - allocations: Allocation[]; + allocations!: Allocation[]; @Column({ name: 'tracking_link', diff --git a/apps/backend/src/orders/order.service.ts b/apps/backend/src/orders/order.service.ts index 4d329a5c7..443671777 100644 --- a/apps/backend/src/orders/order.service.ts +++ b/apps/backend/src/orders/order.service.ts @@ -129,6 +129,11 @@ export class OrdersService { if (!order) { throw new NotFoundException(`Order ${orderId} not found`); } + if (!order.foodManufacturer) { + throw new NotFoundException( + `Order ${orderId} does not have a food manufacturer assigned`, + ); + } return order.foodManufacturer; } @@ -142,8 +147,8 @@ export class OrdersService { .set({ status: newStatus as OrderStatus, shippedBy: 1, - shippedAt: newStatus === OrderStatus.SHIPPED ? new Date() : null, - deliveredAt: newStatus === OrderStatus.DELIVERED ? new Date() : null, + shippedAt: newStatus === OrderStatus.SHIPPED ? new Date() : undefined, + deliveredAt: newStatus === OrderStatus.DELIVERED ? new Date() : undefined, }) .where('order_id = :orderId', { orderId }) .execute(); diff --git a/apps/frontend/src/types/types.ts b/apps/frontend/src/types/types.ts index 80e8d84a8..f56404aa4 100644 --- a/apps/frontend/src/types/types.ts +++ b/apps/frontend/src/types/types.ts @@ -195,13 +195,14 @@ export interface Order { request: FoodRequest; requestId: number; foodManufacturer: FoodManufacturer | null; - shippedBy: number | null; + shippedBy?: number; status: OrderStatus; createdAt: string; - shippedAt: string | null; - deliveredAt: string | null; - trackingLink: string | null; - shippingCost: number | null; + shippedAt?: Date; + deliveredAt?: Date; + allocations: Allocation[]; + trackingLink?: string; + shippingCost?: number; } export interface OrderItemDetails { From 28c04f39b259fa55e325b0b7a68a6fbe2df63c1c Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Sat, 7 Feb 2026 21:28:26 -0500 Subject: [PATCH 08/15] prettier --- apps/backend/src/orders/order.service.ts | 3 ++- apps/frontend/src/types/types.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/backend/src/orders/order.service.ts b/apps/backend/src/orders/order.service.ts index 443671777..f21657b73 100644 --- a/apps/backend/src/orders/order.service.ts +++ b/apps/backend/src/orders/order.service.ts @@ -148,7 +148,8 @@ export class OrdersService { status: newStatus as OrderStatus, shippedBy: 1, shippedAt: newStatus === OrderStatus.SHIPPED ? new Date() : undefined, - deliveredAt: newStatus === OrderStatus.DELIVERED ? new Date() : undefined, + deliveredAt: + newStatus === OrderStatus.DELIVERED ? new Date() : undefined, }) .where('order_id = :orderId', { orderId }) .execute(); diff --git a/apps/frontend/src/types/types.ts b/apps/frontend/src/types/types.ts index f56404aa4..8129c76c5 100644 --- a/apps/frontend/src/types/types.ts +++ b/apps/frontend/src/types/types.ts @@ -194,7 +194,7 @@ export interface Order { orderId: number; request: FoodRequest; requestId: number; - foodManufacturer: FoodManufacturer | null; + foodManufacturer?: FoodManufacturer; shippedBy?: number; status: OrderStatus; createdAt: string; From ee3c1726cc253ddc6c759eee0132dd00c37ba6ae Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Tue, 10 Feb 2026 14:41:52 -0500 Subject: [PATCH 09/15] fix logic --- apps/backend/src/orders/order.service.ts | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/apps/backend/src/orders/order.service.ts b/apps/backend/src/orders/order.service.ts index f21657b73..646d57f26 100644 --- a/apps/backend/src/orders/order.service.ts +++ b/apps/backend/src/orders/order.service.ts @@ -183,15 +183,35 @@ export class OrdersService { if (!order) { throw new NotFoundException(`Order ${orderId} not found`); } - if (order.status !== OrderStatus.SHIPPED) { + + const isFirstTimeSetting = !order.trackingLink && !order.shippingCost; + + if (isFirstTimeSetting && (!dto.shippingCost || !dto.shippingCost)) { + throw new BadRequestException( + 'Must provide both tracking link and shipping cost on initial assignment', + ); + } + + if ( + order.status !== OrderStatus.SHIPPED && + order.status !== OrderStatus.PENDING + ) { throw new BadRequestException( - 'Can only update tracking info for shipped orders', + 'Can only update tracking info for pending or shipped orders', ); } if (dto.trackingLink) order.trackingLink = dto.trackingLink; if (dto.shippingCost) order.shippingCost = dto.shippingCost; + if ( + order.status === OrderStatus.PENDING && + order.trackingLink && + order.shippingCost + ) { + this.updateStatus(orderId, OrderStatus.SHIPPED); + } + await this.repo.save(order); } } From ce920e59982088bf0637e16ba14b1b10f5757c2d Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Tue, 10 Feb 2026 15:34:45 -0500 Subject: [PATCH 10/15] fix --- apps/backend/src/orders/order.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/backend/src/orders/order.service.ts b/apps/backend/src/orders/order.service.ts index 646d57f26..18f9b296b 100644 --- a/apps/backend/src/orders/order.service.ts +++ b/apps/backend/src/orders/order.service.ts @@ -186,7 +186,7 @@ export class OrdersService { const isFirstTimeSetting = !order.trackingLink && !order.shippingCost; - if (isFirstTimeSetting && (!dto.shippingCost || !dto.shippingCost)) { + if (isFirstTimeSetting && (!dto.trackingLink || !dto.shippingCost)) { throw new BadRequestException( 'Must provide both tracking link and shipping cost on initial assignment', ); @@ -209,7 +209,7 @@ export class OrdersService { order.trackingLink && order.shippingCost ) { - this.updateStatus(orderId, OrderStatus.SHIPPED); + await this.updateStatus(orderId, OrderStatus.SHIPPED); } await this.repo.save(order); From 3e8eaa63a083ca6d7ab6169da9526f867c693436 Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Tue, 10 Feb 2026 15:45:20 -0500 Subject: [PATCH 11/15] fix setting shipped status --- apps/backend/src/orders/order.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/backend/src/orders/order.service.ts b/apps/backend/src/orders/order.service.ts index 18f9b296b..e6be1d7c3 100644 --- a/apps/backend/src/orders/order.service.ts +++ b/apps/backend/src/orders/order.service.ts @@ -209,7 +209,8 @@ export class OrdersService { order.trackingLink && order.shippingCost ) { - await this.updateStatus(orderId, OrderStatus.SHIPPED); + order.status = OrderStatus.SHIPPED; + order.shippedAt = new Date(); } await this.repo.save(order); From 7295d5acdfb705bec4947376690bfe665ed2cf93 Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Sun, 8 Feb 2026 20:07:55 -0500 Subject: [PATCH 12/15] add orders tests --- .../src/orders/order.controller.spec.ts | 149 +++++++++ apps/backend/src/orders/order.service.spec.ts | 297 ++++++++++++++++++ apps/backend/src/orders/order.service.ts | 4 - 3 files changed, 446 insertions(+), 4 deletions(-) diff --git a/apps/backend/src/orders/order.controller.spec.ts b/apps/backend/src/orders/order.controller.spec.ts index 980fc6bab..3ecd7b1c0 100644 --- a/apps/backend/src/orders/order.controller.spec.ts +++ b/apps/backend/src/orders/order.controller.spec.ts @@ -9,6 +9,8 @@ import { OrderStatus } from './types'; import { FoodRequest } from '../foodRequests/request.entity'; import { Pantry } from '../pantries/pantries.entity'; import { TrackingCostDto } from './dtos/tracking-cost.dto'; +import { BadRequestException } from '@nestjs/common'; +import { FoodManufacturer } from '../foodManufacturers/manufacturer.entity'; const mockOrdersService = mock(); const mockAllocationsService = mock(); @@ -28,21 +30,29 @@ describe('OrdersController', () => { { requestId: 3, pantry: mockPantries[2] as Pantry }, ]; + const mockFoodManufacturer: Partial = { + foodManufacturerId: 1, + foodManufacturerName: 'Test FM', + }; + const mockOrders: Partial[] = [ { orderId: 1, status: OrderStatus.PENDING, request: mockRequests[0] as FoodRequest, + foodManufacturer: mockFoodManufacturer as FoodManufacturer, }, { orderId: 2, status: OrderStatus.DELIVERED, request: mockRequests[1] as FoodRequest, + foodManufacturer: mockFoodManufacturer as FoodManufacturer, }, { orderId: 3, status: OrderStatus.SHIPPED, request: mockRequests[2] as FoodRequest, + foodManufacturer: mockFoodManufacturer as FoodManufacturer, }, ]; @@ -86,6 +96,122 @@ describe('OrdersController', () => { }); }); + describe('getCurrentOrders', () => { + it('should call ordersService.getCurrentOrders and return orders', async () => { + mockOrdersService.getCurrentOrders.mockResolvedValueOnce([ + mockOrders[0], + mockOrders[2], + ] as Order[]); + + const result = await controller.getCurrentOrders(); + + expect(result).toEqual([mockOrders[0], mockOrders[2]] as Order[]); + expect(mockOrdersService.getCurrentOrders).toHaveBeenCalled(); + }); + }); + + describe('getPastOrders', () => { + it('should call ordersService.getPastOrders and return orders', async () => { + mockOrdersService.getPastOrders.mockResolvedValueOnce([ + mockOrders[1], + ] as Order[]); + + const result = await controller.getPastOrders(); + + expect(result).toEqual([mockOrders[1]] as Order[]); + expect(mockOrdersService.getPastOrders).toHaveBeenCalled(); + }); + }); + + describe('getPantryFromOrder', () => { + it('should call ordersService.findOrderPantry and return pantry', async () => { + const orderId = 1; + mockOrdersService.findOrderPantry.mockResolvedValueOnce( + mockPantries[0] as Pantry, + ); + + const result = await controller.getPantryFromOrder(orderId); + + expect(result).toEqual(mockPantries[0] as Pantry); + expect(mockOrdersService.findOrderPantry).toHaveBeenCalledWith(orderId); + }); + + it('should throw for non-numeric input', async () => { + const invalidInput = NaN; + mockOrdersService.findOrderPantry.mockRejectedValue( + new BadRequestException( + 'Validation failed (numeric string is expected)', + ), + ); + + await expect(controller.getPantryFromOrder(invalidInput)).rejects.toThrow( + new BadRequestException( + 'Validation failed (numeric string is expected)', + ), + ); + }); + }); + + describe('getRequestFromOrder', () => { + it('should call ordersService.findOrderFoodRequest and return food request', async () => { + const orderId = 1; + mockOrdersService.findOrderFoodRequest.mockResolvedValueOnce( + mockRequests[0] as FoodRequest, + ); + + const result = await controller.getRequestFromOrder(orderId); + + expect(result).toEqual(mockRequests[0] as Pantry); + expect(mockOrdersService.findOrderFoodRequest).toHaveBeenCalledWith( + orderId, + ); + }); + }); + + describe('getManufacturerFromOrder', () => { + it('should call ordersService.findOrderFoodManufacturer and return FM', async () => { + const orderId = 1; + mockOrdersService.findOrderFoodManufacturer.mockResolvedValueOnce( + mockFoodManufacturer as FoodManufacturer, + ); + + const result = await controller.getManufacturerFromOrder(orderId); + + expect(result).toEqual(mockFoodManufacturer as FoodManufacturer); + expect(mockOrdersService.findOrderFoodManufacturer).toHaveBeenCalledWith( + orderId, + ); + }); + }); + + describe('getOrder', () => { + it('should call ordersService.findOne and return order', async () => { + const orderId = 1; + mockOrdersService.findOne.mockResolvedValueOnce(mockOrders[0] as Order); + + const result = await controller.getOrder(orderId); + + expect(result).toEqual(mockOrders[0] as Order); + expect(mockOrdersService.findOne).toHaveBeenCalledWith(orderId); + }); + }); + + describe('getOrderByRequestId', () => { + it('should call ordersService.findOrderByRequest and return order', async () => { + const requestId = 1; + mockOrdersService.findOrderByRequest.mockResolvedValueOnce( + mockOrders[0] as Order, + ); + + const result = await controller.getOrderByRequestId(requestId); + + expect(result).toEqual(mockOrders[0] as Order); + expect(mockOrdersService.findOrderByRequest).toHaveBeenCalledWith( + requestId, + ); + }); + }); + describe('getAllAllocationsByOrder', () => { it('should call allocationsService.getAllAllocationsByOrder and return allocations', async () => { const orderId = 1; @@ -102,6 +228,29 @@ describe('OrdersController', () => { }); }); + describe('updateStatus', () => { + it('should call ordersService.updateStatus', async () => { + const status = OrderStatus.DELIVERED; + const orderId = 1; + + await controller.updateStatus(orderId, status); + + expect(mockOrdersService.updateStatus).toHaveBeenCalledWith( + orderId, + status, + ); + }); + + it('should throw with invalid status', async () => { + const invalidStatus = 'invalid status'; + const orderId = 1; + + await expect( + controller.updateStatus(orderId, invalidStatus), + ).rejects.toThrow(new BadRequestException('Invalid status')); + }); + }); + describe('updateTrackingCostInfo', () => { it('should call ordersService.updateTrackingCostInfo with correct parameters', async () => { const orderId = 1; diff --git a/apps/backend/src/orders/order.service.spec.ts b/apps/backend/src/orders/order.service.spec.ts index e8e41949e..bbf8281b4 100644 --- a/apps/backend/src/orders/order.service.spec.ts +++ b/apps/backend/src/orders/order.service.spec.ts @@ -5,6 +5,8 @@ import { Order } from './order.entity'; import { testDataSource } from '../config/typeormTestDataSource'; import { OrderStatus } from './types'; import { Pantry } from '../pantries/pantries.entity'; +import { BadRequestException, NotFoundException } from '@nestjs/common'; +import { TrackingCostDto } from './dtos/tracking-cost.dto'; // Set 1 minute timeout for async DB operations jest.setTimeout(60000); @@ -121,4 +123,299 @@ describe('OrdersService', () => { expect(orders[0].status).toBe(OrderStatus.DELIVERED); }); }); + + describe('getCurrentOrders', () => { + it(`returns only orders with status 'pending' or 'shipped'`, async () => { + const orders = await service.getCurrentOrders(); + expect(orders).toHaveLength(2); + expect( + orders.every( + (order) => + order.status === OrderStatus.PENDING || + order.status === OrderStatus.SHIPPED, + ), + ).toBe(true); + }); + }); + + describe('getPastOrders', () => { + it(`returns only orders with status 'delivered'`, async () => { + const orders = await service.getPastOrders(); + expect(orders).toHaveLength(2); + expect( + orders.every((order) => order.status === OrderStatus.DELIVERED), + ).toBe(true); + }); + }); + + describe('findOne', () => { + it('returns order by ID', async () => { + const orderId = 1; + const result = await service.findOne(orderId); + + expect(result).toBeDefined(); + expect(result.orderId).toBe(1); + }); + + it('throws BadRequestException for non positive ID', async () => { + const orderId = 0; + await expect(service.findOne(orderId)).rejects.toThrow( + new BadRequestException('Invalid Order ID'), + ); + }); + + it('throws BadRequestException when not given ID', async () => { + await expect(service.findOne(null)).rejects.toThrow( + new BadRequestException('Invalid Order ID'), + ); + }); + + it('throws NotFoundException for non-existent order', async () => { + await expect(service.findOne(9999)).rejects.toThrow( + new NotFoundException('Order 9999 not found'), + ); + }); + }); + + describe('findOrderByRequest', () => { + it('returns order by request ID', async () => { + const order = await service.findOrderByRequest(1); + + expect(order).toBeDefined(); + expect(order.requestId).toBe(1); + }); + + it('validates ID', async () => { + await expect(service.findOrderByRequest(0)).rejects.toThrow( + new BadRequestException('Invalid Request ID'), + ); + await expect(service.findOrderByRequest(null)).rejects.toThrow( + new BadRequestException('Invalid Request ID'), + ); + }); + + it('throws NotFoundException for non-existent order', async () => { + await expect(service.findOrderByRequest(9999)).rejects.toThrow( + new NotFoundException('Order with request ID 9999 not found'), + ); + }); + }); + + describe('findOrderFoodRequest', () => { + it('returns food request of order', async () => { + const foodRequest = await service.findOrderFoodRequest(1); + + expect(foodRequest).toBeDefined(); + expect(foodRequest.requestId).toBe(1); + }); + + it('validates order ID', async () => { + await expect(service.findOrderFoodRequest(0)).rejects.toThrow( + new BadRequestException('Invalid Order ID'), + ); + await expect(service.findOrderFoodRequest(null)).rejects.toThrow( + new BadRequestException('Invalid Order ID'), + ); + }); + + it('throws NotFoundException for non-existent order', async () => { + await expect(service.findOrderFoodRequest(9999)).rejects.toThrow( + new NotFoundException('Order 9999 not found'), + ); + }); + }); + + describe('findOrderPantry', () => { + it('returns pantry of order', async () => { + const pantry = await service.findOrderPantry(1); + + expect(pantry).toBeDefined(); + expect(pantry.pantryName).toEqual('Community Food Pantry Downtown'); + expect(pantry.pantryId).toEqual(1); + }); + }); + + describe('findOrderFoodManufacturer', () => { + it('returns FM of order', async () => { + const foodManufacturer = await service.findOrderFoodManufacturer(2); + + expect(foodManufacturer).toBeDefined(); + expect(foodManufacturer.foodManufacturerName).toEqual('Healthy Foods Co'); + expect(foodManufacturer.foodManufacturerId).toEqual(2); + }); + + it('validates order ID', async () => { + await expect(service.findOrderFoodManufacturer(0)).rejects.toThrow( + new BadRequestException('Invalid Order ID'), + ); + await expect(service.findOrderFoodManufacturer(null)).rejects.toThrow( + new BadRequestException('Invalid Order ID'), + ); + }); + + it('throws NotFoundException for non-existent order', async () => { + await expect(service.findOrderFoodManufacturer(9999)).rejects.toThrow( + new NotFoundException('Order 9999 not found'), + ); + }); + }); + + describe('updateStatus', () => { + it('updates order status to delivered', async () => { + const orderId = 3; + const order = await service.findOne(orderId); + + expect(order.status).toEqual(OrderStatus.SHIPPED); + + await service.updateStatus(orderId, OrderStatus.DELIVERED); + const updatedOrder = await service.findOne(orderId); + + expect(updatedOrder.status).toEqual(OrderStatus.DELIVERED); + expect(updatedOrder.deliveredAt).toBeDefined(); + }); + + it('updates order status to shipped', async () => { + const orderId = 4; + const order = await service.findOne(orderId); + + expect(order.status).toEqual(OrderStatus.PENDING); + + await service.updateStatus(orderId, OrderStatus.SHIPPED); + const updatedOrder = await service.findOne(orderId); + + expect(updatedOrder.status).toEqual(OrderStatus.SHIPPED); + expect(updatedOrder.shippedAt).toBeDefined(); + expect(updatedOrder.deliveredAt).toBeNull(); + }); + + it('validates order ID', async () => { + await expect( + service.updateStatus(0, OrderStatus.DELIVERED), + ).rejects.toThrow(new BadRequestException('Invalid Order ID')); + await expect( + service.updateStatus(null, OrderStatus.DELIVERED), + ).rejects.toThrow(new BadRequestException('Invalid Order ID')); + }); + }); + + describe('getOrdersByPantry', () => { + it('validates pantry ID', async () => { + await expect(service.getOrdersByPantry(0)).rejects.toThrow( + new BadRequestException('Invalid Pantry ID'), + ); + await expect(service.getOrdersByPantry(null)).rejects.toThrow( + new BadRequestException('Invalid Pantry ID'), + ); + }); + + it('returns order from pantry ID', async () => { + const pantryId = 1; + const orders = await service.getOrdersByPantry(pantryId); + + expect(orders.length).toBe(2); + expect(orders.every((order) => order.request.pantryId === 1)).toBe(true); + }); + + it('returns empty list for pantry with no orderes', async () => { + const pantryId = 5; + const orders = await service.getOrdersByPantry(pantryId); + + expect(orders).toEqual([]); + }); + }); + + describe('updateTrackingCostInfo', () => { + it('validates order ID', async () => { + await expect(service.updateTrackingCostInfo(0, {})).rejects.toThrow( + new BadRequestException('Invalid Order ID'), + ); + await expect(service.updateTrackingCostInfo(null, {})).rejects.toThrow( + new BadRequestException('Invalid Order ID'), + ); + }); + + it('throws when order is non-existent', async () => { + const trackingCostDto: TrackingCostDto = { + trackingLink: 'test', + shippingCost: 5.99, + }; + + await expect( + service.updateTrackingCostInfo(9999, trackingCostDto), + ).rejects.toThrow(new NotFoundException('Order 9999 not found')); + }); + + it('throws when tracking link and shipping cost not given', async () => { + await expect(service.updateTrackingCostInfo(3, {})).rejects.toThrow( + new BadRequestException( + 'At least one of tracking link or shipping cost must be provided', + ), + ); + }); + + it('updates tracking link for shipped order', async () => { + const trackingCostDto: TrackingCostDto = { + trackingLink: 'samplelink.com', + }; + + await service.updateTrackingCostInfo(3, trackingCostDto); + + const order = await service.findOne(3); + expect(order.trackingLink).toBeDefined(); + expect(order.trackingLink).toEqual('samplelink.com'); + }); + + it('updates shipping cost for shipped order', async () => { + const trackingCostDto: TrackingCostDto = { + shippingCost: 12.99, + }; + + await service.updateTrackingCostInfo(3, trackingCostDto); + + const order = await service.findOne(3); + expect(order.shippingCost).toBeDefined(); + expect(order.shippingCost).toEqual('12.99'); + }); + + it('updates both shipping cost and tracking link', async () => { + const trackingCostDto: TrackingCostDto = { + trackingLink: 'testtracking.com', + shippingCost: 7.5, + }; + + await service.updateTrackingCostInfo(3, trackingCostDto); + + const order = await service.findOne(3); + expect(order.trackingLink).toEqual('testtracking.com'); + expect(order.shippingCost).toEqual('7.50'); + }); + }); + + it('throws BadRequestException for non-shipped order', async () => { + const trackingCostDto: TrackingCostDto = { + trackingLink: 'testtracking.com', + shippingCost: 7.5, + }; + + const order1 = await service.findOne(2); + const order2 = await service.findOne(4); + + expect(order1.status).toEqual(OrderStatus.DELIVERED); + expect(order2.status).toEqual(OrderStatus.PENDING); + + await expect( + service.updateTrackingCostInfo(2, trackingCostDto), + ).rejects.toThrow( + new BadRequestException( + 'Can only update tracking info for shipped orders', + ), + ); + await expect( + service.updateTrackingCostInfo(4, trackingCostDto), + ).rejects.toThrow( + new BadRequestException( + 'Can only update tracking info for shipped orders', + ), + ); + }); }); diff --git a/apps/backend/src/orders/order.service.ts b/apps/backend/src/orders/order.service.ts index e6be1d7c3..adf327b76 100644 --- a/apps/backend/src/orders/order.service.ts +++ b/apps/backend/src/orders/order.service.ts @@ -97,10 +97,6 @@ export class OrdersService { pantryId: request.pantryId, }); - if (!pantry) { - throw new NotFoundException(`Pantry ${request.pantryId} not found`); - } - return pantry; } From 0f1059832c8ea942769417761e52239a27202de7 Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Sun, 8 Feb 2026 20:12:16 -0500 Subject: [PATCH 13/15] move test inside describe --- apps/backend/src/orders/order.service.spec.ts | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/apps/backend/src/orders/order.service.spec.ts b/apps/backend/src/orders/order.service.spec.ts index bbf8281b4..94ccfa2c3 100644 --- a/apps/backend/src/orders/order.service.spec.ts +++ b/apps/backend/src/orders/order.service.spec.ts @@ -389,33 +389,33 @@ describe('OrdersService', () => { expect(order.trackingLink).toEqual('testtracking.com'); expect(order.shippingCost).toEqual('7.50'); }); - }); - it('throws BadRequestException for non-shipped order', async () => { - const trackingCostDto: TrackingCostDto = { - trackingLink: 'testtracking.com', - shippingCost: 7.5, - }; - - const order1 = await service.findOne(2); - const order2 = await service.findOne(4); - - expect(order1.status).toEqual(OrderStatus.DELIVERED); - expect(order2.status).toEqual(OrderStatus.PENDING); - - await expect( - service.updateTrackingCostInfo(2, trackingCostDto), - ).rejects.toThrow( - new BadRequestException( - 'Can only update tracking info for shipped orders', - ), - ); - await expect( - service.updateTrackingCostInfo(4, trackingCostDto), - ).rejects.toThrow( - new BadRequestException( - 'Can only update tracking info for shipped orders', - ), - ); + it('throws BadRequestException for non-shipped order', async () => { + const trackingCostDto: TrackingCostDto = { + trackingLink: 'testtracking.com', + shippingCost: 7.5, + }; + + const order1 = await service.findOne(2); + const order2 = await service.findOne(4); + + expect(order1.status).toEqual(OrderStatus.DELIVERED); + expect(order2.status).toEqual(OrderStatus.PENDING); + + await expect( + service.updateTrackingCostInfo(2, trackingCostDto), + ).rejects.toThrow( + new BadRequestException( + 'Can only update tracking info for shipped orders', + ), + ); + await expect( + service.updateTrackingCostInfo(4, trackingCostDto), + ).rejects.toThrow( + new BadRequestException( + 'Can only update tracking info for shipped orders', + ), + ); + }); }); }); From 1a806ca5f987baf07d2bbcb02d5051af4cc3f322 Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Tue, 10 Feb 2026 15:46:39 -0500 Subject: [PATCH 14/15] add test for fixed logic --- apps/backend/src/orders/order.service.spec.ts | 61 ++++++++++++++++--- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/apps/backend/src/orders/order.service.spec.ts b/apps/backend/src/orders/order.service.spec.ts index 94ccfa2c3..033cf13e3 100644 --- a/apps/backend/src/orders/order.service.spec.ts +++ b/apps/backend/src/orders/order.service.spec.ts @@ -390,32 +390,77 @@ describe('OrdersService', () => { expect(order.shippingCost).toEqual('7.50'); }); - it('throws BadRequestException for non-shipped order', async () => { + it('throws BadRequestException for delivered order', async () => { const trackingCostDto: TrackingCostDto = { trackingLink: 'testtracking.com', shippingCost: 7.5, }; + const orderId = 2; - const order1 = await service.findOne(2); - const order2 = await service.findOne(4); + const order = await service.findOne(orderId); - expect(order1.status).toEqual(OrderStatus.DELIVERED); - expect(order2.status).toEqual(OrderStatus.PENDING); + expect(order.status).toEqual(OrderStatus.DELIVERED); await expect( - service.updateTrackingCostInfo(2, trackingCostDto), + service.updateTrackingCostInfo(orderId, trackingCostDto), ).rejects.toThrow( new BadRequestException( - 'Can only update tracking info for shipped orders', + 'Can only update tracking info for pending or shipped orders', ), ); + }); + + it('throws when both fields are not provided for first time setting', async () => { + const trackingCostDto: TrackingCostDto = { + trackingLink: 'testtracking.com', + }; + const orderId = 4; + + const order = await service.findOne(orderId); + + expect(order.shippedAt).toBeNull(); + expect(order.trackingLink).toBeNull(); + await expect( service.updateTrackingCostInfo(4, trackingCostDto), ).rejects.toThrow( new BadRequestException( - 'Can only update tracking info for shipped orders', + 'Must provide both tracking link and shipping cost on initial assignment', ), ); }); + + it('sets status to shipped when both fields provided and previous status pending', async () => { + const trackingCostDto: TrackingCostDto = { + trackingLink: 'testtracking.com', + shippingCost: 5.75, + }; + const orderId = 4; + + const order = await service.findOne(orderId); + console.log('BEFORE UPDATE:', { + orderId: order.orderId, + status: order.status, + trackingLink: order.trackingLink, + shippingCost: order.shippingCost, + shippedAt: order.shippedAt, + }); + + expect(order.status).toEqual(OrderStatus.PENDING); + expect(order.shippedAt).toBeNull(); + + await service.updateTrackingCostInfo(orderId, trackingCostDto); + + const updatedOrder = await service.findOne(orderId); + console.log('AFTER UPDATE:', { + orderId: updatedOrder.orderId, + status: updatedOrder.status, + trackingLink: updatedOrder.trackingLink, + shippingCost: updatedOrder.shippingCost, + shippedAt: updatedOrder.shippedAt, + }); + expect(updatedOrder.status).toEqual(OrderStatus.SHIPPED); + expect(updatedOrder.shippedAt).toBeDefined(); + }); }); }); From f0d69658a8f2880593c1c43b04c25658a31a3a0f Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Tue, 10 Feb 2026 15:50:40 -0500 Subject: [PATCH 15/15] remove validate id tests --- apps/backend/src/orders/order.service.spec.ts | 54 ------------------- 1 file changed, 54 deletions(-) diff --git a/apps/backend/src/orders/order.service.spec.ts b/apps/backend/src/orders/order.service.spec.ts index 033cf13e3..e554f11b1 100644 --- a/apps/backend/src/orders/order.service.spec.ts +++ b/apps/backend/src/orders/order.service.spec.ts @@ -185,15 +185,6 @@ describe('OrdersService', () => { expect(order.requestId).toBe(1); }); - it('validates ID', async () => { - await expect(service.findOrderByRequest(0)).rejects.toThrow( - new BadRequestException('Invalid Request ID'), - ); - await expect(service.findOrderByRequest(null)).rejects.toThrow( - new BadRequestException('Invalid Request ID'), - ); - }); - it('throws NotFoundException for non-existent order', async () => { await expect(service.findOrderByRequest(9999)).rejects.toThrow( new NotFoundException('Order with request ID 9999 not found'), @@ -209,15 +200,6 @@ describe('OrdersService', () => { expect(foodRequest.requestId).toBe(1); }); - it('validates order ID', async () => { - await expect(service.findOrderFoodRequest(0)).rejects.toThrow( - new BadRequestException('Invalid Order ID'), - ); - await expect(service.findOrderFoodRequest(null)).rejects.toThrow( - new BadRequestException('Invalid Order ID'), - ); - }); - it('throws NotFoundException for non-existent order', async () => { await expect(service.findOrderFoodRequest(9999)).rejects.toThrow( new NotFoundException('Order 9999 not found'), @@ -244,15 +226,6 @@ describe('OrdersService', () => { expect(foodManufacturer.foodManufacturerId).toEqual(2); }); - it('validates order ID', async () => { - await expect(service.findOrderFoodManufacturer(0)).rejects.toThrow( - new BadRequestException('Invalid Order ID'), - ); - await expect(service.findOrderFoodManufacturer(null)).rejects.toThrow( - new BadRequestException('Invalid Order ID'), - ); - }); - it('throws NotFoundException for non-existent order', async () => { await expect(service.findOrderFoodManufacturer(9999)).rejects.toThrow( new NotFoundException('Order 9999 not found'), @@ -287,27 +260,9 @@ describe('OrdersService', () => { expect(updatedOrder.shippedAt).toBeDefined(); expect(updatedOrder.deliveredAt).toBeNull(); }); - - it('validates order ID', async () => { - await expect( - service.updateStatus(0, OrderStatus.DELIVERED), - ).rejects.toThrow(new BadRequestException('Invalid Order ID')); - await expect( - service.updateStatus(null, OrderStatus.DELIVERED), - ).rejects.toThrow(new BadRequestException('Invalid Order ID')); - }); }); describe('getOrdersByPantry', () => { - it('validates pantry ID', async () => { - await expect(service.getOrdersByPantry(0)).rejects.toThrow( - new BadRequestException('Invalid Pantry ID'), - ); - await expect(service.getOrdersByPantry(null)).rejects.toThrow( - new BadRequestException('Invalid Pantry ID'), - ); - }); - it('returns order from pantry ID', async () => { const pantryId = 1; const orders = await service.getOrdersByPantry(pantryId); @@ -325,15 +280,6 @@ describe('OrdersService', () => { }); describe('updateTrackingCostInfo', () => { - it('validates order ID', async () => { - await expect(service.updateTrackingCostInfo(0, {})).rejects.toThrow( - new BadRequestException('Invalid Order ID'), - ); - await expect(service.updateTrackingCostInfo(null, {})).rejects.toThrow( - new BadRequestException('Invalid Order ID'), - ); - }); - it('throws when order is non-existent', async () => { const trackingCostDto: TrackingCostDto = { trackingLink: 'test',