Compare commits

...

3 Commits

Author SHA1 Message Date
Claude Bot
54102fbc53 test(net): use await using and AbortSignal for disposal/timeouts
Address additional CodeRabbit review feedback:
- Use `await using` for automatic server disposal instead of try/finally
- Use `once()` from node:events for cleaner event waiting
- Replace setTimeout-based timeout with AbortSignal.timeout()
- Simplify helper functions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 16:08:12 +00:00
Claude Bot
893cdd501b test(net): address review feedback - use event-driven patterns
Address CodeRabbit review feedback:
- Replace Bun.sleep() delays with Promise-based condition waiting
- Use queueMicrotask instead of setTimeout for synchronization
- Use try/finally blocks for proper resource cleanup
- Await server.close() completion before test ends
- Add helper functions for common patterns

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 15:49:24 +00:00
Claude Bot
32bafed4bc test(net): add regression test for socket.pipe() data events
Adds regression test for issue #26418 where net.Socket data events
were reported to stop firing when the socket is piped to another
stream.

The issue appears to be resolved in the current version. This test
ensures the fix remains in place by testing:

1. Basic pipe scenario: socket.on('data') fires when socket.pipe()
   is called
2. Bidirectional pipe (SSH tunnel pattern): socket.pipe(stream).pipe(socket)
   works correctly with data events firing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 15:38:55 +00:00

View File

@@ -0,0 +1,110 @@
import { describe, it } from "bun:test";
import { once } from "node:events";
import type { AddressInfo } from "node:net";
import { connect, createServer, Socket } from "node:net";
import { Duplex, Writable } from "node:stream";
describe("issue/026418", () => {
it("net.Socket data events fire when socket is piped to another stream", async () => {
const { promise: dataPromise, resolve: dataResolve } = Promise.withResolvers<void>();
await using server = createServer((socket: Socket) => {
socket.on("data", () => {
dataResolve();
});
const writable = new Writable({
write(_chunk, _enc, cb) {
cb();
},
});
socket.pipe(writable);
});
await once(server.listen(0, "127.0.0.1"), "listening");
const { port } = server.address() as AddressInfo;
const client = connect({ host: "127.0.0.1", port });
await once(client, "connect");
// Send data
client.write("HELLO");
// Wait for data to be received with AbortSignal timeout
await Promise.race([dataPromise, abortAfterTimeout(1000, "data event not received")]);
client.destroy();
});
it("net.Socket bidirectional pipe works (SSH tunnel pattern)", async () => {
const { promise: dataPromise, resolve: dataResolve } = Promise.withResolvers<void>();
const { promise: responsePromise, resolve: responseResolve } = Promise.withResolvers<void>();
const { promise: pipeReadyPromise, resolve: pipeReadyResolve } = Promise.withResolvers<void>();
// Create a fake "remote" stream that simulates what ssh2's forwardOut returns
function createFakeForwardStream() {
return new Duplex({
read() {},
write(chunk, _encoding, callback) {
// Use queueMicrotask instead of setTimeout to avoid timing issues
queueMicrotask(() => {
this.push("RESPONSE:" + chunk.toString());
});
callback();
},
});
}
await using server = createServer((socket: Socket) => {
socket.on("data", () => {
dataResolve();
});
// Use queueMicrotask instead of setTimeout
queueMicrotask(() => {
const forwardStream = createFakeForwardStream();
// This is the pattern from the issue: socket.pipe(stream).pipe(socket)
socket.pipe(forwardStream).pipe(socket);
pipeReadyResolve();
});
});
await once(server.listen(0, "127.0.0.1"), "listening");
const { port } = server.address() as AddressInfo;
const client = connect({ host: "127.0.0.1", port });
await once(client, "connect");
client.on("data", (chunk: Buffer) => {
if (chunk.toString().includes("RESPONSE:")) {
responseResolve();
}
});
// Wait for forward stream to be set up
await Promise.race([pipeReadyPromise, abortAfterTimeout(1000, "pipe setup timeout")]);
// Send data
client.write("REQUEST");
// Wait for data and response with timeout
await Promise.race([
Promise.all([dataPromise, responsePromise]),
abortAfterTimeout(1000, "data/response not received"),
]);
client.destroy();
});
});
// Helper to create a promise that rejects after timeout using AbortSignal
function abortAfterTimeout(ms: number, message: string): Promise<never> {
const signal = AbortSignal.timeout(ms);
return new Promise((_, reject) => {
signal.addEventListener("abort", () => {
reject(new Error(message));
});
});
}