From 3cd4acfefd202e022aeae291d136957de36d8fc3 Mon Sep 17 00:00:00 2001 From: adityapat24 Date: Sun, 16 Nov 2025 23:45:28 -0500 Subject: [PATCH 1/5] changes and tests --- .../src/grant/__test__/grant.service.spec.ts | 56 +++++------ backend/src/grant/grant.controller.ts | 15 +-- backend/src/grant/grant.service.ts | 94 ++++++++++++------- 3 files changed, 88 insertions(+), 77 deletions(-) diff --git a/backend/src/grant/__test__/grant.service.spec.ts b/backend/src/grant/__test__/grant.service.spec.ts index 30fe3da9..079580a2 100644 --- a/backend/src/grant/__test__/grant.service.spec.ts +++ b/backend/src/grant/__test__/grant.service.spec.ts @@ -167,54 +167,44 @@ describe("GrantService", () => { }); }); - describe("unarchiveGrants()", () => { - it("should unarchive multiple grants and return their ids", async () => { + describe("makeGrantsInactive()", () => { + it("should inactivate multiple grants and return the updated grant objects", async () => { + // First two update() calls respond with DynamoDB Attributes mockPromise - .mockResolvedValueOnce({ Attributes: { isArchived: false } }) - .mockResolvedValueOnce({ Attributes: { isArchived: false } }); - - const data = await grantService.unarchiveGrants([1, 2]); - - expect(data).toEqual([1, 2]); + .mockResolvedValueOnce({ Attributes: { status: Status.Inactive } }) + .mockResolvedValueOnce({ Attributes: { status: Status.Inactive } }); + + // Next two calls are from getGrantById() + mockPromise + .mockResolvedValueOnce({ Item: mockGrants[0] }) + .mockResolvedValueOnce({ Item: mockGrants[1] }); + + const data = await grantService.makeGrantsInactive([1, 2]); + + expect(data).toEqual([mockGrants[0], mockGrants[1]]); expect(mockUpdate).toHaveBeenCalledTimes(2); - + const firstCallArgs = mockUpdate.mock.calls[0][0]; const secondCallArgs = mockUpdate.mock.calls[1][0]; - + expect(firstCallArgs).toMatchObject({ TableName: "Grants", Key: { grantId: 1 }, - UpdateExpression: "set isArchived = :archived", - ExpressionAttributeValues: { ":archived": false }, + UpdateExpression: "SET #status = :inactiveStatus", + ExpressionAttributeNames: { "#status": "status" }, + ExpressionAttributeValues: { ":inactiveStatus": Status.Inactive }, ReturnValues: "UPDATED_NEW", }); + expect(secondCallArgs).toMatchObject({ TableName: "Grants", Key: { grantId: 2 }, - UpdateExpression: "set isArchived = :archived", - ExpressionAttributeValues: { ":archived": false }, + UpdateExpression: "SET #status = :inactiveStatus", + ExpressionAttributeNames: { "#status": "status" }, + ExpressionAttributeValues: { ":inactiveStatus": Status.Inactive }, ReturnValues: "UPDATED_NEW", }); }); - - it("should skip over grants that are already ", async () => { - mockPromise - .mockResolvedValueOnce({ Attributes: { isArchived: true } }) - .mockResolvedValueOnce({ Attributes: { isArchived: false } }); - - const data = await grantService.unarchiveGrants([1, 2]); - - expect(data).toEqual([2]); - expect(mockUpdate).toHaveBeenCalledTimes(2); - }); - - it("should throw an error if any update call fails", async () => { - mockPromise.mockRejectedValueOnce(new Error("DB Error")); - - await expect(grantService.unarchiveGrants([90])).rejects.toThrow( - "Failed to update Grant 90 status." - ); - }); }); describe("updateGrant()", () => { diff --git a/backend/src/grant/grant.controller.ts b/backend/src/grant/grant.controller.ts index 49352be6..df70c1ba 100644 --- a/backend/src/grant/grant.controller.ts +++ b/backend/src/grant/grant.controller.ts @@ -16,18 +16,11 @@ export class GrantController { return await this.grantService.getGrantById(parseInt(GrantId, 10)); } - @Put('archive') - async archive( + @Put('inactivate') + async inactivate( @Body('grantIds') grantIds: number[] - ): Promise { - return await this.grantService.unarchiveGrants(grantIds) - } - - @Put('unarchive') - async unarchive( - @Body('grantIds') grantIds: number[] - ): Promise { - return await this.grantService.unarchiveGrants(grantIds) + ): Promise { + return await this.grantService.makeGrantsInactive(grantIds) } @Post('new-grant') diff --git a/backend/src/grant/grant.service.ts b/backend/src/grant/grant.service.ts index 7ccc4492..14260f8d 100644 --- a/backend/src/grant/grant.service.ts +++ b/backend/src/grant/grant.service.ts @@ -4,6 +4,7 @@ import { Grant } from '../../../middle-layer/types/Grant'; import { NotificationService } from '.././notifications/notifcation.service'; import { Notification } from '../../../middle-layer/types/Notification'; import { TDateISO } from '../utils/date'; +import { Status } from '../../../middle-layer/types/Status'; @Injectable() export class GrantService { private readonly logger = new Logger(GrantService.name); @@ -19,9 +20,29 @@ export class GrantService { }; try { - const data = await this.dynamoDb.scan(params).promise(); - - return data.Items as Grant[] || []; + const data = await this.dynamoDb.scan(params).promise(); + const grants = (data.Items as Grant[]) || []; + const inactiveGrantIds: number[] = []; + const now = new Date(); + + for (const grant of grants) { + if (grant.status === "Active") { + const startDate = new Date(grant.grant_start_date); + + // add timeline years to start date + const endDate = new Date(startDate); + endDate.setFullYear( + endDate.getFullYear() + grant.timeline + ); + + if (now >= endDate) { + inactiveGrantIds.push(grant.grantId); + } + } + } + const updatedGrants = this.makeGrantsInactive(inactiveGrantIds); + grants.push(...await updatedGrants); + return grants; } catch (error) { console.log(error) throw new Error('Could not retrieve grants.'); @@ -54,38 +75,45 @@ export class GrantService { } } - // Method to unarchive grants takes in array - async unarchiveGrants(grantIds :number[]) : Promise { - let successfulUpdates: number[] = []; - for (const grantId of grantIds) { - const params = { - TableName: process.env.DYNAMODB_GRANT_TABLE_NAME || 'TABLE_FAILURE', - Key: { - grantId: grantId, - }, - UpdateExpression: "set isArchived = :archived", - ExpressionAttributeValues: { ":archived": false }, - ReturnValues: "UPDATED_NEW", - }; + // Method to make grants inactive +async makeGrantsInactive(grantIds: number[]): Promise { + const updatedGrants: Grant[] = []; - try{ - const res = await this.dynamoDb.update(params).promise(); - console.log(res) + for (const grantId of grantIds) { + const params = { + TableName: process.env.DYNAMODB_GRANT_TABLE_NAME || "TABLE_FAILURE", + Key: { grantId }, + UpdateExpression: "SET #status = :inactiveStatus", + ExpressionAttributeNames: { + "#status": "status", + }, + ExpressionAttributeValues: { + ":inactiveStatus": Status.Inactive, + }, + ReturnValues: "UPDATED_NEW", + }; + + try { + const res = await this.dynamoDb.update(params).promise(); + + if (res.Attributes?.status === Status.Inactive) { + console.log(`Grant ${grantId} successfully marked as inactive.`); + + const grant = await this.getGrantById(grantId); + + updatedGrants.push(grant); + } else { + console.log(`Grant ${grantId} update failed or no change in status.`); + } + } catch (err) { + console.log(err); + throw new Error(`Failed to update Grant ${grantId} status.`); + } + } + + return updatedGrants; +} - if (res.Attributes && res.Attributes.isArchived === false) { - console.log(`Grant ${grantId} successfully un-archived.`); - successfulUpdates.push(grantId); - } else { - console.log(`Grant ${grantId} update failed or no change in status.`); - } - } - catch(err){ - console.log(err); - throw new Error(`Failed to update Grant ${grantId} status.`); - } - }; - return successfulUpdates; - } /** * Will push or overwrite new grant data to database From e93b510b1fe27800be50eb58cc67222c1010985e Mon Sep 17 00:00:00 2001 From: adityapat24 Date: Tue, 18 Nov 2025 18:07:46 -0500 Subject: [PATCH 2/5] comment out stuff --- backend/src/grant/__test__/grant.service.spec.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/backend/src/grant/__test__/grant.service.spec.ts b/backend/src/grant/__test__/grant.service.spec.ts index 079580a2..28c0c6d3 100644 --- a/backend/src/grant/__test__/grant.service.spec.ts +++ b/backend/src/grant/__test__/grant.service.spec.ts @@ -58,6 +58,7 @@ const mockGet = vi.fn().mockReturnThis(); const mockDelete = vi.fn().mockReturnThis(); const mockUpdate = vi.fn().mockReturnThis(); const mockPut = vi.fn().mockReturnThis(); +// const mockGetGrantById = vi.fn(); const mockDocumentClient = { scan: mockScan, @@ -99,6 +100,8 @@ describe("GrantService", () => { updateNotification: vi.fn() } }); + + controller = module.get(GrantController); grantService = module.get(GrantService); @@ -175,10 +178,13 @@ describe("GrantService", () => { .mockResolvedValueOnce({ Attributes: { status: Status.Inactive } }); // Next two calls are from getGrantById() - mockPromise - .mockResolvedValueOnce({ Item: mockGrants[0] }) - .mockResolvedValueOnce({ Item: mockGrants[1] }); + // mockPromise + // .mockResolvedValueOnce({ Item: mockGrants[0] }) + // .mockResolvedValueOnce({ Item: mockGrants[1] }); + // mockGetGrantById + // .mockResolvedValueOnce({ grantId: 1, status: Status.Inactive }) + // .mockResolvedValueOnce({ grantId: 2, status: Status.Inactive }); const data = await grantService.makeGrantsInactive([1, 2]); expect(data).toEqual([mockGrants[0], mockGrants[1]]); From 72881e11548b6c2329db8b25c9b4a44eb9b172e1 Mon Sep 17 00:00:00 2001 From: adityapat24 Date: Mon, 24 Nov 2025 14:15:04 -0500 Subject: [PATCH 3/5] tests --- .../src/grant/__test__/grant.service.spec.ts | 86 +++++++++++++++++-- backend/src/grant/grant.controller.ts | 10 +-- backend/src/grant/grant.module.ts | 3 +- backend/src/grant/grant.service.ts | 12 +-- 4 files changed, 91 insertions(+), 20 deletions(-) diff --git a/backend/src/grant/__test__/grant.service.spec.ts b/backend/src/grant/__test__/grant.service.spec.ts index 28c0c6d3..e9fa948d 100644 --- a/backend/src/grant/__test__/grant.service.spec.ts +++ b/backend/src/grant/__test__/grant.service.spec.ts @@ -49,6 +49,40 @@ const mockGrants: Grant[] = [ attachments: [], isRestricted: true }, + { + grantId: 3, + organization: "Test Organization", + does_bcan_qualify: true, + status: Status.Active, + amount: 1000, + grant_start_date: "2024-01-01", + application_deadline: "2025-01-01", + report_deadlines: ["2025-01-01"], + description: "Test Description", + timeline: 1, + estimated_completion_time: 100, + grantmaker_poc: { POC_name: "name", POC_email: "test@test.com" }, + bcan_poc: { POC_name: "name", POC_email: ""}, + attachments: [], + isRestricted: false + }, + { + grantId: 4, + organization: "Test Organization 2", + does_bcan_qualify: false, + status: Status.Active, + amount: 1000, + grant_start_date: "2025-02-15", + application_deadline: "2025-02-01", + report_deadlines: ["2025-03-01", "2025-04-01"], + description: "Test Description 2", + timeline: 2, + estimated_completion_time: 300, + bcan_poc: { POC_name: "Allie", POC_email: "allie@gmail.com" }, + grantmaker_poc: { POC_name: "Benjamin", POC_email: "benpetrillo@yahoo.com" }, + attachments: [], + isRestricted: true + }, ]; // Create mock functions that we can reference @@ -173,9 +207,45 @@ describe("GrantService", () => { describe("makeGrantsInactive()", () => { it("should inactivate multiple grants and return the updated grant objects", async () => { // First two update() calls respond with DynamoDB Attributes + + const mockReturnGrants = [ + + ] mockPromise - .mockResolvedValueOnce({ Attributes: { status: Status.Inactive } }) - .mockResolvedValueOnce({ Attributes: { status: Status.Inactive } }); + .mockResolvedValueOnce({ Attributes: { + grantId: 3, + organization: "Test Organization", + does_bcan_qualify: true, + status: Status.Inactive, + amount: 1000, + grant_start_date: "2024-01-01", + application_deadline: "2025-01-01", + report_deadlines: ["2025-01-01"], + description: "Test Description", + timeline: 1, + estimated_completion_time: 100, + grantmaker_poc: { POC_name: "name", POC_email: "test@test.com" }, + bcan_poc: { POC_name: "name", POC_email: ""}, + attachments: [], + isRestricted: false + }, }) + .mockResolvedValueOnce({ Attributes: { + grantId: 4, + organization: "Test Organization 2", + does_bcan_qualify: false, + status: Status.Inactive, + amount: 1000, + grant_start_date: "2025-02-15", + application_deadline: "2025-02-01", + report_deadlines: ["2025-03-01", "2025-04-01"], + description: "Test Description 2", + timeline: 2, + estimated_completion_time: 300, + bcan_poc: { POC_name: "Allie", POC_email: "allie@gmail.com" }, + grantmaker_poc: { POC_name: "Benjamin", POC_email: "benpetrillo@yahoo.com" }, + attachments: [], + isRestricted: true + }, }); // Next two calls are from getGrantById() // mockPromise @@ -185,9 +255,9 @@ describe("GrantService", () => { // mockGetGrantById // .mockResolvedValueOnce({ grantId: 1, status: Status.Inactive }) // .mockResolvedValueOnce({ grantId: 2, status: Status.Inactive }); - const data = await grantService.makeGrantsInactive([1, 2]); + const data = await grantService.makeGrantsInactive([3, 4]); - expect(data).toEqual([mockGrants[0], mockGrants[1]]); + expect(data).toEqual([mockGrants[2], mockGrants[3]]); expect(mockUpdate).toHaveBeenCalledTimes(2); const firstCallArgs = mockUpdate.mock.calls[0][0]; @@ -195,20 +265,20 @@ describe("GrantService", () => { expect(firstCallArgs).toMatchObject({ TableName: "Grants", - Key: { grantId: 1 }, + Key: { grantId: 3 }, UpdateExpression: "SET #status = :inactiveStatus", ExpressionAttributeNames: { "#status": "status" }, ExpressionAttributeValues: { ":inactiveStatus": Status.Inactive }, - ReturnValues: "UPDATED_NEW", + ReturnValues: "ALL_NEW", }); expect(secondCallArgs).toMatchObject({ TableName: "Grants", - Key: { grantId: 2 }, + Key: { grantId: 4 }, UpdateExpression: "SET #status = :inactiveStatus", ExpressionAttributeNames: { "#status": "status" }, ExpressionAttributeValues: { ":inactiveStatus": Status.Inactive }, - ReturnValues: "UPDATED_NEW", + ReturnValues: "ALL_NEW", }); }); }); diff --git a/backend/src/grant/grant.controller.ts b/backend/src/grant/grant.controller.ts index df70c1ba..9dd4c533 100644 --- a/backend/src/grant/grant.controller.ts +++ b/backend/src/grant/grant.controller.ts @@ -11,10 +11,7 @@ export class GrantController { return await this.grantService.getAllGrants(); } - @Get(':id') - async getGrantById(@Param('id') GrantId: string) { - return await this.grantService.getGrantById(parseInt(GrantId, 10)); - } + @Put('inactivate') async inactivate( @@ -40,5 +37,8 @@ export class GrantController { async deleteGrant(@Param('grantId') grantId: string) { return await this.grantService.deleteGrantById(grantId); } - + @Get(':id') + async getGrantById(@Param('id') GrantId: string) { + return await this.grantService.getGrantById(parseInt(GrantId, 10)); + } } \ No newline at end of file diff --git a/backend/src/grant/grant.module.ts b/backend/src/grant/grant.module.ts index 420037aa..20357f28 100644 --- a/backend/src/grant/grant.module.ts +++ b/backend/src/grant/grant.module.ts @@ -1,9 +1,10 @@ import { Module } from '@nestjs/common'; import { GrantService } from './grant.service'; import { GrantController } from './grant.controller'; - +import {NotificationsModule} from "../notifications/notification.module"; @Module({ controllers: [GrantController], providers: [GrantService], + imports: [NotificationsModule], }) export class GrantModule { } \ No newline at end of file diff --git a/backend/src/grant/grant.service.ts b/backend/src/grant/grant.service.ts index 14260f8d..1e36c081 100644 --- a/backend/src/grant/grant.service.ts +++ b/backend/src/grant/grant.service.ts @@ -88,20 +88,20 @@ async makeGrantsInactive(grantIds: number[]): Promise { "#status": "status", }, ExpressionAttributeValues: { - ":inactiveStatus": Status.Inactive, + ":inactiveStatus": Status.Inactive as String, }, - ReturnValues: "UPDATED_NEW", + ReturnValues: "ALL_NEW", }; try { const res = await this.dynamoDb.update(params).promise(); - + if (res.Attributes?.status === Status.Inactive) { console.log(`Grant ${grantId} successfully marked as inactive.`); - const grant = await this.getGrantById(grantId); - - updatedGrants.push(grant); + const currentGrant = res.Attributes as Grant; + console.log(currentGrant); + updatedGrants.push(currentGrant); } else { console.log(`Grant ${grantId} update failed or no change in status.`); } From efa18bdbc8a289766c18ee8050d59044b8ecfb8f Mon Sep 17 00:00:00 2001 From: adityapat24 Date: Mon, 24 Nov 2025 14:22:46 -0500 Subject: [PATCH 4/5] test fixes --- .../src/grant/__test__/grant.service.spec.ts | 51 ++++++++++++++----- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/backend/src/grant/__test__/grant.service.spec.ts b/backend/src/grant/__test__/grant.service.spec.ts index e9fa948d..d38a62fc 100644 --- a/backend/src/grant/__test__/grant.service.spec.ts +++ b/backend/src/grant/__test__/grant.service.spec.ts @@ -206,11 +206,7 @@ describe("GrantService", () => { describe("makeGrantsInactive()", () => { it("should inactivate multiple grants and return the updated grant objects", async () => { - // First two update() calls respond with DynamoDB Attributes - const mockReturnGrants = [ - - ] mockPromise .mockResolvedValueOnce({ Attributes: { grantId: 3, @@ -247,17 +243,46 @@ describe("GrantService", () => { isRestricted: true }, }); - // Next two calls are from getGrantById() - // mockPromise - // .mockResolvedValueOnce({ Item: mockGrants[0] }) - // .mockResolvedValueOnce({ Item: mockGrants[1] }); - - // mockGetGrantById - // .mockResolvedValueOnce({ grantId: 1, status: Status.Inactive }) - // .mockResolvedValueOnce({ grantId: 2, status: Status.Inactive }); + const data = await grantService.makeGrantsInactive([3, 4]); - expect(data).toEqual([mockGrants[2], mockGrants[3]]); + expect(data).toEqual([ + { + grantId: 3, + organization: "Test Organization", + does_bcan_qualify: true, + status: Status.Inactive, + amount: 1000, + grant_start_date: "2024-01-01", + application_deadline: "2025-01-01", + report_deadlines: ["2025-01-01"], + description: "Test Description", + timeline: 1, + estimated_completion_time: 100, + grantmaker_poc: { POC_name: "name", POC_email: "test@test.com" }, + bcan_poc: { POC_name: "name", POC_email: "" }, + attachments: [], + isRestricted: false + }, + { + grantId: 4, + organization: "Test Organization 2", + does_bcan_qualify: false, + status: Status.Inactive, + amount: 1000, + grant_start_date: "2025-02-15", + application_deadline: "2025-02-01", + report_deadlines: ["2025-03-01", "2025-04-01"], + description: "Test Description 2", + timeline: 2, + estimated_completion_time: 300, + bcan_poc: { POC_name: "Allie", POC_email: "allie@gmail.com" }, + grantmaker_poc: { POC_name: "Benjamin", POC_email: "benpetrillo@yahoo.com" }, + attachments: [], + isRestricted: true + } + ]); + expect(mockUpdate).toHaveBeenCalledTimes(2); const firstCallArgs = mockUpdate.mock.calls[0][0]; From 2d6d9ae8df75af507eb09e97e627d767a606e9f9 Mon Sep 17 00:00:00 2001 From: prooflesben Date: Tue, 2 Dec 2025 18:41:13 -0500 Subject: [PATCH 5/5] Updated make inactive so it only takes in one grant at time --- .../src/grant/__test__/grant.service.spec.ts | 121 +++++------------- backend/src/grant/grant.controller.ts | 8 +- backend/src/grant/grant.service.ts | 19 ++- 3 files changed, 48 insertions(+), 100 deletions(-) diff --git a/backend/src/grant/__test__/grant.service.spec.ts b/backend/src/grant/__test__/grant.service.spec.ts index d38a62fc..d006096d 100644 --- a/backend/src/grant/__test__/grant.service.spec.ts +++ b/backend/src/grant/__test__/grant.service.spec.ts @@ -206,89 +206,35 @@ describe("GrantService", () => { describe("makeGrantsInactive()", () => { it("should inactivate multiple grants and return the updated grant objects", async () => { + const inactiveGrant3 = { + grantId: 3, + organization: "Test Organization", + does_bcan_qualify: true, + status: Status.Inactive, + amount: 1000, + grant_start_date: "2024-01-01", + application_deadline: "2025-01-01", + report_deadlines: ["2025-01-01"], + description: "Test Description", + timeline: 1, + estimated_completion_time: 100, + grantmaker_poc: { POC_name: "name", POC_email: "test@test.com" }, + bcan_poc: { POC_name: "name", POC_email: ""}, + attachments: [], + isRestricted: false + }; - mockPromise - .mockResolvedValueOnce({ Attributes: { - grantId: 3, - organization: "Test Organization", - does_bcan_qualify: true, - status: Status.Inactive, - amount: 1000, - grant_start_date: "2024-01-01", - application_deadline: "2025-01-01", - report_deadlines: ["2025-01-01"], - description: "Test Description", - timeline: 1, - estimated_completion_time: 100, - grantmaker_poc: { POC_name: "name", POC_email: "test@test.com" }, - bcan_poc: { POC_name: "name", POC_email: ""}, - attachments: [], - isRestricted: false - }, }) - .mockResolvedValueOnce({ Attributes: { - grantId: 4, - organization: "Test Organization 2", - does_bcan_qualify: false, - status: Status.Inactive, - amount: 1000, - grant_start_date: "2025-02-15", - application_deadline: "2025-02-01", - report_deadlines: ["2025-03-01", "2025-04-01"], - description: "Test Description 2", - timeline: 2, - estimated_completion_time: 300, - bcan_poc: { POC_name: "Allie", POC_email: "allie@gmail.com" }, - grantmaker_poc: { POC_name: "Benjamin", POC_email: "benpetrillo@yahoo.com" }, - attachments: [], - isRestricted: true - }, }); - + mockPromise.mockResolvedValueOnce({ Attributes: inactiveGrant3 }); - const data = await grantService.makeGrantsInactive([3, 4]); + const data = await grantService.makeGrantsInactive(3); - expect(data).toEqual([ - { - grantId: 3, - organization: "Test Organization", - does_bcan_qualify: true, - status: Status.Inactive, - amount: 1000, - grant_start_date: "2024-01-01", - application_deadline: "2025-01-01", - report_deadlines: ["2025-01-01"], - description: "Test Description", - timeline: 1, - estimated_completion_time: 100, - grantmaker_poc: { POC_name: "name", POC_email: "test@test.com" }, - bcan_poc: { POC_name: "name", POC_email: "" }, - attachments: [], - isRestricted: false - }, - { - grantId: 4, - organization: "Test Organization 2", - does_bcan_qualify: false, - status: Status.Inactive, - amount: 1000, - grant_start_date: "2025-02-15", - application_deadline: "2025-02-01", - report_deadlines: ["2025-03-01", "2025-04-01"], - description: "Test Description 2", - timeline: 2, - estimated_completion_time: 300, - bcan_poc: { POC_name: "Allie", POC_email: "allie@gmail.com" }, - grantmaker_poc: { POC_name: "Benjamin", POC_email: "benpetrillo@yahoo.com" }, - attachments: [], - isRestricted: true - } - ]); + expect(data).toEqual(inactiveGrant3); - expect(mockUpdate).toHaveBeenCalledTimes(2); + expect(mockUpdate).toHaveBeenCalledTimes(1); - const firstCallArgs = mockUpdate.mock.calls[0][0]; - const secondCallArgs = mockUpdate.mock.calls[1][0]; + const callArgs = mockUpdate.mock.calls[0][0]; - expect(firstCallArgs).toMatchObject({ + expect(callArgs).toMatchObject({ TableName: "Grants", Key: { grantId: 3 }, UpdateExpression: "SET #status = :inactiveStatus", @@ -296,15 +242,6 @@ describe("GrantService", () => { ExpressionAttributeValues: { ":inactiveStatus": Status.Inactive }, ReturnValues: "ALL_NEW", }); - - expect(secondCallArgs).toMatchObject({ - TableName: "Grants", - Key: { grantId: 4 }, - UpdateExpression: "SET #status = :inactiveStatus", - ExpressionAttributeNames: { "#status": "status" }, - ExpressionAttributeValues: { ":inactiveStatus": Status.Inactive }, - ReturnValues: "ALL_NEW", - }); }); }); @@ -455,7 +392,9 @@ describe("GrantService", () => { // Tests for deleteGrantById method describe('deleteGrantById', () => { it('should call DynamoDB delete with the correct params and return success message', async () => { - mockPromise.mockResolvedValueOnce({}); + mockDelete.mockReturnValue({ + promise: vi.fn().mockResolvedValue({}) + }); const result = await grantService.deleteGrantById('123'); @@ -478,14 +417,18 @@ describe('deleteGrantById', () => { const conditionalError = new Error('Conditional check failed'); (conditionalError as any).code = 'ConditionalCheckFailedException'; - mockPromise.mockRejectedValueOnce(conditionalError); + mockDelete.mockReturnValue({ + promise: vi.fn().mockRejectedValue(conditionalError) + }); await expect(grantService.deleteGrantById('999')) .rejects.toThrow(/does not exist/); }); it('should throw a generic failure when DynamoDB fails for other reasons', async () => { - mockPromise.mockRejectedValueOnce(new Error('Some other DynamoDB error')); + mockDelete.mockReturnValue({ + promise: vi.fn().mockRejectedValue(new Error('Some other DynamoDB error')) + }); await expect(grantService.deleteGrantById('123')) .rejects.toThrow(/Failed to delete/); diff --git a/backend/src/grant/grant.controller.ts b/backend/src/grant/grant.controller.ts index 9dd4c533..dff64af5 100644 --- a/backend/src/grant/grant.controller.ts +++ b/backend/src/grant/grant.controller.ts @@ -17,7 +17,13 @@ export class GrantController { async inactivate( @Body('grantIds') grantIds: number[] ): Promise { - return await this.grantService.makeGrantsInactive(grantIds) + let grants: Grant[] = []; + for(const id of grantIds){ + Logger.log(`Inactivating grant with ID: ${id}`); + let newGrant = await this.grantService.makeGrantsInactive(id) + grants.push(newGrant); + } + return grants; } @Post('new-grant') diff --git a/backend/src/grant/grant.service.ts b/backend/src/grant/grant.service.ts index 1e36c081..08ab72cb 100644 --- a/backend/src/grant/grant.service.ts +++ b/backend/src/grant/grant.service.ts @@ -37,11 +37,13 @@ export class GrantService { if (now >= endDate) { inactiveGrantIds.push(grant.grantId); + let newGrant = this.makeGrantsInactive(grant.grantId) + grants.filter(g => g.grantId !== grant.grantId); + grants.push(await newGrant); + } } } - const updatedGrants = this.makeGrantsInactive(inactiveGrantIds); - grants.push(...await updatedGrants); return grants; } catch (error) { console.log(error) @@ -76,10 +78,9 @@ export class GrantService { } // Method to make grants inactive -async makeGrantsInactive(grantIds: number[]): Promise { - const updatedGrants: Grant[] = []; +async makeGrantsInactive(grantId: number): Promise { + let updatedGrant: Grant = {} as Grant; - for (const grantId of grantIds) { const params = { TableName: process.env.DYNAMODB_GRANT_TABLE_NAME || "TABLE_FAILURE", Key: { grantId }, @@ -101,7 +102,7 @@ async makeGrantsInactive(grantIds: number[]): Promise { const currentGrant = res.Attributes as Grant; console.log(currentGrant); - updatedGrants.push(currentGrant); + updatedGrant = currentGrant } else { console.log(`Grant ${grantId} update failed or no change in status.`); } @@ -109,9 +110,8 @@ async makeGrantsInactive(grantIds: number[]): Promise { console.log(err); throw new Error(`Failed to update Grant ${grantId} status.`); } - } - return updatedGrants; + return updatedGrant; } @@ -203,7 +203,7 @@ async makeGrantsInactive(grantIds: number[]): Promise { try { await this.dynamoDb.delete(params).promise(); this.logger.log(`Grant ${grantId} deleted successfully`); - return 'Grant ${grantId} deleted successfully'; + return `Grant ${grantId} deleted successfully`; } catch (error: any) { if (error.code === "ConditionalCheckFailedException") { throw new Error(`Grant ${grantId} does not exist`); @@ -211,7 +211,6 @@ async makeGrantsInactive(grantIds: number[]): Promise { this.logger.error(`Failed to delete Grant ${grantId}`, error.stack); throw new Error(`Failed to delete Grant ${grantId}`); } - } /*