Browse Source

Simplify recent market data gathering

task/simplify-recent-market-data-gathering
Thomas Kaul 5 days ago
parent
commit
c77c8d13ea
  1. 30
      apps/api/jest-environment-tz.js
  2. 70
      apps/api/src/services/queues/data-gathering/data-gathering.processor.time-zone.spec.ts
  3. 48
      apps/api/src/services/queues/data-gathering/data-gathering.service.spec.ts

30
apps/api/jest-environment-tz.js

@ -0,0 +1,30 @@
const NodeEnvironment = require('jest-environment-node').TestEnvironment;
// Jest gives each test file a copy of `process`, so a test cannot change the
// time zone at run time. This environment sets `TZ` on the real process of the
// worker, which resets the internal date cache of Node.
class TimeZoneEnvironment extends NodeEnvironment {
constructor(config, context) {
super(config, context);
this.timeZone = config.projectConfig.testEnvironmentOptions?.timeZone;
}
async setup() {
this.previousTimeZone = process.env.TZ;
if (this.timeZone) {
process.env.TZ = this.timeZone;
}
await super.setup();
}
async teardown() {
await super.teardown();
process.env.TZ = this.previousTimeZone;
}
}
module.exports = TimeZoneEnvironment;

70
apps/api/src/services/queues/data-gathering/data-gathering.processor.time-zone.spec.ts

@ -0,0 +1,70 @@
/**
* @jest-environment <rootDir>/jest-environment-tz.js
* @jest-environment-options {"timeZone": "America/New_York"}
*/
import { DataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
import { parseDate } from '@ghostfolio/common/helper';
import { Job } from 'bull';
import { DataGatheringProcessor } from './data-gathering.processor';
describe('DataGatheringProcessor in a time zone behind UTC', () => {
let dataGatheringProcessor: DataGatheringProcessor;
let dataProviderService: { getHistoricalRaw: jest.Mock };
let marketDataService: { replaceForSymbol: jest.Mock; updateMany: jest.Mock };
beforeAll(() => {
// 2026-08-23 21:30 in New York, but already 2026-08-24 in UTC
jest.useFakeTimers().setSystemTime(new Date('2026-08-24T01:30:00.000Z'));
});
beforeEach(() => {
dataProviderService = {
getHistoricalRaw: jest.fn().mockResolvedValue({
'COINGECKO-bitcoin': {
'2026-08-21': { marketPrice: 5 },
'2026-08-22': { marketPrice: 6 },
'2026-08-23': { marketPrice: 7 }
}
})
};
marketDataService = {
replaceForSymbol: jest.fn(),
updateMany: jest.fn()
};
dataGatheringProcessor = new DataGatheringProcessor(
null,
dataProviderService as any,
marketDataService as any,
null
);
});
afterAll(() => {
jest.useRealTimers();
});
it('gathers up to the last complete UTC day', async () => {
await dataGatheringProcessor.gatherHistoricalMarketData({
data: {
dataSource: 'COINGECKO',
symbol: 'bitcoin',
date: parseDate('2026-08-21').toISOString()
}
} as unknown as Job<DataGatheringItem>);
const { data } = marketDataService.updateMany.mock.calls[0][0];
expect(
data.map(({ date }) => {
return date;
})
).toEqual([
new Date('2026-08-21T00:00:00.000Z'),
new Date('2026-08-22T00:00:00.000Z'),
new Date('2026-08-23T00:00:00.000Z')
]);
});
});

48
apps/api/src/services/queues/data-gathering/data-gathering.service.spec.ts

@ -60,6 +60,54 @@ describe('DataGatheringService', () => {
);
});
it('includes the Friday close when it runs on Saturday', async () => {
jest.useFakeTimers().setSystemTime(new Date('2026-08-22T14:00:00.000Z'));
await dataGatheringService[
'getAssetProfileIdentifiersWithRecentMarketData'
]();
expect(prismaService.marketData.groupBy).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({
date: { gte: new Date('2026-08-21T00:00:00.000Z') }
})
})
);
});
it('excludes the Friday close when it runs on Sunday', async () => {
jest.useFakeTimers().setSystemTime(new Date('2026-08-23T14:00:00.000Z'));
await dataGatheringService[
'getAssetProfileIdentifiersWithRecentMarketData'
]();
expect(prismaService.marketData.groupBy).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({
date: { gte: new Date('2026-08-22T00:00:00.000Z') }
})
})
);
});
it('excludes the Friday close when it runs on Monday', async () => {
jest.useFakeTimers().setSystemTime(new Date('2026-08-24T14:00:00.000Z'));
await dataGatheringService[
'getAssetProfileIdentifiersWithRecentMarketData'
]();
expect(prismaService.marketData.groupBy).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({
date: { gte: new Date('2026-08-23T00:00:00.000Z') }
})
})
);
});
it('maps the query result to asset profile identifiers', async () => {
prismaService.marketData.groupBy.mockResolvedValue([
{ dataSource: 'COINGECKO', symbol: 'bitcoin' },

Loading…
Cancel
Save