Browse Source

Merge df515937d3 into b0747b025c

pull/7176/merge
Benny Powers 6 days ago
committed by GitHub
parent
commit
1a619a2d83
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 268
      apps/api/src/app/import/import.service.spec.ts
  2. 9
      apps/api/src/app/import/import.service.ts

268
apps/api/src/app/import/import.service.spec.ts

@ -0,0 +1,268 @@
import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service';
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service';
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
import { TagService } from '@ghostfolio/api/services/tag/tag.service';
import { UserWithSettings } from '@ghostfolio/common/types';
import { DataSource } from '@prisma/client';
import { ImportService } from './import.service';
describe('ImportService', () => {
let importService: ImportService;
let accountService: Partial<AccountService>;
let activitiesService: Partial<ActivitiesService>;
let dataGatheringService: Partial<DataGatheringService>;
let dataProviderService: Partial<DataProviderService>;
let symbolProfileService: Partial<SymbolProfileService>;
let marketDataService: Partial<MarketDataService>;
let tagService: Partial<TagService>;
const userId = 'test-user-id';
const user = {
id: userId,
permissions: [],
settings: { settings: { baseCurrency: 'USD' } }
} as unknown as UserWithSettings;
beforeEach(() => {
symbolProfileService = {
getSymbolProfiles: jest.fn(),
add: jest.fn()
};
marketDataService = {
updateMany: jest.fn()
};
dataProviderService = {
validateActivities: jest.fn().mockResolvedValue([])
};
dataGatheringService = {
gatherSymbols: jest.fn()
};
activitiesService = {
getActivities: jest.fn().mockResolvedValue({ activities: [] })
};
accountService = {
getAccounts: jest.fn().mockResolvedValue([])
};
tagService = {
getTagsForUser: jest.fn().mockResolvedValue([])
};
importService = new ImportService(
accountService as unknown as AccountService,
activitiesService as unknown as ActivitiesService,
null, // apiService
dataGatheringService as unknown as DataGatheringService,
dataProviderService as unknown as DataProviderService,
null, // exchangeRateDataService
marketDataService as MarketDataService,
null, // platformService
null, // portfolioService
symbolProfileService as SymbolProfileService,
tagService as unknown as TagService
);
});
describe('import', () => {
it('should reuse existing global asset profile (userId is null) instead of creating a duplicate', async () => {
const existingGlobalProfile = {
dataSource: DataSource.YAHOO,
symbol: 'VOO',
userId: null
};
(symbolProfileService.getSymbolProfiles as jest.Mock).mockResolvedValue([
existingGlobalProfile
]);
(marketDataService.updateMany as jest.Mock).mockResolvedValue(undefined);
await importService.import({
accountsWithBalancesDto: [],
activitiesDto: [],
assetProfilesWithMarketDataDto: [
{
currency: 'USD',
dataSource: DataSource.YAHOO,
symbol: 'VOO',
marketData: []
}
],
isDryRun: false,
maxActivitiesToImport: 100,
tagsDto: [],
user
});
expect(symbolProfileService.add).not.toHaveBeenCalled();
});
it('should create a new profile with remapped symbol when asset profile belongs to a different user', async () => {
const otherUsersProfile = {
dataSource: DataSource.MANUAL,
symbol: 'MY_ASSET',
userId: 'other-user-id'
};
(symbolProfileService.getSymbolProfiles as jest.Mock).mockResolvedValue([
otherUsersProfile
]);
(marketDataService.updateMany as jest.Mock).mockResolvedValue(undefined);
await importService.import({
accountsWithBalancesDto: [],
activitiesDto: [],
assetProfilesWithMarketDataDto: [
{
currency: 'USD',
dataSource: DataSource.MANUAL,
symbol: 'MY_ASSET',
marketData: []
}
],
isDryRun: false,
maxActivitiesToImport: 100,
tagsDto: [],
user
});
expect(symbolProfileService.add).toHaveBeenCalledTimes(1);
const addedProfile = (symbolProfileService.add as jest.Mock).mock
.calls[0][0];
// Symbol should have been remapped to a UUID (not original)
expect(addedProfile.symbol).not.toBe('MY_ASSET');
});
it('should reuse existing profile when it belongs to the importing user', async () => {
const usersOwnProfile = {
dataSource: DataSource.MANUAL,
symbol: 'MY_ASSET',
userId
};
(symbolProfileService.getSymbolProfiles as jest.Mock).mockResolvedValue([
usersOwnProfile
]);
(marketDataService.updateMany as jest.Mock).mockResolvedValue(undefined);
await importService.import({
accountsWithBalancesDto: [],
activitiesDto: [],
assetProfilesWithMarketDataDto: [
{
currency: 'USD',
dataSource: DataSource.MANUAL,
symbol: 'MY_ASSET',
marketData: []
}
],
isDryRun: false,
maxActivitiesToImport: 100,
tagsDto: [],
user
});
expect(symbolProfileService.add).not.toHaveBeenCalled();
});
it('should create a new profile when no existing profile exists', async () => {
(symbolProfileService.getSymbolProfiles as jest.Mock).mockResolvedValue(
[]
);
(marketDataService.updateMany as jest.Mock).mockResolvedValue(undefined);
await importService.import({
accountsWithBalancesDto: [],
activitiesDto: [],
assetProfilesWithMarketDataDto: [
{
currency: 'USD',
dataSource: DataSource.YAHOO,
symbol: 'AAPL',
marketData: []
}
],
isDryRun: false,
maxActivitiesToImport: 100,
tagsDto: [],
user
});
expect(symbolProfileService.add).toHaveBeenCalledTimes(1);
const addedProfile = (symbolProfileService.add as jest.Mock).mock
.calls[0][0];
expect(addedProfile.symbol).toBe('AAPL');
expect(addedProfile.user).toEqual({ connect: { id: userId } });
});
it('should skip profile creation when isDryRun is true', async () => {
await importService.import({
accountsWithBalancesDto: [],
activitiesDto: [],
assetProfilesWithMarketDataDto: [
{
currency: 'USD',
dataSource: DataSource.YAHOO,
symbol: 'AAPL',
marketData: []
}
],
isDryRun: true,
maxActivitiesToImport: 100,
tagsDto: [],
user
});
expect(symbolProfileService.getSymbolProfiles).not.toHaveBeenCalled();
expect(symbolProfileService.add).not.toHaveBeenCalled();
expect(marketDataService.updateMany).not.toHaveBeenCalled();
});
it('should call marketDataService.updateMany when reusing a global profile', async () => {
const existingGlobalProfile = {
dataSource: DataSource.YAHOO,
symbol: 'VOO',
userId: null
};
(symbolProfileService.getSymbolProfiles as jest.Mock).mockResolvedValue([
existingGlobalProfile
]);
(marketDataService.updateMany as jest.Mock).mockResolvedValue(undefined);
await importService.import({
accountsWithBalancesDto: [],
activitiesDto: [],
assetProfilesWithMarketDataDto: [
{
currency: 'USD',
dataSource: DataSource.YAHOO,
symbol: 'VOO',
marketData: []
}
],
isDryRun: false,
maxActivitiesToImport: 100,
tagsDto: [],
user
});
expect(symbolProfileService.add).not.toHaveBeenCalled();
expect(marketDataService.updateMany).toHaveBeenCalledWith({ data: [] });
});
});
});

9
apps/api/src/app/import/import.service.ts

@ -344,8 +344,13 @@ export class ImportService {
}
);
// If there is no asset profile or if the asset profile belongs to a different user, then create a new asset profile
if (!existingAssetProfile || existingAssetProfile.userId !== user.id) {
// If there is no asset profile or if the asset profile belongs to a different user, then create a new asset profile.
// Global asset profiles (userId is null) are shared and should be reused by everyone.
if (
!existingAssetProfile ||
(existingAssetProfile.userId !== null &&
existingAssetProfile.userId !== user.id)
) {
const assetProfile: CreateAssetProfileDto = omit(
assetProfileWithMarketData,
'marketData'

Loading…
Cancel
Save