current commit
260925.57 · http: preserve socket errors as response error causes
'use strict';

const common = require('../common');
const assert = require('assert');
const http = require('http');
const { Writable, pipeline } = require('stream');

for (const method of ['emit', 'emitAndDestroy', 'socketDestroy', 'requestDestroy', 'reset', 'close']) {
  let error = method === 'close' || method === 'reset' ? null : new Error('Socket failure');
  let serverSocket;
  const server = http.createServer(common.mustCall((req, res) => {
    serverSocket = req.socket;
    res.writeHead(200, { 'Content-Length': 100 });
    res.write('partial body');
  }));

  server.listen(0, common.mustCall(() => {
    const req = http.get({
      port: server.address().port,
      agent: false,
    }, common.mustCall((res) => {
      res.on('aborted', common.mustCall());
      res.on('close', common.mustCall());
      pipeline(res, new Writable({
        write(chunk, encoding, callback) {
          callback();
        },
      }), common.mustCall((err) => {
        assert.strictEqual(res.complete, false);
        assert.strictEqual(res.aborted, true);
        assert.strictEqual(err.code, 'ECONNRESET');
        assert.strictEqual(err.message, 'aborted');
        assert.strictEqual(res.errored, err);
        if (error) {
          assert.notStrictEqual(err, error);
          assert.strictEqual(err.cause, error);
          assert.deepStrictEqual(Object.getOwnPropertyDescriptor(err, 'cause'), {
            value: error,
            writable: true,
            enumerable: false,
            configurable: true,
          });
        } else {
          assert.strictEqual(Object.hasOwn(err, 'cause'), false);
        }
        server.close(common.mustCall());
      }));

      switch (method) {
        case 'emit':
        case 'emitAndDestroy':
          // TLSSocket can emit an error without setting socket.errored.
          req.socket.emit('error', error);
          break;
        case 'socketDestroy':
          req.socket.destroy(error);
          break;
        case 'requestDestroy':
          req.destroy(error);
          break;
        case 'reset':
          serverSocket.resetAndDestroy();
          break;
        case 'close':
          req.socket.destroy();
          break;
      }
    }));
    req.on('error', method !== 'close' ? common.mustCall((err) => {
      if (method === 'reset') {
        assert.strictEqual(err.code, 'ECONNRESET');
        assert.strictEqual(err.syscall, 'read');
        error = err;
      } else {
        assert.strictEqual(err, error);
      }
      if (method === 'emitAndDestroy')
        req.destroy();
    }) : common.mustNotCall());
    req.on('close', common.mustCall());
  }));
}