Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/backend/src/config/migrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { UpdatePantryFields1763762628431 } from '../migrations/1763762628431-Upd
import { PopulateDummyData1768501812134 } from '../migrations/1768501812134-populateDummyData';
import { RemovePantryFromOrders1769316004958 } from '../migrations/1769316004958-RemovePantryFromOrders';
import { UpdateManufacturerEntity1768680807820 } from '../migrations/1768680807820-UpdateManufacturerEntity';
import { UpdateOrderEntity1769990652833 } from '../migrations/1769990652833-UpdateOrderEntity';

const schemaMigrations = [
User1725726359198,
Expand Down Expand Up @@ -56,6 +57,7 @@ const schemaMigrations = [
PopulateDummyData1768501812134,
RemovePantryFromOrders1769316004958,
UpdateManufacturerEntity1768680807820,
UpdateOrderEntity1769990652833,
];

export default schemaMigrations;
46 changes: 0 additions & 46 deletions apps/backend/src/foodRequests/request.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,9 +337,6 @@ describe('RequestsService', () => {
const mockOrder: Partial<Order> = {
orderId: 1,
request: null,
requestId: 1,
foodManufacturer: null,
shippedBy: 1,
status: OrderStatus.SHIPPED,
createdAt: new Date(),
shippedAt: new Date(),
Expand Down Expand Up @@ -446,48 +443,5 @@ describe('RequestsService', () => {
relations: ['orders'],
});
});

it('should throw an error if the order does not have a food manufacturer', async () => {
const mockOrder: Partial<Order> = {
orderId: 1,
request: null,
requestId: 1,
foodManufacturer: null,
shippedBy: null,
status: OrderStatus.SHIPPED,
createdAt: new Date(),
shippedAt: new Date(),
deliveredAt: null,
};
const mockRequest2: Partial<FoodRequest> = {
...mockRequest,
orders: [mockOrder] as Order[],
};

const requestId = 1;
const deliveryDate = new Date();
const feedback = 'Good delivery!';
const photos = ['photo1.jpg', 'photo2.jpg'];

mockRequestsRepository.findOne.mockResolvedValueOnce(
mockRequest2 as FoodRequest,
);

await expect(
service.updateDeliveryDetails(
requestId,
deliveryDate,
feedback,
photos,
),
).rejects.toThrow(
'No associated food manufacturer found for an associated order',
);

expect(mockRequestsRepository.findOne).toHaveBeenCalledWith({
where: { requestId },
relations: ['orders'],
});
});
});
});
10 changes: 0 additions & 10 deletions apps/backend/src/foodRequests/request.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,16 +134,6 @@ export class RequestsService {
);
}

const orders = request.orders;

for (const order of orders) {
if (!order.shippedBy) {
throw new NotFoundException(
'No associated food manufacturer found for an associated order',
);
}
}

request.feedback = feedback;
request.dateReceived = deliveryDate;
request.photos = photos;
Expand Down
30 changes: 30 additions & 0 deletions apps/backend/src/migrations/1769990652833-UpdateOrderEntity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class UpdateOrderEntity1769990652833 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
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);

UPDATE orders
SET tracking_link = 'www.samplelink/samplelink',
shipping_cost = 20.00
WHERE status = 'delivered' OR status = 'shipped' AND shipped_at IS NOT NULL;

ALTER TABLE orders
RENAME COLUMN shipped_by TO food_manufacturer_id;
`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE orders
DROP COLUMN IF EXISTS tracking_link,
DROP COLUMN IF EXISTS shipping_cost;

ALTER TABLE orders
RENAME COLUMN food_manufacturer_id TO shipped_by;
`);
}
}
15 changes: 15 additions & 0 deletions apps/backend/src/orders/dtos/tracking-cost.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { IsUrl, IsNumber, Min, IsOptional } from 'class-validator';

export class TrackingCostDto {
@IsUrl({}, { message: 'Tracking link must be a valid URL' })
@IsOptional()
trackingLink?: string;

@IsNumber(
{ maxDecimalPlaces: 2 },
{ message: 'Shipping cost must have at most 2 decimal places' },
)
@Min(0, { message: 'Shipping cost cannot be negative' })
@IsOptional()
shippingCost?: number;
}
151 changes: 151 additions & 0 deletions apps/backend/src/orders/order.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import { mock } from 'jest-mock-extended';
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/manufacturers.entity';

const mockOrdersService = mock<OrdersService>();
const mockAllocationsService = mock<AllocationsService>();
Expand All @@ -27,21 +30,29 @@ describe('OrdersController', () => {
{ requestId: 3, pantry: mockPantries[2] as Pantry },
];

const mockFoodManufacturer: Partial<FoodManufacturer> = {
foodManufacturerId: 1,
foodManufacturerName: 'Test FM',
};

const mockOrders: Partial<Order>[] = [
{
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,
},
];

Expand Down Expand Up @@ -85,6 +96,107 @@ 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);
});
});

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;
Expand All @@ -100,4 +212,43 @@ describe('OrdersController', () => {
).toHaveBeenCalledWith(orderId);
});
});

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;
const trackingLink = 'www.samplelink/samplelink';
const shippingCost = 15.99;
const dto: TrackingCostDto = { trackingLink, shippingCost };

await controller.updateTrackingCostInfo(orderId, dto);

expect(mockOrdersService.updateTrackingCostInfo).toHaveBeenCalledWith(
orderId,
dto,
);
});
});
});
11 changes: 11 additions & 0 deletions apps/backend/src/orders/order.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Body,
Query,
BadRequestException,
ValidationPipe,
} from '@nestjs/common';
import { OrdersService } from './order.service';
import { Order } from './order.entity';
Expand All @@ -15,6 +16,7 @@ import { FoodManufacturer } from '../foodManufacturers/manufacturers.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 {
Expand Down Expand Up @@ -99,4 +101,13 @@ export class OrdersController {
}
return this.ordersService.updateStatus(orderId, newStatus as OrderStatus);
}

@Patch('/:orderId/update-tracking-cost-info')
async updateTrackingCostInfo(
@Param('orderId', ParseIntPipe) orderId: number,
@Body(new ValidationPipe())
dto: TrackingCostDto,
): Promise<void> {
return this.ordersService.updateTrackingCostInfo(orderId, dto);
}
}
Loading