superwstest icon indicating copy to clipboard operation
superwstest copied to clipboard

IPv6 getaddrinfo ENOTFOUND Error

Open dtillner opened this issue 11 months ago • 4 comments

I'm using supertest to test HTTP requests and would like to use superwstest for WebSocket testing. My HTTP request tests with supertest work as expected. Here's a simplified version of my code:

import request from "supertest";

let server = null;

  before(async () => {
      server = await app.listen(0);
  });

  after(async () => {
      await server.close();
      await server.closeAllConnections();
  });


  test('testquery', async (t) => {
      await request(server)
      .post('/')
     // ...
 });

According to the superwstest documentation, I should be able to replace:

import request from "supertest";

with:

import request from "superwstest";

However, when I do this, I encounter the following error:

Error: getaddrinfo ENOTFOUND [::]
      at GetAddrInfoReqWrap.onlookupall [as oncomplete] (node:dns:120:26) {
    errno: -3008,
    code: 'ENOTFOUND',
    syscall: 'getaddrinfo',
    hostname: '[::]',
    response: undefined
  }

Interestingly, if I explicitly request the server with:

await request('http://127.0.0.1:<port>/')

everything works as expected. But a request to:

http://:::<port>/

does not work, suggesting an issue with how IPv6 addresses are being handled.

I've noticed that superwstest includes checks for different Node.js versions. Could there have been a recent change in how Node.js handles IPv6 addresses, which might be causing this issue?

Any guidance or insights on resolving this would be appreciated!

Environment:

Node.js: 23.4.0 Runtime: Docker container

dtillner avatar Jan 17 '25 20:01 dtillner

I've noticed that superwstest includes checks for different Node.js versions. Could there have been a recent change in how Node.js handles IPv6 addresses, which might be causing this issue?

There was a temporary change in NodeJS where it returned family: 6 instead of family: 'IPv6' in version 18, but that turned out to be a mistake and I believe it has gone back to being IPv6 since then. The fact that it's trying to access the hostname [::] suggests the IPv6 detection is working correctly here.

The error you're seeing is an interesting one, and seems likely to be related to your Docker network setup. I suspect that although NodeJS believes the network is IPv6-capable, in practice it isn't.

A simple workaround would be to listen on an IPv4 address explicitly:

before(async () => {
  server = await app.listen(0, '127.0.0.1');
});

As an aside, the URL format for connecting to an IPv6 address is http://[::]:<port>/ not http://:::<port>/

davidje13 avatar Jan 18 '25 00:01 davidje13

I investigated this further and found that:

  • The issue is not specific to the Node.js version.
  • This is not just a Docker-related problem—it also occurs on Ubuntu 24.10.

Below are examples of request calls that succeed or fail:

For post requests either with supertest or superwstest

import request from "supertest";
import wsrequest from "superwstest";

...
// ✅ await request('127.0.0.1:' + server.address().port)
// ✅ await request('http://127.0.0.1:' + server.address().port)
// ❌ await request('[::1]:' + server.address().port)await request('[::1]:' + server.address().port)
// ❌ await request('http://[::1]:' + server.address().port)
// ❌ await request(`[${server.address().address}]:${server.address().port}`)
// ❌ await request(`http://[${server.address().address}]:${server.address().port}`)
// ✅ await request(server)

// ✅ await wsrequest('127.0.0.1:' + server.address().port)
// ✅ await wsrequest('http://127.0.0.1:' + server.address().port)
// ❌ await wsrequest('[::1]:' + server.address().port)
// ❌ await wsrequest('http://[::1]:' + server.address().port)
// ❌ await wsrequest(`[${server.address().address}]:${server.address().port}`)
// ❌ await wsrequest(`http://[${server.address().address}]:${server.address().port}`)
// ❌ await wsrequest(server)

console.log(server.address());
// { address: '::', family: 'IPv6', port: 46323 }
...
    

For WebSocket requests with superwstest

import wsrequest from "superwstest";

...
    
// ❌ await wsrequest('127.0.0.1:' + server.address().port) 
// SyntaxError [Error]: Invalid URL: 127.0.0.1:46323/subscriptions

// ✅ await wsrequest('http://127.0.0.1:' + server.address().port)
// ✅ await wsrequest('ws://127.0.0.1:' + server.address().port)

// ❌ await wsrequest('[::1]:' + server.address().port)
// SyntaxError [Error]: Invalid URL: [::1]:46323/subscriptions

// ✅ await wsrequest('http://[::1]:' + server.address().port)
// ✅ await wsrequest('ws://[::1]:' + server.address().port)

// ❌ await wsrequest(`[${server.address().address}]:${server.address().port}`)
// ✅ await wsrequest(`http://[${server.address().address}]:${server.address().port}`)
// ✅ await wsrequest(`ws://[${server.address().address}]:${server.address().port}`)

// ✅ await wsrequest(server)

...

Using request(server) does not work for POST requests with superwstest, while WebSocket requests function correctly.

Interestingly, superwstest supports IPv6 (whereas supertest does not), but it requires a protocol in the URL, while supertest does not require one for IPv4.

The documentation states:

The server URL given should be http(s) rather than ws(s); this will provide compatibility with native supertest requests such as post, get, etc. and will be converted automatically as needed.

Is there logic that dynamically changes the protocol based on the request type? Could this behavior interfere with request(server) when making a POST request over IPv6?

I'm eager to help resolve this issue and am willing to run additional tests or make code changes with some guidance.

dtillner avatar Feb 06 '25 10:02 dtillner

After a bit of digging, I found the root issue in superagent (which is used by supertest)

Specifically, this line tries to set the host option in a call to http.request to the hostname from the requested URL, but URL.hostname includes [] wrapping, and http.request's host fails when this is provided:

new URL('http://[::1]:8080').hostname // [::1]

http.request({ port: 8080, host: '[::1]' }) // fails

http.request({ port: 8080, host: '::1' }) // fine

I'd recommend reporting this to superagent, since it does try to be IPv6 compatible (and has plenty of IPv6 handling code elsewhere).


On the differences you noted: supertest will always assume servers are available at http(s)://127.0.0.1:port (even if the server is actually only listening on IPv6 and isn't available at all on IPv4!), whereas superwstest will get the address from the server itself.

When supertest's methods are proxied via superwstest, the server address code from superwstest is still used (hence it attempting to connect via IPv6, triggering the bug in superagent)

davidje13 avatar Feb 06 '25 20:02 davidje13

I have reported this in the relevant library here: https://github.com/ladjs/superagent/issues/1828

davidje13 avatar Feb 12 '25 18:02 davidje13

This has been fixed upstream, so should be resolved now. Let me know if you're still having problems.

davidje13 avatar Jun 26 '25 08:06 davidje13

Added some automated testing for this to ensure it doesn't get broken in any future updates to superwstest.

davidje13 avatar Jul 12 '25 09:07 davidje13