Repository: denoland/deno
Pull request: fix(ext/node): validate randomUUID() options

Enables `parallel/test-crypto-randomuuid.js`

Ref https://github.com/denoland/deno/issues/29637


Changed files:
filename: ext/node/polyfills/internal/crypto/random.ts
patch: @@ -298,7 +298,18 @@ function unsignedBigIntToBuffer(bigint: bigint, name: string) {
   return Buffer.from(padded, "hex");
 }
 
-export const randomUUID = () => globalThis.crypto.randomUUID();
+export function randomUUID(options) {
+  if (options !== undefined) {
+    validateObject(options, "options");
+  }
+  const {
+    disableEntropyCache = false,
+  } = options || {};
+
+  validateBoolean(disableEntropyCache, "options.disableEntropyCache");
+
+  return globalThis.crypto.randomUUID();
+}
 
 export default {
   checkPrime,

filename: tests/node_compat/config.jsonc
patch: @@ -413,6 +413,7 @@
       "test-crypto-psychic-signatures.js",
       "test-crypto-publicDecrypt-fails-first-time.js",
       "test-crypto-randomfillsync-regression.js",
+      "test-crypto-randomuuid.js",
       "test-crypto-scrypt.js",
       "test-crypto-secret-keygen.js",
       "test-crypto-stream.js",

filename: tests/node_compat/runner/TODO.md
patch: @@ -1,7 +1,7 @@
 <!-- deno-fmt-ignore-file -->
 # Remaining Node Tests
 
-1195 tests out of 3993 have been ported from Node 23.9.0 (29.93% ported, 70.67% remaining).
+1196 tests out of 3993 have been ported from Node 23.9.0 (29.95% ported, 70.65% remaining).
 
 NOTE: This file should not be manually edited. Please edit `tests/node_compat/config.json` and run `deno task setup` in `tests/node_compat/runner` dir instead.
 
@@ -545,7 +545,6 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co
 - [parallel/test-crypto-padding.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-crypto-padding.js)
 - [parallel/test-crypto-private-decrypt-gh32240.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-crypto-private-decrypt-gh32240.js)
 - [parallel/test-crypto-random.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-crypto-random.js)
-- [parallel/test-crypto-randomuuid.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-crypto-randomuuid.js)
 - [parallel/test-crypto-rsa-dsa.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-crypto-rsa-dsa.js)
 - [parallel/test-crypto-sec-level.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-crypto-sec-level.js)
 - [parallel/test-crypto-secure-heap.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-crypto-secure-heap.js)

filename: tests/node_compat/test/parallel/test-crypto-randomuuid.js
patch: @@ -0,0 +1,65 @@
+// deno-fmt-ignore-file
+// deno-lint-ignore-file
+
+// Copyright Joyent and Node contributors. All rights reserved. MIT license.
+// Taken from Node 23.9.0
+// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually.
+
+'use strict';
+
+const common = require('../common');
+
+if (!common.hasCrypto)
+  common.skip('missing crypto');
+
+const assert = require('assert');
+const {
+  randomUUID,
+} = require('crypto');
+
+const last = new Set([
+  '00000000-0000-0000-0000-000000000000',
+]);
+
+function testMatch(uuid) {
+  assert.match(
+    uuid,
+    /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
+}
+
+// Generate a number of UUID's to make sure we're
+// not just generating the same value over and over
+// and to make sure the batching changes the random
+// bytes.
+for (let n = 0; n < 130; n++) {
+  const uuid = randomUUID();
+  assert(!last.has(uuid));
+  last.add(uuid);
+  assert.strictEqual(typeof uuid, 'string');
+  assert.strictEqual(uuid.length, 36);
+  testMatch(uuid);
+
+  // Check that version 4 identifier was populated.
+  assert.strictEqual(
+    Buffer.from(uuid.slice(14, 16), 'hex')[0] & 0x40, 0x40);
+
+  // Check that clock_seq_hi_and_reserved was populated with reserved bits.
+  assert.strictEqual(
+    Buffer.from(uuid.slice(19, 21), 'hex')[0] & 0b1100_0000, 0b1000_0000);
+}
+
+// Test non-buffered UUID's
+{
+  testMatch(randomUUID({ disableEntropyCache: true }));
+  testMatch(randomUUID({ disableEntropyCache: true }));
+  testMatch(randomUUID({ disableEntropyCache: true }));
+  testMatch(randomUUID({ disableEntropyCache: true }));
+
+  assert.throws(() => randomUUID(1), {
+    code: 'ERR_INVALID_ARG_TYPE'
+  });
+
+  assert.throws(() => randomUUID({ disableEntropyCache: '' }), {
+    code: 'ERR_INVALID_ARG_TYPE'
+  });
+}