Skip to content
Merged
2 changes: 2 additions & 0 deletions apps/backend/src/config/typeorm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { RemovePantryFromOrders1769316004958 } from '../migrations/1769316004958-RemovePantryFromOrders';

const config = {
type: 'postgres',
Expand Down Expand Up @@ -67,6 +68,7 @@ const config = {
RemoveMultipleVolunteerTypes1764811878152,
RemoveUnusedStatuses1764816885341,
PopulateDummyData1768501812134,
RemovePantryFromOrders1769316004958,
],
};

Expand Down
7 changes: 7 additions & 0 deletions apps/backend/src/foodRequests/request.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import {
PrimaryGeneratedColumn,
CreateDateColumn,
OneToMany,
ManyToOne,
JoinColumn,
} from 'typeorm';
import { Order } from '../orders/order.entity';
import { RequestSize } from './types';
import { Pantry } from '../pantries/pantries.entity';

@Entity('food_requests')
export class FoodRequest {
Expand All @@ -16,6 +19,10 @@ export class FoodRequest {
@Column({ name: 'pantry_id', type: 'int' })
pantryId: number;

@ManyToOne(() => Pantry, { nullable: false })
@JoinColumn({ name: 'pantry_id', referencedColumnName: 'pantryId' })
pantry: Pantry;

@Column({
name: 'requested_size',
type: 'enum',
Expand Down
2 changes: 0 additions & 2 deletions apps/backend/src/foodRequests/request.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,6 @@ describe('RequestsService', () => {
it('should update and return the food request with new delivery details', async () => {
const mockOrder: Partial<Order> = {
orderId: 1,
pantry: null,
request: null,
requestId: 1,
foodManufacturer: null,
Expand Down Expand Up @@ -451,7 +450,6 @@ describe('RequestsService', () => {
it('should throw an error if the order does not have a food manufacturer', async () => {
const mockOrder: Partial<Order> = {
orderId: 1,
pantry: null,
request: null,
requestId: 1,
foodManufacturer: null,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class RemovePantryFromOrders1769316004958 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE orders
DROP CONSTRAINT IF EXISTS fk_pantry,
DROP COLUMN IF EXISTS pantry_id;
`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE orders
ADD COLUMN pantry_id INT;
UPDATE orders o
SET pantry_id = fr.pantry_id
FROM food_requests fr
WHERE o.request_id = fr.request_id;
ALTER TABLE orders
ALTER COLUMN pantry_id SET NOT NULL,
ADD CONSTRAINT fk_pantry FOREIGN KEY(pantry_id) REFERENCES pantries(pantry_id);
`);
}
}
39 changes: 33 additions & 6 deletions apps/backend/src/orders/order.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,43 @@ import { Order } from './order.entity';
import { Allocation } from '../allocations/allocations.entity';
import { mock } from 'jest-mock-extended';
import { OrderStatus } from './types';
import { FoodRequest } from '../foodRequests/request.entity';
import { Pantry } from '../pantries/pantries.entity';

const mockOrdersService = mock<OrdersService>();
const mockAllocationsService = mock<AllocationsService>();

describe('OrdersController', () => {
let controller: OrdersController;

const mockPantries: Partial<Pantry>[] = [
{ pantryId: 1, pantryName: 'Test Pantry' },
{ pantryId: 2, pantryName: 'Test Pantry 2' },
{ pantryId: 3, pantryName: 'Test Pantry 3' },
];

const mockRequests: Partial<FoodRequest>[] = [
{ requestId: 1, pantry: mockPantries[0] as Pantry },
{ requestId: 2, pantry: mockPantries[1] as Pantry },
{ requestId: 3, pantry: mockPantries[2] as Pantry },
];

const mockOrders: Partial<Order>[] = [
{ orderId: 1, status: OrderStatus.PENDING },
{ orderId: 2, status: OrderStatus.DELIVERED },
{
orderId: 1,
status: OrderStatus.PENDING,
request: mockRequests[0] as FoodRequest,
},
{
orderId: 2,
status: OrderStatus.DELIVERED,
request: mockRequests[1] as FoodRequest,
},
{
orderId: 3,
status: OrderStatus.SHIPPED,
request: mockRequests[2] as FoodRequest,
},
];

const mockAllocations: Partial<Allocation>[] = [
Expand Down Expand Up @@ -44,13 +71,13 @@ describe('OrdersController', () => {
it('should call ordersService.getAll and return orders', async () => {
const status = 'pending';
const pantryNames = ['Test Pantry', 'Test Pantry 2'];
mockOrdersService.getAll.mockResolvedValueOnce([
mockOrders[0],
] as Order[]);
mockOrdersService.getAll.mockResolvedValueOnce(
mockOrders.slice(0, 2) as Order[],
);

const result = await controller.getAllOrders(status, pantryNames);

expect(result).toEqual([mockOrders[0]] as Order[]);
expect(result).toEqual(mockOrders.slice(0, 2) as Order[]);
expect(mockOrdersService.getAll).toHaveBeenCalledWith({
status,
pantryNames,
Expand Down
8 changes: 0 additions & 8 deletions apps/backend/src/orders/order.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
OneToMany,
} from 'typeorm';
import { FoodRequest } from '../foodRequests/request.entity';
import { Pantry } from '../pantries/pantries.entity';
import { FoodManufacturer } from '../foodManufacturers/manufacturer.entity';
import { OrderStatus } from './types';
import { Allocation } from '../allocations/allocations.entity';
Expand All @@ -18,13 +17,6 @@ export class Order {
@PrimaryGeneratedColumn({ name: 'order_id' })
orderId: number;

@ManyToOne(() => Pantry, { nullable: false })
@JoinColumn({
name: 'pantry_id',
referencedColumnName: 'pantryId',
})
pantry: Pantry;

@ManyToOne(() => FoodRequest, { nullable: false })
@JoinColumn({
name: 'request_id',
Expand Down
3 changes: 2 additions & 1 deletion apps/backend/src/orders/order.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import { Order } from './order.entity';
import { OrdersService } from './order.service';
import { JwtStrategy } from '../auth/jwt.strategy';
import { AuthService } from '../auth/auth.service';
import { Pantry } from '../pantries/pantries.entity';
import { AllocationModule } from '../allocations/allocations.module';

@Module({
imports: [TypeOrmModule.forFeature([Order]), AllocationModule],
imports: [TypeOrmModule.forFeature([Order, Pantry]), AllocationModule],
controllers: [OrdersController],
providers: [OrdersService, AuthService, JwtStrategy],
exports: [OrdersService],
Expand Down
114 changes: 108 additions & 6 deletions apps/backend/src/orders/order.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ import {
ServeAllergicChildren,
} from '../pantries/types';
import { OrderStatus } from './types';
import { FoodRequest } from '../foodRequests/request.entity';

const mockOrdersRepository = mock<Repository<Order>>();
const mockPantryRepository = mock<Repository<Pantry>>();

const mockPantry: Partial<Pantry> = {
pantryId: 1,
Expand Down Expand Up @@ -54,6 +56,10 @@ describe('OrdersService', () => {
provide: getRepositoryToken(Order),
useValue: mockOrdersRepository,
},
{
provide: getRepositoryToken(Pantry),
useValue: mockPantryRepository,
},
],
}).compile();

Expand All @@ -63,6 +69,7 @@ describe('OrdersService', () => {
beforeEach(() => {
qb = {
leftJoinAndSelect: jest.fn().mockReturnThis(),
leftJoin: jest.fn().mockReturnThis(),
select: jest.fn().mockReturnThis(),
andWhere: jest.fn().mockReturnThis(),
getMany: jest.fn().mockResolvedValue([]),
Expand Down Expand Up @@ -108,17 +115,14 @@ describe('OrdersService', () => {
{
orderId: 3,
status: OrderStatus.DELIVERED,
pantry: { ...(mockPantry as Pantry), pantryName: 'Test Pantry' },
},
{
orderId: 4,
status: OrderStatus.DELIVERED,
pantry: { ...(mockPantry as Pantry), pantryName: 'Test Pantry 2' },
},
{
orderId: 5,
status: OrderStatus.DELIVERED,
pantry: { ...(mockPantry as Pantry), pantryName: 'Test Pantry 3' },
},
];

Expand Down Expand Up @@ -156,17 +160,14 @@ describe('OrdersService', () => {
{
orderId: 3,
status: OrderStatus.DELIVERED,
pantry: { ...(mockPantry as Pantry), pantryName: 'Test Pantry 1' },
},
{
orderId: 4,
status: OrderStatus.DELIVERED,
pantry: { ...(mockPantry as Pantry), pantryName: 'Test Pantry 2' },
},
{
orderId: 5,
status: OrderStatus.DELIVERED,
pantry: { ...(mockPantry as Pantry), pantryName: 'Test Pantry 2' },
},
];

Expand All @@ -189,4 +190,105 @@ describe('OrdersService', () => {
);
});
});

describe('findOrderPantry', () => {
it('should return pantry for given order', async () => {
const mockFoodRequest: Partial<FoodRequest> = {
requestId: 1,
pantryId: 1,
};

const mockOrder: Partial<Order> = {
orderId: 1,
requestId: 1,
request: mockFoodRequest as FoodRequest,
};

(mockOrdersRepository.findOne as jest.Mock).mockResolvedValue(mockOrder);
(mockPantryRepository.findOneBy as jest.Mock).mockResolvedValue(
mockPantry as Pantry,
);

const result = await service.findOrderPantry(1);

expect(result).toEqual(mockPantry);
expect(mockPantryRepository.findOneBy).toHaveBeenCalledWith({
pantryId: 1,
});
});

it('should throw NotFoundException if order not found', async () => {
(mockOrdersRepository.findOne as jest.Mock).mockResolvedValue(null);

await expect(service.findOrderPantry(999)).rejects.toThrow(
'Order 999 not found',
);
});

it('should throw NotFoundException if pantry not found', async () => {
const mockFoodRequest: Partial<FoodRequest> = {
requestId: 1,
pantryId: 999,
};

const mockOrder: Partial<Order> = {
orderId: 1,
requestId: 1,
request: mockFoodRequest as FoodRequest,
};

(mockOrdersRepository.findOne as jest.Mock).mockResolvedValue(mockOrder);
(mockPantryRepository.findOneBy as jest.Mock).mockResolvedValue(null);

await expect(service.findOrderPantry(1)).rejects.toThrow(
'Pantry 999 not found',
);
});
});

describe('getOrdersByPantry', () => {
it('should return orders for given pantry', async () => {
const mockOrders: Partial<Order>[] = [
{ orderId: 1, requestId: 1 },
{ orderId: 2, requestId: 2 },
];

(mockPantryRepository.findOneBy as jest.Mock).mockResolvedValue(
mockPantry as Pantry,
);
(mockOrdersRepository.find as jest.Mock).mockResolvedValue(
mockOrders as Order[],
);

const result = await service.getOrdersByPantry(1);

expect(result).toEqual(mockOrders);
expect(mockPantryRepository.findOneBy).toHaveBeenCalledWith({
pantryId: 1,
});
expect(mockOrdersRepository.find).toHaveBeenCalledWith({
where: { request: { pantryId: 1 } },
relations: ['request'],
});
});

it('should throw NotFoundException if pantry does not exist', async () => {
(mockPantryRepository.findOneBy as jest.Mock).mockResolvedValue(null);

await expect(service.getOrdersByPantry(999)).rejects.toThrow(
'Pantry 999 not found',
);
});

it('should return empty array if pantry has no orders', async () => {
(mockPantryRepository.findOneBy as jest.Mock).mockResolvedValue(
mockPantry as Pantry,
);
(mockOrdersRepository.find as jest.Mock).mockResolvedValue([]);

const result = await service.getOrdersByPantry(1);

expect(result).toEqual([]);
});
});
});
Loading
Loading