This commit is contained in:
@@ -1,42 +1,51 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { HistoryScheduler, type HistoryCaptureStore } from './scheduler.js';
|
||||
import { testBatch } from './fixtures.js';
|
||||
function fixture(enabledAt='2026-10-19T13:00:00+08:00') {
|
||||
|
||||
function fixture(enabledAt='2026-10-30T13:00:00+08:00') {
|
||||
const captures:string[]=[],missed:string[]=[],failed:string[]=[];
|
||||
let activates=0;
|
||||
const store:HistoryCaptureStore={
|
||||
activate:async()=>{activates++;return{enabledAt};},batches:async()=>[],
|
||||
activate:async()=>{activates++;return{enabledAt};},dailyBatches:async()=>[],
|
||||
capture:async(month)=>{captures.push(month);return'captured';},
|
||||
recordOutcome:async(month,status)=>{(status==='FAILED'?failed:missed).push(month);},
|
||||
};
|
||||
return {store,captures,missed,failed,activates:()=>activates};
|
||||
}
|
||||
test('scheduler captures only in month-end 17:00 minute and activation is persisted once',async()=>{
|
||||
const f=fixture();let date=new Date('2026-10-31T16:59:59+08:00');
|
||||
test('scheduler captures only in daily 23:55 minute and activation is persisted once',async()=>{
|
||||
const f=fixture();let date=new Date('2026-10-31T23:54:59+08:00');
|
||||
const scheduler=new HistoryScheduler(f.store,()=>date,()=>{});
|
||||
await scheduler.tick();assert.deepEqual(f.captures,[]);
|
||||
date=new Date('2026-10-31T17:00:00+08:00');await scheduler.tick();assert.deepEqual(f.captures,['2026-10']);assert.equal(f.activates(),1);
|
||||
date=new Date('2026-10-31T23:55:00+08:00');await scheduler.tick();assert.deepEqual(f.captures,['2026-10-31']);assert.equal(f.activates(),1);
|
||||
});
|
||||
test('restart records missed eligible months without backfilling old live data or pre-activation periods',async()=>{
|
||||
const f=fixture();const scheduler=new HistoryScheduler(f.store,()=>new Date('2027-01-15T08:00:00+08:00'),()=>{});
|
||||
await scheduler.tick();assert.deepEqual(f.captures,[]);assert.deepEqual(f.missed,['2026-10','2026-11','2026-12']);
|
||||
const late=fixture('2026-10-31T17:00:10+08:00');await new HistoryScheduler(late.store,()=>new Date('2026-10-31T17:00:20+08:00'),()=>{}).tick();
|
||||
test('restart records missed eligible days without backfilling old live data or pre-activation periods',async()=>{
|
||||
const f=fixture();const scheduler=new HistoryScheduler(f.store,()=>new Date('2026-11-02T08:00:00+08:00'),()=>{});
|
||||
await scheduler.tick();assert.deepEqual(f.captures,[]);assert.deepEqual(f.missed,['2026-10-30','2026-10-31','2026-11-01']);
|
||||
const late=fixture('2026-10-31T23:55:10+08:00');await new HistoryScheduler(late.store,()=>new Date('2026-10-31T23:55:20+08:00'),()=>{}).tick();
|
||||
assert.deepEqual(late.captures,[]);assert.deepEqual(late.missed,[]);
|
||||
});
|
||||
test('completed December skips duplicate annual capture; failures stay marked without late retries',async()=>{
|
||||
const f=fixture();f.store.batches=async()=>[testBatch('2026-10')];
|
||||
test('completed days skip capture and existing failures are preserved after the window',async()=>{
|
||||
const f=fixture();f.store.dailyBatches=async()=>[{day:'2026-10-30',status:'COMPLETE'}];
|
||||
f.store.capture=async()=>{throw new Error('source unavailable');};
|
||||
await new HistoryScheduler(f.store,()=>new Date('2026-12-31T17:00:20+08:00'),()=>{}).tick();
|
||||
assert.deepEqual(f.missed,['2026-11']);assert.deepEqual(f.failed,['2026-12']);
|
||||
const done=fixture('2026-12-01T00:00+08:00');done.store.batches=async()=>[testBatch('2026-12')];
|
||||
await new HistoryScheduler(done.store,()=>new Date('2026-12-31T17:00:20+08:00'),()=>{}).tick();assert.deepEqual(done.captures,[]);
|
||||
await new HistoryScheduler(f.store,()=>new Date('2026-10-31T23:55:20+08:00'),()=>{}).tick();
|
||||
assert.deepEqual(f.missed,[]);assert.deepEqual(f.failed,['2026-10-31']);
|
||||
f.store.dailyBatches=async()=>[{day:'2026-10-30',status:'COMPLETE'},{day:'2026-10-31',status:'FAILED'}];
|
||||
await new HistoryScheduler(f.store,()=>new Date('2026-10-31T23:56:00+08:00'),()=>{}).tick();
|
||||
assert.deepEqual(f.failed,['2026-10-31']);assert.deepEqual(f.missed,[]);
|
||||
});
|
||||
test('concurrent ticks and repeated start do not register duplicate jobs',async()=>{
|
||||
const f=fixture();let release!:()=>void;
|
||||
f.store.capture=async()=>{f.captures.push('2026-10');await new Promise<void>(r=>{release=r;});return'captured';};
|
||||
const scheduler=new HistoryScheduler(f.store,()=>new Date('2026-10-31T17:00:20+08:00'),()=>{});
|
||||
f.store.capture=async()=>{f.captures.push('2026-10-31');await new Promise<void>(r=>{release=r;});return'captured';};
|
||||
const scheduler=new HistoryScheduler(f.store,()=>new Date('2026-10-31T23:55:20+08:00'),()=>{});
|
||||
const stop=scheduler.start();scheduler.start();
|
||||
await new Promise<void>(r=>setImmediate(r));await scheduler.tick();assert.equal(f.captures.length,1);
|
||||
stop();release();await new Promise<void>(r=>setImmediate(r));
|
||||
});
|
||||
|
||||
test('consecutive dates in one month and across month boundary capture independently',async()=>{
|
||||
const f=fixture();
|
||||
for(const day of ['2026-10-30','2026-10-31','2026-11-01']) {
|
||||
await new HistoryScheduler(f.store,()=>new Date(`${day}T23:55:00+08:00`),()=>{}).tick();
|
||||
}
|
||||
assert.deepEqual(f.captures,['2026-10-30','2026-10-31','2026-11-01']);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user