Created
November 25, 2025 23:49
-
-
Save akapug/51ade9cac8b16d64fd094820dcae9b39 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| pug@pugtop:~$ curl -sL https://github.com/elide-dev/elide/pull/1770.diff | |
| diff --git a/packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/intrinsics/js/crypto/WebCryptoIntrinsic.kt b/packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/intrinsics/js/crypto/WebCryptoIntrinsic.kt | |
| index 863e10b9c5..978d70c7a8 100644 | |
| --- a/packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/intrinsics/js/crypto/WebCryptoIntrinsic.kt | |
| +++ b/packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/intrinsics/js/crypto/WebCryptoIntrinsic.kt | |
| @@ -27,6 +27,7 @@ import elide.runtime.gvm.internals.intrinsics.js.typed.UUIDValue | |
| import elide.runtime.intrinsics.GuestIntrinsic | |
| import elide.runtime.intrinsics.js.Crypto.Companion.MAX_RANDOM_BYTES_SIZE | |
| import elide.runtime.intrinsics.js.SubtleCrypto | |
| +import elide.runtime.intrinsics.js.err.AbstractJsException | |
| import elide.runtime.intrinsics.js.err.QuotaExceededError | |
| import elide.runtime.intrinsics.js.err.ValueError | |
| import elide.runtime.intrinsics.js.typed.UUID | |
| diff --git a/packages/graalvm/src/main/kotlin/elide/runtime/intrinsics/js/node/CryptoAPI.kt b/packages/graalvm/src/main/kotlin/elide/runtime/intrinsics/js/node/CryptoAPI.kt | |
| index 509221a3e6..fe48d702bb 100644 | |
| --- a/packages/graalvm/src/main/kotlin/elide/runtime/intrinsics/js/node/CryptoAPI.kt | |
| +++ b/packages/graalvm/src/main/kotlin/elide/runtime/intrinsics/js/node/CryptoAPI.kt | |
| @@ -14,6 +14,7 @@ package elide.runtime.intrinsics.js.node | |
| import org.graalvm.polyglot.Value | |
| import elide.annotations.API | |
| +import elide.runtime.intrinsics.js.node.crypto.RandomIntCallback | |
| import elide.vm.annotations.Polyglot | |
| /** | |
| @@ -30,4 +31,56 @@ import elide.vm.annotations.Polyglot | |
| * @return A randomly generated 36 character UUID c4 string in lowercase format (e.g. "5cb34cef-5fc2-47e4-a3ac-4bb055fa2025") | |
| */ | |
| @Polyglot public fun randomUUID(options: Value? = null): String | |
| + | |
| + /** | |
| + * ## Crypto: `randomInt` | |
| + * Generates a cryptographically secure random integer between the specified `min` (inclusive) and `max` (exclusive) values. | |
| + * | |
| + * See also: [Node Crypto API: `randomInt`](https://nodejs.org/api/crypto.html#cryptorandomintmin-max-callback) | |
| + * | |
| + * @param min Lower bound (inclusive) | |
| + * @param max Upper bound (exclusive) | |
| + * @param callback Callback to receive the generated safe integer or an error. | |
| + * @return Unit | |
| + */ | |
| + @Polyglot public fun randomInt(min: Long, max: Long, callback: RandomIntCallback) | |
| + | |
| + /** | |
| + * ## Crypto: randomInt | |
| + * Generates a cryptographically secure random integer between the specified `min` (inclusive) and `max` (exclusive) values. | |
| + * | |
| + * See also: [Node Crypto API: `randomInt`](https://nodejs.org/api/crypto.html#cryptorandomintmin-max-callback) | |
| + * | |
| + * @param min Lower bound (inclusive) | |
| + * @param max Upper bound (exclusive) | |
| + * @return A randomly generated safe integer between `min` (inclusive) and `max` (exclusive). | |
| + */ | |
| + @Polyglot public fun randomInt(min: Long, max: Long): Long | |
| + | |
| + /** | |
| + * ## Crypto: randomInt | |
| + * Public overload of [randomInt]. | |
| + * Generates a cryptographically secure random integer between the specified `min` (inclusive) and `max` (exclusive) values. | |
| + * @param min Lower bound (inclusive) | |
| + * @param max Upper bound (exclusive) | |
| + * @param callback Callback to receive the generated safe integer or an error. | |
| + */ | |
| + @Polyglot public fun randomInt(min: Value, max: Value, callback: Value) | |
| + | |
| + /** | |
| + * ## Crypto: randomInt | |
| + * Public overload of [randomInt]. | |
| + * Generates a cryptographically secure random integer between the specified `min` (inclusive) and `max` (exclusive) values. | |
| + * @param min Lower bound (inclusive) | |
| + * @param max Upper bound (exclusive) | |
| + */ | |
| + @Polyglot public fun randomInt(min: Value, max: Value): Long | |
| + | |
| + /** | |
| + * ## Crypto: randomInt | |
| + * Public overload of [randomInt]. | |
| + * Generates a cryptographically secure random integer between the specified `min` (inclusive) and `max` (exclusive) values. | |
| + * @param max Upper bound (exclusive) | |
| + */ | |
| + @Polyglot public fun randomInt(max: Value): Long | |
| } | |
| diff --git a/packages/graalvm/src/main/kotlin/elide/runtime/intrinsics/js/node/crypto/CryptoOptions.kt b/packages/graalvm/src/main/kotlin/elide/runtime/intrinsics/js/node/crypto/CryptoOptions.kt | |
| new file mode 100644 | |
| index 0000000000..603ffdaee4 | |
| --- /dev/null | |
| +++ b/packages/graalvm/src/main/kotlin/elide/runtime/intrinsics/js/node/crypto/CryptoOptions.kt | |
| @@ -0,0 +1,20 @@ | |
| +/* | |
| + * Copyright (c) 2024-2025 Elide Technologies, Inc. | |
| + * | |
| + * Licensed under the MIT license (the "License"); you may not use this file except in compliance | |
| + * with the License. You may obtain a copy of the License at | |
| + * | |
| + * https://opensource.org/license/mit/ | |
| + * | |
| + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on | |
| + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| + * License for the specific language governing permissions and limitations under the License. | |
| + */ | |
| +package elide.runtime.intrinsics.js.node.crypto | |
| + | |
| +/** | |
| + * ## Callback: `crypto.randomInt` | |
| + * | |
| + * Describes the callback function shape which is provided to the `randomInt` operation. | |
| + */ | |
| +public typealias RandomIntCallback = (err: Throwable?, value: Long?) -> Unit | |
| diff --git a/packages/graalvm/src/main/kotlin/elide/runtime/node/crypto/NodeCrypto.kt b/packages/graalvm/src/main/kotlin/elide/runtime/node/crypto/NodeCrypto.kt | |
| index 3407a12395..4fbaaef773 100644 | |
| --- a/packages/graalvm/src/main/kotlin/elide/runtime/node/crypto/NodeCrypto.kt | |
| +++ b/packages/graalvm/src/main/kotlin/elide/runtime/node/crypto/NodeCrypto.kt | |
| @@ -14,8 +14,10 @@ package elide.runtime.node.crypto | |
| import org.graalvm.polyglot.Value | |
| import org.graalvm.polyglot.proxy.ProxyExecutable | |
| +import java.math.BigInteger | |
| import elide.runtime.gvm.api.Intrinsic | |
| import elide.runtime.gvm.internals.intrinsics.js.AbstractNodeBuiltinModule | |
| +import elide.runtime.gvm.js.JsError | |
| import elide.runtime.gvm.js.JsSymbol.JsSymbols.asJsSymbol | |
| import elide.runtime.gvm.loader.ModuleInfo | |
| import elide.runtime.gvm.loader.ModuleRegistry | |
| @@ -24,12 +26,85 @@ import elide.runtime.intrinsics.GuestIntrinsic.MutableIntrinsicBindings | |
| import elide.runtime.intrinsics.js.node.CryptoAPI | |
| import elide.runtime.lang.javascript.NodeModuleName | |
| import elide.vm.annotations.Polyglot | |
| +import java.security.SecureRandom | |
| +import kotlin.concurrent.thread | |
| +import elide.runtime.intrinsics.js.err.RangeError | |
| +import elide.runtime.intrinsics.js.err.TypeError | |
| +import elide.runtime.intrinsics.js.node.crypto.RandomIntCallback | |
| // Internal symbol where the Node built-in module is installed. | |
| private const val CRYPTO_MODULE_SYMBOL = "node_${NodeModuleName.CRYPTO}" | |
| -// Functiopn name for randomUUID | |
| +// Function name for randomUUID | |
| private const val F_RANDOM_UUID = "randomUUID" | |
| +private const val F_RANDOM_INT = "randomInt" | |
| + | |
| +// Cached Int generator to ensure we don't create multiple instances. | |
| +private val cryptoRandomGenerator by lazy { SecureRandom() } | |
| + | |
| +// The maximum range (max - min) allowed is 2^48 in Node.js. | |
| +private val MAX_48_BIT_LIMIT = BigInteger.valueOf(2L).pow(48) | |
| + | |
| +// Generates a cryptographically secure random integer between the specified `min` (inclusive) and `max` (exclusive) values. | |
| +private fun genRandomInt(min: Long, max: Long): Long { | |
| + try { | |
| + return cryptoRandomGenerator.nextLong(min, max) | |
| + } catch (e: Throwable) { | |
| + throw TypeError.create("Error generating random bytes for randomInt: ${e.message}") | |
| + } | |
| +} | |
| + | |
| +// Safely converts a Value to a BigInteger, ensuring it is a safe integer within JS limits. | |
| +private fun safeValueToBigInt(value: Value, name: String): BigInteger { | |
| + if (value.isNumber) { | |
| + val bigIntValue: BigInteger? = when { | |
| + value.fitsInLong() -> { | |
| + BigInteger.valueOf(value.asLong()) | |
| + } | |
| + // Reject integers that exceed Long.MAX_VALUE or are less than Long.MIN_VALUE | |
| + value.fitsInBigInteger() -> throw RangeError.create("The \"$name\" argument must be a safe integer. Received an integer that exceeds the max bounds ${MAX_48_BIT_LIMIT}.") | |
| + // Reject non-integer numbers | |
| + value.fitsInDouble() -> { | |
| + throw TypeError.create("The \"$name\" argument must be a safe integer. Received a non-integer number: ${value.asDouble()}.") | |
| + } | |
| + else -> null // Reject non-integer (e.g. Infinity, NaN, very large BigInts) | |
| + } | |
| + | |
| + // Define JS safe integer bounds | |
| + val jsMaxSafeInt = BigInteger("9007199254740991") // 2^53 - 1 | |
| + val jsMinSafeInt = BigInteger("-9007199254740991") // -(2^53 - 1) | |
| + | |
| + // Final check: even if conversion works, ensure it falls within JS safe limits | |
| + if (bigIntValue != null && bigIntValue >= jsMinSafeInt && bigIntValue <= jsMaxSafeInt) { | |
| + return bigIntValue | |
| + } | |
| + } | |
| + // Invalid value type, we don't want it | |
| + throw TypeError.create("The \"$name\" argument must be a safe integer. Received ${value}.") | |
| +} | |
| + | |
| +// Validates that the provided min and max values are safe integers and that the range difference does not exceed 2^48. | |
| +private fun genSafeRange(min: Value, max: Value): Pair<Long, Long> { | |
| + // Safely convert both inputs to BigInteger | |
| + val minBigInt = safeValueToBigInt(min, "min") | |
| + val maxBigInt = safeValueToBigInt(max, "max") | |
| + | |
| + // Enforce the Min <= Max rule otherwise we throw a RangeError | |
| + if (minBigInt >= maxBigInt) { | |
| + throw RangeError.create("The value of \"max\" is out of range. It must be greater than the value of \"min\" (${minBigInt}). Received ${maxBigInt}.") | |
| + } | |
| + | |
| + val rangeDifference = maxBigInt.subtract(minBigInt) | |
| + | |
| + // If the range difference exceeds 2^48, we throw a RangeError. Node.js has a range limit of 2^48 for randomInt. | |
| + if (rangeDifference > MAX_48_BIT_LIMIT) { | |
| + println("Range difference exceeds 2^48 limit: $rangeDifference") | |
| + throw RangeError.create("The value of \"max - min\" is out of range. It must be <= 281474976710655. Received ${rangeDifference}.") | |
| + } | |
| + | |
| + // Return the validated safe Long values | |
| + return Pair(minBigInt.toLong(), maxBigInt.toLong()) | |
| +} | |
| // Installs the Node crypto module into the intrinsic bindings. | |
| @Intrinsic internal class NodeCryptoModule : AbstractNodeBuiltinModule() { | |
| @@ -46,14 +121,13 @@ private const val F_RANDOM_UUID = "randomUUID" | |
| * # Node API: `crypto` | |
| */ | |
| internal class NodeCrypto private constructor () : ReadOnlyProxyObject, CryptoAPI { | |
| - // | |
| - | |
| internal companion object { | |
| @JvmStatic fun create(): NodeCrypto = NodeCrypto() | |
| // Module members | |
| private val moduleMembers = arrayOf( | |
| F_RANDOM_UUID, | |
| + F_RANDOM_INT, | |
| ).apply { sort() } | |
| } | |
| @@ -64,10 +138,51 @@ internal class NodeCrypto private constructor () : ReadOnlyProxyObject, CryptoAP | |
| return java.util.UUID.randomUUID().toString() | |
| } | |
| + @Polyglot override fun randomInt(min: Long, max: Long): Long { | |
| + return genRandomInt(min, max) | |
| + } | |
| + | |
| + @Polyglot override fun randomInt(min: Long, max: Long, callback: RandomIntCallback) { | |
| + val randomValue = genRandomInt(min, max) | |
| + | |
| + thread { | |
| + try { | |
| + callback.invoke(null, randomValue) | |
| + } catch (e: Throwable) { | |
| + callback.invoke(TypeError.create(e.message ?: "Unknown error"), randomValue) | |
| + } | |
| + } | |
| + } | |
| + | |
| + @Polyglot override fun randomInt(min: Value, max: Value, callback: Value) { | |
| + val (safeMin, safeMax) = genSafeRange(min, max) | |
| + | |
| + val safeCallback: RandomIntCallback = callback.let { cb -> | |
| + { err: Throwable?, value: Long? -> | |
| + cb.execute( | |
| + err?.let { Value.asValue(it) }, | |
| + value?.let { Value.asValue(it) } | |
| + ) | |
| + } | |
| + } | |
| + | |
| + return randomInt(safeMin, safeMax, safeCallback) | |
| + } | |
| + | |
| + @Polyglot override fun randomInt(min: Value, max: Value): Long { | |
| + val (safeMin, safeMax) = genSafeRange(min, max) | |
| + return randomInt(safeMin, safeMax) | |
| + } | |
| + | |
| + @Polyglot override fun randomInt(max: Value): Long { | |
| + val (safeMin, safeMax) = genSafeRange(Value.asValue(0), max) | |
| + return randomInt(safeMin, safeMax) | |
| + } | |
| + | |
| // ProxyObject implementation | |
| override fun getMemberKeys(): Array<String> = moduleMembers | |
| - override fun hasMember(key: String): Boolean = | |
| + override fun hasMember(key: String): Boolean = | |
| moduleMembers.binarySearch(key) >= 0 | |
| override fun getMember(key: String): Any? = when (key) { | |
| @@ -76,6 +191,31 @@ internal class NodeCrypto private constructor () : ReadOnlyProxyObject, CryptoAP | |
| val options = args.getOrNull(0) | |
| randomUUID(options) | |
| } | |
| + F_RANDOM_INT -> ProxyExecutable { args -> | |
| + // Check if last argument is a callback function | |
| + val lastIsCb = args.lastOrNull()?.canExecute() == true | |
| + | |
| + when (args.size) { | |
| + 1 -> { | |
| + // randomInt(max) | |
| + this.randomInt(args[0]) | |
| + } | |
| + 2 -> { | |
| + if (lastIsCb) { | |
| + // randomInt(max, callback) | |
| + this.randomInt(Value.asValue(0), args[0], args.last()) | |
| + } else { | |
| + // randomInt(min, max) | |
| + this.randomInt(args[0], args[1]) | |
| + } | |
| + } | |
| + 3 -> { | |
| + // randomInt(min, max, callback) | |
| + this.randomInt(args[0], args[1], args.last()) | |
| + } | |
| + else -> throw JsError.typeError("Invalid number of arguments for crypto.randomInt: ${args.size}") | |
| + } | |
| + } | |
| else -> null | |
| } | |
| } | |
| diff --git a/packages/graalvm/src/test/kotlin/elide/runtime/node/NodeCryptoTest.kt b/packages/graalvm/src/test/kotlin/elide/runtime/node/NodeCryptoTest.kt | |
| index eb564fbdd9..e9476af3e7 100644 | |
| --- a/packages/graalvm/src/test/kotlin/elide/runtime/node/NodeCryptoTest.kt | |
| +++ b/packages/graalvm/src/test/kotlin/elide/runtime/node/NodeCryptoTest.kt | |
| @@ -12,13 +12,21 @@ | |
| */ | |
| package elide.runtime.node | |
| +import org.graalvm.polyglot.Value | |
| +import org.junit.jupiter.api.assertThrows | |
| +import java.util.concurrent.CountDownLatch | |
| +import java.util.concurrent.TimeUnit | |
| import kotlin.test.Test | |
| import kotlin.test.assertEquals | |
| +import kotlin.test.assertFailsWith | |
| import kotlin.test.assertIs | |
| import kotlin.test.assertNotEquals | |
| import kotlin.test.assertTrue | |
| import kotlin.test.assertNotNull | |
| +import kotlin.test.assertNull | |
| import elide.annotations.Inject | |
| +import elide.runtime.intrinsics.js.err.RangeError | |
| +import elide.runtime.intrinsics.js.err.TypeError | |
| import elide.runtime.node.crypto.NodeCryptoModule | |
| import elide.testing.annotations.TestCase | |
| @@ -198,4 +206,316 @@ import elide.testing.annotations.TestCase | |
| assert.equal(typeof uuid2, "string"); | |
| """ | |
| } | |
| + | |
| + @Test fun `randomInt should return a Long when valid min and max are provided with no callback`() = conforms { | |
| + val min = Value.asValue(5L) | |
| + val max = Value.asValue(10L) | |
| + val result = crypto.provide().randomInt(min, max) | |
| + | |
| + assertIs<Long>(result) | |
| + assertTrue(result in 5 until 10) | |
| + }.guest { | |
| + //language=javascript | |
| + """ | |
| + const crypto = require("crypto"); | |
| + const assert = require("assert"); | |
| + | |
| + const result = crypto.randomInt(5, 10); | |
| + | |
| + assert.equal(typeof result, "number"); | |
| + assert.ok(result >= 5 && result < 10); | |
| + """ | |
| + } | |
| + | |
| + @Test fun `randomInt should throw a RangeError when min is greater than or equal to max`() = conforms { | |
| + | |
| + assertFailsWith<RangeError> { crypto.provide().randomInt(Value.asValue(10L), Value.asValue(10L)) } | |
| + assertFailsWith<RangeError> { crypto.provide().randomInt(Value.asValue(10L), Value.asValue( 5L)) } | |
| + }.guest { | |
| + //language=javascript | |
| + """ | |
| + const crypto = require("crypto"); | |
| + const assert = require("assert"); | |
| + | |
| + assert.throws(() => crypto.randomInt(10, 10), RangeError); | |
| + assert.throws(() => crypto.randomInt(10, 5), RangeError); | |
| + """ | |
| + } | |
| + | |
| + @Test fun `randomInt should default min to 0 when only max is provided`() = conforms { | |
| + val result = crypto.provide().randomInt(Value.asValue(5L)) | |
| + | |
| + assertIs<Long>(result) | |
| + assertTrue(result in 0 until 5) | |
| + }.guest { | |
| + //language=javascript | |
| + """ | |
| + const crypto = require("crypto"); | |
| + const assert = require("assert"); | |
| + | |
| + const result = crypto.randomInt(5); | |
| + | |
| + assert.equal(typeof result, "number"); | |
| + assert.ok(result >= 0 && result < 5); | |
| + """ | |
| + } | |
| + | |
| + @Test fun `randomInt should invoke callback when callback is provided`() = conforms { | |
| + val latch = CountDownLatch(1) | |
| + var called = false | |
| + val result = crypto.provide().randomInt(10L, 20L) { err, value -> | |
| + called = true | |
| + assertNull(err) | |
| + assertTrue(value in 10L until 20L) | |
| + latch.countDown() | |
| + } | |
| + assertIs<Unit>(result) | |
| + latch.await(1, TimeUnit.SECONDS) | |
| + assertTrue(called) | |
| + }.guest { | |
| + //language=javascript | |
| + """ | |
| + const crypto = require("crypto") | |
| + const assert = require("assert") | |
| + | |
| + function randomIntPromise(min, max) { | |
| + return new Promise((resolve, reject) => { | |
| + crypto.randomInt(min, max, (err, int) => { | |
| + callbackInvoked = true; | |
| + assert.equal(err, null, "Callback error should be null"); | |
| + resolve(int); | |
| + }); | |
| + }); | |
| + }; | |
| + | |
| + let callbackInvoked = false; | |
| + | |
| + randomIntPromise(10, 20) | |
| + .then((int) => { | |
| + assert.equal(typeof int, "number"); | |
| + assert.ok(int >= 10 && int < 20, "randomInt should be within the range"); | |
| + assert.ok(callbackInvoked, "Callback should have been invoked"); | |
| + }) | |
| + """ | |
| + } | |
| + | |
| + @Test fun `randomInt should return min when range is 1`() = conforms { | |
| + val min = 7L | |
| + val max = 8L | |
| + | |
| + val result = crypto.provide().randomInt(min,max) | |
| + | |
| + assertEquals(min, result) | |
| + }.guest { | |
| + //language=javascript | |
| + """ | |
| + const crypto = require("crypto"); | |
| + const assert = require("assert"); | |
| + | |
| + const min = 7; | |
| + const max = 8; | |
| + | |
| + const result = crypto.randomInt(min, max); | |
| + | |
| + assert.equal(result, min); | |
| + """ | |
| + } | |
| + | |
| + @Test fun `randomInt should handle large ranges correctly`() = conforms { | |
| + val min = 0L | |
| + val max = 100_000_000_000L | |
| + val result = crypto.provide().randomInt(min, max) | |
| + | |
| + assertIs<Long>(result) | |
| + assertTrue(result in min until max) | |
| + }.guest { | |
| + //language=javascript | |
| + """ | |
| + const crypto = require("crypto"); | |
| + const assert = require("assert"); | |
| + | |
| + const min = 0; | |
| + const max = 100000000000; | |
| + const result = crypto.randomInt(min, max); | |
| + | |
| + assert.ok(result >= min && result < max); | |
| + """ | |
| + } | |
| + | |
| + @Test fun `randomInt should throw TypeError for non-numeric arguments`() = conforms { | |
| + | |
| + val invalidMin: Value = Value.asValue("a") | |
| + val validMax: Value = Value.asValue(10L) | |
| + val validMin: Value = Value.asValue(0L) | |
| + val invalidMax: Value = Value.asValue("b") | |
| + | |
| + assertFailsWith<TypeError> { crypto.provide().randomInt(invalidMin, validMax) } | |
| + assertFailsWith<TypeError> { crypto.provide().randomInt(validMin, invalidMax) } | |
| + }.guest { | |
| + //language=javascript | |
| + """ | |
| + const crypto = require("crypto"); | |
| + const assert = require("assert"); | |
| + | |
| + assert.throws(() => crypto.randomInt("a", 10), TypeError); | |
| + assert.throws(() => crypto.randomInt(0, "b"), TypeError); | |
| + """ | |
| + } | |
| + | |
| + @Test fun `randomInt callback should be async`() = conforms { | |
| + var callbackCalled = false | |
| + val min = 0L | |
| + val max = 10L | |
| + | |
| + fun callbackFn (err: Throwable?, value: Long?) { | |
| + callbackCalled = true | |
| + | |
| + if (err != null) { | |
| + throw err | |
| + } else { | |
| + assertTrue(value in min until max) | |
| + } | |
| + } | |
| + | |
| + val result = crypto.provide().randomInt(min, max, ::callbackFn) | |
| + // Callback should not have been called yet | |
| + assertTrue(!callbackCalled, "Callback should not have been invoked yet") | |
| + | |
| + // The result should be Unit since a callback was provided | |
| + assertIs<Unit>(result) | |
| + | |
| + // Callback should have been called by this point | |
| + assertTrue(callbackCalled, "Callback should have been invoked asynchronously") | |
| + }.guest { | |
| + //language=javascript | |
| + """ | |
| + const crypto = require("crypto"); | |
| + const assert = require("assert"); | |
| + | |
| + let callbackCalled = false; | |
| + crypto.randomInt(1, 10, (err, val) => { | |
| + callbackCalled = true; | |
| + | |
| + assert.equal(err, null); | |
| + assert.ok(val >= 1 && val < 10); | |
| + assert.ok(callbackCalled, "Callback should have been invoked asynchronously"); | |
| + }); | |
| + | |
| + assert.equal(callbackCalled, false); | |
| + """ | |
| + } | |
| + | |
| + @Test fun `randomInt should throw TypeError for float arguments`() = conforms { | |
| + assertFailsWith<TypeError> { crypto.provide().randomInt(Value.asValue(1.5), Value.asValue(10L)) } | |
| + assertFailsWith<TypeError> { crypto.provide().randomInt(Value.asValue(1L), Value.asValue(10.5)) } | |
| + assertFailsWith<TypeError> { crypto.provide().randomInt(Value.asValue(1.5), Value.asValue(10.5)) } | |
| + }.guest { | |
| + //language=javascript | |
| + """ | |
| + const crypto = require("crypto"); | |
| + const assert = require("assert"); | |
| + | |
| + assert.throws(() => crypto.randomInt(1.5, 10), TypeError); | |
| + assert.throws(() => crypto.randomInt(1, 10.5), TypeError); | |
| + assert.throws(() => crypto.randomInt(1.5, 10.5), TypeError); | |
| + """ | |
| + } | |
| + | |
| + @Test fun `randomInt should throw when range exceeds MAX_SAFE_INTEGER`() = conforms { | |
| + val min = Value.asValue(-9007199254740991L) | |
| + val max = Value.asValue(9007199254740991L) | |
| + | |
| + assertFailsWith<RangeError> { crypto.provide().randomInt(min, max) } | |
| + }.guest { | |
| + //language=javascript | |
| + """ | |
| + const crypto = require("crypto"); | |
| + const assert = require("assert"); | |
| + | |
| + const min = Number.MIN_SAFE_INTEGER; | |
| + const max = Number.MAX_SAFE_INTEGER; | |
| + | |
| + assert.throws(() => crypto.randomInt(min, max), RangeError); | |
| + """ | |
| + } | |
| + | |
| + @Test fun `randomInt large positive range`() = conforms { | |
| + val min = Value.asValue(0L) | |
| + val max = Value.asValue(281474976710655L) | |
| + | |
| + val result = crypto.provide().randomInt(min, max) | |
| + | |
| + assertTrue(result in 0L until 281474976710655L) | |
| + }.guest { | |
| + //language=javascript | |
| + """ | |
| + const crypto = require("crypto"); | |
| + const assert = require("assert"); | |
| + | |
| + const min = 0; | |
| + const max = 281474976710655; | |
| + const result = crypto.randomInt(min, max); | |
| + | |
| + assert.ok(result >= min && result < max, "Value is within the expected range"); | |
| + """ | |
| + } | |
| + | |
| + @Test fun `randomInt large negative range`() = conforms { | |
| + val min = -9_000_000_000_000L | |
| + val max = 0L | |
| + val result = crypto.provide().randomInt(min, max) | |
| + | |
| + // @TODO The assertion is causing failures, possibly due to number type coercion? | |
| + assertTrue(result in -9000000000000L until 0L) | |
| + }.guest { | |
| + //language=javascript | |
| + """ | |
| + const crypto = require("crypto"); | |
| + const assert = require("assert"); | |
| + | |
| + const min = -9000000000000; | |
| + const max = 0; | |
| + const result = crypto.randomInt(min, max); | |
| + | |
| + assert.ok(result >= min && result < max, "Value is within the expected range"); | |
| + """ | |
| + } | |
| + | |
| + @Test fun `randomInt max safe integer`() = conforms { | |
| + val min = Value.asValue(0) | |
| + val max = Value.asValue(9007199254740991) | |
| + | |
| + assertThrows<RangeError>{ crypto.provide().randomInt(min, max) } | |
| + }.guest { | |
| + //language=javascript | |
| + """ | |
| + const crypto = require("crypto"); | |
| + const assert = require("assert"); | |
| + | |
| + const min = 0; | |
| + const max = Number.MAX_SAFE_INTEGER; | |
| + | |
| + assert.throws(() => crypto.randomInt(min, max), RangeError); | |
| + """ | |
| + } | |
| + | |
| + @Test fun `randomInt range crossing zero`() = conforms { | |
| + val min = -100L | |
| + val max = 100L | |
| + val result = crypto.provide().randomInt(min, max) | |
| + | |
| + assertTrue(result in min until max) | |
| + }.guest { | |
| + //language=javascript | |
| + """ | |
| + const crypto = require("crypto"); | |
| + const assert = require("assert"); | |
| + | |
| + const min = -100; | |
| + const max = 100; | |
| + const result = crypto.randomInt(min, max); | |
| + | |
| + assert.ok(result >= min && result < max); | |
| + """ | |
| + } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment