diff --git a/apps/api/src/app/activities/activities.service.spec.ts b/apps/api/src/app/activities/activities.service.spec.ts index fb2c68ff40..9c1938179f 100644 --- a/apps/api/src/app/activities/activities.service.spec.ts +++ b/apps/api/src/app/activities/activities.service.spec.ts @@ -18,7 +18,9 @@ import { Activity, Filter } from '@ghostfolio/common/interfaces'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { + AssetClass, AssetProfileSplit, + AssetSubClass, DataSource, Prisma, Type as ActivityType @@ -519,6 +521,85 @@ describe('ActivitiesService', () => { }); }); }); + + describe('updateActivity', () => { + it('updates the custom asset profile of the user', async () => { + const data = await getUpdatedActivityData({ userId: 'user-id' }); + + expect(data.SymbolProfile).toEqual({ + update: { + assetClass: AssetClass.COMMODITY, + assetSubClass: AssetSubClass.PRECIOUS_METAL + } + }); + }); + + it.each([ + { owner: 'the admin', userId: null }, + { owner: 'another user', userId: 'other-user-id' } + ])('does not update the asset profile of $owner', async ({ userId }) => { + const data = await getUpdatedActivityData({ userId }); + + expect(data).not.toHaveProperty('SymbolProfile'); + }); + + async function getUpdatedActivityData({ + userId + }: { + userId: string | null; + }) { + const update = jest.fn().mockResolvedValue({ userId: 'user-id' }); + + const service = new ActivitiesService( + null, + null, + null, + null, + null, + null, + { emit: jest.fn() } as unknown as EventEmitter2, + null, + null, + { + order: { + update, + findUnique: jest + .fn() + .mockResolvedValue({ SymbolProfile: { userId } }) + } + } as unknown as PrismaService, + null, + { validateTagIds: jest.fn() } as unknown as TagService + ); + + await service.updateActivity({ + data: { + assetClass: AssetClass.COMMODITY, + assetSubClass: AssetSubClass.PRECIOUS_METAL, + date: parseDate('2024-01-01'), + SymbolProfile: { + connect: { + dataSource_symbol: { + dataSource: DataSource.MANUAL, + symbol: 'GF_GOLD' + } + }, + update: { + assetClass: AssetClass.COMMODITY, + assetSubClass: AssetSubClass.PRECIOUS_METAL, + name: 'GF_GOLD' + } + }, + type: 'BUY' + }, + originalDate: parseDate('2024-01-01'), + userId: 'user-id', + where: { id: 'activity-id' } + }); + + return (update.mock.calls[0] as [Prisma.OrderUpdateArgs])[0].data; + } + }); }); function createActivity({ diff --git a/apps/api/src/app/activities/activities.service.ts b/apps/api/src/app/activities/activities.service.ts index 0de3f7c3c4..34179b9e69 100644 --- a/apps/api/src/app/activities/activities.service.ts +++ b/apps/api/src/app/activities/activities.service.ts @@ -934,6 +934,17 @@ export class ActivitiesService { ) { delete data.SymbolProfile.connect; delete data.SymbolProfile.update.name; + + const activityToUpdate = await this.prismaService.order.findUnique({ + select: { SymbolProfile: { select: { userId: true } } }, + where + }); + + // Update the asset profile only if it is a custom asset profile of the + // user, but not one of the admin, of a data provider or of another user + if (activityToUpdate?.SymbolProfile.userId !== userId) { + delete data.SymbolProfile; + } } else { delete data.SymbolProfile.update;