From 45e9da463760d9cb28930b8027a003ae0bce21fd Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 28 Oct 2026 19:42:04 +0100 Subject: [PATCH] Bugfix/stale benchmarks in markets overview (#8330) * Fix stale benchmarks in markets overview * Update changelog --- CHANGELOG.md | 1 + .../benchmark/benchmark.service.spec.ts | 101 +++++++++++++++++- .../services/benchmark/benchmark.service.ts | 2 + 3 files changed, 102 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fef7349f68..c16f9e0148 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed the double submission of forms in the account detail dialog, the create or update access dialog, the historical market data editor, the import activities dialog, the coupon system and the create asset profile dialog of the admin control panel +- Fixed an issue where the benchmarks in the markets overview were not updated after adding or deleting a benchmark - Fixed an issue where users could grant a private access to themselves - Fixed the status code of the endpoints to add and delete a benchmark if the asset profile does not exist - Fixed the git-hook via `husky` to lint and check the formatting of the uncommitted changes before a commit diff --git a/apps/api/src/services/benchmark/benchmark.service.spec.ts b/apps/api/src/services/benchmark/benchmark.service.spec.ts index 27124e89e4..6124ee9add 100644 --- a/apps/api/src/services/benchmark/benchmark.service.spec.ts +++ b/apps/api/src/services/benchmark/benchmark.service.spec.ts @@ -1,10 +1,33 @@ +import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; +import { PropertyService } from '@ghostfolio/api/services/property/property.service'; +import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service'; +import { PROPERTY_BENCHMARKS } from '@ghostfolio/common/config'; + +import { DataSource } from '@prisma/client'; + import { BenchmarkService } from './benchmark.service'; describe('BenchmarkService', () => { let benchmarkService: BenchmarkService; + let findFirst: jest.Mock; + let getByKey: jest.Mock; + let put: jest.Mock; + let remove: jest.Mock; + + beforeEach(() => { + findFirst = jest.fn(); + getByKey = jest.fn(); + put = jest.fn().mockResolvedValue(undefined); + remove = jest.fn().mockResolvedValue(true); - beforeAll(async () => { - benchmarkService = new BenchmarkService(null, null, null, null, null, null); + benchmarkService = new BenchmarkService( + null, + null, + { symbolProfile: { findFirst } } as unknown as PrismaService, + { getByKey, put } as unknown as PropertyService, + { remove } as unknown as RedisCacheService, + null + ); }); it('calculateChangeInPercentage', async () => { @@ -34,4 +57,78 @@ describe('BenchmarkService', () => { -0.1 ); }); + + describe('addBenchmark', () => { + it('stores the benchmark and removes the cached benchmarks', async () => { + findFirst.mockResolvedValue({ id: 'profile-id', name: 'S&P 500' }); + getByKey.mockResolvedValue([{ symbolProfileId: 'other-profile-id' }]); + + await benchmarkService.addBenchmark({ + dataSource: DataSource.YAHOO, + symbol: '^GSPC' + }); + + expect(put).toHaveBeenCalledWith({ + key: PROPERTY_BENCHMARKS, + value: JSON.stringify([ + { symbolProfileId: 'other-profile-id' }, + { symbolProfileId: 'profile-id' } + ]) + }); + expect(remove).toHaveBeenCalledWith('BENCHMARKS'); + expect(put.mock.invocationCallOrder[0]).toBeLessThan( + remove.mock.invocationCallOrder[0] + ); + }); + + it('keeps the cached benchmarks if the asset profile is not found', async () => { + findFirst.mockResolvedValue(null); + + const result = await benchmarkService.addBenchmark({ + dataSource: DataSource.YAHOO, + symbol: '^GSPC' + }); + + expect(result).toBeNull(); + expect(put).not.toHaveBeenCalled(); + expect(remove).not.toHaveBeenCalled(); + }); + }); + + describe('deleteBenchmark', () => { + it('stores the remaining benchmarks and removes the cached benchmarks', async () => { + findFirst.mockResolvedValue({ id: 'profile-id', name: 'S&P 500' }); + getByKey.mockResolvedValue([ + { symbolProfileId: 'other-profile-id' }, + { symbolProfileId: 'profile-id' } + ]); + + await benchmarkService.deleteBenchmark({ + dataSource: DataSource.YAHOO, + symbol: '^GSPC' + }); + + expect(put).toHaveBeenCalledWith({ + key: PROPERTY_BENCHMARKS, + value: JSON.stringify([{ symbolProfileId: 'other-profile-id' }]) + }); + expect(remove).toHaveBeenCalledWith('BENCHMARKS'); + expect(put.mock.invocationCallOrder[0]).toBeLessThan( + remove.mock.invocationCallOrder[0] + ); + }); + + it('keeps the cached benchmarks if the asset profile is not found', async () => { + findFirst.mockResolvedValue(null); + + const result = await benchmarkService.deleteBenchmark({ + dataSource: DataSource.YAHOO, + symbol: '^GSPC' + }); + + expect(result).toBeNull(); + expect(put).not.toHaveBeenCalled(); + expect(remove).not.toHaveBeenCalled(); + }); + }); }); diff --git a/apps/api/src/services/benchmark/benchmark.service.ts b/apps/api/src/services/benchmark/benchmark.service.ts index ea7bbaec12..e0120bf127 100644 --- a/apps/api/src/services/benchmark/benchmark.service.ts +++ b/apps/api/src/services/benchmark/benchmark.service.ts @@ -335,5 +335,7 @@ export class BenchmarkService { key: PROPERTY_BENCHMARKS, value: JSON.stringify(benchmarks) }); + + await this.redisCacheService.remove(this.CACHE_KEY_BENCHMARKS); } }