Persist notification email batches with leased idempotent delivery
This commit is contained in:
@@ -11,7 +11,7 @@ test('delivery failure retains pending items and batches acknowledge only includ
|
||||
const rows=Array.from({length:28},(_,i)=>({...future,id:'t-'+i,ikn:'2026/'+i,baslik:'Laboratuvar '+i}));
|
||||
const subscription={id:1,email:'qa@example.test',initialized:true,sonGorulenler:[]};
|
||||
const sent=[],messages=[];let fail=true;
|
||||
const dependencies={now,configured:()=>true,telegramReady:()=>false,readSubscriptions:()=>[subscription],saveSent:(id,ids)=>{sent.push(ids);subscription.sonGorulenler.push(...ids);},deliverEmail:async message=>{if(fail)throw new Error('provider unavailable');messages.push(message);}};
|
||||
const dependencies={now,configured:()=>true,telegramReady:()=>false,readSubscriptions:()=>[subscription],reserve:(id,payload,ids)=>({id:'test-delivery',payload,tenderIds:ids}),accepted:delivery=>{sent.push(delivery.tenderIds);subscription.sonGorulenler.push(...delivery.tenderIds);},failed:()=>{},deliverEmail:async message=>{if(fail)throw new Error('provider unavailable');messages.push(message);return {id:'provider-test'};}};
|
||||
assert.equal((await bildirimleriIsle(async()=>({tenders:rows}),{},dependencies)).hata,1);assert.equal(sent.length,0);
|
||||
fail=false;assert.equal((await bildirimleriIsle(async()=>({tenders:rows}),{},dependencies)).gonderilen,1);assert.equal(sent[0].length,25);assert.match(messages[0].html,/>Laboratuvar 24</);assert.doesNotMatch(messages[0].html,/>Laboratuvar 25</);
|
||||
await bildirimleriIsle(async()=>({tenders:rows}),{},dependencies);assert.equal(sent[1].length,3);assert.match(messages[1].html,/>Laboratuvar 27</);
|
||||
|
||||
@@ -34,3 +34,38 @@ test('baseline does not claim delivery and seen history is not truncated at 400'
|
||||
subscriptions.markSent(row.id,['new-1','old-1']);saved=subscriptions.list('history')[0];
|
||||
assert.equal(saved.sonGorulenler.length,451);assert.ok(saved.sonGonderim);assert.ok(saved.sonGorulenler.includes('old-0'));
|
||||
});
|
||||
|
||||
test('outbox pins payload and key, fences workers, and atomically acknowledges only its tender ids',()=>{
|
||||
const sub=subscriptions.subscribe('outbox',{konular:['Laboratuvar']})[0];subscriptions.markBaseline(sub.id,[]);
|
||||
const first=subscriptions.reserveDelivery(sub.id,{to:'outbox',html:'source A',from:'sender A'},['a'],{clock:1000});
|
||||
assert.equal(subscriptions.reserveDelivery(sub.id,{html:'source B'},['b'],{clock:1001}),null);
|
||||
const reclaimed=subscriptions.reserveDelivery(sub.id,{html:'source B'},['b'],{clock:122000});
|
||||
assert.equal(reclaimed.id,first.id);assert.notEqual(reclaimed.lease,first.lease);assert.equal(reclaimed.payload.html,'source A');
|
||||
assert.throws(()=>subscriptions.acceptDelivery(first,'late-provider'),/artık etkin/);
|
||||
assert.equal(subscriptions.failDelivery(first,new Error('late failure')),false);
|
||||
subscriptions.failDelivery(reclaimed,new Error('provider response lost'));
|
||||
const retry=subscriptions.reserveDelivery(sub.id,null,[],{clock:123000});
|
||||
assert.equal(retry.id,first.id);subscriptions.acceptDelivery(retry,'provider-id');
|
||||
const saved=subscriptions.list('outbox')[0];assert.deepEqual(saved.sonGorulenler,['a']);assert.equal(saved.deliveryPending,false);
|
||||
assert.equal(subscriptions.reserveDelivery(sub.id,{html:'stale snapshot'},['a'],{clock:124000}),null);
|
||||
});
|
||||
test('uncertain outbox past safe retry window blocks replay, and unsubscribe cancels pending delivery',()=>{
|
||||
const sub=subscriptions.subscribe('uncertain',{konular:['Test']})[0];
|
||||
const first=subscriptions.reserveDelivery(sub.id,{html:'A'},['a'],{clock:1000});
|
||||
subscriptions.failDelivery(first,'unknown');
|
||||
assert.deepEqual(subscriptions.reserveDelivery(sub.id,{html:'B'},['b'],{clock:1000+23*3600000}),{blocked:true});
|
||||
assert.equal(subscriptions.list('uncertain')[0].deliveryStatus.state,'uncertain');
|
||||
assert.equal(subscriptions.remove(sub.id,'uncertain'),true);
|
||||
assert.equal(subscriptions.reserveDelivery(sub.id,{html:'B'},['b']),null);
|
||||
assert.throws(()=>subscriptions.acceptDelivery(first,'late'),/artık etkin/);
|
||||
});
|
||||
test('notifier resumes exact saved email after provider failure even when current source changes',async()=>{
|
||||
const {bildirimleriIsle}=await import('../server/notifier.mjs');
|
||||
const sub=subscriptions.subscribe('integration',{konular:['Lab']})[0];subscriptions.markBaseline(sub.id,[]);
|
||||
const tender={id:'integrated',tarih:'10.09.2026',saat:'10:00',baslik:'Lab source A'};let fail=true;const calls=[];
|
||||
const dependencies={now:()=>new Date('2026-09-06T00:00:00Z'),configured:()=>true,telegramReady:()=>false,readSubscriptions:()=>subscriptions.list('integration'),deliverEmail:async mail=>{calls.push(mail);if(fail)throw new Error('unknown provider result');return {id:'integrated-provider'};}};
|
||||
await bildirimleriIsle(async()=>({tenders:[tender]}),{},dependencies);fail=false;
|
||||
await bildirimleriIsle(async()=>({tenders:[{...tender,baslik:'Lab source B'}]}),{},dependencies);
|
||||
assert.equal(calls.length,2);assert.deepEqual(calls[0],calls[1]);assert.ok(calls[0].idempotencyKey.startsWith('notify/'));
|
||||
assert.deepEqual(subscriptions.list('integration')[0].sonGorulenler,['integrated']);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user