Browse Source

Merge 77c82ebb52 into e1d343fb70

pull/6294/merge
Varun Chawla 12 hours ago
committed by GitHub
parent
commit
ef0702878c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 210
      apps/api/src/app/import/import.service.spec.ts
  3. 10
      apps/api/src/app/import/import.service.ts

1
CHANGELOG.md

@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Fixed the missing `valueInBaseCurrency` in the import endpoint (`POST api/v1/import`)
- Fixed the accounts of the assistant for the impersonation mode - Fixed the accounts of the assistant for the impersonation mode
- Fixed the tags of the assistant for the impersonation mode - Fixed the tags of the assistant for the impersonation mode

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

@ -0,0 +1,210 @@
import { ImportService } from './import.service';
describe('ImportService', () => {
let importService: ImportService;
let exchangeRateDataServiceMock: { toCurrencyAtDate: jest.Mock };
let dataProviderServiceMock: {
getDataSourceForImport: jest.Mock;
getDataSources: jest.Mock;
getAssetProfiles: jest.Mock;
};
let orderServiceMock: { getOrders: jest.Mock };
let accountServiceMock: { getAccounts: jest.Mock };
let tagServiceMock: { getTagsForUser: jest.Mock };
let configurationServiceMock: { get: jest.Mock };
beforeEach(() => {
exchangeRateDataServiceMock = {
toCurrencyAtDate: jest.fn()
};
dataProviderServiceMock = {
getDataSourceForImport: jest.fn().mockReturnValue('YAHOO'),
getDataSources: jest.fn().mockResolvedValue(['MANUAL', 'YAHOO']),
getAssetProfiles: jest.fn()
};
orderServiceMock = {
getOrders: jest.fn().mockResolvedValue({ activities: [], count: 0 })
};
accountServiceMock = {
getAccounts: jest.fn().mockResolvedValue([])
};
tagServiceMock = {
getTagsForUser: jest.fn().mockResolvedValue([])
};
configurationServiceMock = {
get: jest.fn().mockImplementation((key: string) => {
if (key === 'ENABLE_FEATURE_SUBSCRIPTION') {
return false;
}
if (key === 'MAX_ACTIVITIES_TO_IMPORT') {
return Number.MAX_SAFE_INTEGER;
}
return undefined;
})
};
importService = new ImportService(
accountServiceMock as any,
null,
configurationServiceMock as any,
null,
dataProviderServiceMock as any,
exchangeRateDataServiceMock as any,
null,
orderServiceMock as any,
null,
null,
null,
tagServiceMock as any
);
});
describe('import', () => {
it('should include valueInBaseCurrency for a dry-run FEE activity', async () => {
const expectedValueInBaseCurrency = 15;
exchangeRateDataServiceMock.toCurrencyAtDate.mockResolvedValue(
expectedValueInBaseCurrency
);
const activities = await importService.import({
isDryRun: true,
maxActivitiesToImport: Number.MAX_SAFE_INTEGER,
accountsWithBalancesDto: [],
activitiesDto: [
{
currency: 'USD',
dataSource: 'MANUAL',
date: '2024-01-15T00:00:00.000Z',
fee: 0,
quantity: 1,
symbol: 'Account Opening Fee',
type: 'FEE',
unitPrice: 15
} as any
],
assetProfilesWithMarketDataDto: [],
tagsDto: [],
user: {
id: 'test-user-id',
settings: { settings: { baseCurrency: 'USD' } }
} as any
});
expect(activities).toHaveLength(1);
expect(activities[0].valueInBaseCurrency).toBe(
expectedValueInBaseCurrency
);
expect(activities[0].value).toBe(15);
expect(exchangeRateDataServiceMock.toCurrencyAtDate).toHaveBeenCalledWith(
15,
'USD',
'USD',
expect.any(Date)
);
});
it('should convert valueInBaseCurrency using the correct currencies', async () => {
exchangeRateDataServiceMock.toCurrencyAtDate.mockResolvedValue(1350.5);
dataProviderServiceMock.getAssetProfiles.mockResolvedValue({
MSFT: {
currency: 'USD',
dataSource: 'YAHOO',
name: 'Microsoft Corporation',
symbol: 'MSFT'
}
});
const activities = await importService.import({
isDryRun: true,
maxActivitiesToImport: Number.MAX_SAFE_INTEGER,
accountsWithBalancesDto: [],
activitiesDto: [
{
currency: 'USD',
dataSource: 'YAHOO',
date: '2024-01-15T00:00:00.000Z',
fee: 19,
quantity: 5,
symbol: 'MSFT',
type: 'BUY',
unitPrice: 298.58
} as any
],
assetProfilesWithMarketDataDto: [],
tagsDto: [],
user: {
id: 'test-user-id',
settings: { settings: { baseCurrency: 'EUR' } }
} as any
});
expect(activities).toHaveLength(1);
expect(activities[0].valueInBaseCurrency).toBe(1350.5);
expect(activities[0].value).toBeCloseTo(1492.9);
expect(exchangeRateDataServiceMock.toCurrencyAtDate).toHaveBeenCalledWith(
expect.closeTo(1492.9),
'USD',
'EUR',
expect.any(Date)
);
});
it('should fall back to asset profile currency when activity currency is not set', async () => {
exchangeRateDataServiceMock.toCurrencyAtDate.mockResolvedValue(450);
dataProviderServiceMock.getAssetProfiles.mockResolvedValue({
AAPL: {
currency: 'EUR',
dataSource: 'YAHOO',
name: 'Apple Inc.',
symbol: 'AAPL'
}
});
const activities = await importService.import({
isDryRun: true,
maxActivitiesToImport: Number.MAX_SAFE_INTEGER,
accountsWithBalancesDto: [],
activitiesDto: [
{
currency: undefined,
dataSource: 'YAHOO',
date: '2024-06-01T00:00:00.000Z',
fee: 0,
quantity: 2,
symbol: 'AAPL',
type: 'BUY',
unitPrice: 250
} as any
],
assetProfilesWithMarketDataDto: [],
tagsDto: [],
user: {
id: 'test-user-id',
settings: { settings: { baseCurrency: 'CHF' } }
} as any
});
expect(activities).toHaveLength(1);
expect(activities[0].valueInBaseCurrency).toBe(450);
// Should fall back to asset profile currency (EUR) when activity currency is undefined
expect(exchangeRateDataServiceMock.toCurrencyAtDate).toHaveBeenCalledWith(
500,
'EUR',
'CHF',
expect.any(Date)
);
});
});
});

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

@ -5,6 +5,7 @@ import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.servic
import { ApiService } from '@ghostfolio/api/services/api/api.service'; import { ApiService } from '@ghostfolio/api/services/api/api.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
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 { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.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 { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service';
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
@ -48,6 +49,7 @@ export class ImportService {
private readonly configurationService: ConfigurationService, private readonly configurationService: ConfigurationService,
private readonly dataGatheringService: DataGatheringService, private readonly dataGatheringService: DataGatheringService,
private readonly dataProviderService: DataProviderService, private readonly dataProviderService: DataProviderService,
private readonly exchangeRateDataService: ExchangeRateDataService,
private readonly marketDataService: MarketDataService, private readonly marketDataService: MarketDataService,
private readonly orderService: OrderService, private readonly orderService: OrderService,
private readonly platformService: PlatformService, private readonly platformService: PlatformService,
@ -590,10 +592,18 @@ export class ImportService {
const value = new Big(quantity).mul(unitPrice).toNumber(); const value = new Big(quantity).mul(unitPrice).toNumber();
const valueInBaseCurrency = this.exchangeRateDataService.toCurrencyAtDate(
value,
currency ?? assetProfile.currency,
userCurrency,
date
);
activities.push({ activities.push({
...order, ...order,
error, error,
value, value,
valueInBaseCurrency: await valueInBaseCurrency,
// @ts-ignore // @ts-ignore
SymbolProfile: assetProfile SymbolProfile: assetProfile
}); });

Loading…
Cancel
Save