|
|
@ -9,9 +9,14 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration/con |
|
|
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; |
|
|
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; |
|
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; |
|
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; |
|
|
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; |
|
|
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; |
|
|
import { TAG_ID_EMERGENCY_FUND, UNKNOWN_KEY } from '@ghostfolio/common/config'; |
|
|
import { |
|
|
|
|
|
TAG_ID_EMERGENCY_FUND, |
|
|
|
|
|
TAG_ID_EXCLUDE_FROM_ANALYSIS, |
|
|
|
|
|
UNKNOWN_KEY |
|
|
|
|
|
} from '@ghostfolio/common/config'; |
|
|
import { parseDate } from '@ghostfolio/common/helper'; |
|
|
import { parseDate } from '@ghostfolio/common/helper'; |
|
|
import { |
|
|
import { |
|
|
|
|
|
Activity, |
|
|
AssetProfileIdentifier, |
|
|
AssetProfileIdentifier, |
|
|
Filter, |
|
|
Filter, |
|
|
PortfolioSummary |
|
|
PortfolioSummary |
|
|
@ -490,6 +495,145 @@ describe('PortfolioService', () => { |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
describe('getHolding', () => { |
|
|
|
|
|
const dataSource = DataSource.YAHOO; |
|
|
|
|
|
const symbol = 'AAPL'; |
|
|
|
|
|
const includedActivity = { |
|
|
|
|
|
assetProfile: { dataSource, symbol }, |
|
|
|
|
|
tags: [] |
|
|
|
|
|
} as Activity; |
|
|
|
|
|
|
|
|
|
|
|
beforeEach(() => { |
|
|
|
|
|
jest.spyOn(userService, 'user').mockResolvedValue({ |
|
|
|
|
|
id: userDummyData.id, |
|
|
|
|
|
settings: { settings: { baseCurrency: 'USD' } } |
|
|
|
|
|
} as unknown as Awaited<ReturnType<typeof userService.user>>); |
|
|
|
|
|
|
|
|
|
|
|
jest |
|
|
|
|
|
.spyOn(symbolProfileService, 'getSymbolProfiles') |
|
|
|
|
|
.mockResolvedValue([]); |
|
|
|
|
|
|
|
|
|
|
|
jest |
|
|
|
|
|
.spyOn(portfolioCalculatorFactory, 'createCalculator') |
|
|
|
|
|
.mockReturnValue({ |
|
|
|
|
|
getSnapshot: jest.fn().mockResolvedValue({ positions: [] }), |
|
|
|
|
|
getTransactionPoints: jest.fn().mockReturnValue([]) |
|
|
|
|
|
} as unknown as PortfolioCalculator); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
it('keeps the cached path when the holding has included and excluded activities', async () => { |
|
|
|
|
|
const excludedActivity = { |
|
|
|
|
|
...includedActivity, |
|
|
|
|
|
tags: [{ id: TAG_ID_EXCLUDE_FROM_ANALYSIS }] |
|
|
|
|
|
} as Activity; |
|
|
|
|
|
const getActivities = jest |
|
|
|
|
|
.spyOn(activitiesService, 'getActivitiesForPortfolioCalculator') |
|
|
|
|
|
.mockResolvedValueOnce({ activities: [includedActivity], count: 1 }) |
|
|
|
|
|
.mockResolvedValue({ |
|
|
|
|
|
activities: [includedActivity, excludedActivity], |
|
|
|
|
|
count: 2 |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
await portfolioService.getHolding({ |
|
|
|
|
|
dataSource, |
|
|
|
|
|
symbol, |
|
|
|
|
|
userId: userDummyData.id, |
|
|
|
|
|
withExcludedActivities: true |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
expect(getActivities).toHaveBeenCalledTimes(1); |
|
|
|
|
|
expect(getActivities).toHaveBeenCalledWith({ |
|
|
|
|
|
userCurrency: 'USD', |
|
|
|
|
|
userId: userDummyData.id |
|
|
|
|
|
}); |
|
|
|
|
|
expect(portfolioCalculatorFactory.createCalculator).toHaveBeenCalledWith( |
|
|
|
|
|
expect.objectContaining({ |
|
|
|
|
|
activities: [includedActivity], |
|
|
|
|
|
filters: undefined, |
|
|
|
|
|
usePortfolioSnapshotCache: true |
|
|
|
|
|
}) |
|
|
|
|
|
); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
it.each([ |
|
|
|
|
|
{ |
|
|
|
|
|
account: { tags: [{ id: TAG_ID_EXCLUDE_FROM_ANALYSIS }] }, |
|
|
|
|
|
name: 'account', |
|
|
|
|
|
tags: [] |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
account: { tags: [] }, |
|
|
|
|
|
name: 'activity tag', |
|
|
|
|
|
tags: [{ id: TAG_ID_EXCLUDE_FROM_ANALYSIS }] |
|
|
|
|
|
} |
|
|
|
|
|
])( |
|
|
|
|
|
'uses the direct path for a holding excluded by its $name', |
|
|
|
|
|
async ({ account, tags }) => { |
|
|
|
|
|
const excludedActivity = { |
|
|
|
|
|
...includedActivity, |
|
|
|
|
|
account, |
|
|
|
|
|
tags |
|
|
|
|
|
} as Activity; |
|
|
|
|
|
|
|
|
|
|
|
const getActivities = jest |
|
|
|
|
|
.spyOn(activitiesService, 'getActivitiesForPortfolioCalculator') |
|
|
|
|
|
.mockResolvedValueOnce({ activities: [], count: 0 }) |
|
|
|
|
|
.mockResolvedValueOnce({ |
|
|
|
|
|
activities: [excludedActivity], |
|
|
|
|
|
count: 1 |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
await portfolioService.getHolding({ |
|
|
|
|
|
dataSource, |
|
|
|
|
|
symbol, |
|
|
|
|
|
userId: userDummyData.id, |
|
|
|
|
|
withExcludedActivities: true |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
expect(getActivities).toHaveBeenCalledTimes(2); |
|
|
|
|
|
expect(getActivities).toHaveBeenLastCalledWith({ |
|
|
|
|
|
filters: [ |
|
|
|
|
|
{ id: dataSource, type: 'DATA_SOURCE' }, |
|
|
|
|
|
{ id: symbol, type: 'SYMBOL' } |
|
|
|
|
|
], |
|
|
|
|
|
userCurrency: 'USD', |
|
|
|
|
|
userId: userDummyData.id, |
|
|
|
|
|
withExcludedAccountsAndActivities: true |
|
|
|
|
|
}); |
|
|
|
|
|
expect( |
|
|
|
|
|
portfolioCalculatorFactory.createCalculator |
|
|
|
|
|
).toHaveBeenCalledWith( |
|
|
|
|
|
expect.objectContaining({ |
|
|
|
|
|
activities: [excludedActivity], |
|
|
|
|
|
filters: [ |
|
|
|
|
|
{ id: dataSource, type: 'DATA_SOURCE' }, |
|
|
|
|
|
{ id: symbol, type: 'SYMBOL' } |
|
|
|
|
|
], |
|
|
|
|
|
usePortfolioSnapshotCache: false |
|
|
|
|
|
}) |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
it('does not load excluded activities by default', async () => { |
|
|
|
|
|
const getActivities = jest |
|
|
|
|
|
.spyOn(activitiesService, 'getActivitiesForPortfolioCalculator') |
|
|
|
|
|
.mockResolvedValue({ activities: [], count: 0 }); |
|
|
|
|
|
|
|
|
|
|
|
const holding = await portfolioService.getHolding({ |
|
|
|
|
|
dataSource, |
|
|
|
|
|
symbol, |
|
|
|
|
|
userId: userDummyData.id |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
expect(holding).toBeUndefined(); |
|
|
|
|
|
expect(getActivities).toHaveBeenCalledTimes(1); |
|
|
|
|
|
expect( |
|
|
|
|
|
portfolioCalculatorFactory.createCalculator |
|
|
|
|
|
).not.toHaveBeenCalled(); |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
describe('getSummary', () => { |
|
|
describe('getSummary', () => { |
|
|
const getSummary = (args: object) => { |
|
|
const getSummary = (args: object) => { |
|
|
return ( |
|
|
return ( |
|
|
|