diff --git a/.github/maintainers_guide.md b/.github/maintainers_guide.md index 29fdfd0ef..83d2ba86c 100644 --- a/.github/maintainers_guide.md +++ b/.github/maintainers_guide.md @@ -15,13 +15,13 @@ All you need to work with this project is a supported version of [Node.js](https #### Unit Tests -This package has unit tests for most files in the same directory the code is in with the suffix `.spec` (i.e. `exampleFile.spec.ts`). You can run the entire test suite using the npm script `npm test`. This command is also executed by GitHub Actions, the continuous integration service, for every Pull Request and branch. The coverage is computed with the `codecov` package. The tests themselves are run using the `mocha` test runner. +This package has unit tests for most files in the same directory the code is in with the suffix `.spec` (i.e. `exampleFile.spec.ts`). You can run the entire test suite using the npm script `npm test`. This command is also executed by GitHub Actions, the continuous integration service, for every Pull Request and branch. Coverage is collected with Node.js's built-in test coverage support and uploaded by CI. The tests themselves are run using Node.js's built-in test runner. Test code should be written in syntax that runs on the oldest supported Node.js version. This ensures that backwards compatibility is tested and the APIs look reasonable in versions of Node.js that do not support the most modern syntax. #### Debugging -A useful trick for debugging inside tests is to use the Chrome Debugging Protocol feature of Node.js to set breakpoints and interactively debug. In order to do this you must run mocha directly. This means that you should have already linted the source (`npm run lint`), manually. You then run the tests using the following command: `./node_modules/.bin/mocha test/{test-name}.js --debug-brk --inspect` (replace {test-name} with an actual test file). +A useful trick for debugging inside tests is to use the Chrome Debugging Protocol feature of Node.js to set breakpoints and interactively debug. In order to do this you should have already linted the source (`npm run lint`), manually. You can then run a specific test file with Node.js's test runner, for example: `node --inspect-brk --import tsx --test test/unit/{test-name}.spec.ts` (replace `{test-name}` with an actual test file path). #### Local Development diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 81c6f31a4..c3f306aac 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -40,7 +40,7 @@ jobs: uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 with: token: ${{ secrets.CODECOV_TOKEN }} - directory: coverage + files: coverage/lcov.info fail_ci_if_error: true verbose: true notifications: diff --git a/.vscode/launch.json b/.vscode/launch.json index 9b9055530..c1ebc81b6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,14 +8,19 @@ "type": "node", "request": "launch", "name": "Spec tests", - "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "runtimeExecutable": "node", "stopOnEntry": false, - "args": ["--config", ".mocharc.json", "--no-timeouts", "src/*.spec.ts", "src/**/*.spec.ts"], + "args": [ + "--inspect-brk", + "--import", + "tsx", + "--test", + "test/unit/**/*.spec.ts" + ], "cwd": "${workspaceFolder}", - "runtimeExecutable": null, "env": { "NODE_ENV": "testing", - "TS_NODE_PROJECT": "tsconfig.test.json" + "TSX_TSCONFIG_PATH": "tsconfig.test.json" }, "skipFiles": ["/**"] } diff --git a/AGENTS.md b/AGENTS.md index 32f728f65..1642e44ee 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,8 +31,8 @@ npm test # Full pipeline: build -> lint -> type tests -> unit test npm run build # Clean build (rm dist/ + tsc compilation) npm run lint # Biome check (formatting + linting) npm run lint:fix # Biome auto-fix -npm run test:unit # Unit tests only (mocha) -npm run test:coverage # Unit tests with coverage (c8) +npm run test:unit # Unit tests only (Node.js test runner) +npm run test:coverage # Unit tests with built-in Node.js coverage npm run test:types # Type definition tests (tsd) npm run watch # Watch mode for development (rebuilds on src/ changes) ``` @@ -171,9 +171,9 @@ test/types/ # tsd type tests ### Test Conventions - **Test files** use `*.spec.ts` suffix -- **Assertions** use chai (`expect`, `assert`) +- **Assertions** in tests use direct `node:assert/strict` and `sinon.assert` imports - **Mocking** uses sinon (`stub`, `spy`, `fake`) and proxyquire for module-level dependency replacement -- **Test config** in `test/unit/.mocharc.json` +- **Test config** lives in `package.json` scripts plus direct `node:test` imports in the spec files - **Where to put new tests:** Mirror the source structure. For `src/Foo.ts`, add `test/unit/Foo.spec.ts`. For `src/receivers/Bar.ts`, add `test/unit/receivers/Bar.spec.ts`. ### CI diff --git a/package-lock.json b/package-lock.json index 35bc4640a..4e4377f4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,21 +23,15 @@ "@biomejs/biome": "^2.4.15", "@changesets/cli": "^2.29.8", "@tsconfig/node20": "^20.1.5", - "@types/chai": "^4.1.7", - "@types/mocha": "^10.0.1", "@types/node": "20.19.0", "@types/proxyquire": "^1.3.31", "@types/sinon": "^17.0.4", "@types/tsscmp": "^1.0.0", - "c8": "^10.1.2", - "chai": "~4.3.0", - "mocha": "^10.2.0", "proxyquire": "^2.1.3", "shx": "^0.3.2", "sinon": "^20.0.0", - "source-map-support": "^0.5.12", - "ts-node": "^10.9.2", "tsd": "^0.31.2", + "tsx": "^4.20.6", "typescript": "5.3.3" }, "engines": { @@ -48,119 +42,6 @@ "@types/express": "^5.0.0" } }, - "examples/custom-properties": { - "name": "bolt-js-custom-properties-app", - "version": "1.0.0", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@slack/bolt": "^4" - } - }, - "examples/custom-receiver": { - "name": "bolt-oauth-example", - "version": "1.0.0", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@koa/router": "^15.4.0", - "@slack/bolt": "^4", - "@slack/logger": "^4.0.0", - "@slack/oauth": "^3", - "dotenv": "^17", - "fastify": "^5", - "koa": "^3" - }, - "devDependencies": { - "@tsconfig/node18": "^18.2.6", - "@types/koa": "^3.0.2", - "@types/node": "^24", - "ts-node": "^10", - "typescript": "6.0.3" - } - }, - "examples/deploy-aws-lambda": { - "name": "bolt-js-aws-lambda-receiver", - "version": "1.0.0", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@slack/bolt": "^4.7.0" - }, - "devDependencies": { - "serverless": "^4.34.0", - "serverless-offline": "^14.5.0" - } - }, - "examples/deploy-heroku": { - "name": "bolt-js-heroku-app", - "version": "1.0.0", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@slack/bolt": "^4.7.0" - } - }, - "examples/getting-started-typescript": { - "version": "1.0.0", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@slack/bolt": "^4.7.0", - "dotenv": "^17" - }, - "devDependencies": { - "@tsconfig/node18": "^18.2.6", - "@types/node": "^24", - "ts-node": "^10", - "typescript": "6.0.3" - } - }, - "examples/message-metadata": { - "name": "bolt-message-metadata-example", - "version": "1.0.0", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@slack/bolt": "^4.7.0" - } - }, - "examples/oauth": { - "name": "bolt-oauth-example", - "version": "1.0.0", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@slack/bolt": "^4.7.0" - } - }, - "examples/oauth-express-receiver": { - "name": "bolt-oauth-express-example", - "version": "1.0.0", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@slack/bolt": "^4.7.0" - } - }, - "examples/socket-mode": { - "name": "bolt-socket-mode-example", - "version": "1.0.0", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@slack/bolt": "^4.7.0" - } - }, - "examples/socket-mode-oauth": { - "name": "socket-mode-oauth-example", - "version": "1.0.0", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@slack/bolt": "^4.7.0" - } - }, "node_modules/@babel/code-frame": { "version": "7.29.0", "dev": true, @@ -190,18 +71,8 @@ "node": ">=6.9.0" } }, - "node_modules/@bcoe/v8-coverage": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/@biomejs/biome": { "version": "2.4.15", - "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.15.tgz", - "integrity": "sha512-j5VH3a/h/HXTKBM50MDMxRCzkeLv9S2XJcW2WgnZT1+xyisi+0bISrXR82gCX+8S9lvK0skEvHJRN+3Ktr2hlw==", "dev": true, "license": "MIT OR Apache-2.0", "bin": { @@ -295,8 +166,6 @@ }, "node_modules/@biomejs/cli-linux-x64": { "version": "2.4.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.15.tgz", - "integrity": "sha512-0jj7THz12GbUOLmMibktK6DZjqz2zV64KFxyBtcFTKPiiOIY0a7vns1elpO1dERvxpsZ5ik0oFfz0oGwFde1+g==", "cpu": [ "x64" ], @@ -312,8 +181,6 @@ }, "node_modules/@biomejs/cli-linux-x64-musl": { "version": "2.4.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.15.tgz", - "integrity": "sha512-CNq/9W38SYSH023lfcQ4KKU8K0YX8T//FZUhcgtMMRABDojx5XsMV7jlweAvGSl389wJQB29Qo6Zb/a+jdvt+w==", "cpu": [ "x64" ], @@ -569,132 +436,464 @@ "prettier": "^2.7.1" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/@inquirer/external-editor": { - "version": "1.0.3", + "node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "chardet": "^2.1.1", - "iconv-lite": "^0.7.0" - }, + "optional": true, + "os": [ + "android" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", + "node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", + "node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": ">=18" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=18" } }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.2.0", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">=18" } }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", + "node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=18" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", "dev": true, "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@jest/schemas": { @@ -708,28 +907,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, "node_modules/@manypkg/find-root": { "version": "1.1.0", "dev": true, @@ -870,15 +1047,6 @@ "node": ">= 8" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@sinclair/typebox": { "version": "0.27.10", "dev": true, @@ -996,26 +1164,6 @@ "npm": ">=9.6.4" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.12", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "dev": true, - "license": "MIT" - }, "node_modules/@tsconfig/node20": { "version": "20.1.9", "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.9.tgz", @@ -1040,11 +1188,6 @@ "@types/node": "*" } }, - "node_modules/@types/chai": { - "version": "4.3.20", - "dev": true, - "license": "MIT" - }, "node_modules/@types/connect": { "version": "3.4.38", "license": "MIT", @@ -1093,11 +1236,6 @@ "license": "MIT", "peer": true }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "dev": true, - "license": "MIT" - }, "node_modules/@types/json-schema": { "version": "7.0.15", "dev": true, @@ -1116,11 +1254,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/mocha": { - "version": "10.0.10", - "dev": true, - "license": "MIT" - }, "node_modules/@types/ms": { "version": "2.1.0", "license": "MIT" @@ -1134,12 +1267,6 @@ "undici-types": "~6.21.0" } }, - "node_modules/@types/node/node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "license": "MIT" - }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", "dev": true, @@ -1162,6 +1289,8 @@ }, "node_modules/@types/retry": { "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "license": "MIT" }, "node_modules/@types/send": { @@ -1210,28 +1339,6 @@ "node": ">= 0.6" } }, - "node_modules/acorn": { - "version": "8.16.0", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.5", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/ansi-colors": { "version": "4.1.3", "dev": true, @@ -1276,23 +1383,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "dev": true, - "license": "MIT" - }, "node_modules/argparse": { "version": "2.0.1", "dev": true, @@ -1314,14 +1404,6 @@ "node": ">=0.10.0" } }, - "node_modules/assertion-error": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "dev": true, @@ -1338,17 +1420,6 @@ "node": ">=4" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/body-parser": { "version": "2.2.2", "license": "MIT", @@ -1371,14 +1442,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/brace-expansion": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/braces": { "version": "3.0.3", "dev": true, @@ -1390,20 +1453,10 @@ "node": ">=8" } }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "dev": true, - "license": "ISC" - }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "license": "BSD-3-Clause" }, - "node_modules/buffer-from": { - "version": "1.1.2", - "dev": true, - "license": "MIT" - }, "node_modules/bytes": { "version": "3.1.2", "license": "MIT", @@ -1411,38 +1464,6 @@ "node": ">= 0.8" } }, - "node_modules/c8": { - "version": "10.1.3", - "dev": true, - "license": "ISC", - "dependencies": { - "@bcoe/v8-coverage": "^1.0.1", - "@istanbuljs/schema": "^0.1.3", - "find-up": "^5.0.0", - "foreground-child": "^3.1.1", - "istanbul-lib-coverage": "^3.2.0", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.1.6", - "test-exclude": "^7.0.1", - "v8-to-istanbul": "^9.0.0", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1" - }, - "bin": { - "c8": "bin/c8.js" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "monocart-coverage-reports": "^2" - }, - "peerDependenciesMeta": { - "monocart-coverage-reports": { - "optional": true - } - } - }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "license": "MIT", @@ -1492,23 +1513,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/chai": { - "version": "4.3.10", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.0.8" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/chalk": { "version": "4.1.2", "dev": true, @@ -1529,53 +1533,6 @@ "dev": true, "license": "MIT" }, - "node_modules/check-error": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/color-convert": { "version": "2.0.1", "dev": true, @@ -1615,11 +1572,6 @@ "node": ">= 0.6" } }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, "node_modules/cookie": { "version": "0.7.2", "license": "MIT", @@ -1634,11 +1586,6 @@ "node": ">=6.6.0" } }, - "node_modules/create-require": { - "version": "1.1.1", - "dev": true, - "license": "MIT" - }, "node_modules/cross-spawn": { "version": "7.0.6", "dev": true, @@ -1698,17 +1645,6 @@ "node": ">=0.10.0" } }, - "node_modules/deep-eql": { - "version": "4.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/depd": { "version": "2.0.0", "license": "MIT", @@ -1724,14 +1660,6 @@ "node": ">=8" } }, - "node_modules/diff": { - "version": "5.2.2", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/diff-sequences": { "version": "29.6.3", "dev": true, @@ -1763,11 +1691,6 @@ "node": ">= 0.4" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "dev": true, - "license": "MIT" - }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "license": "Apache-2.0", @@ -1835,29 +1758,50 @@ "node": ">= 0.4" } }, - "node_modules/escalade": { - "version": "3.2.0", + "node_modules/esbuild": { + "version": "0.28.0", "dev": true, + "hasInstallScript": true, "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, "engines": { - "node": ">=6" + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" } }, "node_modules/escape-html": { "version": "1.0.3", "license": "MIT" }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint-formatter-pretty": { "version": "4.1.0", "dev": true, @@ -1905,6 +1849,8 @@ }, "node_modules/eventemitter3": { "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, "node_modules/express": { @@ -2002,66 +1948,28 @@ "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "2.1.1", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" + }, + "engines": { + "node": ">=8" } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "dev": true, - "license": "ISC", + "node_modules/finalhandler": { + "version": "2.1.1", + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { - "node": ">=14" + "node": ">= 18.0.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/forwarded": { @@ -2098,7 +2006,10 @@ }, "node_modules/fsevents": { "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ @@ -2115,22 +2026,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "license": "MIT", @@ -2164,24 +2059,6 @@ "node": ">= 0.4" } }, - "node_modules/glob": { - "version": "8.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "5.1.2", "dev": true, @@ -2263,14 +2140,6 @@ "node": ">= 0.4" } }, - "node_modules/he": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, "node_modules/hosted-git-info": { "version": "4.1.0", "dev": true, @@ -2282,11 +2151,6 @@ "node": ">=10" } }, - "node_modules/html-escaper": { - "version": "2.0.2", - "dev": true, - "license": "MIT" - }, "node_modules/http-errors": { "version": "2.0.1", "license": "MIT", @@ -2384,17 +2248,6 @@ "dev": true, "license": "MIT" }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-core-module": { "version": "2.16.1", "dev": true, @@ -2499,53 +2352,6 @@ "dev": true, "license": "ISC" }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/jest-diff": { "version": "29.7.0", "dev": true, @@ -2647,20 +2453,6 @@ "dev": true, "license": "MIT" }, - "node_modules/locate-path": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lodash.includes": { "version": "4.3.0", "license": "MIT" @@ -2709,14 +2501,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/loupe": { - "version": "2.3.7", - "dev": true, - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.1" - } - }, "node_modules/lru-cache": { "version": "6.0.0", "dev": true, @@ -2728,25 +2512,6 @@ "node": ">=10" } }, - "node_modules/make-dir": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "dev": true, - "license": "ISC" - }, "node_modules/map-obj": { "version": "4.3.0", "dev": true, @@ -2875,17 +2640,6 @@ "node": ">=4" } }, - "node_modules/minimatch": { - "version": "5.1.9", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/minimist": { "version": "1.2.8", "dev": true, @@ -2907,97 +2661,6 @@ "node": ">= 6" } }, - "node_modules/minipass": { - "version": "7.1.3", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mocha": { - "version": "10.8.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.3", - "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", - "debug": "^4.3.5", - "diff": "^5.2.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^8.1.0", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", - "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^16.2.0", - "yargs-parser": "^20.2.9", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/mocha/node_modules/cliui": { - "version": "7.0.4", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/mocha/node_modules/yargs": { - "version": "16.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/yargs-parser": { - "version": "20.2.9", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/module-not-found-error": { "version": "1.0.1", "dev": true, @@ -3036,14 +2699,6 @@ "node": ">=10" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object-inspect": { "version": "1.13.4", "license": "MIT", @@ -3089,39 +2744,13 @@ }, "node_modules/p-finally": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", "license": "MIT", "engines": { "node": ">=4" } }, - "node_modules/p-limit": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-map": { "version": "2.1.0", "dev": true, @@ -3132,6 +2761,8 @@ }, "node_modules/p-queue": { "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", "license": "MIT", "dependencies": { "eventemitter3": "^4.0.4", @@ -3146,10 +2777,14 @@ }, "node_modules/p-queue/node_modules/eventemitter3": { "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "license": "MIT" }, "node_modules/p-retry": { "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", "license": "MIT", "dependencies": { "@types/retry": "0.12.0", @@ -3161,6 +2796,8 @@ }, "node_modules/p-timeout": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", "license": "MIT", "dependencies": { "p-finally": "^1.0.0" @@ -3177,11 +2814,6 @@ "node": ">=6" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "dev": true, - "license": "BlueOak-1.0.0" - }, "node_modules/package-manager-detector": { "version": "0.2.11", "dev": true, @@ -3243,26 +2875,6 @@ "dev": true, "license": "MIT" }, - "node_modules/path-scurry": { - "version": "1.11.1", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "dev": true, - "license": "ISC" - }, "node_modules/path-to-regexp": { "version": "8.4.2", "license": "MIT", @@ -3279,14 +2891,6 @@ "node": ">=8" } }, - "node_modules/pathval": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/picocolors": { "version": "1.1.1", "dev": true, @@ -3439,14 +3043,6 @@ "node": ">=8" } }, - "node_modules/randombytes": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/range-parser": { "version": "1.2.1", "license": "MIT", @@ -3620,19 +3216,8 @@ "argparse": "^1.0.7", "esprima": "^4.0.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, "node_modules/rechoir": { @@ -3657,14 +3242,6 @@ "node": ">=8" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/resolve": { "version": "1.22.11", "dev": true, @@ -3694,6 +3271,8 @@ }, "node_modules/retry": { "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", "license": "MIT", "engines": { "node": ">= 4" @@ -3800,14 +3379,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/serve-static": { "version": "2.2.1", "license": "MIT", @@ -4025,23 +3596,6 @@ "node": ">=8" } }, - "node_modules/source-map": { - "version": "0.6.1", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, "node_modules/spawndamnit": { "version": "3.0.1", "dev": true, @@ -4104,20 +3658,6 @@ "node": ">=8" } }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "dev": true, @@ -4129,18 +3669,6 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-bom": { "version": "3.0.0", "dev": true, @@ -4160,17 +3688,6 @@ "node": ">=8" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/supports-color": { "version": "7.2.0", "dev": true, @@ -4216,85 +3733,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/test-exclude": { - "version": "7.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^10.2.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/test-exclude/node_modules/balanced-match": { - "version": "4.0.4", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "10.5.0", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/test-exclude/node_modules/glob/node_modules/minimatch": { - "version": "9.0.9", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "10.2.5", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/test-exclude/node_modules/minimatch/node_modules/brace-expansion": { - "version": "5.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "dev": true, @@ -4321,56 +3759,6 @@ "node": ">=8" } }, - "node_modules/ts-node": { - "version": "10.9.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node/node_modules/diff": { - "version": "4.0.4", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/tsd": { "version": "0.31.2", "dev": true, @@ -4398,6 +3786,23 @@ "node": ">=0.6.x" } }, + "node_modules/tsx": { + "version": "4.22.0", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-detect": { "version": "4.1.0", "dev": true, @@ -4451,6 +3856,12 @@ "node": ">=20.18.1" } }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, "node_modules/universalify": { "version": "0.1.2", "dev": true, @@ -4466,33 +3877,6 @@ "node": ">= 0.8" } }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/v8-to-istanbul/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "dev": true, @@ -4523,148 +3907,14 @@ "node": ">= 8" } }, - "node_modules/workerpool": { - "version": "6.5.1", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/wrappy": { "version": "1.0.2", "license": "ISC" }, - "node_modules/y18n": { - "version": "5.0.8", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/yallist": { "version": "4.0.0", "dev": true, "license": "ISC" - }, - "node_modules/yargs": { - "version": "17.7.2", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/camelcase": { - "version": "6.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs-unparser/node_modules/decamelize": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs-unparser/node_modules/is-plain-obj": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } } } } diff --git a/package.json b/package.json index d1851d8cb..c5f1f9a3f 100644 --- a/package.json +++ b/package.json @@ -32,9 +32,9 @@ "lint:fix": "npx @biomejs/biome check --write docs src test examples", "prepare": "npm run build", "test": "npm run build && npm run lint && npm run test:types && npm run test:coverage", - "test:coverage": "c8 npm run test:unit", + "test:unit": "TSX_TSCONFIG_PATH=tsconfig.test.json tsx --test test/unit/**/*.spec.ts", + "test:coverage": "npm run build && shx mkdir -p coverage && TSX_TSCONFIG_PATH=tsconfig.test.json node --experimental-test-coverage --test-reporter=spec --test-reporter-destination=stdout --test-reporter=lcov --test-reporter-destination=coverage/lcov.info --import tsx --test test/unit/**/*.spec.ts", "test:types": "tsd --files test/types", - "test:unit": "TS_NODE_PROJECT=tsconfig.json mocha --config test/unit/.mocharc.json", "version": "npm run changeset version && npm install", "watch": "npx nodemon --watch 'src' --ext 'ts' --exec npm run build" }, @@ -61,21 +61,15 @@ "@biomejs/biome": "^2.4.15", "@changesets/cli": "^2.29.8", "@tsconfig/node20": "^20.1.5", - "@types/chai": "^4.1.7", - "@types/mocha": "^10.0.1", "@types/node": "20.19.0", "@types/proxyquire": "^1.3.31", "@types/sinon": "^17.0.4", "@types/tsscmp": "^1.0.0", - "c8": "^10.1.2", - "chai": "~4.3.0", - "mocha": "^10.2.0", "proxyquire": "^2.1.3", "shx": "^0.3.2", "sinon": "^20.0.0", - "source-map-support": "^0.5.12", - "ts-node": "^10.9.2", "tsd": "^0.31.2", + "tsx": "^4.20.6", "typescript": "5.3.3" }, "peerDependencies": { diff --git a/test/unit/.mocharc.json b/test/unit/.mocharc.json deleted file mode 100644 index ad1427e24..000000000 --- a/test/unit/.mocharc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "require": ["ts-node/register", "source-map-support/register"], - "spec": ["test/unit/**/*.spec.ts"], - "timeout": 10000 -} diff --git a/test/unit/App/basic.spec.ts b/test/unit/App/basic.spec.ts index 11ea3a2a3..90fad4131 100644 --- a/test/unit/App/basic.spec.ts +++ b/test/unit/App/basic.spec.ts @@ -1,5 +1,6 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; import { LogLevel } from '@slack/logger'; -import { assert } from 'chai'; import sinon from 'sinon'; import { ErrorCode } from '../../../src/errors'; import SocketModeReceiver from '../../../src/receivers/SocketModeReceiver'; @@ -34,13 +35,17 @@ describe('App basic features', () => { const MockApp = importApp(overrides); const app = new MockApp({ token: '', signingSecret: '', port: 9999 }); // biome-ignore lint/complexity/useLiteralKeys: reaching into private fields - assert.propertyVal(app['receiver'], 'port', 9999); + assert.ok(app['receiver'] && typeof app['receiver'] === 'object'); + assert.ok('port' in app.receiver); + assert.deepStrictEqual((app.receiver as unknown as Record).port, 9999); }); it('should accept a port value under installerOptions', async () => { const MockApp = importApp(overrides); const app = new MockApp({ token: '', signingSecret: '', port: 7777, installerOptions: { port: 9999 } }); // biome-ignore lint/complexity/useLiteralKeys: reaching into private fields - assert.propertyVal(app['receiver'], 'port', 9999); + assert.ok(app['receiver'] && typeof app['receiver'] === 'object'); + assert.ok('port' in app.receiver); + assert.deepStrictEqual((app.receiver as unknown as Record).port, 9999); }); }); @@ -65,7 +70,9 @@ describe('App basic features', () => { installationStore, }); // biome-ignore lint/complexity/useLiteralKeys: reaching into private fields - assert.propertyVal(app['receiver'], 'httpServerPort', 9999); + assert.ok(app['receiver'] && typeof app['receiver'] === 'object'); + assert.ok('httpServerPort' in app.receiver); + assert.deepStrictEqual((app.receiver as unknown as Record).httpServerPort, 9999); }); it('should accept a port value under installerOptions', async () => { const MockApp = importApp(overrides); @@ -82,7 +89,9 @@ describe('App basic features', () => { installationStore, }); // biome-ignore lint/complexity/useLiteralKeys: reaching into private fields - assert.propertyVal(app['receiver'], 'httpServerPort', 9999); + assert.ok(app['receiver'] && typeof app['receiver'] === 'object'); + assert.ok('httpServerPort' in app.receiver); + assert.deepStrictEqual((app.receiver as unknown as Record).httpServerPort, 9999); }); }); @@ -93,12 +102,12 @@ describe('App basic features', () => { const MockApp = importApp(overrides); const app = new MockApp({ token: '', signingSecret: '' }); // TODO: verify that the fake bot ID and fake bot user ID are retrieved - assert.instanceOf(app, MockApp); + assert.ok(app instanceof MockApp); }); it('should pass the given token to app.client', async () => { const MockApp = importApp(overrides); const app = new MockApp({ token: 'xoxb-foo-bar', signingSecret: '' }); - assert.isDefined(app.client); + assert.notStrictEqual(app.client, undefined); assert.equal(app.client.token, 'xoxb-foo-bar'); }); }); @@ -114,7 +123,12 @@ describe('App basic features', () => { new MockApp({ signingSecret: '' }); assert.fail(); } catch (error) { - assert.propertyVal(error, 'code', ErrorCode.AppInitializationError); + assert.ok(error && typeof error === 'object'); + assert.ok('code' in error); + assert.deepStrictEqual( + (error as unknown as Record).code, + ErrorCode.AppInitializationError, + ); } }); it('should fail when both a token and authorize callback are specified', async () => { @@ -124,7 +138,12 @@ describe('App basic features', () => { new MockApp({ token: '', authorize: authorizeCallback, signingSecret: '' }); assert.fail(); } catch (error) { - assert.propertyVal(error, 'code', ErrorCode.AppInitializationError); + assert.ok(error && typeof error === 'object'); + assert.ok('code' in error); + assert.deepStrictEqual( + (error as unknown as Record).code, + ErrorCode.AppInitializationError, + ); assert(authorizeCallback.notCalled); } }); @@ -135,7 +154,12 @@ describe('App basic features', () => { new MockApp({ token: '', clientId: '', clientSecret: '', stateSecret: '', signingSecret: '' }); assert.fail(); } catch (error) { - assert.propertyVal(error, 'code', ErrorCode.AppInitializationError); + assert.ok(error && typeof error === 'object'); + assert.ok('code' in error); + assert.deepStrictEqual( + (error as unknown as Record).code, + ErrorCode.AppInitializationError, + ); assert(authorizeCallback.notCalled); } }); @@ -152,7 +176,12 @@ describe('App basic features', () => { }); assert.fail(); } catch (error) { - assert.propertyVal(error, 'code', ErrorCode.AppInitializationError); + assert.ok(error && typeof error === 'object'); + assert.ok('code' in error); + assert.deepStrictEqual( + (error as unknown as Record).code, + ErrorCode.AppInitializationError, + ); assert(authorizeCallback.notCalled); } }); @@ -171,7 +200,12 @@ describe('App basic features', () => { new MockApp({ authorize: noop }); assert.fail(); } catch (error) { - assert.propertyVal(error, 'code', ErrorCode.AppInitializationError); + assert.ok(error && typeof error === 'object'); + assert.ok('code' in error); + assert.deepStrictEqual( + (error as unknown as Record).code, + ErrorCode.AppInitializationError, + ); } }); it('should fail when both socketMode and a custom receiver are specified', async () => { @@ -181,7 +215,12 @@ describe('App basic features', () => { new MockApp({ token: '', signingSecret: '', socketMode: true, receiver: fakeReceiver }); assert.fail(); } catch (error) { - assert.propertyVal(error, 'code', ErrorCode.AppInitializationError); + assert.ok(error && typeof error === 'object'); + assert.ok('code' in error); + assert.deepStrictEqual( + (error as unknown as Record).code, + ErrorCode.AppInitializationError, + ); } }); it('should succeed when both socketMode and SocketModeReceiver are specified', async () => { @@ -220,7 +259,7 @@ describe('App basic features', () => { const dummyConvoStore = createFakeConversationStore(); const MockApp = importApp(overrides); const app = new MockApp({ convoStore: dummyConvoStore, authorize: noop, signingSecret: '' }); - assert.instanceOf(app, MockApp); + assert.ok(app instanceof MockApp); assert(fakeConversationContext.firstCall.calledWith(dummyConvoStore)); }); }); @@ -231,7 +270,12 @@ describe('App basic features', () => { new MockApp({ token: '', signingSecret: '', redirectUri: 'http://example.com/redirect' }); // eslint-disable-line no-new assert.fail(); } catch (error) { - assert.propertyVal(error, 'code', ErrorCode.AppInitializationError); + assert.ok(error && typeof error === 'object'); + assert.ok('code' in error); + assert.deepStrictEqual( + (error as unknown as Record).code, + ErrorCode.AppInitializationError, + ); } }); it('should fail when missing installerOptions.redirectUriPath', async () => { @@ -245,7 +289,12 @@ describe('App basic features', () => { }); assert.fail(); } catch (error) { - assert.propertyVal(error, 'code', ErrorCode.AppInitializationError); + assert.ok(error && typeof error === 'object'); + assert.ok('code' in error); + assert.deepStrictEqual( + (error as unknown as Record).code, + ErrorCode.AppInitializationError, + ); } }); }); @@ -306,14 +355,15 @@ describe('App basic features', () => { signingSecret: 'invalid-one', deferInitialization: true, }); - assert.instanceOf(app, MockApp); + assert.ok(app instanceof MockApp); try { await app.start(); assert.fail('The start() method should fail before init() call'); } catch (err) { - assert.propertyVal( - err, - 'message', + assert.ok(err && typeof err === 'object'); + assert.ok('message' in err); + assert.deepStrictEqual( + (err as unknown as Record).message, 'This App instance is not yet initialized. Call `await App#init()` before starting the app.', ); } @@ -322,7 +372,9 @@ describe('App basic features', () => { assert.fail('The init() method should fail here'); } catch (err) { console.log(err); - assert.propertyVal(err, 'message', exception); + assert.ok(err && typeof err === 'object'); + assert.ok('message' in err); + assert.deepStrictEqual((err as unknown as Record).message, exception); } }); }); @@ -336,8 +388,12 @@ describe('App basic features', () => { const fakeLogger = createFakeLogger(); const MockApp = importApp(overrides); const app = new MockApp({ logger: fakeLogger, token: '', appToken: fakeAppToken, developerMode: true }); - assert.propertyVal(app, 'logLevel', LogLevel.DEBUG); - assert.propertyVal(app, 'socketMode', true); + assert.ok(app && typeof app === 'object'); + assert.ok('logLevel' in app); + assert.deepStrictEqual((app as unknown as Record).logLevel, LogLevel.DEBUG); + assert.ok(app && typeof app === 'object'); + assert.ok('socketMode' in app); + assert.deepStrictEqual((app as unknown as Record).socketMode, true); }); }); diff --git a/test/unit/App/middlewares/arguments.spec.ts b/test/unit/App/middlewares/arguments.spec.ts index b6338bfe5..a342ca923 100644 --- a/test/unit/App/middlewares/arguments.spec.ts +++ b/test/unit/App/middlewares/arguments.spec.ts @@ -1,5 +1,6 @@ +import assert from 'node:assert/strict'; +import { beforeEach, describe, it } from 'node:test'; import type { WebClient } from '@slack/web-api'; -import { assert } from 'chai'; import sinon, { type SinonSpy } from 'sinon'; import { LogLevel } from '../../../../src/App'; import type { SayStreamFn } from '../../../../src/context/create-say-stream'; @@ -439,7 +440,7 @@ describe('App middleware and listener arguments', () => { sinon.assert.calledThrice(fakeAck); - assert.isUndefined(app.client.token); + assert.strictEqual(app.client.token, undefined); assert.equal(clients[0].token, 'xoxb-123'); assert.equal(clients[1].token, 'xoxp-456'); assert.equal(clients[2].token, 'xoxb-123'); @@ -556,8 +557,12 @@ describe('App middleware and listener arguments', () => { // Assert that each call to fakePostMessage had the right arguments for (const call of fakePostMessage.getCalls()) { const firstArg = call.args[0]; - assert.propertyVal(firstArg, 'text', dummyMessage); - assert.propertyVal(firstArg, 'channel', dummyChannelId); + assert.ok(firstArg && typeof firstArg === 'object'); + assert.ok('text' in firstArg); + assert.deepStrictEqual((firstArg as unknown as Record).text, dummyMessage); + assert.ok(firstArg && typeof firstArg === 'object'); + assert.ok('channel' in firstArg); + assert.deepStrictEqual((firstArg as unknown as Record).channel, dummyChannelId); } sinon.assert.notCalled(fakeErrorHandler); }); @@ -583,8 +588,12 @@ describe('App middleware and listener arguments', () => { // Assert that each call to fakePostMessage had the right arguments for (const call of fakePostMessage.getCalls()) { const firstArg = call.args[0]; - assert.propertyVal(firstArg, 'channel', dummyChannelId); - assert.propertyVal(firstArg, 'text', dummyMessage.text); + assert.ok(firstArg && typeof firstArg === 'object'); + assert.ok('channel' in firstArg); + assert.deepStrictEqual((firstArg as unknown as Record).channel, dummyChannelId); + assert.ok(firstArg && typeof firstArg === 'object'); + assert.ok('text' in firstArg); + assert.deepStrictEqual((firstArg as unknown as Record).text, dummyMessage.text); } sinon.assert.notCalled(fakeErrorHandler); }); @@ -645,7 +654,7 @@ describe('App middleware and listener arguments', () => { const app = new MockApp({ receiver: fakeReceiver, authorize: sinon.fake.resolves(dummyAuthorizationResult) }); app.use(async (args) => { - assert.notProperty(args, 'say'); + assert.ok(!('say' in args)); // If the above assertion fails, then it would throw an AssertionError and the following line will not be // called assertionAggregator(); @@ -689,7 +698,7 @@ describe('App middleware and listener arguments', () => { app.use(async (args) => { // biome-ignore lint/suspicious/noExplicitAny: test utility const sayStream = (args as any).sayStream as SayStreamFn; - assert.isFunction(sayStream); + assert.strictEqual(typeof sayStream, 'function'); assertionAggregator(); }); app.error(fakeErrorHandler); @@ -753,7 +762,7 @@ describe('App middleware and listener arguments', () => { const assertionAggregator = sinon.fake(); const app = new MockApp({ receiver: fakeReceiver, authorize: sinon.fake.resolves(dummyAuthorizationResult) }); app.use(async (args) => { - assert.notProperty(args, 'sayStream'); + assert.ok(!('sayStream' in args)); assertionAggregator(); }); @@ -783,7 +792,7 @@ describe('App middleware and listener arguments', () => { app.use(async (args) => { // biome-ignore lint/suspicious/noExplicitAny: test utility const setStatus = (args as any).setStatus as SetStatusFn; - assert.isFunction(setStatus); + assert.strictEqual(typeof setStatus, 'function'); assertionAggregator(); }); app.error(fakeErrorHandler); @@ -812,7 +821,7 @@ describe('App middleware and listener arguments', () => { const assertionAggregator = sinon.fake(); const app = new MockApp({ receiver: fakeReceiver, authorize: sinon.fake.resolves(dummyAuthorizationResult) }); app.use(async (args) => { - assert.notProperty(args, 'setStatus'); + assert.ok(!('setStatus' in args)); assertionAggregator(); }); @@ -967,7 +976,7 @@ describe('App middleware and listener arguments', () => { ack: fakeAck, }); - assert.isTrue(called); + assert.strictEqual(called, true); sinon.assert.calledOnce(fakeAck); }); diff --git a/test/unit/App/middlewares/global.spec.ts b/test/unit/App/middlewares/global.spec.ts index 46ab98417..dbec732b7 100644 --- a/test/unit/App/middlewares/global.spec.ts +++ b/test/unit/App/middlewares/global.spec.ts @@ -1,5 +1,6 @@ +import assert from 'node:assert/strict'; +import { beforeEach, describe, it } from 'node:test'; import type { WebClient } from '@slack/web-api'; -import { assert } from 'chai'; import sinon, { type SinonSpy } from 'sinon'; import type App from '../../../../src/App'; import type { ExtendedErrorHandlerArgs } from '../../../../src/App'; @@ -81,7 +82,7 @@ describe('App global middleware Processing', () => { assert(fakeErrorHandler.notCalled); assert(fakeMiddleware.notCalled); - assert.isAtLeast(fakeLogger.warn.callCount, invalidReceiverEvents.length); + assert.ok(fakeLogger.warn.callCount >= invalidReceiverEvents.length); }); it('should warn, send to global error handler, acknowledge, and skip when a receiver event fails authorization', async () => { @@ -103,9 +104,19 @@ describe('App global middleware Processing', () => { assert(fakeMiddleware.notCalled); assert(fakeLogger.warn.called); - assert.instanceOf(fakeErrorHandler.firstCall.args[0], AuthorizationError); - assert.propertyVal(fakeErrorHandler.firstCall.args[0], 'code', ErrorCode.AuthorizationError); - assert.strictEqual(fakeErrorHandler.firstCall.args[0].original, dummyAuthorizationError); + assert.ok(fakeErrorHandler.firstCall.args[0] instanceof Error); + assert.ok(fakeErrorHandler.firstCall.args[0] && typeof fakeErrorHandler.firstCall.args[0] === 'object'); + assert.ok('code' in fakeErrorHandler.firstCall.args[0]); + assert.deepStrictEqual( + (fakeErrorHandler.firstCall.args[0] as unknown as Record).code, + ErrorCode.AuthorizationError, + ); + assert.ok(fakeErrorHandler.firstCall.args[0] && typeof fakeErrorHandler.firstCall.args[0] === 'object'); + assert.ok('original' in fakeErrorHandler.firstCall.args[0]); + assert.deepStrictEqual( + (fakeErrorHandler.firstCall.args[0] as unknown as Record).original, + dummyAuthorizationError.original, + ); assert(fakeAck.called); }); @@ -123,7 +134,7 @@ describe('App global middleware Processing', () => { await fakeReceiver.sendEvent(dummyReceiverEvent); // Assert - assert.instanceOf(fakeErrorHandler.firstCall.args[0], Error); + assert.ok(fakeErrorHandler.firstCall.args[0] instanceof Error); }); it('correctly waits for async listeners', async () => { @@ -137,7 +148,7 @@ describe('App global middleware Processing', () => { }); await fakeReceiver.sendEvent(dummyReceiverEvent); - assert.isTrue(changed); + assert.strictEqual(changed, true); assert(fakeErrorHandler.notCalled); }); @@ -216,7 +227,7 @@ describe('App global middleware Processing', () => { }); app.error(async (codedError: CodedError) => { - assert.instanceOf(codedError, UnknownError); + assert.ok(codedError instanceof UnknownError); assert.equal(codedError.message, error.message); }); @@ -233,14 +244,14 @@ describe('App global middleware Processing', () => { }); app.error(async (args: ExtendedErrorHandlerArgs) => { - assert.property(args, 'error'); - assert.property(args, 'body'); - assert.property(args, 'context'); - assert.property(args, 'logger'); - assert.isDefined(args.error); - assert.isDefined(args.body); - assert.isDefined(args.context); - assert.isDefined(args.logger); + assert.ok('error' in args); + assert.ok('body' in args); + assert.ok('context' in args); + assert.ok('logger' in args); + assert.notStrictEqual(args.error, undefined); + assert.notStrictEqual(args.body, undefined); + assert.notStrictEqual(args.context, undefined); + assert.notStrictEqual(args.logger, undefined); assert.equal(args.error.message, error.message); }); @@ -265,7 +276,7 @@ describe('App global middleware Processing', () => { actualError = err; } - assert.instanceOf(actualError, UnknownError); + assert.ok(actualError instanceof UnknownError); assert.equal(actualError.message, error.message); }); @@ -283,7 +294,7 @@ describe('App global middleware Processing', () => { const testData = createDummyCustomFunctionMiddlewareArgs({ options: { autoAcknowledge: false } }); await fakeReceiver.sendEvent({ ack: fakeAck, body: testData.body }); - assert.isDefined(clientArg); + assert.notStrictEqual(clientArg, undefined); assert.equal(clientArg.token, 'xwfp-valid'); }); @@ -302,7 +313,7 @@ describe('App global middleware Processing', () => { const testData = createDummyCustomFunctionMiddlewareArgs({ options: { autoAcknowledge: false } }); await fakeReceiver.sendEvent({ ack: fakeAck, body: testData.body }); - assert.isDefined(clientArg); + assert.notStrictEqual(clientArg, undefined); assert.equal(clientArg.token, undefined); }); @@ -320,11 +331,11 @@ describe('App global middleware Processing', () => { const testData = createDummyCustomFunctionMiddlewareArgs({ options: { autoAcknowledge: false } }); await fakeReceiver.sendEvent({ ack: fakeAck, body: testData.body }); - assert.isDefined(clientArg); + assert.notStrictEqual(clientArg, undefined); assert.equal(clientArg.token, 'xwfp-valid'); await fakeReceiver.sendEvent(dummyReceiverEvent); - assert.isDefined(clientArg); + assert.notStrictEqual(clientArg, undefined); assert.equal(clientArg.token, 'xoxb-valid'); }); }); diff --git a/test/unit/App/middlewares/ignore-self.spec.ts b/test/unit/App/middlewares/ignore-self.spec.ts index da6be9d50..c791667af 100644 --- a/test/unit/App/middlewares/ignore-self.spec.ts +++ b/test/unit/App/middlewares/ignore-self.spec.ts @@ -1,3 +1,4 @@ +import { beforeEach, describe, it } from 'node:test'; import sinon, { type SinonSpy } from 'sinon'; import type App from '../../../../src/App'; import { diff --git a/test/unit/App/middlewares/listener.spec.ts b/test/unit/App/middlewares/listener.spec.ts index 9d162ee84..440797fff 100644 --- a/test/unit/App/middlewares/listener.spec.ts +++ b/test/unit/App/middlewares/listener.spec.ts @@ -1,4 +1,5 @@ -import { assert } from 'chai'; +import assert from 'node:assert/strict'; +import { beforeEach, describe, it } from 'node:test'; import sinon, { type SinonSpy } from 'sinon'; import type App from '../../../../src/App'; import { ErrorCode, isCodedError } from '../../../../src/errors'; @@ -56,8 +57,12 @@ describe('App listener middleware processing', () => { const error = fakeErrorHandler.firstCall.args[0]; assert.ok(isCodedError(error)); assert(error.code === ErrorCode.MultipleListenerError); - assert.isArray(error.originals); - if (error.originals) assert.sameMembers(error.originals, errorsToThrow); + assert.ok(Array.isArray(error.originals)); + if (error.originals) + assert.deepStrictEqual( + [...error.originals].sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b))), + [...errorsToThrow].sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b))), + ); }); // https://github.com/slackapi/bolt-js/issues/1457 diff --git a/test/unit/App/routing-action.spec.ts b/test/unit/App/routing-action.spec.ts index b4b0bc4c7..1c318010d 100644 --- a/test/unit/App/routing-action.spec.ts +++ b/test/unit/App/routing-action.spec.ts @@ -1,4 +1,5 @@ -import { assert } from 'chai'; +import assert from 'node:assert/strict'; +import { beforeEach, describe, it } from 'node:test'; import sinon, { type SinonSpy } from 'sinon'; import type App from '../../../src/App'; import { @@ -118,8 +119,8 @@ describe('App action() routing', () => { const testInputs = { test: true }; const testHandler = sinon.spy(async ({ inputs, complete, fail, client }) => { assert.equal(inputs, testInputs); - assert.typeOf(complete, 'function'); - assert.typeOf(fail, 'function'); + assert.strictEqual(typeof complete, 'function'); + assert.strictEqual(typeof fail, 'function'); assert.equal(client.token, 'xwfp-valid'); }); app.action('my_id', testHandler); diff --git a/test/unit/App/routing-assistant.spec.ts b/test/unit/App/routing-assistant.spec.ts index 104d9438b..dfc115b2a 100644 --- a/test/unit/App/routing-assistant.spec.ts +++ b/test/unit/App/routing-assistant.spec.ts @@ -1,3 +1,4 @@ +import { beforeEach, describe, it } from 'node:test'; import sinon, { type SinonSpy } from 'sinon'; import type App from '../../../src/App'; import { Assistant } from '../../../src/Assistant'; diff --git a/test/unit/App/routing-command.spec.ts b/test/unit/App/routing-command.spec.ts index 999199b06..0ab86ce3e 100644 --- a/test/unit/App/routing-command.spec.ts +++ b/test/unit/App/routing-command.spec.ts @@ -1,3 +1,4 @@ +import { beforeEach, describe, it } from 'node:test'; import sinon, { type SinonSpy } from 'sinon'; import type App from '../../../src/App'; import { diff --git a/test/unit/App/routing-event.spec.ts b/test/unit/App/routing-event.spec.ts index c7fb22b60..9c33ed7d6 100644 --- a/test/unit/App/routing-event.spec.ts +++ b/test/unit/App/routing-event.spec.ts @@ -1,4 +1,5 @@ import assert from 'node:assert'; +import { beforeEach, describe, it } from 'node:test'; import sinon, { type SinonSpy } from 'sinon'; import type App from '../../../src/App'; import { diff --git a/test/unit/App/routing-function.spec.ts b/test/unit/App/routing-function.spec.ts index b894b47fd..3c30e1b59 100644 --- a/test/unit/App/routing-function.spec.ts +++ b/test/unit/App/routing-function.spec.ts @@ -1,4 +1,5 @@ -import { assert } from 'chai'; +import assert from 'node:assert/strict'; +import { beforeEach, describe, it } from 'node:test'; import sinon from 'sinon'; import type App from '../../../src/App'; import { @@ -65,8 +66,8 @@ describe('App function() routing', () => { const testInputs = { test: true }; const testHandler = sinon.spy(async ({ inputs, complete, fail, client }) => { assert.equal(inputs, testInputs); - assert.typeOf(complete, 'function'); - assert.typeOf(fail, 'function'); + assert.strictEqual(typeof complete, 'function'); + assert.strictEqual(typeof fail, 'function'); assert.equal(client.token, 'xwfp-valid'); }); app.function('my_id', testHandler); diff --git a/test/unit/App/routing-message.spec.ts b/test/unit/App/routing-message.spec.ts index fbe263b43..456fe7a63 100644 --- a/test/unit/App/routing-message.spec.ts +++ b/test/unit/App/routing-message.spec.ts @@ -1,3 +1,4 @@ +import { beforeEach, describe, it } from 'node:test'; import sinon, { type SinonSpy } from 'sinon'; import type App from '../../../src/App'; import { diff --git a/test/unit/App/routing-options.spec.ts b/test/unit/App/routing-options.spec.ts index 72f090130..f7758050d 100644 --- a/test/unit/App/routing-options.spec.ts +++ b/test/unit/App/routing-options.spec.ts @@ -1,3 +1,4 @@ +import { beforeEach, describe, it } from 'node:test'; import sinon, { type SinonSpy } from 'sinon'; import type App from '../../../src/App'; import { diff --git a/test/unit/App/routing-shortcut.spec.ts b/test/unit/App/routing-shortcut.spec.ts index 9374d2efa..9e6c60479 100644 --- a/test/unit/App/routing-shortcut.spec.ts +++ b/test/unit/App/routing-shortcut.spec.ts @@ -1,3 +1,4 @@ +import { beforeEach, describe, it } from 'node:test'; import sinon, { type SinonSpy } from 'sinon'; import type App from '../../../src/App'; import { diff --git a/test/unit/App/routing-view.spec.ts b/test/unit/App/routing-view.spec.ts index de43b6d95..c31b65096 100644 --- a/test/unit/App/routing-view.spec.ts +++ b/test/unit/App/routing-view.spec.ts @@ -1,3 +1,4 @@ +import { beforeEach, describe, it } from 'node:test'; import sinon, { type SinonSpy } from 'sinon'; import type App from '../../../src/App'; import { diff --git a/test/unit/Assistant.spec.ts b/test/unit/Assistant.spec.ts index a81ae55f8..41341bbd6 100644 --- a/test/unit/Assistant.spec.ts +++ b/test/unit/Assistant.spec.ts @@ -1,7 +1,8 @@ +import assert from 'node:assert/strict'; import path from 'node:path'; +import { describe, it } from 'node:test'; import type { AssistantThreadStartedEvent } from '@slack/types'; import type { WebClient } from '@slack/web-api'; -import { assert } from 'chai'; import sinon from 'sinon'; import { type AllAssistantMiddlewareArgs, @@ -48,12 +49,12 @@ describe('Assistant class', () => { describe('constructor', () => { it('should accept config as single functions', async () => { const assistant = new Assistant(MOCK_CONFIG_SINGLE); - assert.isNotNull(assistant); + assert.notStrictEqual(assistant, null); }); it('should accept config as multiple functions', async () => { const assistant = new Assistant(MOCK_CONFIG_MULTIPLE); - assert.isNotNull(assistant); + assert.notStrictEqual(assistant, null); }); describe('validate', () => { @@ -133,7 +134,7 @@ describe('Assistant class', () => { it('should return false if not a recognized assistant event', async () => { const fakeMessageArgs = wrapMiddleware(createDummyAppMentionEventMiddlewareArgs()); const { isAssistantEvent } = importAssistant(); - assert.isFalse(isAssistantEvent(fakeMessageArgs)); + assert.strictEqual(isAssistantEvent(fakeMessageArgs), false); }); }); @@ -148,7 +149,7 @@ describe('Assistant class', () => { const fakeMessageArgs = wrapMiddleware(createDummyMessageEventMiddlewareArgs()); const { matchesConstraints } = importAssistant(); // casting here as we intentionally are providing type-mismatched argument as a runtime test - assert.isFalse(matchesConstraints(fakeMessageArgs as unknown as AssistantMiddlewareArgs)); + assert.strictEqual(matchesConstraints(fakeMessageArgs as unknown as AssistantMiddlewareArgs), false); }); it('should return true if not message event', async () => { @@ -168,19 +169,19 @@ describe('Assistant class', () => { it('should return false if not correct subtype', async () => { const fakeMessageArgs = wrapMiddleware(createDummyMessageEventMiddlewareArgs({ thread_ts: '1234.56' })); const { isAssistantMessage } = importAssistant(); - assert.isFalse(isAssistantMessage(fakeMessageArgs.payload)); + assert.strictEqual(isAssistantMessage(fakeMessageArgs.payload), false); }); it('should return false if thread_ts is missing', async () => { const fakeMessageArgs = wrapMiddleware(createDummyMessageEventMiddlewareArgs()); const { isAssistantMessage } = importAssistant(); - assert.isFalse(isAssistantMessage(fakeMessageArgs.payload)); + assert.strictEqual(isAssistantMessage(fakeMessageArgs.payload), false); }); it('should return false if channel_type is incorrect', async () => { const fakeMessageArgs = wrapMiddleware(createDummyMessageEventMiddlewareArgs({ channel_type: 'mpim' })); const { isAssistantMessage } = importAssistant(); - assert.isFalse(isAssistantMessage(fakeMessageArgs.payload)); + assert.strictEqual(isAssistantMessage(fakeMessageArgs.payload), false); }); }); }); @@ -201,9 +202,9 @@ describe('Assistant class', () => { const threadContextChangedArgs = enrichAssistantArgs(mockThreadContextStore, mockThreadContextChangedArgs); const userMessageArgs = enrichAssistantArgs(mockThreadContextStore, mockUserMessageArgs); - assert.notExists(threadStartedArgs.next); - assert.notExists(threadContextChangedArgs.next); - assert.notExists(userMessageArgs.next); + assert.equal(threadStartedArgs.next ?? null, null); + assert.equal(threadContextChangedArgs.next ?? null, null); + assert.equal(userMessageArgs.next ?? null, null); }); it('should augment assistant_thread_started args with utilities', async () => { @@ -215,11 +216,11 @@ describe('Assistant class', () => { payload, } as AllAssistantMiddlewareArgs); - assert.exists(assistantArgs.say); - assert.exists(assistantArgs.setStatus); - assert.exists(assistantArgs.sayStream); - assert.exists(assistantArgs.setSuggestedPrompts); - assert.exists(assistantArgs.setTitle); + assert.notEqual(assistantArgs.say ?? null, null); + assert.notEqual(assistantArgs.setStatus ?? null, null); + assert.notEqual(assistantArgs.sayStream ?? null, null); + assert.notEqual(assistantArgs.setSuggestedPrompts ?? null, null); + assert.notEqual(assistantArgs.setTitle ?? null, null); }); it('should augment assistant_thread_context_changed args with utilities', async () => { @@ -231,11 +232,11 @@ describe('Assistant class', () => { payload, } as AllAssistantMiddlewareArgs); - assert.exists(assistantArgs.say); - assert.exists(assistantArgs.setStatus); - assert.exists(assistantArgs.sayStream); - assert.exists(assistantArgs.setSuggestedPrompts); - assert.exists(assistantArgs.setTitle); + assert.notEqual(assistantArgs.say ?? null, null); + assert.notEqual(assistantArgs.setStatus ?? null, null); + assert.notEqual(assistantArgs.sayStream ?? null, null); + assert.notEqual(assistantArgs.setSuggestedPrompts ?? null, null); + assert.notEqual(assistantArgs.setTitle ?? null, null); }); it('should augment message args with utilities', async () => { @@ -247,11 +248,11 @@ describe('Assistant class', () => { payload, } as AllAssistantMiddlewareArgs); - assert.exists(assistantArgs.say); - assert.exists(assistantArgs.setStatus); - assert.exists(assistantArgs.sayStream); - assert.exists(assistantArgs.setSuggestedPrompts); - assert.exists(assistantArgs.setTitle); + assert.notEqual(assistantArgs.say ?? null, null); + assert.notEqual(assistantArgs.setStatus ?? null, null); + assert.notEqual(assistantArgs.sayStream ?? null, null); + assert.notEqual(assistantArgs.setSuggestedPrompts ?? null, null); + assert.notEqual(assistantArgs.setTitle ?? null, null); }); describe('extractThreadInfo', () => { @@ -288,7 +289,13 @@ describe('Assistant class', () => { assert.equal(payload.channel, channelId); // @ts-expect-error TODO: AssistantUserMessageMiddlewareArgs extends from too broad of a message event type, which contains types that explicitly DO NOT have a thread_ts. this is at odds with the expectation around assistant user message events. assert.equal(payload.thread_ts, threadTs); - assert.isEmpty(context); + if (Array.isArray(context) || typeof context === 'string') { + assert.strictEqual(context.length, 0); + } else if (context && typeof context === 'object') { + assert.strictEqual(Object.keys(context).length, 0); + } else { + assert.fail('expected value to be empty'); + } }); it('should throw error if `channel_id` or `thread_ts` are missing', async () => { diff --git a/test/unit/AssistantThreadContextStore.spec.ts b/test/unit/AssistantThreadContextStore.spec.ts index d1453c332..49bef1092 100644 --- a/test/unit/AssistantThreadContextStore.spec.ts +++ b/test/unit/AssistantThreadContextStore.spec.ts @@ -1,5 +1,6 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; import type { WebClient } from '@slack/web-api'; -import { assert } from 'chai'; import sinon from 'sinon'; import { extractThreadInfo } from '../../src/Assistant'; import { DefaultThreadContextStore } from '../../src/AssistantThreadContextStore'; @@ -42,7 +43,13 @@ describe('DefaultThreadContextStore class', () => { mockThreadStartedArgs.client = fakeClient as unknown as WebClient; const threadContext = await mockContextStore.get(mockThreadStartedArgs); - assert.isEmpty(threadContext); + if (Array.isArray(threadContext) || typeof threadContext === 'string') { + assert.strictEqual(threadContext.length, 0); + } else if (threadContext && typeof threadContext === 'object') { + assert.strictEqual(Object.keys(threadContext).length, 0); + } else { + assert.fail('expected value to be empty'); + } }); it('should return an empty object if no message metadata exists', async () => { @@ -64,7 +71,13 @@ describe('DefaultThreadContextStore class', () => { mockThreadStartedArgs.client = fakeClient as unknown as WebClient; const threadContext = await mockContextStore.get(mockThreadStartedArgs); - assert.isEmpty(threadContext); + if (Array.isArray(threadContext) || typeof threadContext === 'string') { + assert.strictEqual(threadContext.length, 0); + } else if (threadContext && typeof threadContext === 'object') { + assert.strictEqual(Object.keys(threadContext).length, 0); + } else { + assert.fail('expected value to be empty'); + } }); it('should retrieve instance context if it has been saved previously', async () => { diff --git a/test/unit/CustomFunction.spec.ts b/test/unit/CustomFunction.spec.ts index fcff37321..890e13d7d 100644 --- a/test/unit/CustomFunction.spec.ts +++ b/test/unit/CustomFunction.spec.ts @@ -1,4 +1,5 @@ -import { assert } from 'chai'; +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; import { CustomFunction, matchCallbackId, @@ -19,12 +20,12 @@ describe('CustomFunction', () => { describe('constructor', () => { it('should accept single function as middleware', async () => { const fn = new CustomFunction('test_callback_id', MOCK_MIDDLEWARE_SINGLE, { autoAcknowledge: true }); - assert.isNotNull(fn); + assert.notStrictEqual(fn, null); }); it('should accept multiple functions as middleware', async () => { const fn = new CustomFunction('test_callback_id', MOCK_MIDDLEWARE_MULTIPLE, { autoAcknowledge: true }); - assert.isNotNull(fn); + assert.notStrictEqual(fn, null); }); }); @@ -45,7 +46,7 @@ describe('CustomFunction', () => { const cbId = 'test_executed_callback_id'; const fn = new CustomFunction(cbId, MOCK_MIDDLEWARE_SINGLE, { autoAcknowledge: false }); const listeners = fn.getListeners(); - assert.isFalse(listeners.includes(autoAcknowledge)); + assert.strictEqual(listeners.includes(autoAcknowledge), false); }); }); diff --git a/test/unit/context/create-function-complete.spec.ts b/test/unit/context/create-function-complete.spec.ts index c96b6503f..3c3b0ce61 100644 --- a/test/unit/context/create-function-complete.spec.ts +++ b/test/unit/context/create-function-complete.spec.ts @@ -1,5 +1,6 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; import { WebClient } from '@slack/web-api'; -import { assert } from 'chai'; import sinon from 'sinon'; import { createFunctionComplete } from '../../../src/context'; @@ -28,9 +29,9 @@ describe('createFunctionComplete', () => { sinon.stub(client.functions, 'completeSuccess').resolves(); const complete = createFunctionComplete({ isEnterpriseInstall: false, functionExecutionId: 'Fx1234' }, client); - assert.isFalse(complete.hasBeenCalled(), 'hasBeenCalled should be false initially'); + assert.strictEqual(complete.hasBeenCalled(), false, 'hasBeenCalled should be false initially'); await complete(); - assert.isTrue(complete.hasBeenCalled(), 'hasBeenCalled should be true after invoking complete'); + assert.strictEqual(complete.hasBeenCalled(), true, 'hasBeenCalled should be true after invoking complete'); }); }); diff --git a/test/unit/context/create-function-fail.spec.ts b/test/unit/context/create-function-fail.spec.ts index 41c04ec71..61dc65b8f 100644 --- a/test/unit/context/create-function-fail.spec.ts +++ b/test/unit/context/create-function-fail.spec.ts @@ -1,5 +1,6 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; import { WebClient } from '@slack/web-api'; -import { assert } from 'chai'; import sinon from 'sinon'; import { createFunctionFail } from '../../../src/context'; @@ -28,9 +29,9 @@ describe('createFunctionFail', () => { sinon.stub(client.functions, 'completeError').resolves(); const fail = createFunctionFail({ isEnterpriseInstall: false, functionExecutionId: 'Fx1234' }, client); - assert.isFalse(fail.hasBeenCalled(), 'hasBeenCalled should be false initially'); + assert.strictEqual(fail.hasBeenCalled(), false, 'hasBeenCalled should be false initially'); await fail({ error: 'boom' }); - assert.isTrue(fail.hasBeenCalled(), 'hasBeenCalled should be true after calling the function'); + assert.strictEqual(fail.hasBeenCalled(), true, 'hasBeenCalled should be true after calling the function'); }); }); diff --git a/test/unit/context/create-respond.spec.ts b/test/unit/context/create-respond.spec.ts index fe02d1259..e65e0ee39 100644 --- a/test/unit/context/create-respond.spec.ts +++ b/test/unit/context/create-respond.spec.ts @@ -1,5 +1,6 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; import type { FetchFunction } from '@slack/web-api'; -import { assert } from 'chai'; import sinon from 'sinon'; import { createRespond } from '../../../src/context'; diff --git a/test/unit/context/create-say-stream.spec.ts b/test/unit/context/create-say-stream.spec.ts index b7a9010c6..59c8a9fbb 100644 --- a/test/unit/context/create-say-stream.spec.ts +++ b/test/unit/context/create-say-stream.spec.ts @@ -1,5 +1,6 @@ +import assert from 'node:assert/strict'; +import { afterEach, beforeEach, describe, it } from 'node:test'; import { WebClient } from '@slack/web-api'; -import { assert } from 'chai'; import sinon from 'sinon'; import { createSayStream } from '../../../src/context'; import type { Context } from '../../../src/types'; diff --git a/test/unit/context/create-say.spec.ts b/test/unit/context/create-say.spec.ts index 006e23b40..e28e6664a 100644 --- a/test/unit/context/create-say.spec.ts +++ b/test/unit/context/create-say.spec.ts @@ -1,5 +1,6 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; import { WebClient } from '@slack/web-api'; -import { assert } from 'chai'; import sinon from 'sinon'; import { createSay } from '../../../src/context'; diff --git a/test/unit/context/create-set-status.spec.ts b/test/unit/context/create-set-status.spec.ts index 4709e19d0..a8518f8b9 100644 --- a/test/unit/context/create-set-status.spec.ts +++ b/test/unit/context/create-set-status.spec.ts @@ -1,5 +1,6 @@ +import assert from 'node:assert/strict'; +import { afterEach, beforeEach, describe, it } from 'node:test'; import { WebClient } from '@slack/web-api'; -import { assert } from 'chai'; import sinon from 'sinon'; import { createSetStatus } from '../../../src/context'; diff --git a/test/unit/conversation-store.spec.ts b/test/unit/conversation-store.spec.ts index a50412a21..ade2b6d8f 100644 --- a/test/unit/conversation-store.spec.ts +++ b/test/unit/conversation-store.spec.ts @@ -1,7 +1,8 @@ +import assert, { AssertionError } from 'node:assert/strict'; import path from 'node:path'; +import { describe, it } from 'node:test'; import type { Logger } from '@slack/logger'; import type { WebClient } from '@slack/web-api'; -import { AssertionError, assert } from 'chai'; import sinon, { type SinonSpy } from 'sinon'; import type { AnyMiddlewareArgs, Context, NextFn } from '../../src/types'; import { createFakeLogger, delay, type Override, proxyquire } from './helpers'; @@ -69,8 +70,8 @@ describe('conversationContext middleware', () => { // Assert assert(fakeLogger.debug.called); assert(fakeNext.called); - assert.notProperty(dummyContext, 'updateConversation'); - assert.notProperty(dummyContext, 'conversation'); + assert.ok(!('updateConversation' in dummyContext)); + assert.ok(!('conversation' in dummyContext)); }); it('should add to the context for events within a conversation that was not previously stored and pass expiresAt', async () => { @@ -100,7 +101,7 @@ describe('conversationContext middleware', () => { await middleware(fakeArgs); assert(fakeNext.called); - assert.notProperty(dummyContext, 'conversation'); + assert.ok(!('conversation' in dummyContext)); if (dummyContext.updateConversation === undefined) { assert.fail(); } @@ -136,8 +137,8 @@ describe('conversationContext middleware', () => { // Assert assert(fakeNext.called); - assert.notProperty(dummyContext, 'conversation'); - // NOTE: chai types do not offer assertion signatures yet, and neither do node's assert module types. + assert.ok(!('conversation' in dummyContext)); + // NOTE: node:assert types do not offer assertion signatures here. if (dummyContext.updateConversation === undefined) { assert.fail(); } @@ -173,7 +174,7 @@ describe('conversationContext middleware', () => { // Assert assert.equal(dummyContext.conversation, dummyConversationState); - // NOTE: chai types do not offer assertion signatures yet, and neither do node's assert module types. + // NOTE: node:assert types do not offer assertion signatures here. if (dummyContext.updateConversation === undefined) { assert.fail(); } @@ -195,7 +196,7 @@ describe('MemoryStore', () => { const store = new MemoryStore(); // Assert - assert.isOk(store); + assert.ok(store); }); }); @@ -229,8 +230,8 @@ describe('MemoryStore', () => { assert.fail(); } catch (error) { // Assert - assert.instanceOf(error, Error); - assert.notInstanceOf(error, AssertionError); + assert.ok(error instanceof Error); + assert.ok(!(error instanceof AssertionError)); } }); @@ -250,8 +251,8 @@ describe('MemoryStore', () => { assert.fail(); } catch (error) { // Assert - assert.instanceOf(error, Error); - assert.notInstanceOf(error, AssertionError); + assert.ok(error instanceof Error); + assert.ok(!(error instanceof AssertionError)); } }); }); diff --git a/test/unit/errors.spec.ts b/test/unit/errors.spec.ts index d65f69ee7..06c2cb78c 100644 --- a/test/unit/errors.spec.ts +++ b/test/unit/errors.spec.ts @@ -1,4 +1,5 @@ -import { assert } from 'chai'; +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; import { AppInitializationError, AuthorizationError, @@ -28,7 +29,7 @@ describe('Errors', () => { }); it('wraps non-coded errors', () => { - assert.instanceOf(asCodedError(new Error('AHH!')), UnknownError); + assert.ok(asCodedError(new Error('AHH!')) instanceof UnknownError); }); it('passes coded errors through', () => { diff --git a/test/unit/helpers.spec.ts b/test/unit/helpers.spec.ts index c746cf99b..17c2a545f 100644 --- a/test/unit/helpers.spec.ts +++ b/test/unit/helpers.spec.ts @@ -1,4 +1,5 @@ -import { assert } from 'chai'; +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; import { extractEventChannelId, extractEventThreadTs, @@ -116,7 +117,13 @@ describe('Helpers', () => { const typeAndConversation = getTypeAndConversation(fakeEventBody); // Assert - assert.isEmpty(typeAndConversation); + if (Array.isArray(typeAndConversation) || typeof typeAndConversation === 'string') { + assert.strictEqual(typeAndConversation.length, 0); + } else if (typeAndConversation && typeof typeAndConversation === 'object') { + assert.strictEqual(Object.keys(typeAndConversation).length, 0); + } else { + assert.fail('expected value to be empty'); + } }); }); }); @@ -236,47 +243,47 @@ describe('Helpers', () => { }); describe(`${isRecord.name}()`, () => { it('should return true for plain objects', () => { - assert.isTrue(isRecord({})); - assert.isTrue(isRecord({ key: 'value' })); + assert.strictEqual(isRecord({}), true); + assert.strictEqual(isRecord({ key: 'value' }), true); }); it('should return true for arrays', () => { - assert.isTrue(isRecord([])); + assert.strictEqual(isRecord([]), true); }); it('should return false for null', () => { - assert.isFalse(isRecord(null)); + assert.strictEqual(isRecord(null), false); }); it('should return false for undefined', () => { - assert.isFalse(isRecord(undefined)); + assert.strictEqual(isRecord(undefined), false); }); it('should return false for primitives', () => { - assert.isFalse(isRecord('string')); - assert.isFalse(isRecord(42)); - assert.isFalse(isRecord(true)); + assert.strictEqual(isRecord('string'), false); + assert.strictEqual(isRecord(42), false); + assert.strictEqual(isRecord(true), false); }); }); describe(`${hasStringProperty.name}()`, () => { it('should return true when key exists with a string value', () => { - assert.isTrue(hasStringProperty({ name: 'test' }, 'name')); + assert.strictEqual(hasStringProperty({ name: 'test' }, 'name'), true); }); it('should return false when key exists with a non-string value', () => { - assert.isFalse(hasStringProperty({ count: 42 }, 'count')); - assert.isFalse(hasStringProperty({ flag: true }, 'flag')); - assert.isFalse(hasStringProperty({ nested: {} }, 'nested')); + assert.strictEqual(hasStringProperty({ count: 42 }, 'count'), false); + assert.strictEqual(hasStringProperty({ flag: true }, 'flag'), false); + assert.strictEqual(hasStringProperty({ nested: {} }, 'nested'), false); }); it('should return false when key does not exist', () => { - assert.isFalse(hasStringProperty({ other: 'value' }, 'missing')); + assert.strictEqual(hasStringProperty({ other: 'value' }, 'missing'), false); }); it('should return false for null or undefined input', () => { - assert.isFalse(hasStringProperty(null, 'key')); - assert.isFalse(hasStringProperty(undefined, 'key')); + assert.strictEqual(hasStringProperty(null, 'key'), false); + assert.strictEqual(hasStringProperty(undefined, 'key'), false); }); }); @@ -329,7 +336,7 @@ describe('Helpers', () => { for (const { name, event } of noThreadTsEvents) { it(`should return undefined for ${name}`, () => { - assert.isUndefined(extractEventThreadTs(event as KnownEventFromType)); + assert.strictEqual(extractEventThreadTs(event as KnownEventFromType), undefined); }); } }); @@ -357,7 +364,7 @@ describe('Helpers', () => { for (const { name, event } of noTsEvents) { it(`should return undefined for ${name}`, () => { - assert.isUndefined(extractEventTs(event as KnownEventFromType)); + assert.strictEqual(extractEventTs(event as KnownEventFromType), undefined); }); } }); @@ -408,7 +415,7 @@ describe('Helpers', () => { for (const { name, event } of noChannelEvents) { it(`should return undefined for ${name}`, () => { - assert.isUndefined(extractEventChannelId(event as KnownEventFromType)); + assert.strictEqual(extractEventChannelId(event as KnownEventFromType), undefined); }); } diff --git a/test/unit/middleware/builtin.spec.ts b/test/unit/middleware/builtin.spec.ts index 9badf9368..96d28c11e 100644 --- a/test/unit/middleware/builtin.spec.ts +++ b/test/unit/middleware/builtin.spec.ts @@ -1,5 +1,6 @@ +import assert from 'node:assert/strict'; import path from 'node:path'; -import { assert } from 'chai'; +import { beforeEach, describe, it } from 'node:test'; import sinon from 'sinon'; import { expectType } from 'tsd'; import { ErrorCode } from '../../../src/errors'; @@ -40,7 +41,7 @@ describe('Built-in global middleware', () => { function matchesPatternTestCase( pattern: string | RegExp, event: SlackEventMiddlewareArgs<'message' | 'app_mention'>, - ): Mocha.AsyncFunc { + ): () => Promise { return async () => { const { matchMessage } = builtins; const middleware = matchMessage(pattern); @@ -52,7 +53,7 @@ describe('Built-in global middleware', () => { // The following assertion(s) check behavior that is only targeted at RegExp patterns if (typeof pattern !== 'string') { if (ctx.matches !== undefined) { - assert.lengthOf(ctx.matches, 1); + assert.strictEqual(ctx.matches.length, 1); } else { assert.fail(); } @@ -63,7 +64,7 @@ describe('Built-in global middleware', () => { function notMatchesPatternTestCase( pattern: string | RegExp, event: SlackEventMiddlewareArgs<'message' | 'app_mention'>, - ): Mocha.AsyncFunc { + ): () => Promise { return async () => { const { matchMessage } = builtins; const middleware = matchMessage(pattern); @@ -72,7 +73,7 @@ describe('Built-in global middleware', () => { await middleware(args); sinon.assert.notCalled(args.next); - assert.notProperty(ctx, 'matches'); + assert.ok(!('matches' in ctx)); }; } @@ -161,9 +162,16 @@ describe('Built-in global middleware', () => { error = err as Error; } - assert.instanceOf(error, Error); - assert.propertyVal(error, 'code', ErrorCode.ContextMissingPropertyError); - assert.propertyVal(error, 'missingProperty', 'botUserId'); + assert.ok(error instanceof Error); + assert.ok(error && typeof error === 'object'); + assert.ok('code' in error); + assert.deepStrictEqual( + (error as unknown as Record).code, + ErrorCode.ContextMissingPropertyError, + ); + assert.ok(error && typeof error === 'object'); + assert.ok('missingProperty' in error); + assert.deepStrictEqual((error as unknown as Record).missingProperty, 'botUserId'); }); it('should match message events that mention the bot user ID at the beginning of message text', async () => { @@ -417,7 +425,7 @@ describe('Built-in global middleware', () => { describe(isSlackEventMiddlewareArgsOptions.name, () => { it('should return true if object is SlackEventMiddlewareArgsOptions', async () => { const actual = isSlackEventMiddlewareArgsOptions({ autoAcknowledge: true }); - assert.isTrue(actual); + assert.strictEqual(actual, true); }); it('should narrow proper type if object is SlackEventMiddlewareArgsOptions', async () => { @@ -431,7 +439,7 @@ describe('Built-in global middleware', () => { it('should return false if object is Middleware', async () => { const actual = isSlackEventMiddlewareArgsOptions(async () => {}); - assert.isFalse(actual); + assert.strictEqual(actual, false); }); }); }); diff --git a/test/unit/receivers/AwsLambdaReceiver.spec.ts b/test/unit/receivers/AwsLambdaReceiver.spec.ts index 43c5cf3a1..46ceaf1d1 100644 --- a/test/unit/receivers/AwsLambdaReceiver.spec.ts +++ b/test/unit/receivers/AwsLambdaReceiver.spec.ts @@ -1,5 +1,6 @@ +import assert from 'node:assert/strict'; import crypto from 'node:crypto'; -import { assert } from 'chai'; +import { describe, it } from 'node:test'; import sinon from 'sinon'; import AwsLambdaReceiver from '../../../src/receivers/AwsLambdaReceiver'; import { @@ -30,7 +31,7 @@ describe('AwsLambdaReceiver', () => { signingSecret: 'my-secret', logger: noopLogger, }); - assert.isNotNull(awsReceiver); + assert.notStrictEqual(awsReceiver, null); }); it('should have start method', async () => { @@ -39,7 +40,7 @@ describe('AwsLambdaReceiver', () => { logger: noopLogger, }); const startedHandler = await awsReceiver.start(); - assert.isNotNull(startedHandler); + assert.notStrictEqual(startedHandler, null); }); it('should have stop method', async () => { diff --git a/test/unit/receivers/ExpressReceiver.spec.ts b/test/unit/receivers/ExpressReceiver.spec.ts index f131aaa52..38f79e040 100644 --- a/test/unit/receivers/ExpressReceiver.spec.ts +++ b/test/unit/receivers/ExpressReceiver.spec.ts @@ -1,9 +1,10 @@ +import assert from 'node:assert/strict'; import type { Server } from 'node:http'; import type { Server as HTTPSServer } from 'node:https'; import path from 'node:path'; import { Readable } from 'node:stream'; +import { afterEach, beforeEach, describe, it } from 'node:test'; import type { InstallProvider } from '@slack/oauth'; -import { assert } from 'chai'; import type { Application, IRouter, Request, Response } from 'express'; import sinon, { type SinonFakeTimers } from 'sinon'; import App from '../../../src/App'; @@ -19,7 +20,6 @@ import ExpressReceiver, { respondToUrlVerification, verifySignatureAndParseRawBody, } from '../../../src/receivers/ExpressReceiver'; -import * as httpFunc from '../../../src/receivers/HTTPModuleFunctions'; import type { ReceiverEvent } from '../../../src/types'; import { createFakeLogger, @@ -86,7 +86,7 @@ describe('ExpressReceiver', () => { }, customPropertiesExtractor: (req) => ({ headers: req.headers }), }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); }); it('should accept custom Express app / router', async () => { const app = { @@ -113,7 +113,7 @@ describe('ExpressReceiver', () => { app: app as unknown as Application, router: router as unknown as IRouter, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); sinon.assert.calledOnce(app.use); sinon.assert.calledOnce(router.get); sinon.assert.calledOnce(router.post); @@ -138,7 +138,7 @@ describe('ExpressReceiver', () => { redirectUri, installerOptions, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); // missing redirectUriPath assert.throws( () => @@ -230,7 +230,7 @@ describe('ExpressReceiver', () => { caughtError = error as Error; } - assert.instanceOf(caughtError, Error); + assert.ok(caughtError instanceof Error); }); it('should reject with an error when the built-in HTTP server returns undefined', async () => { const fakeCreateUndefinedServer = sinon.fake.returns(undefined); @@ -249,8 +249,13 @@ describe('ExpressReceiver', () => { caughtError = error as Error; } - assert.instanceOf(caughtError, ReceiverInconsistentStateError); - assert.propertyVal(caughtError, 'code', ErrorCode.ReceiverInconsistentStateError); + assert.ok(caughtError instanceof ReceiverInconsistentStateError); + assert.ok(caughtError && typeof caughtError === 'object'); + assert.ok('code' in caughtError); + assert.deepStrictEqual( + (caughtError as unknown as Record).code, + ErrorCode.ReceiverInconsistentStateError, + ); }); it('should reject with an error when starting and the server was already previously started', async () => { const ER = importExpressReceiver(overrides); @@ -265,8 +270,13 @@ describe('ExpressReceiver', () => { caughtError = error as Error; } - assert.instanceOf(caughtError, ReceiverInconsistentStateError); - assert.propertyVal(caughtError, 'code', ErrorCode.ReceiverInconsistentStateError); + assert.ok(caughtError instanceof ReceiverInconsistentStateError); + assert.ok(caughtError && typeof caughtError === 'object'); + assert.ok('code' in caughtError); + assert.deepStrictEqual( + (caughtError as unknown as Record).code, + ErrorCode.ReceiverInconsistentStateError, + ); }); }); @@ -290,8 +300,13 @@ describe('ExpressReceiver', () => { caughtError = error as Error; } - assert.instanceOf(caughtError, ReceiverInconsistentStateError); - assert.propertyVal(caughtError, 'code', ErrorCode.ReceiverInconsistentStateError); + assert.ok(caughtError instanceof ReceiverInconsistentStateError); + assert.ok(caughtError && typeof caughtError === 'object'); + assert.ok('code' in caughtError); + assert.deepStrictEqual( + (caughtError as unknown as Record).code, + ErrorCode.ReceiverInconsistentStateError, + ); }); it('should reject when a built-in HTTP server raises an error when closing', async () => { fakeServer = new FakeServer( @@ -314,16 +329,16 @@ describe('ExpressReceiver', () => { caughtError = error as Error; } - assert.instanceOf(caughtError, Error); + assert.ok(caughtError instanceof Error); assert.equal(caughtError?.message, 'this error will be raised by the underlying HTTP server during close()'); }); }); describe('#requestHandler()', () => { - const extractRetryNumStub = sinon.stub(httpFunc, 'extractRetryNumFromHTTPRequest'); - const extractRetryReasonStub = sinon.stub(httpFunc, 'extractRetryReasonFromHTTPRequest'); - const buildNoBodyResponseStub = sinon.stub(httpFunc, 'buildNoBodyResponse'); - const buildContentResponseStub = sinon.stub(httpFunc, 'buildContentResponse'); + let extractRetryNumStub: sinon.SinonStub; + let extractRetryReasonStub: sinon.SinonStub; + let buildNoBodyResponseStub: sinon.SinonStub; + let buildContentResponseStub: sinon.SinonStub; const processStub = sinon.stub<[ReceiverEvent]>().resolves({}); const ackStub = function ackStub() {}; ackStub.prototype.bind = function () { @@ -331,21 +346,27 @@ describe('ExpressReceiver', () => { }; ackStub.prototype.ack = sinon.spy(); beforeEach(() => { + extractRetryNumStub = sinon.stub().returns(undefined); + extractRetryReasonStub = sinon.stub().returns(undefined); + buildNoBodyResponseStub = sinon.stub().returns(undefined); + buildContentResponseStub = sinon.stub().returns(undefined); overrides = mergeOverrides( withHttpCreateServer(fakeCreateServer), withHttpsCreateServer(sinon.fake.throws('Should not be used.')), { './HTTPResponseAck': { HTTPResponseAck: ackStub } }, + { + './HTTPModuleFunctions': { + extractRetryNumFromHTTPRequest: extractRetryNumStub, + extractRetryReasonFromHTTPRequest: extractRetryReasonStub, + buildNoBodyResponse: buildNoBodyResponseStub, + buildContentResponse: buildContentResponseStub, + }, + }, ); }); afterEach(() => { sinon.reset(); }); - after(() => { - extractRetryNumStub.restore(); - extractRetryReasonStub.restore(); - buildNoBodyResponseStub.restore(); - buildContentResponseStub.restore(); - }); it('should not build an HTTP response if processBeforeResponse=false', async () => { const ER = importExpressReceiver(overrides); const receiver = new ER({ signingSecret: '' }); @@ -612,21 +633,21 @@ describe('ExpressReceiver', () => { // biome-ignore lint/suspicious/noExplicitAny: errors can be anything const state: any = {}; await runWithValidRequest(buildExpressRequest(), state); - assert.isUndefined(state.error); + assert.strictEqual(state.error, undefined); }); it('should verify requests on GCP', async () => { // biome-ignore lint/suspicious/noExplicitAny: errors can be anything const state: any = {}; await runWithValidRequest(buildGCPRequest(), state); - assert.isUndefined(state.error); + assert.strictEqual(state.error, undefined); }); it('should verify requests on GCP using async signingSecret', async () => { // biome-ignore lint/suspicious/noExplicitAny: errors can be anything const state: any = {}; await runWithValidRequest(buildGCPRequest(), state, () => Promise.resolve(signingSecret)); - assert.isUndefined(state.error); + assert.strictEqual(state.error, undefined); }); // ---------------------------- diff --git a/test/unit/receivers/HTTPModuleFunctions.spec.ts b/test/unit/receivers/HTTPModuleFunctions.spec.ts index dfec2044c..b7d1d9c5b 100644 --- a/test/unit/receivers/HTTPModuleFunctions.spec.ts +++ b/test/unit/receivers/HTTPModuleFunctions.spec.ts @@ -1,6 +1,7 @@ +import assert from 'node:assert/strict'; import { createHmac } from 'node:crypto'; import { IncomingMessage, ServerResponse } from 'node:http'; -import { assert } from 'chai'; +import { describe, it } from 'node:test'; import sinon from 'sinon'; import { AuthorizationError, HTTPReceiverDeferredRequestError, ReceiverMultipleAckError } from '../../../src/errors'; @@ -14,7 +15,7 @@ describe('HTTPModuleFunctions', async () => { it('should work when the header does not exist', async () => { const req = sinon.createStubInstance(IncomingMessage) as IncomingMessage; const result = func.extractRetryNumFromHTTPRequest(req); - assert.isUndefined(result); + assert.strictEqual(result, undefined); }); it('should parse a single value header', async () => { const req = sinon.createStubInstance(IncomingMessage) as IncomingMessage; @@ -33,7 +34,7 @@ describe('HTTPModuleFunctions', async () => { it('should work when the header does not exist', async () => { const req = sinon.createStubInstance(IncomingMessage) as IncomingMessage; const result = func.extractRetryReasonFromHTTPRequest(req); - assert.isUndefined(result); + assert.strictEqual(result, undefined); }); it('should parse a valid header', async () => { const req = sinon.createStubInstance(IncomingMessage) as IncomingMessage; @@ -77,7 +78,7 @@ describe('HTTPModuleFunctions', async () => { func.getHeader(req, 'Cookie'); assert.fail('Error should be thrown here'); } catch (e) { - assert.isTrue((e as Error).message.length > 0); + assert.strictEqual((e as Error).message.length > 0, true); } }); it('should parse a valid header', async () => { @@ -106,7 +107,7 @@ describe('HTTPModuleFunctions', async () => { } as unknown as BufferedIncomingMessage; const res = sinon.createStubInstance(ServerResponse) as unknown as ServerResponse; const result = await func.parseAndVerifyHTTPRequest({ signingSecret }, req, res); - assert.isDefined(result.rawBody); + assert.notStrictEqual(result.rawBody, undefined); }); it('should detect an invalid timestamp', async () => { const signingSecret = 'secret'; @@ -127,9 +128,10 @@ describe('HTTPModuleFunctions', async () => { try { await func.parseAndVerifyHTTPRequest({ signingSecret }, req, res); } catch (e) { - assert.propertyVal( - e, - 'message', + assert.ok(e && typeof e === 'object'); + assert.ok('message' in e); + assert.deepStrictEqual( + (e as unknown as Record).message, 'Failed to verify authenticity: x-slack-request-timestamp must differ from system time by no more than 5 minutes or request is stale', ); } @@ -150,7 +152,12 @@ describe('HTTPModuleFunctions', async () => { try { await func.parseAndVerifyHTTPRequest({ signingSecret }, req, res); } catch (e) { - assert.propertyVal(e, 'message', 'Failed to verify authenticity: signature mismatch'); + assert.ok(e && typeof e === 'object'); + assert.ok('message' in e); + assert.deepStrictEqual( + (e as unknown as Record).message, + 'Failed to verify authenticity: signature mismatch', + ); } }); it('should parse a ssl_check request body without signature verification', async () => { @@ -164,7 +171,7 @@ describe('HTTPModuleFunctions', async () => { } as unknown as BufferedIncomingMessage; const res: ServerResponse = sinon.createStubInstance(ServerResponse) as unknown as ServerResponse; const result = await func.parseAndVerifyHTTPRequest({ signingSecret }, req, res); - assert.isDefined(result.rawBody); + assert.notStrictEqual(result.rawBody, undefined); assert.equal(result.rawBody.toString(), 'ssl_check=1'); }); it('should strip smuggled event payloads from ssl_check requests', async () => { @@ -194,7 +201,8 @@ describe('HTTPModuleFunctions', async () => { await func.parseAndVerifyHTTPRequest({ signingSecret }, req, res); assert.fail('Expected an error to be thrown'); } catch (e) { - assert.include((e as Error).message, 'Failed to verify authenticity'); + assert.ok(e instanceof Error); + assert.match(e.message, /Failed to verify authenticity/); } }); it('should not bypass signature verification when ssl_check=true', async () => { @@ -211,7 +219,8 @@ describe('HTTPModuleFunctions', async () => { await func.parseAndVerifyHTTPRequest({ signingSecret }, req, res); assert.fail('Expected an error to be thrown'); } catch (e) { - assert.include((e as Error).message, 'Failed to verify authenticity'); + assert.ok(e instanceof Error); + assert.match(e.message, /Failed to verify authenticity/); } }); it('should not bypass signature verification for ssl_check=1 with JSON content-type', async () => { @@ -228,7 +237,8 @@ describe('HTTPModuleFunctions', async () => { await func.parseAndVerifyHTTPRequest({ signingSecret }, req, res); assert.fail('Expected an error to be thrown'); } catch (e) { - assert.include((e as Error).message, 'Failed to verify authenticity'); + assert.ok(e instanceof Error); + assert.match(e.message, /Failed to verify authenticity/); } }); it('should not bypass signature verification when ssl_check is empty', async () => { @@ -245,7 +255,8 @@ describe('HTTPModuleFunctions', async () => { await func.parseAndVerifyHTTPRequest({ signingSecret }, req, res); assert.fail('Expected an error to be thrown'); } catch (e) { - assert.include((e as Error).message, 'Failed to verify authenticity'); + assert.ok(e instanceof Error); + assert.match(e.message, /Failed to verify authenticity/); } }); it('should detect invalid signature for application/x-www-form-urlencoded body', async () => { @@ -264,7 +275,12 @@ describe('HTTPModuleFunctions', async () => { try { await func.parseAndVerifyHTTPRequest({ signingSecret }, req, res); } catch (e) { - assert.propertyVal(e, 'message', 'Failed to verify authenticity: signature mismatch'); + assert.ok(e && typeof e === 'object'); + assert.ok('message' in e); + assert.deepStrictEqual( + (e as unknown as Record).message, + 'Failed to verify authenticity: signature mismatch', + ); } }); }); @@ -274,24 +290,24 @@ describe('HTTPModuleFunctions', async () => { it('should have buildContentResponse', async () => { const res = sinon.createStubInstance(ServerResponse); func.buildContentResponse(res as unknown as ServerResponse, 'OK'); - assert.isTrue(res.writeHead.calledWith(200)); + assert.strictEqual(res.writeHead.calledWith(200), true); }); it('should have buildNoBodyResponse', async () => { const res = sinon.createStubInstance(ServerResponse); func.buildNoBodyResponse(res as unknown as ServerResponse, 500); - assert.isTrue(res.writeHead.calledWith(500)); + assert.strictEqual(res.writeHead.calledWith(500), true); }); it('should have buildSSLCheckResponse', async () => { const res = sinon.createStubInstance(ServerResponse); func.buildSSLCheckResponse(res as unknown as ServerResponse); - assert.isTrue(res.writeHead.calledWith(200)); + assert.strictEqual(res.writeHead.calledWith(200), true); }); it('should have buildUrlVerificationResponse', async () => { const res = sinon.createStubInstance(ServerResponse); func.buildUrlVerificationResponse(res as unknown as ServerResponse, { challenge: '3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P', }); - assert.isTrue(res.writeHead.calledWith(200)); + assert.strictEqual(res.writeHead.calledWith(200), true); }); }); @@ -308,7 +324,7 @@ describe('HTTPModuleFunctions', async () => { request, response: response as unknown as ServerResponse, }); - assert.isTrue(response.writeHead.calledWith(500)); + assert.strictEqual(response.writeHead.calledWith(500), true); }); it('should properly handle HTTPReceiverDeferredRequestError', async () => { const request = sinon.createStubInstance(IncomingMessage) as IncomingMessage; @@ -319,7 +335,7 @@ describe('HTTPModuleFunctions', async () => { request, response: response as unknown as ServerResponse, }); - assert.isTrue(response.writeHead.calledWith(404)); + assert.strictEqual(response.writeHead.calledWith(404), true); }); }); @@ -334,7 +350,7 @@ describe('HTTPModuleFunctions', async () => { request, response: response as unknown as ServerResponse, }); - assert.isTrue(response.writeHead.calledWith(500)); + assert.strictEqual(response.writeHead.calledWith(500), true); }); it('should properly handle AuthorizationError', async () => { const request = sinon.createStubInstance(IncomingMessage) as IncomingMessage; @@ -346,7 +362,7 @@ describe('HTTPModuleFunctions', async () => { request, response: response as unknown as ServerResponse, }); - assert.isTrue(response.writeHead.calledWith(401)); + assert.strictEqual(response.writeHead.calledWith(401), true); }); }); @@ -359,7 +375,7 @@ describe('HTTPModuleFunctions', async () => { request, response: response as unknown as ServerResponse, }); - assert.isTrue(response.writeHead.calledWith(404)); + assert.strictEqual(response.writeHead.calledWith(404), true); }); }); }); diff --git a/test/unit/receivers/HTTPReceiver.spec.ts b/test/unit/receivers/HTTPReceiver.spec.ts index 8b3e15a09..be77e4196 100644 --- a/test/unit/receivers/HTTPReceiver.spec.ts +++ b/test/unit/receivers/HTTPReceiver.spec.ts @@ -1,7 +1,8 @@ +import assert from 'node:assert/strict'; import { IncomingMessage, ServerResponse } from 'node:http'; import path from 'node:path'; +import { beforeEach, describe, it } from 'node:test'; import { InstallProvider } from '@slack/oauth'; -import { assert } from 'chai'; import type { ParamsDictionary } from 'express-serve-static-core'; import { match } from 'path-to-regexp'; import sinon from 'sinon'; @@ -86,7 +87,7 @@ describe('HTTPReceiver', () => { }, unhandledRequestTimeoutMillis: 2000, // the default is 3001 }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); }); it('should accept a custom port', async () => { @@ -95,15 +96,19 @@ describe('HTTPReceiver', () => { const defaultPort = new HTTPReceiver({ signingSecret: 'secret', }); - assert.isNotNull(defaultPort); - assert.propertyVal(defaultPort, 'port', 3000); + assert.notStrictEqual(defaultPort, null); + assert.ok(defaultPort && typeof defaultPort === 'object'); + assert.ok('port' in defaultPort); + assert.deepStrictEqual((defaultPort as unknown as Record).port, 3000); const customPort = new HTTPReceiver({ port: 9999, signingSecret: 'secret', }); - assert.isNotNull(customPort); - assert.propertyVal(customPort, 'port', 9999); + assert.notStrictEqual(customPort, null); + assert.ok(customPort && typeof customPort === 'object'); + assert.ok('port' in customPort); + assert.deepStrictEqual((customPort as unknown as Record).port, 9999); const customPort2 = new HTTPReceiver({ port: 7777, @@ -112,8 +117,10 @@ describe('HTTPReceiver', () => { port: 9999, }, }); - assert.isNotNull(customPort2); - assert.propertyVal(customPort2, 'port', 9999); + assert.notStrictEqual(customPort2, null); + assert.ok(customPort2 && typeof customPort2 === 'object'); + assert.ok('port' in customPort2); + assert.deepStrictEqual((customPort2 as unknown as Record).port, 9999); }); it('should throw an error if redirect uri options supplied invalid or incomplete', async () => { @@ -137,7 +144,7 @@ describe('HTTPReceiver', () => { redirectUri, installerOptions, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); // redirectUri supplied, but missing redirectUriPath assert.throws( () => @@ -190,8 +197,10 @@ describe('HTTPReceiver', () => { const defaultPort = new HTTPReceiver({ signingSecret: 'secret', }); - assert.isNotNull(defaultPort); - assert.propertyVal(defaultPort, 'port', 3000); + assert.notStrictEqual(defaultPort, null); + assert.ok(defaultPort && typeof defaultPort === 'object'); + assert.ok('port' in defaultPort); + assert.deepStrictEqual((defaultPort as unknown as Record).port, 3000); await defaultPort.start(9001); sinon.assert.calledWithMatch(fakeServer.listen, sinon.match(9001)); await defaultPort.stop(); @@ -224,7 +233,7 @@ describe('HTTPReceiver', () => { userScopes, }, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); receiver.installer = installProviderStub as unknown as InstallProvider; const fakeReq = sinon.createStubInstance(IncomingMessage) as IncomingMessage; fakeReq.url = '/hiya'; @@ -256,7 +265,7 @@ describe('HTTPReceiver', () => { userScopes, }, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); receiver.installer = installProviderStub as unknown as InstallProvider; const fakeReq: IncomingMessage = sinon.createStubInstance(IncomingMessage) as IncomingMessage; fakeReq.url = '/hiya'; @@ -288,7 +297,7 @@ describe('HTTPReceiver', () => { userScopes, }, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); receiver.installer = installProviderStub as unknown as InstallProvider; const fakeReq: IncomingMessage = sinon.createStubInstance(IncomingMessage) as IncomingMessage; fakeReq.url = '/hiya'; @@ -326,7 +335,7 @@ describe('HTTPReceiver', () => { userScopes, }, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); receiver.installer = installProviderStub as unknown as InstallProvider; const fakeReq = sinon.createStubInstance(IncomingMessage) as IncomingMessage; fakeReq.url = '/heyo'; @@ -370,7 +379,7 @@ describe('HTTPReceiver', () => { userScopes, }, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); receiver.installer = installProviderStub as unknown as InstallProvider; const fakeReq = sinon.createStubInstance(IncomingMessage) as IncomingMessage; fakeReq.url = '/heyo'; @@ -598,7 +607,7 @@ describe('HTTPReceiver', () => { customRoutes, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); receiver.installer = installProviderStub as unknown as InstallProvider; const fakeReq = sinon.createStubInstance(IncomingMessage) as IncomingMessage; diff --git a/test/unit/receivers/HTTPResponseAck.spec.ts b/test/unit/receivers/HTTPResponseAck.spec.ts index 5a2cd3de6..ab9fcd1e7 100644 --- a/test/unit/receivers/HTTPResponseAck.spec.ts +++ b/test/unit/receivers/HTTPResponseAck.spec.ts @@ -1,12 +1,18 @@ +import assert from 'node:assert/strict'; import { IncomingMessage, ServerResponse } from 'node:http'; -import { assert } from 'chai'; +import path from 'node:path'; +import { afterEach, beforeEach, describe, it } from 'node:test'; import sinon from 'sinon'; import { expectType } from 'tsd'; import { ReceiverMultipleAckError } from '../../../src/errors'; -import * as HTTPModuleFunctions from '../../../src/receivers/HTTPModuleFunctions'; import { HTTPResponseAck } from '../../../src/receivers/HTTPResponseAck'; import type { ResponseAck } from '../../../src/types'; -import { createFakeLogger } from '../helpers'; +import { createFakeLogger, proxyquire } from '../helpers'; + +function importHTTPResponseAck(overrides = {}): typeof import('../../../src/receivers/HTTPResponseAck') { + const absolutePath = path.resolve(__dirname, '../../../src/receivers/HTTPResponseAck'); + return proxyquire(absolutePath, overrides); +} describe('HTTPResponseAck', async () => { let setTimeoutSpy: sinon.SinonSpy; @@ -30,8 +36,8 @@ describe('HTTPResponseAck', async () => { httpRequest, httpResponse, }); - assert.isDefined(responseAck); - assert.isDefined(responseAck.bind()); + assert.notStrictEqual(responseAck, undefined); + assert.notStrictEqual(responseAck.bind(), undefined); expectType(responseAck); responseAck.ack(); // no exception }); @@ -52,7 +58,7 @@ describe('HTTPResponseAck', async () => { 'a 3 seconds timeout for the unhandledRequestHandler callback is expected', ); }); - it('should trigger unhandledRequestHandler if unacknowledged', (done) => { + it('should trigger unhandledRequestHandler if unacknowledged', async () => { const httpRequest = sinon.createStubInstance(IncomingMessage) as IncomingMessage; const httpResponse: ServerResponse = sinon.createStubInstance(ServerResponse) as unknown as ServerResponse; const unhandledRequestTimeoutMillis = 1; @@ -70,12 +76,10 @@ describe('HTTPResponseAck', async () => { unhandledRequestTimeoutMillis, `a ${unhandledRequestTimeoutMillis} timeout for the unhandledRequestHandler callback is expected`, ); - setTimeout(() => { - assert(spy.calledOnce); - done(); - }, 2); + await new Promise((resolve) => setTimeout(resolve, 2)); + assert(spy.calledOnce); }); - it('should not trigger unhandledRequestHandler if acknowledged', (done) => { + it('should not trigger unhandledRequestHandler if acknowledged', async () => { const httpRequest = sinon.createStubInstance(IncomingMessage) as IncomingMessage; const httpResponse: ServerResponse = sinon.createStubInstance(ServerResponse) as unknown as ServerResponse; const spy = sinon.spy(); @@ -88,10 +92,8 @@ describe('HTTPResponseAck', async () => { httpResponse, }); responseAck.ack(); - setTimeout(() => { - assert(spy.notCalled); - done(); - }, 2); + await new Promise((resolve) => setTimeout(resolve, 2)); + assert(spy.notCalled); }); it('should throw an error if a bound Ack invocation was already acknowledged', async () => { const httpRequest = sinon.createStubInstance(IncomingMessage) as IncomingMessage; @@ -108,7 +110,7 @@ describe('HTTPResponseAck', async () => { await bound(); assert.fail('No exception raised'); } catch (e) { - assert.instanceOf(e, ReceiverMultipleAckError); + assert.ok(e instanceof ReceiverMultipleAckError); } }); it('should store response body if processBeforeResponse=true', async () => { @@ -144,10 +146,15 @@ describe('HTTPResponseAck', async () => { ); }); it('should call buildContentResponse with response body if processBeforeResponse=false', async () => { - const stub = sinon.stub(HTTPModuleFunctions, 'buildContentResponse'); + const stub = sinon.stub(); + const { HTTPResponseAck: HTTPResponseAckWithStub } = importHTTPResponseAck({ + './HTTPModuleFunctions': { + buildContentResponse: stub, + }, + }); const httpRequest = sinon.createStubInstance(IncomingMessage) as IncomingMessage; const httpResponse: ServerResponse = sinon.createStubInstance(ServerResponse) as unknown as ServerResponse; - const responseAck = new HTTPResponseAck({ + const responseAck = new HTTPResponseAckWithStub({ logger: createFakeLogger(), processBeforeResponse: false, httpRequest, diff --git a/test/unit/receivers/SocketModeFunctions.spec.ts b/test/unit/receivers/SocketModeFunctions.spec.ts index fe68da142..19c394c2e 100644 --- a/test/unit/receivers/SocketModeFunctions.spec.ts +++ b/test/unit/receivers/SocketModeFunctions.spec.ts @@ -1,4 +1,5 @@ -import { assert } from 'chai'; +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; import { AuthorizationError, ReceiverMultipleAckError } from '../../../src/errors'; import { defaultProcessEventErrorHandler } from '../../../src/receivers/SocketModeFunctions'; import type { ReceiverEvent } from '../../../src/types'; @@ -19,7 +20,7 @@ describe('SocketModeFunctions', async () => { logger, event, }); - assert.isFalse(shouldBeAcked); + assert.strictEqual(shouldBeAcked, false); }); it('should return true if passed an AuthorizationError', async () => { const event: ReceiverEvent = { @@ -31,7 +32,7 @@ describe('SocketModeFunctions', async () => { logger, event, }); - assert.isTrue(shouldBeAcked); + assert.strictEqual(shouldBeAcked, true); }); }); }); diff --git a/test/unit/receivers/SocketModeReceiver.spec.ts b/test/unit/receivers/SocketModeReceiver.spec.ts index f76b8f2de..df38de9c2 100644 --- a/test/unit/receivers/SocketModeReceiver.spec.ts +++ b/test/unit/receivers/SocketModeReceiver.spec.ts @@ -1,9 +1,10 @@ +import assert from 'node:assert/strict'; import { EventEmitter } from 'node:events'; import { IncomingMessage, ServerResponse } from 'node:http'; import path from 'node:path'; +import { beforeEach, describe, it } from 'node:test'; import { InstallProvider } from '@slack/oauth'; import { SocketModeClient } from '@slack/socket-mode'; -import { assert } from 'chai'; import type { ParamsDictionary } from 'express-serve-static-core'; import { match } from 'path-to-regexp'; import sinon from 'sinon'; @@ -66,7 +67,7 @@ describe('SocketModeReceiver', () => { userScopes: ['chat:write'], }, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); }); it('should allow for customizing port the socket listens on', async () => { const SocketModeReceiver = importSocketModeReceiver(overrides); @@ -85,7 +86,7 @@ describe('SocketModeReceiver', () => { port: customPort, }, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); }); it('should allow for extracting additional values from Socket Mode messages', async () => { const SocketModeReceiver = importSocketModeReceiver(overrides); @@ -95,7 +96,7 @@ describe('SocketModeReceiver', () => { logger: noopLogger, customPropertiesExtractor: ({ type, body }) => ({ payload_type: type, body }), }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); }); it('should pass clientPingTimeout to SocketModeClient', async () => { const constructorSpy = sinon.spy(); @@ -117,7 +118,7 @@ describe('SocketModeReceiver', () => { logger: noopLogger, clientPingTimeout: 15000, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); sinon.assert.calledOnce(constructorSpy); const constructorArgs = constructorSpy.firstCall.args[0]; assert.equal(constructorArgs.clientPingTimeout, 15000); @@ -142,7 +143,7 @@ describe('SocketModeReceiver', () => { logger: noopLogger, serverPingTimeout: 60000, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); sinon.assert.calledOnce(constructorSpy); const constructorArgs = constructorSpy.firstCall.args[0]; assert.equal(constructorArgs.serverPingTimeout, 60000); @@ -170,7 +171,7 @@ describe('SocketModeReceiver', () => { pingPongLoggingEnabled: true, autoReconnectEnabled: false, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); sinon.assert.calledOnce(constructorSpy); const constructorArgs = constructorSpy.firstCall.args[0]; assert.equal(constructorArgs.clientPingTimeout, 15000); @@ -197,14 +198,14 @@ describe('SocketModeReceiver', () => { appToken: 'my-secret', logger: noopLogger, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); sinon.assert.calledOnce(constructorSpy); const constructorArgs = constructorSpy.firstCall.args[0]; assert.equal(constructorArgs.appToken, 'my-secret'); - assert.isUndefined(constructorArgs.clientPingTimeout); - assert.isUndefined(constructorArgs.serverPingTimeout); - assert.isUndefined(constructorArgs.pingPongLoggingEnabled); - assert.isUndefined(constructorArgs.autoReconnectEnabled); + assert.strictEqual(constructorArgs.clientPingTimeout, undefined); + assert.strictEqual(constructorArgs.serverPingTimeout, undefined); + assert.strictEqual(constructorArgs.pingPongLoggingEnabled, undefined); + assert.strictEqual(constructorArgs.autoReconnectEnabled, undefined); }); it('should throw an error if redirect uri options supplied invalid or incomplete', async () => { const SocketModeReceiver = importSocketModeReceiver(overrides); @@ -227,7 +228,7 @@ describe('SocketModeReceiver', () => { redirectUri, installerOptions, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); // redirectUri supplied, but no redirectUriPath assert.throws( () => @@ -299,7 +300,7 @@ describe('SocketModeReceiver', () => { userScopes, }, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); receiver.installer = installProviderStub as unknown as InstallProvider; const fakeReq = sinon.createStubInstance(IncomingMessage); fakeReq.url = '/nope'; @@ -331,7 +332,7 @@ describe('SocketModeReceiver', () => { userScopes, }, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); receiver.installer = installProviderStub as unknown as InstallProvider; const fakeReq = { url: '/hiya', @@ -366,7 +367,7 @@ describe('SocketModeReceiver', () => { userScopes, }, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); receiver.installer = installProviderStub as unknown as InstallProvider; const fakeReq = { url: '/hiya', @@ -401,7 +402,7 @@ describe('SocketModeReceiver', () => { userScopes, }, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); receiver.installer = installProviderStub as unknown as InstallProvider; const fakeReq = { url: '/hiya', @@ -440,7 +441,7 @@ describe('SocketModeReceiver', () => { callbackOptions, }, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); receiver.installer = installProviderStub as unknown as InstallProvider; const fakeReq = { url: '/heyo', @@ -490,7 +491,7 @@ describe('SocketModeReceiver', () => { metadata, }, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); receiver.installer = installProviderStub as unknown as InstallProvider; const fakeReq = sinon.createStubInstance(IncomingMessage); fakeReq.url = '/heyo'; @@ -518,7 +519,7 @@ describe('SocketModeReceiver', () => { appToken: 'my-secret', customRoutes, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); receiver.installer = installProviderStub as unknown as InstallProvider; const fakeReq = sinon.createStubInstance(IncomingMessage); @@ -556,7 +557,7 @@ describe('SocketModeReceiver', () => { appToken: 'my-secret', customRoutes, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); receiver.installer = installProviderStub as unknown as InstallProvider; const fakeReq = sinon.createStubInstance(IncomingMessage); @@ -594,7 +595,7 @@ describe('SocketModeReceiver', () => { appToken: 'my-secret', customRoutes, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); receiver.installer = installProviderStub as unknown as InstallProvider; const fakeReq = sinon.createStubInstance(IncomingMessage); @@ -635,7 +636,7 @@ describe('SocketModeReceiver', () => { appToken: 'my-secret', customRoutes, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); receiver.installer = installProviderStub as unknown as InstallProvider; const fakeReq = sinon.createStubInstance(IncomingMessage); @@ -678,7 +679,7 @@ describe('SocketModeReceiver', () => { appToken: 'my-secret', customRoutes, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); receiver.installer = installProviderStub as unknown as InstallProvider; const fakeReq = sinon.createStubInstance(IncomingMessage); @@ -737,7 +738,7 @@ describe('SocketModeReceiver', () => { userScopes: ['chat:write'], }, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); receiver.client = clientStub as unknown as SocketModeClient; await receiver.start(); assert(clientStub.start.called); @@ -761,7 +762,7 @@ describe('SocketModeReceiver', () => { userScopes: ['chat:write'], }, }); - assert.isNotNull(receiver); + assert.notStrictEqual(receiver, null); receiver.client = clientStub as unknown as SocketModeClient; await receiver.stop(); assert(clientStub.disconnect.called); diff --git a/test/unit/receivers/SocketModeResponseAck.spec.ts b/test/unit/receivers/SocketModeResponseAck.spec.ts index a10020d48..be463844e 100644 --- a/test/unit/receivers/SocketModeResponseAck.spec.ts +++ b/test/unit/receivers/SocketModeResponseAck.spec.ts @@ -1,4 +1,5 @@ -import { assert } from 'chai'; +import assert from 'node:assert/strict'; +import { beforeEach, describe, it } from 'node:test'; import sinon from 'sinon'; import { expectType } from 'tsd'; import { SocketModeResponseAck } from '../../../src/receivers/SocketModeResponseAck'; @@ -18,8 +19,8 @@ describe('SocketModeResponseAck', async () => { logger: fakeLogger, socketModeClientAck: fakeSocketModeClientAck, }); - assert.isDefined(responseAck); - assert.isDefined(responseAck.bind()); + assert.notStrictEqual(responseAck, undefined); + assert.notStrictEqual(responseAck.bind(), undefined); expectType(responseAck); }); diff --git a/test/unit/receivers/verify-request.spec.ts b/test/unit/receivers/verify-request.spec.ts index 7554abb9c..e3cd6b111 100644 --- a/test/unit/receivers/verify-request.spec.ts +++ b/test/unit/receivers/verify-request.spec.ts @@ -1,5 +1,6 @@ +import assert from 'node:assert/strict'; import { createHmac } from 'node:crypto'; -import { assert } from 'chai'; +import { describe, it } from 'node:test'; import { isValidSlackRequest, verifySlackRequest } from '../../../src/receivers/verify-request'; describe('Request verification', async () => { @@ -37,9 +38,10 @@ describe('Request verification', async () => { body: rawBody, }); } catch (e) { - assert.propertyVal( - e, - 'message', + assert.ok(e && typeof e === 'object'); + assert.ok('message' in e); + assert.deepStrictEqual( + (e as unknown as Record).message, 'Failed to verify authenticity: x-slack-request-timestamp must differ from system time by no more than 5 minutes or request is stale', ); } @@ -57,7 +59,12 @@ describe('Request verification', async () => { body: rawBody, }); } catch (e) { - assert.propertyVal(e, 'message', 'Failed to verify authenticity: signature mismatch'); + assert.ok(e && typeof e === 'object'); + assert.ok('message' in e); + assert.deepStrictEqual( + (e as unknown as Record).message, + 'Failed to verify authenticity: signature mismatch', + ); } }); }); @@ -69,7 +76,7 @@ describe('Request verification', async () => { const hmac = createHmac('sha256', signingSecret); hmac.update(`v0:${timestamp}:${rawBody}`); const signature = hmac.digest('hex'); - assert.isTrue( + assert.strictEqual( isValidSlackRequest({ signingSecret, headers: { @@ -78,6 +85,7 @@ describe('Request verification', async () => { }, body: rawBody, }), + true, ); }); it('should detect an invalid timestamp', async () => { @@ -86,7 +94,7 @@ describe('Request verification', async () => { const hmac = createHmac('sha256', signingSecret); hmac.update(`v0:${timestamp}:${rawBody}`); const signature = hmac.digest('hex'); - assert.isFalse( + assert.strictEqual( isValidSlackRequest({ signingSecret, headers: { @@ -95,12 +103,13 @@ describe('Request verification', async () => { }, body: rawBody, }), + false, ); }); it('should detect an invalid signature', async () => { const timestamp = Math.floor(Date.now() / 1000); const rawBody = '{"foo":"bar"}'; - assert.isFalse( + assert.strictEqual( isValidSlackRequest({ signingSecret, headers: { @@ -109,6 +118,7 @@ describe('Request verification', async () => { }, body: rawBody, }), + false, ); }); }); diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 000000000..8315678f6 --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + "types": ["node"] + }, + "include": [ + "src/**/*", + "test/**/*" + ], + "exclude": [] +}