diff --git a/backend/src/revenue/__test__/cashflow-revenue.service.spec.ts b/backend/src/revenue/__test__/cashflow-revenue.service.spec.ts index e50b32b5..177a6fc2 100644 --- a/backend/src/revenue/__test__/cashflow-revenue.service.spec.ts +++ b/backend/src/revenue/__test__/cashflow-revenue.service.spec.ts @@ -3,6 +3,8 @@ import { BadRequestException, InternalServerErrorException } from '@nestjs/commo import { RevenueService } from '../cashflow-revenue.service'; import { RevenueType } from '../../../../middle-layer/types/RevenueType'; import { CashflowRevenue } from '../../../../middle-layer/types/CashflowRevenue'; +import { Grant } from '../../../../middle-layer/types/Grant'; +import { Status } from '../../../../middle-layer/types/Status'; import { describe, it, expect, beforeEach, afterEach, beforeAll, vi } from 'vitest'; // ─── Mock function declarations ─────────────────────────────────────────────── @@ -43,23 +45,63 @@ const mockDatabase: CashflowRevenue[] = [ { name: 'Revenue Three', amount: 3000, type: RevenueType.Sponsorship, installments: [{ amount: 3000, date: '2024-03-01' as any }] }, ]; +const activeGrant: Grant = { + grantId: 1, + organization: 'Active Grant Org', + does_bcan_qualify: true, + status: Status.Active, + amount: 4000, + grant_start_date: '2024-07-01' as any, + application_deadline: '2024-06-01' as any, + timeline: 1, + estimated_completion_time: 8, + bcan_poc: { POC_name: 'bcan', POC_email: 'bcan@gmail.com' } as any, + attachments: [] as any, + isRestricted: false, +} as Grant; + +const inactiveGrant: Grant = { + ...activeGrant, + grantId: 2, + organization: 'Inactive Grant Org', + status: Status.Inactive, + grant_start_date: '2024-08-01' as any, +} as Grant; + // ─── Test suite ─────────────────────────────────────────────────────────────── describe('RevenueService', () => { let service: RevenueService; + let revenueItems: CashflowRevenue[]; + let grantItems: Grant[]; beforeAll(() => { process.env.CASHFLOW_REVENUE_TABLE_NAME = 'test-revenue-table'; + process.env.DYNAMODB_GRANT_TABLE_NAME = 'test-grant-table'; }); // Guarantee env var is restored after every test, even if the test throws afterEach(() => { process.env.CASHFLOW_REVENUE_TABLE_NAME = 'test-revenue-table'; + process.env.DYNAMODB_GRANT_TABLE_NAME = 'test-grant-table'; }); beforeEach(async () => { vi.clearAllMocks(); - mockScan.mockReturnValue(resolved({})); + revenueItems = mockDatabase; + grantItems = []; + + mockScan.mockImplementation((params) => { + if (params.TableName === 'test-revenue-table') { + return resolved({ Items: revenueItems }); + } + + if (params.TableName === 'test-grant-table') { + return resolved({ Items: grantItems }); + } + + return resolved({}); + }); mockGet.mockReturnValue(resolved({})); mockPut.mockReturnValue(resolved({})); mockDelete.mockReturnValue(resolved({})); @@ -81,6 +123,15 @@ describe('RevenueService', () => { expect(mockScan).toHaveBeenCalledWith({ TableName: 'test-revenue-table' }); }); + it('should only scan the revenue table', async () => { + grantItems = [activeGrant, inactiveGrant]; + + await service.getAllRevenue(); + + expect(mockScan).toHaveBeenCalledTimes(1); + expect(mockScan).toHaveBeenCalledWith({ TableName: 'test-revenue-table' }); + }); + it('should return an empty array when no items exist', async () => { mockScan.mockReturnValue(resolved({ Items: [] })); const result = await service.getAllRevenue(); diff --git a/backend/src/revenue/cashflow-revenue.service.ts b/backend/src/revenue/cashflow-revenue.service.ts index f9b5f0bc..c8fe2621 100644 --- a/backend/src/revenue/cashflow-revenue.service.ts +++ b/backend/src/revenue/cashflow-revenue.service.ts @@ -14,7 +14,7 @@ import { Installment } from "../../../middle-layer/types/Installment"; export class RevenueService { private readonly logger = new Logger(RevenueService.name); private dynamoDb = new AWS.DynamoDB.DocumentClient(); - private revenueTableName : string = process.env.CASHFLOW_REVENUE_TABLE_NAME || "" + private revenueTableName : string = process.env.CASHFLOW_REVENUE_TABLE_NAME || ""; /** * Helper method to check if an error is an AWS error and extract relevant information */ @@ -206,6 +206,7 @@ private validateTableName(tableName : string){ } } + /** * Method to retrieve all of the revenue data * @returns All the revenue objects in the data base diff --git a/frontend/src/main-page/cash-flow/processCashflowData.ts b/frontend/src/main-page/cash-flow/processCashflowData.ts index 0fc2dd47..019da77e 100644 --- a/frontend/src/main-page/cash-flow/processCashflowData.ts +++ b/frontend/src/main-page/cash-flow/processCashflowData.ts @@ -4,6 +4,9 @@ import { fetchCashflowCosts, fetchCashflowRevenues, setCashflowSettings } from " import {CashflowRevenue} from "../../../../middle-layer/types/CashflowRevenue.ts"; import {CashflowCost} from "../../../../middle-layer/types/CashflowCost.ts"; import {CashflowSettings} from "../../../../middle-layer/types/CashflowSettings.ts"; +import { Grant } from "../../../../middle-layer/types/Grant.ts"; +import { RevenueType } from "../../../../middle-layer/types/RevenueType.ts"; +import { Status } from "../../../../middle-layer/types/Status.ts"; import { api } from "../../api.ts"; // This has not been tested yet but the basic structure when implemented should be the same @@ -26,12 +29,37 @@ export const fetchCosts = async () => { export const fetchRevenues = async () => { try { - const response = await api("/cashflow-revenue"); - if (!response.ok) { - throw new Error(`HTTP Error, Status: ${response.status}`); + const [revenueResponse, grantResponse] = await Promise.all([ + api("/cashflow-revenue"), + api("/grant"), + ]); + + if (!revenueResponse.ok) { + throw new Error(`HTTP Error, Status: ${revenueResponse.status}`); + } + + if (!grantResponse.ok) { + throw new Error(`HTTP Error, Status: ${grantResponse.status}`); } - const updatedRevenues: CashflowRevenue[] = await response.json(); - fetchCashflowRevenues(updatedRevenues); + + const updatedRevenues: CashflowRevenue[] = await revenueResponse.json(); + const grants: Grant[] = await grantResponse.json(); + + const mappedActiveGrantRevenues: CashflowRevenue[] = grants + .filter((grant) => grant.status === Status.Active) + .map((grant) => ({ + amount: grant.amount, + type: RevenueType.Grants, + name: grant.organization.trim(), + installments: [ + { + amount: grant.amount, + date: new Date(grant.grant_start_date), + }, + ], + })); + + fetchCashflowRevenues([...updatedRevenues, ...mappedActiveGrantRevenues]); } catch (error) { console.error("Error fetching revenues:", error); }