From b958f3c39b78f1c27bfdf780f084c3332febdede Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Thu, 3 Sep 2026 23:30:20 +0000 Subject: [PATCH 1/4] test: pin iat in the jsonwebtoken async vs sync sign comparison The test signs the same payload once asynchronously and once synchronously and compares the tokens. Each sign() reads Date.now() for iat. When a second boundary falls between the two calls the tokens differ and expect() throws inside the jws callback. jws catches the throw and emits 'error', but jsonwebtoken's once() wrapped callback already ran, so the error is lost and the test times out after 90s with no message. Pin iat in the payload so both tokens are deterministic. Wrap the assertions in try/catch so a failure reaches done(err). --- .../jsonwebtoken/async_sign.test.js | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/test/js/third_party/jsonwebtoken/async_sign.test.js b/test/js/third_party/jsonwebtoken/async_sign.test.js index 1b35396767e2..2b6185d84453 100644 --- a/test/js/third_party/jsonwebtoken/async_sign.test.js +++ b/test/js/third_party/jsonwebtoken/async_sign.test.js @@ -9,12 +9,22 @@ describe("signing a token asynchronously", function () { var secret = "shhhhhh"; it("should return the same result as singing synchronously", function (done) { - jwt.sign({ foo: "bar" }, secret, { algorithm: "HS256" }, function (err, asyncToken) { + // Pin `iat`. Without it each sign() reads Date.now(), and the two tokens + // differ when a second boundary falls between the async and the sync call. + var payload = { foo: "bar", iat: 1700000000 }; + jwt.sign(payload, secret, { algorithm: "HS256" }, function (err, asyncToken) { if (err) return done(err); - var syncToken = jwt.sign({ foo: "bar" }, secret, { algorithm: "HS256" }); - expect(typeof asyncToken).toBe("string"); - expect(asyncToken.split(".")).toHaveLength(3); - expect(asyncToken).toEqual(syncToken); + // jws runs this callback inside a try/catch and routes a throw to the + // 'error' listener, which jsonwebtoken wrapped in once(). A failed + // expect() here is swallowed and the test only times out. Report it. + try { + var syncToken = jwt.sign(payload, secret, { algorithm: "HS256" }); + expect(typeof asyncToken).toBe("string"); + expect(asyncToken.split(".")).toHaveLength(3); + expect(asyncToken).toEqual(syncToken); + } catch (e) { + return done(e); + } done(); }); }); From b35dd70ba3e111f4f9e14766e25f35a1684c76a1 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Thu, 3 Sep 2026 23:51:56 +0000 Subject: [PATCH 2/4] test: freeze the clock with sinon fake timers instead of pinning iat The sibling claim tests in this directory already use sinon.useFakeTimers({ now: 60000 }). Do the same here so the async and the sync sign read the same Date.now() and the payload stays the upstream { foo: "bar" }. --- .../jsonwebtoken/async_sign.test.js | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/test/js/third_party/jsonwebtoken/async_sign.test.js b/test/js/third_party/jsonwebtoken/async_sign.test.js index 2b6185d84453..faa95d63b6b9 100644 --- a/test/js/third_party/jsonwebtoken/async_sign.test.js +++ b/test/js/third_party/jsonwebtoken/async_sign.test.js @@ -1,24 +1,33 @@ -import { describe, expect, it } from "bun:test"; +import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import { generateKeyPairSync } from "crypto"; import jwt from "jsonwebtoken"; import jws from "jws"; +import sinon from "sinon"; var PS_SUPPORTED = true; describe("signing a token asynchronously", function () { describe("when signing a token", function () { var secret = "shhhhhh"; + // Each sign() reads Date.now() for `iat`. Freeze the clock so the async + // and the sync token cannot differ when a second boundary falls between them. + var fakeClock; + beforeEach(function () { + fakeClock = sinon.useFakeTimers({ now: 60000 }); + }); + + afterEach(function () { + fakeClock.uninstall(); + }); + it("should return the same result as singing synchronously", function (done) { - // Pin `iat`. Without it each sign() reads Date.now(), and the two tokens - // differ when a second boundary falls between the async and the sync call. - var payload = { foo: "bar", iat: 1700000000 }; - jwt.sign(payload, secret, { algorithm: "HS256" }, function (err, asyncToken) { + jwt.sign({ foo: "bar" }, secret, { algorithm: "HS256" }, function (err, asyncToken) { if (err) return done(err); // jws runs this callback inside a try/catch and routes a throw to the // 'error' listener, which jsonwebtoken wrapped in once(). A failed // expect() here is swallowed and the test only times out. Report it. try { - var syncToken = jwt.sign(payload, secret, { algorithm: "HS256" }); + var syncToken = jwt.sign({ foo: "bar" }, secret, { algorithm: "HS256" }); expect(typeof asyncToken).toBe("string"); expect(asyncToken.split(".")).toHaveLength(3); expect(asyncToken).toEqual(syncToken); From 86ba1c31b69f3c232a917967b67c541ddce6d459 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Thu, 3 Sep 2026 23:52:55 +0000 Subject: [PATCH 3/4] test: use setSystemTime from bun:test instead of sinon fake timers --- test/js/third_party/jsonwebtoken/async_sign.test.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/js/third_party/jsonwebtoken/async_sign.test.js b/test/js/third_party/jsonwebtoken/async_sign.test.js index faa95d63b6b9..226d303ecd51 100644 --- a/test/js/third_party/jsonwebtoken/async_sign.test.js +++ b/test/js/third_party/jsonwebtoken/async_sign.test.js @@ -1,8 +1,7 @@ -import { afterEach, beforeEach, describe, expect, it } from "bun:test"; +import { afterEach, beforeEach, describe, expect, it, setSystemTime } from "bun:test"; import { generateKeyPairSync } from "crypto"; import jwt from "jsonwebtoken"; import jws from "jws"; -import sinon from "sinon"; var PS_SUPPORTED = true; describe("signing a token asynchronously", function () { @@ -11,13 +10,12 @@ describe("signing a token asynchronously", function () { // Each sign() reads Date.now() for `iat`. Freeze the clock so the async // and the sync token cannot differ when a second boundary falls between them. - var fakeClock; beforeEach(function () { - fakeClock = sinon.useFakeTimers({ now: 60000 }); + setSystemTime(60000); }); afterEach(function () { - fakeClock.uninstall(); + setSystemTime(); }); it("should return the same result as singing synchronously", function (done) { From bc61e7b9405143bf8b9a896dd696e040c25fcbdd Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Fri, 4 Sep 2026 00:06:46 +0000 Subject: [PATCH 4/4] ci: retrigger