@@ -5,6 +5,20 @@ import { getSessionBindingService } from "openclaw/plugin-sdk/conversation-runti
55import { resolveStateDir } from "openclaw/plugin-sdk/state-paths" ;
66import { afterEach , beforeEach , describe , expect , it , vi } from "vitest" ;
77import { importFreshModule } from "../../../test/helpers/import-fresh.js" ;
8+
9+ const writeJsonFileAtomicallyMock = vi . hoisted ( ( ) => vi . fn ( ) ) ;
10+
11+ vi . mock ( "openclaw/plugin-sdk/json-store" , async ( ) => {
12+ const actual = await vi . importActual < typeof import ( "openclaw/plugin-sdk/json-store" ) > (
13+ "openclaw/plugin-sdk/json-store" ,
14+ ) ;
15+ writeJsonFileAtomicallyMock . mockImplementation ( actual . writeJsonFileAtomically ) ;
16+ return {
17+ ...actual ,
18+ writeJsonFileAtomically : writeJsonFileAtomicallyMock ,
19+ } ;
20+ } ) ;
21+
822import {
923 __testing ,
1024 createTelegramThreadBindingManager ,
@@ -16,6 +30,7 @@ describe("telegram thread bindings", () => {
1630 let stateDirOverride : string | undefined ;
1731
1832 beforeEach ( async ( ) => {
33+ writeJsonFileAtomicallyMock . mockClear ( ) ;
1934 await __testing . resetTelegramThreadBindingsForTests ( ) ;
2035 } ) ;
2136
@@ -313,4 +328,43 @@ describe("telegram thread bindings", () => {
313328 } ;
314329 expect ( persisted . bindings ?. [ 0 ] ?. idleTimeoutMs ) . toBe ( 90_000 ) ;
315330 } ) ;
331+
332+ it ( "does not leak unhandled rejections when a persist write fails" , async ( ) => {
333+ stateDirOverride = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "openclaw-telegram-bindings-" ) ) ;
334+ process . env . OPENCLAW_STATE_DIR = stateDirOverride ;
335+ const unhandled : unknown [ ] = [ ] ;
336+ const onUnhandledRejection = ( reason : unknown ) => {
337+ unhandled . push ( reason ) ;
338+ } ;
339+ process . on ( "unhandledRejection" , onUnhandledRejection ) ;
340+
341+ try {
342+ const manager = createTelegramThreadBindingManager ( {
343+ accountId : "persist-failure" ,
344+ persist : true ,
345+ enableSweeper : false ,
346+ } ) ;
347+
348+ await getSessionBindingService ( ) . bind ( {
349+ targetSessionKey : "agent:main:subagent:child-persist-failure" ,
350+ targetKind : "subagent" ,
351+ conversation : {
352+ channel : "telegram" ,
353+ accountId : "persist-failure" ,
354+ conversationId : "-100200300:topic:100" ,
355+ } ,
356+ } ) ;
357+
358+ writeJsonFileAtomicallyMock . mockImplementationOnce ( async ( ) => {
359+ throw new Error ( "persist boom" ) ;
360+ } ) ;
361+ manager . touchConversation ( "-100200300:topic:100" ) ;
362+
363+ await __testing . resetTelegramThreadBindingsForTests ( ) ;
364+ await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) ) ;
365+ expect ( unhandled ) . toEqual ( [ ] ) ;
366+ } finally {
367+ process . off ( "unhandledRejection" , onUnhandledRejection ) ;
368+ }
369+ } ) ;
316370} ) ;
0 commit comments