Browse Source

Merge a02add2743 into 3d4aa19674

pull/8156/merge
jonathanwxh-cell 15 hours ago
committed by GitHub
parent
commit
8a6c8b1961
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 106
      apps/client/src/app/services/import-activities.service.spec.ts
  2. 8
      apps/client/src/app/services/import-activities.service.ts

106
apps/client/src/app/services/import-activities.service.spec.ts

@ -0,0 +1,106 @@
import { HttpClient } from '@angular/common/http';
import { TestBed } from '@angular/core/testing';
import { of } from 'rxjs';
import { ImportActivitiesService } from './import-activities.service';
jest.mock('uuid', () => ({
v4: () => '00000000-0000-4000-8000-000000000001'
}));
describe('ImportActivitiesService', () => {
let service: ImportActivitiesService;
let post: jest.Mock;
beforeEach(() => {
post = jest.fn().mockImplementation((_url, { activities }) => {
return of({ activities });
});
TestBed.configureTestingModule({
providers: [
ImportActivitiesService,
{ provide: HttpClient, useValue: { post } }
]
});
service = TestBed.inject(ImportActivitiesService);
});
describe('importCsv', () => {
it.each(['Symbol', 'CODE', 'ticker'])(
'preserves leading zeroes in the %s column',
async (symbolColumn) => {
await service.importCsv({
fileContent: `${symbolColumn},currency,date,type,quantity,unitPrice,fee\n"0005",HKD,2026-01-02,BUY,10,3.12,-0.5`,
isDryRun: true,
userAccounts: []
});
expect(post).toHaveBeenCalledWith(
'/api/v1/import?dryRun=true',
expect.objectContaining({
activities: [
expect.objectContaining({
currency: 'HKD',
date: new Date(2026, 0, 2).toISOString(),
fee: 0.5,
quantity: 10,
symbol: '0005',
type: 'BUY',
unitPrice: 3.12
})
]
})
);
}
);
it.each(['639', '000000', '1E3', 'TRUE', 'FALSE', 'AAPL', '0639.HK'])(
'preserves the symbol %s without CSV type coercion',
async (symbol) => {
await service.importCsv({
fileContent: `symbol,currency,date,type,quantity,unitPrice,fee\n${symbol},USD,2026-01-02,BUY,1,10,0`,
userAccounts: []
});
expect(post.mock.calls[0][1].activities[0].symbol).toBe(symbol);
}
);
it.each(['', '""'])(
'rejects an empty symbol (%s) before posting the import',
async (symbol) => {
await expect(
service.importCsv({
fileContent: `symbol,currency,date,type,quantity,unitPrice,fee\n${symbol},USD,2026-01-02,BUY,1,10,0`,
userAccounts: []
})
).rejects.toMatchObject({
message: 'activities.0.symbol is not valid'
});
expect(post).not.toHaveBeenCalled();
}
);
it('preserves numeric names when creating a manual asset profile', async () => {
const result = await service.importCsv({
fileContent:
'symbol,dataSource,currency,date,type,quantity,unitPrice,fee\n0005,MANUAL,HKD,2026-01-02,BUY,10,3.12,0',
userAccounts: []
});
expect(result.assetProfiles).toEqual([
expect.objectContaining({
dataSource: 'MANUAL',
name: '0005',
symbol: '00000000-0000-4000-8000-000000000001'
})
]);
expect(post.mock.calls[0][1].activities[0].symbol).toBe(
result.assetProfiles[0].symbol
);
});
});
});

8
apps/client/src/app/services/import-activities.service.ts

@ -66,7 +66,11 @@ export class ImportActivitiesService {
assetProfiles: CreateAssetProfileWithMarketDataDto[]; assetProfiles: CreateAssetProfileWithMarketDataDto[];
}> { }> {
const content = csvToJson<Record<string, unknown>>(fileContent, { const content = csvToJson<Record<string, unknown>>(fileContent, {
dynamicTyping: true, dynamicTyping: (field: string) => {
return !ImportActivitiesService.SYMBOL_KEYS.includes(
field.toLowerCase()
);
},
header: true, header: true,
skipEmptyLines: true skipEmptyLines: true
}).data; }).data;
@ -420,7 +424,7 @@ export class ImportActivitiesService {
for (const key of ImportActivitiesService.SYMBOL_KEYS) { for (const key of ImportActivitiesService.SYMBOL_KEYS) {
const value = item[key]; const value = item[key];
if (isNumber(value) || isString(value)) { if (isNumber(value) || (isString(value) && value.length > 0)) {
return String(value); return String(value);
} }
} }

Loading…
Cancel
Save