diff --git a/.evergreen/config.in.yml b/.evergreen/config.in.yml index 3ad36514e5..aeec8a62a9 100644 --- a/.evergreen/config.in.yml +++ b/.evergreen/config.in.yml @@ -798,18 +798,6 @@ functions: bash ${PROJECT_DIRECTORY}/.evergreen/run-custom-csfle-tests.sh - "run custom snappy tests": - - command: subprocess.exec - params: - working_dir: "src" - timeout_secs: 60 - env: - MONGODB_URI: ${MONGODB_URI} - PROJECT_DIRECTORY: ${PROJECT_DIRECTORY} - binary: bash - args: - - "${PROJECT_DIRECTORY}/.evergreen/run-snappy-version-test.sh" - "run lambda handler example tests": - command: subprocess.exec params: diff --git a/.evergreen/config.yml b/.evergreen/config.yml index d431ae124b..c471a75d78 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -758,17 +758,6 @@ functions: echo "CRYPT_SHARED_LIB_PATH: $CRYPT_SHARED_LIB_PATH" bash ${PROJECT_DIRECTORY}/.evergreen/run-custom-csfle-tests.sh - run custom snappy tests: - - command: subprocess.exec - params: - working_dir: src - timeout_secs: 60 - env: - MONGODB_URI: ${MONGODB_URI} - PROJECT_DIRECTORY: ${PROJECT_DIRECTORY} - binary: bash - args: - - ${PROJECT_DIRECTORY}/.evergreen/run-snappy-version-test.sh run lambda handler example tests: - command: subprocess.exec params: @@ -2142,20 +2131,6 @@ tasks: variant: '*' status: '*' patch_optional: true - - name: run-custom-snappy-tests - tags: - - run-custom-dependency-tests - commands: - - func: install dependencies - vars: - NODE_LTS_NAME: erbium - - func: bootstrap mongo-orchestration - vars: - VERSION: '5.0' - TOPOLOGY: server - AUTH: auth - - name: run-custom-snappy-tests - func: run custom snappy tests - name: run-bson-ext-integration tags: - run-custom-dependency-tests @@ -3175,7 +3150,6 @@ buildvariants: display_name: Custom Dependency Version Test run_on: rhel80-large tasks: - - run-custom-snappy-tests - run-bson-ext-integration - run-bson-ext-unit - run-custom-csfle-tests-5.0-pinned-commit diff --git a/.evergreen/generate_evergreen_tasks.js b/.evergreen/generate_evergreen_tasks.js index 26e9d7e3ec..8883537216 100644 --- a/.evergreen/generate_evergreen_tasks.js +++ b/.evergreen/generate_evergreen_tasks.js @@ -578,10 +578,6 @@ BUILD_VARIANTS.push({ }); const oneOffFuncs = [ - { - name: 'run-custom-snappy-tests', - func: 'run custom snappy tests' - }, { name: 'run-bson-ext-integration', func: 'run bson-ext test', diff --git a/.evergreen/run-snappy-version-test.sh b/.evergreen/run-snappy-version-test.sh deleted file mode 100644 index 15df4a577d..0000000000 --- a/.evergreen/run-snappy-version-test.sh +++ /dev/null @@ -1,12 +0,0 @@ -#! /usr/bin/env bash - -source "${PROJECT_DIRECTORY}/.evergreen/init-nvm.sh" -export MONGODB_URI="${MONGODB_URI}" - -npm i --no-save snappy@6 - -npm run check:snappy - -npm i --no-save snappy@7 - -npm run check:snappy diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index 40356a5cdf..531d8e7170 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -22,5 +22,5 @@ jobs: with: node-version: ${{ matrix.node-version }} cache: 'npm' - - run: npm install + - run: npm clean-install - run: npm run check:dependencies diff --git a/etc/notes/CHANGES_5.0.0.md b/etc/notes/CHANGES_5.0.0.md new file mode 100644 index 0000000000..c3f93b2262 --- /dev/null +++ b/etc/notes/CHANGES_5.0.0.md @@ -0,0 +1,26 @@ +# Changes in v5 + +## TOC + +- TODO + +## About + +The following is a detailed collection of the changes in the major v5 release of the mongodb package for Node.js. + + + +## Changes + +### Snappy v7.x.x or later and optional peerDependency + +`snappy` compression has been added to the package.json as a peerDependency that is **optional**. +This means `npm` will let you know if the version of snappy you have installed is incompatible with the driver. + +```sh +npm install --save snappy@7 +``` diff --git a/package-lock.json b/package-lock.json index 4116d469ce..b1b0460cf9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,6 +52,7 @@ "semver": "^7.3.8", "sinon": "^13.0.1", "sinon-chai": "^3.7.0", + "snappy": "^7.2.2", "source-map-support": "^0.5.21", "standard-version": "^9.5.0", "ts-node": "^10.9.1", @@ -67,6 +68,14 @@ "optionalDependencies": { "@aws-sdk/credential-providers": "^3.186.0", "saslprep": "^1.0.3" + }, + "peerDependencies": { + "snappy": "7.x.x" + }, + "peerDependenciesMeta": { + "snappy": { + "optional": true + } } }, "node_modules/@ampproject/remapping": { @@ -1861,6 +1870,214 @@ "node": ">= 10" } }, + "node_modules/@napi-rs/snappy-android-arm-eabi": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-android-arm-eabi/-/snappy-android-arm-eabi-7.2.2.tgz", + "integrity": "sha512-H7DuVkPCK5BlAr1NfSU8bDEN7gYs+R78pSHhDng83QxRnCLmVIZk33ymmIwurmoA1HrdTxbkbuNl+lMvNqnytw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-android-arm64": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-android-arm64/-/snappy-android-arm64-7.2.2.tgz", + "integrity": "sha512-2R/A3qok+nGtpVK8oUMcrIi5OMDckGYNoBLFyli3zp8w6IArPRfg1yOfVUcHvpUDTo9T7LOS1fXgMOoC796eQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-darwin-arm64": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-darwin-arm64/-/snappy-darwin-arm64-7.2.2.tgz", + "integrity": "sha512-USgArHbfrmdbuq33bD5ssbkPIoT7YCXCRLmZpDS6dMDrx+iM7eD2BecNbOOo7/v1eu6TRmQ0xOzeQ6I/9FIi5g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-darwin-x64": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-darwin-x64/-/snappy-darwin-x64-7.2.2.tgz", + "integrity": "sha512-0APDu8iO5iT0IJKblk2lH0VpWSl9zOZndZKnBYIc+ei1npw2L5QvuErFOTeTdHBtzvUHASB+9bvgaWnQo4PvTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-freebsd-x64": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-freebsd-x64/-/snappy-freebsd-x64-7.2.2.tgz", + "integrity": "sha512-mRTCJsuzy0o/B0Hnp9CwNB5V6cOJ4wedDTWEthsdKHSsQlO7WU9W1yP7H3Qv3Ccp/ZfMyrmG98Ad7u7lG58WXA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-linux-arm-gnueabihf": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-arm-gnueabihf/-/snappy-linux-arm-gnueabihf-7.2.2.tgz", + "integrity": "sha512-v1uzm8+6uYjasBPcFkv90VLZ+WhLzr/tnfkZ/iD9mHYiULqkqpRuC8zvc3FZaJy5wLQE9zTDkTJN1IvUcZ+Vcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-linux-arm64-gnu": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-arm64-gnu/-/snappy-linux-arm64-gnu-7.2.2.tgz", + "integrity": "sha512-LrEMa5pBScs4GXWOn6ZYXfQ72IzoolZw5txqUHVGs8eK4g1HR9HTHhb2oY5ySNaKakG5sOgMsb1rwaEnjhChmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-linux-arm64-musl": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-arm64-musl/-/snappy-linux-arm64-musl-7.2.2.tgz", + "integrity": "sha512-3orWZo9hUpGQcB+3aTLW7UFDqNCQfbr0+MvV67x8nMNYj5eAeUtMmUE/HxLznHO4eZ1qSqiTwLbVx05/Socdlw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-linux-x64-gnu": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-x64-gnu/-/snappy-linux-x64-gnu-7.2.2.tgz", + "integrity": "sha512-jZt8Jit/HHDcavt80zxEkDpH+R1Ic0ssiVCoueASzMXa7vwPJeF4ZxZyqUw4qeSy7n8UUExomu8G8ZbP6VKhgw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-linux-x64-musl": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-x64-musl/-/snappy-linux-x64-musl-7.2.2.tgz", + "integrity": "sha512-Dh96IXgcZrV39a+Tej/owcd9vr5ihiZ3KRix11rr1v0MWtVb61+H1GXXlz6+Zcx9y8jM1NmOuiIuJwkV4vZ4WA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-win32-arm64-msvc": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-win32-arm64-msvc/-/snappy-win32-arm64-msvc-7.2.2.tgz", + "integrity": "sha512-9No0b3xGbHSWv2wtLEn3MO76Yopn1U2TdemZpCaEgOGccz1V+a/1d16Piz3ofSmnA13HGFz3h9NwZH9EOaIgYA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-win32-ia32-msvc": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-win32-ia32-msvc/-/snappy-win32-ia32-msvc-7.2.2.tgz", + "integrity": "sha512-QiGe+0G86J74Qz1JcHtBwM3OYdTni1hX1PFyLRo3HhQUSpmi13Bzc1En7APn+6Pvo7gkrcy81dObGLDSxFAkQQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-win32-x64-msvc": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-win32-x64-msvc/-/snappy-win32-x64-msvc-7.2.2.tgz", + "integrity": "sha512-a43cyx1nK0daw6BZxVcvDEXxKMFLSBSDTAhsFD0VqSKcC7MGUBMaqyoWUcMiI7LBSz4bxUmxDWKfCYzpEmeb3w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -7844,6 +8061,34 @@ "npm": ">= 3.0.0" } }, + "node_modules/snappy": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/snappy/-/snappy-7.2.2.tgz", + "integrity": "sha512-iADMq1kY0v3vJmGTuKcFWSXt15qYUz7wFkArOrsSg0IFfI3nJqIJvK2/ZbEIndg7erIJLtAVX2nSOqPz7DcwbA==", + "dev": true, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/snappy-android-arm-eabi": "7.2.2", + "@napi-rs/snappy-android-arm64": "7.2.2", + "@napi-rs/snappy-darwin-arm64": "7.2.2", + "@napi-rs/snappy-darwin-x64": "7.2.2", + "@napi-rs/snappy-freebsd-x64": "7.2.2", + "@napi-rs/snappy-linux-arm-gnueabihf": "7.2.2", + "@napi-rs/snappy-linux-arm64-gnu": "7.2.2", + "@napi-rs/snappy-linux-arm64-musl": "7.2.2", + "@napi-rs/snappy-linux-x64-gnu": "7.2.2", + "@napi-rs/snappy-linux-x64-musl": "7.2.2", + "@napi-rs/snappy-win32-arm64-msvc": "7.2.2", + "@napi-rs/snappy-win32-ia32-msvc": "7.2.2", + "@napi-rs/snappy-win32-x64-msvc": "7.2.2" + } + }, "node_modules/socks": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", @@ -10644,6 +10889,97 @@ "dev": true, "optional": true }, + "@napi-rs/snappy-android-arm-eabi": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-android-arm-eabi/-/snappy-android-arm-eabi-7.2.2.tgz", + "integrity": "sha512-H7DuVkPCK5BlAr1NfSU8bDEN7gYs+R78pSHhDng83QxRnCLmVIZk33ymmIwurmoA1HrdTxbkbuNl+lMvNqnytw==", + "dev": true, + "optional": true + }, + "@napi-rs/snappy-android-arm64": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-android-arm64/-/snappy-android-arm64-7.2.2.tgz", + "integrity": "sha512-2R/A3qok+nGtpVK8oUMcrIi5OMDckGYNoBLFyli3zp8w6IArPRfg1yOfVUcHvpUDTo9T7LOS1fXgMOoC796eQw==", + "dev": true, + "optional": true + }, + "@napi-rs/snappy-darwin-arm64": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-darwin-arm64/-/snappy-darwin-arm64-7.2.2.tgz", + "integrity": "sha512-USgArHbfrmdbuq33bD5ssbkPIoT7YCXCRLmZpDS6dMDrx+iM7eD2BecNbOOo7/v1eu6TRmQ0xOzeQ6I/9FIi5g==", + "dev": true, + "optional": true + }, + "@napi-rs/snappy-darwin-x64": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-darwin-x64/-/snappy-darwin-x64-7.2.2.tgz", + "integrity": "sha512-0APDu8iO5iT0IJKblk2lH0VpWSl9zOZndZKnBYIc+ei1npw2L5QvuErFOTeTdHBtzvUHASB+9bvgaWnQo4PvTQ==", + "dev": true, + "optional": true + }, + "@napi-rs/snappy-freebsd-x64": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-freebsd-x64/-/snappy-freebsd-x64-7.2.2.tgz", + "integrity": "sha512-mRTCJsuzy0o/B0Hnp9CwNB5V6cOJ4wedDTWEthsdKHSsQlO7WU9W1yP7H3Qv3Ccp/ZfMyrmG98Ad7u7lG58WXA==", + "dev": true, + "optional": true + }, + "@napi-rs/snappy-linux-arm-gnueabihf": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-arm-gnueabihf/-/snappy-linux-arm-gnueabihf-7.2.2.tgz", + "integrity": "sha512-v1uzm8+6uYjasBPcFkv90VLZ+WhLzr/tnfkZ/iD9mHYiULqkqpRuC8zvc3FZaJy5wLQE9zTDkTJN1IvUcZ+Vcg==", + "dev": true, + "optional": true + }, + "@napi-rs/snappy-linux-arm64-gnu": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-arm64-gnu/-/snappy-linux-arm64-gnu-7.2.2.tgz", + "integrity": "sha512-LrEMa5pBScs4GXWOn6ZYXfQ72IzoolZw5txqUHVGs8eK4g1HR9HTHhb2oY5ySNaKakG5sOgMsb1rwaEnjhChmQ==", + "dev": true, + "optional": true + }, + "@napi-rs/snappy-linux-arm64-musl": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-arm64-musl/-/snappy-linux-arm64-musl-7.2.2.tgz", + "integrity": "sha512-3orWZo9hUpGQcB+3aTLW7UFDqNCQfbr0+MvV67x8nMNYj5eAeUtMmUE/HxLznHO4eZ1qSqiTwLbVx05/Socdlw==", + "dev": true, + "optional": true + }, + "@napi-rs/snappy-linux-x64-gnu": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-x64-gnu/-/snappy-linux-x64-gnu-7.2.2.tgz", + "integrity": "sha512-jZt8Jit/HHDcavt80zxEkDpH+R1Ic0ssiVCoueASzMXa7vwPJeF4ZxZyqUw4qeSy7n8UUExomu8G8ZbP6VKhgw==", + "dev": true, + "optional": true + }, + "@napi-rs/snappy-linux-x64-musl": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-x64-musl/-/snappy-linux-x64-musl-7.2.2.tgz", + "integrity": "sha512-Dh96IXgcZrV39a+Tej/owcd9vr5ihiZ3KRix11rr1v0MWtVb61+H1GXXlz6+Zcx9y8jM1NmOuiIuJwkV4vZ4WA==", + "dev": true, + "optional": true + }, + "@napi-rs/snappy-win32-arm64-msvc": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-win32-arm64-msvc/-/snappy-win32-arm64-msvc-7.2.2.tgz", + "integrity": "sha512-9No0b3xGbHSWv2wtLEn3MO76Yopn1U2TdemZpCaEgOGccz1V+a/1d16Piz3ofSmnA13HGFz3h9NwZH9EOaIgYA==", + "dev": true, + "optional": true + }, + "@napi-rs/snappy-win32-ia32-msvc": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-win32-ia32-msvc/-/snappy-win32-ia32-msvc-7.2.2.tgz", + "integrity": "sha512-QiGe+0G86J74Qz1JcHtBwM3OYdTni1hX1PFyLRo3HhQUSpmi13Bzc1En7APn+6Pvo7gkrcy81dObGLDSxFAkQQ==", + "dev": true, + "optional": true + }, + "@napi-rs/snappy-win32-x64-msvc": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-win32-x64-msvc/-/snappy-win32-x64-msvc-7.2.2.tgz", + "integrity": "sha512-a43cyx1nK0daw6BZxVcvDEXxKMFLSBSDTAhsFD0VqSKcC7MGUBMaqyoWUcMiI7LBSz4bxUmxDWKfCYzpEmeb3w==", + "dev": true, + "optional": true + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -15165,6 +15501,27 @@ "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" }, + "snappy": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/snappy/-/snappy-7.2.2.tgz", + "integrity": "sha512-iADMq1kY0v3vJmGTuKcFWSXt15qYUz7wFkArOrsSg0IFfI3nJqIJvK2/ZbEIndg7erIJLtAVX2nSOqPz7DcwbA==", + "dev": true, + "requires": { + "@napi-rs/snappy-android-arm-eabi": "7.2.2", + "@napi-rs/snappy-android-arm64": "7.2.2", + "@napi-rs/snappy-darwin-arm64": "7.2.2", + "@napi-rs/snappy-darwin-x64": "7.2.2", + "@napi-rs/snappy-freebsd-x64": "7.2.2", + "@napi-rs/snappy-linux-arm-gnueabihf": "7.2.2", + "@napi-rs/snappy-linux-arm64-gnu": "7.2.2", + "@napi-rs/snappy-linux-arm64-musl": "7.2.2", + "@napi-rs/snappy-linux-x64-gnu": "7.2.2", + "@napi-rs/snappy-linux-x64-musl": "7.2.2", + "@napi-rs/snappy-win32-arm64-msvc": "7.2.2", + "@napi-rs/snappy-win32-ia32-msvc": "7.2.2", + "@napi-rs/snappy-win32-x64-msvc": "7.2.2" + } + }, "socks": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", diff --git a/package.json b/package.json index 4671cf52a3..3286c56045 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,18 @@ "mongodb-connection-string-url": "^2.6.0", "socks": "^2.7.1" }, + "optionalDependencies": { + "@aws-sdk/credential-providers": "^3.186.0", + "saslprep": "^1.0.3" + }, + "peerDependencies": { + "snappy": "7.x.x" + }, + "peerDependenciesMeta": { + "snappy": { + "optional": true + } + }, "devDependencies": { "@iarna/toml": "^2.2.5", "@istanbuljs/nyc-config-typescript": "^1.0.2", @@ -68,6 +80,7 @@ "semver": "^7.3.8", "sinon": "^13.0.1", "sinon-chai": "^3.7.0", + "snappy": "^7.2.2", "source-map-support": "^0.5.21", "standard-version": "^9.5.0", "ts-node": "^10.9.1", @@ -85,10 +98,6 @@ "url": "https://jira.mongodb.org/projects/NODE/issues/" }, "homepage": "https://github.com/mongodb/node-mongodb-native", - "optionalDependencies": { - "@aws-sdk/credential-providers": "^3.186.0", - "saslprep": "^1.0.3" - }, "scripts": { "build:evergreen": "node .evergreen/generate_evergreen_tasks.js", "build:ts": "node ./node_modules/typescript/bin/tsc", diff --git a/src/cmap/message_stream.ts b/src/cmap/message_stream.ts index f0c4ad639d..409e999777 100644 --- a/src/cmap/message_stream.ts +++ b/src/cmap/message_stream.ts @@ -80,12 +80,8 @@ export class MessageStream extends Duplex { command: WriteProtocolMessageType, operationDescription: OperationDescription ): void { - // TODO: agreed compressor should live in `StreamDescription` - const compressorName: CompressorName = - operationDescription && operationDescription.agreedCompressor - ? operationDescription.agreedCompressor - : 'none'; - if (compressorName === 'none' || !canCompress(command)) { + const agreedCompressor = operationDescription.agreedCompressor ?? 'none'; + if (agreedCompressor === 'none' || !canCompress(command)) { const data = command.toBin(); this.push(Array.isArray(data) ? Buffer.concat(data) : data); return; @@ -97,30 +93,34 @@ export class MessageStream extends Duplex { // Extract information needed for OP_COMPRESSED from the uncompressed message const originalCommandOpCode = concatenatedOriginalCommandBuffer.readInt32LE(12); + const options = { + agreedCompressor, + zlibCompressionLevel: operationDescription.zlibCompressionLevel ?? 0 + }; // Compress the message body - compress({ options: operationDescription }, messageToBeCompressed, (err, compressedMessage) => { - if (err || !compressedMessage) { - operationDescription.cb(err); - return; + compress(options, messageToBeCompressed).then( + compressedMessage => { + // Create the msgHeader of OP_COMPRESSED + const msgHeader = Buffer.alloc(MESSAGE_HEADER_SIZE); + msgHeader.writeInt32LE( + MESSAGE_HEADER_SIZE + COMPRESSION_DETAILS_SIZE + compressedMessage.length, + 0 + ); // messageLength + msgHeader.writeInt32LE(command.requestId, 4); // requestID + msgHeader.writeInt32LE(0, 8); // responseTo (zero) + msgHeader.writeInt32LE(OP_COMPRESSED, 12); // opCode + + // Create the compression details of OP_COMPRESSED + const compressionDetails = Buffer.alloc(COMPRESSION_DETAILS_SIZE); + compressionDetails.writeInt32LE(originalCommandOpCode, 0); // originalOpcode + compressionDetails.writeInt32LE(messageToBeCompressed.length, 4); // Size of the uncompressed compressedMessage, excluding the MsgHeader + compressionDetails.writeUInt8(Compressor[agreedCompressor], 8); // compressorID + this.push(Buffer.concat([msgHeader, compressionDetails, compressedMessage])); + }, + error => { + operationDescription.cb(error); } - - // Create the msgHeader of OP_COMPRESSED - const msgHeader = Buffer.alloc(MESSAGE_HEADER_SIZE); - msgHeader.writeInt32LE( - MESSAGE_HEADER_SIZE + COMPRESSION_DETAILS_SIZE + compressedMessage.length, - 0 - ); // messageLength - msgHeader.writeInt32LE(command.requestId, 4); // requestID - msgHeader.writeInt32LE(0, 8); // responseTo (zero) - msgHeader.writeInt32LE(OP_COMPRESSED, 12); // opCode - - // Create the compression details of OP_COMPRESSED - const compressionDetails = Buffer.alloc(COMPRESSION_DETAILS_SIZE); - compressionDetails.writeInt32LE(originalCommandOpCode, 0); // originalOpcode - compressionDetails.writeInt32LE(messageToBeCompressed.length, 4); // Size of the uncompressed compressedMessage, excluding the MsgHeader - compressionDetails.writeUInt8(Compressor[compressorName], 8); // compressorID - this.push(Buffer.concat([msgHeader, compressionDetails, compressedMessage])); - }); + ); } } @@ -197,33 +197,34 @@ function processIncomingData(stream: MessageStream, callback: Callback): messageHeader.fromCompressed = true; messageHeader.opCode = message.readInt32LE(MESSAGE_HEADER_SIZE); messageHeader.length = message.readInt32LE(MESSAGE_HEADER_SIZE + 4); - const compressorID: Compressor = message[MESSAGE_HEADER_SIZE + 8] as Compressor; + const compressorID = message[MESSAGE_HEADER_SIZE + 8]; const compressedBuffer = message.slice(MESSAGE_HEADER_SIZE + 9); // recalculate based on wrapped opcode ResponseType = messageHeader.opCode === OP_MSG ? BinMsg : Response; - return decompress(compressorID, compressedBuffer, (err, messageBody) => { - if (err || !messageBody) { - return callback(err); - } - - if (messageBody.length !== messageHeader.length) { - return callback( - new MongoDecompressionError('Message body and message header must be the same length') - ); - } + decompress(compressorID, compressedBuffer).then( + messageBody => { + if (messageBody.length !== messageHeader.length) { + return callback( + new MongoDecompressionError('Message body and message header must be the same length') + ); + } - // If we are a monitoring connection message stream and - // there is more in the buffer that can be read, skip processing since we - // want the last hello command response that is in the buffer. - if (monitorHasAnotherHello()) { - return processIncomingData(stream, callback); - } - stream.emit('message', new ResponseType(message, messageHeader, messageBody)); + // If we are a monitoring connection message stream and + // there is more in the buffer that can be read, skip processing since we + // want the last hello command response that is in the buffer. + if (monitorHasAnotherHello()) { + return processIncomingData(stream, callback); + } + stream.emit('message', new ResponseType(message, messageHeader, messageBody)); - if (buffer.length >= 4) { - return processIncomingData(stream, callback); + if (buffer.length >= 4) { + return processIncomingData(stream, callback); + } + return callback(); + }, + error => { + return callback(error); } - return callback(); - }); + ); } diff --git a/src/cmap/wire_protocol/compression.ts b/src/cmap/wire_protocol/compression.ts index 68e1af5450..090c42e926 100644 --- a/src/cmap/wire_protocol/compression.ts +++ b/src/cmap/wire_protocol/compression.ts @@ -1,10 +1,9 @@ +import { promisify } from 'util'; import * as zlib from 'zlib'; import { LEGACY_HELLO_COMMAND } from '../../constants'; -import { PKG_VERSION, Snappy, ZStandard } from '../../deps'; +import { Snappy, ZStandard } from '../../deps'; import { MongoDecompressionError, MongoInvalidArgumentError } from '../../error'; -import type { Callback } from '../../utils'; -import type { OperationDescription } from '../message_stream'; /** @public */ export const Compressor = Object.freeze({ @@ -33,98 +32,73 @@ export const uncompressibleCommands = new Set([ 'copydb' ]); -const MAX_COMPRESSOR_ID = 3; const ZSTD_COMPRESSION_LEVEL = 3; +const zlibInflate = promisify(zlib.inflate.bind(zlib)); +const zlibDeflate = promisify(zlib.deflate.bind(zlib)); + // Facilitate compressing a message using an agreed compressor -export function compress( - self: { options: OperationDescription & zlib.ZlibOptions }, - dataToBeCompressed: Buffer, - callback: Callback -): void { +export async function compress( + options: { zlibCompressionLevel: number; agreedCompressor: CompressorName }, + dataToBeCompressed: Buffer +): Promise { const zlibOptions = {} as zlib.ZlibOptions; - switch (self.options.agreedCompressor) { - case 'snappy': { + switch (options.agreedCompressor) { + case 'snappy': if ('kModuleError' in Snappy) { - return callback(Snappy['kModuleError']); + throw Snappy['kModuleError']; } + return Snappy.compress(dataToBeCompressed); - if (Snappy[PKG_VERSION].major <= 6) { - Snappy.compress(dataToBeCompressed, callback); - } else { - Snappy.compress(dataToBeCompressed).then( - buffer => callback(undefined, buffer), - error => callback(error) - ); - } - break; - } - case 'zlib': - // Determine zlibCompressionLevel - if (self.options.zlibCompressionLevel) { - zlibOptions.level = self.options.zlibCompressionLevel; - } - zlib.deflate(dataToBeCompressed, zlibOptions, callback as zlib.CompressCallback); - break; case 'zstd': if ('kModuleError' in ZStandard) { - return callback(ZStandard['kModuleError']); + throw ZStandard['kModuleError']; } - ZStandard.compress(dataToBeCompressed, ZSTD_COMPRESSION_LEVEL).then( - buffer => callback(undefined, buffer), - error => callback(error) - ); - break; + return ZStandard.compress(dataToBeCompressed, ZSTD_COMPRESSION_LEVEL); + + case 'zlib': + if (options.zlibCompressionLevel) { + zlibOptions.level = options.zlibCompressionLevel; + } + return zlibDeflate(dataToBeCompressed, zlibOptions); + default: throw new MongoInvalidArgumentError( - `Unknown compressor ${self.options.agreedCompressor} failed to compress` + `Unknown compressor ${options.agreedCompressor} failed to compress` ); } } // Decompress a message using the given compressor -export function decompress( - compressorID: Compressor, - compressedData: Buffer, - callback: Callback -): void { - if (compressorID < 0 || compressorID > MAX_COMPRESSOR_ID) { +export async function decompress(compressorID: number, compressedData: Buffer): Promise { + if ( + compressorID !== Compressor.snappy && + compressorID !== Compressor.zstd && + compressorID !== Compressor.zlib && + compressorID !== Compressor.none + ) { throw new MongoDecompressionError( `Server sent message compressed using an unsupported compressor. (Received compressor ID ${compressorID})` ); } switch (compressorID) { - case Compressor.snappy: { + case Compressor.snappy: if ('kModuleError' in Snappy) { - return callback(Snappy['kModuleError']); + throw Snappy['kModuleError']; } + return Snappy.uncompress(compressedData, { asBuffer: true }); - if (Snappy[PKG_VERSION].major <= 6) { - Snappy.uncompress(compressedData, { asBuffer: true }, callback); - } else { - Snappy.uncompress(compressedData, { asBuffer: true }).then( - buffer => callback(undefined, buffer), - error => callback(error) - ); - } - break; - } - case Compressor.zstd: { + case Compressor.zstd: if ('kModuleError' in ZStandard) { - return callback(ZStandard['kModuleError']); + throw ZStandard['kModuleError']; } + return ZStandard.decompress(compressedData); - ZStandard.decompress(compressedData).then( - buffer => callback(undefined, buffer), - error => callback(error) - ); - break; - } case Compressor.zlib: - zlib.inflate(compressedData, callback as zlib.CompressCallback); - break; + return zlibInflate(compressedData); + default: - callback(undefined, compressedData); + return compressedData; } } diff --git a/src/deps.ts b/src/deps.ts index 752e1ddf05..28290fb670 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -4,9 +4,7 @@ import type { AWSCredentials } from './cmap/auth/mongodb_aws'; import type { ProxyOptions } from './cmap/connection'; import { MongoMissingDependencyError } from './error'; import type { MongoClient } from './mongo_client'; -import { Callback, parsePackageVersion } from './utils'; - -export const PKG_VERSION = Symbol('kPkgVersion'); +import type { Callback } from './utils'; function makeErrorModule(error: any) { const props = error ? { kModuleError: error } : {}; @@ -90,33 +88,17 @@ export function getAwsCredentialProvider(): } type SnappyLib = { - [PKG_VERSION]: { major: number; minor: number; patch: number }; - /** - * - Snappy 6.x takes a callback and returns void - * - Snappy 7.x returns a promise - * * In order to support both we must check the return value of the function * @param buf - Buffer to be compressed - * @param callback - ONLY USED IN SNAPPY 6.x */ compress(buf: Buffer): Promise; - compress(buf: Buffer, callback: (error?: Error, buffer?: Buffer) => void): void; /** - * - Snappy 6.x takes a callback and returns void - * - Snappy 7.x returns a promise - * * In order to support both we must check the return value of the function * @param buf - Buffer to be compressed - * @param callback - ONLY USED IN SNAPPY 6.x */ uncompress(buf: Buffer, opt: { asBuffer: true }): Promise; - uncompress( - buf: Buffer, - opt: { asBuffer: true }, - callback: (error?: Error, buffer?: Buffer) => void - ): void; }; export let Snappy: SnappyLib | { kModuleError: MongoMissingDependencyError } = makeErrorModule( @@ -128,9 +110,6 @@ export let Snappy: SnappyLib | { kModuleError: MongoMissingDependencyError } = m try { // Ensure you always wrap an optional require in the try block NODE-3199 Snappy = require('snappy'); - try { - (Snappy as any)[PKG_VERSION] = parsePackageVersion(require('snappy/package.json')); - } catch {} // eslint-disable-line } catch {} // eslint-disable-line export let saslprep: typeof import('saslprep') | { kModuleError: MongoMissingDependencyError } = diff --git a/src/utils.ts b/src/utils.ts index 3fce528b4a..f078421420 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1305,15 +1305,6 @@ export function supportsRetryableWrites(server?: Server): boolean { return false; } -export function parsePackageVersion({ version }: { version: string }): { - major: number; - minor: number; - patch: number; -} { - const [major, minor, patch] = version.split('.').map((n: string) => Number.parseInt(n, 10)); - return { major, minor, patch }; -} - /** * Fisher–Yates Shuffle * diff --git a/test/action/dependency.test.ts b/test/action/dependency.test.ts index 932a224d22..812a629f3a 100644 --- a/test/action/dependency.test.ts +++ b/test/action/dependency.test.ts @@ -1,13 +1,81 @@ +import { execSync } from 'node:child_process'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; + import { expect } from 'chai'; -import { dependencies } from '../../package.json'; +import { dependencies, peerDependencies, peerDependenciesMeta } from '../../package.json'; const EXPECTED_DEPENDENCIES = ['bson', 'mongodb-connection-string-url', 'socks']; +const EXPECTED_PEER_DEPENDENCIES = ['snappy']; describe('package.json', function () { describe('dependencies', function () { it('only contains the expected dependencies', function () { - expect(Object.getOwnPropertyNames(dependencies)).to.deep.equal(EXPECTED_DEPENDENCIES); + expect(dependencies).to.have.keys(EXPECTED_DEPENDENCIES); + }); + }); + + describe('peerDependencies', function () { + it('only contains the expected peerDependencies', function () { + expect(peerDependencies).to.have.keys(EXPECTED_PEER_DEPENDENCIES); + }); + + it('has a meta field for each expected peer', function () { + expect(peerDependenciesMeta).to.have.keys(EXPECTED_PEER_DEPENDENCIES); }); }); + + describe('Optional Peer dependencies', () => { + after('reset npm dependencies', () => { + // This test file is not meant to be run alongside others, but in case + // we can attempt to reset the environment + fs.rmSync(path.join(repoRoot, 'node_modules'), { recursive: true, force: true }); + execSync(`npm clean-install`); + }); + + const repoRoot = path.resolve(__dirname, '../..'); + + const testScript = ` + const mdb = require('${repoRoot}/src/index.ts'); + console.log('import success!');` + .split('\n') + .join(''); + + for (const [depName, depVersion] of Object.entries(peerDependencies)) { + const depMajor = depVersion.split('.')[0]; + + context(`when ${depName} is NOT installed`, () => { + beforeEach(async () => { + fs.rmSync(path.join(repoRoot, 'node_modules', depName), { recursive: true, force: true }); + }); + + it(`driver is importable`, () => { + expect(fs.existsSync(path.join(repoRoot, 'node_modules', depName))).to.be.false; + + const result = execSync(`./node_modules/.bin/ts-node -e "${testScript}"`, { + encoding: 'utf8' + }); + + expect(result).to.include('import success!'); + }); + }); + + context(`when ${depName} is installed`, () => { + beforeEach(async () => { + execSync(`npm install --no-save "${depName}"@${depMajor}`); + }); + + it(`driver is importable`, () => { + expect(fs.existsSync(path.join(repoRoot, 'node_modules', depName))).to.be.true; + + const result = execSync(`./node_modules/.bin/ts-node -e "${testScript}"`, { + encoding: 'utf8' + }); + + expect(result).to.include('import success!'); + }); + }); + } + }); }); diff --git a/test/unit/assorted/snappy.test.js b/test/unit/assorted/snappy.test.js deleted file mode 100644 index 6bb77f04de..0000000000 --- a/test/unit/assorted/snappy.test.js +++ /dev/null @@ -1,65 +0,0 @@ -'use strict'; - -const { expect } = require('chai'); -const mock = require('../../tools/mongodb-mock/index'); - -const snappy = optionalRequire('snappy'); -const snappyVersion = optionalRequire('snappy/package.json').version; -const { MongoClient } = require('../../../src'); -const { isHello } = require('../../../src/utils'); - -function optionalRequire(mod) { - try { - return require(mod); - } catch { - return false; - } -} - -describe('Compression', function () { - let client; - /** @type {mock.MockServer} */ - let server; - before(async function () { - if (!snappy) return this.skip(); - server = await mock.createServer(); - client = new MongoClient(`mongodb://${server.uri()}`, { compressors: 'snappy' }); - }); - - afterEach(async function () { - if (server) await mock.cleanup(); - if (client) await client.close(); - }); - - describe('Snappy', () => { - it(`should compress messages sent with snappy ${snappyVersion}`, async function () { - // the timeout is being set because the test should not take any longer than 5 seconds, - // and that if it doesn't complete, it will hang due to the callback never being called - this.timeout(5000); - - server.setMessageHandler(request => { - const doc = request.document; - if (isHello(doc)) { - return request.reply({ ...mock.HELLO, compression: ['snappy'] }); - } - if (doc.insert === 'snappy') { - return request.reply({ ok: 1 }); - } - }); - // The mock server uses snappy to decode messages so - // if this passes we implicitly test snappy is working - // TODO(NODE-3560): Add more comprehensive round trip testing - await client.connect(); - await client.db().collection('snappy').insertOne({ a: 1 }); - }); - - it('should define a version number on the optional import', function () { - const { Snappy, PKG_VERSION } = require('../../../src/deps'); - const [major, minor, patch] = snappyVersion.split('.').map(n => +n); - expect(Snappy).to.have.property(PKG_VERSION).that.is.an('object'); - expect(Snappy[PKG_VERSION]).to.have.property('major', major); - expect(Snappy[PKG_VERSION]).to.have.property('minor', minor); - expect(Snappy[PKG_VERSION]).to.have.property('patch', patch); - }); - }); -}); diff --git a/test/unit/cmap/message_stream.test.js b/test/unit/cmap/message_stream.test.ts similarity index 87% rename from test/unit/cmap/message_stream.test.js rename to test/unit/cmap/message_stream.test.ts index 69ffc08496..74d4055651 100644 --- a/test/unit/cmap/message_stream.test.js +++ b/test/unit/cmap/message_stream.test.ts @@ -1,12 +1,11 @@ -'use strict'; -const { on, once } = require('events'); -const { Readable, Writable } = require('stream'); +import { expect } from 'chai'; +import { on, once } from 'events'; +import { Readable, Writable } from 'stream'; -const { MessageStream } = require('../../../src/cmap/message_stream'); -const { Msg } = require('../../../src/cmap/commands'); -const expect = require('chai').expect; -const { LEGACY_HELLO_COMMAND } = require('../../../src/constants'); -const { bufferToStream, generateOpMsgBuffer } = require('../../tools/utils'); +import { Msg } from '../../../src/cmap/commands'; +import { MessageStream } from '../../../src/cmap/message_stream'; +import { LEGACY_HELLO_COMMAND } from '../../../src/constants'; +import { bufferToStream, generateOpMsgBuffer } from '../../tools/utils'; describe('MessageStream', function () { context('when the stream is for a monitoring connection', function () { @@ -119,7 +118,11 @@ describe('MessageStream', function () { context('when writing to the message stream', function () { it('pushes the message', function (done) { - const readableStream = new Readable({ read() {} }); + const readableStream = new Readable({ + read() { + // ignore + } + }); const writeableStream = new Writable({ write: (chunk, _, callback) => { readableStream.push(chunk); @@ -139,8 +142,15 @@ describe('MessageStream', function () { messageStream.pipe(writeableStream); const command = new Msg('admin.$cmd', { [LEGACY_HELLO_COMMAND]: 1 }, { requestId: 3 }); - messageStream.writeCommand(command, null, err => { - done(err); + messageStream.writeCommand(command, { + started: 0, + command: true, + noResponse: false, + raw: false, + requestId: command.requestId, + cb: err => { + done(err); + } }); }); }); diff --git a/test/unit/cmap/wire_protocol/compression.test.ts b/test/unit/cmap/wire_protocol/compression.test.ts index b5f82dafed..3efa8a81d1 100644 --- a/test/unit/cmap/wire_protocol/compression.test.ts +++ b/test/unit/cmap/wire_protocol/compression.test.ts @@ -5,51 +5,85 @@ import { compress, Compressor, decompress } from '../../../../src/cmap/wire_prot describe('compression', function () { describe('.compress()', function () { context('when the compression library is zstd', function () { - const buffer = Buffer.from('test'); + const buffer = Buffer.from('test', 'utf8'); context('when a level is not provided', function () { - const options = { options: { agreedCompressor: 'zstd' } }; - - it('compresses the data', function (done) { - compress(options, buffer, (error, data) => { - expect(error).to.not.exist; - const zstdMagicNumber = data.reverse().toString('hex').substring(16, 26); - // Zstd magic number first set of bytes is is 0xFD2FB528 - expect(zstdMagicNumber).to.equal('00fd2fb528'); - done(); - }); + const options = { agreedCompressor: 'zstd' as const, zlibCompressionLevel: 0 }; + + it('compresses the data', async function () { + const data = await compress(options, buffer); + const zstdMagicNumber = data.reverse().toString('hex').substring(16, 26); + // Zstd magic number first set of bytes is is 0xFD2FB528 + expect(zstdMagicNumber).to.equal('00fd2fb528'); }); }); context('when a level is provided', function () { - const options = { options: { agreedCompressor: 'zstd', zstdCompressionLevel: 2 } }; - - it('compresses the data', function (done) { - compress(options, buffer, (error, data) => { - expect(error).to.not.exist; - const zstdMagicNumber = data.reverse().toString('hex').substring(16, 26); - // Zstd magic number first set of bytes is is 0xFD2FB528 - expect(zstdMagicNumber).to.equal('00fd2fb528'); - done(); - }); + const options = { agreedCompressor: 'zstd' as const, zlibCompressionLevel: 2 }; + + it('compresses the data', async function () { + const data = await compress(options, buffer); + const zstdMagicNumber = data.reverse().toString('hex').substring(16, 26); + // Zstd magic number first set of bytes is is 0xFD2FB528 + expect(zstdMagicNumber).to.equal('00fd2fb528'); }); }); }); + + context('when the agreed compressor is zlib', () => { + const options = { agreedCompressor: 'zlib' as const, zlibCompressionLevel: 2 }; + const input = Buffer.from('test', 'utf8'); + + it('compresses input with zlib', async () => { + const data = await compress(options, input); + // https://www.rfc-editor.org/rfc/rfc1950 (always leads with 0x78) + expect(data.toString('hex', 0, 1)).to.equal('78'); + }); + }); + + context('when the agreed compressor is snappy', () => { + const options = { agreedCompressor: 'snappy' as const, zlibCompressionLevel: 2 }; + const input = Buffer.from('test', 'utf8'); + + it('compresses input with snappy', async () => { + // https://github.com/google/snappy/blob/main/format_description.txt#L18 + // Snappy starts with the length of the uncompressed data in bytes + const data = await compress(options, input); + expect(data.toString('hex', 0, 1)).to.equal('04'); + }); + }); }); describe('.decompress()', function () { context('when the compression library is zstd', function () { - const buffer = Buffer.from('test'); - const options = { options: { agreedCompressor: 'zstd' } }; - - it('decompresses the data', function (done) { - compress(options, buffer, (error, data) => { - expect(error).to.not.exist; - decompress(Compressor.zstd, data, (err, decompressed) => { - expect(decompressed).to.deep.equal(buffer); - done(); - }); - }); + const buffer = Buffer.from('test', 'utf8'); + const options = { agreedCompressor: 'zstd' as const, zlibCompressionLevel: 0 }; + + it('decompresses the data', async function () { + const data = await compress(options, buffer); + const decompressed = await decompress(Compressor.zstd, data); + expect(decompressed).to.deep.equal(buffer); + }); + }); + + context('when the input has a compressorID corresponding to zlib', () => { + // zlib compressed string "test" + const input = Buffer.from('785e2b492d2e0100045d01c1', 'hex'); + + it('decompresses input with zlib', async () => { + const data = await decompress(Compressor.zlib, input); + expect(data.toString('utf8')).to.equal('test'); + }); + }); + + context('when the agreed compressor is snappy', () => { + // https://github.com/google/snappy/blob/main/format_description.txt#L18 + // 0x04 is the size, 0x0c are flags + const input = Buffer.from('040c' + Buffer.from('test', 'utf8').toString('hex'), 'hex'); + + it('decompresses input with snappy', async () => { + const data = await decompress(Compressor.snappy, input); + expect(data.toString('utf8')).to.equal('test'); }); }); });