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: 1 addition & 1 deletion .github/workflows/backend-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@ jobs:
node-version: 20
- run: yarn install --frozen-lockfile
- run: yarn list strip-ansi string-width string-length
- run: npx jest
- run: yarn test
2 changes: 2 additions & 0 deletions apps/backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { APP_GUARD } from '@nestjs/core';
import { RolesGuard } from './auth/roles.guard';
import { JwtAuthGuard } from './auth/jwt-auth.guard';
import { ScheduleModule } from '@nestjs/schedule';
import { VolunteersModule } from './volunteers/volunteers.module';

@Module({
imports: [
Expand All @@ -43,6 +44,7 @@ import { ScheduleModule } from '@nestjs/schedule';
OrdersModule,
ManufacturerModule,
AllocationModule,
VolunteersModule,
],
controllers: [AppController],
providers: [
Expand Down
5 changes: 2 additions & 3 deletions apps/backend/src/orders/order.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ describe('OrdersService', () => {
}

// Clean database at the start
await testDataSource.query(`DROP SCHEMA public CASCADE`);
await testDataSource.query(`CREATE SCHEMA public`);

const module: TestingModule = await Test.createTestingModule({
providers: [
Expand All @@ -42,7 +40,8 @@ describe('OrdersService', () => {
});

beforeEach(async () => {
// Run all migrations fresh for each test
await testDataSource.query(`DROP SCHEMA IF EXISTS public CASCADE`);
await testDataSource.query(`CREATE SCHEMA public`);
await testDataSource.runMigrations();
});

Expand Down
134 changes: 0 additions & 134 deletions apps/backend/src/users/users.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { userSchemaDto } from './dtos/userSchema.dto';

import { Test, TestingModule } from '@nestjs/testing';
import { mock } from 'jest-mock-extended';
import { Pantry } from '../pantries/pantries.entity';

const mockUserService = mock<UsersService>();

Expand All @@ -20,35 +19,6 @@ const mockUser1: Partial<User> = {
role: Role.VOLUNTEER,
};

const mockUser2: Partial<User> = {
id: 2543210,
email: 'bobsmith@example.com',
firstName: 'Bob',
lastName: 'Smith',
phone: '9876',
role: Role.VOLUNTEER,
};

const mockUser3: Partial<User> = {
id: 3,
role: Role.VOLUNTEER,
};

const mockPantries: Partial<Pantry>[] = [
{
pantryId: 1,
pantryUser: mockUser1 as User,
},
{
pantryId: 2,
pantryUser: mockUser1 as User,
},
{
pantryId: 3,
pantryUser: mockUser2 as User,
},
];

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

Expand Down Expand Up @@ -76,45 +46,6 @@ describe('UsersController', () => {
expect(controller).toBeDefined();
});

describe('GET /volunteers', () => {
it('should return all volunteers', async () => {
const users: (Omit<Partial<User>, 'pantries'> & {
pantryIds: number[];
})[] = [
{
id: 1,
role: Role.VOLUNTEER,
pantryIds: [1],
},
{
id: 2,
role: Role.VOLUNTEER,
pantryIds: [2],
},
{
id: 3,
role: Role.ADMIN,
pantryIds: [3],
},
];

const volunteers = users.slice(0, 2);

mockUserService.getVolunteersAndPantryAssignments.mockResolvedValue(
volunteers as (Omit<User, 'pantries'> & { pantryIds: number[] })[],
);

const result = await controller.getAllVolunteers();

expect(result).toEqual(volunteers);
expect(result.length).toBe(2);
expect(result.every((u) => u.role === Role.VOLUNTEER)).toBe(true);
expect(
mockUserService.getVolunteersAndPantryAssignments,
).toHaveBeenCalled();
});
});

describe('GET /:id', () => {
it('should return a user by id', async () => {
mockUserService.findOne.mockResolvedValue(mockUser1 as User);
Expand Down Expand Up @@ -200,69 +131,4 @@ describe('UsersController', () => {
);
});
});

describe('GET /volunteers', () => {
it('should return all volunteers with their pantry assignments', async () => {
const assignments: (User & { pantryIds: number[] })[] = [
{ ...(mockUser1 as User), pantryIds: [1, 2] },
{ ...(mockUser2 as User), pantryIds: [1] },
{ ...(mockUser3 as User), pantryIds: [] },
];

mockUserService.getVolunteersAndPantryAssignments.mockResolvedValue(
assignments,
);

const result = await controller.getAllVolunteers();

expect(result).toEqual(assignments);
expect(result).toHaveLength(3);
expect(result[0].id).toBe(1);
expect(result[0].pantryIds).toEqual([1, 2]);
expect(result[1].id).toBe(2543210);
expect(result[1].pantryIds).toEqual([1]);
expect(result[2].id).toBe(3);
expect(result[2].pantryIds).toEqual([]);
expect(
mockUserService.getVolunteersAndPantryAssignments,
).toHaveBeenCalled();
});
});

describe('GET /:id/pantries', () => {
it('should return pantries assigned to a user', async () => {
mockUserService.getVolunteerPantries.mockResolvedValue(
mockPantries.slice(0, 2) as Pantry[],
);

const result = await controller.getVolunteerPantries(1);

expect(result).toHaveLength(2);
expect(result).toEqual(mockPantries.slice(0, 2));
expect(mockUserService.getVolunteerPantries).toHaveBeenCalledWith(1);
});
});

describe('POST /:id/pantries', () => {
it('should assign pantries to a volunteer and return result', async () => {
const pantryIds = [1, 3];
const updatedUser = {
...mockUser3,
pantries: [mockPantries[0] as Pantry, mockPantries[2] as Pantry],
} as User;

mockUserService.assignPantriesToVolunteer.mockResolvedValue(updatedUser);

const result = await controller.assignPantries(3, pantryIds);

expect(result).toEqual(updatedUser);
expect(result.pantries).toHaveLength(2);
expect(result.pantries[0].pantryId).toBe(1);
expect(result.pantries[1].pantryId).toBe(3);
expect(mockUserService.assignPantriesToVolunteer).toHaveBeenCalledWith(
3,
pantryIds,
);
});
});
});
23 changes: 0 additions & 23 deletions apps/backend/src/users/users.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,16 @@ import { UsersService } from './users.service';
import { User } from './user.entity';
import { Role } from './types';
import { userSchemaDto } from './dtos/userSchema.dto';
import { Pantry } from '../pantries/pantries.entity';

@Controller('users')
export class UsersController {
constructor(private usersService: UsersService) {}

@Get('/volunteers')
async getAllVolunteers(): Promise<
(Omit<User, 'pantries'> & { pantryIds: number[] })[]
> {
return this.usersService.getVolunteersAndPantryAssignments();
}

@Get('/:id')
async getUser(@Param('id', ParseIntPipe) userId: number): Promise<User> {
return this.usersService.findOne(userId);
}

@Get('/:id/pantries')
async getVolunteerPantries(
@Param('id', ParseIntPipe) id: number,
): Promise<Pantry[]> {
return this.usersService.getVolunteerPantries(id);
}

@Delete('/:id')
removeUser(@Param('id', ParseIntPipe) userId: number): Promise<User> {
return this.usersService.remove(userId);
Expand All @@ -59,12 +44,4 @@ export class UsersController {
const { email, firstName, lastName, phone, role } = createUserDto;
return this.usersService.create(email, firstName, lastName, phone, role);
}

@Post('/:id/pantries')
async assignPantries(
@Param('id', ParseIntPipe) id: number,
@Body('pantryIds') pantryIds: number[],
): Promise<User> {
return this.usersService.assignPantriesToVolunteer(id, pantryIds);
}
}
13 changes: 0 additions & 13 deletions apps/backend/src/users/users.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,19 +128,6 @@ describe('UsersService', () => {
});
});

describe('findByEmail', () => {
it('should return user by email', async () => {
mockUserRepository.findOneBy.mockResolvedValue(mockUser as User);

const result = await service.findByEmail('test@example.com');

expect(result).toEqual(mockUser);
expect(mockUserRepository.findOneBy).toHaveBeenCalledWith({
email: 'test@example.com',
});
});
});

describe('update', () => {
it('should update user attributes', async () => {
const updateData = { firstName: 'Updated', role: Role.ADMIN };
Expand Down
71 changes: 1 addition & 70 deletions apps/backend/src/users/users.service.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
import {
BadRequestException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { In, Repository } from 'typeorm';
import { User } from './user.entity';
import { Role } from './types';
import { validateId } from '../utils/validation.utils';
import { Pantry } from '../pantries/pantries.entity';
import { PantriesService } from '../pantries/pantries.service';

@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private repo: Repository<User>,

private pantriesService: PantriesService,
) {}

async create(
Expand Down Expand Up @@ -49,30 +41,6 @@ export class UsersService {
return user;
}

async findVolunteer(volunteerId: number): Promise<User> {
validateId(volunteerId, 'Volunteer');

const volunteer = await this.repo.findOne({
where: { id: volunteerId },
relations: ['pantries'],
});

if (!volunteer)
throw new NotFoundException(`User ${volunteerId} not found`);
if (volunteer.role !== Role.VOLUNTEER) {
throw new BadRequestException(`User ${volunteerId} is not a volunteer`);
}
return volunteer;
}

async findByEmail(email: string): Promise<User> {
const user = await this.repo.findOneBy({ email });
if (!user) {
throw new NotFoundException(`User with email ${email} not found`);
}
return user;
}

async update(id: number, attrs: Partial<User>) {
validateId(id, 'User');

Expand Down Expand Up @@ -106,43 +74,6 @@ export class UsersService {
});
}

async getVolunteersAndPantryAssignments(): Promise<
(Omit<User, 'pantries'> & { pantryIds: number[] })[]
> {
const volunteers = await this.findUsersByRoles([Role.VOLUNTEER]);

return volunteers.map((v) => {
const { pantries, ...volunteerWithoutPantries } = v;
return {
...volunteerWithoutPantries,
pantryIds: pantries.map((p) => p.pantryId),
};
});
}

async getVolunteerPantries(volunteerId: number): Promise<Pantry[]> {
const volunteer = await this.findVolunteer(volunteerId);
return volunteer.pantries;
}

async assignPantriesToVolunteer(
volunteerId: number,
pantryIds: number[],
): Promise<User> {
pantryIds.forEach((id) => validateId(id, 'Pantry'));

const volunteer = await this.findVolunteer(volunteerId);

const pantries = await this.pantriesService.findByIds(pantryIds);
const existingPantryIds = volunteer.pantries.map((p) => p.pantryId);
const newPantries = pantries.filter(
(p) => !existingPantryIds.includes(p.pantryId),
);

volunteer.pantries = [...volunteer.pantries, ...newPantries];
return this.repo.save(volunteer);
}

async findUserByCognitoId(cognitoId: string): Promise<User> {
const user = await this.repo.findOneBy({ userCognitoSub: cognitoId });
if (!user) {
Expand Down
Loading