// Flags: --expose-internals
'use strict';
const common = require('../common');
const assert = require('assert');
const { internalBinding } = require('internal/test/binding');
const cares = internalBinding('cares_wrap');

// Stub `getaddrinfo` to proxy its call dynamic stub. This has to be done before
// we load the `dns` module to guarantee that the `dns` module uses the stub.
let getaddrinfoStub = null;
cares.getaddrinfo = (req) => getaddrinfoStub(req);

const dnsPromises = require('dns').promises;

function getaddrinfoNegative() {
  return common.mustCall(function getaddrinfoNegativeHandler(req) {
    const originalReject = req.reject;
    req.resolve = common.mustNotCall();
    req.reject = common.mustCall(originalReject);
    req.oncomplete(internalBinding('uv').UV_ENOMEM);
  });
}

function getaddrinfoPositive(addresses) {
  return common.mustCall(function getaddrinfo_positive(req) {
    const originalResolve = req.resolve;
    req.reject = common.mustNotCall();
    req.resolve = common.mustCall(originalResolve);
    req.oncomplete(null, addresses);
  });
}

async function lookupPositive() {
  [
    {
      stub: getaddrinfoPositive(['::1']),
      factory: () => dnsPromises.lookup('example.com'),
      expectation: { address: '::1', family: 6 }
    },
    {
      stub: getaddrinfoPositive(['127.0.0.1']),
      factory: () => dnsPromises.lookup('example.com'),
      expectation: { address: '127.0.0.1', family: 4 }
    },
    {
      stub: getaddrinfoPositive(['127.0.0.1'], { family: 4 }),
      factory: () => dnsPromises.lookup('example.com'),
      expectation: { address: '127.0.0.1', family: 4 }
    },
    {
      stub: getaddrinfoPositive(['some-address']),
      factory: () => dnsPromises.lookup('example.com'),
      expectation: { address: 'some-address', family: 0 }
    },
    {
      stub: getaddrinfoPositive(['some-address2']),
      factory: () => dnsPromises.lookup('example.com', { family: 6 }),
      expectation: { address: 'some-address2', family: 6 }
    },
  ].forEach(async ({ stub, factory, expectation }) => {
    getaddrinfoStub = stub;
    assert.deepStrictEqual(await factory(), expectation);
  });
}

async function lookupNegative() {
  getaddrinfoStub = getaddrinfoNegative();
  const expected = {
    code: 'ENOMEM',
    hostname: 'example.com',
    syscall: 'getaddrinfo'
  };
  return assert.rejects(dnsPromises.lookup('example.com'), expected);
}

async function lookupallPositive() {
  [
    {
      stub: getaddrinfoPositive(['::1', '::2']),
      factory: () => dnsPromises.lookup('example', { all: true }),
      expectation: [
        { address: '::1', family: 6 },
        { address: '::2', family: 6 },
      ]
    },
    {
      stub: getaddrinfoPositive(['::1', '::2']),
      factory: () => dnsPromises.lookup('example', { all: true, family: 4 }),
      expectation: [
        { address: '::1', family: 4 },
        { address: '::2', family: 4 },
      ]
    },
    {
      stub: getaddrinfoPositive(['127.0.0.1', 'some-address']),
      factory: () => dnsPromises.lookup('example', { all: true }),
      expectation: [
        { address: '127.0.0.1', family: 4 },
        { address: 'some-address', family: 0 },
      ]
    },
    {
      stub: getaddrinfoPositive(['127.0.0.1', 'some-address']),
      factory: () => dnsPromises.lookup('example', { all: true, family: 6 }),
      expectation: [
        { address: '127.0.0.1', family: 6 },
        { address: 'some-address', family: 6 },
      ]
    },
    {
      stub: getaddrinfoPositive([]),
      factory: () => dnsPromises.lookup('example', { all: true }),
      expectation: []
    },
  ].forEach(async ({ stub, factory, expectation }) => {
    getaddrinfoStub = stub;
    assert.deepStrictEqual(await factory(), expectation);
  });
}

async function lookupallNegative() {
  getaddrinfoStub = getaddrinfoNegative();
  const expected = {
    code: 'ENOMEM',
    hostname: 'example.com',
    syscall: 'getaddrinfo'
  };
  return assert.rejects(dnsPromises.lookup('example.com', { all: true }),
                        expected);
}

(async () => {
  await Promise.all([
    lookupPositive(),
    lookupNegative(),
    lookupallPositive(),
    lookupallNegative(),
  ]);
})().then(common.mustCall());
