mirror of https://github.com/ghostfolio/ghostfolio
committed by
GitHub
11 changed files with 927 additions and 53 deletions
@ -0,0 +1,148 @@ |
|||||
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; |
||||
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; |
||||
|
import { DataProviderInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; |
||||
|
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; |
||||
|
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; |
||||
|
import { PropertyService } from '@ghostfolio/api/services/property/property.service'; |
||||
|
import { SubscriptionType } from '@ghostfolio/common/enums'; |
||||
|
import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; |
||||
|
import type { UserWithSettings } from '@ghostfolio/common/types'; |
||||
|
|
||||
|
import { AssetClass, AssetSubClass, DataSource } from '@prisma/client'; |
||||
|
|
||||
|
import { DataProviderService } from './data-provider.service'; |
||||
|
|
||||
|
describe('DataProviderService', () => { |
||||
|
describe('validateActivities', () => { |
||||
|
const assetProfileOfUser = { |
||||
|
assetClass: AssetClass.EQUITY, |
||||
|
assetSubClass: AssetSubClass.ETF, |
||||
|
currency: 'USD', |
||||
|
dataSource: DataSource.EOD_HISTORICAL_DATA, |
||||
|
isin: 'IE00BK5BQT80', |
||||
|
name: 'Vanguard FTSE All-World UCITS ETF', |
||||
|
symbol: 'VWRA.LSE' |
||||
|
}; |
||||
|
|
||||
|
const activityOfAssetProfileOfUser = { |
||||
|
currency: 'USD', |
||||
|
dataSource: DataSource.EOD_HISTORICAL_DATA, |
||||
|
symbol: 'VWRA.LSE', |
||||
|
type: 'BUY' as const |
||||
|
}; |
||||
|
|
||||
|
const basicSubscription = { |
||||
|
type: SubscriptionType.Basic |
||||
|
} as UserWithSettings['subscription']; |
||||
|
|
||||
|
const userId = 'user-id'; |
||||
|
|
||||
|
let dataProviderService: DataProviderService; |
||||
|
let eodHistoricalDataService: DataProviderInterface; |
||||
|
let findFirstSymbolProfile: jest.Mock; |
||||
|
|
||||
|
beforeEach(() => { |
||||
|
const configuration = { |
||||
|
DATA_SOURCES: [DataSource.EOD_HISTORICAL_DATA, DataSource.YAHOO], |
||||
|
DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER: [], |
||||
|
ENABLE_FEATURE_SUBSCRIPTION: true |
||||
|
}; |
||||
|
|
||||
|
eodHistoricalDataService = { |
||||
|
getAssetProfile: jest.fn().mockResolvedValue(assetProfileOfUser), |
||||
|
getDataProviderInfo: jest.fn().mockReturnValue({ isPremium: true }), |
||||
|
getName: jest.fn().mockReturnValue(DataSource.EOD_HISTORICAL_DATA) |
||||
|
} as unknown as DataProviderInterface; |
||||
|
|
||||
|
findFirstSymbolProfile = jest.fn().mockResolvedValue(assetProfileOfUser); |
||||
|
|
||||
|
dataProviderService = new DataProviderService( |
||||
|
{ |
||||
|
get: (key: keyof typeof configuration) => { |
||||
|
return configuration[key]; |
||||
|
} |
||||
|
} as unknown as ConfigurationService, |
||||
|
[eodHistoricalDataService], |
||||
|
{} as MarketDataService, |
||||
|
{ |
||||
|
symbolProfile: { findFirst: findFirstSymbolProfile } |
||||
|
} as unknown as PrismaService, |
||||
|
{ |
||||
|
getByKey: jest.fn().mockResolvedValue(undefined) |
||||
|
} as unknown as PropertyService, |
||||
|
{} as RedisCacheService |
||||
|
); |
||||
|
}); |
||||
|
|
||||
|
it('takes the stored asset profile of a premium data provider without a request to the data provider for the Basic subscription if the user has an activity with it', async () => { |
||||
|
await expect( |
||||
|
dataProviderService.validateActivities({ |
||||
|
userId, |
||||
|
activitiesDto: [activityOfAssetProfileOfUser], |
||||
|
maxActivitiesToImport: 1, |
||||
|
subscription: basicSubscription |
||||
|
}) |
||||
|
).resolves.toEqual({ |
||||
|
[getAssetProfileIdentifier(assetProfileOfUser)]: assetProfileOfUser |
||||
|
}); |
||||
|
|
||||
|
expect(findFirstSymbolProfile).toHaveBeenCalledWith( |
||||
|
expect.objectContaining({ |
||||
|
where: { |
||||
|
activities: { some: { userId } }, |
||||
|
dataSource: DataSource.EOD_HISTORICAL_DATA, |
||||
|
symbol: 'VWRA.LSE' |
||||
|
} |
||||
|
}) |
||||
|
); |
||||
|
|
||||
|
expect(eodHistoricalDataService.getAssetProfile).not.toHaveBeenCalled(); |
||||
|
}); |
||||
|
|
||||
|
it('gets the stored asset profile once for several activities with it', async () => { |
||||
|
await dataProviderService.validateActivities({ |
||||
|
userId, |
||||
|
activitiesDto: [ |
||||
|
activityOfAssetProfileOfUser, |
||||
|
{ ...activityOfAssetProfileOfUser, type: 'SELL' } |
||||
|
], |
||||
|
maxActivitiesToImport: 2, |
||||
|
subscription: basicSubscription |
||||
|
}); |
||||
|
|
||||
|
expect(findFirstSymbolProfile).toHaveBeenCalledTimes(1); |
||||
|
}); |
||||
|
|
||||
|
it('throws an error for a premium data provider for the Basic subscription if the user has no activity with the asset profile', async () => { |
||||
|
findFirstSymbolProfile.mockResolvedValue(null); |
||||
|
|
||||
|
await expect( |
||||
|
dataProviderService.validateActivities({ |
||||
|
userId, |
||||
|
activitiesDto: [activityOfAssetProfileOfUser], |
||||
|
maxActivitiesToImport: 1, |
||||
|
subscription: basicSubscription |
||||
|
}) |
||||
|
).rejects.toThrow( |
||||
|
'activity.dataSource ("EOD_HISTORICAL_DATA") requires Ghostfolio Premium' |
||||
|
); |
||||
|
}); |
||||
|
|
||||
|
it('resolves the asset profile of a premium data provider with the data provider for the Premium subscription', async () => { |
||||
|
await dataProviderService.validateActivities({ |
||||
|
userId, |
||||
|
activitiesDto: [activityOfAssetProfileOfUser], |
||||
|
maxActivitiesToImport: 1, |
||||
|
subscription: { |
||||
|
type: SubscriptionType.Premium |
||||
|
} as UserWithSettings['subscription'] |
||||
|
}); |
||||
|
|
||||
|
expect(findFirstSymbolProfile).not.toHaveBeenCalled(); |
||||
|
|
||||
|
expect(eodHistoricalDataService.getAssetProfile).toHaveBeenCalledWith({ |
||||
|
symbol: 'VWRA.LSE' |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
Loading…
Reference in new issue