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
45 changes: 44 additions & 1 deletion apps/backend/src/coordinators/coordinators.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import { Test, TestingModule } from '@nestjs/testing';
import { BadRequestException } from '@nestjs/common';
import { CoordinatorsController } from './coordinators.controller';
import { CoordinatorsService } from './coordinators.service';

describe('CoordinatorsController', () => {
let controller: CoordinatorsController;
let service: { deactivate: jest.Mock; activate: jest.Mock };

beforeEach(async () => {
service = {
deactivate: jest.fn(),
activate: jest.fn(),
};

const module: TestingModule = await Test.createTestingModule({
controllers: [CoordinatorsController],
providers: [
{
provide: CoordinatorsService,
useValue: {},
useValue: service,
},
],
}).compile();
Expand All @@ -22,4 +29,40 @@ describe('CoordinatorsController', () => {
it('should be defined', () => {
expect(controller).toBeDefined();
});

describe('deactivate', () => {
it('should call service.deactivate with the parsed id', async () => {
const coordinator = { coordinatorId: 1, active: false };

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type cast

service.deactivate.mockResolvedValue(coordinator);

const result = await controller.deactivate(1);

expect(service.deactivate).toHaveBeenCalledWith(1);
expect(result).toEqual(coordinator);
});

it('should throw BadRequestException for an invalid id', async () => {
await expect(controller.deactivate(0)).rejects.toThrow(
BadRequestException,
);
expect(service.deactivate).not.toHaveBeenCalled();
});
});

describe('activate', () => {
it('should call service.activate with the parsed id', async () => {
const coordinator = { coordinatorId: 1, active: true };

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type cast

service.activate.mockResolvedValue(coordinator);

const result = await controller.activate(1);

expect(service.activate).toHaveBeenCalledWith(1);
expect(result).toEqual(coordinator);
});

it('should throw BadRequestException for an invalid id', async () => {
await expect(controller.activate(0)).rejects.toThrow(BadRequestException);
expect(service.activate).not.toHaveBeenCalled();
});
});
});
22 changes: 21 additions & 1 deletion apps/backend/src/coordinators/coordinators.controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,27 @@
import { Controller } from '@nestjs/common';
import { Controller, Param, ParseIntPipe, Patch } from '@nestjs/common';
import { CoordinatorsService } from './coordinators.service';
import { validateId } from '../utils/validation.utils';
import { FosterCoordinator } from './coordinators.entity';

@Controller('coordinators')
export class CoordinatorsController {
constructor(private coordinatorsService: CoordinatorsService) {}

@Patch('/:id/deactivate')
async deactivate(
@Param('id', ParseIntPipe) id: number,
): Promise<FosterCoordinator> {
validateId(id, 'Coordinator');

return this.coordinatorsService.deactivate(id);
}

@Patch('/:id/activate')
async activate(
@Param('id', ParseIntPipe) id: number,
): Promise<FosterCoordinator> {
validateId(id, 'Coordinator');

return this.coordinatorsService.activate(id);
}
}
81 changes: 80 additions & 1 deletion apps/backend/src/coordinators/coordinators.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { NotFoundException } from '@nestjs/common';
import { CoordinatorsService } from './coordinators.service';
import { FosterCoordinator } from './coordinators.entity';

describe('CoordinatorsService', () => {
let service: CoordinatorsService;
let repo: { findOne: jest.Mock; save: jest.Mock; delete: jest.Mock };

beforeEach(async () => {
repo = {
findOne: jest.fn(),
save: jest.fn(),
delete: jest.fn(),
};

const module: TestingModule = await Test.createTestingModule({
providers: [
CoordinatorsService,
{
provide: getRepositoryToken(FosterCoordinator),
useValue: {},
useValue: repo,
},
],
}).compile();
Expand All @@ -23,4 +31,75 @@ describe('CoordinatorsService', () => {
it('should be defined', () => {
expect(service).toBeDefined();
});

describe('deactivate', () => {
it('should set active to false and save the coordinator', async () => {
const coordinator = {
coordinatorId: 1,
active: true,
} as FosterCoordinator;
repo.findOne.mockResolvedValue(coordinator);
repo.save.mockResolvedValue({ ...coordinator, active: false });

const result = await service.deactivate(1);

expect(repo.findOne).toHaveBeenCalledWith({
where: { coordinatorId: 1 },
});
expect(repo.save).toHaveBeenCalledWith({
...coordinator,
active: false,
});
expect(result.active).toBe(false);
});

it('should throw NotFoundException if coordinator does not exist', async () => {
repo.findOne.mockResolvedValue(null);

await expect(service.deactivate(999)).rejects.toThrow(
new NotFoundException('Coordinator with ID 999 not found'),
);
expect(repo.save).not.toHaveBeenCalled();
});
it('should not delete the coordinator record', async () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same thing here as the volunteer service

const coordinator = {
coordinatorId: 1,
active: true,
} as FosterCoordinator;
repo.findOne.mockResolvedValue(coordinator);
repo.save.mockResolvedValue({ ...coordinator, active: false });

await service.deactivate(1);

expect(repo.delete).not.toHaveBeenCalled();
});
});

describe('activate', () => {
it('should set active to true and save the coordinator', async () => {
const coordinator = {
coordinatorId: 1,
active: false,
} as FosterCoordinator;
repo.findOne.mockResolvedValue(coordinator);
repo.save.mockResolvedValue({ ...coordinator, active: true });

const result = await service.activate(1);

expect(repo.findOne).toHaveBeenCalledWith({
where: { coordinatorId: 1 },
});
expect(repo.save).toHaveBeenCalledWith({ ...coordinator, active: true });
expect(result.active).toBe(true);
});

it('should throw NotFoundException if coordinator does not exist', async () => {
repo.findOne.mockResolvedValue(null);

await expect(service.activate(999)).rejects.toThrow(
new NotFoundException('Coordinator with ID 999 not found'),
);
expect(repo.save).not.toHaveBeenCalled();
});
});
});
30 changes: 29 additions & 1 deletion apps/backend/src/coordinators/coordinators.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { FosterCoordinator } from './coordinators.entity';
Expand All @@ -9,4 +9,32 @@ export class CoordinatorsService {
@InjectRepository(FosterCoordinator)
private repo: Repository<FosterCoordinator>,
) {}

async deactivate(id: number): Promise<FosterCoordinator> {
const coordinator = await this.repo.findOne({
where: { coordinatorId: id },
});

if (!coordinator) {
throw new NotFoundException(`Coordinator with ID ${id} not found`);
}

coordinator.active = false;

return this.repo.save(coordinator);
}

async activate(id: number): Promise<FosterCoordinator> {
const coordinator = await this.repo.findOne({
where: { coordinatorId: id },
});

if (!coordinator) {
throw new NotFoundException(`Coordinator with ID ${id} not found`);
}

coordinator.active = true;

return this.repo.save(coordinator);
}
}
45 changes: 44 additions & 1 deletion apps/backend/src/volunteers/volunteers.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import { Test, TestingModule } from '@nestjs/testing';
import { BadRequestException } from '@nestjs/common';
import { VolunteersController } from './volunteers.controller';
import { VolunteersService } from './volunteers.service';

describe('VolunteersController', () => {
let controller: VolunteersController;
let service: { deactivate: jest.Mock; activate: jest.Mock };

beforeEach(async () => {
service = {
deactivate: jest.fn(),
activate: jest.fn(),
};

const module: TestingModule = await Test.createTestingModule({
controllers: [VolunteersController],
providers: [
{
provide: VolunteersService,
useValue: {},
useValue: service,
},
],
}).compile();
Expand All @@ -22,4 +29,40 @@ describe('VolunteersController', () => {
it('should be defined', () => {
expect(controller).toBeDefined();
});

describe('deactivate', () => {
it('should call service.deactivate with the parsed id', async () => {
const volunteer = { volunteerId: 1, active: false };

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we type cast this to a FosterVolunteer

service.deactivate.mockResolvedValue(volunteer);

const result = await controller.deactivate(1);

expect(service.deactivate).toHaveBeenCalledWith(1);
expect(result).toEqual(volunteer);
});

it('should throw BadRequestException for an invalid id', async () => {
await expect(controller.deactivate(0)).rejects.toThrow(
BadRequestException,
);
expect(service.deactivate).not.toHaveBeenCalled();
});
});

describe('activate', () => {
it('should call service.activate with the parsed id', async () => {
const volunteer = { volunteerId: 1, active: true };

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

service.activate.mockResolvedValue(volunteer);

const result = await controller.activate(1);

expect(service.activate).toHaveBeenCalledWith(1);
expect(result).toEqual(volunteer);
});

it('should throw BadRequestException for an invalid id', async () => {
await expect(controller.activate(0)).rejects.toThrow(BadRequestException);
expect(service.activate).not.toHaveBeenCalled();
});
});
});
22 changes: 21 additions & 1 deletion apps/backend/src/volunteers/volunteers.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Controller } from '@nestjs/common';
import { Controller, Param, ParseIntPipe, Patch } from '@nestjs/common';
import { VolunteersService } from './volunteers.service';
import { validateId } from '../utils/validation.utils';
import { FosterVolunteer } from './volunteers.entity';

// @ApiTags('Volunteers')
// @ApiBearerAuth()
Expand All @@ -12,4 +14,22 @@ export class VolunteersController {
// async getUser(@Param('userId', ParseIntPipe) userId: number): Promise<User> {
// return this.usersService.findOne(userId);
// }

@Patch('/:id/deactivate')

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should've specified this in the ticket, but we are going to be documenting all of our endpoints so that they can be easily read within Swagger without having to go into our repo. To do this, we are gonna add Tags from NestJs. For all your endpoints, can you add an @apioperation, @ApiParam, and just one @apiresponse for the happy path, including the status code, description, and type it returns? We will do this for all API endpoints moving forward.

async deactivate(
@Param('id', ParseIntPipe) id: number,
): Promise<FosterVolunteer> {
validateId(id, 'Volunteer');

return this.volunteersService.deactivate(id);
}

@Patch('/:id/activate')
async activate(
@Param('id', ParseIntPipe) id: number,
): Promise<FosterVolunteer> {
validateId(id, 'Volunteer');

return this.volunteersService.activate(id);
}
}
Loading
Loading