'use strict';
const common = require('../common');
if ((!common.hasCrypto) || (!common.hasIntl)) {
  common.skip('ESLint tests require crypto and Intl');
}
common.skipIfEslintMissing();

const RuleTester = require('../../tools/eslint/node_modules/eslint').RuleTester;
const rule = require('../../tools/eslint-rules/must-call-assert');

const message = 'Assertions must be wrapped into `common.mustSucceed`, `common.mustCall` or `common.mustCallAtLeast`';

const tester = new RuleTester();
tester.run('must-call-assert', rule, {
  valid: [
    'assert.strictEqual(2+2, 4)',
    'process.on("message", common.mustCallAtLeast((code) => {assert.strictEqual(code, 0)}));',
    'process.once("message", common.mustCall((code) => {assert.strictEqual(code, 0)}));',
    'process.once("message", common.mustCall((code) => {if(2+2 === 5) { assert.strictEqual(code, 0)} }));',
    'process.once("message", common.mustCall((code) => { (() => assert.strictEqual(code, 0))(); }));',
    'someAsyncTask(common.mustSucceed((code) => { (() => assert.strictEqual(code, 0))(); }));',
    'someAsyncTask(mustSucceed((code) => { (() => assert.strictEqual(code, 0))(); }));',
    '(async () => {await assert.rejects(fun())})().then(common.mustCall())',
    '[1, true].forEach((val) => assert.strictEqual(fun(val), 0));',
    'const assert = require("node:assert")',
    'const assert = require("assert")',
    'const assert = require("assert/strict")',
    'const assert = require("node:assert/strict")',
    'import assert from "assert"',
    'import * as assert from "assert"',
    'import assert from "assert/strict"',
    'import * as assert from "assert/strict"',
    'import assert from "node:assert"',
    'import * as assert from "node:assert"',
    'import assert from "node:assert/strict"',
    'import * as assert from "node:assert/strict"',
    `
    assert.throws(() => {}, (err) => {
      assert.strictEqual(err, 5);
    });
    process.on('exit', () => {
      assert.ok();
    });
    process.once('exit', () => {
      assert.ok();
    });
    process.on('message', () => {
      assert.fail('error message');
    });
    Promise.resolve().then((arg) => {
      assert.ok(arg);
    }).then(common.mustCall());
    new Promise(() => {
      assert.ok(global.prop);
    }).then(common.mustCall());
    process.nextTick(() => {
      assert.ok(String);
    });
    `,
    `
    import test from 'node:test';
    import assert from 'node:assert';

    test("whatever", () => {
      assert.strictEqual(2+2, 5);
    });
    `,
    `
    import test from 'node:test';
    import assert from 'node:assert';

    describe("whatever", () => {
      it("should not be reported", async (t) => {
        assert.strictEqual(2+2, 5);
        await t.test("name", () => {
          assert.ok(global.test);
        });
      });
    });
    `,
    `
    process.on("message", common.mustCall(() => {
      Promise.all([].map(async (val) => {
        val = await asyncTask(val);
        assert.strictEqual(val, 3);
      })).then(common.mustCall());
      Promise.all(Object.keys(keys).map(async (val) => {
        val = await asyncTask(val);
        assert.strictEqual(val, 3);
      })).then(common.mustCall());
    }));
    `,
    `
    spawnSyncAndAssert(
      outputFile,
      {
        env: {
          NODE_DEBUG_NATIVE: 'SEA,MKSNAPSHOT',
          ...process.env,
        },
      },
      {
        trim: true,
        stdout: 'Hello from snapshot',
        stderr(output) {
          assert.doesNotMatch(
            output,
            /Single executable application is an experimental feature/);
        },
      },
    );
    `,
    'eval("(" + function() {} + ")()")',
    // eslint-disable-next-line no-template-curly-in-string
    'eval(`(${function() {}})()`)',
    'promise.then(mustCall())',
    'promise.then(mustNotCall("expected a never settling promise"))',
    'promise.then(common.mustCall((val) => { assert.ok(val); }))',
    'await promise.then((val) => { assert.ok(val); })',
    'checkSupportReusePort().then(test, () => common.skip("reuse port is not supported"))',
  ],
  invalid: [
    {
      code: 'process.on("message", (code) => assert.strictEqual(code, 0))',
      errors: [{ message }],
    },
    {
      code: `
      process.once("message", () => {
        process.once("message", common.mustCall((code) => {
          assert.strictEqual(code, 0);
        }));
      });
      `,
      errors: [{ message }],
    },
    {
      code: 'function test() { process.on("message", (code) => assert.strictEqual(code, 0)) }',
      errors: [{ message }],
    },
    {
      code: 'process.once("message", (code) => {if(2+2 === 5) { assert.strictEqual(code, 0)} });',
      errors: [{ message }],
    },
    {
      code: 'process.once("message", (code) => { assert(code) });',
      errors: [{ message }],
    },
    {
      code: 'process.once("message", (code) => { setTimeout(mustCall()); });',
      errors: [{ message }],
    },
    {
      code: 'process.once("message", (code) => { (() => { assert.strictEqual(code, 0)})(); });',
      errors: [{ message }],
    },
    {
      code: 'child.once("exit", common.mustCall((code) => {setImmediate(() => { assert.strictEqual(code, 0)}); }));',
      errors: [{ message }],
    },
    {
      code: 'require("node:assert").strictEqual(2+2, 5)',
      errors: [{ message: 'Only assign `node:assert` to `assert`' }],
    },
    {
      code: 'const { strictEqual } = require("node:assert")',
      errors: [{ message: 'Only assign `node:assert` to `assert`' }],
    },
    {
      code: 'const { strictEqual } = require("node:assert/strict")',
      errors: [{ message: 'Only assign `node:assert` to `assert`' }],
    },
    {
      code: 'const { strictEqual } = require("assert")',
      errors: [{ message: 'Only assign `node:assert` to `assert`' }],
    },
    {
      code: 'const { strictEqual } = require("assert/strict")',
      errors: [{ message: 'Only assign `node:assert` to `assert`' }],
    },
    {
      code: 'const someOtherName = require("assert")',
      errors: [{ message: 'Only assign `node:assert` to `assert`' }],
    },
    {
      code: 'import assert, { strictEqual } from "assert"',
      errors: [{ message: 'Only assign `node:assert` to `assert`' }],
    },
    {
      code: 'import * as someOtherName from "assert"',
      errors: [{ message: 'Only assign `node:assert` to `assert`' }],
    },
    {
      code: 'import someOtherName from "assert"',
      errors: [{ message: 'Only assign `node:assert` to `assert`' }],
    },
    {
      code: 'import "assert"',
      errors: [{ message: 'Only assign `node:assert` to `assert`' }],
    },
    {
      code: 'import { strictEqual } from "node:assert"',
      errors: [{ message: 'Only assign `node:assert` to `assert`' }],
    },
    {
      code: 'import assert, { strictEqual } from "node:assert"',
      errors: [{ message: 'Only assign `node:assert` to `assert`' }],
    },
    {
      code: 'import * as someOtherName from "node:assert"',
      errors: [{ message: 'Only assign `node:assert` to `assert`' }],
    },
    {
      code: 'import someOtherName from "node:assert"',
      errors: [{ message: 'Only assign `node:assert` to `assert`' }],
    },
    {
      code: 'import "node:assert"',
      errors: [{ message: 'Only assign `node:assert` to `assert`' }],
    },
    {
      code: 'promise.then(common.mustCall((val) => assert.ok(val)))',
      errors: [{ message: 'Add an additional `.then(common.mustCall())` to detect if resulting promise settles' }],
    },
    {
      code: 'promise.then(common.mustCall(async (val) => { assert.ok(val); }))',
      errors: [{ message: 'Add an additional `.then(common.mustCall())` to detect if resulting promise settles' }],
    },
    {
      code: 'promise.then(common.mustCall((val) => { assert.ok(val); return; }))',
      errors: [{ message: 'Cannot mix `common.mustCall` and return statement inside a `.then` chain' }],
    },
    {
      code: 'promise.then((val) => val).then(common.mustCall((val) => { assert.ok(val); })).then(common.mustCall())',
      errors: [{ message: 'in a `.then` chain, only use mustCall as the last node' }],
    },
    {
      code: 'promise.then((val) => val).then((val) => { assert.ok(val); })',
      errors: [{
        message: 'Last item of `.then` list must use `mustCall()` or `mustNotCall("expected never settling promise")`',
      }],
    },
    {
      code: 'promise.then((val) => { assert.ok(val); })',
      errors: [{
        message: 'Last item of `.then` list must use `mustCall()` or `mustNotCall("expected never settling promise")`',
      }],
    },
    {
      code: 'promise.then((val) => { assert.ok(val); }).then(common.mustCallAtLeast())',
      errors: [{
        message: 'Last item of `.then` list must use `mustCall()` or `mustNotCall("expected never settling promise")`',
      }],
    },
  ]
});
