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
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import { DonationService } from '../donations/donations.service';
import { PantriesService } from '../pantries/pantries.service';
import { Pantry } from '../pantries/pantries.entity';
import { Allocation } from '../allocations/allocations.entity';
import { AllocationsService } from '../allocations/allocations.service';

jest.setTimeout(60000);

Expand Down
23 changes: 0 additions & 23 deletions apps/backend/src/foodRequests/request.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ describe('RequestsController', () => {

beforeEach(async () => {
mockRequestsService.findOne.mockReset();
mockRequestsService.find.mockReset();
mockRequestsService.create.mockReset();
mockRequestsService.getOrderDetails.mockReset();
mockRequestsService.update.mockReset();
Expand Down Expand Up @@ -94,28 +93,6 @@ describe('RequestsController', () => {
});
});

describe('GET /:pantryId/all', () => {
it('should call requestsService.find and return all food requests for a specific pantry', async () => {
const foodRequests: Partial<FoodRequest>[] = [
foodRequest1,
{
requestId: 2,
pantryId: 1,
},
];
const pantryId = 1;

mockRequestsService.find.mockResolvedValueOnce(
foodRequests as FoodRequest[],
);

const result = await controller.getAllPantryRequests(pantryId);

expect(result).toEqual(foodRequests);
expect(mockRequestsService.find).toHaveBeenCalledWith(pantryId);
});
});

describe('GET /:requestId/order-details', () => {
it('should call requestsService.getOrderDetails and return all associated orders and their details', async () => {
const mockOrderDetails: OrderDetailsDto[] = [
Expand Down
8 changes: 0 additions & 8 deletions apps/backend/src/foodRequests/request.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,6 @@ export class RequestsController {
return this.requestsService.findOne(requestId);
}

@Roles(Role.PANTRY, Role.ADMIN)
@Get('/:pantryId/all')
async getAllPantryRequests(
@Param('pantryId', ParseIntPipe) pantryId: number,
): Promise<FoodRequestSummaryDto[]> {
return this.requestsService.find(pantryId);
}

@Roles(Role.VOLUNTEER, Role.PANTRY, Role.ADMIN)
@Get('/:requestId/order-details')
async getAllOrderDetailsFromRequest(
Expand Down
18 changes: 10 additions & 8 deletions apps/backend/src/foodRequests/request.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,11 @@ describe('RequestsService', () => {
FoodType.REFRIGERATED_MEALS,
]);

if (!pantry) throw new Error('Missing pantry test object');
const { subject, bodyHTML } = emailTemplates.pantrySubmitsFoodRequest({
pantryName: pantry!.pantryName,
pantryName: pantry.pantryName,
});
const volunteerEmails = (pantry!.volunteers ?? []).map((v) => v.email);
const volunteerEmails = (pantry.volunteers ?? []).map((v) => v.email);

expect(mockEmailsService.sendEmails).toHaveBeenCalledTimes(1);
expect(mockEmailsService.sendEmails).toHaveBeenCalledWith(
Expand All @@ -275,10 +276,11 @@ describe('RequestsService', () => {
FoodType.REFRIGERATED_MEALS,
]);

if (!pantry) throw new Error('Missing pantry test object');
const { subject, bodyHTML } = emailTemplates.pantrySubmitsFoodRequest({
pantryName: pantry!.pantryName,
pantryName: pantry.pantryName,
});
const volunteerEmails = (pantry!.volunteers ?? []).map((v) => v.email);
const volunteerEmails = (pantry.volunteers ?? []).map((v) => v.email);

expect(volunteerEmails).toEqual([]);
expect(mockEmailsService.sendEmails).toHaveBeenCalledTimes(1);
Expand All @@ -303,7 +305,7 @@ describe('RequestsService', () => {
),
);

const requests = await service.find(pantryId);
const requests = await service.findAllForPantry(pantryId);
expect(requests.length).toBe(3);
});

Expand All @@ -319,10 +321,10 @@ describe('RequestsService', () => {
});
});

describe('find', () => {
describe('findAllForPantry', () => {
it('should return all food requests for a specific pantry with pantry details', async () => {
const pantryId = 1;
const result = await service.find(pantryId);
const result = await service.findAllForPantry(pantryId);

expect(result).toBeDefined();
expect(result).toHaveLength(2);
Expand All @@ -332,7 +334,7 @@ describe('RequestsService', () => {

it('should return empty array for pantry with no requests', async () => {
const pantryId = 5;
const result = await service.find(pantryId);
const result = await service.findAllForPantry(pantryId);

expect(result).toBeDefined();
expect(result).toEqual([]);
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/src/foodRequests/request.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ export class RequestsService {
return foodRequest;
}

async find(pantryId: number): Promise<FoodRequestSummaryDto[]> {
async findAllForPantry(pantryId: number): Promise<FoodRequestSummaryDto[]> {
validateId(pantryId, 'Pantry');

return await this.repo.find({
Expand Down
54 changes: 51 additions & 3 deletions apps/backend/src/orders/order.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,45 @@ describe('OrdersService', () => {
});
});

describe('getRecentOrdersByAssignee', () => {
it('returns empty array when volunteer has no assigned orders', async () => {
// assign all seed orders away from volunteer 6
await testDataSource.query(
`UPDATE orders SET assignee_id = (SELECT user_id FROM users WHERE role = 'volunteer' AND user_id != 6 LIMIT 1)`,
);

const result = await service.getRecentOrdersByAssignee(6);
expect(result).toEqual([]);
});

it('returns at most 2 orders even when volunteer has more', async () => {
// assign all seed orders to volunteer 6
await testDataSource.query(`UPDATE orders SET assignee_id = 6`);

const result = await service.getRecentOrdersByAssignee(6);
expect(result).toHaveLength(2);
});

it('returns correct shape of orders', async () => {
await testDataSource.query(`UPDATE orders SET assignee_id = 6`);

const result = await service.getRecentOrdersByAssignee(6);

expect(result[0].createdAt >= result[1].createdAt).toBe(true);
result.forEach((order) => {
expect(order.pantryName).toBeDefined();
expect(order.assignee.id).toBe(6);
expect(order.assignee.firstName).toBe('James');
expect(order.assignee.lastName).toBe('Thomas');
expect(order.orderId).toBeDefined();
expect(order.status).toBeDefined();
expect(order.createdAt).toBeDefined();
expect(order.shippedAt).toBeDefined();
expect(order.deliveredAt).toBeDefined();
});
});
});

describe('findOrderDetails', () => {
it('returns mapped OrderDetailsDto including allocations and manufacturer', async () => {
const orderId = 1;
Expand Down Expand Up @@ -371,6 +410,8 @@ describe('OrdersService', () => {
expect(orders.length).toBe(2);
expect(orders.every((order) => order.request)).toBeDefined();
expect(orders.every((order) => order.request.pantryId === 1)).toBe(true);
expect(orders.every((order) => order.request.pantry)).toBeDefined();
expect(orders.every((order) => order.assignee)).toBeDefined();
});

it('returns empty list for pantry with no orderes', async () => {
Expand Down Expand Up @@ -810,13 +851,20 @@ describe('OrdersService', () => {
where: { itemId: 9 },
});

expect(updatedDonationItem1!.reservedQuantity).toBe(
if (
!updatedDonationItem1 ||
!updatedDonationItem2 ||
!updatedDonationItem3
) {
throw new Error('Missing donation item test object');
}
expect(updatedDonationItem1.reservedQuantity).toBe(
donationItem1.reservedQuantity + 10,
);
expect(updatedDonationItem2!.reservedQuantity).toBe(
expect(updatedDonationItem2.reservedQuantity).toBe(
donationItem2.reservedQuantity + 3,
);
expect(updatedDonationItem3!.reservedQuantity).toBe(
expect(updatedDonationItem3.reservedQuantity).toBe(
donationItem3.reservedQuantity + 5,
);

Expand Down
41 changes: 41 additions & 0 deletions apps/backend/src/orders/order.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,44 @@ export class OrdersService {
});
}

async getRecentOrdersByAssignee(
volunteerId: number,
): Promise<VolunteerOrder[]> {
validateId(volunteerId, 'Volunteer');

const orders = await this.repo
.createQueryBuilder('order')
.leftJoinAndSelect('order.request', 'request')
.leftJoinAndSelect('request.pantry', 'pantry')
.leftJoinAndSelect('order.assignee', 'assignee')
.select([
'order.orderId',
'order.status',
'order.createdAt',
'order.shippedAt',
'order.deliveredAt',
'request.pantryId',
'pantry.pantryName',
'assignee.id',
'assignee.firstName',
'assignee.lastName',
])
.where('order.assigneeId = :volunteerId', { volunteerId })
.orderBy('order.createdAt', 'DESC')
.take(2)
.getMany();

return orders.map((o) => ({
orderId: o.orderId,
status: o.status,
createdAt: o.createdAt,
shippedAt: o.shippedAt,
deliveredAt: o.deliveredAt,
pantryName: o.request.pantry.pantryName,
assignee: o.assignee,
}));
}

async getCurrentOrders() {
return this.repo.find({
where: { status: In([OrderStatus.PENDING, OrderStatus.SHIPPED]) },
Expand Down Expand Up @@ -428,8 +466,11 @@ export class OrdersService {
const qb = this.repo
.createQueryBuilder('order')
.leftJoinAndSelect('order.request', 'request')
.leftJoin('request.pantry', 'pantry')
.addSelect('pantry.pantryName')
.leftJoinAndSelect('order.allocations', 'allocations')
.leftJoinAndSelect('allocations.item', 'item')
.leftJoinAndSelect('order.assignee', 'assignee')
.where('request.pantryId = :pantryId', { pantryId });

if (years && years.length > 0) {
Expand Down
42 changes: 42 additions & 0 deletions apps/backend/src/pantries/pantries.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@ import { ApplicationStatus } from '../shared/types';
import { User } from '../users/users.entity';
import { AuthenticatedRequest } from '../auth/authenticated-request';
import { UpdatePantryApplicationDto } from './dtos/update-pantry-application.dto';
import { RequestsService } from '../foodRequests/request.service';
import { FoodRequest } from '../foodRequests/request.entity';

const mockPantriesService = mock<PantriesService>();
const mockOrdersService = mock<OrdersService>();
const mockEmailsService = mock<EmailsService>();
const mockRequestsService = mock<RequestsService>();

describe('PantriesController', () => {
let controller: PantriesController;
Expand Down Expand Up @@ -80,6 +83,16 @@ describe('PantriesController', () => {
newsletterSubscription: true,
} as PantryApplicationDto;

// Mock Food Request
const foodRequest1: Partial<FoodRequest> = {
requestId: 1,
pantryId: 1,
pantry: {
pantryId: 1,
pantryName: 'Test Pantry 1',
} as Pantry,
};

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [PantriesController],
Expand All @@ -96,6 +109,10 @@ describe('PantriesController', () => {
provide: EmailsService,
useValue: mockEmailsService,
},
{
provide: RequestsService,
useValue: mockRequestsService,
},
],
}).compile();

Expand Down Expand Up @@ -427,6 +444,7 @@ describe('PantriesController', () => {
);
});
});

describe('getCurrentUserPantryId', () => {
it('returns pantryId for authenticated user', async () => {
const req = { user: { id: 1 } };
Expand Down Expand Up @@ -525,4 +543,28 @@ describe('PantriesController', () => {
expect(mockPantriesService.getTotalStats).toHaveBeenCalledWith(years);
});
});

describe('getFoodRequests', () => {
it('should call requestsService.find and return all food requests for a specific pantry', async () => {
const foodRequests: Partial<FoodRequest>[] = [
foodRequest1,
{
requestId: 2,
pantryId: 1,
},
];
const pantryId = 1;

mockRequestsService.findAllForPantry.mockResolvedValueOnce(
foodRequests as FoodRequest[],
);

const result = await controller.getFoodRequests(pantryId);

expect(result).toEqual(foodRequests);
expect(mockRequestsService.findAllForPantry).toHaveBeenCalledWith(
pantryId,
);
});
});
});
12 changes: 12 additions & 0 deletions apps/backend/src/pantries/pantries.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,16 @@ import { Public } from '../auth/public.decorator';
import { AuthenticatedRequest } from '../auth/authenticated-request';
import { UpdatePantryApplicationDto } from './dtos/update-pantry-application.dto';
import { UpdatePantryVolunteersDto } from './dtos/update-pantry-volunteers-dto';
import { FoodRequest } from '../foodRequests/request.entity';
import { RequestsService } from '../foodRequests/request.service';
import { FoodRequestSummaryDto } from '../foodRequests/dtos/food-request-summary.dto';

@Controller('pantries')
export class PantriesController {
constructor(
private pantriesService: PantriesService,
private ordersService: OrdersService,
private requestsService: RequestsService,
) {}

@Roles(Role.ADMIN)
Expand Down Expand Up @@ -123,6 +127,14 @@ export class PantriesController {
return this.ordersService.getOrdersByPantry(pantryId);
}

@Roles(Role.PANTRY, Role.ADMIN)
@Get('/:pantryId/requests')
async getFoodRequests(
@Param('pantryId', ParseIntPipe) pantryId: number,
): Promise<FoodRequestSummaryDto[]> {
return this.requestsService.findAllForPantry(pantryId);
}

@ApiBody({
description: 'Details for submitting a pantry application',
schema: {
Expand Down
2 changes: 2 additions & 0 deletions apps/backend/src/pantries/pantries.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { EmailsModule } from '../emails/email.module';
import { User } from '../users/users.entity';
import { UsersModule } from '../users/users.module';
import { Order } from '../orders/order.entity';
import { RequestsModule } from '../foodRequests/request.module';

@Module({
imports: [
Expand All @@ -17,6 +18,7 @@ import { Order } from '../orders/order.entity';
forwardRef(() => UsersModule),
EmailsModule,
forwardRef(() => AuthModule),
forwardRef(() => RequestsModule),
],
controllers: [PantriesController],
providers: [PantriesService],
Expand Down
Loading
Loading