diff options
| author | 2023-03-08 15:36:04 -0800 | |
|---|---|---|
| committer | 2023-03-08 15:36:16 -0800 | |
| commit | ebb42bb67b378921fb5ad001c4b7a1d4628b92d9 (patch) | |
| tree | 8679b364ce3809c5a0d3e8dde102f985ae983297 | |
| parent | 42edcaae8cffa64af76828a9b970de2081e0db86 (diff) | |
| download | bun-ebb42bb67b378921fb5ad001c4b7a1d4628b92d9.tar.gz bun-ebb42bb67b378921fb5ad001c4b7a1d4628b92d9.tar.zst bun-ebb42bb67b378921fb5ad001c4b7a1d4628b92d9.zip | |
Add so many more tests, it's not even funny
25 files changed, 5150 insertions, 37 deletions
diff --git a/test/js/deno/abort/abort-controller.test.ts b/test/js/deno/abort/abort-controller.test.ts index 651cadf31..32f33a15d 100644 --- a/test/js/deno/abort/abort-controller.test.ts +++ b/test/js/deno/abort/abort-controller.test.ts @@ -1,6 +1,5 @@ // Copyright 2018+ the Deno authors. All rights reserved. MIT license. // https://raw.githubusercontent.com/denoland/deno/main/cli/tests/unit/abort_controller_test.ts - import { assert, assertEquals } from "deno:harness"; Deno.test(function basicAbortController() { const controller = new AbortController(); diff --git a/test/js/deno/crypto/random.test.ts b/test/js/deno/crypto/random.test.ts new file mode 100644 index 000000000..09258b3f5 --- /dev/null +++ b/test/js/deno/crypto/random.test.ts @@ -0,0 +1,54 @@ +// Copyright 2018+ the Deno authors. All rights reserved. MIT license. +// https://raw.githubusercontent.com/denoland/deno/main/cli/tests/unit/get_random_values_test.ts +import { assertNotEquals, assertStrictEquals } from "deno:harness"; +Deno.test(function getRandomValuesInt8Array() { + const arr = new Int8Array(32); + crypto.getRandomValues(arr); + assertNotEquals(arr, new Int8Array(32)); +}); +Deno.test(function getRandomValuesUint8Array() { + const arr = new Uint8Array(32); + crypto.getRandomValues(arr); + assertNotEquals(arr, new Uint8Array(32)); +}); +Deno.test(function getRandomValuesUint8ClampedArray() { + const arr = new Uint8ClampedArray(32); + crypto.getRandomValues(arr); + assertNotEquals(arr, new Uint8ClampedArray(32)); +}); +Deno.test(function getRandomValuesInt16Array() { + const arr = new Int16Array(4); + crypto.getRandomValues(arr); + assertNotEquals(arr, new Int16Array(4)); +}); +Deno.test(function getRandomValuesUint16Array() { + const arr = new Uint16Array(4); + crypto.getRandomValues(arr); + assertNotEquals(arr, new Uint16Array(4)); +}); +Deno.test(function getRandomValuesInt32Array() { + const arr = new Int32Array(8); + crypto.getRandomValues(arr); + assertNotEquals(arr, new Int32Array(8)); +}); +Deno.test(function getRandomValuesBigInt64Array() { + const arr = new BigInt64Array(8); + crypto.getRandomValues(arr); + assertNotEquals(arr, new BigInt64Array(8)); +}); +Deno.test(function getRandomValuesUint32Array() { + const arr = new Uint32Array(8); + crypto.getRandomValues(arr); + assertNotEquals(arr, new Uint32Array(8)); +}); +Deno.test(function getRandomValuesBigUint64Array() { + const arr = new BigUint64Array(8); + crypto.getRandomValues(arr); + assertNotEquals(arr, new BigUint64Array(8)); +}); +Deno.test(function getRandomValuesReturnValue() { + const arr = new Uint32Array(8); + const rtn = crypto.getRandomValues(arr); + assertNotEquals(arr, new Uint32Array(8)); + assertStrictEquals(rtn, arr); +}); diff --git a/test/js/deno/crypto/webcrypto.test.ts b/test/js/deno/crypto/webcrypto.test.ts new file mode 100644 index 000000000..406892ebd --- /dev/null +++ b/test/js/deno/crypto/webcrypto.test.ts @@ -0,0 +1,2673 @@ +// Copyright 2018+ the Deno authors. All rights reserved. MIT license. +// https://raw.githubusercontent.com/denoland/deno/main/cli/tests/unit/webcrypto_test.ts +import { assert, assertEquals, assertNotEquals, assertRejects } from "deno:harness"; +Deno.test(async function testImportArrayBufferKey() { + const subtle = window.crypto.subtle; + assert(subtle); + const key = new Uint8Array([ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16 + ]); + const cryptoKey = await subtle.importKey("raw", key.buffer, { + name: "HMAC", + hash: "SHA-1" + }, true, [ + "sign" + ]); + assert(cryptoKey); + await subtle.sign({ + name: "HMAC" + }, cryptoKey, new Uint8Array(8)); +}); +Deno.test(async function testSignVerify() { + const subtle = window.crypto.subtle; + assert(subtle); + for (const algorithm of [ + "RSA-PSS", + "RSASSA-PKCS1-v1_5" + ]){ + for (const hash of [ + "SHA-1", + "SHA-256", + "SHA-384", + "SHA-512" + ]){ + const keyPair = await subtle.generateKey({ + name: algorithm, + modulusLength: 2048, + publicExponent: new Uint8Array([ + 1, + 0, + 1 + ]), + hash + }, true, [ + "sign", + "verify" + ]); + const data = new Uint8Array([ + 1, + 2, + 3 + ]); + const signAlgorithm = { + name: algorithm, + saltLength: 32 + }; + const signature = await subtle.sign(signAlgorithm, keyPair.privateKey, data); + assert(signature); + assert(signature.byteLength > 0); + assert(signature.byteLength % 8 == 0); + assert(signature instanceof ArrayBuffer); + const verified = await subtle.verify(signAlgorithm, keyPair.publicKey, signature, data); + assert(verified); + } + } +}); +const plainText = new Uint8Array([ + 95, + 77, + 186, + 79, + 50, + 12, + 12, + 232, + 118, + 114, + 90, + 252, + 229, + 251, + 210, + 91, + 248, + 62, + 90, + 113, + 37, + 160, + 140, + 175, + 231, + 60, + 62, + 186, + 196, + 33, + 119, + 157, + 249, + 213, + 93, + 24, + 12, + 58, + 233, + 148, + 38, + 69, + 225, + 216, + 47, + 238, + 140, + 157, + 41, + 75, + 60, + 177, + 160, + 138, + 153, + 49, + 32, + 27, + 60, + 14, + 129, + 252, + 71, + 202, + 207, + 131, + 21, + 162, + 175, + 102, + 50, + 65, + 19, + 195, + 182, + 98, + 48, + 195, + 70, + 8, + 196, + 244, + 89, + 54, + 52, + 206, + 2, + 178, + 103, + 54, + 34, + 119, + 240, + 168, + 64, + 202, + 116, + 188, + 61, + 26, + 98, + 54, + 149, + 44, + 94, + 215, + 170, + 248, + 168, + 254, + 203, + 221, + 250, + 117, + 132, + 230, + 151, + 140, + 234, + 93, + 42, + 91, + 159, + 183, + 241, + 180, + 140, + 139, + 11, + 229, + 138, + 48, + 82, + 2, + 117, + 77, + 131, + 118, + 16, + 115, + 116, + 121, + 60, + 240, + 38, + 170, + 238, + 83, + 0, + 114, + 125, + 131, + 108, + 215, + 30, + 113, + 179, + 69, + 221, + 178, + 228, + 68, + 70, + 255, + 197, + 185, + 1, + 99, + 84, + 19, + 137, + 13, + 145, + 14, + 163, + 128, + 152, + 74, + 144, + 25, + 16, + 49, + 50, + 63, + 22, + 219, + 204, + 157, + 107, + 225, + 104, + 184, + 72, + 133, + 56, + 76, + 160, + 62, + 18, + 96, + 10, + 193, + 194, + 72, + 2, + 138, + 243, + 114, + 108, + 201, + 52, + 99, + 136, + 46, + 168, + 192, + 42, + 171 +]); +const hashPlainTextVector = [ + { + hash: "SHA-1", + plainText: plainText.slice(0, 214) + }, + { + hash: "SHA-256", + plainText: plainText.slice(0, 190) + }, + { + hash: "SHA-384", + plainText: plainText.slice(0, 158) + }, + { + hash: "SHA-512", + plainText: plainText.slice(0, 126) + } +]; +Deno.test(async function testEncryptDecrypt() { + const subtle = window.crypto.subtle; + assert(subtle); + for (const { hash , plainText } of hashPlainTextVector){ + const keyPair = await subtle.generateKey({ + name: "RSA-OAEP", + modulusLength: 2048, + publicExponent: new Uint8Array([ + 1, + 0, + 1 + ]), + hash + }, true, [ + "encrypt", + "decrypt" + ]); + const encryptAlgorithm = { + name: "RSA-OAEP" + }; + const cipherText = await subtle.encrypt(encryptAlgorithm, keyPair.publicKey, plainText); + assert(cipherText); + assert(cipherText.byteLength > 0); + assertEquals(cipherText.byteLength * 8, 2048); + assert(cipherText instanceof ArrayBuffer); + const decrypted = await subtle.decrypt(encryptAlgorithm, keyPair.privateKey, cipherText); + assert(decrypted); + assert(decrypted instanceof ArrayBuffer); + assertEquals(new Uint8Array(decrypted), plainText); + const badPlainText = new Uint8Array(plainText.byteLength + 1); + badPlainText.set(plainText, 0); + badPlainText.set(new Uint8Array([ + 32 + ]), plainText.byteLength); + await assertRejects(async ()=>{ + await subtle.encrypt(encryptAlgorithm, keyPair.publicKey, badPlainText); + throw new TypeError("unreachable"); + }, DOMException); + } +}); +Deno.test(async function testGenerateRSAKey() { + const subtle = window.crypto.subtle; + assert(subtle); + const keyPair = await subtle.generateKey({ + name: "RSA-PSS", + modulusLength: 2048, + publicExponent: new Uint8Array([ + 1, + 0, + 1 + ]), + hash: "SHA-256" + }, true, [ + "sign", + "verify" + ]); + assert(keyPair.privateKey); + assert(keyPair.publicKey); + assertEquals(keyPair.privateKey.extractable, true); + assert(keyPair.privateKey.usages.includes("sign")); +}); +Deno.test(async function testGenerateHMACKey() { + const key = await window.crypto.subtle.generateKey({ + name: "HMAC", + hash: "SHA-512" + }, true, [ + "sign", + "verify" + ]); + assert(key); + assertEquals(key.extractable, true); + assert(key.usages.includes("sign")); +}); +Deno.test(async function testECDSASignVerify() { + const key = await window.crypto.subtle.generateKey({ + name: "ECDSA", + namedCurve: "P-384" + }, true, [ + "sign", + "verify" + ]); + const encoder = new TextEncoder(); + const encoded = encoder.encode("Hello, World!"); + const signature = await window.crypto.subtle.sign({ + name: "ECDSA", + hash: "SHA-384" + }, key.privateKey, encoded); + assert(signature); + assert(signature instanceof ArrayBuffer); + const verified = await window.crypto.subtle.verify({ + hash: { + name: "SHA-384" + }, + name: "ECDSA" + }, key.publicKey, signature, encoded); + assert(verified); +}); +Deno.test(async function testECDSASignVerifyFail() { + const key = await window.crypto.subtle.generateKey({ + name: "ECDSA", + namedCurve: "P-384" + }, true, [ + "sign", + "verify" + ]); + const encoded = new Uint8Array([ + 1 + ]); + await assertRejects(async ()=>{ + await window.crypto.subtle.sign({ + name: "ECDSA", + hash: "SHA-384" + }, key.publicKey, new Uint8Array([ + 1 + ])); + throw new TypeError("unreachable"); + }, DOMException); + const signature = await window.crypto.subtle.sign({ + name: "ECDSA", + hash: "SHA-384" + }, key.privateKey, encoded); + await assertRejects(async ()=>{ + await window.crypto.subtle.verify({ + hash: { + name: "SHA-384" + }, + name: "ECDSA" + }, key.privateKey, signature, encoded); + throw new TypeError("unreachable"); + }, DOMException); +}); +Deno.test(async function testSignRSASSAKey() { + const subtle = window.crypto.subtle; + assert(subtle); + const keyPair = await subtle.generateKey({ + name: "RSASSA-PKCS1-v1_5", + modulusLength: 2048, + publicExponent: new Uint8Array([ + 1, + 0, + 1 + ]), + hash: "SHA-256" + }, true, [ + "sign", + "verify" + ]); + assert(keyPair.privateKey); + assert(keyPair.publicKey); + assertEquals(keyPair.privateKey.extractable, true); + assert(keyPair.privateKey.usages.includes("sign")); + const encoder = new TextEncoder(); + const encoded = encoder.encode("Hello, World!"); + const signature = await window.crypto.subtle.sign({ + name: "RSASSA-PKCS1-v1_5" + }, keyPair.privateKey, encoded); + assert(signature); +}); +const rawKey = new Uint8Array([ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16 +]); +const jwk: JsonWebKey = { + kty: "oct", + k: "AQIDBAUGBwgJCgsMDQ4PEA", + alg: "HS256", + ext: true, + "key_ops": [ + "sign" + ] +}; +Deno.test(async function subtleCryptoHmacImportExport() { + const key1 = await crypto.subtle.importKey("raw", rawKey, { + name: "HMAC", + hash: "SHA-256" + }, true, [ + "sign" + ]); + const key2 = await crypto.subtle.importKey("jwk", jwk, { + name: "HMAC", + hash: "SHA-256" + }, true, [ + "sign" + ]); + const actual1 = await crypto.subtle.sign({ + name: "HMAC" + }, key1, new Uint8Array([ + 1, + 2, + 3, + 4 + ])); + const actual2 = await crypto.subtle.sign({ + name: "HMAC" + }, key2, new Uint8Array([ + 1, + 2, + 3, + 4 + ])); + const expected = new Uint8Array([ + 59, + 170, + 255, + 216, + 51, + 141, + 51, + 194, + 213, + 48, + 41, + 191, + 184, + 40, + 216, + 47, + 130, + 165, + 203, + 26, + 163, + 43, + 38, + 71, + 23, + 122, + 222, + 1, + 146, + 46, + 182, + 87 + ]); + assertEquals(new Uint8Array(actual1), expected); + assertEquals(new Uint8Array(actual2), expected); + const exportedKey1 = await crypto.subtle.exportKey("raw", key1); + assertEquals(new Uint8Array(exportedKey1), rawKey); + const exportedKey2 = await crypto.subtle.exportKey("jwk", key2); + assertEquals(exportedKey2, jwk); +}); +Deno.test(async function generateImportHmacJwk() { + const key = await crypto.subtle.generateKey({ + name: "HMAC", + hash: "SHA-512" + }, true, [ + "sign" + ]); + assert(key); + assertEquals(key.type, "secret"); + assertEquals(key.extractable, true); + assertEquals(key.usages, [ + "sign" + ]); + const exportedKey = await crypto.subtle.exportKey("jwk", key); + assertEquals(exportedKey.kty, "oct"); + assertEquals(exportedKey.alg, "HS512"); + assertEquals(exportedKey.key_ops, [ + "sign" + ]); + assertEquals(exportedKey.ext, true); + assert(typeof exportedKey.k == "string"); + assertEquals(exportedKey.k.length, 171); +}); +const pkcs8TestVectors = [ + { + pem: "cli/tests/testdata/webcrypto/id_rsaEncryption.pem", + hash: "SHA-256" + } +]; +Deno.test({ + permissions: { + read: true + } +}, async function importRsaPkcs8() { + const pemHeader = "-----BEGIN PRIVATE KEY-----"; + const pemFooter = "-----END PRIVATE KEY-----"; + for (const { pem , hash } of pkcs8TestVectors){ + const keyFile = await Deno.readTextFile(pem); + const pemContents = keyFile.substring(pemHeader.length, keyFile.length - pemFooter.length); + const binaryDerString = atob(pemContents); + const binaryDer = new Uint8Array(binaryDerString.length); + for(let i = 0; i < binaryDerString.length; i++){ + binaryDer[i] = binaryDerString.charCodeAt(i); + } + const key = await crypto.subtle.importKey("pkcs8", binaryDer, { + name: "RSA-PSS", + hash + }, true, [ + "sign" + ]); + assert(key); + assertEquals(key.type, "private"); + assertEquals(key.extractable, true); + assertEquals(key.usages, [ + "sign" + ]); + const algorithm = key.algorithm as RsaHashedKeyAlgorithm; + assertEquals(algorithm.name, "RSA-PSS"); + assertEquals(algorithm.hash.name, hash); + assertEquals(algorithm.modulusLength, 2048); + assertEquals(algorithm.publicExponent, new Uint8Array([ + 1, + 0, + 1 + ])); + } +}); +const nonInteroperableVectors = [ + { + pem: "cli/tests/testdata/webcrypto/id_rsassaPss.pem", + hash: "SHA-256" + }, + { + pem: "cli/tests/testdata/webcrypto/id_rsassaPss_default.pem", + hash: "SHA-1" + }, + { + pem: "cli/tests/testdata/webcrypto/id_rsassaPss_saltLen_30.pem", + hash: "SHA-1" + } +]; +Deno.test({ + permissions: { + read: true + } +}, async function importNonInteroperableRsaPkcs8() { + const pemHeader = "-----BEGIN PRIVATE KEY-----"; + const pemFooter = "-----END PRIVATE KEY-----"; + for (const { pem , hash } of nonInteroperableVectors){ + const keyFile = await Deno.readTextFile(pem); + const pemContents = keyFile.substring(pemHeader.length, keyFile.length - pemFooter.length); + const binaryDerString = atob(pemContents); + const binaryDer = new Uint8Array(binaryDerString.length); + for(let i = 0; i < binaryDerString.length; i++){ + binaryDer[i] = binaryDerString.charCodeAt(i); + } + await assertRejects(()=>crypto.subtle.importKey("pkcs8", binaryDer, { + name: "RSA-PSS", + hash + }, true, [ + "sign" + ]), DOMException, "unsupported algorithm"); + } +}); +const asn1AlgorithmIdentifier = new Uint8Array([ + 0x02, + 0x01, + 0x00, + 0x30, + 0x0d, + 0x06, + 0x09, + 0x2a, + 0x86, + 0x48, + 0x86, + 0xf7, + 0x0d, + 0x01, + 0x01, + 0x01, + 0x05, + 0x00 +]); +Deno.test(async function rsaExport() { + for (const algorithm of [ + "RSASSA-PKCS1-v1_5", + "RSA-PSS", + "RSA-OAEP" + ]){ + const keyPair = await crypto.subtle.generateKey({ + name: algorithm, + modulusLength: 2048, + publicExponent: new Uint8Array([ + 1, + 0, + 1 + ]), + hash: "SHA-256" + }, true, algorithm !== "RSA-OAEP" ? [ + "sign", + "verify" + ] : [ + "encrypt", + "decrypt" + ]); + assert(keyPair.privateKey); + assert(keyPair.publicKey); + assertEquals(keyPair.privateKey.extractable, true); + const exportedPrivateKey = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey); + assert(exportedPrivateKey); + assert(exportedPrivateKey instanceof ArrayBuffer); + const pkcs8 = new Uint8Array(exportedPrivateKey); + assert(pkcs8.length > 0); + assertEquals(pkcs8.slice(4, asn1AlgorithmIdentifier.byteLength + 4), asn1AlgorithmIdentifier); + const exportedPublicKey = await crypto.subtle.exportKey("spki", keyPair.publicKey); + const spki = new Uint8Array(exportedPublicKey); + assert(spki.length > 0); + assertEquals(spki.slice(4, asn1AlgorithmIdentifier.byteLength + 1), asn1AlgorithmIdentifier.slice(3)); + } +}); +Deno.test(async function testHkdfDeriveBits() { + const rawKey = crypto.getRandomValues(new Uint8Array(16)); + const key = await crypto.subtle.importKey("raw", rawKey, { + name: "HKDF", + hash: "SHA-256" + }, false, [ + "deriveBits" + ]); + const salt = crypto.getRandomValues(new Uint8Array(16)); + const info = crypto.getRandomValues(new Uint8Array(16)); + const result = await crypto.subtle.deriveBits({ + name: "HKDF", + hash: "SHA-256", + salt: salt, + info: info + }, key, 128); + assertEquals(result.byteLength, 128 / 8); +}); +Deno.test(async function testHkdfDeriveBitsWithLargeKeySize() { + const key = await crypto.subtle.importKey("raw", new Uint8Array([ + 0x00 + ]), "HKDF", false, [ + "deriveBits" + ]); + await assertRejects(()=>crypto.subtle.deriveBits({ + name: "HKDF", + hash: "SHA-1", + salt: new Uint8Array(), + info: new Uint8Array() + }, key, ((20 * 255) << 3) + 8), DOMException, "The length provided for HKDF is too large"); +}); +Deno.test(async function testEcdhDeriveBitsWithShorterLength() { + const keypair = await crypto.subtle.generateKey({ + name: "ECDH", + namedCurve: "P-384" + }, true, [ + "deriveBits", + "deriveKey" + ]); + const result = await crypto.subtle.deriveBits({ + name: "ECDH", + public: keypair.publicKey + }, keypair.privateKey, 256); + assertEquals(result.byteLength * 8, 256); +}); +Deno.test(async function testEcdhDeriveBitsWithLongerLength() { + const keypair = await crypto.subtle.generateKey({ + name: "ECDH", + namedCurve: "P-384" + }, true, [ + "deriveBits", + "deriveKey" + ]); + await assertRejects(()=>crypto.subtle.deriveBits({ + name: "ECDH", + public: keypair.publicKey + }, keypair.privateKey, 512), DOMException, "Invalid length"); +}); +Deno.test(async function testEcdhDeriveBitsWithNullLength() { + const keypair = await crypto.subtle.generateKey({ + name: "ECDH", + namedCurve: "P-384" + }, true, [ + "deriveBits", + "deriveKey" + ]); + const result = await crypto.subtle.deriveBits({ + name: "ECDH", + public: keypair.publicKey + }, keypair.privateKey, null); + assertEquals(result.byteLength * 8, 384); +}); +Deno.test(async function testDeriveKey() { + const rawKey = crypto.getRandomValues(new Uint8Array(16)); + const key = await crypto.subtle.importKey("raw", rawKey, "PBKDF2", false, [ + "deriveKey", + "deriveBits" + ]); + const salt = crypto.getRandomValues(new Uint8Array(16)); + const derivedKey = await crypto.subtle.deriveKey({ + name: "PBKDF2", + salt, + iterations: 1000, + hash: "SHA-256" + }, key, { + name: "HMAC", + hash: "SHA-256" + }, true, [ + "sign" + ]); + assert(derivedKey instanceof CryptoKey); + assertEquals(derivedKey.type, "secret"); + assertEquals(derivedKey.extractable, true); + assertEquals(derivedKey.usages, [ + "sign" + ]); + const algorithm = derivedKey.algorithm as HmacKeyAlgorithm; + assertEquals(algorithm.name, "HMAC"); + assertEquals(algorithm.hash.name, "SHA-256"); + assertEquals(algorithm.length, 512); +}); +Deno.test(async function testAesCbcEncryptDecrypt() { + const key = await crypto.subtle.generateKey({ + name: "AES-CBC", + length: 128 + }, true, [ + "encrypt", + "decrypt" + ]); + const iv = crypto.getRandomValues(new Uint8Array(16)); + const encrypted = await crypto.subtle.encrypt({ + name: "AES-CBC", + iv + }, key as CryptoKey, new Uint8Array([ + 1, + 2, + 3, + 4, + 5, + 6 + ])); + assert(encrypted instanceof ArrayBuffer); + assertEquals(encrypted.byteLength, 16); + const decrypted = await crypto.subtle.decrypt({ + name: "AES-CBC", + iv + }, key as CryptoKey, encrypted); + assert(decrypted instanceof ArrayBuffer); + assertEquals(decrypted.byteLength, 6); + assertEquals(new Uint8Array(decrypted), new Uint8Array([ + 1, + 2, + 3, + 4, + 5, + 6 + ])); +}); +Deno.test(async function testAesCtrEncryptDecrypt() { + async function aesCtrRoundTrip(key: CryptoKey, counter: Uint8Array, length: number, plainText: Uint8Array) { + const cipherText = await crypto.subtle.encrypt({ + name: "AES-CTR", + counter, + length + }, key, plainText); + assert(cipherText instanceof ArrayBuffer); + assertEquals(cipherText.byteLength, plainText.byteLength); + assertNotEquals(new Uint8Array(cipherText), plainText); + const decryptedText = await crypto.subtle.decrypt({ + name: "AES-CTR", + counter, + length + }, key, cipherText); + assert(decryptedText instanceof ArrayBuffer); + assertEquals(decryptedText.byteLength, plainText.byteLength); + assertEquals(new Uint8Array(decryptedText), plainText); + } + for (const keySize of [ + 128, + 192, + 256 + ]){ + const key = await crypto.subtle.generateKey({ + name: "AES-CTR", + length: keySize + }, true, [ + "encrypt", + "decrypt" + ]) as CryptoKey; + for (const length of [ + 128 + ]){ + const counter = crypto.getRandomValues(new Uint8Array(16)); + await aesCtrRoundTrip(key, counter, length, new Uint8Array([ + 1, + 2, + 3, + 4, + 5, + 6 + ])); + } + for (const length of [ + 32, + 64, + 128 + ]){ + const plaintext1 = crypto.getRandomValues(new Uint8Array(32)); + const counter = new Uint8Array(16); + for(let off = 0; off < 16 - (length / 8); ++off){ + counter[off] = off; + } + const ciphertext1 = await crypto.subtle.encrypt({ + name: "AES-CTR", + counter, + length + }, key, plaintext1); + for(let off = 16 - (length / 8); off < 16; ++off){ + counter[off] = 0xff; + } + const plaintext2 = new Uint8Array(48); + plaintext2.set(plaintext1, 16); + const ciphertext2 = await crypto.subtle.encrypt({ + name: "AES-CTR", + counter, + length + }, key, plaintext2); + assertEquals(new Uint8Array(ciphertext1), new Uint8Array(ciphertext2).slice(16)); + } + } +}); +Deno.test(async function testECDH() { + for (const keySize of [ + 256, + 384 + ]){ + const keyPair = await crypto.subtle.generateKey({ + name: "ECDH", + namedCurve: "P-" + keySize + }, true, [ + "deriveBits" + ]); + const derivedKey = await crypto.subtle.deriveBits({ + name: "ECDH", + public: keyPair.publicKey + }, keyPair.privateKey, keySize); + assert(derivedKey instanceof ArrayBuffer); + assertEquals(derivedKey.byteLength, keySize / 8); + } +}); +Deno.test(async function testWrapKey() { + const key = await crypto.subtle.generateKey({ + name: "RSA-OAEP", + modulusLength: 4096, + publicExponent: new Uint8Array([ + 1, + 0, + 1 + ]), + hash: "SHA-256" + }, true, [ + "wrapKey", + "unwrapKey" + ]); + const hmacKey = await crypto.subtle.generateKey({ + name: "HMAC", + hash: "SHA-256", + length: 128 + }, true, [ + "sign" + ]); + const wrappedKey = await crypto.subtle.wrapKey("raw", hmacKey, key.publicKey, { + name: "RSA-OAEP", + label: new Uint8Array(8) + }); + assert(wrappedKey instanceof ArrayBuffer); + assertEquals(wrappedKey.byteLength, 512); +}); +Deno.test(async function testAesKeyGen() { + const key = await crypto.subtle.generateKey({ + name: "AES-GCM", + length: 256 + }, true, [ + "encrypt", + "decrypt" + ]); + assert(key); + assertEquals(key.type, "secret"); + assertEquals(key.extractable, true); + assertEquals(key.usages, [ + "encrypt", + "decrypt" + ]); + const algorithm = key.algorithm as AesKeyAlgorithm; + assertEquals(algorithm.name, "AES-GCM"); + assertEquals(algorithm.length, 256); +}); +Deno.test(async function testUnwrapKey() { + const subtle = crypto.subtle; + const AES_KEY: AesKeyAlgorithm & AesCbcParams = { + name: "AES-CBC", + length: 128, + iv: new Uint8Array(16) + }; + const RSA_KEY: RsaHashedKeyGenParams & RsaOaepParams = { + name: "RSA-OAEP", + modulusLength: 2048, + publicExponent: new Uint8Array([ + 1, + 0, + 1 + ]), + hash: "SHA-1" + }; + const aesKey = await subtle.generateKey(AES_KEY, true, [ + "encrypt", + "decrypt" + ]); + const rsaKeyPair = await subtle.generateKey({ + name: "RSA-OAEP", + hash: "SHA-1", + publicExponent: new Uint8Array([ + 1, + 0, + 1 + ]), + modulusLength: 2048 + }, false, [ + "wrapKey", + "encrypt", + "unwrapKey", + "decrypt" + ]); + const enc = await subtle.wrapKey("raw", aesKey, rsaKeyPair.publicKey, RSA_KEY); + const unwrappedKey = await subtle.unwrapKey("raw", enc, rsaKeyPair.privateKey, RSA_KEY, AES_KEY, false, [ + "encrypt", + "decrypt" + ]); + assert(unwrappedKey instanceof CryptoKey); + assertEquals(unwrappedKey.type, "secret"); + assertEquals(unwrappedKey.extractable, false); + assertEquals(unwrappedKey.usages, [ + "encrypt", + "decrypt" + ]); +}); +Deno.test(async function testDecryptWithInvalidIntializationVector() { + const data = new Uint8Array([ + 42, + 42, + 42, + 42, + 42, + 42, + 42, + 42, + 42, + 42, + 42, + 42, + 42, + 42, + 42 + ]); + const key = await crypto.subtle.importKey("raw", new Uint8Array(16), { + name: "AES-CBC", + length: 256 + }, true, [ + "encrypt", + "decrypt" + ]); + const initVector = new Uint8Array([ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15 + ]); + const encrypted = await crypto.subtle.encrypt({ + name: "AES-CBC", + iv: initVector + }, key, data); + const initVector2 = new Uint8Array([ + 15, + 14, + 13, + 12, + 11, + 10, + 9, + 8, + 7, + 6, + 5, + 4, + 3, + 2, + 1, + 0 + ]); + await assertRejects(async ()=>{ + await crypto.subtle.decrypt({ + name: "AES-CBC", + iv: initVector2 + }, key, encrypted); + }, DOMException); +}); +const jwtRSAKeys = { + "1024": { + size: 1024, + publicJWK: { + kty: "RSA", + n: "zZn4sRGfjQos56yL_Qy1R9NI-THMnFynn94g5RxA6wGrJh4BJT3x6I9x0IbpS3q-d4ORA6R2vuDMh8dDFRr9RDH6XY-gUScc9U5Jz3UA2KmVfsCbnUPvcAmMV_ENA7_TF0ivVjuIFodyDTx7EKHNVTrHHSlrbt7spbmcivs23Zc", + e: "AQAB" + }, + privateJWK: { + kty: "RSA", + n: "zZn4sRGfjQos56yL_Qy1R9NI-THMnFynn94g5RxA6wGrJh4BJT3x6I9x0IbpS3q-d4ORA6R2vuDMh8dDFRr9RDH6XY-gUScc9U5Jz3UA2KmVfsCbnUPvcAmMV_ENA7_TF0ivVjuIFodyDTx7EKHNVTrHHSlrbt7spbmcivs23Zc", + e: "AQAB", + d: "YqIK_GdH85F-GWZdgfgmv15NE78gOaL5h2g4v7DeM9-JC7A5PHSLKNYn87HFGcC4vv0PBIBRtyCA_mJJfEaGWORVCOXSBpWNepMYpio52n3w5uj5UZEsBnbtZc0EtWhVF2Auqa7VbiKrWcQUEgEI8V0gE5D4tyBg8GXv9975dQE", + p: "9BrAg5L1zfqGPuWJDuDCBX-TmtZdrOI3Ys4ZaN-yMPlTjwWSEPO0qnfjEZcw2VgXHgJJmbVco6TxckJCmEYqeQ", + q: "157jDJ1Ya5nmQvTPbhKAPAeMWogxCyaQTkBrp30pEKd6mGSB385hqr4BIk8s3f7MdXpM-USpaZgUoT4o_2VEjw", + dp: "qdd_QUzcaB-6jkKo1Ug-1xKIAgDLFsIjJUUfWt_iHL8ti2Kl2dOnTcCypgebPm5TT1bqHN-agGYAdK5zpX2UiQ", + dq: "hNRfwOSplNfhLvxLUN7a2qA3yYm-1MSz_1DWQP7srlLORlUcYPht2FZmsnEeDcAqynBGPQUcbG2Av_hgHz2OZw", + qi: "zbpJQAhinrxSbVKxBQ2EZGFUD2e3WCXbAJRYpk8HVQ5AA52OhKTicOye2hEHnrgpFKzC8iznTsCG3FMkvwcj4Q" + } + }, + "2048": { + size: 2048, + publicJWK: { + kty: "RSA", + n: "09eVwAhT9SPBxdEN-74BBeEANGaVGwqH-YglIc4VV7jfhR2by5ivzVq8NCeQ1_ACDIlTDY8CTMQ5E1c1SEXmo_T7q84XUGXf8U9mx6uRg46sV7fF-hkwJR80BFVsvWxp4ahPlVJYj__94ft7rIVvchb5tyalOjrYFCJoFnSgq-i3ZjU06csI9XnO5klINucD_Qq0vUhO23_Add2HSYoRjab8YiJJR_Eths7Pq6HHd2RSXmwYp5foRnwe0_U75XmesHWDJlJUHYbwCZo0kP9G8g4QbucwU-MSNBkZOO2x2ZtZNexpHd0ThkATbnNlpVG_z2AGNORp_Ve3rlXwrGIXXw", + e: "AQAB" + }, + privateJWK: { + kty: "RSA", + n: "09eVwAhT9SPBxdEN-74BBeEANGaVGwqH-YglIc4VV7jfhR2by5ivzVq8NCeQ1_ACDIlTDY8CTMQ5E1c1SEXmo_T7q84XUGXf8U9mx6uRg46sV7fF-hkwJR80BFVsvWxp4ahPlVJYj__94ft7rIVvchb5tyalOjrYFCJoFnSgq-i3ZjU06csI9XnO5klINucD_Qq0vUhO23_Add2HSYoRjab8YiJJR_Eths7Pq6HHd2RSXmwYp5foRnwe0_U75XmesHWDJlJUHYbwCZo0kP9G8g4QbucwU-MSNBkZOO2x2ZtZNexpHd0ThkATbnNlpVG_z2AGNORp_Ve3rlXwrGIXXw", + e: "AQAB", + d: "H4xboN2co0VP9kXL71G8lUOM5EDis8Q9u8uqu_4U75t4rjpamVeD1vFMVfgOehokM_m_hKVnkkcmuNqj9L90ObaiRFPM5QxG7YkFpXbHlPAKeoXD1hsqMF0VQg_2wb8DhberInHA_rEA_kaVhHvavQLu7Xez45gf1d_J4I4931vjlCB6cupbLL0H5hHsxbMsX_5nnmAJdL_U3gD-U7ZdQheUPhDBJR2KeGzvnTm3KVKpOnwn-1Cd45MU4-KDdP0FcBVEuBsSrsQHliTaciBgkbyj__BangPj3edDxTkb-fKkEvhkXRjAoJs1ixt8nfSGDce9cM_GqAX9XGb4s2QkAQ", + dp: "mM82RBwzGzi9LAqjGbi-badLtHRRBoH9sfMrJuOtzxRnmwBFccg_lwy-qAhUTqnN9kvD0H1FzXWzoFPFJbyi-AOmumYGpWm_PvzQGldne5CPJ02pYaeg-t1BePsT3OpIq0Am8E2Kjf9polpRJwIjO7Kx8UJKkhg5bISnsy0V8wE", + dq: "ZlM4AvrWIpXwqsH_5Q-6BsLJdbnN_GypFCXoT9VXniXncSBZIWCkgDndBdWkSzyzIN65NiMRBfZaf9yduTFj4kvOPwb3ch3J0OxGJk0Ary4OGSlS1zNwMl93ALGal1FzpWUuiia9L9RraGqXAUr13L7TIIMRobRjpAV-z7M-ruM", + p: "7VwGt_tJcAFQHrmDw5dM1EBru6fidM45NDv6VVOEbxKuD5Sh2EfAHfm5c6oouA1gZqwvKH0sn_XpB1NsyYyHEQd3sBVdK0zRjTo-E9mRP-1s-LMd5YDXVq6HE339nxpXsmO25slQEF6zBrj1bSNNXBFc7fgDnlq-HIeleMvsY_E", + q: "5HqMHLzb4IgXhUl4pLz7E4kjY8PH2YGzaQfK805zJMbOXzmlZK0hizKo34Qqd2nB9xos7QgzOYQrNfSWheARwVsSQzAE0vGvw3zHIPP_lTtChBlCTPctQcURjw4dXcnK1oQ-IT321FNOW3EO-YTsyGcypJqJujlZrLbxYjOjQE8", + qi: "OQXzi9gypDnpdHatIi0FaUGP8LSzfVH0AUugURJXs4BTJpvA9y4hcpBQLrcl7H_vq6kbGmvC49V-9I5HNVX_AuxGIXKuLZr5WOxPq8gLTqHV7X5ZJDtWIP_nq2NNgCQQyNNRrxebiWlwGK9GnX_unewT6jopI_oFhwp0Q13rBR0" + } + }, + "4096": { + size: 4096, + publicJWK: { + kty: "RSA", + n: "2qr2TL2c2JmbsN0OLIRnaAB_ZKb1-Gh9H0qb4lrBuDaqkW_eFPwT-JIsvnNJvDT7BLJ57tTMIj56ZMtv6efSSTWSk9MOoW2J1K_iEretZ2cegB_aRX7qQVjnoFsz9U02BKfAIUT0o_K7b9G08d1rrAUohi_SVQhwObodg7BddMbKUmz70QNIS487LN44WUVnn9OgE9atTYUARNukT0DuQb3J-K20ksTuVujXbSelohDmLobqlGoi5sY_548Qs9BtFmQ2nGuEHNB2zdlZ5EvEqbUFVZ2QboG6jXdoos6qcwdgUvAhj1Hz10Ngic_RFqL7bNDoIOzNp66hdA35uxbwuaygZ16ikxoPj7eTYud1hrkyQCgeGw2YhCiKIE6eos_U5dL7WHRD5aSkkzsgXtnF8pVmStsuf0QcdAoC-eeCex0tSTgRw9AtGTz8Yr1tGQD9l_580zAXnE6jmrwRRQ68EEA7vohGov3tnG8pGyg_zcxeADLtPlfTc1tEwmh3SGrioDClioYCipm1JvkweEgP9eMPpEC8SgRU1VNDSVe1SF4uNsH8vA7PHFKfg6juqJEc5ht-l10FYER-Qq6bZXsU2oNcfE5SLDeLTWmxiHmxK00M8ABMFIV5gUkPoMiWcl87O6XwzA2chsIERp7Vb-Vn2O-EELiXzv7lPhc6fTGQ0Nc", + e: "AQAB" + }, + privateJWK: { + kty: "RSA", + n: "2qr2TL2c2JmbsN0OLIRnaAB_ZKb1-Gh9H0qb4lrBuDaqkW_eFPwT-JIsvnNJvDT7BLJ57tTMIj56ZMtv6efSSTWSk9MOoW2J1K_iEretZ2cegB_aRX7qQVjnoFsz9U02BKfAIUT0o_K7b9G08d1rrAUohi_SVQhwObodg7BddMbKUmz70QNIS487LN44WUVnn9OgE9atTYUARNukT0DuQb3J-K20ksTuVujXbSelohDmLobqlGoi5sY_548Qs9BtFmQ2nGuEHNB2zdlZ5EvEqbUFVZ2QboG6jXdoos6qcwdgUvAhj1Hz10Ngic_RFqL7bNDoIOzNp66hdA35uxbwuaygZ16ikxoPj7eTYud1hrkyQCgeGw2YhCiKIE6eos_U5dL7WHRD5aSkkzsgXtnF8pVmStsuf0QcdAoC-eeCex0tSTgRw9AtGTz8Yr1tGQD9l_580zAXnE6jmrwRRQ68EEA7vohGov3tnG8pGyg_zcxeADLtPlfTc1tEwmh3SGrioDClioYCipm1JvkweEgP9eMPpEC8SgRU1VNDSVe1SF4uNsH8vA7PHFKfg6juqJEc5ht-l10FYER-Qq6bZXsU2oNcfE5SLDeLTWmxiHmxK00M8ABMFIV5gUkPoMiWcl87O6XwzA2chsIERp7Vb-Vn2O-EELiXzv7lPhc6fTGQ0Nc", + e: "AQAB", + d: "uXPRXBhcE5-DWabBRKQuhxgU8ype5gTISWefeYP7U96ZHqu_sBByZ5ihdgyU9pgAZGVx4Ep9rnVKnH2lNr2zrP9Qhyqy99nM0aMxmypIWLAuP__DwLj4t99M4sU29c48CAq1egHfccSFjzpNuetOTCA71EJuokt70pm0OmGzgTyvjuR7VTLxd5PMXitBowSn8_cphmnFpT8tkTiuy8CH0R3DU7MOuINomDD1s8-yPBcVAVTPUnwJiauNuzestLQKMLlhT5wn-cAbYk36XRKdgkjSc2AkhHRl4WDqT1nzWYdh_DVIYSLiKSktkPO9ovMrRYiPtozfhl0m9SR9Ll0wXtcnnDlWXc_MSGpw18vmUBSJ4PIhkiFsvLn-db3wUkA8uve-iqqfk0sxlGWughWx03kGmZDmprWbXugCBHfsI4X93w4exznXH_tapxPnmjbhVUQR6p41MvO2lcHWPLwGJgLIoejBHpnn3TmMN0UjFZki7q9B_dJ3fXh0mX9DzAlC0sil1NgCPhMPq02393_giinQquMknrBvgKxGSfGUrDKuflCx611ZZlRM3R7YMX2OIy1g4DyhPzBVjxRMtm8PnIs3m3Hi-O-C_PHF93w9J8Wqd0yIw7SpavDqZXLPC6Cqi8K7MBZyVECXHtRj1bBqT-h_xZmFCDjSU0NqfOdgApE", + p: "9NrXwq4kY9kBBOwLoFZVQc4kJI_NbKa_W9FLdQdRIbMsZZHXJ3XDUR9vJAcaaR75WwIC7X6N55nVtWTq28Bys9flJ9RrCTfciOntHEphBhYaL5ZTUl-6khYmsOf_psff2VaOOCvHGff5ejuOmBQxkw2E-cv7knRgWFHoLWpku2NJIMuGHt9ks7OAUfIZVYl9YJnw4FYUzhgaxemknjLeZ8XTkGW2zckzF-d95YI9i8zD80Umubsw-YxriSfqFQ0rGHBsbQ8ZOTd_KJju42BWnXIjNDYmjFUqdzVjI4XQ8EGrCEf_8_iwphGyXD7LOJ4fqd97B3bYpoRTPnCgY_SEHQ", + q: "5J758_NeKr1XPZiLxXohYQQnh0Lb4QtGZ1xzCgjhBQLcIBeTOG_tYjCues9tmLt93LpJfypSJ-SjDLwkR2s069_IByYGpxyeGtV-ulqYhSw1nD2CXKMDGyO5jXDs9tJrS_UhfobXKQH03CRdFugyPkSNmXY-AafFynG7xLr7oYBC05FnhUXPm3VBTPt9K-BpqwYd_h9vkAWeprSPo83UlwcLMupSJY9LaHxhRdz2yi0ZKNwXXHRwcszGjDBvvzUcCYbqWqjzbEvFY6KtH8Jh4LhM46rHaoEOTernJsDF6a6W8Df88RthqTExcwnaQf0O_dlbjSxEIPfbxx8t1EQugw", + dp: "4Y7Hu5tYAnLhMXuQqj9dgqU3PkcKYdCp7xc6f7Ah2P2JJHfYz4z4RD7Ez1eLyNKzulZ8A_PVHUjlSZiRkaYTBAEaJDrV70P6cFWuC6WpA0ZREQ1V7EgrQnANbGILa8QsPbYyhSQu4YlB1IwQq5_OmzyVBtgWA7AZIMMzMsMT0FuB_if-gWohBjmRN-vh0p45VUf6UW568-_YmgDFmMYbg1UFs7s_TwrNenPR0h7MO4CB8hP9vJLoZrooRczzIjljPbwy5bRG9CJfjTJ0vhj9MUT3kR1hHV1HJVGU5iBbfTfBKnvJGSI6-IDM4ZUm-B0R5hbs6s9cfOjhFmACIJIbMQ", + dq: "gT4iPbfyHyVEwWyQb4X4grjvg7bXSKSwG1SXMDAOzV9tg7LwJjKYNy8gJAtJgNNVdsfVLs-E_Epzpoph1AIWO9YZZXkov6Yc9zyEVONMX9S7ReU74hTBd8E9b2lMfMg9ogYk9jtSPTt-6kigW4fOh4cHqZ6_tP3cgfLD3JZ8FDPHE4WaySvLDq49yUBO5dQKyIU_xV6OGhQjOUjP_yEoMmzn9tOittsIHTxbXTxqQ6c1FvU9O6YTv8Jl5_Cl66khfX1I1RG38xvurcHULyUbYgeuZ_Iuo9XreT73h9_owo9RguGT29XH4vcNZmRGf5GIvRb4e5lvtleIZkwJA3u78w", + qi: "JHmVKb1zwW5iRR6RCeexYnh2fmY-3DrPSdM8Dxhr0F8dayi-tlRqEdnG0hvp45n8gLUskWWcB9EXlUJObZGKDfGuxgMa3g_xeLA2vmFQ12MxPsyH4iCNZvsgmGxx7TuOHrnDh5EBVnM4_de63crEJON2sYI8Ozi-xp2OEmAr2seWKq4sxkFni6exLhqb-NE4m9HMKlng1EtQh2rLBFG1VYD3SYYpMLc5fxzqGvSxn3Fa-Xgg-IZPY3ubrcm52KYgmLUGmnYStfVqGSWSdhDXHlNgI5pdAA0FzpyBk3ZX-JsxhwcnneKrYBBweq06kRMGWgvdbdAQ-7wSeGqqj5VPwA" + } + } +}; +Deno.test(async function testImportRsaJwk() { + const subtle = window.crypto.subtle; + assert(subtle); + for (const [_key, jwkData] of Object.entries(jwtRSAKeys)){ + const { size , publicJWK , privateJWK } = jwkData; + if (size < 2048) { + continue; + } + for (const hash of [ + "SHA-1", + "SHA-256", + "SHA-384", + "SHA-512" + ]){ + const hashMapPSS: Record<string, string> = { + "SHA-1": "PS1", + "SHA-256": "PS256", + "SHA-384": "PS384", + "SHA-512": "PS512" + }; + if (size == 1024 && hash == "SHA-512") { + continue; + } + const privateKeyPSS = await crypto.subtle.importKey("jwk", { + alg: hashMapPSS[hash], + ...privateJWK, + ext: true, + "key_ops": [ + "sign" + ] + }, { + name: "RSA-PSS", + hash + }, true, [ + "sign" + ]); + const publicKeyPSS = await crypto.subtle.importKey("jwk", { + alg: hashMapPSS[hash], + ...publicJWK, + ext: true, + "key_ops": [ + "verify" + ] + }, { + name: "RSA-PSS", + hash + }, true, [ + "verify" + ]); + const signaturePSS = await crypto.subtle.sign({ + name: "RSA-PSS", + saltLength: 32 + }, privateKeyPSS, new Uint8Array([ + 1, + 2, + 3, + 4 + ])); + const verifyPSS = await crypto.subtle.verify({ + name: "RSA-PSS", + saltLength: 32 + }, publicKeyPSS, signaturePSS, new Uint8Array([ + 1, + 2, + 3, + 4 + ])); + assert(verifyPSS); + } + for (const hash of [ + "SHA-1", + "SHA-256", + "SHA-384", + "SHA-512" + ]){ + const hashMapPKCS1: Record<string, string> = { + "SHA-1": "RS1", + "SHA-256": "RS256", + "SHA-384": "RS384", + "SHA-512": "RS512" + }; + if (size == 1024 && hash == "SHA-512") { + continue; + } + const privateKeyPKCS1 = await crypto.subtle.importKey("jwk", { + alg: hashMapPKCS1[hash], + ...privateJWK, + ext: true, + "key_ops": [ + "sign" + ] + }, { + name: "RSASSA-PKCS1-v1_5", + hash + }, true, [ + "sign" + ]); + const publicKeyPKCS1 = await crypto.subtle.importKey("jwk", { + alg: hashMapPKCS1[hash], + ...publicJWK, + ext: true, + "key_ops": [ + "verify" + ] + }, { + name: "RSASSA-PKCS1-v1_5", + hash + }, true, [ + "verify" + ]); + const signaturePKCS1 = await crypto.subtle.sign({ + name: "RSASSA-PKCS1-v1_5", + saltLength: 32 + }, privateKeyPKCS1, new Uint8Array([ + 1, + 2, + 3, + 4 + ])); + const verifyPKCS1 = await crypto.subtle.verify({ + name: "RSASSA-PKCS1-v1_5", + saltLength: 32 + }, publicKeyPKCS1, signaturePKCS1, new Uint8Array([ + 1, + 2, + 3, + 4 + ])); + assert(verifyPKCS1); + } + for (const { hash , plainText } of hashPlainTextVector){ + const hashMapOAEP: Record<string, string> = { + "SHA-1": "RSA-OAEP", + "SHA-256": "RSA-OAEP-256", + "SHA-384": "RSA-OAEP-384", + "SHA-512": "RSA-OAEP-512" + }; + if (size == 1024 && hash == "SHA-512") { + continue; + } + const encryptAlgorithm = { + name: "RSA-OAEP" + }; + const privateKeyOAEP = await crypto.subtle.importKey("jwk", { + alg: hashMapOAEP[hash], + ...privateJWK, + ext: true, + "key_ops": [ + "decrypt" + ] + }, { + ...encryptAlgorithm, + hash + }, true, [ + "decrypt" + ]); + const publicKeyOAEP = await crypto.subtle.importKey("jwk", { + alg: hashMapOAEP[hash], + ...publicJWK, + ext: true, + "key_ops": [ + "encrypt" + ] + }, { + ...encryptAlgorithm, + hash + }, true, [ + "encrypt" + ]); + const cipherText = await subtle.encrypt(encryptAlgorithm, publicKeyOAEP, plainText); + assert(cipherText); + assert(cipherText.byteLength > 0); + assertEquals(cipherText.byteLength * 8, size); + assert(cipherText instanceof ArrayBuffer); + const decrypted = await subtle.decrypt(encryptAlgorithm, privateKeyOAEP, cipherText); + assert(decrypted); + assert(decrypted instanceof ArrayBuffer); + assertEquals(new Uint8Array(decrypted), plainText); + } + } +}); +const jwtECKeys = { + "256": { + size: 256, + algo: "ES256", + publicJWK: { + kty: "EC", + crv: "P-256", + x: "0hCwpvnZ8BKGgFi0P6T0cQGFQ7ugDJJQ35JXwqyuXdE", + y: "zgN1UtSBRQzjm00QlXAbF1v6s0uObAmeGPHBmDWDYeg" + }, + privateJWK: { + kty: "EC", + crv: "P-256", + x: "0hCwpvnZ8BKGgFi0P6T0cQGFQ7ugDJJQ35JXwqyuXdE", + y: "zgN1UtSBRQzjm00QlXAbF1v6s0uObAmeGPHBmDWDYeg", + d: "E9M6LVq_nPnrsh_4YNSu_m5W53eQ9N7ptAiE69M1ROo" + } + }, + "384": { + size: 384, + algo: "ES384", + publicJWK: { + kty: "EC", + crv: "P-384", + x: "IZwU1mYXs27G2IVrOFtzp000T9iude8EZDXdpU47RL1fvevR0I3Wni19wdwhjLQ1", + y: "vSgTjMd4M3qEL2vWGyQOdCSfJGZ8KlgQp2v8KOAzX4imUB3sAZdtqFr7AIactqzo" + }, + privateJWK: { + kty: "EC", + crv: "P-384", + x: "IZwU1mYXs27G2IVrOFtzp000T9iude8EZDXdpU47RL1fvevR0I3Wni19wdwhjLQ1", + y: "vSgTjMd4M3qEL2vWGyQOdCSfJGZ8KlgQp2v8KOAzX4imUB3sAZdtqFr7AIactqzo", + d: "RTe1mQeE08LSLpao-S-hqkku6HPldqQVguFEGDyYiNEOa560ztSyzEAS5KxeqEBz" + } + } +}; +type JWK = Record<string, string>; +function equalJwk(expected: JWK, got: JWK): boolean { + const fields = Object.keys(expected); + for(let i = 0; i < fields.length; i++){ + const fieldName = fields[i]; + if (!(fieldName in got)) { + return false; + } + if (expected[fieldName] !== got[fieldName]) { + return false; + } + } + return true; +} +Deno.test(async function testImportExportEcDsaJwk() { + const subtle = crypto.subtle; + assert(subtle); + for (const [_key, keyData] of Object.entries(jwtECKeys)){ + const { publicJWK , privateJWK , algo } = keyData; + const privateKeyECDSA = await subtle.importKey("jwk", { + alg: algo, + ...privateJWK, + ext: true, + "key_ops": [ + "sign" + ] + }, { + name: "ECDSA", + namedCurve: privateJWK.crv + }, true, [ + "sign" + ]); + const expPrivateKeyJWK = await subtle.exportKey("jwk", privateKeyECDSA); + assert(equalJwk(privateJWK, expPrivateKeyJWK as JWK)); + const publicKeyECDSA = await subtle.importKey("jwk", { + alg: algo, + ...publicJWK, + ext: true, + "key_ops": [ + "verify" + ] + }, { + name: "ECDSA", + namedCurve: publicJWK.crv + }, true, [ + "verify" + ]); + const expPublicKeyJWK = await subtle.exportKey("jwk", publicKeyECDSA); + assert(equalJwk(publicJWK, expPublicKeyJWK as JWK)); + const signatureECDSA = await subtle.sign({ + name: "ECDSA", + hash: `SHA-${keyData.size}` + }, privateKeyECDSA, new Uint8Array([ + 1, + 2, + 3, + 4 + ])); + const verifyECDSA = await subtle.verify({ + name: "ECDSA", + hash: `SHA-${keyData.size}` + }, publicKeyECDSA, signatureECDSA, new Uint8Array([ + 1, + 2, + 3, + 4 + ])); + assert(verifyECDSA); + } +}); +Deno.test(async function testImportEcDhJwk() { + const subtle = crypto.subtle; + assert(subtle); + for (const [_key, jwkData] of Object.entries(jwtECKeys)){ + const { size , publicJWK , privateJWK } = jwkData; + const privateKeyECDH = await subtle.importKey("jwk", { + ...privateJWK, + ext: true, + "key_ops": [ + "deriveBits" + ] + }, { + name: "ECDH", + namedCurve: privateJWK.crv + }, true, [ + "deriveBits" + ]); + const expPrivateKeyJWK = await subtle.exportKey("jwk", privateKeyECDH); + assert(equalJwk(privateJWK, expPrivateKeyJWK as JWK)); + const publicKeyECDH = await subtle.importKey("jwk", { + ...publicJWK, + ext: true, + "key_ops": [] + }, { + name: "ECDH", + namedCurve: publicJWK.crv + }, true, []); + const expPublicKeyJWK = await subtle.exportKey("jwk", publicKeyECDH); + assert(equalJwk(publicJWK, expPublicKeyJWK as JWK)); + const derivedKey = await subtle.deriveBits({ + name: "ECDH", + public: publicKeyECDH + }, privateKeyECDH, size); + assert(derivedKey instanceof ArrayBuffer); + assertEquals(derivedKey.byteLength, size / 8); + } +}); +const ecTestKeys = [ + { + size: 256, + namedCurve: "P-256", + signatureLength: 64, + raw: new Uint8Array([ + 4, + 210, + 16, + 176, + 166, + 249, + 217, + 240, + 18, + 134, + 128, + 88, + 180, + 63, + 164, + 244, + 113, + 1, + 133, + 67, + 187, + 160, + 12, + 146, + 80, + 223, + 146, + 87, + 194, + 172, + 174, + 93, + 209, + 206, + 3, + 117, + 82, + 212, + 129, + 69, + 12, + 227, + 155, + 77, + 16, + 149, + 112, + 27, + 23, + 91, + 250, + 179, + 75, + 142, + 108, + 9, + 158, + 24, + 241, + 193, + 152, + 53, + 131, + 97, + 232 + ]), + spki: new Uint8Array([ + 48, + 89, + 48, + 19, + 6, + 7, + 42, + 134, + 72, + 206, + 61, + 2, + 1, + 6, + 8, + 42, + 134, + 72, + 206, + 61, + 3, + 1, + 7, + 3, + 66, + 0, + 4, + 210, + 16, + 176, + 166, + 249, + 217, + 240, + 18, + 134, + 128, + 88, + 180, + 63, + 164, + 244, + 113, + 1, + 133, + 67, + 187, + 160, + 12, + 146, + 80, + 223, + 146, + 87, + 194, + 172, + 174, + 93, + 209, + 206, + 3, + 117, + 82, + 212, + 129, + 69, + 12, + 227, + 155, + 77, + 16, + 149, + 112, + 27, + 23, + 91, + 250, + 179, + 75, + 142, + 108, + 9, + 158, + 24, + 241, + 193, + 152, + 53, + 131, + 97, + 232 + ]), + pkcs8: new Uint8Array([ + 48, + 129, + 135, + 2, + 1, + 0, + 48, + 19, + 6, + 7, + 42, + 134, + 72, + 206, + 61, + 2, + 1, + 6, + 8, + 42, + 134, + 72, + 206, + 61, + 3, + 1, + 7, + 4, + 109, + 48, + 107, + 2, + 1, + 1, + 4, + 32, + 19, + 211, + 58, + 45, + 90, + 191, + 156, + 249, + 235, + 178, + 31, + 248, + 96, + 212, + 174, + 254, + 110, + 86, + 231, + 119, + 144, + 244, + 222, + 233, + 180, + 8, + 132, + 235, + 211, + 53, + 68, + 234, + 161, + 68, + 3, + 66, + 0, + 4, + 210, + 16, + 176, + 166, + 249, + 217, + 240, + 18, + 134, + 128, + 88, + 180, + 63, + 164, + 244, + 113, + 1, + 133, + 67, + 187, + 160, + 12, + 146, + 80, + 223, + 146, + 87, + 194, + 172, + 174, + 93, + 209, + 206, + 3, + 117, + 82, + 212, + 129, + 69, + 12, + 227, + 155, + 77, + 16, + 149, + 112, + 27, + 23, + 91, + 250, + 179, + 75, + 142, + 108, + 9, + 158, + 24, + 241, + 193, + 152, + 53, + 131, + 97, + 232 + ]) + }, + { + size: 384, + namedCurve: "P-384", + signatureLength: 96, + raw: new Uint8Array([ + 4, + 118, + 64, + 176, + 165, + 100, + 177, + 112, + 49, + 254, + 58, + 53, + 158, + 63, + 73, + 200, + 148, + 248, + 242, + 216, + 186, + 80, + 92, + 160, + 53, + 64, + 232, + 157, + 19, + 1, + 12, + 226, + 115, + 51, + 42, + 143, + 98, + 206, + 55, + 220, + 108, + 78, + 24, + 71, + 157, + 21, + 120, + 126, + 104, + 157, + 86, + 48, + 226, + 110, + 96, + 52, + 48, + 77, + 170, + 9, + 231, + 159, + 26, + 165, + 200, + 26, + 164, + 99, + 46, + 227, + 169, + 105, + 172, + 225, + 60, + 102, + 141, + 145, + 139, + 165, + 47, + 72, + 53, + 17, + 17, + 246, + 161, + 220, + 26, + 21, + 23, + 219, + 1, + 107, + 185, + 163, + 215 + ]), + spki: new Uint8Array([ + 48, + 118, + 48, + 16, + 6, + 7, + 42, + 134, + 72, + 206, + 61, + 2, + 1, + 6, + 5, + 43, + 129, + 4, + 0, + 34, + 3, + 98, + 0, + 4, + 118, + 64, + 176, + 165, + 100, + 177, + 112, + 49, + 254, + 58, + 53, + 158, + 63, + 73, + 200, + 148, + 248, + 242, + 216, + 186, + 80, + 92, + 160, + 53, + 64, + 232, + 157, + 19, + 1, + 12, + 226, + 115, + 51, + 42, + 143, + 98, + 206, + 55, + 220, + 108, + 78, + 24, + 71, + 157, + 21, + 120, + 126, + 104, + 157, + 86, + 48, + 226, + 110, + 96, + 52, + 48, + 77, + 170, + 9, + 231, + 159, + 26, + 165, + 200, + 26, + 164, + 99, + 46, + 227, + 169, + 105, + 172, + 225, + 60, + 102, + 141, + 145, + 139, + 165, + 47, + 72, + 53, + 17, + 17, + 246, + 161, + 220, + 26, + 21, + 23, + 219, + 1, + 107, + 185, + 163, + 215 + ]), + pkcs8: new Uint8Array([ + 48, + 129, + 182, + 2, + 1, + 0, + 48, + 16, + 6, + 7, + 42, + 134, + 72, + 206, + 61, + 2, + 1, + 6, + 5, + 43, + 129, + 4, + 0, + 34, + 4, + 129, + 158, + 48, + 129, + 155, + 2, + 1, + 1, + 4, + 48, + 202, + 7, + 195, + 169, + 124, + 170, + 81, + 169, + 253, + 127, + 56, + 28, + 98, + 90, + 255, + 165, + 72, + 142, + 133, + 138, + 237, + 200, + 176, + 92, + 179, + 192, + 83, + 28, + 47, + 118, + 157, + 152, + 47, + 65, + 133, + 140, + 50, + 83, + 182, + 191, + 224, + 96, + 216, + 179, + 59, + 150, + 15, + 233, + 161, + 100, + 3, + 98, + 0, + 4, + 118, + 64, + 176, + 165, + 100, + 177, + 112, + 49, + 254, + 58, + 53, + 158, + 63, + 73, + 200, + 148, + 248, + 242, + 216, + 186, + 80, + 92, + 160, + 53, + 64, + 232, + 157, + 19, + 1, + 12, + 226, + 115, + 51, + 42, + 143, + 98, + 206, + 55, + 220, + 108, + 78, + 24, + 71, + 157, + 21, + 120, + 126, + 104, + 157, + 86, + 48, + 226, + 110, + 96, + 52, + 48, + 77, + 170, + 9, + 231, + 159, + 26, + 165, + 200, + 26, + 164, + 99, + 46, + 227, + 169, + 105, + 172, + 225, + 60, + 102, + 141, + 145, + 139, + 165, + 47, + 72, + 53, + 17, + 17, + 246, + 161, + 220, + 26, + 21, + 23, + 219, + 1, + 107, + 185, + 163, + 215 + ]) + } +]; +Deno.test(async function testImportEcSpkiPkcs8() { + const subtle = window.crypto.subtle; + assert(subtle); + for (const { namedCurve , raw , spki , pkcs8 , signatureLength } of ecTestKeys){ + const rawPublicKeyECDSA = await subtle.importKey("raw", raw, { + name: "ECDSA", + namedCurve + }, true, [ + "verify" + ]); + const expPublicKeyRaw = await subtle.exportKey("raw", rawPublicKeyECDSA); + assertEquals(new Uint8Array(expPublicKeyRaw), raw); + const privateKeyECDSA = await subtle.importKey("pkcs8", pkcs8, { + name: "ECDSA", + namedCurve + }, true, [ + "sign" + ]); + const expPrivateKeyPKCS8 = await subtle.exportKey("pkcs8", privateKeyECDSA); + assertEquals(new Uint8Array(expPrivateKeyPKCS8), pkcs8); + const expPrivateKeyJWK = await subtle.exportKey("jwk", privateKeyECDSA); + assertEquals(expPrivateKeyJWK.crv, namedCurve); + const publicKeyECDSA = await subtle.importKey("spki", spki, { + name: "ECDSA", + namedCurve + }, true, [ + "verify" + ]); + const expPublicKeySPKI = await subtle.exportKey("spki", publicKeyECDSA); + assertEquals(new Uint8Array(expPublicKeySPKI), spki); + const expPublicKeyJWK = await subtle.exportKey("jwk", publicKeyECDSA); + assertEquals(expPublicKeyJWK.crv, namedCurve); + for (const hash of [ + "SHA-1", + "SHA-256", + "SHA-384", + "SHA-512" + ]){ + if ((hash == "SHA-256" && namedCurve == "P-256") || (hash == "SHA-384" && namedCurve == "P-384")) { + const signatureECDSA = await subtle.sign({ + name: "ECDSA", + hash + }, privateKeyECDSA, new Uint8Array([ + 1, + 2, + 3, + 4 + ])); + const verifyECDSA = await subtle.verify({ + name: "ECDSA", + hash + }, publicKeyECDSA, signatureECDSA, new Uint8Array([ + 1, + 2, + 3, + 4 + ])); + assert(verifyECDSA); + } else { + await assertRejects(async ()=>{ + await subtle.sign({ + name: "ECDSA", + hash + }, privateKeyECDSA, new Uint8Array([ + 1, + 2, + 3, + 4 + ])); + }, DOMException, "Not implemented"); + await assertRejects(async ()=>{ + await subtle.verify({ + name: "ECDSA", + hash + }, publicKeyECDSA, new Uint8Array(signatureLength), new Uint8Array([ + 1, + 2, + 3, + 4 + ])); + }, DOMException, "Not implemented"); + } + } + } +}); +Deno.test(async function testAesGcmEncrypt() { + const key = await crypto.subtle.importKey("raw", new Uint8Array(16), { + name: "AES-GCM", + length: 256 + }, true, [ + "encrypt", + "decrypt" + ]); + const nonces = [ + { + iv: new Uint8Array([ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11 + ]), + ciphertext: new Uint8Array([ + 50, + 223, + 112, + 178, + 166, + 156, + 255, + 110, + 125, + 138, + 95, + 141, + 82, + 47, + 14, + 164, + 134, + 247, + 22 + ]) + }, + { + iv: new Uint8Array([ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15 + ]), + ciphertext: new Uint8Array([ + 210, + 101, + 81, + 216, + 151, + 9, + 192, + 197, + 62, + 254, + 28, + 132, + 89, + 106, + 40, + 29, + 175, + 232, + 201 + ]) + } + ]; + for (const { iv , ciphertext: fixture } of nonces){ + const data = new Uint8Array([ + 1, + 2, + 3 + ]); + const cipherText = await crypto.subtle.encrypt({ + name: "AES-GCM", + iv + }, key, data); + assert(cipherText instanceof ArrayBuffer); + assertEquals(cipherText.byteLength, 19); + assertEquals(new Uint8Array(cipherText), fixture); + const plainText = await crypto.subtle.decrypt({ + name: "AES-GCM", + iv + }, key, cipherText); + assert(plainText instanceof ArrayBuffer); + assertEquals(plainText.byteLength, 3); + assertEquals(new Uint8Array(plainText), data); + } +}); +async function roundTripSecretJwk(jwk: JsonWebKey, algId: AlgorithmIdentifier | HmacImportParams, ops: KeyUsage[], validateKeys: (key: CryptoKey, originalJwk: JsonWebKey, exportedJwk: JsonWebKey) => void) { + const key = await crypto.subtle.importKey("jwk", jwk, algId, true, ops); + assert(key instanceof CryptoKey); + assertEquals(key.type, "secret"); + const exportedKey = await crypto.subtle.exportKey("jwk", key); + validateKeys(key, jwk, exportedKey); +} +Deno.test(async function testSecretJwkBase64Url() { + const keyData = `{ + "kty": "oct", + "k": "xxx", + "alg": "HS512", + "key_ops": ["sign", "verify"], + "ext": true + }`; + await roundTripSecretJwk(JSON.parse(keyData), { + name: "HMAC", + hash: "SHA-512" + }, [ + "sign", + "verify" + ], (key, _orig, exp)=>{ + assertEquals((key.algorithm as HmacKeyAlgorithm).length, 16); + assertEquals(exp.k, "xxw"); + }); + await roundTripSecretJwk({ + kty: "oct", + k: "HnZXRyDKn-_G5Fx4JWR1YA", + alg: "HS256", + "key_ops": [ + "sign", + "verify" + ], + ext: true + }, { + name: "HMAC", + hash: "SHA-256" + }, [ + "sign", + "verify" + ], (key, orig, exp)=>{ + assertEquals((key.algorithm as HmacKeyAlgorithm).length, 128); + assertEquals(orig.k, exp.k); + }); + await roundTripSecretJwk({ + kty: "oct", + k: "a-_AlFa-2-OmEGa_-z==", + alg: "HS384", + "key_ops": [ + "sign", + "verify" + ], + ext: true + }, { + name: "HMAC", + hash: "SHA-384" + }, [ + "sign", + "verify" + ], (key, _orig, exp)=>{ + assertEquals((key.algorithm as HmacKeyAlgorithm).length, 104); + assertEquals("a-_AlFa-2-OmEGa_-w", exp.k); + }); + await roundTripSecretJwk({ + kty: "oct", + k: "_u3K_gEjRWf-7cr-ASNFZw", + alg: "A128CBC", + "key_ops": [ + "encrypt", + "decrypt" + ], + ext: true + }, { + name: "AES-CBC" + }, [ + "encrypt", + "decrypt" + ], (_key, orig, exp)=>{ + assertEquals(orig.k, exp.k); + }); + await roundTripSecretJwk({ + kty: "oct", + k: "_____________________w==", + alg: "A128CBC", + "key_ops": [ + "encrypt", + "decrypt" + ], + ext: true + }, { + name: "AES-CBC" + }, [ + "encrypt", + "decrypt" + ], (_key, _orig, exp)=>{ + assertEquals(exp.k, "_____________________w"); + }); +}); +Deno.test(async function testAESWrapKey() { + const key = await crypto.subtle.generateKey({ + name: "AES-KW", + length: 128 + }, true, [ + "wrapKey", + "unwrapKey" + ]); + const hmacKey = await crypto.subtle.generateKey({ + name: "HMAC", + hash: "SHA-256", + length: 128 + }, true, [ + "sign" + ]); + const wrappedKey = await crypto.subtle.wrapKey("raw", hmacKey, key, { + name: "AES-KW" + }); + assert(wrappedKey instanceof ArrayBuffer); + assertEquals(wrappedKey.byteLength, 16 + 8); + const unwrappedKey = await crypto.subtle.unwrapKey("raw", wrappedKey, key, { + name: "AES-KW" + }, { + name: "HMAC", + hash: "SHA-256" + }, true, [ + "sign" + ]); + assert(unwrappedKey instanceof CryptoKey); + assertEquals((unwrappedKey.algorithm as HmacKeyAlgorithm).length, 128); + const hmacKeyBytes = await crypto.subtle.exportKey("raw", hmacKey); + const unwrappedKeyBytes = await crypto.subtle.exportKey("raw", unwrappedKey); + assertEquals(new Uint8Array(hmacKeyBytes), new Uint8Array(unwrappedKeyBytes)); +}); +Deno.test(async function testAesGcmTagLength() { + const key = await crypto.subtle.importKey("raw", new Uint8Array(32), "AES-GCM", false, [ + "encrypt", + "decrypt" + ]); + const iv = crypto.getRandomValues(new Uint8Array(12)); + const encrypted = await crypto.subtle.encrypt({ + name: "AES-GCM", + iv, + tagLength: 96 + }, key, new Uint8Array(32)); + await assertRejects(async ()=>{ + await crypto.subtle.decrypt({ + name: "AES-GCM", + iv, + tagLength: 96 + }, key, encrypted); + }); +}); +Deno.test(async function ecPrivateKeyMaterialExportSpki() { + const keys = await crypto.subtle.generateKey({ + name: "ECDSA", + namedCurve: "P-256" + }, true, [ + "sign", + "verify" + ]); + assert(keys.privateKey instanceof CryptoKey); + assert(keys.publicKey instanceof CryptoKey); + const spki = await crypto.subtle.exportKey("spki", keys.publicKey); + assert(spki instanceof ArrayBuffer); +}); +Deno.test(async function importJwkWithUse() { + const jwk = { + "kty": "EC", + "use": "sig", + "crv": "P-256", + "x": "FWZ9rSkLt6Dx9E3pxLybhdM6xgR5obGsj5_pqmnz5J4", + "y": "_n8G69C-A2Xl4xUW2lF0i8ZGZnk_KPYrhv4GbTGu5G4" + }; + const algorithm = { + name: "ECDSA", + namedCurve: "P-256" + }; + const key = await crypto.subtle.importKey("jwk", jwk, algorithm, true, [ + "verify" + ]); + assert(key instanceof CryptoKey); +}); +Deno.test(async function exportKeyNotExtractable() { + const key = await crypto.subtle.generateKey({ + name: "HMAC", + hash: "SHA-512" + }, false, [ + "sign", + "verify" + ]); + assert(key); + assertEquals(key.extractable, false); + await assertRejects(async ()=>{ + await crypto.subtle.exportKey("raw", key); + }, DOMException); +}); +Deno.test(async function testImportLeadingZeroesKey() { + const alg = { + name: "ECDSA", + namedCurve: "P-256" + }; + const jwk = { + kty: "EC", + crv: "P-256", + alg: "ES256", + x: "EvidcdFB1xC6tgfakqZsU9aIURxAJkcX62zHe1Nt6xU", + y: "AHsk6BioGM7MZWeXOE_49AGmtuaXFT3Ill3DYtz9uYg", + d: "WDeYo4o1heCF9l_2VIaClRyIeO16zsMlN8UG6Le9dU8", + "key_ops": [ + "sign" + ], + ext: true + }; + const key = await crypto.subtle.importKey("jwk", jwk, alg, true, [ + "sign" + ]); + assert(key instanceof CryptoKey); + assertEquals(key.type, "private"); +}); +Deno.test(async function testECspkiRoundTrip() { + const alg = { + name: "ECDH", + namedCurve: "P-256" + }; + const { publicKey } = await crypto.subtle.generateKey(alg, true, [ + "deriveBits" + ]); + const spki = await crypto.subtle.exportKey("spki", publicKey); + await crypto.subtle.importKey("spki", spki, alg, true, []); +}); +Deno.test(async function testHmacJwkImport() { + await crypto.subtle.importKey("jwk", { + kty: "oct", + use: "sig", + alg: "HS256", + k: "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg" + }, { + name: "HMAC", + hash: "SHA-256" + }, false, [ + "sign", + "verify" + ]); +}); diff --git a/test/js/deno/encoding/encoding.test.ts b/test/js/deno/encoding/encoding.test.ts new file mode 100644 index 000000000..18700eddb --- /dev/null +++ b/test/js/deno/encoding/encoding.test.ts @@ -0,0 +1,343 @@ +// Copyright 2018+ the Deno authors. All rights reserved. MIT license. +// https://raw.githubusercontent.com/denoland/deno/main/cli/tests/unit/text_encoding_test.ts +import { assert, assertEquals, assertThrows } from "deno:harness"; +Deno.test(function btoaSuccess() { + const text = "hello world"; + const encoded = btoa(text); + assertEquals(encoded, "aGVsbG8gd29ybGQ="); +}); +Deno.test(function atobSuccess() { + const encoded = "aGVsbG8gd29ybGQ="; + const decoded = atob(encoded); + assertEquals(decoded, "hello world"); +}); +Deno.test(function atobWithAsciiWhitespace() { + const encodedList = [ + " aGVsbG8gd29ybGQ=", + " aGVsbG8gd29ybGQ=", + "aGVsbG8gd29ybGQ= ", + "aGVsbG8gd29ybGQ=\n", + "aGVsbG\t8gd29ybGQ=", + `aGVsbG\t8g + d29ybGQ=` + ]; + for (const encoded of encodedList){ + const decoded = atob(encoded); + assertEquals(decoded, "hello world"); + } +}); +Deno.test(function atobThrows() { + let threw = false; + try { + atob("aGVsbG8gd29ybGQ=="); + } catch (_e) { + threw = true; + } + assert(threw); +}); +Deno.test(function atobThrows2() { + let threw = false; + try { + atob("aGVsbG8gd29ybGQ==="); + } catch (_e) { + threw = true; + } + assert(threw); +}); +Deno.test(function atobThrows3() { + let threw = false; + try { + atob("foobar!!"); + } catch (e) { + if (e instanceof DOMException && e.toString().startsWith("InvalidCharacterError:")) { + threw = true; + } + } + assert(threw); +}); +Deno.test(function btoaFailed() { + const text = "你好"; + assertThrows(()=>{ + btoa(text); + }, DOMException); +}); +Deno.test(function textDecoder2() { + const fixture = new Uint8Array([ + 0xf0, + 0x9d, + 0x93, + 0xbd, + 0xf0, + 0x9d, + 0x93, + 0xae, + 0xf0, + 0x9d, + 0x94, + 0x81, + 0xf0, + 0x9d, + 0x93, + 0xbd + ]); + const decoder = new TextDecoder(); + assertEquals(decoder.decode(fixture), "𝓽𝓮𝔁𝓽"); +}); +Deno.test(function textDecoderASCII() { + const fixture = new Uint8Array([ + 0x89, + 0x95, + 0x9f, + 0xbf + ]); + const decoder = new TextDecoder("ascii"); + assertEquals(decoder.decode(fixture), "‰•Ÿ¿"); +}); +Deno.test(function textDecoderErrorEncoding() { + let didThrow = false; + try { + new TextDecoder("Foo"); + } catch (e) { + didThrow = true; + assert(e instanceof Error); + assertEquals(e.message, "The encoding label provided ('Foo') is invalid."); + } + assert(didThrow); +}); +Deno.test(function textEncoder() { + const fixture = "𝓽𝓮𝔁𝓽"; + const encoder = new TextEncoder(); + assertEquals(Array.from(encoder.encode(fixture)), [ + 0xf0, + 0x9d, + 0x93, + 0xbd, + 0xf0, + 0x9d, + 0x93, + 0xae, + 0xf0, + 0x9d, + 0x94, + 0x81, + 0xf0, + 0x9d, + 0x93, + 0xbd + ]); +}); +Deno.test(function textEncodeInto() { + const fixture = "text"; + const encoder = new TextEncoder(); + const bytes = new Uint8Array(5); + const result = encoder.encodeInto(fixture, bytes); + assertEquals(result.read, 4); + assertEquals(result.written, 4); + assertEquals(Array.from(bytes), [ + 0x74, + 0x65, + 0x78, + 0x74, + 0x00 + ]); +}); +Deno.test(function textEncodeInto2() { + const fixture = "𝓽𝓮𝔁𝓽"; + const encoder = new TextEncoder(); + const bytes = new Uint8Array(17); + const result = encoder.encodeInto(fixture, bytes); + assertEquals(result.read, 8); + assertEquals(result.written, 16); + assertEquals(Array.from(bytes), [ + 0xf0, + 0x9d, + 0x93, + 0xbd, + 0xf0, + 0x9d, + 0x93, + 0xae, + 0xf0, + 0x9d, + 0x94, + 0x81, + 0xf0, + 0x9d, + 0x93, + 0xbd, + 0x00 + ]); +}); +Deno.test(function textEncodeInto3() { + const fixture = "𝓽𝓮𝔁𝓽"; + const encoder = new TextEncoder(); + const bytes = new Uint8Array(5); + const result = encoder.encodeInto(fixture, bytes); + assertEquals(result.read, 2); + assertEquals(result.written, 4); + assertEquals(Array.from(bytes), [ + 0xf0, + 0x9d, + 0x93, + 0xbd, + 0x00 + ]); +}); +Deno.test(function loneSurrogateEncodeInto() { + const fixture = "lone𝄞\ud888surrogate"; + const encoder = new TextEncoder(); + const bytes = new Uint8Array(20); + const result = encoder.encodeInto(fixture, bytes); + assertEquals(result.read, 16); + assertEquals(result.written, 20); + assertEquals(Array.from(bytes), [ + 0x6c, + 0x6f, + 0x6e, + 0x65, + 0xf0, + 0x9d, + 0x84, + 0x9e, + 0xef, + 0xbf, + 0xbd, + 0x73, + 0x75, + 0x72, + 0x72, + 0x6f, + 0x67, + 0x61, + 0x74, + 0x65 + ]); +}); +Deno.test(function loneSurrogateEncodeInto2() { + const fixture = "\ud800"; + const encoder = new TextEncoder(); + const bytes = new Uint8Array(3); + const result = encoder.encodeInto(fixture, bytes); + assertEquals(result.read, 1); + assertEquals(result.written, 3); + assertEquals(Array.from(bytes), [ + 0xef, + 0xbf, + 0xbd + ]); +}); +Deno.test(function loneSurrogateEncodeInto3() { + const fixture = "\udc00"; + const encoder = new TextEncoder(); + const bytes = new Uint8Array(3); + const result = encoder.encodeInto(fixture, bytes); + assertEquals(result.read, 1); + assertEquals(result.written, 3); + assertEquals(Array.from(bytes), [ + 0xef, + 0xbf, + 0xbd + ]); +}); +Deno.test(function swappedSurrogatePairEncodeInto4() { + const fixture = "\udc00\ud800"; + const encoder = new TextEncoder(); + const bytes = new Uint8Array(8); + const result = encoder.encodeInto(fixture, bytes); + assertEquals(result.read, 2); + assertEquals(result.written, 6); + assertEquals(Array.from(bytes), [ + 0xef, + 0xbf, + 0xbd, + 0xef, + 0xbf, + 0xbd, + 0x00, + 0x00 + ]); +}); +Deno.test(function textDecoderSharedUint8Array() { + const ab = new SharedArrayBuffer(6); + const dataView = new DataView(ab); + const charCodeA = "A".charCodeAt(0); + for(let i = 0; i < ab.byteLength; i++){ + dataView.setUint8(i, charCodeA + i); + } + const ui8 = new Uint8Array(ab); + const decoder = new TextDecoder(); + const actual = decoder.decode(ui8); + assertEquals(actual, "ABCDEF"); +}); +Deno.test(function textDecoderSharedInt32Array() { + const ab = new SharedArrayBuffer(8); + const dataView = new DataView(ab); + const charCodeA = "A".charCodeAt(0); + for(let i = 0; i < ab.byteLength; i++){ + dataView.setUint8(i, charCodeA + i); + } + const i32 = new Int32Array(ab); + const decoder = new TextDecoder(); + const actual = decoder.decode(i32); + assertEquals(actual, "ABCDEFGH"); +}); +Deno.test(function toStringShouldBeWebCompatibility() { + const encoder = new TextEncoder(); + assertEquals(encoder.toString(), "[object TextEncoder]"); + const decoder = new TextDecoder(); + assertEquals(decoder.toString(), "[object TextDecoder]"); +}); +Deno.test(function textEncoderShouldCoerceToString() { + const encoder = new TextEncoder(); + const fixutreText = "text"; + const fixture = { + toString () { + return fixutreText; + } + }; + const bytes = encoder.encode(fixture as unknown as string); + const decoder = new TextDecoder(); + const decoded = decoder.decode(bytes); + assertEquals(decoded, fixutreText); +}); +Deno.test(function binaryEncode() { + const ops = Deno[Deno.internal].core.ops; + function asBinaryString(bytes: Uint8Array): string { + return Array.from(bytes).map((v: number)=>String.fromCodePoint(v)).join(""); + } + function decodeBinary(binaryString: string) { + const chars: string[] = Array.from(binaryString); + return chars.map((v: string): number | undefined =>v.codePointAt(0)); + } + const invalid = new Uint8Array([ + 0xC0 + ]); + assertEquals(ops.op_encode_binary_string(invalid), asBinaryString(invalid)); + const invalid2 = new Uint8Array([ + 0xC1 + ]); + assertEquals(ops.op_encode_binary_string(invalid2), asBinaryString(invalid2)); + for(let i = 0, j = 255; i <= 255; i++, j--){ + const bytes = new Uint8Array([ + i, + j + ]); + const binaryString = ops.op_encode_binary_string(bytes); + assertEquals(binaryString, asBinaryString(bytes)); + assertEquals(Array.from(bytes), decodeBinary(binaryString)); + } + const inputs = [ + "σ😀", + "Кириллица is Cyrillic", + "𝓽𝓮𝔁𝓽", + "lone𝄞\ud888surrogate", + "\udc00\ud800", + "\ud800" + ]; + for (const input of inputs){ + const bytes = new TextEncoder().encode(input); + const binaryString = ops.op_encode_binary_string(bytes); + assertEquals(binaryString, asBinaryString(bytes)); + assertEquals(Array.from(bytes), decodeBinary(binaryString)); + } +}); diff --git a/test/js/deno/event/custom-event.test.ts b/test/js/deno/event/custom-event.test.ts new file mode 100644 index 000000000..29bb0dbc9 --- /dev/null +++ b/test/js/deno/event/custom-event.test.ts @@ -0,0 +1,27 @@ +// Copyright 2018+ the Deno authors. All rights reserved. MIT license. +// https://raw.githubusercontent.com/denoland/deno/main/cli/tests/unit/custom_event_test.ts +import { assertEquals } from "deno:harness"; +Deno.test(function customEventInitializedWithDetail() { + const type = "touchstart"; + const detail = { + message: "hello" + }; + const customEventInit = { + bubbles: true, + cancelable: true, + detail + } as CustomEventInit; + const event = new CustomEvent(type, customEventInit); + assertEquals(event.bubbles, true); + assertEquals(event.cancelable, true); + assertEquals(event.currentTarget, null); + assertEquals(event.detail, detail); + assertEquals(event.isTrusted, false); + assertEquals(event.target, null); + assertEquals(event.type, type); +}); +Deno.test(function toStringShouldBeWebCompatibility() { + const type = "touchstart"; + const event = new CustomEvent(type, {}); + assertEquals(event.toString(), "[object CustomEvent]"); +}); diff --git a/test/js/deno/event/event-target.test.ts b/test/js/deno/event/event-target.test.ts new file mode 100644 index 000000000..69791b93c --- /dev/null +++ b/test/js/deno/event/event-target.test.ts @@ -0,0 +1,225 @@ +// Copyright 2018+ the Deno authors. All rights reserved. MIT license. +// https://raw.githubusercontent.com/denoland/deno/main/cli/tests/unit/event_target_test.ts +import { assertEquals, assertThrows } from "deno:harness"; +Deno.test(function addEventListenerTest() { + const document = new EventTarget(); + assertEquals(document.addEventListener("x", null, false), undefined); + assertEquals(document.addEventListener("x", null, true), undefined); + assertEquals(document.addEventListener("x", null), undefined); +}); +Deno.test(function constructedEventTargetCanBeUsedAsExpected() { + const target = new EventTarget(); + const event = new Event("foo", { + bubbles: true, + cancelable: false + }); + let callCount = 0; + const listener = (e: Event)=>{ + assertEquals(e, event); + ++callCount; + }; + target.addEventListener("foo", listener); + target.dispatchEvent(event); + assertEquals(callCount, 1); + target.dispatchEvent(event); + assertEquals(callCount, 2); + target.removeEventListener("foo", listener); + target.dispatchEvent(event); + assertEquals(callCount, 2); +}); +Deno.test(function anEventTargetCanBeSubclassed() { + class NicerEventTarget extends EventTarget { + on(type: string, callback: ((e: Event) => void) | null, options?: AddEventListenerOptions) { + this.addEventListener(type, callback, options); + } + off(type: string, callback: ((e: Event) => void) | null, options?: EventListenerOptions) { + this.removeEventListener(type, callback, options); + } + } + const target = new NicerEventTarget(); + new Event("foo", { + bubbles: true, + cancelable: false + }); + let callCount = 0; + const listener = ()=>{ + ++callCount; + }; + target.on("foo", listener); + assertEquals(callCount, 0); + target.off("foo", listener); + assertEquals(callCount, 0); +}); +Deno.test(function removingNullEventListenerShouldSucceed() { + const document = new EventTarget(); + assertEquals(document.removeEventListener("x", null, false), undefined); + assertEquals(document.removeEventListener("x", null, true), undefined); + assertEquals(document.removeEventListener("x", null), undefined); +}); +Deno.test(function constructedEventTargetUseObjectPrototype() { + const target = new EventTarget(); + const event = new Event("toString", { + bubbles: true, + cancelable: false + }); + let callCount = 0; + const listener = (e: Event)=>{ + assertEquals(e, event); + ++callCount; + }; + target.addEventListener("toString", listener); + target.dispatchEvent(event); + assertEquals(callCount, 1); + target.dispatchEvent(event); + assertEquals(callCount, 2); + target.removeEventListener("toString", listener); + target.dispatchEvent(event); + assertEquals(callCount, 2); +}); +Deno.test(function toStringShouldBeWebCompatible() { + const target = new EventTarget(); + assertEquals(target.toString(), "[object EventTarget]"); +}); +Deno.test(function dispatchEventShouldNotThrowError() { + let hasThrown = false; + try { + const target = new EventTarget(); + const event = new Event("hasOwnProperty", { + bubbles: true, + cancelable: false + }); + const listener = ()=>{}; + target.addEventListener("hasOwnProperty", listener); + target.dispatchEvent(event); + } catch { + hasThrown = true; + } + assertEquals(hasThrown, false); +}); +Deno.test(function eventTargetThisShouldDefaultToWindow() { + const { addEventListener , dispatchEvent , removeEventListener } = EventTarget.prototype; + let n = 1; + const event = new Event("hello"); + const listener = ()=>{ + n = 2; + }; + addEventListener("hello", listener); + window.dispatchEvent(event); + assertEquals(n, 2); + n = 1; + removeEventListener("hello", listener); + window.dispatchEvent(event); + assertEquals(n, 1); + window.addEventListener("hello", listener); + dispatchEvent(event); + assertEquals(n, 2); + n = 1; + window.removeEventListener("hello", listener); + dispatchEvent(event); + assertEquals(n, 1); +}); +Deno.test(function eventTargetShouldAcceptEventListenerObject() { + const target = new EventTarget(); + const event = new Event("foo", { + bubbles: true, + cancelable: false + }); + let callCount = 0; + const listener = { + handleEvent (e: Event) { + assertEquals(e, event); + ++callCount; + } + }; + target.addEventListener("foo", listener); + target.dispatchEvent(event); + assertEquals(callCount, 1); + target.dispatchEvent(event); + assertEquals(callCount, 2); + target.removeEventListener("foo", listener); + target.dispatchEvent(event); + assertEquals(callCount, 2); +}); +Deno.test(function eventTargetShouldAcceptAsyncFunction() { + const target = new EventTarget(); + const event = new Event("foo", { + bubbles: true, + cancelable: false + }); + let callCount = 0; + const listener = (e: Event)=>{ + assertEquals(e, event); + ++callCount; + }; + target.addEventListener("foo", listener); + target.dispatchEvent(event); + assertEquals(callCount, 1); + target.dispatchEvent(event); + assertEquals(callCount, 2); + target.removeEventListener("foo", listener); + target.dispatchEvent(event); + assertEquals(callCount, 2); +}); +Deno.test(function eventTargetShouldAcceptAsyncFunctionForEventListenerObject() { + const target = new EventTarget(); + const event = new Event("foo", { + bubbles: true, + cancelable: false + }); + let callCount = 0; + const listener = { + handleEvent (e: Event) { + assertEquals(e, event); + ++callCount; + } + }; + target.addEventListener("foo", listener); + target.dispatchEvent(event); + assertEquals(callCount, 1); + target.dispatchEvent(event); + assertEquals(callCount, 2); + target.removeEventListener("foo", listener); + target.dispatchEvent(event); + assertEquals(callCount, 2); +}); +Deno.test(function eventTargetDispatchShouldSetTargetNoListener() { + const target = new EventTarget(); + const event = new Event("foo"); + assertEquals(event.target, null); + target.dispatchEvent(event); + assertEquals(event.target, target); +}); +Deno.test(function eventTargetDispatchShouldSetTargetInListener() { + const target = new EventTarget(); + const event = new Event("foo"); + assertEquals(event.target, null); + let called = false; + target.addEventListener("foo", (e)=>{ + assertEquals(e.target, target); + called = true; + }); + target.dispatchEvent(event); + assertEquals(called, true); +}); +Deno.test(function eventTargetAddEventListenerGlobalAbort() { + return new Promise((resolve)=>{ + const c = new AbortController(); + c.signal.addEventListener("abort", ()=>resolve()); + addEventListener("test", ()=>{}, { + signal: c.signal + }); + c.abort(); + }); +}); +Deno.test(function eventTargetBrandChecking() { + const self = {}; + assertThrows(()=>{ + EventTarget.prototype.addEventListener.call(self, "test", null); + }, TypeError); + assertThrows(()=>{ + EventTarget.prototype.removeEventListener.call(self, "test", null); + }, TypeError); + assertThrows(()=>{ + EventTarget.prototype.dispatchEvent.call(self, new Event("test")); + }, TypeError); +}); diff --git a/test/js/deno/event/event.test.ts b/test/js/deno/event/event.test.ts new file mode 100644 index 000000000..cd23b5215 --- /dev/null +++ b/test/js/deno/event/event.test.ts @@ -0,0 +1,124 @@ +// Copyright 2018+ the Deno authors. All rights reserved. MIT license. +// https://raw.githubusercontent.com/denoland/deno/main/cli/tests/unit/event_test.ts +import { assert, assertEquals, assertStringIncludes } from "deno:harness"; +Deno.test(function eventInitializedWithType() { + const type = "click"; + const event = new Event(type); + assertEquals(event.isTrusted, false); + assertEquals(event.target, null); + assertEquals(event.currentTarget, null); + assertEquals(event.type, "click"); + assertEquals(event.bubbles, false); + assertEquals(event.cancelable, false); +}); +Deno.test(function eventInitializedWithTypeAndDict() { + const init = "submit"; + const eventInit = { + bubbles: true, + cancelable: true + } as EventInit; + const event = new Event(init, eventInit); + assertEquals(event.isTrusted, false); + assertEquals(event.target, null); + assertEquals(event.currentTarget, null); + assertEquals(event.type, "submit"); + assertEquals(event.bubbles, true); + assertEquals(event.cancelable, true); +}); +Deno.test(function eventComposedPathSuccess() { + const type = "click"; + const event = new Event(type); + const composedPath = event.composedPath(); + assertEquals(composedPath, []); +}); +Deno.test(function eventStopPropagationSuccess() { + const type = "click"; + const event = new Event(type); + assertEquals(event.cancelBubble, false); + event.stopPropagation(); + assertEquals(event.cancelBubble, true); +}); +Deno.test(function eventStopImmediatePropagationSuccess() { + const type = "click"; + const event = new Event(type); + assertEquals(event.cancelBubble, false); + event.stopImmediatePropagation(); + assertEquals(event.cancelBubble, true); +}); +Deno.test(function eventPreventDefaultSuccess() { + const type = "click"; + const event = new Event(type); + assertEquals(event.defaultPrevented, false); + event.preventDefault(); + assertEquals(event.defaultPrevented, false); + const eventInit = { + bubbles: true, + cancelable: true + } as EventInit; + const cancelableEvent = new Event(type, eventInit); + assertEquals(cancelableEvent.defaultPrevented, false); + cancelableEvent.preventDefault(); + assertEquals(cancelableEvent.defaultPrevented, true); +}); +Deno.test(function eventInitializedWithNonStringType() { + const type: any = undefined; + const event = new Event(type); + assertEquals(event.isTrusted, false); + assertEquals(event.target, null); + assertEquals(event.currentTarget, null); + assertEquals(event.type, "undefined"); + assertEquals(event.bubbles, false); + assertEquals(event.cancelable, false); +}); +Deno.test(function eventIsTrusted() { + const desc1 = Object.getOwnPropertyDescriptor(new Event("x"), "isTrusted"); + assert(desc1); + assertEquals(typeof desc1.get, "function"); + const desc2 = Object.getOwnPropertyDescriptor(new Event("x"), "isTrusted"); + assert(desc2); + assertEquals(typeof desc2!.get, "function"); + assertEquals(desc1!.get, desc2!.get); +}); +Deno.test(function eventInspectOutput() { + const cases: Array<[any, (event: any) => string]> = [ + [ + new Event("test"), + (event: Event)=>`Event {\n bubbles: false,\n cancelable: false,\n composed: false,\n currentTarget: null,\n defaultPrevented: false,\n eventPhase: 0,\n srcElement: null,\n target: null,\n returnValue: true,\n timeStamp: ${event.timeStamp},\n type: "test"\n}` + ], + [ + new ErrorEvent("error"), + (event: Event)=>`ErrorEvent {\n bubbles: false,\n cancelable: false,\n composed: false,\n currentTarget: null,\n defaultPrevented: false,\n eventPhase: 0,\n srcElement: null,\n target: null,\n returnValue: true,\n timeStamp: ${event.timeStamp},\n type: "error",\n message: "",\n filename: "",\n lineno: 0,\n colno: 0,\n error: undefined\n}` + ], + [ + new CloseEvent("close"), + (event: Event)=>`CloseEvent {\n bubbles: false,\n cancelable: false,\n composed: false,\n currentTarget: null,\n defaultPrevented: false,\n eventPhase: 0,\n srcElement: null,\n target: null,\n returnValue: true,\n timeStamp: ${event.timeStamp},\n type: "close",\n wasClean: false,\n code: 0,\n reason: ""\n}` + ], + [ + new CustomEvent("custom"), + (event: Event)=>`CustomEvent {\n bubbles: false,\n cancelable: false,\n composed: false,\n currentTarget: null,\n defaultPrevented: false,\n eventPhase: 0,\n srcElement: null,\n target: null,\n returnValue: true,\n timeStamp: ${event.timeStamp},\n type: "custom",\n detail: undefined\n}` + ], + [ + new ProgressEvent("progress"), + (event: Event)=>`ProgressEvent {\n bubbles: false,\n cancelable: false,\n composed: false,\n currentTarget: null,\n defaultPrevented: false,\n eventPhase: 0,\n srcElement: null,\n target: null,\n returnValue: true,\n timeStamp: ${event.timeStamp},\n type: "progress",\n lengthComputable: false,\n loaded: 0,\n total: 0\n}` + ] + ]; + for (const [event, outputProvider] of cases){ + assertEquals(Deno.inspect(event), outputProvider(event)); + } +}); +Deno.test(function inspectEvent() { + assertEquals(Deno.inspect(Event.prototype), `Event { + bubbles: [Getter], + cancelable: [Getter], + composed: [Getter], + currentTarget: [Getter], + defaultPrevented: [Getter], + eventPhase: [Getter], + srcElement: [Getter/Setter], + target: [Getter], + returnValue: [Getter/Setter], + timeStamp: [Getter], + type: [Getter] +}`); + assertStringIncludes(Deno.inspect(new Event("test")), `Event {\n bubbles: false,\n cancelable: false,`); +}); diff --git a/test/js/deno/html/blob.test.ts b/test/js/deno/fetch/blob.test.ts index 306d9ca59..8c9c7318c 100644 --- a/test/js/deno/html/blob.test.ts +++ b/test/js/deno/fetch/blob.test.ts @@ -1,6 +1,5 @@ // Copyright 2018+ the Deno authors. All rights reserved. MIT license. // https://raw.githubusercontent.com/denoland/deno/main/cli/tests/unit/blob_test.ts - import { assert, assertEquals, assertStringIncludes } from "deno:harness"; import { concat } from "deno:harness"; Deno.test(function blobString() { diff --git a/test/js/deno/fetch/body.test.ts b/test/js/deno/fetch/body.test.ts index 92ad70c0a..87966bc48 100644 --- a/test/js/deno/fetch/body.test.ts +++ b/test/js/deno/fetch/body.test.ts @@ -1,6 +1,5 @@ // Copyright 2018+ the Deno authors. All rights reserved. MIT license. // https://raw.githubusercontent.com/denoland/deno/main/cli/tests/unit/body_test.ts - import { assert, assertEquals } from "deno:harness"; function buildBody(body: any, headers?: Headers): Body { const stub = new Request("http://foo/", { diff --git a/test/js/deno/fetch/file.test.ts b/test/js/deno/fetch/file.test.ts new file mode 100644 index 000000000..a74cabc64 --- /dev/null +++ b/test/js/deno/fetch/file.test.ts @@ -0,0 +1,124 @@ +// Copyright 2018+ the Deno authors. All rights reserved. MIT license. +// https://raw.githubusercontent.com/denoland/deno/main/cli/tests/unit/file_test.ts +import { assert, assertEquals } from "deno:harness"; +function testFirstArgument(arg1: any[], expectedSize: number) { + const file = new File(arg1, "name"); + assert(file instanceof File); + assertEquals(file.name, "name"); + assertEquals(file.size, expectedSize); + assertEquals(file.type, ""); +} +Deno.test(function fileEmptyFileBits() { + testFirstArgument([], 0); +}); +Deno.test(function fileStringFileBits() { + testFirstArgument([ + "bits" + ], 4); +}); +Deno.test(function fileUnicodeStringFileBits() { + testFirstArgument([ + "𝓽𝓮𝔁𝓽" + ], 16); +}); +Deno.test(function fileStringObjectFileBits() { + testFirstArgument([ + new String("string object") + ], 13); +}); +Deno.test(function fileEmptyBlobFileBits() { + testFirstArgument([ + new Blob() + ], 0); +}); +Deno.test(function fileBlobFileBits() { + testFirstArgument([ + new Blob([ + "bits" + ]) + ], 4); +}); +Deno.test(function fileEmptyFileFileBits() { + testFirstArgument([ + new File([], "world.txt") + ], 0); +}); +Deno.test(function fileFileFileBits() { + testFirstArgument([ + new File([ + "bits" + ], "world.txt") + ], 4); +}); +Deno.test(function fileArrayBufferFileBits() { + testFirstArgument([ + new ArrayBuffer(8) + ], 8); +}); +Deno.test(function fileTypedArrayFileBits() { + testFirstArgument([ + new Uint8Array([ + 0x50, + 0x41, + 0x53, + 0x53 + ]) + ], 4); +}); +Deno.test(function fileVariousFileBits() { + testFirstArgument([ + "bits", + new Blob([ + "bits" + ]), + new Blob(), + new Uint8Array([ + 0x50, + 0x41 + ]), + new Uint16Array([ + 0x5353 + ]), + new Uint32Array([ + 0x53534150 + ]) + ], 16); +}); +Deno.test(function fileNumberInFileBits() { + testFirstArgument([ + 12 + ], 2); +}); +Deno.test(function fileArrayInFileBits() { + testFirstArgument([ + [ + 1, + 2, + 3 + ] + ], 5); +}); +Deno.test(function fileObjectInFileBits() { + testFirstArgument([ + {} + ], 15); +}); +function testSecondArgument(arg2: any, expectedFileName: string) { + const file = new File([ + "bits" + ], arg2); + assert(file instanceof File); + assertEquals(file.name, expectedFileName); +} +Deno.test(function fileUsingFileName() { + testSecondArgument("dummy", "dummy"); +}); +Deno.test(function fileUsingNullFileName() { + testSecondArgument(null, "null"); +}); +Deno.test(function fileUsingNumberFileName() { + testSecondArgument(1, "1"); +}); +Deno.test(function fileUsingEmptyStringFileName() { + testSecondArgument("", ""); +}); diff --git a/test/js/deno/fetch/headers.test.ts b/test/js/deno/fetch/headers.test.ts new file mode 100644 index 000000000..1bac1b93c --- /dev/null +++ b/test/js/deno/fetch/headers.test.ts @@ -0,0 +1,459 @@ +// Copyright 2018+ the Deno authors. All rights reserved. MIT license. +// https://raw.githubusercontent.com/denoland/deno/main/cli/tests/unit/headers_test.ts +import { assert, assertEquals, assertThrows } from "deno:harness"; +const { inspectArgs } = Deno[Deno.internal]; +Deno.test(function headersHasCorrectNameProp() { + assertEquals(Headers.name, "Headers"); +}); +Deno.test(function newHeaderTest() { + new Headers(); + new Headers(undefined); + new Headers({}); + try { + new Headers(null as any); + } catch (e) { + assert(e instanceof TypeError); + } +}); +const headerDict: Record<string, string> = { + name1: "value1", + name2: "value2", + name3: "value3", + name4: undefined as any, + "Content-Type": "value4" +}; +const headerSeq: any[] = []; +for (const [name, value] of Object.entries(headerDict)){ + headerSeq.push([ + name, + value + ]); +} +Deno.test(function newHeaderWithSequence() { + const headers = new Headers(headerSeq); + for (const [name, value] of Object.entries(headerDict)){ + assertEquals(headers.get(name), String(value)); + } + assertEquals(headers.get("length"), null); +}); +Deno.test(function newHeaderWithRecord() { + const headers = new Headers(headerDict); + for (const [name, value] of Object.entries(headerDict)){ + assertEquals(headers.get(name), String(value)); + } +}); +Deno.test(function newHeaderWithHeadersInstance() { + const headers = new Headers(headerDict); + const headers2 = new Headers(headers); + for (const [name, value] of Object.entries(headerDict)){ + assertEquals(headers2.get(name), String(value)); + } +}); +Deno.test(function headerAppendSuccess() { + const headers = new Headers(); + for (const [name, value] of Object.entries(headerDict)){ + headers.append(name, value); + assertEquals(headers.get(name), String(value)); + } +}); +Deno.test(function headerSetSuccess() { + const headers = new Headers(); + for (const [name, value] of Object.entries(headerDict)){ + headers.set(name, value); + assertEquals(headers.get(name), String(value)); + } +}); +Deno.test(function headerHasSuccess() { + const headers = new Headers(headerDict); + for (const name of Object.keys(headerDict)){ + assert(headers.has(name), "headers has name " + name); + assert(!headers.has("nameNotInHeaders"), "headers do not have header: nameNotInHeaders"); + } +}); +Deno.test(function headerDeleteSuccess() { + const headers = new Headers(headerDict); + for (const name of Object.keys(headerDict)){ + assert(headers.has(name), "headers have a header: " + name); + headers.delete(name); + assert(!headers.has(name), "headers do not have anymore a header: " + name); + } +}); +Deno.test(function headerGetSuccess() { + const headers = new Headers(headerDict); + for (const [name, value] of Object.entries(headerDict)){ + assertEquals(headers.get(name), String(value)); + assertEquals(headers.get("nameNotInHeaders"), null); + } +}); +Deno.test(function headerEntriesSuccess() { + const headers = new Headers(headerDict); + const iterators = headers.entries(); + for (const it of iterators){ + const key = it[0]; + const value = it[1]; + assert(headers.has(key)); + assertEquals(value, headers.get(key)); + } +}); +Deno.test(function headerKeysSuccess() { + const headers = new Headers(headerDict); + const iterators = headers.keys(); + for (const it of iterators){ + assert(headers.has(it)); + } +}); +Deno.test(function headerValuesSuccess() { + const headers = new Headers(headerDict); + const iterators = headers.values(); + const entries = headers.entries(); + const values = []; + for (const pair of entries){ + values.push(pair[1]); + } + for (const it of iterators){ + assert(values.includes(it)); + } +}); +const headerEntriesDict: Record<string, string> = { + name1: "value1", + Name2: "value2", + name: "value3", + "content-Type": "value4", + "Content-Typ": "value5", + "Content-Types": "value6" +}; +Deno.test(function headerForEachSuccess() { + const headers = new Headers(headerEntriesDict); + const keys = Object.keys(headerEntriesDict); + keys.forEach((key)=>{ + const value = headerEntriesDict[key]; + const newkey = key.toLowerCase(); + headerEntriesDict[newkey] = value; + }); + let callNum = 0; + headers.forEach((value, key, container)=>{ + assertEquals(headers, container); + assertEquals(value, headerEntriesDict[key]); + callNum++; + }); + assertEquals(callNum, keys.length); +}); +Deno.test(function headerSymbolIteratorSuccess() { + assert(Symbol.iterator in Headers.prototype); + const headers = new Headers(headerEntriesDict); + for (const header of headers){ + const key = header[0]; + const value = header[1]; + assert(headers.has(key)); + assertEquals(value, headers.get(key)); + } +}); +Deno.test(function headerTypesAvailable() { + function newHeaders(): Headers { + return new Headers(); + } + const headers = newHeaders(); + assert(headers instanceof Headers); +}); +Deno.test(function headerIllegalReject() { + let errorCount = 0; + try { + new Headers({ + "He y": "ok" + }); + } catch (_e) { + errorCount++; + } + try { + new Headers({ + "Hé-y": "ok" + }); + } catch (_e) { + errorCount++; + } + try { + new Headers({ + "He-y": "ăk" + }); + } catch (_e) { + errorCount++; + } + const headers = new Headers(); + try { + headers.append("Hé-y", "ok"); + } catch (_e) { + errorCount++; + } + try { + headers.delete("Hé-y"); + } catch (_e) { + errorCount++; + } + try { + headers.get("Hé-y"); + } catch (_e) { + errorCount++; + } + try { + headers.has("Hé-y"); + } catch (_e) { + errorCount++; + } + try { + headers.set("Hé-y", "ok"); + } catch (_e) { + errorCount++; + } + try { + headers.set("", "ok"); + } catch (_e) { + errorCount++; + } + assertEquals(errorCount, 9); + new Headers({ + "He-y": "o k" + }); +}); +Deno.test(function headerParamsShouldThrowTypeError() { + let hasThrown = 0; + try { + new Headers(([ + [ + "1" + ] + ] as unknown) as Array<[string, string]>); + hasThrown = 1; + } catch (err) { + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEquals(hasThrown, 2); +}); +Deno.test(function headerParamsArgumentsCheck() { + const methodRequireOneParam = [ + "delete", + "get", + "has", + "forEach" + ] as const; + const methodRequireTwoParams = [ + "append", + "set" + ] as const; + methodRequireOneParam.forEach((method)=>{ + const headers = new Headers(); + let hasThrown = 0; + try { + (headers as any)[method](); + hasThrown = 1; + } catch (err) { + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEquals(hasThrown, 2); + }); + methodRequireTwoParams.forEach((method)=>{ + const headers = new Headers(); + let hasThrown = 0; + try { + (headers as any)[method](); + hasThrown = 1; + } catch (err) { + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEquals(hasThrown, 2); + hasThrown = 0; + try { + (headers as any)[method]("foo"); + hasThrown = 1; + } catch (err) { + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEquals(hasThrown, 2); + }); +}); +Deno.test(function headersInitMultiple() { + const headers = new Headers([ + [ + "Set-Cookie", + "foo=bar" + ], + [ + "Set-Cookie", + "bar=baz" + ], + [ + "X-Deno", + "foo" + ], + [ + "X-Deno", + "bar" + ] + ]); + const actual = [ + ...headers + ]; + assertEquals(actual, [ + [ + "set-cookie", + "foo=bar" + ], + [ + "set-cookie", + "bar=baz" + ], + [ + "x-deno", + "foo, bar" + ] + ]); +}); +Deno.test(function headerInitWithPrototypePollution() { + const originalExec = RegExp.prototype.exec; + try { + RegExp.prototype.exec = ()=>{ + throw Error(); + }; + new Headers([ + [ + "X-Deno", + "foo" + ], + [ + "X-Deno", + "bar" + ] + ]); + } finally{ + RegExp.prototype.exec = originalExec; + } +}); +Deno.test(function headersAppendMultiple() { + const headers = new Headers([ + [ + "Set-Cookie", + "foo=bar" + ], + [ + "X-Deno", + "foo" + ] + ]); + headers.append("set-Cookie", "bar=baz"); + headers.append("x-Deno", "bar"); + const actual = [ + ...headers + ]; + assertEquals(actual, [ + [ + "set-cookie", + "foo=bar" + ], + [ + "set-cookie", + "bar=baz" + ], + [ + "x-deno", + "foo, bar" + ] + ]); +}); +Deno.test(function headersAppendDuplicateSetCookieKey() { + const headers = new Headers([ + [ + "Set-Cookie", + "foo=bar" + ] + ]); + headers.append("set-Cookie", "foo=baz"); + headers.append("Set-cookie", "baz=bar"); + const actual = [ + ...headers + ]; + assertEquals(actual, [ + [ + "set-cookie", + "foo=bar" + ], + [ + "set-cookie", + "foo=baz" + ], + [ + "set-cookie", + "baz=bar" + ] + ]); +}); +Deno.test(function headersGetSetCookie() { + const headers = new Headers([ + [ + "Set-Cookie", + "foo=bar" + ], + [ + "set-Cookie", + "bar=qat" + ] + ]); + assertEquals(headers.get("SET-COOKIE"), "foo=bar, bar=qat"); +}); +Deno.test(function toStringShouldBeWebCompatibility() { + const headers = new Headers(); + assertEquals(headers.toString(), "[object Headers]"); +}); +function stringify(...args: unknown[]): string { + return inspectArgs(args).replace(/\n$/, ""); +} +Deno.test.ignore(function customInspectReturnsCorrectHeadersFormat() { + const blankHeaders = new Headers(); + assertEquals(stringify(blankHeaders), "Headers {}"); + const singleHeader = new Headers([ + [ + "Content-Type", + "application/json" + ] + ]); + assertEquals(stringify(singleHeader), `Headers { "content-type": "application/json" }`); + const multiParamHeader = new Headers([ + [ + "Content-Type", + "application/json" + ], + [ + "Content-Length", + "1337" + ] + ]); + assertEquals(stringify(multiParamHeader), `Headers { "content-length": "1337", "content-type": "application/json" }`); +}); +Deno.test(function invalidHeadersFlaky() { + assertThrows(()=>new Headers([ + [ + "x", + "\u0000x" + ] + ]), TypeError, "Header value is not valid."); + assertThrows(()=>new Headers([ + [ + "x", + "\u0000x" + ] + ]), TypeError, "Header value is not valid."); +}); diff --git a/test/js/deno/fetch/request.test.ts b/test/js/deno/fetch/request.test.ts new file mode 100644 index 000000000..d1a444196 --- /dev/null +++ b/test/js/deno/fetch/request.test.ts @@ -0,0 +1,59 @@ +// Copyright 2018+ the Deno authors. All rights reserved. MIT license. +// https://raw.githubusercontent.com/denoland/deno/main/cli/tests/unit/request_test.ts +import { assertEquals, assertStringIncludes } from "deno:harness"; +Deno.test(async function fromInit() { + const req = new Request("http://foo/", { + body: "ahoyhoy", + method: "POST", + headers: { + "test-header": "value" + } + }); + assertEquals("ahoyhoy", await req.text()); + assertEquals(req.url, "http://foo/"); + assertEquals(req.headers.get("test-header"), "value"); +}); +Deno.test(function requestNonString() { + const nonString = { + toString () { + return "http://foo/"; + } + }; + assertEquals(new Request(nonString).url, "http://foo/"); +}); +Deno.test(function methodNonString() { + assertEquals(new Request("http://foo/", { + method: undefined + }).method, "GET"); +}); +Deno.test.ignore(function requestRelativeUrl() { + assertEquals(new Request("relative-url").url, "http://js-unit-tests/foo/relative-url"); +}); +Deno.test(async function cloneRequestBodyStream() { + const stream = new Request("http://foo/", { + body: "a test body", + method: "POST" + }).body; + const r1 = new Request("http://foo/", { + body: stream, + method: "POST" + }); + const r2 = r1.clone(); + const b1 = await r1.text(); + const b2 = await r2.text(); + assertEquals(b1, b2); +}); +Deno.test.ignore(function customInspectFunction() { + const request = new Request("https://example.com"); + assertEquals(Deno.inspect(request), `Request { + bodyUsed: false, + headers: Headers {}, + method: "GET", + redirect: "follow", + url: "https://example.com/" +}`); + assertStringIncludes(Deno.inspect(Request.prototype), "Request"); +}); +Deno.test(function requestConstructorTakeURLObjectAsParameter() { + assertEquals(new Request(new URL("http://foo/")).url, "http://foo/"); +}); diff --git a/test/js/deno/fetch/response.test.ts b/test/js/deno/fetch/response.test.ts new file mode 100644 index 000000000..2e83ed0d8 --- /dev/null +++ b/test/js/deno/fetch/response.test.ts @@ -0,0 +1,105 @@ +// Copyright 2018+ the Deno authors. All rights reserved. MIT license. +// https://raw.githubusercontent.com/denoland/deno/main/cli/tests/unit/response_test.ts +import { assert, assertEquals, assertStringIncludes, assertThrows } from "deno:harness"; +Deno.test(async function responseText() { + const response = new Response("hello world"); + const textPromise = response.text(); + assert(textPromise instanceof Promise); + const text = await textPromise; + assert(typeof text === "string"); + assertEquals(text, "hello world"); +}); +Deno.test(async function responseArrayBuffer() { + const response = new Response(new Uint8Array([ + 1, + 2, + 3 + ])); + const arrayBufferPromise = response.arrayBuffer(); + assert(arrayBufferPromise instanceof Promise); + const arrayBuffer = await arrayBufferPromise; + assert(arrayBuffer instanceof ArrayBuffer); + assertEquals(new Uint8Array(arrayBuffer), new Uint8Array([ + 1, + 2, + 3 + ])); +}); +Deno.test(async function responseJson() { + const response = new Response('{"hello": "world"}'); + const jsonPromise = response.json(); + assert(jsonPromise instanceof Promise); + const json = await jsonPromise; + assert(json instanceof Object); + assertEquals(json, { + hello: "world" + }); +}); +Deno.test(async function responseBlob() { + const response = new Response(new Uint8Array([ + 1, + 2, + 3 + ])); + const blobPromise = response.blob(); + assert(blobPromise instanceof Promise); + const blob = await blobPromise; + assert(blob instanceof Blob); + assertEquals(blob.size, 3); + assertEquals(await blob.arrayBuffer(), new Uint8Array([ + 1, + 2, + 3 + ]).buffer); +}); +Deno.test(async function responseFormData() { + const input = new FormData(); + input.append("hello", "world"); + const response = new Response(input); + const contentType = response.headers.get("content-type")!; + assert(contentType.startsWith("multipart/form-data")); + const formDataPromise = response.formData(); + assert(formDataPromise instanceof Promise); + const formData = await formDataPromise; + assert(formData instanceof FormData); + assertEquals([ + ...formData + ], [ + ...input + ]); +}); +Deno.test(function responseInvalidInit() { + assertThrows(()=>new Response("", 0)); + assertThrows(()=>new Response("", { + status: 0 + })); + assertThrows(()=>new Response("", { + status: null + })); +}); +Deno.test(function responseNullInit() { + const response = new Response("", null); + assertEquals(response.status, 200); +}); +Deno.test.ignore(function customInspectFunction() { + const response = new Response(); + assertEquals(Deno.inspect(response), `Response { + body: null, + bodyUsed: false, + headers: Headers {}, + ok: true, + redirected: false, + status: 200, + statusText: "", + url: "" +}`); + assertStringIncludes(Deno.inspect(Response.prototype), "Response"); +}); +Deno.test(async function responseBodyUsed() { + const response = new Response("body"); + assert(!response.bodyUsed); + await response.text(); + assert(response.bodyUsed); + response.body; + assert(response.bodyUsed); +}); diff --git a/test/js/deno/harness.ts b/test/js/deno/harness.ts index 8634d5261..70f0ceede 100644 --- a/test/js/deno/harness.ts +++ b/test/js/deno/harness.ts @@ -1,4 +1,24 @@ -export * from "./harness/global.js"; +export * from "./harness/test.js"; export * from "./harness/util.js"; export * from "./harness/assert.js"; export * from "./harness/fixture.js"; + +import { readTextFile } from "./harness/fixture.js"; +import { test } from "./harness/test.js"; + +export const Deno = { + test, + readTextFile, + internal: "[internal]", + ["[internal]"]: {}, +}; + +// @ts-expect-error +globalThis["Deno"] = Deno; + +export const window = { + crypto: crypto, +}; + +// @ts-expect-error +globalThis["window"] = window; diff --git a/test/js/deno/harness/fixture.ts b/test/js/deno/harness/fixture.ts index 13a531e52..6917fc893 100644 --- a/test/js/deno/harness/fixture.ts +++ b/test/js/deno/harness/fixture.ts @@ -1,6 +1,7 @@ import type { Server } from "bun"; import { serve } from "bun"; import { afterAll, beforeAll } from "bun:test"; +import baseUrl from "../resources/url.json"; let server: Server; @@ -10,10 +11,10 @@ beforeAll(() => { fetch(request: Request): Response { const { url } = request; const { pathname, search } = new URL(url); - const redirect = new URL( - `${pathname}?${search}`, - "https://raw.githubusercontent.com/denoland/deno/main/cli/tests/testdata/", - ); + if (pathname === "/echo_server") { + return new Response(request.body, request); + } + const redirect = new URL(`${pathname}?${search}`, baseUrl); return Response.redirect(redirect.toString()); }, }); @@ -24,3 +25,12 @@ afterAll(() => { server.stop(true); } }); + +export async function readTextFile(path: string): Promise<string> { + const url = new URL(path, baseUrl); + const response = await fetch(url); + if (response.ok) { + return response.text(); + } + throw new Error(`${response.status}: ${response.url}`); +} diff --git a/test/js/deno/harness/global.ts b/test/js/deno/harness/test.ts index 4992f026f..8e31c3d4d 100644 --- a/test/js/deno/harness/global.ts +++ b/test/js/deno/harness/test.ts @@ -2,17 +2,23 @@ import { test as bunTest } from "bun:test"; type Fn = () => void | Promise<unknown>; type Options = { - permissions?: { + permissions?: "none" | { net?: boolean; + read?: boolean; }; ignore?: boolean; }; -function test(arg0: Fn | Options, arg1?: Fn): void { +export function test(arg0: Fn | Options, arg1?: Fn): void { if (typeof arg0 === "function") { bunTest(arg0.name, arg0); } else if (typeof arg1 === "function") { - if (arg0?.ignore === true || arg0?.permissions?.net === false) { + if ( + arg0?.ignore === true + || arg0?.permissions === "none" + || arg0?.permissions?.net === false + || arg0?.permissions?.read === false + ) { bunTest.skip(arg1.name, arg1); } else { bunTest(arg1.name, arg1); @@ -31,15 +37,3 @@ test.ignore = (arg0: Fn | Options, arg1?: Fn) => { throw new Error("Unimplemented"); } }; - -export function inspect(...args: unknown[]): string { - return Bun.inspect(...args); -} - -export const Deno = { - test, - inspect, -}; - -// @ts-expect-error -globalThis["Deno"] = Deno; diff --git a/test/js/deno/harness/util.ts b/test/js/deno/harness/util.ts index a7fbf7fce..ecd8a6953 100644 --- a/test/js/deno/harness/util.ts +++ b/test/js/deno/harness/util.ts @@ -1,5 +1,50 @@ +// Copyright 2018+ the Deno authors. All rights reserved. MIT license. +// https://github.com/denoland/deno/blob/main/ext/node/polyfills/_util/async.ts + import { concatArrayBuffers } from "bun"; export function concat(...buffers: Uint8Array[]): Uint8Array { return new Uint8Array(concatArrayBuffers(buffers)); } + +export function deferred<T>() { + let methods; + let state = "pending"; + const promise = new Promise<T>((resolve, reject) => { + methods = { + async resolve(value: T | PromiseLike<T>) { + await value; + state = "fulfilled"; + resolve(value); + }, + reject(reason?: any) { + state = "rejected"; + reject(reason); + }, + }; + }); + Object.defineProperty(promise, "state", { get: () => state }); + return Object.assign(promise, methods); +} + +export function delay( + ms: number, + options: { signal?: AbortSignal } = {}, +): Promise<void> { + const { signal } = options; + if (signal?.aborted) { + return Promise.reject(new DOMException("Delay was aborted.", "AbortError")); + } + return new Promise((resolve, reject) => { + const abort = () => { + clearTimeout(i); + reject(new DOMException("Delay was aborted.", "AbortError")); + }; + const done = () => { + signal?.removeEventListener("abort", abort); + resolve(); + }; + const i = setTimeout(done, ms); + signal?.addEventListener("abort", abort, { once: true }); + }); +} diff --git a/test/js/deno/resources/imports.json b/test/js/deno/resources/imports.json index 6b044a9a5..182555661 100644 --- a/test/js/deno/resources/imports.json +++ b/test/js/deno/resources/imports.json @@ -1 +1,5 @@ -["test_util.ts", "test_util/std/bytes/concat.ts"] +[ + "test_util.ts", + "test_util/std/bytes/concat.ts", + "test_util/std/io/buffer.ts" +] diff --git a/test/js/deno/resources/tests.json b/test/js/deno/resources/tests.json index 7de35dd78..5a7b10530 100644 --- a/test/js/deno/resources/tests.json +++ b/test/js/deno/resources/tests.json @@ -1,15 +1,78 @@ [ { "path": "abort/abort-controller.test.ts", - "remotePath": "unit/abort_controller_test.ts" + "remotePath": "cli/tests/unit/abort_controller_test.ts" }, { - "path": "html/blob.test.ts", - "remotePath": "unit/blob_test.ts", - "skip": ["blobCustomInspectFunction"] + "path": "url/url.test.ts", + "remotePath": "cli/tests/unit/url_test.ts", + "skip": ["customInspectFunction"] + }, + { + "path": "url/urlsearchparams.test.ts", + "remotePath": "cli/tests/unit/url_search_params_test.ts" + }, + { + "path": "event/event.test.ts", + "remotePath": "cli/tests/unit/event_test.ts" + }, + { + "path": "event/event-target.test.ts", + "remotePath": "cli/tests/unit/event_target_test.ts" + }, + { + "path": "event/custom-event.test.ts", + "remotePath": "cli/tests/unit/custom_event_test.ts" }, { "path": "fetch/body.test.ts", - "remotePath": "unit/body_test.ts" + "remotePath": "cli/tests/unit/body_test.ts" + }, + { + "path": "fetch/blob.test.ts", + "remotePath": "cli/tests/unit/blob_test.ts", + "skip": ["blobCustomInspectFunction"] + }, + { + "path": "fetch/file.test.ts", + "remotePath": "cli/tests/unit/file_test.ts" + }, + { + "path": "fetch/headers.test.ts", + "remotePath": "cli/tests/unit/headers_test.ts", + "skip": ["customInspectReturnsCorrectHeadersFormat"] + }, + { + "path": "fetch/request.test.ts", + "remotePath": "cli/tests/unit/request_test.ts", + "skip": [ + "customInspectFunction", + "requestRelativeUrl" + ] + }, + { + "path": "fetch/response.test.ts", + "remotePath": "cli/tests/unit/response_test.ts", + "skip": ["customInspectFunction"] + }, + { + "path": "crypto/random.test.ts", + "remotePath": "cli/tests/unit/get_random_values_test.ts" + }, + { + "path": "crypto/webcrypto.test.ts", + "remotePath": "cli/tests/unit/webcrypto_test.ts" + }, + { + "path": "encoding/encoding.test.ts", + "remotePath": "cli/tests/unit/text_encoding_test.ts" + }, + { + "path": "websocket/websocket.test.ts", + "remotePath": "cli/tests/unit/websocket_test.ts" + }, + { + "path": "v8/error.test.ts", + "remotePath": "cli/tests/unit/error_stack_test.ts" } ] diff --git a/test/js/deno/resources/url.json b/test/js/deno/resources/url.json new file mode 100644 index 000000000..348c6daf7 --- /dev/null +++ b/test/js/deno/resources/url.json @@ -0,0 +1 @@ +"https://raw.githubusercontent.com/denoland/deno/main/" diff --git a/test/js/deno/scripts/postinstall.ts b/test/js/deno/scripts/postinstall.ts index 65fd5a04e..11368585b 100644 --- a/test/js/deno/scripts/postinstall.ts +++ b/test/js/deno/scripts/postinstall.ts @@ -4,13 +4,14 @@ import { parse, print } from "@swc/core"; import type { ImportDeclaration, ExpressionStatement, CallExpression } from "@swc/core"; import imports from "../resources/imports.json"; import tests from "../resources/tests.json"; +import baseUrl from "../resources/url.json"; // FIXME: https://github.com/oven-sh/bun/issues/2350 // import * as harness from "deno:harness"; for (const test of tests) { const path = join(import.meta.dir, "..", test.path); - const url = new URL(test.remotePath, "https://raw.githubusercontent.com/denoland/deno/main/cli/tests/"); + const url = new URL(test.remotePath, baseUrl); const response = await fetch(url); console.log(response.status, url.toString(), "->", test.path); if (!response.ok) { @@ -36,12 +37,11 @@ async function visit(src: string, test: any): Promise<string> { visitImport(item); } if (item.type === "ExpressionStatement") { - visitExpression(item, test); + visitExpression(item); } } - const header = `// Copyright 2018+ the Deno authors. All rights reserved. MIT license. -// https://raw.githubusercontent.com/denoland/deno/main/cli/tests/${test.remotePath} -\n`; + const url = new URL(test.remotePath, baseUrl); + const header = `// Copyright 2018+ the Deno authors. All rights reserved. MIT license.\n// ${url}\n`; const { code } = await print(ast, { isModule: true, }); @@ -75,7 +75,7 @@ function visitImport(item: ImportDeclaration): void { }*/ } -function visitExpression(item: ExpressionStatement, test: any): void { +function visitExpression(item: ExpressionStatement): void { if ( item.expression.type === "CallExpression" && item.expression.callee.type === "MemberExpression" && @@ -92,8 +92,10 @@ function visitTest(item: CallExpression): void { for (const argument of item.arguments) { if (argument.expression.type === "FunctionExpression") { const fn = argument.expression.identifier?.value; - for (const test of tests) { - if (test.skip && test.skip.includes(fn)) { + if (fn) { + const pattern = new RegExp(tests.flatMap((test) => test.skip ?? []).join("|"), "i"); + if (pattern.test(fn)) { + // @ts-ignore item.callee.property.value = "test.ignore"; } } diff --git a/test/js/deno/url/url.test.ts b/test/js/deno/url/url.test.ts new file mode 100644 index 000000000..66d6e853d --- /dev/null +++ b/test/js/deno/url/url.test.ts @@ -0,0 +1,362 @@ +// Copyright 2018+ the Deno authors. All rights reserved. MIT license. +// https://raw.githubusercontent.com/denoland/deno/main/cli/tests/unit/url_test.ts +import { assert, assertEquals, assertStrictEquals, assertThrows } from "deno:harness"; +Deno.test(function urlParsing() { + const url = new URL("https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat"); + assertEquals(url.hash, "#qat"); + assertEquals(url.host, "baz.qat:8000"); + assertEquals(url.hostname, "baz.qat"); + assertEquals(url.href, "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat"); + assertEquals(url.origin, "https://baz.qat:8000"); + assertEquals(url.password, "bar"); + assertEquals(url.pathname, "/qux/quux"); + assertEquals(url.port, "8000"); + assertEquals(url.protocol, "https:"); + assertEquals(url.search, "?foo=bar&baz=12"); + assertEquals(url.searchParams.getAll("foo"), [ + "bar" + ]); + assertEquals(url.searchParams.getAll("baz"), [ + "12" + ]); + assertEquals(url.username, "foo"); + assertEquals(String(url), "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat"); +}); +Deno.test(function urlProtocolParsing() { + assertEquals(new URL("Aa+-.1://foo").protocol, "aa+-.1:"); + assertEquals(new URL("aA+-.1://foo").protocol, "aa+-.1:"); + assertThrows(()=>new URL("1://foo"), TypeError, "Invalid URL: '1://foo'"); + assertThrows(()=>new URL("+://foo"), TypeError, "Invalid URL: '+://foo'"); + assertThrows(()=>new URL("-://foo"), TypeError, "Invalid URL: '-://foo'"); + assertThrows(()=>new URL(".://foo"), TypeError, "Invalid URL: '.://foo'"); + assertThrows(()=>new URL("_://foo"), TypeError, "Invalid URL: '_://foo'"); + assertThrows(()=>new URL("=://foo"), TypeError, "Invalid URL: '=://foo'"); + assertThrows(()=>new URL("!://foo"), TypeError, "Invalid URL: '!://foo'"); + assertThrows(()=>new URL(`"://foo`), TypeError, `Invalid URL: '"://foo'`); + assertThrows(()=>new URL("$://foo"), TypeError, "Invalid URL: '$://foo'"); + assertThrows(()=>new URL("%://foo"), TypeError, "Invalid URL: '%://foo'"); + assertThrows(()=>new URL("^://foo"), TypeError, "Invalid URL: '^://foo'"); + assertThrows(()=>new URL("*://foo"), TypeError, "Invalid URL: '*://foo'"); + assertThrows(()=>new URL("*://foo"), TypeError, "Invalid URL: '*://foo'"); + assertThrows(()=>new URL("!:", "*://foo"), TypeError, "Invalid URL: '!:' with base '*://foo'"); +}); +Deno.test(function urlAuthenticationParsing() { + const specialUrl = new URL("http://foo:bar@baz"); + assertEquals(specialUrl.username, "foo"); + assertEquals(specialUrl.password, "bar"); + assertEquals(specialUrl.hostname, "baz"); + assertThrows(()=>new URL("file://foo:bar@baz"), TypeError, "Invalid URL"); + const nonSpecialUrl = new URL("abcd://foo:bar@baz"); + assertEquals(nonSpecialUrl.username, "foo"); + assertEquals(nonSpecialUrl.password, "bar"); + assertEquals(nonSpecialUrl.hostname, "baz"); +}); +Deno.test(function urlHostnameParsing() { + assertEquals(new URL("http://[::1]").hostname, "[::1]"); + assertEquals(new URL("file://[::1]").hostname, "[::1]"); + assertEquals(new URL("abcd://[::1]").hostname, "[::1]"); + assertEquals(new URL("http://[0:f:0:0:f:f:0:0]").hostname, "[0:f::f:f:0:0]"); + assertThrows(()=>new URL("http:// a"), TypeError, "Invalid URL"); + assertThrows(()=>new URL("file:// a"), TypeError, "Invalid URL"); + assertThrows(()=>new URL("abcd:// a"), TypeError, "Invalid URL"); + assertThrows(()=>new URL("http://%"), TypeError, "Invalid URL"); + assertThrows(()=>new URL("file://%"), TypeError, "Invalid URL"); + assertEquals(new URL("abcd://%").hostname, "%"); + assertEquals(new URL("http://%21").hostname, "!"); + assertEquals(new URL("file://%21").hostname, "!"); + assertEquals(new URL("abcd://%21").hostname, "%21"); + assertEquals(new URL("http://260").hostname, "0.0.1.4"); + assertEquals(new URL("file://260").hostname, "0.0.1.4"); + assertEquals(new URL("abcd://260").hostname, "260"); + assertEquals(new URL("http://255.0.0.0").hostname, "255.0.0.0"); + assertThrows(()=>new URL("http://256.0.0.0"), TypeError, "Invalid URL"); + assertEquals(new URL("http://0.255.0.0").hostname, "0.255.0.0"); + assertThrows(()=>new URL("http://0.256.0.0"), TypeError, "Invalid URL"); + assertEquals(new URL("http://0.0.255.0").hostname, "0.0.255.0"); + assertThrows(()=>new URL("http://0.0.256.0"), TypeError, "Invalid URL"); + assertEquals(new URL("http://0.0.0.255").hostname, "0.0.0.255"); + assertThrows(()=>new URL("http://0.0.0.256"), TypeError, "Invalid URL"); + assertEquals(new URL("http://0.0.65535").hostname, "0.0.255.255"); + assertThrows(()=>new URL("http://0.0.65536"), TypeError, "Invalid URL"); + assertEquals(new URL("http://0.16777215").hostname, "0.255.255.255"); + assertThrows(()=>new URL("http://0.16777216"), TypeError, "Invalid URL"); + assertEquals(new URL("http://4294967295").hostname, "255.255.255.255"); + assertThrows(()=>new URL("http://4294967296"), TypeError, "Invalid URL"); +}); +Deno.test(function urlPortParsing() { + const specialUrl = new URL("http://foo:8000"); + assertEquals(specialUrl.hostname, "foo"); + assertEquals(specialUrl.port, "8000"); + assertThrows(()=>new URL("file://foo:8000"), TypeError, "Invalid URL"); + const nonSpecialUrl = new URL("abcd://foo:8000"); + assertEquals(nonSpecialUrl.hostname, "foo"); + assertEquals(nonSpecialUrl.port, "8000"); +}); +Deno.test(function urlModifications() { + const url = new URL("https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat"); + url.hash = ""; + assertEquals(url.href, "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12"); + url.host = "qat.baz:8080"; + assertEquals(url.href, "https://foo:bar@qat.baz:8080/qux/quux?foo=bar&baz=12"); + url.hostname = "foo.bar"; + assertEquals(url.href, "https://foo:bar@foo.bar:8080/qux/quux?foo=bar&baz=12"); + url.password = "qux"; + assertEquals(url.href, "https://foo:qux@foo.bar:8080/qux/quux?foo=bar&baz=12"); + url.pathname = "/foo/bar%qat"; + assertEquals(url.href, "https://foo:qux@foo.bar:8080/foo/bar%qat?foo=bar&baz=12"); + url.port = ""; + assertEquals(url.href, "https://foo:qux@foo.bar/foo/bar%qat?foo=bar&baz=12"); + url.protocol = "http:"; + assertEquals(url.href, "http://foo:qux@foo.bar/foo/bar%qat?foo=bar&baz=12"); + url.search = "?foo=bar&foo=baz"; + assertEquals(url.href, "http://foo:qux@foo.bar/foo/bar%qat?foo=bar&foo=baz"); + assertEquals(url.searchParams.getAll("foo"), [ + "bar", + "baz" + ]); + url.username = "foo@bar"; + assertEquals(url.href, "http://foo%40bar:qux@foo.bar/foo/bar%qat?foo=bar&foo=baz"); + url.searchParams.set("bar", "qat"); + assertEquals(url.href, "http://foo%40bar:qux@foo.bar/foo/bar%qat?foo=bar&foo=baz&bar=qat"); + url.searchParams.delete("foo"); + assertEquals(url.href, "http://foo%40bar:qux@foo.bar/foo/bar%qat?bar=qat"); + url.searchParams.append("foo", "bar"); + assertEquals(url.href, "http://foo%40bar:qux@foo.bar/foo/bar%qat?bar=qat&foo=bar"); +}); +Deno.test(function urlModifyHref() { + const url = new URL("http://example.com/"); + url.href = "https://foo:bar@example.com:8080/baz/qat#qux"; + assertEquals(url.protocol, "https:"); + assertEquals(url.username, "foo"); + assertEquals(url.password, "bar"); + assertEquals(url.host, "example.com:8080"); + assertEquals(url.hostname, "example.com"); + assertEquals(url.pathname, "/baz/qat"); + assertEquals(url.hash, "#qux"); +}); +Deno.test(function urlNormalize() { + const url = new URL("http://example.com"); + assertEquals(url.pathname, "/"); + assertEquals(url.href, "http://example.com/"); +}); +Deno.test(function urlModifyPathname() { + const url = new URL("http://foo.bar/baz%qat/qux%quux"); + assertEquals(url.pathname, "/baz%qat/qux%quux"); + url.pathname = url.pathname; + assertEquals(url.pathname, "/baz%qat/qux%quux"); + url.pathname = "baz#qat qux"; + assertEquals(url.pathname, "/baz%23qat%20qux"); + url.pathname = url.pathname; + assertEquals(url.pathname, "/baz%23qat%20qux"); + url.pathname = "\\a\\b\\c"; + assertEquals(url.pathname, "/a/b/c"); +}); +Deno.test(function urlModifyHash() { + const url = new URL("http://foo.bar"); + url.hash = "%foo bar/qat%qux#bar"; + assertEquals(url.hash, "#%foo%20bar/qat%qux#bar"); + url.hash = url.hash; + assertEquals(url.hash, "#%foo%20bar/qat%qux#bar"); +}); +Deno.test(function urlSearchParamsReuse() { + const url = new URL("https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat"); + const sp = url.searchParams; + url.host = "baz.qat"; + assert(sp === url.searchParams, "Search params should be reused."); +}); +Deno.test(function urlBackSlashes() { + const url = new URL("https:\\\\foo:bar@baz.qat:8000\\qux\\quux?foo=bar&baz=12#qat"); + assertEquals(url.href, "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat"); +}); +Deno.test(function urlProtocolSlashes() { + assertEquals(new URL("http:foo").href, "http://foo/"); + assertEquals(new URL("http://foo").href, "http://foo/"); + assertEquals(new URL("file:foo").href, "file:///foo"); + assertEquals(new URL("file://foo").href, "file://foo/"); + assertEquals(new URL("abcd:foo").href, "abcd:foo"); + assertEquals(new URL("abcd://foo").href, "abcd://foo"); +}); +Deno.test(function urlRequireHost() { + assertEquals(new URL("file:///").href, "file:///"); + assertThrows(()=>new URL("ftp:///"), TypeError, "Invalid URL"); + assertThrows(()=>new URL("http:///"), TypeError, "Invalid URL"); + assertThrows(()=>new URL("https:///"), TypeError, "Invalid URL"); + assertThrows(()=>new URL("ws:///"), TypeError, "Invalid URL"); + assertThrows(()=>new URL("wss:///"), TypeError, "Invalid URL"); +}); +Deno.test(function urlDriveLetter() { + assertEquals(new URL("file:///C:").href, "file:///C:"); + assertEquals(new URL("file:///C:/").href, "file:///C:/"); + assertEquals(new URL("file:///C:/..").href, "file:///C:/"); + assertEquals(new URL("file://foo/C:").href, "file:///C:"); +}); +Deno.test(function urlHostnameUpperCase() { + assertEquals(new URL("http://EXAMPLE.COM").href, "http://example.com/"); + assertEquals(new URL("abcd://EXAMPLE.COM").href, "abcd://EXAMPLE.COM"); +}); +Deno.test(function urlEmptyPath() { + assertEquals(new URL("http://foo").pathname, "/"); + assertEquals(new URL("file://foo").pathname, "/"); + assertEquals(new URL("abcd://foo").pathname, ""); +}); +Deno.test(function urlPathRepeatedSlashes() { + assertEquals(new URL("http://foo//bar//").pathname, "//bar//"); + assertEquals(new URL("file://foo///bar//").pathname, "/bar//"); + assertEquals(new URL("abcd://foo//bar//").pathname, "//bar//"); +}); +Deno.test(function urlTrim() { + assertEquals(new URL(" http://example.com ").href, "http://example.com/"); +}); +Deno.test(function urlEncoding() { + assertEquals(new URL("http://a !$&*()=,;+'\"@example.com").username, "a%20!$&*()%3D,%3B+'%22"); + assertEquals(new URL("http://:a !$&*()=,;+'\"@example.com").password, "a%20!$&*()%3D,%3B+'%22"); + assertEquals(new URL("http://mañana/c?d#e").hostname, "xn--maana-pta"); + assertEquals(new URL("abcd://mañana/c?d#e").hostname, "ma%C3%B1ana"); + assertEquals(new URL("http://example.com/a ~!@$&*()=:/,;+'\"\\").pathname, "/a%20~!@$&*()=:/,;+'%22/"); + assertEquals(new URL("http://example.com?a ~!@$&*()=:/,;?+'\"\\").search, "?a%20~!@$&*()=:/,;?+%27%22\\"); + assertEquals(new URL("abcd://example.com?a ~!@$&*()=:/,;?+'\"\\").search, "?a%20~!@$&*()=:/,;?+'%22\\"); + assertEquals(new URL("http://example.com#a ~!@#$&*()=:/,;?+'\"\\").hash, "#a%20~!@#$&*()=:/,;?+'%22\\"); +}); +Deno.test(function urlBase() { + assertEquals(new URL("d", new URL("http://foo/a?b#c")).href, "http://foo/d"); + assertEquals(new URL("", "http://foo/a/b?c#d").href, "http://foo/a/b?c"); + assertEquals(new URL("", "file://foo/a/b?c#d").href, "file://foo/a/b?c"); + assertEquals(new URL("", "abcd://foo/a/b?c#d").href, "abcd://foo/a/b?c"); + assertEquals(new URL("#e", "http://foo/a/b?c#d").href, "http://foo/a/b?c#e"); + assertEquals(new URL("#e", "file://foo/a/b?c#d").href, "file://foo/a/b?c#e"); + assertEquals(new URL("#e", "abcd://foo/a/b?c#d").href, "abcd://foo/a/b?c#e"); + assertEquals(new URL("?e", "http://foo/a/b?c#d").href, "http://foo/a/b?e"); + assertEquals(new URL("?e", "file://foo/a/b?c#d").href, "file://foo/a/b?e"); + assertEquals(new URL("?e", "abcd://foo/a/b?c#d").href, "abcd://foo/a/b?e"); + assertEquals(new URL("e", "http://foo/a/b?c#d").href, "http://foo/a/e"); + assertEquals(new URL("e", "file://foo/a/b?c#d").href, "file://foo/a/e"); + assertEquals(new URL("e", "abcd://foo/a/b?c#d").href, "abcd://foo/a/e"); + assertEquals(new URL(".", "http://foo/a/b?c#d").href, "http://foo/a/"); + assertEquals(new URL(".", "file://foo/a/b?c#d").href, "file://foo/a/"); + assertEquals(new URL(".", "abcd://foo/a/b?c#d").href, "abcd://foo/a/"); + assertEquals(new URL("..", "http://foo/a/b?c#d").href, "http://foo/"); + assertEquals(new URL("..", "file://foo/a/b?c#d").href, "file://foo/"); + assertEquals(new URL("..", "abcd://foo/a/b?c#d").href, "abcd://foo/"); + assertEquals(new URL("/e", "http://foo/a/b?c#d").href, "http://foo/e"); + assertEquals(new URL("/e", "file://foo/a/b?c#d").href, "file://foo/e"); + assertEquals(new URL("/e", "abcd://foo/a/b?c#d").href, "abcd://foo/e"); + assertEquals(new URL("//bar", "http://foo/a/b?c#d").href, "http://bar/"); + assertEquals(new URL("//bar", "file://foo/a/b?c#d").href, "file://bar/"); + assertEquals(new URL("//bar", "abcd://foo/a/b?c#d").href, "abcd://bar"); + assertEquals(new URL("efgh:", "http://foo/a/b?c#d").href, "efgh:"); + assertEquals(new URL("efgh:", "file://foo/a/b?c#d").href, "efgh:"); + assertEquals(new URL("efgh:", "abcd://foo/a/b?c#d").href, "efgh:"); + assertEquals(new URL("/foo", "abcd:/").href, "abcd:/foo"); +}); +Deno.test(function urlDriveLetterBase() { + assertEquals(new URL("/b", "file:///C:/a/b").href, "file:///C:/b"); + assertEquals(new URL("/D:", "file:///C:/a/b").href, "file:///D:"); +}); +Deno.test(function urlSameProtocolBase() { + assertEquals(new URL("http:", "http://foo/a").href, "http://foo/a"); + assertEquals(new URL("file:", "file://foo/a").href, "file://foo/a"); + assertEquals(new URL("abcd:", "abcd://foo/a").href, "abcd:"); + assertEquals(new URL("http:b", "http://foo/a").href, "http://foo/b"); + assertEquals(new URL("file:b", "file://foo/a").href, "file://foo/b"); + assertEquals(new URL("abcd:b", "abcd://foo/a").href, "abcd:b"); +}); +Deno.test(function deletingAllParamsRemovesQuestionMarkFromURL() { + const url = new URL("http://example.com/?param1¶m2"); + url.searchParams.delete("param1"); + url.searchParams.delete("param2"); + assertEquals(url.href, "http://example.com/"); + assertEquals(url.search, ""); +}); +Deno.test(function removingNonExistentParamRemovesQuestionMarkFromURL() { + const url = new URL("http://example.com/?"); + assertEquals(url.href, "http://example.com/?"); + url.searchParams.delete("param1"); + assertEquals(url.href, "http://example.com/"); + assertEquals(url.search, ""); +}); +Deno.test(function sortingNonExistentParamRemovesQuestionMarkFromURL() { + const url = new URL("http://example.com/?"); + assertEquals(url.href, "http://example.com/?"); + url.searchParams.sort(); + assertEquals(url.href, "http://example.com/"); + assertEquals(url.search, ""); +}); +Deno.test.ignore(function customInspectFunction() { + const url = new URL("http://example.com/?"); + assertEquals(Deno.inspect(url), `URL { + href: "http://example.com/?", + origin: "http://example.com", + protocol: "http:", + username: "", + password: "", + host: "example.com", + hostname: "example.com", + port: "", + pathname: "/", + hash: "", + search: "" +}`); +}); +Deno.test(function protocolNotHttpOrFile() { + const url = new URL("about:blank"); + assertEquals(url.href, "about:blank"); + assertEquals(url.protocol, "about:"); + assertEquals(url.origin, "null"); +}); +Deno.test(function throwForInvalidPortConstructor() { + const urls = [ + `https://baz.qat:${2 ** 16}`, + "https://baz.qat:-32", + "https://baz.qat:deno", + "https://baz.qat:9land", + "https://baz.qat:10.5" + ]; + for (const url of urls){ + assertThrows(()=>new URL(url), TypeError, "Invalid URL"); + } + new URL("https://baz.qat:65535"); + new URL("https://baz.qat:0"); +}); +Deno.test(function doNotOverridePortIfInvalid() { + const initialPort = "3000"; + const url = new URL(`https://deno.land:${initialPort}`); + url.port = `${2 ** 16}`; + assertEquals(url.port, initialPort); +}); +Deno.test(function emptyPortForSchemeDefaultPort() { + const nonDefaultPort = "3500"; + const url = new URL("ftp://baz.qat:21"); + assertEquals(url.port, ""); + url.port = nonDefaultPort; + assertEquals(url.port, nonDefaultPort); + url.port = "21"; + assertEquals(url.port, ""); + url.protocol = "http"; + assertEquals(url.port, ""); + const url2 = new URL("https://baz.qat:443"); + assertEquals(url2.port, ""); + url2.port = nonDefaultPort; + assertEquals(url2.port, nonDefaultPort); + url2.port = "443"; + assertEquals(url2.port, ""); + url2.protocol = "http"; + assertEquals(url2.port, ""); +}); +Deno.test(function assigningPortPropertyAffectsReceiverOnly() { + const u1 = new URL("http://google.com/"); + const u2 = new URL(u1 as any); + u2.port = "123"; + assertStrictEquals(u1.port, ""); + assertStrictEquals(u2.port, "123"); +}); +Deno.test(function urlSearchParamsIdentityPreserved() { + const u = new URL("http://foo.com/"); + const sp1 = u.searchParams; + u.href = "http://bar.com/?baz=42"; + const sp2 = u.searchParams; + assertStrictEquals(sp1, sp2); +}); +Deno.test(function urlTakeURLObjectAsParameter() { + const url = new URL(new URL("https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat")); + assertEquals(url.href, "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat"); +}); diff --git a/test/js/deno/url/urlsearchparams.test.ts b/test/js/deno/url/urlsearchparams.test.ts new file mode 100644 index 000000000..792a92a54 --- /dev/null +++ b/test/js/deno/url/urlsearchparams.test.ts @@ -0,0 +1,364 @@ +// Copyright 2018+ the Deno authors. All rights reserved. MIT license. +// https://raw.githubusercontent.com/denoland/deno/main/cli/tests/unit/url_search_params_test.ts +import { assert, assertEquals } from "deno:harness"; +Deno.test(function urlSearchParamsWithMultipleSpaces() { + const init = { + str: "this string has spaces in it" + }; + const searchParams = new URLSearchParams(init).toString(); + assertEquals(searchParams, "str=this+string+has+spaces+in+it"); +}); +Deno.test(function urlSearchParamsWithExclamation() { + const init = [ + [ + "str", + "hello, world!" + ] + ]; + const searchParams = new URLSearchParams(init).toString(); + assertEquals(searchParams, "str=hello%2C+world%21"); +}); +Deno.test(function urlSearchParamsWithQuotes() { + const init = [ + [ + "str", + "'hello world'" + ] + ]; + const searchParams = new URLSearchParams(init).toString(); + assertEquals(searchParams, "str=%27hello+world%27"); +}); +Deno.test(function urlSearchParamsWithBraket() { + const init = [ + [ + "str", + "(hello world)" + ] + ]; + const searchParams = new URLSearchParams(init).toString(); + assertEquals(searchParams, "str=%28hello+world%29"); +}); +Deno.test(function urlSearchParamsWithTilde() { + const init = [ + [ + "str", + "hello~world" + ] + ]; + const searchParams = new URLSearchParams(init).toString(); + assertEquals(searchParams, "str=hello%7Eworld"); +}); +Deno.test(function urlSearchParamsInitString() { + const init = "c=4&a=2&b=3&%C3%A1=1"; + const searchParams = new URLSearchParams(init); + assert(init === searchParams.toString(), "The init query string does not match"); +}); +Deno.test(function urlSearchParamsInitStringWithPlusCharacter() { + let params = new URLSearchParams("q=a+b"); + assertEquals(params.toString(), "q=a+b"); + assertEquals(params.get("q"), "a b"); + params = new URLSearchParams("q=a+b+c"); + assertEquals(params.toString(), "q=a+b+c"); + assertEquals(params.get("q"), "a b c"); +}); +Deno.test(function urlSearchParamsInitStringWithMalformedParams() { + let params = new URLSearchParams("id=0&value=%"); + assert(params != null, "constructor returned non-null value."); + assert(params.has("id"), 'Search params object has name "id"'); + assert(params.has("value"), 'Search params object has name "value"'); + assertEquals(params.get("id"), "0"); + assertEquals(params.get("value"), "%"); + params = new URLSearchParams("b=%2sf%2a"); + assert(params != null, "constructor returned non-null value."); + assert(params.has("b"), 'Search params object has name "b"'); + assertEquals(params.get("b"), "%2sf*"); + params = new URLSearchParams("b=%2%2af%2a"); + assert(params != null, "constructor returned non-null value."); + assert(params.has("b"), 'Search params object has name "b"'); + assertEquals(params.get("b"), "%2*f*"); + params = new URLSearchParams("b=%%2a"); + assert(params != null, "constructor returned non-null value."); + assert(params.has("b"), 'Search params object has name "b"'); + assertEquals(params.get("b"), "%*"); +}); +Deno.test(function urlSearchParamsInitIterable() { + const init = [ + [ + "a", + "54" + ], + [ + "b", + "true" + ] + ]; + const searchParams = new URLSearchParams(init); + assertEquals(searchParams.toString(), "a=54&b=true"); +}); +Deno.test(function urlSearchParamsInitRecord() { + const init = { + a: "54", + b: "true" + }; + const searchParams = new URLSearchParams(init); + assertEquals(searchParams.toString(), "a=54&b=true"); +}); +Deno.test(function urlSearchParamsInit() { + const params1 = new URLSearchParams("a=b"); + assertEquals(params1.toString(), "a=b"); + const params2 = new URLSearchParams(params1); + assertEquals(params2.toString(), "a=b"); +}); +Deno.test(function urlSearchParamsAppendSuccess() { + const searchParams = new URLSearchParams(); + searchParams.append("a", "true"); + assertEquals(searchParams.toString(), "a=true"); +}); +Deno.test(function urlSearchParamsDeleteSuccess() { + const init = "a=54&b=true"; + const searchParams = new URLSearchParams(init); + searchParams.delete("b"); + assertEquals(searchParams.toString(), "a=54"); +}); +Deno.test(function urlSearchParamsGetAllSuccess() { + const init = "a=54&b=true&a=true"; + const searchParams = new URLSearchParams(init); + assertEquals(searchParams.getAll("a"), [ + "54", + "true" + ]); + assertEquals(searchParams.getAll("b"), [ + "true" + ]); + assertEquals(searchParams.getAll("c"), []); +}); +Deno.test(function urlSearchParamsGetSuccess() { + const init = "a=54&b=true&a=true"; + const searchParams = new URLSearchParams(init); + assertEquals(searchParams.get("a"), "54"); + assertEquals(searchParams.get("b"), "true"); + assertEquals(searchParams.get("c"), null); +}); +Deno.test(function urlSearchParamsHasSuccess() { + const init = "a=54&b=true&a=true"; + const searchParams = new URLSearchParams(init); + assert(searchParams.has("a")); + assert(searchParams.has("b")); + assert(!searchParams.has("c")); +}); +Deno.test(function urlSearchParamsSetReplaceFirstAndRemoveOthers() { + const init = "a=54&b=true&a=true"; + const searchParams = new URLSearchParams(init); + searchParams.set("a", "false"); + assertEquals(searchParams.toString(), "a=false&b=true"); +}); +Deno.test(function urlSearchParamsSetAppendNew() { + const init = "a=54&b=true&a=true"; + const searchParams = new URLSearchParams(init); + searchParams.set("c", "foo"); + assertEquals(searchParams.toString(), "a=54&b=true&a=true&c=foo"); +}); +Deno.test(function urlSearchParamsSortSuccess() { + const init = "c=4&a=2&b=3&a=1"; + const searchParams = new URLSearchParams(init); + searchParams.sort(); + assertEquals(searchParams.toString(), "a=2&a=1&b=3&c=4"); +}); +Deno.test(function urlSearchParamsForEachSuccess() { + const init = [ + [ + "a", + "54" + ], + [ + "b", + "true" + ] + ]; + const searchParams = new URLSearchParams(init); + let callNum = 0; + searchParams.forEach((value, key, parent)=>{ + assertEquals(searchParams, parent); + assertEquals(value, init[callNum][1]); + assertEquals(key, init[callNum][0]); + callNum++; + }); + assertEquals(callNum, init.length); +}); +Deno.test(function urlSearchParamsMissingName() { + const init = "=4"; + const searchParams = new URLSearchParams(init); + assertEquals(searchParams.get(""), "4"); + assertEquals(searchParams.toString(), "=4"); +}); +Deno.test(function urlSearchParamsMissingValue() { + const init = "4="; + const searchParams = new URLSearchParams(init); + assertEquals(searchParams.get("4"), ""); + assertEquals(searchParams.toString(), "4="); +}); +Deno.test(function urlSearchParamsMissingEqualSign() { + const init = "4"; + const searchParams = new URLSearchParams(init); + assertEquals(searchParams.get("4"), ""); + assertEquals(searchParams.toString(), "4="); +}); +Deno.test(function urlSearchParamsMissingPair() { + const init = "c=4&&a=54&"; + const searchParams = new URLSearchParams(init); + assertEquals(searchParams.toString(), "c=4&a=54"); +}); +Deno.test(function urlSearchParamsForShortEncodedChar() { + const init = { + linefeed: "\n", + tab: "\t" + }; + const searchParams = new URLSearchParams(init); + assertEquals(searchParams.toString(), "linefeed=%0A&tab=%09"); +}); +Deno.test(function urlSearchParamsShouldThrowTypeError() { + let hasThrown = 0; + try { + new URLSearchParams([ + [ + "1" + ] + ]); + hasThrown = 1; + } catch (err) { + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEquals(hasThrown, 2); + try { + new URLSearchParams([ + [ + "1", + "2", + "3" + ] + ]); + hasThrown = 1; + } catch (err) { + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEquals(hasThrown, 2); +}); +Deno.test(function urlSearchParamsAppendArgumentsCheck() { + const methodRequireOneParam = [ + "delete", + "getAll", + "get", + "has", + "forEach" + ]; + const methodRequireTwoParams = [ + "append", + "set" + ]; + methodRequireOneParam.concat(methodRequireTwoParams).forEach((method: string)=>{ + const searchParams = new URLSearchParams(); + let hasThrown = 0; + try { + (searchParams as any)[method](); + hasThrown = 1; + } catch (err) { + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEquals(hasThrown, 2); + }); + methodRequireTwoParams.forEach((method: string)=>{ + const searchParams = new URLSearchParams(); + let hasThrown = 0; + try { + (searchParams as any)[method]("foo"); + hasThrown = 1; + } catch (err) { + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEquals(hasThrown, 2); + }); +}); +Deno.test(function urlSearchParamsDeletingAppendedMultiple() { + const params = new URLSearchParams(); + params.append("first", (1 as unknown) as string); + assert(params.has("first")); + assertEquals(params.get("first"), "1"); + params.delete("first"); + assertEquals(params.has("first"), false); + params.append("first", (1 as unknown) as string); + params.append("first", (10 as unknown) as string); + params.delete("first"); + assertEquals(params.has("first"), false); +}); +Deno.test(function urlSearchParamsCustomSymbolIterator() { + const params = new URLSearchParams(); + params[Symbol.iterator] = function*(): IterableIterator<[string, string]> { + yield [ + "a", + "b" + ]; + }; + const params1 = new URLSearchParams((params as unknown) as string[][]); + assertEquals(params1.get("a"), "b"); +}); +Deno.test(function urlSearchParamsCustomSymbolIteratorWithNonStringParams() { + const params = {}; + (params as any)[Symbol.iterator] = function*(): IterableIterator<[number, number]> { + yield [ + 1, + 2 + ]; + }; + const params1 = new URLSearchParams((params as unknown) as string[][]); + assertEquals(params1.get("1"), "2"); +}); +Deno.test(function urlSearchParamsOverridingAppendNotChangeConstructorAndSet() { + let overridedAppendCalled = 0; + class CustomSearchParams extends URLSearchParams { + append(name: string, value: string) { + ++overridedAppendCalled; + super.append(name, value); + } + } + new CustomSearchParams("foo=bar"); + new CustomSearchParams([ + [ + "foo", + "bar" + ] + ]); + new CustomSearchParams(new CustomSearchParams({ + foo: "bar" + })); + new CustomSearchParams().set("foo", "bar"); + assertEquals(overridedAppendCalled, 0); +}); +Deno.test(function urlSearchParamsOverridingEntriesNotChangeForEach() { + class CustomSearchParams extends URLSearchParams { + *entries(): IterableIterator<[string, string]> { + yield* []; + } + } + let loopCount = 0; + const params = new CustomSearchParams({ + foo: "bar" + }); + params.forEach(()=>void ++loopCount); + assertEquals(loopCount, 1); +}); diff --git a/test/js/deno/v8/error.test.ts b/test/js/deno/v8/error.test.ts new file mode 100644 index 000000000..d036c5eb9 --- /dev/null +++ b/test/js/deno/v8/error.test.ts @@ -0,0 +1,39 @@ +// Copyright 2018+ the Deno authors. All rights reserved. MIT license. +// https://raw.githubusercontent.com/denoland/deno/main/cli/tests/unit/error_stack_test.ts +import { assertEquals, assertMatch } from "deno:harness"; +Deno.test(function errorStackMessageLine() { + const e1 = new Error(); + e1.name = "Foo"; + e1.message = "bar"; + assertMatch(e1.stack!, /^Foo: bar\n/); + const e2 = new Error(); + e2.name = ""; + e2.message = "bar"; + assertMatch(e2.stack!, /^bar\n/); + const e3 = new Error(); + e3.name = "Foo"; + e3.message = ""; + assertMatch(e3.stack!, /^Foo\n/); + const e4 = new Error(); + e4.name = ""; + e4.message = ""; + assertMatch(e4.stack!, /^\n/); + const e5 = new Error(); + e5.name = undefined; + e5.message = undefined; + assertMatch(e5.stack!, /^Error\n/); + const e6 = new Error(); + e6.name = null; + e6.message = null; + assertMatch(e6.stack!, /^null: null\n/); +}); +Deno.test(function captureStackTrace() { + function foo() { + const error = new Error(); + const stack1 = error.stack!; + Error.captureStackTrace(error, foo); + const stack2 = error.stack!; + assertEquals(stack2, stack1.replace(/(?<=^[^\n]*\n)[^\n]*\n/, "")); + } + foo(); +}); diff --git a/test/js/deno/websocket/websocket.test.ts b/test/js/deno/websocket/websocket.test.ts new file mode 100644 index 000000000..f414d4adf --- /dev/null +++ b/test/js/deno/websocket/websocket.test.ts @@ -0,0 +1,19 @@ +// Copyright 2018+ the Deno authors. All rights reserved. MIT license. +// https://raw.githubusercontent.com/denoland/deno/main/cli/tests/unit/websocket_test.ts +import { assertEquals, assertThrows, deferred, fail } from "deno:harness"; +Deno.test({ + permissions: "none" +}, function websocketPermissionless() { + assertThrows(()=>new WebSocket("ws://localhost"), Deno.errors.PermissionDenied); +}); +Deno.test(async function websocketConstructorTakeURLObjectAsParameter() { + const promise = deferred(); + const ws = new WebSocket(new URL("ws://localhost:4242/")); + assertEquals(ws.url, "ws://localhost:4242/"); + ws.onerror = ()=>fail(); + ws.onopen = ()=>ws.close(); + ws.onclose = ()=>{ + promise.resolve(); + }; + await promise; +}); |
