import * as NodeNet from "node:net"; import { it as effectIt } from "@effect/vitest"; import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; import * as Net from "@t3tools/shared/Net"; import * as Cause from "effect/Cause"; import * as Effect from "effect/Effect"; import * as Exit from "effect/Exit"; import * as Layer from "effect/Layer"; import * as PlatformError from "effect/PlatformError"; import { expect } from "vite-plus/test"; import * as ProcessRunner from "../processRunner.ts"; import * as PortScanner from "./PortScanner.ts"; const TestProcessRunner = Layer.succeed(ProcessRunner.ProcessRunner, { run: (input) => Effect.fail( new ProcessRunner.ProcessSpawnError({ command: input.command, argumentCount: input.args.length, cwd: input.cwd, cause: PlatformError.systemError({ _tag: "NotFound", module: "ChildProcess", method: "spawn", description: "PowerShell is not installed in the test environment", }), }), ), }); const makeProbeFailureLayer = (run: ProcessRunner.ProcessRunner["Service"]["run"]) => PortScanner.layer.pipe( Layer.provide( Layer.mergeAll( Layer.succeed(ProcessRunner.ProcessRunner, { run }), Layer.succeed(Net.NetService, { canListenOnHost: () => Effect.succeed(true), isPortAvailableOnLoopback: () => Effect.succeed(true), reserveLoopbackPort: () => Effect.succeed(40_000), findAvailablePort: (preferred) => Effect.succeed(preferred), }), Layer.succeed(HostProcessPlatform, "linux"), ), ), ); const TestPortDiscoveryLive = PortScanner.layer.pipe( Layer.provide( Layer.mergeAll(TestProcessRunner, Net.layer, Layer.succeed(HostProcessPlatform, "win32")), ), ); const openServer = (port: number): Effect.Effect => Effect.callback((resume) => { const server = NodeNet.createServer(); server.once("error", () => { resume(Effect.succeed(null)); }); server.listen(port, "127.0.0.1", () => { resume(Effect.succeed(server)); }); return Effect.sync(() => { server.close(); }); }); const closeServer = (server: NodeNet.Server): Effect.Effect => Effect.callback((resume) => { server.close(() => resume(Effect.void)); }); const openCommonDevServer = Effect.fn("PortScannerTest.openCommonDevServer")(function* ( ports: ReadonlyArray, ) { for (const port of ports) { const server = yield* openServer(port); if (server !== null) return { port, server }; } return yield* Effect.die( new Error("No common development port was available for the preview scanner test"), ); }); const commonDevServer = Effect.acquireRelease( openCommonDevServer(PortScanner.COMMON_DEV_PORTS), ({ server }) => closeServer(server), ); /** * Integration tests against a real TCP listener. We provide the Windows host * platform so the tests exercise the TCP-probe fallback without depending on * `lsof` being installed. */ effectIt.layer(TestPortDiscoveryLive)("PortDiscovery integration (TCP probe fallback)", (it) => { it.effect( "scan() returns a server we just opened on a curated dev port", Effect.fn("PortScannerTest.scanFindsCommonDevServer")(function* () { const { port } = yield* commonDevServer; const scanner = yield* PortScanner.PortDiscovery; const result = yield* scanner.scan(); const found = result.find((server) => server.port === port); expect(found).toBeDefined(); expect(found?.host).toBe("localhost"); }), ); it.effect( "retain drives an immediate broadcast to subscribers", Effect.fn("PortScannerTest.retainBroadcastsImmediately")(function* () { const { port } = yield* commonDevServer; const received: number[] = []; const scanner = yield* PortScanner.PortDiscovery; yield* scanner.subscribe((servers) => Effect.sync(() => { for (const server of servers) received.push(server.port); }), ); yield* scanner.retain; expect(received).toContain(port); }), ); }); effectIt("does not swallow process probe defects", () => Effect.gen(function* () { const defect = new Error("unexpected process probe defect"); const layer = makeProbeFailureLayer(() => Effect.die(defect)); const exit = yield* Effect.flatMap(PortScanner.PortDiscovery, (scanner) => scanner.scan()).pipe( Effect.provide(layer), Effect.exit, ); expect(Exit.isFailure(exit)).toBe(true); if (Exit.isFailure(exit)) { expect(Cause.hasDies(exit.cause)).toBe(true); expect(Cause.squash(exit.cause)).toBe(defect); } }), ); effectIt("does not swallow process probe interruption", () => Effect.gen(function* () { const layer = makeProbeFailureLayer(() => Effect.interrupt); const exit = yield* Effect.flatMap(PortScanner.PortDiscovery, (scanner) => scanner.scan()).pipe( Effect.provide(layer), Effect.exit, ); expect(Exit.isFailure(exit)).toBe(true); if (Exit.isFailure(exit)) { expect(Cause.hasInterruptsOnly(exit.cause)).toBe(true); } }), );