@@ -3,7 +3,7 @@ import { promises } from "fs"
33import * as http from "http"
44import * as https from "https"
55import * as path from "path"
6- import { createApp , ensureAddress , handleArgsSocketCatchError , listen } from "../../../src/node/app"
6+ import { createApp , ensureAddress , handleArgsSocketCatchError , isFdOpts , listen } from "../../../src/node/app"
77import { OptionalString , setDefaults } from "../../../src/node/cli"
88import { generateCertificate } from "../../../src/node/util"
99import { clean , mockLogger , getAvailablePort , tmpdir } from "../../utils/helpers"
@@ -261,3 +261,47 @@ describe("listen", () => {
261261 }
262262 } )
263263} )
264+
265+ describe ( "listen (socket-fd)" , ( ) => {
266+ // Wrap a bound-but-not-yet-listening TCP socket so we get a real file
267+ // descriptor that listen({ fd }) can adopt, mirroring the systemd socket
268+ // activation case where the process inherits an fd and calls listen(2) on it.
269+ // Using a live net.Server's fd instead fails with EEXIST because the socket
270+ // is already listening in-process.
271+ // eslint-disable-next-line @typescript-eslint/no-var-requires
272+ const { TCP , constants : TCPConstants } = ( process as any ) . binding ( "tcp_wrap" )
273+
274+ let inherited : any
275+ let httpServer : http . Server
276+ let unlinkSpy : jest . SpyInstance
277+
278+ beforeEach ( async ( ) => {
279+ mockLogger ( )
280+ unlinkSpy = jest . spyOn ( promises , "unlink" )
281+ inherited = new TCP ( TCPConstants . SERVER )
282+ inherited . bind ( "127.0.0.1" , 0 )
283+ httpServer = http . createServer ( )
284+ } )
285+
286+ afterEach ( ( ) => {
287+ httpServer . close ( )
288+ try {
289+ inherited . close ( )
290+ } catch {
291+ // The fd is adopted by httpServer.close() above; ignore double-close.
292+ }
293+ jest . clearAllMocks ( )
294+ } )
295+
296+ it ( "isFdOpts detects a numeric socket-fd" , ( ) => {
297+ expect ( isFdOpts ( { "socket-fd" : 3 } ) ) . toBe ( true )
298+ expect ( isFdOpts ( { socket : "/tmp/x.sock" } as any ) ) . toBe ( false )
299+ } )
300+
301+ it ( "listens on an inherited fd without unlinking" , async ( ) => {
302+ const fd = inherited . fd as number
303+ await listen ( httpServer , { "socket-fd" : fd } )
304+ expect ( httpServer . address ( ) ) . not . toBeNull ( )
305+ expect ( unlinkSpy ) . not . toHaveBeenCalled ( )
306+ } )
307+ } )
0 commit comments