Backend security / security (push) Successful in 8s
Backend verify / verify (push) Successful in 11m9s
Conventional commits / commits (pull_request) Successful in 28s
Backend security / security (pull_request) Successful in 7s
Backend verify / verify (pull_request) Successful in 10m38s
Resolve A01-A10 and C01-C07 from the backend audit. Preserve auth failures, forced candle reconciliation and forecast freshness. Validate MOEX responses and nested HTTP errors; remove unused legacy paths. Verified: 209 unit tests, 262 SQLite e2e tests on Linux, OpenAPI and durability. Record the remaining macOS native SQLite abort in the audit.
114 lines
3.4 KiB
TypeScript
114 lines
3.4 KiB
TypeScript
jest.mock('../infrastructure/candle.repository', () => ({
|
|
CandleRepository: class {},
|
|
}));
|
|
jest.mock('./catalog-enrichment.service', () => ({
|
|
CatalogEnrichmentService: class {},
|
|
}));
|
|
jest.mock('../../../../integrations/tbank/tbank.api', () => ({
|
|
TbankAccessService: class {},
|
|
TbankClient: class {},
|
|
}));
|
|
|
|
import { CandleService } from './candle.service';
|
|
|
|
describe('CandleService forced demand', () => {
|
|
it.each([false, true])(
|
|
'reconciles the full range through the latest forced timestamp while force=%s is loading',
|
|
async (initialForce) => {
|
|
let entered = () => {};
|
|
let release = () => {};
|
|
let complete = () => {};
|
|
const started = new Promise<void>((resolve) => (entered = resolve));
|
|
const blocked = new Promise<void>((resolve) => (release = resolve));
|
|
const finished = new Promise<void>((resolve) => (complete = resolve));
|
|
let completed = 0;
|
|
const work = { id: 'work', attemptId: 'attempt' };
|
|
const repository = {
|
|
reserve: jest
|
|
.fn()
|
|
.mockResolvedValueOnce({ work, launch: true })
|
|
.mockResolvedValueOnce({
|
|
work: { id: 'other', attemptId: 'other-attempt' },
|
|
launch: true,
|
|
})
|
|
.mockResolvedValue({ work, launch: false }),
|
|
read: jest.fn().mockResolvedValue({
|
|
coverage: [
|
|
{
|
|
from: new Date('2026-01-01Z'),
|
|
to: new Date('2026-02-01Z'),
|
|
isComplete: true,
|
|
fetchedAt: new Date('2026-02-01Z'),
|
|
},
|
|
],
|
|
}),
|
|
stage: jest.fn().mockResolvedValue(true),
|
|
publish: jest.fn((_attempt, _intent, done = true) => {
|
|
if (done && ++completed === 2) complete();
|
|
return Promise.resolve(true);
|
|
}),
|
|
fail: jest.fn(),
|
|
};
|
|
const sources = {
|
|
ensureDetail: jest.fn().mockResolvedValue({
|
|
first_1dayCandleDate: {
|
|
seconds: String(Date.parse('2026-01-01Z') / 1000),
|
|
},
|
|
}),
|
|
};
|
|
const upstream = { getCandles: jest.fn().mockResolvedValue([]) };
|
|
upstream.getCandles.mockImplementationOnce(async () => {
|
|
entered();
|
|
await blocked;
|
|
return [];
|
|
});
|
|
const service = new CandleService(
|
|
repository as never,
|
|
sources as never,
|
|
{ tokenForAttempt: jest.fn().mockResolvedValue('token') } as never,
|
|
upstream as never,
|
|
{ event: jest.fn() } as never,
|
|
);
|
|
const input = {
|
|
uid: 'uid',
|
|
instrumentType: 'share' as const,
|
|
period: 'all' as const,
|
|
};
|
|
await service.request(
|
|
'owner',
|
|
'revision',
|
|
input,
|
|
new Date('2026-02-02T12:00:00Z'),
|
|
initialForce,
|
|
2,
|
|
);
|
|
await started;
|
|
const latest = new Date('2026-02-02T13:00:00Z');
|
|
await service.request(
|
|
'owner',
|
|
'revision',
|
|
{ ...input, uid: 'other' },
|
|
latest,
|
|
false,
|
|
1,
|
|
);
|
|
expect(
|
|
await service.request('owner', 'revision', input, latest, true, 0),
|
|
).toBe('work');
|
|
release();
|
|
await finished;
|
|
|
|
expect(upstream.getCandles).toHaveBeenCalledWith(
|
|
'token',
|
|
'uid',
|
|
new Date('2026-01-01Z'),
|
|
latest,
|
|
);
|
|
expect(repository.fail).not.toHaveBeenCalled();
|
|
expect(
|
|
upstream.getCandles.mock.calls.map((call: unknown[]) => call[1]),
|
|
).toEqual(['uid', 'uid', 'other']);
|
|
},
|
|
);
|
|
});
|