'use strict';

var tap = require('tap');

var stripFullStack = require('./common').stripFullStack;
var runProgram = require('./common').runProgram;

var nodeVersion = process.versions.node;
var majorVersion = nodeVersion.split('.')[0];

if (Number(majorVersion) < 8) {
	process.exit(0); // eslint-disable-line no-process-exit
}

var node17 = Number(majorVersion) >= 17;

var lengthMessage = 'Cannot read property \'length\' of null';
try {
	lengthMessage = null.length;
} catch (e) {
	lengthMessage = e.message; // differs in v8 6.9+ (node 16.9+)
}

tap.test('async1', function (t) {
	runProgram('async-await', 'async1.js', function (r) {
		t.deepEqual(stripFullStack(r.stdout.toString('utf8')), [
			'TAP version 13',
			'# async1',
			'ok 1 before await',
			'ok 2 after await',
			'',
			'1..2',
			'# tests 2',
			'# pass  2',
			'',
			'# ok',
			'',
			''
		]);
		t.same(r.exitCode, 0);
		t.same(r.stderr.toString('utf8'), '');
		t.end();
	});
});

tap.test('async2', function (t) {
	runProgram('async-await', 'async2.js', function (r) {
		var stdout = r.stdout.toString('utf8');
		var lines = stdout.split('\n').filter(function (line) {
			return !(/^(\s+)at(\s+)<anonymous>$/).test(line);
		});

		t.deepEqual(stripFullStack(lines.join('\n')), [
			'TAP version 13',
			'# async2',
			'ok 1 before await',
			'not ok 2 after await',
			'  ---',
			'    operator: ok',
			'    expected: true',
			'    actual:   false',
			'    at: Test.myTest ($TEST/async-await/async2.js:$LINE:$COL)',
			'    stack: |-',
			'      Error: after await',
			'          [... stack stripped ...]',
			'          at Test.myTest ($TEST/async-await/async2.js:$LINE:$COL)',
			'  ...',
			'',
			'1..2',
			'# tests 2',
			'# pass  1',
			'# fail  1',
			'',
			''
		]);
		t.same(r.exitCode, 1);
		t.same(r.stderr.toString('utf8'), '');
		t.end();
	});
});

tap.test('async3', function (t) {
	runProgram('async-await', 'async3.js', function (r) {
		t.deepEqual(stripFullStack(r.stdout.toString('utf8')), [
			'TAP version 13',
			'# async3',
			'ok 1 before await',
			'ok 2 after await',
			'',
			'1..2',
			'# tests 2',
			'# pass  2',
			'',
			'# ok',
			'',
			''
		]);
		t.same(r.exitCode, 0);
		t.same(r.stderr.toString('utf8'), '');
		t.end();
	});
});

tap.test('async4', function (t) {
	runProgram('async-await', 'async4.js', function (r) {
		t.deepEqual(stripFullStack(r.stdout.toString('utf8')), [
			'TAP version 13',
			'# async4',
			'ok 1 before await',
			'not ok 2 Error: oops',
			'  ---',
			'    operator: error',
			'    at: Test.myTest ($TEST/async-await/async4.js:$LINE:$COL)',
			'    stack: |-',
			'      Error: oops',
			'          at Timeout.myTimeout [as _onTimeout] ($TEST/async-await/async4.js:$LINE:$COL)',
			'          [... stack stripped ...]',
			'  ...',
			'',
			'1..2',
			'# tests 2',
			'# pass  1',
			'# fail  1',
			'',
			''
		]);
		t.same(r.exitCode, 1);
		t.same(r.stderr.toString('utf8'), '');
		t.end();
	});
});

tap.test('async5', function (t) {
	runProgram('async-await', 'async5.js', function (r) {
		t.same(stripFullStack(r.stdout.toString('utf8')), [
			'TAP version 13',
			'# async5',
			'ok 1 before server',
			'ok 2 after server',
			'ok 3 before request',
			'ok 4 after request',
			'ok 5 res.statusCode is 200',
			'not ok 6 .end() already called: mockDb.state is new',
			'  ---',
			'    operator: fail',
			'    at: Timeout._onTimeout ($TEST/async-await/async5.js:$LINE:$COL)',
			'    stack: |-',
			'      Error: .end() already called: mockDb.state is new',
			'          [... stack stripped ...]',
			'          at Timeout._onTimeout ($TEST/async-await/async5.js:$LINE:$COL)',
			'          [... stack stripped ...]',
			'  ...',
			'not ok 7 .end() already called: error on close',
			'  ---',
			'    operator: fail',
			'    at: Server.<anonymous> ($TEST/async-await/async5.js:$LINE:$COL)',
			'    stack: |-',
			'      Error: .end() already called: error on close',
			'          [... stack stripped ...]',
			'          at Server.<anonymous> ($TEST/async-await/async5.js:$LINE:$COL)',
			'          [... stack stripped ...]',
			'  ...',
			'not ok 8 .end() already called',
			'  ---',
			'    operator: fail',
			'    at: Server.<anonymous> ($TEST/async-await/async5.js:$LINE:$COL)',
			'    stack: |-',
			'      Error: .end() already called',
			'          [... stack stripped ...]',
			'          at Server.<anonymous> ($TEST/async-await/async5.js:$LINE:$COL)',
			'          [... stack stripped ...]',
			'  ...',
			'',
			'1..8',
			'# tests 8',
			'# pass  5',
			'# fail  3',
			'',
			''
		]);
		t.same(r.exitCode, 1);
		t.same(r.stderr.toString('utf8'), '');
		t.end();
	});
});

tap.test('sync-error', function (t) {
	runProgram('async-await', 'sync-error.js', function (r) {
		t.same(stripFullStack(r.stdout.toString('utf8')), [
			'TAP version 13',
			'# sync-error',
			'ok 1 before throw',
			''
		]);
		t.same(r.exitCode, 1);

		var stderr = r.stderr.toString('utf8');
		var lines = stderr.split('\n');
		lines = lines.filter(function (line) {
			return !(/\(timers.js:/).test(line)
                && !(/\(internal\/timers.js:/).test(line)
                && !(/Immediate\.next/).test(line);
		});
		stderr = lines.join('\n');

		t.same(stripFullStack(stderr), [].concat(
			'$TEST/async-await/sync-error.js:7',
			'	throw new Error(\'oopsie\');',
			'	^',
			'',
			'Error: oopsie',
			'    at Test.myTest ($TEST/async-await/sync-error.js:$LINE:$COL)',
			'    at Test.bound [as _cb] ($TAPE/lib/test.js:$LINE:$COL)',
			'    at Test.run ($TAPE/lib/test.js:$LINE:$COL)',
			'    at Test.bound [as run] ($TAPE/lib/test.js:$LINE:$COL)',
			node17 ? [
				'',
				'Node.js ' + process.version
			] : [],
			''
		));
		t.end();
	});
});

tap.test('async-error', function (t) {
	runProgram('async-await', 'async-error.js', function (r) {
		var stdout = r.stdout.toString('utf8');
		var lines = stdout.split('\n');
		lines = lines.filter(function (line) {
			return !(/^(\s+)at(\s+)<anonymous>$/).test(line);
		});
		stdout = lines.join('\n');

		t.same(stripFullStack(stdout), [
			'TAP version 13',
			'# async-error',
			'ok 1 before throw',
			'not ok 2 Error: oopsie',
			'  ---',
			'    operator: error',
			'    stack: |-',
			'      Error: oopsie',
			'          at Test.myTest ($TEST/async-await/async-error.js:$LINE:$COL)',
			'          [... stack stripped ...]',
			'  ...',
			'',
			'1..2',
			'# tests 2',
			'# pass  1',
			'# fail  1',
			'',
			''
		]);
		t.same(r.exitCode, 1);

		var stderr = r.stderr.toString('utf8');
		var stderrLines = stderr.split('\n').filter(function (line) {
			return !(/\(timers.js:/).test(line)
                && !(/\(internal\/timers.js:/).test(line)
                && !(/Immediate\.next/).test(line);
		});

		t.same(stderrLines.join('\n'), '');
		t.end();
	});
});

tap.test('async-bug', function (t) {
	runProgram('async-await', 'async-bug.js', function (r) {
		var stdout = r.stdout.toString('utf8');
		var lines = stdout.split('\n');
		lines = lines.filter(function (line) {
			return !(/^(\s+)at(\s+)<anonymous>$/).test(line);
		});
		stdout = lines.join('\n');

		t.same(stripFullStack(stdout), [
			'TAP version 13',
			'# async-error',
			'ok 1 before throw',
			'ok 2 should be strictly equal',
			'not ok 3 TypeError: ' + lengthMessage,
			'  ---',
			'    operator: error',
			'    stack: |-',
			'      TypeError: ' + lengthMessage,
			'          at myCode ($TEST/async-await/async-bug.js:$LINE:$COL)',
			'          at Test.myTest ($TEST/async-await/async-bug.js:$LINE:$COL)',
			'  ...',
			'',
			'1..3',
			'# tests 3',
			'# pass  2',
			'# fail  1',
			'',
			''
		]);
		t.same(r.exitCode, 1);

		var stderr = r.stderr.toString('utf8');

		t.same(stderr, '');
		t.end();
	});
});
