diff --git a/.github/workflows/build-matrix.json b/.github/workflows/build-matrix.json new file mode 100644 index 0000000000..2651dc24f5 --- /dev/null +++ b/.github/workflows/build-matrix.json @@ -0,0 +1,55 @@ +[ + { + "OS": "ubuntu", + "NAMED_OS": "linux", + "RUNNER": "ubuntu-latest", + "ARCH": "x64", + "TARGET": "x86_64-unknown-linux-gnu", + "PACKAGE_MANAGERS": ["pypi", "npm"] + }, + { + "OS": "ubuntu", + "NAMED_OS": "linux", + "RUNNER": ["self-hosted", "Linux", "ARM64"], + "ARCH": "arm64", + "TARGET": "aarch64-unknown-linux-gnu", + "PACKAGE_MANAGERS": ["pypi", "npm"], + "CONTAINER": "2_28" + }, + { + "OS": "macos", + "NAMED_OS": "darwin", + "RUNNER": "macos-12", + "ARCH": "x64", + "TARGET": "x86_64-apple-darwin", + "PACKAGE_MANAGERS": ["pypi", "npm"] + }, + { + "OS": "macos", + "NAMED_OS": "darwin", + "RUNNER": "macos-13-xlarge", + "ARCH": "arm64", + "TARGET": "aarch64-apple-darwin", + "PACKAGE_MANAGERS": ["pypi", "npm"] + }, + { + "OS": "ubuntu", + "NAMED_OS": "linux", + "ARCH": "arm64", + "TARGET": "aarch64-unknown-linux-musl", + "RUNNER": ["self-hosted", "Linux", "ARM64"], + "IMAGE": "node:alpine", + "CONTAINER_OPTIONS": "--user root --privileged --rm", + "PACKAGE_MANAGERS": ["npm"] + }, + { + "OS": "ubuntu", + "NAMED_OS": "linux", + "ARCH": "x64", + "TARGET": "x86_64-unknown-linux-musl", + "RUNNER": "ubuntu-latest", + "IMAGE": "node:alpine", + "CONTAINER_OPTIONS": "--user root --privileged", + "PACKAGE_MANAGERS": ["npm"] + } +] diff --git a/.github/workflows/node-create-package-file/action.yml b/.github/workflows/node-create-package-file/action.yml index f6d4afc9a7..a02a093b15 100644 --- a/.github/workflows/node-create-package-file/action.yml +++ b/.github/workflows/node-create-package-file/action.yml @@ -69,11 +69,19 @@ runs: # set the registry scope export registry_scope=`if [ "${{ inputs.npm_scope }}" != '' ]; then echo "${{ inputs.npm_scope }}:"; fi` # remove the current name section - SED_FOR_MACOS=`if [[ "${{ inputs.os }}" =~ .*"macos".* ]]; then echo "''"; fi` - sed -i $SED_FOR_MACOS '/"name":/d' ./package.json + if [[ "${{ inputs.os }}" =~ .*"macos".* ]]; then + sed '/"name":/d' ./package.json > temp.json && mv temp.json package.json + else + sed -i '/"name":/d' ./package.json + fi # Remove all `///` occurrences to enable the commented out sections - sed -i -e 's|///||g' package.json + if [[ "${{ inputs.os }}" =~ .*"macos".* ]]; then + sed 's|///||g' package.json > temp.json && mv temp.json package.json + else + sed -i 's|///||g' package.json + fi # generate package.json from the template mv package.json package.json.tmpl envsubst < package.json.tmpl > "package.json" cat package.json + echo $(ls *json*) diff --git a/.github/workflows/npm-cd.yml b/.github/workflows/npm-cd.yml index 72947e2f44..ae576628bc 100644 --- a/.github/workflows/npm-cd.yml +++ b/.github/workflows/npm-cd.yml @@ -2,14 +2,6 @@ name: Continuous Deployment -env: - MATRIX: '[{"OS": "ubuntu","NAMED_OS": "linux","RUNNER": "ubuntu-latest","ARCH": "x64","TARGET": "x86_64-unknown-linux-gnu"}, - {"OS": "ubuntu","NAMED_OS": "linux","RUNNER": ["self-hosted", "Linux", "ARM64"],"ARCH": "arm64","TARGET": "aarch64-unknown-linux-gnu"}, - {"OS": "macos","NAMED_OS": "darwin","RUNNER": "macos-12","ARCH": "x64","TARGET": "x86_64-apple-darwin"}, - {"OS": "macos","NAMED_OS": "darwin","RUNNER": "macos-13-xlarge","ARCH": "arm64","TARGET": "aarch64-apple-darwin"}, - {"OS": "ubuntu","NAMED_OS": "linux","ARCH": "arm64","TARGET": "aarch64-unknown-linux-musl","RUNNER": ["self-hosted", "Linux", "ARM64"],"IMAGE": "node:alpine","CONTAINER_OPTIONS": "--user root --privileged --rm"}, - {"OS": "ubuntu","NAMED_OS": "linux","ARCH": "x64","TARGET": "x86_64-unknown-linux-musl","RUNNER": "ubuntu-latest","IMAGE": "node:alpine","CONTAINER_OPTIONS": "--user root --privileged"}]' - on: pull_request: paths: @@ -29,16 +21,6 @@ permissions: id-token: write jobs: - output-matrix: - runs-on: ubuntu-latest - outputs: - matrix: ${{ steps.set-matrix.outputs.matrix }} - steps: - - name: Set matrix - id: set-matrix - run: | - echo "::set-output name=matrix::${{toJson( env.MATRIX )}}" - start-self-hosted-runner: if: github.repository_owner == 'aws' runs-on: ubuntu-latest @@ -56,8 +38,24 @@ jobs: aws-region: ${{ secrets.AWS_REGION }} ec2-instance-id: ${{ secrets.AWS_EC2_INSTANCE_ID }} + load-platform-matrix: + runs-on: ubuntu-latest + outputs: + PLATFORM_MATRIX: ${{ steps.load-platform-matrix.outputs.PLATFORM_MATRIX }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: load-platform-matrix + id: load-platform-matrix + shell: bash + run: | + # Get the matrix from the matrix.json file, without the object that has the IMAGE key + export "PLATFORM_MATRIX=$(jq 'map(select(.PACKAGE_MANAGERS | contains(["npm"])))' < .github/workflows/build-matrix.json | jq -c .)" + echo "PLATFORM_MATRIX=${PLATFORM_MATRIX}" >> $GITHUB_OUTPUT + publish-binaries: - needs: [start-self-hosted-runner, output-matrix] + needs: [start-self-hosted-runner, load-platform-matrix] if: github.repository_owner == 'aws' name: Publish packages to NPM runs-on: ${{ matrix.build.RUNNER }} @@ -67,9 +65,7 @@ jobs: strategy: fail-fast: false matrix: - build: - ${{fromJson(needs.output-matrix.outputs.matrix)}} - + build: ${{fromJson(needs.load-platform-matrix.outputs.PLATFORM_MATRIX)}} steps: - name: Setup self-hosted runner access if: ${{ contains(matrix.build.RUNNER, 'self-hosted') && matrix.build.TARGET != 'aarch64-unknown-linux-musl' }} @@ -108,7 +104,7 @@ jobs: if: ${{ matrix.build.TARGET != 'aarch64-unknown-linux-musl' }} uses: actions/setup-node@v3 with: - node-version: "16" + node-version: "20" registry-url: "https://registry.npmjs.org" architecture: ${{ matrix.build.ARCH }} scope: "${{ vars.NPM_SCOPE }}" @@ -139,15 +135,38 @@ jobs: npm_scope: ${{ vars.NPM_SCOPE }} publish: "true" github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Check if RC and set a distribution tag for the package + shell: bash + run: | + if [[ "${GITHUB_REF:11}" == *"rc"* ]] + then + echo "This is a release candidate: ${GITHUB_REF:11}" + export npm_tag="next" + else + echo "This is a stable release: ${GITHUB_REF:11}" + export npm_tag="latest" + fi + echo "NPM_TAG=${npm_tag}" >> $GITHUB_ENV + + - name: Check that the release version dont have typo init + if: ${{ github.event_name != 'pull_request' && contains(github.ref, '-') && !contains(github.ref, 'rc') }} + run: | + echo "The release version "${GITHUB_REF:11}" contains a typo, please fix it" + echo "The release version should be in the format v{major-version}.{minor-version}.{patch-version}-rc{release-candidate-number} when it a release candidate or v{major-version}.{minor-version}.{patch-version} in a stable release." + exit 1 - name: Publish to NPM if: github.event_name != 'pull_request' shell: bash working-directory: ./node run: | + npm pkg fix set +e - # Redirect only stderr - { npm_publish_err=$(npm publish --access public 2>&1 >&3 3>&-); } 3>&1 + # 2>&1 1>&3- redirects stderr to stdout and then redirects the original stdout to another file descriptor, + # effectively separating stderr and stdout. The 3>&1 at the end redirects the original stdout back to the console. + # https://github.com/npm/npm/issues/118#issuecomment-325440 - ignoring notice messages since currentlly they are directed to stderr + { npm_publish_err=$(npm publish --tag ${{ env.NPM_TAG }} --access public 2>&1 1>&3- | grep -v "notice") ;} 3>&1 if [[ "$npm_publish_err" == *"You cannot publish over the previously published versions"* ]] then echo "Skipping publishing, package already published" @@ -159,27 +178,6 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} - - name: Pack the Node package - shell: bash - working-directory: ./node - run: | - # Remove the "cpu" and "os" fileds so the base package would be able to install it on ubuntu - SED_FOR_MACOS=`if [[ "${{ matrix.build.OS }}" =~ .*"macos".* ]]; then echo "''"; fi` - sed -i $SED_FOR_MACOS '/"\/\/\/cpu": \[/,/]/d' ./package.json && sed -i $SED_FOR_MACOS '/"\/\/\/os": \[/,/]/d' ./package.json - mkdir -p bin - npm pack --pack-destination ./bin - ls ./bin - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} - - - name: Upload the Node package - if: github.event_name != 'pull_request' - uses: actions/upload-artifact@v3 - with: - name: ${{ matrix.build.TARGET }} - path: ./node/bin - if-no-files-found: error - # Reset the repository to make sure we get the clean checkout of the action later in other actions. # It is not required since in other actions we are cleaning before the action, but it is a good practice to do it here as well. - name: Reset repository @@ -196,14 +194,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "true" - name: Install node uses: actions/setup-node@v3 with: - node-version: "16" + node-version: "20" registry-url: "https://registry.npmjs.org" scope: "${{ vars.NPM_SCOPE }}" always-auth: true @@ -230,33 +228,7 @@ jobs: os: ubuntu target: "x86_64-unknown-linux-gnu" github-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Create a directory for the packed packages - shell: bash - working-directory: ./node/npm/glide - run: mkdir packages - - - name: Download the packed packages - id: download - uses: actions/download-artifact@v3 - with: - path: ./node/npm/glide/packages - - - name: Install the packed packages - shell: bash - working-directory: ./node/npm/glide - run: | - ls -LR packages/ - packages_list=`find ${{steps.download.outputs.download-path}} -type f -follow -print` - for package in $packages_list - do - if [[ "${package}" == *.tgz ]] - then - echo "Installing package $package" - npm i --no-save "$package" - fi - done - + - name: Check if RC and set a distribution tag for the package shell: bash run: | @@ -283,10 +255,10 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} - test-release-candidate: - if: github.event_name != 'pull_request' && contains(github.ref, 'rc') - name: Test the release candidate - needs: [publish-base-to-npm, output-matrix] + test-release: + if: github.event_name != 'pull_request' + name: Test the release + needs: [publish-base-to-npm, load-platform-matrix] runs-on: ${{ matrix.build.RUNNER }} container: image: ${{ matrix.build.IMAGE || '' }} @@ -294,8 +266,7 @@ jobs: strategy: fail-fast: false matrix: - build: - ${{fromJson(needs.output-matrix.outputs.matrix)}} + build: ${{fromJson(needs.load-platform-matrix.outputs.PLATFORM_MATRIX)}} steps: - name: Setup self-hosted runner access if: ${{ matrix.build.TARGET == 'aarch64-unknown-linux-gnu' }} @@ -306,18 +277,25 @@ jobs: run: | apk update apk add redis git + node -v - name: install Redis and Python for ubuntu if: ${{ contains(matrix.build.TARGET, 'linux-gnu') }} run: | sudo apt-get update - sudo apt-get install redis-server python3 + sudo apt-get install redis-server python3 - - name: install Redis and Python for macos + - name: install Redis, Python for macos if: ${{ contains(matrix.build.RUNNER, 'mac') }} run: | brew install redis python3 + - name: Checkout + if: ${{ matrix.build.TARGET != 'aarch64-unknown-linux-musl'}} + uses: actions/checkout@v4 + with: + submodules: "true" + - name: Setup for musl if: ${{ contains(matrix.build.TARGET, 'musl') }} uses: ./.github/workflows/setup-musl-on-linux @@ -327,26 +305,44 @@ jobs: npm-auth-token: ${{ secrets.NPM_AUTH_TOKEN }} arch: ${{ matrix.build.ARCH }} - - name: Checkout - if: ${{ matrix.build.TARGET != 'aarch64-unknown-linux-musl'}} - uses: actions/checkout@v4 - with: - submodules: "true" - - - name: Install node - if: ${{ !contains(matrix.build.TARGET, 'musl') }} + - name: Setup node + if: ${{ matrix.build.TARGET != 'aarch64-unknown-linux-musl' }} uses: actions/setup-node@v3 with: node-version: "16" registry-url: "https://registry.npmjs.org" + architecture: ${{ matrix.build.ARCH }} scope: "${{ vars.NPM_SCOPE }}" always-auth: true + token: ${{ secrets.NPM_AUTH_TOKEN }} + + - name: Install tsc and compile utils + shell: bash + working-directory: ./utils + run: | + npm install + npm install -g typescript + npx tsc -p ./tsconfig.json + + - name: Check if RC and set a distribution tag for the package + shell: bash + run: | + if [[ "${GITHUB_REF:11}" == *"rc"* ]] + then + echo "This is a release candidate" + export npm_tag="next" + else + echo "This is a stable release" + export npm_tag="latest" + fi + echo "NPM_TAG=${npm_tag}" >> $GITHUB_ENV - name: Run the tests shell: bash working-directory: ./utils/release-candidate-testing/node run: | - npm install --no-save @aws/glide-for-redis@next + npm install + npm install --no-save @aws/glide-for-redis@${{ env.NPM_TAG }} npm run test # Reset the repository to make sure we get the clean checkout of the action later in other actions. diff --git a/.github/workflows/pypi-cd.yml b/.github/workflows/pypi-cd.yml index 1948cbad3f..0889a7bcae 100644 --- a/.github/workflows/pypi-cd.yml +++ b/.github/workflows/pypi-cd.yml @@ -20,6 +20,23 @@ permissions: id-token: write jobs: + load-platform-matrix: + runs-on: ubuntu-latest + outputs: + PLATFORM_MATRIX: ${{ steps.load-platform-matrix.outputs.PLATFORM_MATRIX }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: load-platform-matrix + id: load-platform-matrix + shell: bash + run: | + # Get the matrix from the matrix.json file, without the object that has the IMAGE key + export "PLATFORM_MATRIX=$(jq 'map(select(.PACKAGE_MANAGERS | contains(["pypi"])))' < .github/workflows/build-matrix.json | jq -c .)" + echo "PLATFORM_MATRIX=${PLATFORM_MATRIX}" >> $GITHUB_OUTPUT + + start-self-hosted-runner: if: github.repository_owner == 'aws' runs-on: ubuntu-latest @@ -35,7 +52,7 @@ jobs: ec2-instance-id: ${{ secrets.AWS_EC2_INSTANCE_ID }} publish-binaries: - needs: start-self-hosted-runner + needs: [start-self-hosted-runner, load-platform-matrix] if: github.repository_owner == 'aws' name: Publish packages to PyPi runs-on: ${{ matrix.build.RUNNER }} @@ -44,35 +61,7 @@ jobs: fail-fast: false matrix: build: - - { - OS: ubuntu, - NAMED_OS: linux, - RUNNER: ubuntu-latest, - ARCH: x64, - TARGET: x86_64-unknown-linux-gnu, - } - - { - OS: ubuntu, - NAMED_OS: linux, - RUNNER: [self-hosted, Linux, ARM64], - ARCH: arm64, - TARGET: aarch64-unknown-linux-gnu, - CONTAINER: "2_28", - } - - { - OS: macos, - NAMED_OS: darwin, - RUNNER: macos-12, - ARCH: x64, - TARGET: x86_64-apple-darwin, - } - - { - OS: macos, - NAMED_OS: darwin, - RUNNER: macos-13-xlarge, - arch: arm64, - TARGET: aarch64-apple-darwin, - } + ${{fromJson( needs.load-platform-matrix.outputs.PLATFORM_MATRIX )}} steps: - name: Setup self-hosted runner access if: ${{ contains(matrix.build.RUNNER, 'self-hosted') }} @@ -88,6 +77,23 @@ jobs: run: | export version=`if ${{ github.event_name == 'pull_request' }}; then echo '255.255.255'; else echo ${GITHUB_REF:11}; fi` echo "RELEASE_VERSION=${version}" >> $GITHUB_ENV + + # For NPM and Cargo we need the the rc version to be {version}-rc{number} while for PyPi we need it to be {version}rc{number} + # This is because PyPi does not allow the '-' character in the version name + # So the tag will be as NPM and Cargo ask: v1.0.0-rc1 and for PyPi we'll chnage it to: v1.0.0rc1 + - name: Change and set realease version for RC + if: ${{ github.event_name != 'pull_request' && contains(github.ref, 'rc') }} + shell: bash + run: | + export py_version=`echo ${GITHUB_REF:11} | sed 's/-rc/rc/'` + echo "PY_RELEASE_VERSION=${py_version}" >> $GITHUB_ENV + + - name: Check that the release version dont have typo init + if: ${{ github.event_name != 'pull_request' && contains(github.ref, '-') && !contains(github.ref, 'rc') }} + run: | + echo "The release version "${GITHUB_REF:11}" contains a typo, please fix it" + echo "The release version should be in the format v{major-version}.{minor-version}.{patch-version}-rc{release-candidate-number} when it a release candidate or v{major-version}.{minor-version}.{patch-version} in a stable release." + exit 1 - name: Set the package version for Python working-directory: ./python diff --git a/CHANGELOG.md b/CHANGELOG.md index 8322ff2c7c..4f19d75f49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ * Python: Added OBJECT ENCODING command ([#1471](https://github.com/aws/glide-for-redis/pull/1471)) * Python: Added OBJECT FREQ command ([#1472](https://github.com/aws/glide-for-redis/pull/1472)) +## 0.4.1 (2024-02-06) + +#### Fixes +* Node: Fix set command bug with expiry option ([#1508](https://github.com/aws/glide-for-redis/pull/1508)) + ## 0.4.0 (2024-05-26) #### Changes diff --git a/node/.gitignore b/node/.gitignore index 1b023a1ae7..1cae12ee73 100644 --- a/node/.gitignore +++ b/node/.gitignore @@ -138,3 +138,5 @@ yarn.lock src/ProtobufMessage* .npmignore + +package.json.tmpl diff --git a/node/README.md b/node/README.md index a6bba1e409..68c8331411 100644 --- a/node/README.md +++ b/node/README.md @@ -91,3 +91,12 @@ Visit our [wiki](https://github.com/aws/glide-for-redis/wiki/NodeJS-wrapper) for ### Building & Testing Development instructions for local building & testing the package are in the [DEVELOPER.md](https://github.com/aws/glide-for-redis/blob/main/node/DEVELOPER.md#build-from-source) file. + +### Supported platforms + +Currentlly the package is supported on: + +| Operation systems | C lib | Architecture | +| ----------------- | -------------------- | ----------------- | +| `Linux` | `glibc`, `musl libc` | `x86_64`, `arm64` | +| `macOS` | `Darwin` | `x86_64`, `arm64` | diff --git a/node/npm/glide/.gitignore b/node/npm/glide/.gitignore index 646712d437..36bedddd60 100644 --- a/node/npm/glide/.gitignore +++ b/node/npm/glide/.gitignore @@ -131,3 +131,5 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* + +package.json.tmpl diff --git a/node/src/Commands.ts b/node/src/Commands.ts index 65fec321f8..7a9efe013d 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -145,13 +145,13 @@ export function createSet( if (options.expiry === "keepExisting") { args.push("KEEPTTL"); } else if (options.expiry?.type === "seconds") { - args.push("EX " + options.expiry.count); + args.push("EX", options.expiry.count.toString()); } else if (options.expiry?.type === "milliseconds") { - args.push("PX " + options.expiry.count); + args.push("PX", options.expiry.count.toString()); } else if (options.expiry?.type === "unixSeconds") { - args.push("EXAT " + options.expiry.count); + args.push("EXAT", options.expiry.count.toString()); } else if (options.expiry?.type === "unixMilliseconds") { - args.push("PXAT " + options.expiry.count); + args.push("PXAT", options.expiry.count.toString()); } } diff --git a/node/tests/RedisClientInternals.test.ts b/node/tests/RedisClientInternals.test.ts index 768faa67bc..978e2e951f 100644 --- a/node/tests/RedisClientInternals.test.ts +++ b/node/tests/RedisClientInternals.test.ts @@ -524,12 +524,13 @@ describe("SocketConnectionInternals", () => { throw new Error("no args"); } - expect(args.length).toEqual(5); + expect(args.length).toEqual(6); expect(args[0]).toEqual("foo"); expect(args[1]).toEqual("bar"); expect(args[2]).toEqual("XX"); expect(args[3]).toEqual("GET"); - expect(args[4]).toEqual("EX 10"); + expect(args[4]).toEqual("EX"); + expect(args[5]).toEqual("10"); sendResponse(socket, ResponseType.OK, request.callbackIdx); }); const request1 = connection.set("foo", "bar", { @@ -538,7 +539,7 @@ describe("SocketConnectionInternals", () => { expiry: { type: "seconds", count: 10 }, }); - await expect(await request1).toMatch("OK"); + expect(await request1).toMatch("OK"); }); }); diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index f706370bda..103ab076e4 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -165,63 +165,6 @@ export function runBaseTests(config: { config.timeout, ); - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `set with return of old value works_%p`, - async (protocol) => { - await runTest(async (client: BaseClient) => { - const key = uuidv4(); - // Adding random repetition, to prevent the inputs from always having the same alignment. - const value = uuidv4() + "0".repeat(Math.random() * 7); - - let result = await client.set(key, value); - expect(result).toEqual("OK"); - - result = await client.set(key, "", { - returnOldValue: true, - }); - expect(result).toEqual(value); - - result = await client.get(key); - expect(result).toEqual(""); - }, protocol); - }, - config.timeout, - ); - - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `conditional set works_%p`, - async (protocol) => { - await runTest(async (client: BaseClient) => { - const key = uuidv4(); - // Adding random repetition, to prevent the inputs from always having the same alignment. - const value = uuidv4() + "0".repeat(Math.random() * 7); - let result = await client.set(key, value, { - conditionalSet: "onlyIfExists", - }); - expect(result).toEqual(null); - - result = await client.set(key, value, { - conditionalSet: "onlyIfDoesNotExist", - }); - expect(result).toEqual("OK"); - expect(await client.get(key)).toEqual(value); - - result = await client.set(key, "foobar", { - conditionalSet: "onlyIfDoesNotExist", - }); - expect(result).toEqual(null); - - result = await client.set(key, "foobar", { - conditionalSet: "onlyIfExists", - }); - expect(result).toEqual("OK"); - - expect(await client.get(key)).toEqual("foobar"); - }, protocol); - }, - config.timeout, - ); - it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( `custom command works_%p`, async (protocol) => { @@ -2384,6 +2327,218 @@ export function runBaseTests(config: { }, config.timeout, ); + + // Set command tests + + async function setWithExpiryOptions(client: BaseClient) { + const key = uuidv4(); + const value = uuidv4(); + const setResWithExpirySetMilli = await client.set(key, value, { + expiry: { + type: "milliseconds", + count: 500, + }, + }); + expect(setResWithExpirySetMilli).toEqual("OK"); + const getWithExpirySetMilli = await client.get(key); + expect(getWithExpirySetMilli).toEqual(value); + + const setResWithExpirySec = await client.set(key, value, { + expiry: { + type: "seconds", + count: 1, + }, + }); + expect(setResWithExpirySec).toEqual("OK"); + const getResWithExpirySec = await client.get(key); + expect(getResWithExpirySec).toEqual(value); + + const setWithUnixSec = await client.set(key, value, { + expiry: { + type: "unixSeconds", + count: 1, + }, + }); + expect(setWithUnixSec).toEqual("OK"); + const getWithUnixSec = await client.get(key); + expect(getWithUnixSec).toEqual(value); + + const setResWithExpiryKeep = await client.set(key, value, { + expiry: "keepExisting", + }); + expect(setResWithExpiryKeep).toEqual("OK"); + const getResWithExpiryKeep = await client.get(key); + expect(getResWithExpiryKeep).toEqual(value); + // wait for the key to expire base on the previous set + setTimeout(() => {}, 1000); + const getResExpire = await client.get(key); + // key should have expired + expect(getResExpire).toEqual(null); + + const setResWithExpiryWithUmilli = await client.set(key, value, { + expiry: { + type: "unixMilliseconds", + count: 2, + }, + }); + expect(setResWithExpiryWithUmilli).toEqual("OK"); + // wait for the key to expire + setTimeout(() => {}, 3); + const getResWithExpiryWithUmilli = await client.get(key); + // key should have expired + expect(getResWithExpiryWithUmilli).toEqual(null); + } + + async function setWithOnlyIfExistOptions(client: BaseClient) { + const key = uuidv4(); + const value = uuidv4(); + const setExistingKeyRes = await client.set(key, value, { + conditionalSet: "onlyIfExists", + }); + expect(setExistingKeyRes).toEqual("OK"); + const getExistingKeyRes = await client.get(key); + expect(getExistingKeyRes).toEqual(value); + + const notExistingKeyRes = await client.set(key, value + "1", { + conditionalSet: "onlyIfExists", + }); + // key does not exist, so it should not be set + expect(notExistingKeyRes).toEqual(null); + const getNotExistingKey = await client.get(key); + // key should not have been set + expect(getNotExistingKey).toEqual(null); + } + + async function setWithOnlyIfNotExistOptions(client: BaseClient) { + const key = uuidv4(); + const value = uuidv4(); + const notExistingKeyRes = await client.set(key, value, { + conditionalSet: "onlyIfDoesNotExist", + }); + // key does not exist, so it should be set + expect(notExistingKeyRes).toEqual("OK"); + const getNotExistingKey = await client.get(key); + // key should have been set + expect(getNotExistingKey).toEqual(value); + + const existingKeyRes = await client.set(key, value, { + conditionalSet: "onlyIfDoesNotExist", + }); + // key exists, so it should not be set + expect(existingKeyRes).toEqual(null); + const getExistingKey = await client.get(key); + // key should not have been set + expect(getExistingKey).toEqual(value); + } + + async function setWithGetOldOptions(client: BaseClient) { + const key = uuidv4(); + const value = uuidv4(); + + const setResGetNotExistOld = await client.set(key, value, { + returnOldValue: true, + }); + // key does not exist, so old value should be null + expect(setResGetNotExistOld).toEqual(null); + // key should have been set + const getResGetNotExistOld = await client.get(key); + expect(getResGetNotExistOld).toEqual(value); + + const setResGetExistOld = await client.set(key, value, { + returnOldValue: true, + }); + // key exists, so old value should be returned + expect(setResGetExistOld).toEqual(value); + // key should have been set + const getResGetExistOld = await client.get(key); + expect(getResGetExistOld).toEqual(value); + } + + async function setWithAllOptions(client: BaseClient) { + const key = uuidv4(); + const value = uuidv4(); + + const setResWithAllOptions = await client.set(key, value, { + expiry: { + type: "unixSeconds", + count: 1, + }, + conditionalSet: "onlyIfExists", + returnOldValue: true, + }); + // key does not exist, so old value should be null + expect(setResWithAllOptions).toEqual(null); + // key should have been set + const getResWithAllOptions = await client.get(key); + expect(getResWithAllOptions).toEqual(value); + + // wait for the key to expire + setTimeout(() => {}, 1000); + // key should have expired + const gettResWithAllOptions = await client.get(key); + expect(gettResWithAllOptions).toEqual(null); + } + + async function testSetWithAllCombination(client: BaseClient) { + const key = uuidv4(); + const value = uuidv4(); + const count = 1; + const expiryCombination = [ + "keepExisting", + { type: "seconds", count }, + { type: "milliseconds", count }, + { type: "unixSeconds", count }, + { type: "unixMilliseconds", count }, + undefined, + ]; + const conditionalSetCombination = [ + "onlyIfExists", + "onlyIfDoesNotExist", + undefined, + ]; + const returnOldValueCombination = [true, false, undefined]; + + for (const expiryVal of expiryCombination) { + for (const conditionalSetVal of conditionalSetCombination) { + for (const returnOldValueVal of returnOldValueCombination) { + const setRes = await client.set(key, value, { + expiry: expiryVal as + | "keepExisting" + | { + type: + | "seconds" + | "milliseconds" + | "unixSeconds" + | "unixMilliseconds"; + count: number; + } + | undefined, + conditionalSet: conditionalSetVal as + | "onlyIfExists" + | "onlyIfDoesNotExist" + | undefined, + returnOldValue: returnOldValueVal, + }); + expect(setRes).not.toBeNull(); + } + } + } + } + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "Set commands with options test_%p", + async (protocol) => { + await runTest(async (client: BaseClient) => { + await setWithExpiryOptions(client); + await setWithOnlyIfExistOptions(client); + await setWithOnlyIfNotExistOptions(client); + await setWithGetOldOptions(client); + await setWithAllOptions(client); + await testSetWithAllCombination(client); + }, protocol); + }, + config.timeout, + ); } export function runCommonTests(config: { diff --git a/utils/package.json b/utils/package.json index d3162b33a7..1d7c771a8a 100644 --- a/utils/package.json +++ b/utils/package.json @@ -16,6 +16,7 @@ "prettier": "^2.8.8" }, "dependencies": { - "child_process": "^1.0.2" + "child_process": "^1.0.2", + "typescript": "^5.4.5" } } diff --git a/utils/release-candidate-testing/node/package.json b/utils/release-candidate-testing/node/package.json index 188e0420e0..15cae7c6b6 100644 --- a/utils/release-candidate-testing/node/package.json +++ b/utils/release-candidate-testing/node/package.json @@ -5,7 +5,7 @@ "main": "index.ts", "type": "module", "scripts": { - "test": "npm run build-utils && node index.js", + "test": "node index.js", "build-utils": "cd ../../ && npm run build", "prettier:check:ci": "./node_modules/.bin/prettier --check .", "prettier:format": "./node_modules/.bin/prettier --write . "