From 2bed4ed1184a4f70c46a80ecd5c3895318fdd6c1 Mon Sep 17 00:00:00 2001 From: Dalton Burkhart <137221425+dburkhart07@users.noreply.github.com> Date: Mon, 21 Sep 2026 01:48:48 -0400 Subject: [PATCH 1/8] Delete .github/dependabot.yml --- .github/dependabot.yml | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index ef97a9eaf..000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,14 +0,0 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file - -version: 2 -updates: - - package-ecosystem: "npm" # See documentation for possible values - directory: "/" # Location of package manifests - schedule: - interval: "weekly" - day: "monday" - time: "02:34" - timezone: "America/New_York" From 91a46b2ca123503ab434cf929e0f24ebc3e6b5da Mon Sep 17 00:00:00 2001 From: Shreeya Adhikari Date: Thu, 24 Sep 2026 15:16:55 -0400 Subject: [PATCH 2/8] created update volunteer dto --- .../src/volunteers/update-volunteer.dto.ts | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 apps/backend/src/volunteers/update-volunteer.dto.ts diff --git a/apps/backend/src/volunteers/update-volunteer.dto.ts b/apps/backend/src/volunteers/update-volunteer.dto.ts new file mode 100644 index 000000000..a1317a24a --- /dev/null +++ b/apps/backend/src/volunteers/update-volunteer.dto.ts @@ -0,0 +1,61 @@ +import { FosterCoordinator as number } from '../coordinators/coordinators.entity'; +import { Homebase } from '../types'; +import { FosterType } from './volunteers.types'; +import { IsOptional, IsString, IsNumber, IsBoolean, IsEnum } from 'class-validator'; + + +export class UpdateVolunteerDto { + +@IsOptional() +@IsNumber() +volunteerId?: number; +@IsOptional() +@IsString() +firstName?: string; +@IsOptional() +@IsString() +lastName?: string; +@IsOptional() +@IsString() +phone?: string; +@IsOptional() +@IsString() +secondaryPhone?: string; +@IsOptional() +@IsString() +email?: string; +@IsOptional() +@IsString() +address?: string; +@IsOptional() +@IsString() +city?: string; +@IsOptional() +@IsString() +zipcode?: string; +@IsOptional() +@IsEnum(Homebase) +homebase?: Homebase; +@IsOptional() +@IsString() +residentAnimals?: string; +@IsOptional() +@IsString() +notes?: string; +@IsOptional() +@IsEnum(FosterType) +fosterType?: FosterType; +@IsOptional() +@IsBoolean() +completedCanineTraining?: boolean; +@IsOptional() +@IsBoolean() +mostRecentWaiverSigned?: boolean; +@IsOptional() +@IsBoolean() +active?: boolean; +@IsOptional() +@IsNumber() +assignedCoordinatorId?: number; + +} \ No newline at end of file From dc55c8951c48f5652748a012b55eeb5c71d79a41 Mon Sep 17 00:00:00 2001 From: Shreeya Adhikari Date: Sat, 26 Sep 2026 14:02:55 -0400 Subject: [PATCH 3/8] created deny match dto and added service logic --- apps/backend/src/matches/deny-match.dto.ts | 7 +++++++ apps/backend/src/matches/matches.service.ts | 22 +++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 apps/backend/src/matches/deny-match.dto.ts diff --git a/apps/backend/src/matches/deny-match.dto.ts b/apps/backend/src/matches/deny-match.dto.ts new file mode 100644 index 000000000..b4a15ed80 --- /dev/null +++ b/apps/backend/src/matches/deny-match.dto.ts @@ -0,0 +1,7 @@ +import { IsNotEmpty, IsString } from 'class-validator'; + +export class DenyMatchDto { + @IsNotEmpty() + @IsString() + deniedReason!: string; +} \ No newline at end of file diff --git a/apps/backend/src/matches/matches.service.ts b/apps/backend/src/matches/matches.service.ts index e91ccaf65..fab8ef157 100644 --- a/apps/backend/src/matches/matches.service.ts +++ b/apps/backend/src/matches/matches.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Match } from './matches.entity'; +import { MatchStatus } from './matches.types'; @Injectable() export class MatchesService { @@ -9,4 +10,25 @@ export class MatchesService { @InjectRepository(Match) private repo: Repository, ) {} + + async approveMatch(id: number) { + const match = await this.repo.findOneBy({ matchId: id }); + if (!match) { + return null; + } + + match.status = MatchStatus.COMPLETE; + return this.repo.save(match); + } + + async denyMatch(id: number, deniedReason: string) { + const match = await this.repo.findOneBy({ matchId: id }); + if (!match) { + return null; + } + + match.status = MatchStatus.DENIED; + match.deniedReason = deniedReason; + return this.repo.save(match); + } } From 6481731ac992e7ea7740113d6cc55f6cbb218f68 Mon Sep 17 00:00:00 2001 From: Shreeya Adhikari Date: Sat, 26 Sep 2026 14:06:19 -0400 Subject: [PATCH 4/8] .. --- apps/backend/src/matches/matches.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/backend/src/matches/matches.service.ts b/apps/backend/src/matches/matches.service.ts index fab8ef157..62cd787d3 100644 --- a/apps/backend/src/matches/matches.service.ts +++ b/apps/backend/src/matches/matches.service.ts @@ -31,4 +31,5 @@ export class MatchesService { match.deniedReason = deniedReason; return this.repo.save(match); } + } From afa314751a11d2d83dec66a2c408fd9393bfc9af Mon Sep 17 00:00:00 2001 From: Shreeya Adhikari Date: Sat, 26 Sep 2026 17:15:07 -0400 Subject: [PATCH 5/8] added controller logic --- .../backend/src/matches/matches.controller.ts | 32 +++++++++- .../src/volunteers/update-volunteer.dto.ts | 61 ------------------- 2 files changed, 31 insertions(+), 62 deletions(-) delete mode 100644 apps/backend/src/volunteers/update-volunteer.dto.ts diff --git a/apps/backend/src/matches/matches.controller.ts b/apps/backend/src/matches/matches.controller.ts index 9d62ae7e9..94adc616a 100644 --- a/apps/backend/src/matches/matches.controller.ts +++ b/apps/backend/src/matches/matches.controller.ts @@ -1,7 +1,37 @@ -import { Controller } from '@nestjs/common'; +import { Controller, Patch, Param, Body, NotFoundException } from '@nestjs/common'; import { MatchesService } from './matches.service'; +import { validateId } from '../utils/validation.utils'; +import { DenyMatchDto } from './deny-match.dto'; + @Controller('matches') export class MatchesController { constructor(private matchesService: MatchesService) {} + + @Patch(':matchId/approve') + async approveMatch(@Param('matchId') matchId: string) { + const id = Number(matchId); + validateId(id, 'Match'); + + const match = await this.matchesService.approveMatch(id); + if (!match) { + throw new NotFoundException('Match not found'); + } + return match; + } + + @Patch(':matchId/deny') + async denyMatch( + @Param('matchId') matchId: string, + @Body() dto: DenyMatchDto, + ) { + const id = Number(matchId); + validateId(id, 'Match'); + + const match = await this.matchesService.denyMatch(id, dto.deniedReason); + if (!match) { + throw new NotFoundException('Match not found'); + } + return match; + } } diff --git a/apps/backend/src/volunteers/update-volunteer.dto.ts b/apps/backend/src/volunteers/update-volunteer.dto.ts deleted file mode 100644 index a1317a24a..000000000 --- a/apps/backend/src/volunteers/update-volunteer.dto.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { FosterCoordinator as number } from '../coordinators/coordinators.entity'; -import { Homebase } from '../types'; -import { FosterType } from './volunteers.types'; -import { IsOptional, IsString, IsNumber, IsBoolean, IsEnum } from 'class-validator'; - - -export class UpdateVolunteerDto { - -@IsOptional() -@IsNumber() -volunteerId?: number; -@IsOptional() -@IsString() -firstName?: string; -@IsOptional() -@IsString() -lastName?: string; -@IsOptional() -@IsString() -phone?: string; -@IsOptional() -@IsString() -secondaryPhone?: string; -@IsOptional() -@IsString() -email?: string; -@IsOptional() -@IsString() -address?: string; -@IsOptional() -@IsString() -city?: string; -@IsOptional() -@IsString() -zipcode?: string; -@IsOptional() -@IsEnum(Homebase) -homebase?: Homebase; -@IsOptional() -@IsString() -residentAnimals?: string; -@IsOptional() -@IsString() -notes?: string; -@IsOptional() -@IsEnum(FosterType) -fosterType?: FosterType; -@IsOptional() -@IsBoolean() -completedCanineTraining?: boolean; -@IsOptional() -@IsBoolean() -mostRecentWaiverSigned?: boolean; -@IsOptional() -@IsBoolean() -active?: boolean; -@IsOptional() -@IsNumber() -assignedCoordinatorId?: number; - -} \ No newline at end of file From 25107d14ebc3d69bc32b917ebb0795bd853dd0e0 Mon Sep 17 00:00:00 2001 From: Shreeya Adhikari Date: Sat, 26 Sep 2026 17:21:19 -0400 Subject: [PATCH 6/8] get/volunteers endpoint that returns all volunteers --- apps/backend/src/volunteers/volunteers.controller.ts | 7 ++++++- apps/backend/src/volunteers/volunteers.service.ts | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/backend/src/volunteers/volunteers.controller.ts b/apps/backend/src/volunteers/volunteers.controller.ts index d2c246fc8..2efd8b8fb 100644 --- a/apps/backend/src/volunteers/volunteers.controller.ts +++ b/apps/backend/src/volunteers/volunteers.controller.ts @@ -1,4 +1,4 @@ -import { Controller } from '@nestjs/common'; +import { Controller, Get } from '@nestjs/common'; import { VolunteersService } from './volunteers.service'; // @ApiTags('Volunteers') @@ -12,4 +12,9 @@ export class VolunteersController { // async getUser(@Param('userId', ParseIntPipe) userId: number): Promise { // return this.usersService.findOne(userId); // } + + @Get() + async getAllVolunteers() { + return this.volunteersService.getAllVolunteers(); + } } diff --git a/apps/backend/src/volunteers/volunteers.service.ts b/apps/backend/src/volunteers/volunteers.service.ts index 1c6f17a61..81605604b 100644 --- a/apps/backend/src/volunteers/volunteers.service.ts +++ b/apps/backend/src/volunteers/volunteers.service.ts @@ -26,4 +26,8 @@ export class VolunteersService { // return this.repo.save(user); // } + + async getAllVolunteers() { + return this.repo.find(); + } } From fc09a8ac79866c69eb1624d279de3ea1ff105e80 Mon Sep 17 00:00:00 2001 From: Shreeya Adhikari Date: Sat, 26 Sep 2026 17:28:12 -0400 Subject: [PATCH 7/8] controller +service tests --- .../src/matches/matches.controller.spec.ts | 116 +++++++++++++++++- .../src/matches/matches.service.spec.ts | 76 +++++++++++- 2 files changed, 182 insertions(+), 10 deletions(-) diff --git a/apps/backend/src/matches/matches.controller.spec.ts b/apps/backend/src/matches/matches.controller.spec.ts index 7e028c33e..7ec4eea24 100644 --- a/apps/backend/src/matches/matches.controller.spec.ts +++ b/apps/backend/src/matches/matches.controller.spec.ts @@ -1,25 +1,131 @@ import { Test, TestingModule } from '@nestjs/testing'; +import { NotFoundException, BadRequestException } from '@nestjs/common'; +import { validate } from 'class-validator'; import { MatchesController } from './matches.controller'; import { MatchesService } from './matches.service'; +import { DenyMatchDto } from './deny-match.dto'; +import { MatchStatus } from './matches.types'; describe('MatchesController', () => { let controller: MatchesController; + const mockMatch = { + matchId: 1, + status: MatchStatus.COMPLETE, + deniedReason: null, + }; + + const mockMatchesService = { + approveMatch: jest.fn(), + denyMatch: jest.fn(), + }; + beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [MatchesController], providers: [ - { - provide: MatchesService, - useValue: {}, - }, + { provide: MatchesService, useValue: mockMatchesService }, ], }).compile(); controller = module.get(MatchesController); }); + afterEach(() => { + jest.clearAllMocks(); + }); + it('should be defined', () => { expect(controller).toBeDefined(); }); -}); + + describe('approveMatch', () => { + it('should approve and return the match when found', async () => { + mockMatchesService.approveMatch.mockResolvedValue(mockMatch); + + const result = await controller.approveMatch('1'); + + expect(result).toEqual(mockMatch); + expect(mockMatchesService.approveMatch).toHaveBeenCalledWith(1); + }); + + it('should throw NotFoundException when match does not exist', async () => { + mockMatchesService.approveMatch.mockResolvedValue(null); + + await expect(controller.approveMatch('999')).rejects.toThrow( + NotFoundException, + ); + }); + + it('should throw BadRequestException when id is invalid', async () => { + await expect(controller.approveMatch('0')).rejects.toThrow( + BadRequestException, + ); + }); + }); + + describe('denyMatch', () => { + it('should deny and return the match when found', async () => { + const deniedMatch = { + ...mockMatch, + status: MatchStatus.DENIED, + deniedReason: 'Not enough space', + }; + mockMatchesService.denyMatch.mockResolvedValue(deniedMatch); + + const dto: DenyMatchDto = { deniedReason: 'Not enough space' }; + const result = await controller.denyMatch('1', dto); + + expect(result).toEqual(deniedMatch); + expect(mockMatchesService.denyMatch).toHaveBeenCalledWith( + 1, + 'Not enough space', + ); + }); + + it('should throw NotFoundException when match does not exist', async () => { + mockMatchesService.denyMatch.mockResolvedValue(null); + + const dto: DenyMatchDto = { deniedReason: 'Some reason' }; + await expect(controller.denyMatch('999', dto)).rejects.toThrow( + NotFoundException, + ); + }); + + it('should throw BadRequestException when id is invalid', async () => { + const dto: DenyMatchDto = { deniedReason: 'Some reason' }; + await expect(controller.denyMatch('0', dto)).rejects.toThrow( + BadRequestException, + ); + }); + }); + + describe('DenyMatchDto validation', () => { + it('should fail validation when deniedReason is empty', async () => { + const dto = new DenyMatchDto(); + dto.deniedReason = ''; + + const errors = await validate(dto); + + expect(errors.length).toBeGreaterThan(0); + expect(errors[0].property).toBe('deniedReason'); + }); + + it('should fail validation when deniedReason is missing', async () => { + const dto = new DenyMatchDto(); + + const errors = await validate(dto); + + expect(errors.length).toBeGreaterThan(0); + }); + + it('should pass validation when deniedReason is a non-empty string', async () => { + const dto = new DenyMatchDto(); + dto.deniedReason = 'Not enough space'; + + const errors = await validate(dto); + + expect(errors.length).toBe(0); + }); + }); +}); \ No newline at end of file diff --git a/apps/backend/src/matches/matches.service.spec.ts b/apps/backend/src/matches/matches.service.spec.ts index b2d520821..afd494449 100644 --- a/apps/backend/src/matches/matches.service.spec.ts +++ b/apps/backend/src/matches/matches.service.spec.ts @@ -2,25 +2,91 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { MatchesService } from './matches.service'; import { Match } from './matches.entity'; +import { MatchStatus } from './matches.types'; describe('MatchesService', () => { let service: MatchesService; + const mockMatch = { + matchId: 1, + status: MatchStatus.PENDING, + deniedReason: null, + }; + + const mockRepo = { + findOneBy: jest.fn(), + save: jest.fn(), + }; + beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ MatchesService, - { - provide: getRepositoryToken(Match), - useValue: {}, - }, + { provide: getRepositoryToken(Match), useValue: mockRepo }, ], }).compile(); service = module.get(MatchesService); }); + afterEach(() => { + jest.clearAllMocks(); + }); + it('should be defined', () => { expect(service).toBeDefined(); }); -}); + + describe('approveMatch', () => { + it('should approve a match and return it with updated status', async () => { + mockRepo.findOneBy.mockResolvedValue({ ...mockMatch }); + mockRepo.save.mockImplementation((m) => Promise.resolve(m)); + + const result = await service.approveMatch(1); + + if (!result) { + throw new Error('Expected a match, got null'); + } + + expect(result.status).toBe(MatchStatus.COMPLETE); + expect(mockRepo.findOneBy).toHaveBeenCalledWith({ matchId: 1 }); + expect(mockRepo.save).toHaveBeenCalled(); + }); + + it('should return null when match does not exist', async () => { + mockRepo.findOneBy.mockResolvedValue(null); + + const result = await service.approveMatch(999); + + expect(result).toBeNull(); + expect(mockRepo.save).not.toHaveBeenCalled(); + }); + }); + + describe('denyMatch', () => { + it('should deny a match and save the reason', async () => { + mockRepo.findOneBy.mockResolvedValue({ ...mockMatch }); + mockRepo.save.mockImplementation((m) => Promise.resolve(m)); + + const result = await service.denyMatch(1, 'Not enough space in home'); + + if (!result) { + throw new Error('Expected a match, got null'); + } + + expect(result.status).toBe(MatchStatus.DENIED); + expect(result.deniedReason).toBe('Not enough space in home'); + expect(mockRepo.findOneBy).toHaveBeenCalledWith({ matchId: 1 }); + expect(mockRepo.save).toHaveBeenCalled(); + }); + + it('should return null when match does not exist', async () => { + mockRepo.findOneBy.mockResolvedValue(null); + + const result = await service.denyMatch(999, 'Some reason'); + + expect(result).toBeNull(); + expect(mockRepo.save).not.toHaveBeenCalled(); + }); + }); +}); \ No newline at end of file From 10a2f40c8317b53b24a9edc4d965c3510c5278c2 Mon Sep 17 00:00:00 2001 From: Shreeya Adhikari Date: Sat, 26 Sep 2026 17:29:39 -0400 Subject: [PATCH 8/8] fixed prettier checks --- apps/backend/src/matches/deny-match.dto.ts | 2 +- .../src/matches/matches.controller.spec.ts | 6 +-- .../backend/src/matches/matches.controller.ts | 49 ++++++++++--------- .../src/matches/matches.service.spec.ts | 2 +- apps/backend/src/matches/matches.service.ts | 1 - .../src/volunteers/volunteers.controller.ts | 4 +- 6 files changed, 33 insertions(+), 31 deletions(-) diff --git a/apps/backend/src/matches/deny-match.dto.ts b/apps/backend/src/matches/deny-match.dto.ts index b4a15ed80..639d98b82 100644 --- a/apps/backend/src/matches/deny-match.dto.ts +++ b/apps/backend/src/matches/deny-match.dto.ts @@ -4,4 +4,4 @@ export class DenyMatchDto { @IsNotEmpty() @IsString() deniedReason!: string; -} \ No newline at end of file +} diff --git a/apps/backend/src/matches/matches.controller.spec.ts b/apps/backend/src/matches/matches.controller.spec.ts index 7ec4eea24..1b997f361 100644 --- a/apps/backend/src/matches/matches.controller.spec.ts +++ b/apps/backend/src/matches/matches.controller.spec.ts @@ -23,9 +23,7 @@ describe('MatchesController', () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [MatchesController], - providers: [ - { provide: MatchesService, useValue: mockMatchesService }, - ], + providers: [{ provide: MatchesService, useValue: mockMatchesService }], }).compile(); controller = module.get(MatchesController); @@ -128,4 +126,4 @@ describe('MatchesController', () => { expect(errors.length).toBe(0); }); }); -}); \ No newline at end of file +}); diff --git a/apps/backend/src/matches/matches.controller.ts b/apps/backend/src/matches/matches.controller.ts index 94adc616a..3dabdae6b 100644 --- a/apps/backend/src/matches/matches.controller.ts +++ b/apps/backend/src/matches/matches.controller.ts @@ -1,37 +1,42 @@ -import { Controller, Patch, Param, Body, NotFoundException } from '@nestjs/common'; +import { + Controller, + Patch, + Param, + Body, + NotFoundException, +} from '@nestjs/common'; import { MatchesService } from './matches.service'; import { validateId } from '../utils/validation.utils'; import { DenyMatchDto } from './deny-match.dto'; - @Controller('matches') export class MatchesController { constructor(private matchesService: MatchesService) {} @Patch(':matchId/approve') - async approveMatch(@Param('matchId') matchId: string) { - const id = Number(matchId); - validateId(id, 'Match'); + async approveMatch(@Param('matchId') matchId: string) { + const id = Number(matchId); + validateId(id, 'Match'); - const match = await this.matchesService.approveMatch(id); - if (!match) { - throw new NotFoundException('Match not found'); - } - return match; + const match = await this.matchesService.approveMatch(id); + if (!match) { + throw new NotFoundException('Match not found'); } + return match; + } - @Patch(':matchId/deny') - async denyMatch( - @Param('matchId') matchId: string, - @Body() dto: DenyMatchDto, - ) { - const id = Number(matchId); - validateId(id, 'Match'); + @Patch(':matchId/deny') + async denyMatch( + @Param('matchId') matchId: string, + @Body() dto: DenyMatchDto, + ) { + const id = Number(matchId); + validateId(id, 'Match'); - const match = await this.matchesService.denyMatch(id, dto.deniedReason); - if (!match) { - throw new NotFoundException('Match not found'); - } - return match; + const match = await this.matchesService.denyMatch(id, dto.deniedReason); + if (!match) { + throw new NotFoundException('Match not found'); } + return match; + } } diff --git a/apps/backend/src/matches/matches.service.spec.ts b/apps/backend/src/matches/matches.service.spec.ts index afd494449..1c69820df 100644 --- a/apps/backend/src/matches/matches.service.spec.ts +++ b/apps/backend/src/matches/matches.service.spec.ts @@ -89,4 +89,4 @@ describe('MatchesService', () => { expect(mockRepo.save).not.toHaveBeenCalled(); }); }); -}); \ No newline at end of file +}); diff --git a/apps/backend/src/matches/matches.service.ts b/apps/backend/src/matches/matches.service.ts index 62cd787d3..fab8ef157 100644 --- a/apps/backend/src/matches/matches.service.ts +++ b/apps/backend/src/matches/matches.service.ts @@ -31,5 +31,4 @@ export class MatchesService { match.deniedReason = deniedReason; return this.repo.save(match); } - } diff --git a/apps/backend/src/volunteers/volunteers.controller.ts b/apps/backend/src/volunteers/volunteers.controller.ts index 2efd8b8fb..8f3834356 100644 --- a/apps/backend/src/volunteers/volunteers.controller.ts +++ b/apps/backend/src/volunteers/volunteers.controller.ts @@ -14,7 +14,7 @@ export class VolunteersController { // } @Get() - async getAllVolunteers() { - return this.volunteersService.getAllVolunteers(); + async getAllVolunteers() { + return this.volunteersService.getAllVolunteers(); } }