@@ -198,4 +198,112 @@ describe('TaskRunnerV2 — execution failure notification (#415)', () => {
198198 assert . ok ( failMsg . content . includes ( 'kaboom' ) ) ;
199199 runner . stop ( ) ;
200200 } ) ;
201+
202+ it ( 'RUN_DELIVERED triggers notifyTaskSucceeded via onItemOutcome' , async ( ) => {
203+ const Database = ( await import ( 'better-sqlite3' ) ) . default ;
204+ const db = new Database ( ':memory:' ) ;
205+ const { applyMigrations } = await import ( '../../dist/domains/memory/schema.js' ) ;
206+ const { RunLedger } = await import ( '../../dist/infrastructure/scheduler/RunLedger.js' ) ;
207+ const { DynamicTaskStore } = await import ( '../../dist/infrastructure/scheduler/DynamicTaskStore.js' ) ;
208+ const { TaskRunnerV2 } = await import ( '../../dist/infrastructure/scheduler/TaskRunnerV2.js' ) ;
209+ applyMigrations ( db ) ;
210+ const ledger = new RunLedger ( db ) ;
211+ const dynamicTaskStore = new DynamicTaskStore ( db ) ;
212+ const deliverCalls = [ ] ;
213+ const mockDeliver = async ( opts ) => {
214+ deliverCalls . push ( opts ) ;
215+ return 'msg-1' ;
216+ } ;
217+ const noop = ( ) => { } ;
218+ const runner = new TaskRunnerV2 ( {
219+ logger : { info : noop , error : noop } ,
220+ ledger,
221+ dynamicTaskStore,
222+ deliver : mockDeliver ,
223+ } ) ;
224+
225+ dynamicTaskStore . insert ( {
226+ id : 'dyn-ok-1' ,
227+ templateId : 'reminder' ,
228+ trigger : { type : 'interval' , ms : 999999 } ,
229+ params : { message : 'test' , triggerUserId : 'user-42' } ,
230+ display : { label : '成功任务' , category : 'system' } ,
231+ deliveryThreadId : 'thread-ok' ,
232+ enabled : true ,
233+ createdBy : 'opus' ,
234+ createdAt : new Date ( ) . toISOString ( ) ,
235+ } ) ;
236+
237+ runner . registerDynamic (
238+ {
239+ id : 'dyn-ok-1' ,
240+ profile : 'awareness' ,
241+ trigger : { type : 'interval' , ms : 999999 } ,
242+ admission : {
243+ gate : async ( ) => ( { run : true , workItems : [ { signal : 'go' , subjectKey : 'k' } ] } ) ,
244+ } ,
245+ run : {
246+ overlap : 'skip' ,
247+ timeoutMs : 5000 ,
248+ execute : async ( ) => ( { delivered : true } ) ,
249+ } ,
250+ state : { runLedger : 'sqlite' } ,
251+ outcome : { whenNoSignal : 'drop' } ,
252+ enabled : ( ) => true ,
253+ } ,
254+ 'dyn-ok-1' ,
255+ ) ;
256+
257+ await runner . triggerNow ( 'dyn-ok-1' ) ;
258+ await new Promise ( ( r ) => setTimeout ( r , 50 ) ) ;
259+
260+ const successMsg = deliverCalls . find ( ( c ) => c . content . includes ( '执行完成' ) ) ;
261+ assert . ok ( successMsg , 'should contain success notification' ) ;
262+ assert . equal ( successMsg . threadId , 'thread-ok' ) ;
263+ assert . ok ( successMsg . content . includes ( '下次执行时间' ) , 'recurring task should include next fire time' ) ;
264+ runner . stop ( ) ;
265+ } ) ;
266+ } ) ;
267+
268+ describe ( 'schedule-notify: notifyTaskSucceeded' , ( ) => {
269+ const makeDef2 = ( overrides = { } ) => ( {
270+ id : 'dyn-test-1' ,
271+ templateId : 'reminder' ,
272+ trigger : { type : 'once' , fireAt : Date . now ( ) + 60_000 } ,
273+ params : { message : 'test' , triggerUserId : 'user-42' } ,
274+ display : { label : '测试提醒' , category : 'system' } ,
275+ deliveryThreadId : 'thread-xyz' ,
276+ enabled : true ,
277+ createdBy : 'opus' ,
278+ createdAt : new Date ( ) . toISOString ( ) ,
279+ ...overrides ,
280+ } ) ;
281+
282+ it ( 'recurring task includes next fire time' , async ( ) => {
283+ const { notifyTaskSucceeded } = await import ( '../../dist/infrastructure/scheduler/schedule-notify.js' ) ;
284+ const calls = [ ] ;
285+ const mockDeliver = async ( opts ) => {
286+ calls . push ( opts ) ;
287+ return 'msg-1' ;
288+ } ;
289+ notifyTaskSucceeded ( mockDeliver , makeDef2 ( { trigger : { type : 'interval' , ms : 60000 } } ) ) ;
290+ await new Promise ( ( r ) => setTimeout ( r , 20 ) ) ;
291+ assert . equal ( calls . length , 1 ) ;
292+ assert . ok ( calls [ 0 ] . content . includes ( '本次执行完成' ) ) ;
293+ assert . ok ( calls [ 0 ] . content . includes ( '下次执行时间' ) ) ;
294+ } ) ;
295+
296+ it ( 'once task says task has ended' , async ( ) => {
297+ const { notifyTaskSucceeded } = await import ( '../../dist/infrastructure/scheduler/schedule-notify.js' ) ;
298+ const calls = [ ] ;
299+ const mockDeliver = async ( opts ) => {
300+ calls . push ( opts ) ;
301+ return 'msg-1' ;
302+ } ;
303+ notifyTaskSucceeded ( mockDeliver , makeDef2 ( { trigger : { type : 'once' , fireAt : Date . now ( ) } } ) ) ;
304+ await new Promise ( ( r ) => setTimeout ( r , 20 ) ) ;
305+ assert . equal ( calls . length , 1 ) ;
306+ assert . ok ( calls [ 0 ] . content . includes ( '已执行完成' ) ) ;
307+ assert . ok ( calls [ 0 ] . content . includes ( '自动结束' ) ) ;
308+ } ) ;
201309} ) ;
0 commit comments