'use strict';

var test = require('tape');
var net = require('net');

var WrappedError = require('../wrapped.js');

test('can create a wrapped error', function t(assert) {
    var ServerListenError = WrappedError({
        name: 'SomeError',
        message: 'server: {causeMessage}',
        type: 'server.listen-failed',
        requestedPort: null,
        host: null
    });

    var err = new Error('listen EADDRINUSE');
    err.code = 'EADDRINUSE';

    var err2 = ServerListenError(err, {
        requestedPort: 3426,
        host: 'localhost'
    });

    assert.equal(ServerListenError.type, 'server.listen-failed');

    assert.equal(err2.message, 'server: listen EADDRINUSE');
    assert.equal(err2.requestedPort, 3426);
    assert.equal(err2.host, 'localhost');
    assert.equal(err2.code, 'EADDRINUSE');

    assert.equal(err2.cause, err);

    assert.equal(err2.toString(),
        'ServerListenFailedError: server: listen EADDRINUSE');

    assert.equal(JSON.stringify(err2), JSON.stringify({
        type: 'server.listen-failed',
        name: 'ServerListenFailedError',
        message: 'server: listen EADDRINUSE',
        requestedPort: 3426,
        host: 'localhost',
        causeMessage: 'listen EADDRINUSE',
        origMessage: 'listen EADDRINUSE',
        code: 'EADDRINUSE',
        fullType: 'server.listen-failed~!~error.wrapped-unknown'
    }));

    assert.end();
});

test('can create wrapped error with syscall', function t(assert) {
    var SysCallError = WrappedError({
        'message': 'tchannel socket error ({code} from ' +
            '{syscall}): {origMessage}',
        type: 'syscall.error'
    });

    var err = new Error('listen EADDRINUSE');
    err.code = 'EADDRINUSE';
    err.syscall = 'listen';

    var err2 = SysCallError(err);

    assert.equal(err2.message, 'tchannel socket error ' +
        '(EADDRINUSE from listen): listen EADDRINUSE');
    assert.equal(err2.syscall, 'listen');
    assert.equal(err2.code, 'EADDRINUSE');
    assert.equal(err2.type, 'syscall.error');

    assert.end();
});

test('wrapping twice', function t(assert) {
    var ReadError = WrappedError({
        type: 'my.read-error',
        message: 'read: {causeMessage}'
    });

    var DatabaseError = WrappedError({
        type: 'my.database-error',
        message: 'db: {causeMessage}'
    });

    var BusinessError = WrappedError({
        type: 'my.business-error',
        message: 'business: {causeMessage}'
    });

    var err = BusinessError(
        DatabaseError(
            ReadError(
                new Error('oops')
            )
        )
    );
    assert.ok(err);

    assert.equal(err.message, 'business: db: read: oops');
    assert.equal(err.type, 'my.business-error');
    assert.equal(err.fullType, 'my.business-error~!~' +
        'my.database-error~!~' +
        'my.read-error~!~' +
        'error.wrapped-unknown');

    assert.end();
});

test('handles bad recursive strings', function t(assert) {
    var ReadError = WrappedError({
        type: 'wat.wat',
        message: 'read: {causeMessage}'
    });

    var err2 = ReadError(new Error('hi {causeMessage}'));

    assert.ok(err2);
    assert.equal(err2.message,
        'read: hi $INVALID_CAUSE_MESSAGE_LITERAL');

    assert.end();
});

test('can wrap real IO errors', function t(assert) {
    var ServerListenError = WrappedError({
        message: 'server: {causeMessage}',
        type: 'server.listen-failed',
        requestedPort: null,
        host: null
    });

    var otherServer = net.createServer();
    otherServer.once('listening', onPortAllocated);
    otherServer.listen(0);

    function onPortAllocated() {
        var port = otherServer.address().port;

        var server = net.createServer();
        server.on('error', onError);

        server.listen(port);

        function onError(cause) {
            var err = ServerListenError(cause, {
                host: 'localhost',
                requestedPort: port
            });

            otherServer.close();
            assertOnError(err, cause, port);
        }
    }

    function assertOnError(err, cause, port) {
        assert.ok(err.message.indexOf('server: ') >= 0)
        assert.ok(err.message.indexOf('listen EADDRINUSE') >= 0)
        assert.equal(err.requestedPort, port);
        assert.equal(err.host, 'localhost');
        assert.equal(err.code, 'EADDRINUSE');

        assert.equal(err.cause, cause);

        assert.ok(err.toString().indexOf('ServerListenFailedError: ') >= 0)
        assert.ok(err.toString().indexOf('server: ') >= 0)
        assert.ok(err.toString().indexOf('listen EADDRINUSE') >= 0)

        var expectedMessage = err.message
        var expectedOrigMessage = err.origMessage

        assert.ok(err.origMessage.indexOf('listen EADDRINUSE') >= 0)
        assert.ok(err.origMessage.indexOf('server: ') === -1)

        assert.equal(JSON.stringify(err), JSON.stringify({
            type: 'server.listen-failed',
            name: 'ServerListenFailedError',
            message: expectedMessage,
            requestedPort: port,
            host: 'localhost',
            causeMessage: expectedOrigMessage,
            origMessage: expectedOrigMessage,
            code: 'EADDRINUSE',
            errno: 'EADDRINUSE',
            syscall: 'listen',
            fullType: 'server.listen-failed~!~' +
                'error.wrapped-io.listen.EADDRINUSE'
        }));

        assert.end();
    }
});

test('can wrap assert errors', function t(assert) {
  var TestError = WrappedError({
      message: 'error: {origMessage}',
      type: 'error'
  });

  var assertError;
  try { require('assert').equal('a', 'b'); }
  catch (_err) { assertError = _err; }

  var err = TestError(assertError);
  assert.equal(err.cause.actual, 'a');
  assert.end();
})
