From 179be2f564c3a25691f6b1a8597e9ee0f820d7f5 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 1 Aug 2026 10:21:28 +0200 Subject: [PATCH] Task/add portfolio summary tests (#7458) Add portfolio summary tests --- .../app/portfolio/portfolio.service.spec.ts | 86 ++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/apps/api/src/app/portfolio/portfolio.service.spec.ts b/apps/api/src/app/portfolio/portfolio.service.spec.ts index d85258f7e..699e79e0d 100644 --- a/apps/api/src/app/portfolio/portfolio.service.spec.ts +++ b/apps/api/src/app/portfolio/portfolio.service.spec.ts @@ -1,6 +1,7 @@ import { AccountService } from '@ghostfolio/api/app/account/account.service'; import { CashDetails } from '@ghostfolio/api/app/account/interfaces/cash-details.interface'; import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; +import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator'; import { userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; import { UserService } from '@ghostfolio/api/app/user/user.service'; @@ -11,7 +12,10 @@ import { ImpersonationService } from '@ghostfolio/api/services/impersonation/imp import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; import { UNKNOWN_KEY } from '@ghostfolio/common/config'; import { parseDate } from '@ghostfolio/common/helper'; -import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; +import { + AssetProfileIdentifier, + PortfolioSummary +} from '@ghostfolio/common/interfaces'; import { Account, DataSource } from '@prisma/client'; import { Big } from 'big.js'; @@ -338,6 +342,86 @@ describe('PortfolioService', () => { }); }); + describe('getSummary', () => { + const getSummary = (args: object) => { + return ( + portfolioService as unknown as { + getSummary: (aArgs: object) => Promise; + } + ).getSummary(args); + }; + + function createPortfolioCalculator() { + return { + getDividendInBaseCurrency: jest.fn().mockResolvedValue(new Big(0)), + getFeesInBaseCurrency: jest.fn().mockResolvedValue(new Big(0)), + getInterestInBaseCurrency: jest.fn().mockResolvedValue(new Big(0)), + getLiabilitiesInBaseCurrency: jest.fn().mockResolvedValue(new Big(0)), + getSnapshot: jest.fn().mockResolvedValue({ + currentValueInBaseCurrency: new Big(3000), + totalCashInBaseCurrency: new Big(1000), + totalInvestment: new Big(2000), + totalInvestmentWithCurrencyEffect: new Big(2000) + }), + getStartDate: jest.fn().mockReturnValue(parseDate('2024-01-01')) + } as unknown as PortfolioCalculator; + } + + beforeEach(() => { + jest + .spyOn(activitiesService, 'getActivities') + .mockResolvedValue({ activities: [], count: 0 }); + + jest + .spyOn(impersonationService, 'validateImpersonationId') + .mockResolvedValue(null); + + jest.spyOn(portfolioService, 'getPerformance').mockResolvedValue({ + performance: { + currentValueInBaseCurrency: 3000, + netPerformance: 500, + netPerformancePercentage: 0.2, + netPerformancePercentageWithCurrencyEffect: 0.2, + netPerformanceWithCurrencyEffect: 500 + } + } as Awaited>); + + jest.spyOn(userService, 'user').mockResolvedValue({ + id: userDummyData.id, + settings: { + settings: { + baseCurrency: 'CHF' + } + } + } as unknown as Awaited>); + }); + + it('should derive the cash and net worth from the account balance when there are no excluded accounts, no emergency fund and no liabilities', async () => { + jest.spyOn(accountService, 'getCashDetails').mockResolvedValue({ + accounts: [], + balanceInBaseCurrency: 1000 + }); + + const portfolioCalculator = createPortfolioCalculator(); + + const summary = await getSummary({ + portfolioCalculator, + balanceInBaseCurrency: 1000, + emergencyFundHoldingsValueInBaseCurrency: 0, + filteredValueInBaseCurrency: new Big(3000), + impersonationId: undefined, + userCurrency: 'CHF', + userId: userDummyData.id + }); + + expect(summary.cash).toBe(1000); + expect(summary.emergencyFund.total).toBe(0); + expect(summary.excludedAccountsAndActivities).toBe(0); + expect(summary.totalAssetsInBaseCurrency).toBe(3000); + expect(summary.totalValueInBaseCurrency).toBe(3000); + }); + }); + describe('getValueOfAccountsAndPlatforms', () => { const getValueOfAccountsAndPlatforms = (args: object) => { return (