11import { afterEach , beforeAll , beforeEach , describe , expect , it , vi } from "vitest" ;
22
3- const mockReadFileSync = vi . hoisted ( ( ) => vi . fn ( ) ) ;
4- const mockSpawnSync = vi . hoisted ( ( ) => vi . fn ( ) ) ;
5-
63type RestartHealthSnapshot = {
74 healthy : boolean ;
85 staleGatewayPids : number [ ] ;
@@ -35,7 +32,9 @@ const terminateStaleGatewayPids = vi.fn();
3532const renderGatewayPortHealthDiagnostics = vi . fn ( ( ) => [ "diag: unhealthy port" ] ) ;
3633const renderRestartDiagnostics = vi . fn ( ( ) => [ "diag: unhealthy runtime" ] ) ;
3734const resolveGatewayPort = vi . fn ( ( ) => 18789 ) ;
38- const findGatewayPidsOnPortSync = vi . fn < ( port : number ) => number [ ] > ( ( ) => [ ] ) ;
35+ const findVerifiedGatewayListenerPidsOnPortSync = vi . fn < ( port : number ) => number [ ] > ( ( ) => [ ] ) ;
36+ const signalVerifiedGatewayPidSync = vi . fn < ( pid : number , signal : "SIGTERM" | "SIGUSR1" ) => void > ( ) ;
37+ const formatGatewayPidList = vi . fn < ( pids : number [ ] ) => string > ( ( pids ) => pids . join ( ", " ) ) ;
3938const probeGateway = vi . fn <
4039 ( opts : {
4140 url : string ;
@@ -49,24 +48,18 @@ const probeGateway = vi.fn<
4948const isRestartEnabled = vi . fn < ( config ?: { commands ?: unknown } ) => boolean > ( ( ) => true ) ;
5049const loadConfig = vi . fn ( ( ) => ( { } ) ) ;
5150
52- vi . mock ( "node:fs" , ( ) => ( {
53- default : {
54- readFileSync : ( ...args : unknown [ ] ) => mockReadFileSync ( ...args ) ,
55- } ,
56- } ) ) ;
57-
58- vi . mock ( "node:child_process" , ( ) => ( {
59- spawnSync : ( ...args : unknown [ ] ) => mockSpawnSync ( ...args ) ,
60- } ) ) ;
61-
6251vi . mock ( "../../config/config.js" , ( ) => ( {
6352 loadConfig : ( ) => loadConfig ( ) ,
6453 readBestEffortConfig : async ( ) => loadConfig ( ) ,
6554 resolveGatewayPort,
6655} ) ) ;
6756
68- vi . mock ( "../../infra/restart.js" , ( ) => ( {
69- findGatewayPidsOnPortSync : ( port : number ) => findGatewayPidsOnPortSync ( port ) ,
57+ vi . mock ( "../../infra/gateway-processes.js" , ( ) => ( {
58+ findVerifiedGatewayListenerPidsOnPortSync : ( port : number ) =>
59+ findVerifiedGatewayListenerPidsOnPortSync ( port ) ,
60+ signalVerifiedGatewayPidSync : ( pid : number , signal : "SIGTERM" | "SIGUSR1" ) =>
61+ signalVerifiedGatewayPidSync ( pid , signal ) ,
62+ formatGatewayPidList : ( pids : number [ ] ) => formatGatewayPidList ( pids ) ,
7063} ) ) ;
7164
7265vi . mock ( "../../gateway/probe.js" , ( ) => ( {
@@ -121,12 +114,12 @@ describe("runDaemonRestart health checks", () => {
121114 renderGatewayPortHealthDiagnostics . mockReset ( ) ;
122115 renderRestartDiagnostics . mockReset ( ) ;
123116 resolveGatewayPort . mockReset ( ) ;
124- findGatewayPidsOnPortSync . mockReset ( ) ;
117+ findVerifiedGatewayListenerPidsOnPortSync . mockReset ( ) ;
118+ signalVerifiedGatewayPidSync . mockReset ( ) ;
119+ formatGatewayPidList . mockReset ( ) ;
125120 probeGateway . mockReset ( ) ;
126121 isRestartEnabled . mockReset ( ) ;
127122 loadConfig . mockReset ( ) ;
128- mockReadFileSync . mockReset ( ) ;
129- mockSpawnSync . mockReset ( ) ;
130123
131124 service . readCommand . mockResolvedValue ( {
132125 programArguments : [ "openclaw" , "gateway" , "--port" , "18789" ] ,
@@ -158,23 +151,8 @@ describe("runDaemonRestart health checks", () => {
158151 configSnapshot : { commands : { restart : true } } ,
159152 } ) ;
160153 isRestartEnabled . mockReturnValue ( true ) ;
161- mockReadFileSync . mockImplementation ( ( path : string ) => {
162- const match = path . match ( / \/ p r o c \/ ( \d + ) \/ c m d l i n e $ / ) ;
163- if ( ! match ) {
164- throw new Error ( `unexpected path ${ path } ` ) ;
165- }
166- const pid = Number . parseInt ( match [ 1 ] ?? "" , 10 ) ;
167- if ( [ 4200 , 4300 ] . includes ( pid ) ) {
168- return [ "openclaw" , "gateway" , "--port" , "18789" , "" ] . join ( "\0" ) ;
169- }
170- throw new Error ( `unknown pid ${ pid } ` ) ;
171- } ) ;
172- mockSpawnSync . mockReturnValue ( {
173- error : null ,
174- status : 0 ,
175- stdout : "openclaw gateway --port 18789" ,
176- stderr : "" ,
177- } ) ;
154+ signalVerifiedGatewayPidSync . mockImplementation ( ( ) => { } ) ;
155+ formatGatewayPidList . mockImplementation ( ( pids ) => pids . join ( ", " ) ) ;
178156 } ) ;
179157
180158 afterEach ( ( ) => {
@@ -242,38 +220,20 @@ describe("runDaemonRestart health checks", () => {
242220 } ) ;
243221
244222 it ( "signals an unmanaged gateway process on stop" , async ( ) => {
245- vi . spyOn ( process , "platform" , "get" ) . mockReturnValue ( "win32" ) ;
246- const killSpy = vi . spyOn ( process , "kill" ) . mockImplementation ( ( ) => true ) ;
247- findGatewayPidsOnPortSync . mockReturnValue ( [ 4200 , 4200 , 4300 ] ) ;
248- mockSpawnSync . mockReturnValue ( {
249- error : null ,
250- status : 0 ,
251- stdout :
252- 'CommandLine="C:\\\\Program Files\\\\OpenClaw\\\\openclaw.exe" gateway --port 18789\r\n' ,
253- stderr : "" ,
254- } ) ;
223+ findVerifiedGatewayListenerPidsOnPortSync . mockReturnValue ( [ 4200 , 4200 , 4300 ] ) ;
255224 runServiceStop . mockImplementation ( async ( params : { onNotLoaded ?: ( ) => Promise < unknown > } ) => {
256225 await params . onNotLoaded ?.( ) ;
257226 } ) ;
258227
259228 await runDaemonStop ( { json : true } ) ;
260229
261- expect ( findGatewayPidsOnPortSync ) . toHaveBeenCalledWith ( 18789 ) ;
262- expect ( killSpy ) . toHaveBeenCalledWith ( 4200 , "SIGTERM" ) ;
263- expect ( killSpy ) . toHaveBeenCalledWith ( 4300 , "SIGTERM" ) ;
230+ expect ( findVerifiedGatewayListenerPidsOnPortSync ) . toHaveBeenCalledWith ( 18789 ) ;
231+ expect ( signalVerifiedGatewayPidSync ) . toHaveBeenCalledWith ( 4200 , "SIGTERM" ) ;
232+ expect ( signalVerifiedGatewayPidSync ) . toHaveBeenCalledWith ( 4300 , "SIGTERM" ) ;
264233 } ) ;
265234
266235 it ( "signals a single unmanaged gateway process on restart" , async ( ) => {
267- vi . spyOn ( process , "platform" , "get" ) . mockReturnValue ( "win32" ) ;
268- const killSpy = vi . spyOn ( process , "kill" ) . mockImplementation ( ( ) => true ) ;
269- findGatewayPidsOnPortSync . mockReturnValue ( [ 4200 ] ) ;
270- mockSpawnSync . mockReturnValue ( {
271- error : null ,
272- status : 0 ,
273- stdout :
274- 'CommandLine="C:\\\\Program Files\\\\OpenClaw\\\\openclaw.exe" gateway --port 18789\r\n' ,
275- stderr : "" ,
276- } ) ;
236+ findVerifiedGatewayListenerPidsOnPortSync . mockReturnValue ( [ 4200 ] ) ;
277237 runServiceRestart . mockImplementation (
278238 async ( params : RestartParams & { onNotLoaded ?: ( ) => Promise < unknown > } ) => {
279239 await params . onNotLoaded ?.( ) ;
@@ -291,8 +251,8 @@ describe("runDaemonRestart health checks", () => {
291251
292252 await runDaemonRestart ( { json : true } ) ;
293253
294- expect ( findGatewayPidsOnPortSync ) . toHaveBeenCalledWith ( 18789 ) ;
295- expect ( killSpy ) . toHaveBeenCalledWith ( 4200 , "SIGUSR1" ) ;
254+ expect ( findVerifiedGatewayListenerPidsOnPortSync ) . toHaveBeenCalledWith ( 18789 ) ;
255+ expect ( signalVerifiedGatewayPidSync ) . toHaveBeenCalledWith ( 4200 , "SIGUSR1" ) ;
296256 expect ( probeGateway ) . toHaveBeenCalledTimes ( 1 ) ;
297257 expect ( waitForGatewayHealthyListener ) . toHaveBeenCalledTimes ( 1 ) ;
298258 expect ( waitForGatewayHealthyRestart ) . not . toHaveBeenCalled ( ) ;
@@ -301,15 +261,7 @@ describe("runDaemonRestart health checks", () => {
301261 } ) ;
302262
303263 it ( "fails unmanaged restart when multiple gateway listeners are present" , async ( ) => {
304- vi . spyOn ( process , "platform" , "get" ) . mockReturnValue ( "win32" ) ;
305- findGatewayPidsOnPortSync . mockReturnValue ( [ 4200 , 4300 ] ) ;
306- mockSpawnSync . mockReturnValue ( {
307- error : null ,
308- status : 0 ,
309- stdout :
310- 'CommandLine="C:\\\\Program Files\\\\OpenClaw\\\\openclaw.exe" gateway --port 18789\r\n' ,
311- stderr : "" ,
312- } ) ;
264+ findVerifiedGatewayListenerPidsOnPortSync . mockReturnValue ( [ 4200 , 4300 ] ) ;
313265 runServiceRestart . mockImplementation (
314266 async ( params : RestartParams & { onNotLoaded ?: ( ) => Promise < unknown > } ) => {
315267 await params . onNotLoaded ?.( ) ;
@@ -323,7 +275,7 @@ describe("runDaemonRestart health checks", () => {
323275 } ) ;
324276
325277 it ( "fails unmanaged restart when the running gateway has commands.restart disabled" , async ( ) => {
326- findGatewayPidsOnPortSync . mockReturnValue ( [ 4200 ] ) ;
278+ findVerifiedGatewayListenerPidsOnPortSync . mockReturnValue ( [ 4200 ] ) ;
327279 probeGateway . mockResolvedValue ( {
328280 ok : true ,
329281 configSnapshot : { commands : { restart : false } } ,
@@ -342,21 +294,13 @@ describe("runDaemonRestart health checks", () => {
342294 } ) ;
343295
344296 it ( "skips unmanaged signaling for pids that are not live gateway processes" , async ( ) => {
345- const killSpy = vi . spyOn ( process , "kill" ) . mockImplementation ( ( ) => true ) ;
346- findGatewayPidsOnPortSync . mockReturnValue ( [ 4200 ] ) ;
347- mockReadFileSync . mockReturnValue ( [ "python" , "-m" , "http.server" , "" ] . join ( "\0" ) ) ;
348- mockSpawnSync . mockReturnValue ( {
349- error : null ,
350- status : 0 ,
351- stdout : "python -m http.server" ,
352- stderr : "" ,
353- } ) ;
297+ findVerifiedGatewayListenerPidsOnPortSync . mockReturnValue ( [ ] ) ;
354298 runServiceStop . mockImplementation ( async ( params : { onNotLoaded ?: ( ) => Promise < unknown > } ) => {
355299 await params . onNotLoaded ?.( ) ;
356300 } ) ;
357301
358302 await runDaemonStop ( { json : true } ) ;
359303
360- expect ( killSpy ) . not . toHaveBeenCalled ( ) ;
304+ expect ( signalVerifiedGatewayPidSync ) . not . toHaveBeenCalled ( ) ;
361305 } ) ;
362306} ) ;
0 commit comments