From f6e22d2cc302b8b3bb9b8971d2a91fb1df9fbbe8 Mon Sep 17 00:00:00 2001 From: Imamuzzaki Abu Salam Date: Wed, 18 Oct 2023 13:15:54 +0700 Subject: [PATCH 1/4] fix(hgetall): handle unsafe integer in deserialization --- deno.lock | 34 ++++++++++++++++++++++++++++++++++ pkg/commands/hgetall.test.ts | 29 ++++++++++++++++++++++++++--- pkg/commands/hgetall.ts | 15 +++++++++++---- pkg/test-utils.ts | 5 +++++ 4 files changed, 76 insertions(+), 7 deletions(-) diff --git a/deno.lock b/deno.lock index 11130084..e4023815 100644 --- a/deno.lock +++ b/deno.lock @@ -1,6 +1,7 @@ { "version": "3", "redirects": { + "https://deno.land/std/testing/asserts.ts": "https://deno.land/std@0.204.0/testing/asserts.ts", "https://deno.land/x/base64/base64url.ts": "https://deno.land/x/base64@v0.2.1/base64url.ts" }, "remote": { @@ -43,6 +44,39 @@ "https://deno.land/std@0.201.0/assert/unreachable.ts": "4600dc0baf7d9c15a7f7d234f00c23bca8f3eba8b140286aaca7aa998cf9a536", "https://deno.land/std@0.201.0/fmt/colors.ts": "87544aa2bc91087bb37f9c077970c85bfb041b48e4c37356129d7b450a415b6f", "https://deno.land/std@0.201.0/testing/asserts.ts": "b4e4b1359393aeff09e853e27901a982c685cb630df30426ed75496961931946", + "https://deno.land/std@0.204.0/assert/_constants.ts": "8a9da298c26750b28b326b297316cdde860bc237533b07e1337c021379e6b2a9", + "https://deno.land/std@0.204.0/assert/_diff.ts": "58e1461cc61d8eb1eacbf2a010932bf6a05b79344b02ca38095f9b805795dc48", + "https://deno.land/std@0.204.0/assert/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7", + "https://deno.land/std@0.204.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", + "https://deno.land/std@0.204.0/assert/assert_almost_equals.ts": "e15ca1f34d0d5e0afae63b3f5d975cbd18335a132e42b0c747d282f62ad2cd6c", + "https://deno.land/std@0.204.0/assert/assert_array_includes.ts": "6856d7f2c3544bc6e62fb4646dfefa3d1df5ff14744d1bca19f0cbaf3b0d66c9", + "https://deno.land/std@0.204.0/assert/assert_equals.ts": "d8ec8a22447fbaf2fc9d7c3ed2e66790fdb74beae3e482855d75782218d68227", + "https://deno.land/std@0.204.0/assert/assert_exists.ts": "407cb6b9fb23a835cd8d5ad804e2e2edbbbf3870e322d53f79e1c7a512e2efd7", + "https://deno.land/std@0.204.0/assert/assert_false.ts": "0ccbcaae910f52c857192ff16ea08bda40fdc79de80846c206bfc061e8c851c6", + "https://deno.land/std@0.204.0/assert/assert_greater.ts": "ae2158a2d19313bf675bf7251d31c6dc52973edb12ac64ac8fc7064152af3e63", + "https://deno.land/std@0.204.0/assert/assert_greater_or_equal.ts": "1439da5ebbe20855446cac50097ac78b9742abe8e9a43e7de1ce1426d556e89c", + "https://deno.land/std@0.204.0/assert/assert_instance_of.ts": "3aedb3d8186e120812d2b3a5dea66a6e42bf8c57a8bd927645770bd21eea554c", + "https://deno.land/std@0.204.0/assert/assert_is_error.ts": "c21113094a51a296ffaf036767d616a78a2ae5f9f7bbd464cd0197476498b94b", + "https://deno.land/std@0.204.0/assert/assert_less.ts": "aec695db57db42ec3e2b62e97e1e93db0063f5a6ec133326cc290ff4b71b47e4", + "https://deno.land/std@0.204.0/assert/assert_less_or_equal.ts": "5fa8b6a3ffa20fd0a05032fe7257bf985d207b85685fdbcd23651b70f928c848", + "https://deno.land/std@0.204.0/assert/assert_match.ts": "c4083f80600bc190309903c95e397a7c9257ff8b5ae5c7ef91e834704e672e9b", + "https://deno.land/std@0.204.0/assert/assert_not_equals.ts": "9f1acab95bd1f5fc9a1b17b8027d894509a745d91bac1718fdab51dc76831754", + "https://deno.land/std@0.204.0/assert/assert_not_instance_of.ts": "0c14d3dfd9ab7a5276ed8ed0b18c703d79a3d106102077ec437bfe7ed912bd22", + "https://deno.land/std@0.204.0/assert/assert_not_match.ts": "3796a5b0c57a1ce6c1c57883dd4286be13a26f715ea662318ab43a8491a13ab0", + "https://deno.land/std@0.204.0/assert/assert_not_strict_equals.ts": "ca6c6d645e95fbc873d25320efeb8c4c6089a9a5e09f92d7c1c4b6e935c2a6ad", + "https://deno.land/std@0.204.0/assert/assert_object_match.ts": "d8fc2867cfd92eeacf9cea621e10336b666de1874a6767b5ec48988838370b54", + "https://deno.land/std@0.204.0/assert/assert_rejects.ts": "45c59724de2701e3b1f67c391d6c71c392363635aad3f68a1b3408f9efca0057", + "https://deno.land/std@0.204.0/assert/assert_strict_equals.ts": "b1f538a7ea5f8348aeca261d4f9ca603127c665e0f2bbfeb91fa272787c87265", + "https://deno.land/std@0.204.0/assert/assert_string_includes.ts": "b821d39ebf5cb0200a348863c86d8c4c4b398e02012ce74ad15666fc4b631b0c", + "https://deno.land/std@0.204.0/assert/assert_throws.ts": "63784e951475cb7bdfd59878cd25a0931e18f6dc32a6077c454b2cd94f4f4bcd", + "https://deno.land/std@0.204.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", + "https://deno.land/std@0.204.0/assert/equal.ts": "9f1a46d5993966d2596c44e5858eec821859b45f783a5ee2f7a695dfc12d8ece", + "https://deno.land/std@0.204.0/assert/fail.ts": "c36353d7ae6e1f7933d45f8ea51e358c8c4b67d7e7502028598fe1fea062e278", + "https://deno.land/std@0.204.0/assert/mod.ts": "37c49a26aae2b254bbe25723434dc28cd7532e444cf0b481a97c045d110ec085", + "https://deno.land/std@0.204.0/assert/unimplemented.ts": "d56fbeecb1f108331a380f72e3e010a1f161baa6956fd0f7cf3e095ae1a4c75a", + "https://deno.land/std@0.204.0/assert/unreachable.ts": "4600dc0baf7d9c15a7f7d234f00c23bca8f3eba8b140286aaca7aa998cf9a536", + "https://deno.land/std@0.204.0/fmt/colors.ts": "c51c4642678eb690dcf5ffee5918b675bf01a33fba82acf303701ae1a4f8c8d9", + "https://deno.land/std@0.204.0/testing/asserts.ts": "b4e4b1359393aeff09e853e27901a982c685cb630df30426ed75496961931946", "https://deno.land/x/base64@v0.2.1/base.ts": "47dc8d68f07dc91524bdd6db36eccbe59cf4d935b5fc09f27357a3944bb3ff7b", "https://deno.land/x/base64@v0.2.1/base64url.ts": "18bbf879b31f1f32cca8adaa2b6885ae325c2cec6a66c5817b684ca12c46ad5e", "https://deno.land/x/sha1@v1.0.3/deps.ts": "2e1af51a48c8507017fdb057b950366601b177fb7e73d5de54c1b3e0e115d72e", diff --git a/pkg/commands/hgetall.test.ts b/pkg/commands/hgetall.test.ts index cf91609c..530cf9d2 100644 --- a/pkg/commands/hgetall.test.ts +++ b/pkg/commands/hgetall.test.ts @@ -1,8 +1,13 @@ -import { keygen, newHttpClient, randomID } from "../test-utils.ts"; import { assertEquals } from "https://deno.land/std@0.177.0/testing/asserts.ts"; import { afterAll } from "https://deno.land/std@0.177.0/testing/bdd.ts"; -import { HSetCommand } from "./hset.ts"; +import { + keygen, + newHttpClient, + randomID, + randomUnsafeIntegerString, +} from "../test-utils.ts"; import { HGetAllCommand } from "./hgetall.ts"; +import { HSetCommand } from "./hset.ts"; const client = newHttpClient(); @@ -15,7 +20,7 @@ Deno.test("returns all fields", async () => { const value1 = false; const value2 = randomID(); await new HSetCommand([key, { [field1]: value1, [field2]: value2 }]).exec( - client, + client ); const res = await new HGetAllCommand([key]).exec(client); @@ -29,3 +34,21 @@ Deno.test("when hash does not exist", async (t) => { assertEquals(res, null); }); }); +Deno.test("properly return bigint precisely", async () => { + const key = newKey(); + const field3 = randomID(); + const field2 = randomID(); + const field1 = randomID(); + const value1 = false; + const value2 = randomID(); + const value3 = randomUnsafeIntegerString(); + await new HSetCommand([ + key, + { [field1]: value1, [field2]: value2, [field3]: value3 }, + ]).exec(client); + + const res = await new HGetAllCommand([key]).exec(client); + + const obj = { [field1]: value1, [field2]: value2, [field3]: value3 }; + assertEquals(res, obj); +}); diff --git a/pkg/commands/hgetall.ts b/pkg/commands/hgetall.ts index e889a8e9..b8ed350c 100644 --- a/pkg/commands/hgetall.ts +++ b/pkg/commands/hgetall.ts @@ -1,7 +1,7 @@ import { Command, CommandOptions } from "./command.ts"; function deserialize>( - result: string[], + result: string[] ): TData | null { if (result.length === 0) { return null; @@ -11,7 +11,14 @@ function deserialize>( const key = result.shift()!; const value = result.shift()!; try { - obj[key] = JSON.parse(value); + // handle unsafe integer + const valueIsNumberAndNotSafeInteger = + !isNaN(Number(value)) && !Number.isSafeInteger(value); + if (valueIsNumberAndNotSafeInteger) { + obj[key] = value; + } else { + obj[key] = JSON.parse(value); + } } catch { obj[key] = value; } @@ -23,11 +30,11 @@ function deserialize>( * @see https://redis.io/commands/hgetall */ export class HGetAllCommand< - TData extends Record, + TData extends Record > extends Command { constructor( cmd: [key: string], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["hgetall", ...cmd], { deserialize: (result) => deserialize(result as string[]), diff --git a/pkg/test-utils.ts b/pkg/test-utils.ts index b6bc5b05..54692cd1 100644 --- a/pkg/test-utils.ts +++ b/pkg/test-utils.ts @@ -14,6 +14,11 @@ export function randomID(): string { } return btoa(s.join("")); } +export const randomUnsafeIntegerString = () => { + const max = Number.MAX_SAFE_INTEGER + Math.floor(Math.random() * 100); + const min = Number.MIN_SAFE_INTEGER - Math.floor(Math.random() * 100); + return String(Math.floor(Math.random() * (max - min) + min)); +}; export const newHttpClient = () => { const url = Deno.env.get("UPSTASH_REDIS_REST_URL"); if (!url) { From e16ad2e415a5f78aa74ecd24337b90cdbb15cf0d Mon Sep 17 00:00:00 2001 From: Imamuzzaki Abu Salam Date: Wed, 18 Oct 2023 13:25:19 +0700 Subject: [PATCH 2/4] test: add test for randomUnsafeIntegerString() --- pkg/test-utils.test.ts | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 pkg/test-utils.test.ts diff --git a/pkg/test-utils.test.ts b/pkg/test-utils.test.ts new file mode 100644 index 00000000..2dbef9db --- /dev/null +++ b/pkg/test-utils.test.ts @@ -0,0 +1,7 @@ +import { assertFalse } from "https://deno.land/std@0.177.0/testing/asserts.ts"; +import { randomUnsafeIntegerString } from "./test-utils.ts"; + +Deno.test("randomUnsafeIntegerString() return unsafe integer string", () => { + const unsafeIntegerString = randomUnsafeIntegerString(); + assertFalse(Number.isSafeInteger(unsafeIntegerString)); +}); From 1c1c8d65a398e773bbe1af12185154002520c2d6 Mon Sep 17 00:00:00 2001 From: Imamuzzaki Abu Salam Date: Wed, 18 Oct 2023 13:33:11 +0700 Subject: [PATCH 3/4] fix(test-utils): Fix randomUnsafeIntegerString implementation --- pkg/test-utils.test.ts | 23 +++++++++++++++++++---- pkg/test-utils.ts | 10 ++++++---- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/pkg/test-utils.test.ts b/pkg/test-utils.test.ts index 2dbef9db..62908f2c 100644 --- a/pkg/test-utils.test.ts +++ b/pkg/test-utils.test.ts @@ -1,7 +1,22 @@ -import { assertFalse } from "https://deno.land/std@0.177.0/testing/asserts.ts"; +import { + assertEquals, + assertFalse, +} from "https://deno.land/std@0.177.0/testing/asserts.ts"; import { randomUnsafeIntegerString } from "./test-utils.ts"; -Deno.test("randomUnsafeIntegerString() return unsafe integer string", () => { - const unsafeIntegerString = randomUnsafeIntegerString(); - assertFalse(Number.isSafeInteger(unsafeIntegerString)); +Deno.test("randomUnsafeIntegerString() should return a string", () => { + const result = randomUnsafeIntegerString(); + assertEquals(typeof result, "string"); }); +Deno.test("randomUnsafeIntegerString() should return different values", () => { + const result1 = randomUnsafeIntegerString(); + const result2 = randomUnsafeIntegerString(); + assertEquals(result1 !== result2, true); +}); +Deno.test( + "randomUnsafeIntegerString() should return a string with unsafe integer", + () => { + const result = randomUnsafeIntegerString(); + assertFalse(Number.isSafeInteger(Number(result))); + } +); diff --git a/pkg/test-utils.ts b/pkg/test-utils.ts index 54692cd1..75fc9df2 100644 --- a/pkg/test-utils.ts +++ b/pkg/test-utils.ts @@ -14,10 +14,12 @@ export function randomID(): string { } return btoa(s.join("")); } -export const randomUnsafeIntegerString = () => { - const max = Number.MAX_SAFE_INTEGER + Math.floor(Math.random() * 100); - const min = Number.MIN_SAFE_INTEGER - Math.floor(Math.random() * 100); - return String(Math.floor(Math.random() * (max - min) + min)); +export const randomUnsafeIntegerString = (): string => { + const buffer = new Uint8Array(8); + crypto.getRandomValues(buffer); + const dataView = new DataView(buffer.buffer); + const unsafeInteger = dataView.getBigInt64(0, true); // true for little-endian + return unsafeInteger.toString(); }; export const newHttpClient = () => { const url = Deno.env.get("UPSTASH_REDIS_REST_URL"); From 6aeab3e2c0df0a5c509c400662aced93f34bab9e Mon Sep 17 00:00:00 2001 From: ogzhanolguncu Date: Wed, 18 Oct 2023 12:19:58 +0300 Subject: [PATCH 4/4] Format changes with fmt --- pkg/commands/hgetall.test.ts | 2 +- pkg/commands/hgetall.ts | 10 +++++----- pkg/test-utils.test.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/commands/hgetall.test.ts b/pkg/commands/hgetall.test.ts index 530cf9d2..6af3f64c 100644 --- a/pkg/commands/hgetall.test.ts +++ b/pkg/commands/hgetall.test.ts @@ -20,7 +20,7 @@ Deno.test("returns all fields", async () => { const value1 = false; const value2 = randomID(); await new HSetCommand([key, { [field1]: value1, [field2]: value2 }]).exec( - client + client, ); const res = await new HGetAllCommand([key]).exec(client); diff --git a/pkg/commands/hgetall.ts b/pkg/commands/hgetall.ts index b8ed350c..9248a051 100644 --- a/pkg/commands/hgetall.ts +++ b/pkg/commands/hgetall.ts @@ -1,7 +1,7 @@ import { Command, CommandOptions } from "./command.ts"; function deserialize>( - result: string[] + result: string[], ): TData | null { if (result.length === 0) { return null; @@ -12,8 +12,8 @@ function deserialize>( const value = result.shift()!; try { // handle unsafe integer - const valueIsNumberAndNotSafeInteger = - !isNaN(Number(value)) && !Number.isSafeInteger(value); + const valueIsNumberAndNotSafeInteger = !isNaN(Number(value)) && + !Number.isSafeInteger(value); if (valueIsNumberAndNotSafeInteger) { obj[key] = value; } else { @@ -30,11 +30,11 @@ function deserialize>( * @see https://redis.io/commands/hgetall */ export class HGetAllCommand< - TData extends Record + TData extends Record, > extends Command { constructor( cmd: [key: string], - opts?: CommandOptions + opts?: CommandOptions, ) { super(["hgetall", ...cmd], { deserialize: (result) => deserialize(result as string[]), diff --git a/pkg/test-utils.test.ts b/pkg/test-utils.test.ts index 62908f2c..feb58bbc 100644 --- a/pkg/test-utils.test.ts +++ b/pkg/test-utils.test.ts @@ -18,5 +18,5 @@ Deno.test( () => { const result = randomUnsafeIntegerString(); assertFalse(Number.isSafeInteger(Number(result))); - } + }, );