From 6583a061c622dec33486b565535fd3182b71ed98 Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Mon, 13 May 2024 22:22:08 +0000 Subject: [PATCH 01/20] test: move FastifyController tests to `node:test` _I recommend reviewing this with whitespace changes disabled._ This test-only change drops Brittle from our FastifyController tests and replaces them with `node:test` and `node:assert`. --- tests/fastify-controller.js | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/tests/fastify-controller.js b/tests/fastify-controller.js index 46ee1cff0..f82d7d2ad 100644 --- a/tests/fastify-controller.js +++ b/tests/fastify-controller.js @@ -1,10 +1,11 @@ // @ts-check -import { test } from 'brittle' +import test from 'node:test' +import assert from 'node:assert/strict' import Fastify from 'fastify' import { FastifyController } from '../src/fastify-controller.js' -test('lifecycle', async (t) => { +test('lifecycle', async () => { const fastify = Fastify() const fastifyController = new FastifyController({ fastify }) @@ -15,17 +16,17 @@ test('lifecycle', async (t) => { { host: '0.0.0.0' }, ] - for (const opts of startOptsFixtures) { - await fastifyController.start(opts) - await fastifyController.start(opts) - await fastifyController.stop() - await fastifyController.stop() + assert.doesNotReject(async () => { + for (const opts of startOptsFixtures) { + await fastifyController.start(opts) + await fastifyController.start(opts) + await fastifyController.stop() + await fastifyController.stop() - fastifyController.start(opts) - await fastifyController.started() - await fastifyController.started() - await fastifyController.stop() - - t.pass('server lifecycle works with valid opts') - } + fastifyController.start(opts) + await fastifyController.started() + await fastifyController.started() + await fastifyController.stop() + } + }) }) From ceb777afabbcff8f503d7b5bf8201bd0a4fb7750 Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Wed, 15 May 2024 12:03:01 -0500 Subject: [PATCH 02/20] chore: update dot-prop dependency to 9.0.0 (#633) This updates dot-prop to its latest version, v9.0.0. The only breaking change in [the release notes][0] doesn't affect us: Node 18+ is now required. [0]: https://github.com/sindresorhus/dot-prop/releases/tag/v9.0.0 --- package-lock.json | 29 +++++++++-------------------- package.json | 2 +- 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3cf31a1ee..b9d335af9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "compact-encoding": "^2.12.0", "corestore": "^6.8.4", "debug": "^4.3.4", - "dot-prop": "^8.0.2", + "dot-prop": "^9.0.0", "drizzle-orm": "^0.30.8", "fastify": ">= 4", "fastify-plugin": "^4.5.1", @@ -3193,25 +3193,14 @@ } }, "node_modules/dot-prop": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-8.0.2.tgz", - "integrity": "sha512-xaBe6ZT4DHPkg0k4Ytbvn5xoxgpG0jOS1dYxSOwAHPuNLjP3/OzN0gH55SrLqpx8cBfSaVt91lXYkApjb+nYdQ==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz", + "integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==", "dependencies": { - "type-fest": "^3.8.0" + "type-fest": "^4.18.2" }, "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/dot-prop/node_modules/type-fest": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", - "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", - "engines": { - "node": ">=14.16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -8742,9 +8731,9 @@ } }, "node_modules/type-fest": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.8.1.tgz", - "integrity": "sha512-ShaaYnjf+0etG8W/FumARKMjjIToy/haCaTjN2dvcewOSoNqCQzdgG7m2JVOlM5qndGTHjkvsrWZs+k/2Z7E0Q==", + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.18.2.tgz", + "integrity": "sha512-+suCYpfJLAe4OXS6+PPXjW3urOS4IoP9waSiLuXfLgqZODKw/aWwASvzqE886wA0kQgGy0mIWyhd87VpqIy6Xg==", "engines": { "node": ">=16" }, diff --git a/package.json b/package.json index a85868ac6..369ac4015 100644 --- a/package.json +++ b/package.json @@ -156,7 +156,7 @@ "compact-encoding": "^2.12.0", "corestore": "^6.8.4", "debug": "^4.3.4", - "dot-prop": "^8.0.2", + "dot-prop": "^9.0.0", "drizzle-orm": "^0.30.8", "fastify": ">= 4", "fastify-plugin": "^4.5.1", From d06074bd2206be8c01181ad6a36c7e1e8395a218 Mon Sep 17 00:00:00 2001 From: tomasciccola <117094913+tomasciccola@users.noreply.github.com> Date: Wed, 15 May 2024 15:04:49 -0300 Subject: [PATCH 03/20] chore: ignore docs folder for linting and prettier (#653) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ignore docs folder for linting and prettier Co-authored-by: Tomás Ciccola --- .prettierignore | 3 ++- package.json | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.prettierignore b/.prettierignore index 110243302..b79734be6 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,4 +2,5 @@ /drizzle/*/meta/ /src/generated/ *.snapshot.cjs -/tests/fixtures/ \ No newline at end of file +/tests/fixtures/ +/docs/ diff --git a/package.json b/package.json index 369ac4015..421c12a6d 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,10 @@ ] } ] - } + }, + "ignorePatterns": [ + "docs/*" + ] }, "repository": { "type": "git", From e773b90d5fed9a36d7d3ef1b4ffbfdfbb457f26f Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Wed, 15 May 2024 17:05:25 -0400 Subject: [PATCH 04/20] chore: fix lockfile to include platform-specific binaries (#654) --- package-lock.json | 181 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) diff --git a/package-lock.json b/package-lock.json index b9d335af9..7528f0429 100644 --- a/package-lock.json +++ b/package-lock.json @@ -248,6 +248,22 @@ "@bufbuild/buf-win32-x64": "1.26.1" } }, + "node_modules/@bufbuild/buf-darwin-arm64": { + "version": "1.26.1", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.26.1.tgz", + "integrity": "sha512-nmyWiT/59RFja0ZuXFxjNGoAMDPTApU66CZUUevkFVWbNB9nzeQDjx2vsJyACY64k5fTgZiaelSiyppwObQknw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@digidem/types": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@digidem/types/-/types-2.3.0.tgz", @@ -1727,6 +1743,126 @@ "@node-rs/crc32-win32-x64-msvc": "1.7.2" } }, + "node_modules/@node-rs/crc32-android-arm-eabi": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-android-arm-eabi/-/crc32-android-arm-eabi-1.7.2.tgz", + "integrity": "sha512-6IoXQTHt9U/1Ejz/MPbAk3mtcAGcS1WUvg2YfEtezLCmzbDpQO3OTA9fZpu3z2AhBuLHiKMKDVcfrWybRiWBJw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-android-arm64": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-android-arm64/-/crc32-android-arm64-1.7.2.tgz", + "integrity": "sha512-SMEd6cN+034LTv9kFmCGMZjBNTf39xXIcgqq05JM9A55ywUvXdoXnFOttrQ9x/iZgqANNU6Ms5uZCAJbNA2dZA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-darwin-arm64": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-darwin-arm64/-/crc32-darwin-arm64-1.7.2.tgz", + "integrity": "sha512-sPJisK5pyZ+iBs9KuGsvu0Z+Qshw4GvOgaHjPktQ+suz0p00Yts3zl5D6PpGaaW4EAKTo8zCUIlVEArV0vglvw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-darwin-x64": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-darwin-x64/-/crc32-darwin-x64-1.7.2.tgz", + "integrity": "sha512-+/lgHYJaZdXU+7fhGYTnXvGkeSqZE3UwPyKAUO5YSL0nIpFHMybZMnvqjcoxrfx0QfMFOwVEbd7vfVh+1GpwhA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-freebsd-x64": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-freebsd-x64/-/crc32-freebsd-x64-1.7.2.tgz", + "integrity": "sha512-OgkxnkiGdztcBilm7m31Sb6zx89ghK4WpZz9WVVU86PIHQH0sfrZEebdomw6R7mMnQuqbnRwjTS5r1nchVMPzQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-linux-arm-gnueabihf": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-linux-arm-gnueabihf/-/crc32-linux-arm-gnueabihf-1.7.2.tgz", + "integrity": "sha512-hTY83MQML8WrMnD3dmzjrcCn0Sqgw0w2wRc1Ji2dCaE0fDqra47W5KBQXx4hKZYFwNr5KreTqdvD3Ejf/mKzEA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-linux-arm64-gnu": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-linux-arm64-gnu/-/crc32-linux-arm64-gnu-1.7.2.tgz", + "integrity": "sha512-4p6DZ9YT+CBSi+72OclzI5hBin15brqrbLLHFePPl4AhAazg6+ReTv3C4DnyJqyL0ZHZamiA9zDtOlvHo0nk0Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-linux-arm64-musl": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-linux-arm64-musl/-/crc32-linux-arm64-musl-1.7.2.tgz", + "integrity": "sha512-/shZkkNyDyDjaxU5rYFY4aoajLjBqdfKQYZCcA6XS27FiGzHQ3petgP0I5Zjm+Jf75G7gLT8NQXiQWIzkgo2xw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@node-rs/crc32-linux-x64-gnu": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/@node-rs/crc32-linux-x64-gnu/-/crc32-linux-x64-gnu-1.7.2.tgz", @@ -1757,6 +1893,51 @@ "node": ">= 10" } }, + "node_modules/@node-rs/crc32-win32-arm64-msvc": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-win32-arm64-msvc/-/crc32-win32-arm64-msvc-1.7.2.tgz", + "integrity": "sha512-vj+HWzwy86wNBY+1vW+QPje/MrJppufGCYIisFwvghBzk6WtClNGEjbQqotieIxDNohcmHREQEeg8wY8PMCvew==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-win32-ia32-msvc": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-win32-ia32-msvc/-/crc32-win32-ia32-msvc-1.7.2.tgz", + "integrity": "sha512-YQQtPkHvqbMEJmaMzEH3diYHk0q9zWb+Tkzij9d4OZZzpt4HM6j8FuiIB37BJ0CQmgMiDZEBYsX3KOfYxxO0VA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-win32-x64-msvc": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-win32-x64-msvc/-/crc32-win32-x64-msvc-1.7.2.tgz", + "integrity": "sha512-DnluAFM6X8qsYVI1VaFQtI6ukigIQ2P4eVcEuNQ3d1lF5fs0RYAKY7Ajqrdk298TSGZ2joMiqfJksTHBsQoxtA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "dev": true, From f623f1011c971e973e7133fc502acd4bf3844975 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Wed, 15 May 2024 17:49:42 -0400 Subject: [PATCH 05/20] chore: make prettier ignore coverage/ and proto/build/ directories (#656) --- .prettierignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.prettierignore b/.prettierignore index b79734be6..a693f40c0 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,3 +4,5 @@ *.snapshot.cjs /tests/fixtures/ /docs/ +/coverage/ +/proto/build/ From e32162976fe8d43ac3520938546606e3803a5e75 Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Thu, 16 May 2024 08:44:10 -0500 Subject: [PATCH 06/20] test: move LocalDiscovery tests to `node:test` (#642) This test-only change drops Brittle from our LocalDiscovery tests and replaces them with `node:test` and `node:assert`. --- tests/discovery/local-discovery.js | 36 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/discovery/local-discovery.js b/tests/discovery/local-discovery.js index 8408355b6..d0774c9a3 100644 --- a/tests/discovery/local-discovery.js +++ b/tests/discovery/local-discovery.js @@ -1,4 +1,5 @@ -import test from 'brittle' +import test from 'node:test' +import assert from 'node:assert/strict' import { randomBytes } from 'node:crypto' import net from 'node:net' import { every } from 'iterpal' @@ -27,19 +28,19 @@ test('peer discovery - discovery and sharing of data', async (t) => { const str = 'hi' localDiscovery1.on('connection', (stream) => { - stream.on('error', handleConnectionError.bind(null, t)) + stream.on('error', handleConnectionError) stream.write(str) }) localDiscovery2.on('connection', (stream) => { - stream.on('error', handleConnectionError.bind(null, t)) + stream.on('error', handleConnectionError) stream.on('data', (d) => { - t.is(d.toString(), str, 'expected data written') + assert.equal(d.toString(), str, 'expected data written') deferred.resolve() }) }) - t.teardown(() => + t.after(() => Promise.all([ localDiscovery1.stop({ force: true }), localDiscovery2.stop({ force: true }), @@ -56,7 +57,7 @@ test('peer discovery - discovery and sharing of data', async (t) => { return deferred.promise }) -test('deduplicate incoming connections', async (t) => { +test('deduplicate incoming connections', async () => { const localConnections = new Set() const remoteConnections = new Set() @@ -66,23 +67,23 @@ test('deduplicate incoming connections', async (t) => { const { port } = await discovery.start() discovery.on('connection', (conn) => { - conn.on('error', handleConnectionError.bind(null, t)) + conn.on('error', handleConnectionError) localConnections.add(conn) conn.on('close', () => localConnections.delete(conn)) }) for (let i = 0; i < 20; i++) { noiseConnect(port, '127.0.0.1', remoteKp).then((conn) => { - conn.on('error', handleConnectionError.bind(null, t)) + conn.on('error', handleConnectionError) conn.on('connect', () => remoteConnections.add(conn)) conn.on('close', () => remoteConnections.delete(conn)) }) } await delay(1000) - t.is(localConnections.size, 1) - t.is(remoteConnections.size, 1) - t.alike( + assert.equal(localConnections.size, 1) + assert.equal(remoteConnections.size, 1) + assert.deepEqual( localConnections.values().next().value.handshakeHash, remoteConnections.values().next().value.handshakeHash ) @@ -108,7 +109,7 @@ async function noiseConnect(port, host, keyPair) { } /** - * @param {any} t + * @param {import('node:test').TestContext} t * @param {object} opts * @param {number} opts.period Randomly spawn peers within this period * @param {number} [opts.nPeers] Number of peers to spawn (default 20) @@ -141,7 +142,7 @@ async function testMultiple(t, { period, nPeers = 20 }) { const conns = [] connsById.set(peerId, conns) discovery.on('connection', (conn) => { - conn.on('error', handleConnectionError.bind(null, t)) + conn.on('error', handleConnectionError) conns.push(conn) onConnection() }) @@ -151,7 +152,7 @@ async function testMultiple(t, { period, nPeers = 20 }) { const servers = await Promise.all( peers.map(async (peer) => { const result = await peer.start() - t.teardown(() => peer.stop({ force: true })) + t.after(() => peer.stop({ force: true })) return result }) ) @@ -180,7 +181,7 @@ async function testMultiple(t, { period, nPeers = 20 }) { .filter((conn) => !conn.destroyed) .map((conn) => keyToPublicId(conn.remotePublicKey)) .sort() - t.alike( + assert.deepEqual( actual, expected, `peer ${peerId.slice(0, 7)} connected to all ${ @@ -191,15 +192,14 @@ async function testMultiple(t, { period, nPeers = 20 }) { } /** - * @param {import('brittle').TestInstance} t * @param {Error} e */ -function handleConnectionError(t, e) { +function handleConnectionError(e) { // We expected connections to be closed when duplicates happen. On the // closing side the error will be ERR_DUPLICATE, but on the other side // the error will be an ECONNRESET - the error is not sent over the // connection const expectedError = e.message === ERR_DUPLICATE || ('code' in e && e.code === 'ECONNRESET') - t.ok(expectedError, 'connection closed with expected error') + assert(expectedError, 'connection closed with expected error') } From 41de8082fe3d9e1ce4936f55edfc4403bb4e545a Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Thu, 16 May 2024 08:47:16 -0500 Subject: [PATCH 07/20] test: move Fastify plugin tests to `node:test` and `node:assert` (#635) This test-only change drops Brittle from our Fastify plugin tests and replaces them with `node:test` and `node:assert`. --- tests/fastify-plugins/blobs.js | 73 +++++++++++-------- tests/fastify-plugins/icons.js | 23 +++--- tests/fastify-plugins/maps.js | 33 +++++---- tests/fastify-plugins/offline-fallback-map.js | 15 ++-- tests/fastify-plugins/static-maps.js | 57 ++++++++------- 5 files changed, 108 insertions(+), 93 deletions(-) diff --git a/tests/fastify-plugins/blobs.js b/tests/fastify-plugins/blobs.js index 42d5985b2..d14587b6b 100644 --- a/tests/fastify-plugins/blobs.js +++ b/tests/fastify-plugins/blobs.js @@ -1,6 +1,7 @@ // @ts-check import { randomBytes } from 'node:crypto' -import test from 'brittle' +import test from 'node:test' +import assert from 'node:assert/strict' import { readdirSync } from 'fs' import { readFile } from 'fs/promises' import path from 'path' @@ -11,12 +12,14 @@ import { projectKeyToPublicId } from '../../src/utils.js' import { createBlobStore } from '../helpers/blob-store.js' import { waitForCores, replicate } from '../helpers/core-manager.js' -test('Plugin throws error if missing getBlobStore option', async (t) => { +test('Plugin throws error if missing getBlobStore option', async () => { const server = fastify() - await t.exception(() => server.register(BlobServerPlugin)) + await assert.rejects(async () => { + await server.register(BlobServerPlugin) + }) }) -test('Plugin handles prefix option properly', async (t) => { +test('Plugin handles prefix option properly', async () => { const prefix = '/blobs' const { data, server, projectPublicId } = await setup({ prefix }) @@ -30,11 +33,11 @@ test('Plugin handles prefix option properly', async (t) => { }), }) - t.is(res.statusCode, 200, 'request successful') + assert.equal(res.statusCode, 200, 'request successful') } }) -test('Unsupported blob type and variant params are handled properly', async (t) => { +test('Unsupported blob type and variant params are handled properly', async () => { const { data, server, projectPublicId } = await setup() for (const { blobId } of data) { @@ -47,8 +50,8 @@ test('Unsupported blob type and variant params are handled properly', async (t) }), }) - t.is(unsupportedVariantRes.statusCode, 400) - t.is(unsupportedVariantRes.json().code, 'FST_ERR_VALIDATION') + assert.equal(unsupportedVariantRes.statusCode, 400) + assert.equal(unsupportedVariantRes.json().code, 'FST_ERR_VALIDATION') const unsupportedTypeRes = await server.inject({ method: 'GET', @@ -59,12 +62,12 @@ test('Unsupported blob type and variant params are handled properly', async (t) }), }) - t.is(unsupportedTypeRes.statusCode, 400) - t.is(unsupportedTypeRes.json().code, 'FST_ERR_VALIDATION') + assert.equal(unsupportedTypeRes.statusCode, 400) + assert.equal(unsupportedTypeRes.json().code, 'FST_ERR_VALIDATION') } }) -test('Invalid variant-type combination returns error', async (t) => { +test('Invalid variant-type combination returns error', async () => { const { server, projectPublicId } = await setup() const url = buildRouteUrl({ @@ -77,11 +80,11 @@ test('Invalid variant-type combination returns error', async (t) => { const response = await server.inject({ method: 'GET', url }) - t.is(response.statusCode, 400) - t.ok(response.json().message.startsWith('Unsupported variant')) + assert.equal(response.statusCode, 400) + assert(response.json().message.startsWith('Unsupported variant')) }) -test('Incorrect project public id returns 404', async (t) => { +test('Incorrect project public id returns 404', async () => { const { data, server } = await setup() const incorrectProjectPublicId = projectKeyToPublicId(randomBytes(32)) @@ -95,11 +98,11 @@ test('Incorrect project public id returns 404', async (t) => { }), }) - t.is(incorrectProjectPublicIdRes.statusCode, 404) + assert.equal(incorrectProjectPublicIdRes.statusCode, 404) } }) -test('Incorrectly formatted project public id returns 400', async (t) => { +test('Incorrectly formatted project public id returns 400', async () => { const { data, server } = await setup() const hexString = randomBytes(32).toString('hex') @@ -113,11 +116,11 @@ test('Incorrectly formatted project public id returns 400', async (t) => { }), }) - t.is(incorrectProjectPublicIdRes.statusCode, 400) + assert.equal(incorrectProjectPublicIdRes.statusCode, 400) } }) -test('Missing blob name or variant returns 404', async (t) => { +test('Missing blob name or variant returns 404', async () => { const { data, server, projectPublicId } = await setup() for (const { blobId } of data) { @@ -130,7 +133,7 @@ test('Missing blob name or variant returns 404', async (t) => { }), }) - t.is(nameMismatchRes.statusCode, 404) + assert.equal(nameMismatchRes.statusCode, 404) const variantMismatchRes = await server.inject({ method: 'GET', @@ -141,11 +144,11 @@ test('Missing blob name or variant returns 404', async (t) => { }), }) - t.is(variantMismatchRes.statusCode, 404) + assert.equal(variantMismatchRes.statusCode, 404) } }) -test('GET photo returns correct blob payload', async (t) => { +test('GET photo returns correct blob payload', async () => { const { data, server, projectPublicId } = await setup() for (const { blobId, image } of data) { @@ -157,11 +160,11 @@ test('GET photo returns correct blob payload', async (t) => { }), }) - t.alike(res.rawPayload, image.data, 'should be equal') + assert.deepEqual(res.rawPayload, image.data, 'should be equal') } }) -test('GET photo returns inferred content header if metadata is not found', async (t) => { +test('GET photo returns inferred content header if metadata is not found', async () => { const { data, server, projectPublicId } = await setup() for (const { blobId, image } of data) { @@ -176,11 +179,15 @@ test('GET photo returns inferred content header if metadata is not found', async const expectedContentHeader = getImageMimeType(image.ext) || 'application/octet-stream' - t.is(res.headers['content-type'], expectedContentHeader, 'should be equal') + assert.equal( + res.headers['content-type'], + expectedContentHeader, + 'should be equal' + ) } }) -test('GET photo uses mime type from metadata if found', async (t) => { +test('GET photo uses mime type from metadata if found', async () => { const { data, server, projectPublicId, blobStore } = await setup() for (const { blobId, image } of data) { @@ -204,11 +211,15 @@ test('GET photo uses mime type from metadata if found', async (t) => { ? metadata.mimeType : 'application/octet-stream' - t.is(res.headers['content-type'], expectedContentHeader, 'should be equal') + assert.equal( + res.headers['content-type'], + expectedContentHeader, + 'should be equal' + ) } }) -test('GET photo returns 404 when trying to get non-replicated blob', async (t) => { +test('GET photo returns 404 when trying to get non-replicated blob', async () => { const projectKey = randomBytes(32) const { @@ -242,10 +253,10 @@ test('GET photo returns 404 when trying to get non-replicated blob', async (t) = url: buildRouteUrl({ ...blobId, projectPublicId }), }) - t.is(res.statusCode, 404) + assert.equal(res.statusCode, 404) }) -test('GET photo returns 404 when trying to get non-existent blob', async (t) => { +test('GET photo returns 404 when trying to get non-existent blob', async () => { const projectKey = randomBytes(32) const { projectPublicId, blobStore } = await setup({ projectKey }) @@ -271,7 +282,7 @@ test('GET photo returns 404 when trying to get non-existent blob', async (t) => }), }) - t.is(res.statusCode, 404) + assert.equal(res.statusCode, 404) } const driveId = await blobStore.put(blobId, expected) @@ -284,7 +295,7 @@ test('GET photo returns 404 when trying to get non-existent blob', async (t) => url: buildRouteUrl({ ...blobId, projectPublicId, driveId }), }) - t.is(res.statusCode, 404) + assert.equal(res.statusCode, 404) } }) diff --git a/tests/fastify-plugins/icons.js b/tests/fastify-plugins/icons.js index af68ed02f..08a2d29e6 100644 --- a/tests/fastify-plugins/icons.js +++ b/tests/fastify-plugins/icons.js @@ -1,17 +1,20 @@ // @ts-check -import { test } from 'brittle' +import test from 'node:test' +import assert from 'node:assert/strict' import { randomBytes } from 'crypto' import fastify from 'fastify' import IconServerPlugin from '../../src/fastify-plugins/icons.js' import { projectKeyToPublicId } from '../../src/utils.js' -test('Plugin throws error if missing getProject option', async (t) => { +test('Plugin throws error if missing getProject option', async () => { const server = fastify() - await t.exception(() => server.register(IconServerPlugin)) + await assert.rejects(async () => { + await server.register(IconServerPlugin) + }) }) -test('Plugin handles prefix option properly', async (t) => { +test('Plugin handles prefix option properly', async () => { const prefix = 'icons' const server = fastify() @@ -33,10 +36,10 @@ test('Plugin handles prefix option properly', async (t) => { })}`, }) - t.not(response.statusCode, 404, 'returns non-404 status code') + assert.notEqual(response.statusCode, 404, 'returns non-404 status code') }) -test('url param validation', async (t) => { +test('url param validation', async () => { const server = fastify() server.register(IconServerPlugin, { @@ -99,16 +102,14 @@ test('url param validation', async (t) => { ] await Promise.all( - fixtures.map(async ([name, input]) => { + fixtures.map(async ([_, input]) => { const response = await server.inject({ method: 'GET', url: buildIconUrl(input), }) - t.comment(name) - - t.is(response.statusCode, 400, 'returns expected status code') - t.is( + assert.equal(response.statusCode, 400, 'returns expected status code') + assert.equal( response.json().code, 'FST_ERR_VALIDATION', 'error is validation error' diff --git a/tests/fastify-plugins/maps.js b/tests/fastify-plugins/maps.js index 93a8fe86d..6bce9d810 100644 --- a/tests/fastify-plugins/maps.js +++ b/tests/fastify-plugins/maps.js @@ -1,4 +1,5 @@ -import { test } from 'brittle' +import test from 'node:test' +import assert from 'node:assert/strict' import path from 'node:path' import Fastify from 'fastify' import { MockAgent, setGlobalDispatcher } from 'undici' @@ -28,7 +29,7 @@ setupFetchMock() test('fails to register when dependent plugins are not registered', async (t) => { const server = setup(t) - await t.exception(async () => { + await assert.rejects(async () => { await server.register(MapServerPlugin) }, 'fails to register if dependencies are not registered') }) @@ -56,12 +57,12 @@ test('prefix opt is handled correctly', async (t) => { url: '/style.json', }) - t.is(response.statusCode, 404, 'endpoint missing at root prefix') + assert.equal(response.statusCode, 404, 'endpoint missing at root prefix') } { // TODO: Use inject approach when necessary fixtures are set up - t.ok( + assert( server.hasRoute({ method: 'GET', url: '/maps/style.json', @@ -89,11 +90,11 @@ test('mapeoMaps decorator context', async (t) => { const address = await server.listen() - t.ok(server.hasDecorator('mapeoMaps'), 'decorator added') + assert(server.hasDecorator('mapeoMaps'), 'decorator added') - t.test('mapeoMaps.getStyleJsonUrl()', async (st) => { + t.test('mapeoMaps.getStyleJsonUrl()', async () => { const styleJsonUrl = await server.mapeoMaps.getStyleJsonUrl() - st.is(styleJsonUrl, new URL('/maps/style.json', address).href) + assert.equal(styleJsonUrl, new URL('/maps/style.json', address).href) }) }) @@ -118,7 +119,7 @@ test('/style.json resolves style.json of local "default" static map when availab url: '/style.json', }) - t.is(response.statusCode, 200) + assert.equal(response.statusCode, 200) }) test('/style.json resolves online style.json when local static is not available', async (t) => { @@ -145,9 +146,9 @@ test('/style.json resolves online style.json when local static is not available' query: `?key=pk.abc-123`, }) - t.is(response.statusCode, 200) + assert.equal(response.statusCode, 200) - t.is( + assert.equal( response.json().name, // Based on the mapbox-outdoors-v12.json fixture 'Mapbox Outdoors', @@ -182,8 +183,8 @@ test('defaultOnlineStyleUrl opt works', async (t) => { query: `?key=abc-123`, }) - t.is(response.statusCode, 200) - t.is( + assert.equal(response.statusCode, 200) + assert.equal( response.json().name, // Based on the protomaps-dark.v2.json fixture 'style@2.0.0-alpha.4 theme@dark', @@ -214,17 +215,17 @@ test('/style.json resolves style.json of offline fallback map when static and on // Omitting the `key` query param here to simulate not being able to get the online style.json }) - t.is(response.json().id, 'blank', 'gets fallback style.json') - t.is(response.statusCode, 200) + assert.equal(response.json().id, 'blank', 'gets fallback style.json') + assert.equal(response.statusCode, 200) }) /** - * @param {import('brittle').TestInstance} t + * @param {import('node:test').TestContext} t */ function setup(t) { const server = Fastify({ logger: false, forceCloseConnections: true }) - t.teardown(async () => { + t.after(async () => { await server.close() }) diff --git a/tests/fastify-plugins/offline-fallback-map.js b/tests/fastify-plugins/offline-fallback-map.js index 24f5c96de..4599e870c 100644 --- a/tests/fastify-plugins/offline-fallback-map.js +++ b/tests/fastify-plugins/offline-fallback-map.js @@ -1,5 +1,6 @@ import path from 'node:path' -import { test } from 'brittle' +import test from 'node:test' +import assert from 'node:assert/strict' import Fastify from 'fastify' import { plugin as OfflineFallbackMapPlugin } from '../../src/fastify-plugins/maps/offline-fallback-map.js' @@ -12,7 +13,7 @@ const MAPEO_FALLBACK_MAP_PATH = new URL( test('decorator', async (t) => { const server = setup(t) await server.ready() - t.ok(server.hasDecorator('mapeoFallbackMap'), 'decorator is set up') + assert(server.hasDecorator('mapeoFallbackMap'), 'decorator is set up') }) test('/style.json', async (t) => { @@ -24,11 +25,11 @@ test('/style.json', async (t) => { url: '/style.json', }) - t.is(response.statusCode, 200) + assert.equal(response.statusCode, 200) const styleJson = response.json() - t.alike( + assert.deepEqual( styleJson.sources, { 'boundaries-source': { @@ -61,12 +62,12 @@ test('/style.json', async (t) => { url: data, }) - t.is(response.statusCode, 200, `can reach ${sourceName}`) + assert.equal(response.statusCode, 200, `can reach ${sourceName}`) } }) /** - * @param {import('brittle').TestInstance} t + * @param {import('node:test').TestContext} t */ function setup(t) { const server = Fastify({ logger: false, forceCloseConnections: true }) @@ -76,7 +77,7 @@ function setup(t) { sourcesDir: path.join(MAPEO_FALLBACK_MAP_PATH, 'dist'), }) - t.teardown(async () => { + t.after(async () => { await server.close() }) diff --git a/tests/fastify-plugins/static-maps.js b/tests/fastify-plugins/static-maps.js index dfafac731..3eb230e62 100644 --- a/tests/fastify-plugins/static-maps.js +++ b/tests/fastify-plugins/static-maps.js @@ -1,6 +1,7 @@ import fs from 'node:fs' import path from 'node:path' -import { test } from 'brittle' +import test from 'node:test' +import assert from 'node:assert/strict' import Fastify from 'fastify' import { plugin } from '../../src/fastify-plugins/maps/static-maps.js' @@ -10,7 +11,7 @@ const MAP_FIXTURES_PATH = new URL('../fixtures/maps', import.meta.url).pathname test('decorator', async (t) => { const server = setup(t) await server.ready() - t.ok(server.hasDecorator('mapeoStaticMaps'), 'decorator is set up') + assert(server.hasDecorator('mapeoStaticMaps'), 'decorator is set up') }) test('list map styles', async (t) => { @@ -22,11 +23,11 @@ test('list map styles', async (t) => { url: '/', }) - t.is(response.statusCode, 200) + assert.equal(response.statusCode, 200) const data = response.json() - t.alike( + assert.deepEqual( data, [ { @@ -61,11 +62,11 @@ test('get style.json', async (t) => { url: `/${styleId}/style.json`, }) - t.is(response.statusCode, 200) + assert.equal(response.statusCode, 200) const data = response.json() - t.alike( + assert.deepEqual( data, JSON.parse(rawStyleJson.replace(/\{host\}/gm, `${address}/${styleId}`)), 'response data is correct' @@ -94,8 +95,8 @@ test('get sprite.json', async (t) => { url: `/${styleId}/sprites/sprite.json`, }) - t.is(response.statusCode, 200) - t.alike(response.json(), expectedJson) + assert.equal(response.statusCode, 200) + assert.deepEqual(response.json(), expectedJson) }) ) }) @@ -107,7 +108,7 @@ test('get tile (image)', async (t) => { const styleIds = fs.readdirSync(MAP_FIXTURES_PATH) for (const styleId of styleIds) { - t.test('non-existing tile', async (st) => { + await t.test('non-existing tile', async () => { // With extension { const response = await server.inject({ @@ -115,7 +116,7 @@ test('get tile (image)', async (t) => { url: `/${styleId}/tiles/mapbox.satellite/0/0/0.png`, }) - st.is(response.statusCode, 404) + assert.equal(response.statusCode, 404) } // Without extension @@ -125,22 +126,22 @@ test('get tile (image)', async (t) => { url: `/${styleId}/tiles/mapbox.satellite/0/0/0`, }) - st.is(response.statusCode, 404) + assert.equal(response.statusCode, 404) } }) - t.test('non-existing tile id', async (st) => { + await t.test('non-existing tile id', async () => { { const response = await server.inject({ method: 'GET', url: `/${styleId}/tiles/foo.bar/6/10/24.png`, }) - st.is(response.statusCode, 404) + assert.equal(response.statusCode, 404) } }) - t.test('existing tile', async (st) => { + await t.test('existing tile', async () => { // With extension { const response = await server.inject({ @@ -148,13 +149,13 @@ test('get tile (image)', async (t) => { url: `/${styleId}/tiles/mapbox.satellite/6/10/24.png`, }) - st.is(response.statusCode, 200) - st.is( + assert.equal(response.statusCode, 200) + assert.equal( response.headers['content-type'], 'image/png', 'content type correct' ) - st.is( + assert.equal( getContentLength(response.headers), 21014, 'correct content length' @@ -168,13 +169,13 @@ test('get tile (image)', async (t) => { url: `/${styleId}/tiles/mapbox.satellite/6/10/24`, }) - st.is(response.statusCode, 200) - st.is( + assert.equal(response.statusCode, 200) + assert.equal( response.headers['content-type'], 'image/png', 'content type correct' ) - st.is( + assert.equal( getContentLength(response.headers), 21014, 'correct content length' @@ -193,15 +194,15 @@ test('get tile (pbf)', async (t) => { url: '/streets-sat-style/tiles/mapbox.mapbox-streets-v7/12/656/1582.vector.pbf', }) - t.is(response.statusCode, 200) + assert.equal(response.statusCode, 200) - t.is( + assert.equal( response.headers['content-type'], 'application/x-protobuf', 'content type correct' ) - t.is(getContentLength(response.headers), 49229, 'correct file length') + assert.equal(getContentLength(response.headers), 49229, 'correct file length') }) test('get font pbf', async (t) => { @@ -213,26 +214,26 @@ test('get font pbf', async (t) => { url: '/streets-sat-style/fonts/DIN Offc Pro Bold,Arial Unicode MS Bold/0-255.pbf', }) - t.is(response.statusCode, 200) + assert.equal(response.statusCode, 200) - t.is( + assert.equal( response.headers['content-type'], 'application/x-protobuf', 'content type correct' ) - t.is(getContentLength(response.headers), 75287, 'correct file length') + assert.equal(getContentLength(response.headers), 75287, 'correct file length') }) /** - * @param {import('brittle').TestInstance} t + * @param {import('node:test').TestContext} t */ function setup(t) { const server = Fastify({ logger: false, forceCloseConnections: true }) server.register(plugin, { staticRootDir: MAP_FIXTURES_PATH }) - t.teardown(async () => { + t.after(async () => { await server.close() }) From 5f10e09746cfed4b97822144cd318094d8d00535 Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Thu, 16 May 2024 08:47:31 -0500 Subject: [PATCH 08/20] test: move bitfield-rle tests to `node:test` (#636) This test-only change drops Brittle from our bitfield run length encoding tests and replaces them with `node:test` and `node:assert`. --- tests/bitfield-rle.js | 99 ++++++++++++++++++++++++++++--------------- 1 file changed, 64 insertions(+), 35 deletions(-) diff --git a/tests/bitfield-rle.js b/tests/bitfield-rle.js index 01651454f..1dfe37088 100644 --- a/tests/bitfield-rle.js +++ b/tests/bitfield-rle.js @@ -1,39 +1,48 @@ -import test from 'brittle' +import test from 'node:test' +import assert from 'node:assert/strict' import Bitfield from 'bitfield' import * as rle from '../src/core-manager/bitfield-rle.js' -test('encodes and decodes', function (t) { +test('encodes and decodes', function () { var bits = new Bitfield(1024) var deflated = rle.encode(toUint32Array(bits.buffer)) - t.ok(deflated.length < bits.buffer.length, 'is smaller') + assert(deflated.length < bits.buffer.length, 'is smaller') var inflated = rle.decode(deflated) - t.alike(inflated, toUint32Array(bits.buffer), 'decodes to same buffer') + assert.deepEqual( + inflated, + toUint32Array(bits.buffer), + 'decodes to same buffer' + ) }) -test('encodingLength', function (t) { +test('encodingLength', function () { var bits = new Bitfield(1024) var len = rle.encodingLength(bits.buffer) - t.ok(len < bits.buffer.length, 'is smaller') + assert(len < bits.buffer.length, 'is smaller') var deflated = rle.encode(bits.buffer) - t.alike( + assert.deepEqual( len, deflated.length, 'encoding length is similar to encoded buffers length' ) }) -test('encodes and decodes with all bits set', function (t) { +test('encodes and decodes with all bits set', function () { var bits = new Bitfield(1024) for (var i = 0; i < 1024; i++) bits.set(i, true) var deflated = rle.encode(toUint32Array(bits.buffer)) - t.ok(deflated.length < bits.buffer.length, 'is smaller') + assert(deflated.length < bits.buffer.length, 'is smaller') var inflated = rle.decode(deflated) - t.alike(inflated, toUint32Array(bits.buffer), 'decodes to same buffer') + assert.deepEqual( + inflated, + toUint32Array(bits.buffer), + 'decodes to same buffer' + ) }) -test('encodes and decodes with some bits set', function (t) { +test('encodes and decodes with some bits set', function () { var bits = new Bitfield(1024) bits.set(500, true) @@ -45,12 +54,16 @@ test('encodes and decodes with some bits set', function (t) { bits.set(0, true) var deflated = rle.encode(toUint32Array(bits.buffer)) - t.ok(deflated.length < bits.buffer.length, 'is smaller') + assert(deflated.length < bits.buffer.length, 'is smaller') var inflated = rle.decode(deflated) - t.alike(inflated, toUint32Array(bits.buffer), 'decodes to same buffer') + assert.deepEqual( + inflated, + toUint32Array(bits.buffer), + 'decodes to same buffer' + ) }) -test('encodes and decodes with random bits set', function (t) { +test('encodes and decodes with random bits set', function () { var bits = new Bitfield(8 * 1024) for (var i = 0; i < 512; i++) { @@ -58,12 +71,16 @@ test('encodes and decodes with random bits set', function (t) { } var deflated = rle.encode(toUint32Array(bits.buffer)) - t.ok(deflated.length < bits.buffer.length, 'is smaller') + assert(deflated.length < bits.buffer.length, 'is smaller') var inflated = rle.decode(deflated) - t.alike(inflated, toUint32Array(bits.buffer), 'decodes to same buffer') + assert.deepEqual( + inflated, + toUint32Array(bits.buffer), + 'decodes to same buffer' + ) }) -test('encodes and decodes with random bits set (not power of two)', function (t) { +test('encodes and decodes with random bits set (not power of two)', function () { var bits = new Bitfield(8 * 1024) for (var i = 0; i < 313; i++) { @@ -71,36 +88,48 @@ test('encodes and decodes with random bits set (not power of two)', function (t) } var deflated = rle.encode(toUint32Array(bits.buffer)) - t.ok(deflated.length < bits.buffer.length, 'is smaller') + assert(deflated.length < bits.buffer.length, 'is smaller') var inflated = rle.decode(deflated) - t.alike(inflated, toUint32Array(bits.buffer), 'decodes to same buffer') + assert.deepEqual( + inflated, + toUint32Array(bits.buffer), + 'decodes to same buffer' + ) }) -test('encodes empty bitfield', function (t) { +test('encodes empty bitfield', function () { var deflated = rle.encode(new Uint32Array()) var inflated = rle.decode(deflated) - t.alike(inflated, new Uint32Array(), 'still empty') + assert.deepEqual(inflated, new Uint32Array(), 'still empty') }) -test('throws on bad input', function (t) { - t.exception(function () { - rle.decode(toUint32Array([100, 0, 0, 0])) - }, 'invalid delta count') +test('throws on bad input', function () { + assert.throws( + function () { + rle.decode(toUint32Array([100, 0, 0, 0])) + }, + undefined, + 'invalid delta count' + ) // t.exception.all also catches RangeErrors, which is what we expect from this - t.exception.all(function () { - rle.decode( - toUint32Array([ - 10, 0, 10, 0, 10, 0, 10, 0, 10, 0, 10, 0, 10, 0, 10, 0, 10, 0, 10, 0, - 10, 0, - ]) - ) - }, 'missing delta') + assert.throws( + function () { + rle.decode( + toUint32Array([ + 10, 0, 10, 0, 10, 0, 10, 0, 10, 0, 10, 0, 10, 0, 10, 0, 10, 0, 10, 0, + 10, 0, + ]) + ) + }, + undefined, + 'missing delta' + ) }) -test('not power of two', function (t) { +test('not power of two', function () { var deflated = rle.encode(toUint32Array([255, 255, 255, 240])) var inflated = rle.decode(deflated) - t.alike( + assert.deepEqual( inflated, toUint32Array([255, 255, 255, 240]), 'output equal to input' From 7762e5917738cad2fdd24f39a9ec2f595f71b38a Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Thu, 16 May 2024 08:47:46 -0500 Subject: [PATCH 09/20] test: move blob store tests to `node:test` (#638) This test-only change drops Brittle from the blob store tests and replaces them with `node:test` and `node:assert`. --- tests/blob-store/blob-store.js | 151 ++++++++++++++++------------- tests/blob-store/combine-states.js | 19 ++-- tests/blob-store/live-download.js | 98 +++++++++++-------- 3 files changed, 154 insertions(+), 114 deletions(-) diff --git a/tests/blob-store/blob-store.js b/tests/blob-store/blob-store.js index cb909a521..cefca5c0c 100644 --- a/tests/blob-store/blob-store.js +++ b/tests/blob-store/blob-store.js @@ -1,5 +1,4 @@ -// @ts-check -import test from 'brittle' +import test from 'node:test' import assert from 'node:assert/strict' // @ts-ignore import { pipelinePromise as pipeline } from 'streamx' @@ -19,7 +18,7 @@ import { discoveryKey } from 'hypercore-crypto' // Test with buffers that are 3 times the default blockSize for hyperblobs const TEST_BUF_SIZE = 3 * 64 * 1024 -test('blobStore.put(blobId, buf) and blobStore.get(blobId)', async (t) => { +test('blobStore.put(blobId, buf) and blobStore.get(blobId)', async () => { const { blobStore } = await testenv() const diskbuf = await readFile(new URL(import.meta.url)) const blobId = /** @type {const} */ ({ @@ -29,12 +28,12 @@ test('blobStore.put(blobId, buf) and blobStore.get(blobId)', async (t) => { }) const driveId = await blobStore.put(blobId, diskbuf) const bndlbuf = await blobStore.get({ ...blobId, driveId }) - t.alike(bndlbuf, diskbuf, 'should be equal') + assert.deepEqual(bndlbuf, diskbuf, 'should be equal') }) -test('get(), driveId not found', async (t) => { +test('get(), driveId not found', async () => { const { blobStore } = await testenv() - await t.exception( + await assert.rejects( async () => await blobStore.get({ type: 'photo', @@ -45,13 +44,13 @@ test('get(), driveId not found', async (t) => { ) }) -test('get(), valid driveId, missing file', async (t) => { +test('get(), valid driveId, missing file', async () => { const { blobStore, coreManager } = await testenv() const driveId = discoveryKey( coreManager.getWriterCore('blobIndex').key ).toString('hex') - await t.exception( + await assert.rejects( async () => await blobStore.get({ type: 'photo', @@ -62,12 +61,12 @@ test('get(), valid driveId, missing file', async (t) => { ) }) -test('get(), uninitialized drive', async (t) => { +test('get(), uninitialized drive', async () => { const { blobStore, coreManager } = await testenv() const driveKey = randomBytes(32) const driveId = discoveryKey(driveKey).toString('hex') coreManager.addCore(driveKey, 'blobIndex') - await t.exception( + await assert.rejects( async () => await blobStore.get({ type: 'photo', @@ -78,7 +77,7 @@ test('get(), uninitialized drive', async (t) => { ) }) -test('get(), initialized but unreplicated drive', async (t) => { +test('get(), initialized but unreplicated drive', async () => { const projectKey = randomBytes(32) const { blobStore: bs1, coreManager: cm1 } = await testenv({ projectKey }) const { blobStore: bs2, coreManager: cm2 } = await testenv({ projectKey }) @@ -100,12 +99,12 @@ test('get(), initialized but unreplicated drive', async (t) => { ) await replicatedCore.update({ wait: true }) await destroy() - t.is(replicatedCore.contiguousLength, 0, 'data is not downloaded') - t.ok(replicatedCore.length > 0, 'proof of length has updated') - await t.exception(async () => await bs2.get({ ...blob1Id, driveId })) + assert.equal(replicatedCore.contiguousLength, 0, 'data is not downloaded') + assert(replicatedCore.length > 0, 'proof of length has updated') + await assert.rejects(async () => await bs2.get({ ...blob1Id, driveId })) }) -test('get(), replicated blobIndex, but blobs not replicated', async (t) => { +test('get(), replicated blobIndex, but blobs not replicated', async () => { const projectKey = randomBytes(32) const { blobStore: bs1, coreManager: cm1 } = await testenv({ projectKey }) const { blobStore: bs2, coreManager: cm2 } = await testenv({ projectKey }) @@ -128,16 +127,16 @@ test('get(), replicated blobIndex, but blobs not replicated', async (t) => { await replicatedCore.download({ end: replicatedCore.length }).done() await destroy() - t.is( + assert.equal( replicatedCore.contiguousLength, replicatedCore.length, 'blobIndex has downloaded' ) - t.ok(replicatedCore.length > 0) - await t.exception(async () => await bs2.get({ ...blob1Id, driveId })) + assert(replicatedCore.length > 0) + await assert.rejects(async () => await bs2.get({ ...blob1Id, driveId })) }) -test('blobStore.createWriteStream(blobId) and blobStore.createReadStream(blobId)', async (t) => { +test('blobStore.createWriteStream(blobId) and blobStore.createReadStream(blobId)', async () => { const { blobStore } = await testenv() const diskbuf = await readFile(new URL(import.meta.url)) const blobId = /** @type {const} */ ({ @@ -151,10 +150,10 @@ test('blobStore.createWriteStream(blobId) and blobStore.createReadStream(blobId) const bndlbuf = await concat( blobStore.createReadStream({ ...blobId, driveId }) ) - t.alike(bndlbuf, diskbuf, 'should be equal') + assert.deepEqual(bndlbuf, diskbuf, 'should be equal') }) -test('blobStore.createReadStream should not wait', async (t) => { +test('blobStore.createReadStream should not wait', async () => { const { blobStore } = await testenv() const expected = await readFile(new URL(import.meta.url)) @@ -164,13 +163,16 @@ test('blobStore.createReadStream should not wait', async (t) => { name: 'test-file', }) - await t.exception(async () => { - const result = blobStore.createReadStream({ - ...blobId, - driveId: blobStore.writerDriveId, - }) - await concat(result) - }, 'Blob does not exist') + await assert.rejects( + async () => { + const result = blobStore.createReadStream({ + ...blobId, + driveId: blobStore.writerDriveId, + }) + await concat(result) + }, + { message: 'Blob does not exist' } + ) const { blobStore: blobStore2 } = await testenv() @@ -183,16 +185,19 @@ test('blobStore.createReadStream should not wait', async (t) => { driveId: blobStore.writerDriveId, }) const blob = await concat(stream) - t.alike(blob, expected, 'should be equal') + assert.deepEqual(blob, expected, 'should be equal') } - await t.exception(async () => { - const stream = blobStore2.createReadStream({ - ...blobId, - driveId: blobStore2.writerDriveId, - }) - await concat(stream) - }, 'Blob does not exist') + await assert.rejects( + async () => { + const stream = blobStore2.createReadStream({ + ...blobId, + driveId: blobStore2.writerDriveId, + }) + await concat(stream) + }, + { message: 'Blob does not exist' } + ) const ws2 = blobStore2.createWriteStream(blobId) await pipeline(fs.createReadStream(new URL(import.meta.url)), ws2) @@ -203,24 +208,27 @@ test('blobStore.createReadStream should not wait', async (t) => { driveId: blobStore2.writerDriveId, }) const blob = await concat(stream) - t.alike(blob, expected, 'should be equal') + assert.deepEqual(blob, expected, 'should be equal') await blobStore2.clear({ ...blobId, driveId: blobStore2.writerDriveId, }) - await t.exception(async () => { - const stream = blobStore2.createReadStream({ - ...blobId, - driveId: blobStore2.writerDriveId, - }) - await concat(stream) - }, 'Block not available') + await assert.rejects( + async () => { + const stream = blobStore2.createReadStream({ + ...blobId, + driveId: blobStore2.writerDriveId, + }) + await concat(stream) + }, + { message: 'Block not available' } + ) } }) -test('blobStore.writerDriveId', async (t) => { +test('blobStore.writerDriveId', async () => { { const { blobStore } = await testenv() const blobId = /** @type {const} */ ({ @@ -229,7 +237,7 @@ test('blobStore.writerDriveId', async (t) => { name: 'test-file', }) const ws = blobStore.createWriteStream(blobId) - t.is( + assert.equal( ws.driveId, blobStore.writerDriveId, 'writerDriveId is same as driveId used for createWriteStream' @@ -243,7 +251,7 @@ test('blobStore.writerDriveId', async (t) => { name: 'test-file', }) const driveId = await blobStore.put(blobId, Buffer.from('hello')) - t.is( + assert.equal( driveId, blobStore.writerDriveId, 'writerDriveId is same as driveId returned by put()' @@ -254,7 +262,7 @@ test('blobStore.writerDriveId', async (t) => { // Tests: // A) Downloads from peers connected when download() is first called // B) Downloads from peers connected after download() is first called -test('live download', async function (t) { +test('live download', async function () { const projectKey = randomBytes(32) const { blobStore: bs1, coreManager: cm1 } = await testenv({ projectKey }) const { blobStore: bs2, coreManager: cm2 } = await testenv({ projectKey }) @@ -291,19 +299,19 @@ test('live download', async function (t) { await Promise.all([destroy1(), destroy2()]) // Both blob1 and blob2 (from CM1 and CM2) should have been downloaded to CM3 - t.alike( + assert.deepEqual( await bs3.get({ ...blob1Id, driveId: driveId1 }), blob1, 'blob1 was downloaded' ) - t.alike( + assert.deepEqual( await bs3.get({ ...blob2Id, driveId: driveId2 }), blob2, 'blob2 was downloaded' ) }) -test('sparse live download', async function (t) { +test('sparse live download', async function () { const projectKey = randomBytes(32) const { blobStore: bs1, coreManager: cm1 } = await testenv({ projectKey }) const { blobStore: bs2, coreManager: cm2 } = await testenv({ projectKey }) @@ -338,15 +346,23 @@ test('sparse live download', async function (t) { await destroy() - t.alike(await bs2.get({ ...blob1Id, driveId }), blob1, 'blob1 was downloaded') - t.alike(await bs2.get({ ...blob2Id, driveId }), blob2, 'blob2 was downloaded') - await t.exception( + assert.deepEqual( + await bs2.get({ ...blob1Id, driveId }), + blob1, + 'blob1 was downloaded' + ) + assert.deepEqual( + await bs2.get({ ...blob2Id, driveId }), + blob2, + 'blob2 was downloaded' + ) + await assert.rejects( () => bs2.get({ ...blob3Id, driveId }), 'blob3 was not downloaded' ) }) -test('cancelled live download', async function (t) { +test('cancelled live download', async function () { const projectKey = randomBytes(32) const { blobStore: bs1, coreManager: cm1 } = await testenv({ projectKey }) const { blobStore: bs2, coreManager: cm2 } = await testenv({ projectKey }) @@ -386,18 +402,18 @@ test('cancelled live download', async function (t) { await Promise.all([destroy1(), destroy2()]) // Both blob1 and blob2 (from CM1 and CM2) should have been downloaded to CM3 - t.alike( + assert.deepEqual( await bs3.get({ ...blob1Id, driveId: driveId1 }), blob1, 'blob1 was downloaded' ) - await t.exception( + await assert.rejects( async () => bs3.get({ ...blob2Id, driveId: driveId2 }), 'blob2 was not downloaded' ) }) -test('blobStore.getEntryBlob(driveId, entry)', async (t) => { +test('blobStore.getEntryBlob(driveId, entry)', async () => { const { blobStore } = await testenv() const diskbuf = await readFile(new URL(import.meta.url)) const blobId = /** @type {const} */ ({ @@ -411,10 +427,10 @@ test('blobStore.getEntryBlob(driveId, entry)', async (t) => { const buf = await blobStore.getEntryBlob(driveId, entry) - t.alike(buf, diskbuf, 'should be equal') + assert.deepEqual(buf, diskbuf, 'should be equal') }) -test('blobStore.getEntryReadStream(driveId, entry)', async (t) => { +test('blobStore.getEntryReadStream(driveId, entry)', async () => { const { blobStore } = await testenv() const diskbuf = await readFile(new URL(import.meta.url)) const blobId = /** @type {const} */ ({ @@ -430,10 +446,10 @@ test('blobStore.getEntryReadStream(driveId, entry)', async (t) => { await blobStore.createEntryReadStream(driveId, entry) ) - t.alike(buf, diskbuf, 'should be equal') + assert.deepEqual(buf, diskbuf, 'should be equal') }) -test('blobStore.getEntryReadStream(driveId, entry) should not wait', async (t) => { +test('blobStore.getEntryReadStream(driveId, entry) should not wait', async () => { const { blobStore } = await testenv() const expected = await readFile(new URL(import.meta.url)) @@ -449,10 +465,13 @@ test('blobStore.getEntryReadStream(driveId, entry) should not wait', async (t) = assert(entry) await blobStore.clear({ ...blobId, driveId: blobStore.writerDriveId }) - await t.exception(async () => { - const stream = await blobStore.createEntryReadStream(driveId, entry) - await concat(stream) - }, 'Block not available') + await assert.rejects( + async () => { + const stream = await blobStore.createEntryReadStream(driveId, entry) + await concat(stream) + }, + { message: 'Block not available' } + ) }) /** diff --git a/tests/blob-store/combine-states.js b/tests/blob-store/combine-states.js index eed034125..248cc014a 100644 --- a/tests/blob-store/combine-states.js +++ b/tests/blob-store/combine-states.js @@ -1,5 +1,6 @@ import { combineStates } from '../../src/blob-store/live-download.js' -import test from 'brittle' +import test from 'node:test' +import assert from 'node:assert/strict' const partial = { haveCount: 0, @@ -32,17 +33,17 @@ const fixtures = /** @type {const} */ ([ }, ]) -test('expected combined state, no error or abort', (t) => { +test('expected combined state, no error or abort', () => { for (const { statuses, expected } of fixtures) { const inputs = statuses.map((status) => ({ state: { ...partial, status } })) const expectedState = { ...partial, status: expected } for (const permuted of permute(inputs)) { - t.alike(combineStates(permuted), expectedState) + assert.deepEqual(combineStates(permuted), expectedState) } } }) -test('expected combined state, with error', (t) => { +test('expected combined state, with error', () => { for (const { statuses } of fixtures) { const inputs = [ ...statuses.map((status) => ({ state: { ...partial, status } })), @@ -56,12 +57,12 @@ test('expected combined state, with error', (t) => { ] const expectedState = { ...partial, error: new Error(), status: 'error' } for (const permuted of permute(inputs)) { - t.alike(combineStates(permuted), expectedState) + assert.deepEqual(combineStates(permuted), expectedState) } } }) -test('expected combined state, with abort', (t) => { +test('expected combined state, with abort', () => { const controller = new AbortController() controller.abort() const { signal } = controller @@ -69,12 +70,12 @@ test('expected combined state, with abort', (t) => { const inputs = statuses.map((status) => ({ state: { ...partial, status } })) const expectedState = { ...partial, status: 'aborted' } for (const permuted of permute(inputs)) { - t.alike(combineStates(permuted, { signal }), expectedState) + assert.deepEqual(combineStates(permuted, { signal }), expectedState) } } }) -test('arithmetic test', (t) => { +test('arithmetic test', () => { const counts = [ [1, 2, 3, 4], [1, 2, 3, 4], @@ -100,7 +101,7 @@ test('arithmetic test', (t) => { }, } }) - t.alike(combineStates(inputs), expected) + assert.deepEqual(combineStates(inputs), expected) }) /** diff --git a/tests/blob-store/live-download.js b/tests/blob-store/live-download.js index 0fa84090a..88c9c95a3 100644 --- a/tests/blob-store/live-download.js +++ b/tests/blob-store/live-download.js @@ -1,10 +1,10 @@ // @ts-check +import test from 'node:test' import assert from 'node:assert/strict' import { DriveLiveDownload } from '../../src/blob-store/live-download.js' import Hyperdrive from 'hyperdrive' import Corestore from 'corestore' import RAM from 'random-access-memory' -import test from 'brittle' import { setTimeout } from 'node:timers/promises' import { once } from 'node:events' import { randomBytes } from 'node:crypto' @@ -14,7 +14,7 @@ import { randomBytes } from 'node:crypto' // Test with buffers that are 3 times the default blockSize for hyperblobs const TEST_BUF_SIZE = 3 * 64 * 1024 -test('live download', async (t) => { +test('live download', async () => { const { drive1, drive2, replicate } = await testEnv() await drive1.put('/foo', randomBytes(TEST_BUF_SIZE)) @@ -32,14 +32,14 @@ test('live download', async (t) => { await waitForState(download, 'downloaded') // Can't use `drive2.get()` here because connected to replication stream, so // it would download anyway (no `waitFor = false` support for Hyperdrive yet) - t.ok( + assert( await blobCore2.has( blob1.blockOffset, blob1.blockOffset + blob1.blockLength ), 'First blob is downloaded' ) - t.ok(blob1.blockLength > 1, 'Blob is more than one block length') + assert(blob1.blockLength > 1, 'Blob is more than one block length') const expected = randomBytes(TEST_BUF_SIZE) await drive1.put('/bar', expected) @@ -48,10 +48,14 @@ test('live download', async (t) => { stream.destroy() await once(stream, 'close') - t.alike(await drive2.get('/bar'), expected, 'Second blob is downloaded') + assert.deepEqual( + await drive2.get('/bar'), + expected, + 'Second blob is downloaded' + ) }) -test('sparse live download', async (t) => { +test('sparse live download', async () => { const { drive1, drive2, replicate } = await testEnv() const buf1 = randomBytes(TEST_BUF_SIZE) @@ -74,22 +78,26 @@ test('sparse live download', async (t) => { stream.destroy() await once(stream, 'close') - t.alike(await drive2.get('photo/original/one'), buf1) - t.alike(await drive2.get('photo/original/two'), buf2) + assert.deepEqual(await drive2.get('photo/original/one'), buf1) + assert.deepEqual(await drive2.get('photo/original/two'), buf2) - await t.exception( + await assert.rejects( drive2.get('video/original/one', { wait: false }), - /BLOCK_NOT_AVAILABLE/, + { + message: /BLOCK_NOT_AVAILABLE/, + }, 'Block not available' ) - await t.exception( + await assert.rejects( drive2.get('video/original/two', { wait: false }), - /BLOCK_NOT_AVAILABLE/, + { + message: /BLOCK_NOT_AVAILABLE/, + }, 'Block not available' ) }) -test('Abort download (same tick)', async (t) => { +test('Abort download (same tick)', async () => { const { drive1, drive2, replicate } = await testEnv() await drive1.put('/foo', randomBytes(TEST_BUF_SIZE)) const stream = replicate() @@ -98,7 +106,7 @@ test('Abort download (same tick)', async (t) => { controller.abort() stream.destroy() await once(stream, 'close') - t.alike(download.state, { + assert.deepEqual(download.state, { haveCount: 0, haveBytes: 0, wantCount: 0, @@ -106,10 +114,10 @@ test('Abort download (same tick)', async (t) => { error: null, status: 'aborted', }) - t.is(await drive2.get('/foo'), null, 'nothing downloaded') + assert.equal(await drive2.get('/foo'), null, 'nothing downloaded') }) -test('Abort download (next event loop)', async (t) => { +test('Abort download (next event loop)', async () => { const { drive1, drive2, replicate } = await testEnv() await drive1.put('/one', randomBytes(TEST_BUF_SIZE)) const stream = replicate() @@ -120,7 +128,7 @@ test('Abort download (next event loop)', async (t) => { controller.abort() stream.destroy() await once(stream, 'close') - t.alike(download.state, { + assert.deepEqual(download.state, { haveCount: 0, haveBytes: 0, wantCount: 0, @@ -128,14 +136,16 @@ test('Abort download (next event loop)', async (t) => { error: null, status: 'aborted', }) - await t.exception( + await assert.rejects( drive2.get('/foo', { wait: false }), - /Block not available locally/, + { + message: /Block not available locally/, + }, 'Block not available locally' ) }) -test('Abort download (after initial download)', async (t) => { +test('Abort download (after initial download)', async () => { const { drive1, drive2, replicate } = await testEnv() const buf1 = randomBytes(TEST_BUF_SIZE) @@ -156,15 +166,17 @@ test('Abort download (after initial download)', async (t) => { stream.destroy() await once(stream, 'close') - t.alike(await drive2.get('/one'), buf1, 'First blob is downloaded') - await t.exception( + assert.deepEqual(await drive2.get('/one'), buf1, 'First blob is downloaded') + await assert.rejects( drive2.get('/two', { wait: false }), - /BLOCK_NOT_AVAILABLE/, + { + message: /BLOCK_NOT_AVAILABLE/, + }, 'Second blob is not downloaded' ) }) -test('Live download when data is already downloaded', async (t) => { +test('Live download when data is already downloaded', async () => { const { drive1, drive2, replicate } = await testEnv() const buf1 = randomBytes(20) @@ -174,7 +186,7 @@ test('Live download when data is already downloaded', async (t) => { await drive2.db.core.update({ wait: true }) await drive2.download() - t.alike(await drive2.get('/one'), buf1, 'First blob is downloaded') + assert.deepEqual(await drive2.get('/one'), buf1, 'First blob is downloaded') stream1.destroy() await once(stream1, 'close') @@ -182,7 +194,7 @@ test('Live download when data is already downloaded', async (t) => { const stream2 = replicate() const download = new DriveLiveDownload(drive2) await waitForState(download, 'downloaded') - t.alike( + assert.deepEqual( download.state, { haveCount: 1, @@ -202,10 +214,10 @@ test('Live download when data is already downloaded', async (t) => { stream2.destroy() await once(stream2, 'close') - t.alike(await drive2.get('/two'), buf2, 'Second blob is downloaded') + assert.deepEqual(await drive2.get('/two'), buf2, 'Second blob is downloaded') }) -test('Live download continues across disconnection and reconnect', async (t) => { +test('Live download continues across disconnection and reconnect', async () => { const { drive1, drive2, replicate } = await testEnv() const buf1 = randomBytes(TEST_BUF_SIZE) @@ -216,7 +228,7 @@ test('Live download continues across disconnection and reconnect', async (t) => const download = new DriveLiveDownload(drive2) await waitForState(download, 'downloaded') - t.alike(await drive2.get('/one'), buf1, 'First blob is downloaded') + assert.deepEqual(await drive2.get('/one'), buf1, 'First blob is downloaded') stream1.destroy() await once(stream1, 'close') @@ -230,17 +242,21 @@ test('Live download continues across disconnection and reconnect', async (t) => stream2.destroy() await once(stream2, 'close') - t.alike(await drive2.get('/two'), buf2, 'Second blob is downloaded') + assert.deepEqual(await drive2.get('/two'), buf2, 'Second blob is downloaded') }) -test('Initial status', async (t) => { +test('Initial status', async () => { const { drive1 } = await testEnv() const download = new DriveLiveDownload(drive1) - t.is(download.state.status, 'checking', "initial status is 'checking'") + assert.equal( + download.state.status, + 'checking', + "initial status is 'checking'" + ) }) -test('Unitialized drive with no data', async (t) => { +test('Unitialized drive with no data', async () => { // This test is important because it catches an edge case where a drive might // have been added by its key, but has never replicated, so it has no data so // the content feed will never be read from the header, which might result in @@ -249,14 +265,14 @@ test('Unitialized drive with no data', async (t) => { const { drive2 } = await testEnv() const download = new DriveLiveDownload(drive2) await waitForState(download, 'downloaded') - t.is( + assert.equal( download.state.status, 'downloaded', 'uninitialized drive without peers results in `downloaded` state' ) }) -test('live download started before initial replication', async (t) => { +test('live download started before initial replication', async () => { const { drive1, drive2, replicate } = await testEnv() await drive1.put('/foo', randomBytes(TEST_BUF_SIZE)) @@ -269,7 +285,7 @@ test('live download started before initial replication', async (t) => { const download = new DriveLiveDownload(drive2) await waitForState(download, 'downloaded') // initially drive2 is not replicating and empty, so we expect a 'downloaded' status - t.is(download.state.status, 'downloaded') + assert.equal(download.state.status, 'downloaded') const stream = replicate() const blobCore2 = (await drive2.getBlobs())?.core @@ -278,14 +294,14 @@ test('live download started before initial replication', async (t) => { // Can't use `drive2.get()` here because connected to replication stream, so // it would download anyway (no `waitFor = false` support for Hyperdrive yet) - t.ok( + assert( await blobCore2.has( blob1.blockOffset, blob1.blockOffset + blob1.blockLength ), 'First blob is downloaded' ) - t.ok(blob1.blockLength > 1, 'Blob is more than one block length') + assert(blob1.blockLength > 1, 'Blob is more than one block length') const expected = randomBytes(TEST_BUF_SIZE) await drive1.put('/bar', expected) @@ -294,7 +310,11 @@ test('live download started before initial replication', async (t) => { stream.destroy() await once(stream, 'close') - t.alike(await drive2.get('/bar'), expected, 'Second blob is downloaded') + assert.deepEqual( + await drive2.get('/bar'), + expected, + 'Second blob is downloaded' + ) }) /** From 8387eb0c28b1413b85650d06d9dcb74ae1db6cf6 Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Thu, 16 May 2024 08:47:59 -0500 Subject: [PATCH 10/20] test: move DataStore tests to `node:test` (#641) This test-only change drops Brittle from our DataStore tests and replaces them with `node:test` and `node:assert`. --- tests/datastore.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/datastore.js b/tests/datastore.js index 0cab0a98e..83222d813 100644 --- a/tests/datastore.js +++ b/tests/datastore.js @@ -1,5 +1,6 @@ // @ts-check -import test from 'brittle' +import test from 'node:test' +import assert from 'node:assert/strict' import { DataStore } from '../src/datastore/index.js' import { createCoreManager } from './helpers/core-manager.js' import { getVersionId } from '@mapeo/schema' @@ -22,7 +23,7 @@ const obs = { deleted: false, } -test('read and write', async (t) => { +test('read and write', async () => { const cm = createCoreManager() const writerCore = cm.getWriterCore('data').core await writerCore.ready() @@ -44,25 +45,25 @@ test('read and write', async (t) => { const written = await dataStore.write(obs) const coreDiscoveryKey = discoveryKey(writerCore.key) const expectedVersionId = getVersionId({ coreDiscoveryKey, index: 0 }) - t.is( + assert.equal( written.versionId, expectedVersionId, 'versionId is set to expected value' ) const read = await dataStore.read(written.versionId) - t.alike( + assert.deepEqual( read, written, 'data returned from write matches data returned from read' ) - t.alike( + assert.deepEqual( indexedVersionIds, [written.versionId], 'The indexEntries function is called with all data that is added' ) }) -test('writeRaw and read', async (t) => { +test('writeRaw and read', async () => { const cm = createCoreManager() const writerCore = cm.getWriterCore('config').core await writerCore.ready() @@ -78,10 +79,10 @@ test('writeRaw and read', async (t) => { const buf = Buffer.from('myblob') const versionId = await dataStore.writeRaw(buf) const expectedBuf = await dataStore.readRaw(versionId) - t.alike(buf, expectedBuf) + assert.deepEqual(buf, expectedBuf) }) -test('index events', async (t) => { +test('index events', async () => { const cm = createCoreManager() const writerCore = cm.getWriterCore('data').core await writerCore.ready() @@ -114,5 +115,5 @@ test('index events', async (t) => { remaining: 0, }, ] - t.alike(indexStates, expectedStates, 'expected index states emitted') + assert.deepEqual(indexStates, expectedStates, 'expected index states emitted') }) From e354c1b92e2926266075af21cb5379a00e80ac7d Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Thu, 16 May 2024 08:48:47 -0500 Subject: [PATCH 11/20] test: move DataType tests to `node:test` (#640) This test-only change drops Brittle from our DataType tests and replaces them with `node:test` and `node:assert`. --- tests/data-type.js | 52 +++++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/tests/data-type.js b/tests/data-type.js index 2a4f875d9..5e8b6331f 100644 --- a/tests/data-type.js +++ b/tests/data-type.js @@ -1,5 +1,5 @@ // @ts-check -import test from 'brittle' +import test from 'node:test' import assert from 'node:assert/strict' import { DataStore } from '../src/datastore/index.js' import { @@ -31,7 +31,7 @@ const obsFixture = { metadata: {}, } -test('private createWithDocId() method', async (t) => { +test('private createWithDocId() method', async () => { const sqlite = new Database(':memory:') const db = drizzle(sqlite) migrate(db, { @@ -61,12 +61,12 @@ test('private createWithDocId() method', async (t) => { }) const customId = randomBytes(8).toString('hex') const obs = await dataType[kCreateWithDocId](customId, obsFixture) - t.is(obs.docId, customId) + assert.equal(obs.docId, customId) const read = await dataType.getByDocId(customId) - t.is(read.docId, customId) + assert.equal(read.docId, customId) }) -test('private createWithDocId() method throws when doc exists', async (t) => { +test('private createWithDocId() method throws when doc exists', async () => { const sqlite = new Database(':memory:') const db = drizzle(sqlite) migrate(db, { @@ -96,13 +96,13 @@ test('private createWithDocId() method throws when doc exists', async (t) => { }) const customId = randomBytes(8).toString('hex') await dataType[kCreateWithDocId](customId, obsFixture) - await t.exception( + await assert.rejects( () => dataType[kCreateWithDocId](customId, obsFixture), 'Throws with error creating a doc with an id that already exists' ) }) -test('test validity of `createdBy` field', async (t) => { +test('test validity of `createdBy` field', async () => { const projectKey = randomBytes(32) const { dataType: dt1, dataStore: ds1 } = await testenv({ projectKey }) @@ -110,14 +110,14 @@ test('test validity of `createdBy` field', async (t) => { const obs = await dt1[kCreateWithDocId](customId, obsFixture) const createdBy = crypto.discoveryKey(ds1.writerCore.key).toString('hex') - t.is( + assert.equal( obs.createdBy, createdBy, 'createdBy should be generated from the DataStore writerCore discoveryKey' ) }) -test('test validity of `createdBy` field from another peer', async (t) => { +test('test validity of `createdBy` field from another peer', async () => { const projectKey = randomBytes(32) const { coreManager: cm1, @@ -137,7 +137,7 @@ test('test validity of `createdBy` field from another peer', async (t) => { const replicatedObservation = await dt2.getByVersionId(obs.versionId) const createdBy = crypto.discoveryKey(ds1.writerCore.key).toString('hex') - t.is(replicatedObservation.createdBy, createdBy) + assert.equal(replicatedObservation.createdBy, createdBy) /** @type {import('@mapeo/schema').ObservationValue} */ const newObsFixture = { @@ -149,36 +149,40 @@ test('test validity of `createdBy` field from another peer', async (t) => { } const updatedDoc = await dt2.update(obs.versionId, newObsFixture) const updatedObservation = await dt2.getByVersionId(updatedDoc.versionId) - t.is(updatedObservation.createdBy, createdBy) + assert.equal(updatedObservation.createdBy, createdBy) await destroy() }) -test('getByDocId() throws if no document exists with that ID', async (t) => { +test('getByDocId() throws if no document exists with that ID', async () => { const { dataType } = await testenv({ projectKey: randomBytes(32) }) - await t.exception(() => dataType.getByDocId('foo bar'), NotFoundError) + await assert.rejects(() => dataType.getByDocId('foo bar'), NotFoundError) }) -test('delete()', async (t) => { +test('delete()', async () => { const projectKey = randomBytes(32) const { dataType } = await testenv({ projectKey }) const doc = await dataType.create(obsFixture) - t.is(doc.deleted, false, `'deleted' field is false before deletion`) + assert.equal(doc.deleted, false, `'deleted' field is false before deletion`) const deletedDoc = await dataType.delete(doc.docId) - t.is(deletedDoc.deleted, true, `'deleted' field is true after deletion`) - t.alike( + assert.equal( + deletedDoc.deleted, + true, + `'deleted' field is true after deletion` + ) + assert.deepEqual( deletedDoc.links, [doc.versionId], `deleted doc links back to created doc` ) const retrievedDocByDocId = await dataType.getByDocId(deletedDoc.docId) - t.alike( + assert.deepEqual( retrievedDocByDocId, deletedDoc, `retrieving by docId returns deleted doc` ) }) -test('translation', async (t) => { +test('translation', async () => { const projectKey = randomBytes(32) const { dataType, translationApi } = await testenv({ projectKey }) /** @type {import('@mapeo/schema').ObservationValue} */ @@ -206,7 +210,7 @@ test('translation', async (t) => { await translationApi.put(translation) translationApi.index(translation) - t.is( + assert.equal( translation.message, getProperty( await dataType.getByDocId(doc.docId, { lang: 'es' }), @@ -214,7 +218,7 @@ test('translation', async (t) => { ), `we get a valid translated field` ) - t.is( + assert.equal( translation.message, getProperty( await dataType.getByDocId(doc.docId, { lang: 'es-AR' }), @@ -222,7 +226,7 @@ test('translation', async (t) => { ), `we get a valid translated field` ) - t.is( + assert.equal( translation.message, getProperty( await dataType.getByDocId(doc.docId, { lang: 'es-ES' }), @@ -231,7 +235,7 @@ test('translation', async (t) => { `passing an untranslated regionCode, still returns a translated field, since we fallback to only matching languageCode` ) - t.is( + assert.equal( getProperty(observation, 'tags.type'), getProperty( await dataType.getByDocId(doc.docId, { lang: 'de' }), @@ -240,7 +244,7 @@ test('translation', async (t) => { `passing an untranslated language code returns the untranslated message` ) - t.is( + assert.equal( getProperty(observation, 'tags.type'), getProperty(await dataType.getByDocId(doc.docId), 'tags.type'), `not passing a a language code returns the untranslated message` From 9f5467bff823cbe458a77277d117537ffc15e911 Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Thu, 16 May 2024 08:49:02 -0500 Subject: [PATCH 12/20] test: move blob API tests to `node:test` (#637) This test-only change drops Brittle from the blob API tests and replaces them with `node:test` and `node:assert`. --- tests/blob-api.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/blob-api.js b/tests/blob-api.js index 15063fd47..a9fbb431e 100644 --- a/tests/blob-api.js +++ b/tests/blob-api.js @@ -1,14 +1,15 @@ // @ts-check +import test from 'node:test' +import assert from 'node:assert/strict' import { join } from 'node:path' import * as fs from 'node:fs/promises' import { createHash } from 'node:crypto' import { fileURLToPath } from 'url' -import test from 'brittle' import { BlobApi } from '../src/blob-api.js' import { createBlobStore } from './helpers/blob-store.js' -test('create blobs', async (t) => { +test('create blobs', async () => { const { blobStore } = createBlobStore() const blobApi = new BlobApi({ @@ -34,12 +35,12 @@ test('create blobs', async (t) => { { mimeType: 'image/png' } ) - t.is(attachment.driveId, blobStore.writerDriveId) - t.is(attachment.type, 'photo') - t.alike(attachment.hash, hash.digest('hex')) + assert.equal(attachment.driveId, blobStore.writerDriveId) + assert.equal(attachment.type, 'photo') + assert.deepEqual(attachment.hash, hash.digest('hex')) }) -test('get url from blobId', async (t) => { +test('get url from blobId', async () => { const type = 'photo' const variant = 'original' const name = '1234' @@ -63,7 +64,7 @@ test('get url from blobId', async (t) => { name, }) - t.is( + assert.equal( url, `http://127.0.0.1:${port}/${blobStore.writerDriveId}/${type}/${variant}/${name}` ) @@ -80,7 +81,7 @@ test('get url from blobId', async (t) => { name, }) - t.is( + assert.equal( url, `http://127.0.0.1:${port}/${blobStore.writerDriveId}/${type}/${variant}/${name}` ) @@ -97,7 +98,7 @@ test('get url from blobId', async (t) => { name, }) - t.is( + assert.equal( url, `http://127.0.0.1:${port}/${prefix}/${blobStore.writerDriveId}/${type}/${variant}/${name}` ) From f6c0b3590fdfbec59f58d130614411595509b54e Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Thu, 16 May 2024 08:49:15 -0500 Subject: [PATCH 13/20] test: move core ownership tests to `node:test` (#639) This test-only change drops Brittle from our core ownership tests and replaces them with `node:test` and `node:assert`. --- tests/core-ownership.js | 79 +++++++++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 27 deletions(-) diff --git a/tests/core-ownership.js b/tests/core-ownership.js index e198005e0..8fff718d3 100644 --- a/tests/core-ownership.js +++ b/tests/core-ownership.js @@ -1,5 +1,6 @@ // @ts-check -import test from 'brittle' +import test from 'node:test' +import assert from 'node:assert/strict' import { KeyManager, sign } from '@mapeo/crypto' import sodium from 'sodium-universal' import { @@ -10,25 +11,25 @@ import { randomBytes } from 'node:crypto' import { parseVersionId, getVersionId } from '@mapeo/schema' import { discoveryKey } from 'hypercore-crypto' -test('Valid coreOwnership record', (t) => { +test('Valid coreOwnership record', () => { const validDoc = generateValidDoc() const version = parseVersionId(validDoc.versionId) const mappedDoc = mapAndValidateCoreOwnership(validDoc, version) - t.ok(validDoc.links.length > 0, 'original doc has links') - t.alike(mappedDoc.links, [], 'links are stripped from mapped doc') - t.absent( - 'coreSignatures' in mappedDoc, + assert(validDoc.links.length > 0, 'original doc has links') + assert.deepEqual(mappedDoc.links, [], 'links are stripped from mapped doc') + assert( + !('coreSignatures' in mappedDoc), 'coreSignatures are stripped from mapped doc' ) - t.absent( - 'identitySignature' in mappedDoc, + assert( + !('identitySignature' in mappedDoc), 'identitySignature is stripped from mapped doc' ) }) -test('Invalid coreOwnership signatures', (t) => { +test('Invalid coreOwnership signatures', () => { const validDoc = generateValidDoc() const version = parseVersionId(validDoc.versionId) @@ -40,17 +41,17 @@ test('Invalid coreOwnership signatures', (t) => { [key]: randomBytes(sodium.crypto_sign_BYTES), }, } - t.exception(() => mapAndValidateCoreOwnership(invalidDoc, version)) + assert.throws(() => mapAndValidateCoreOwnership(invalidDoc, version)) } const invalidDoc = { ...validDoc, identitySignature: randomBytes(sodium.crypto_sign_BYTES), } - t.exception(() => mapAndValidateCoreOwnership(invalidDoc, version)) + assert.throws(() => mapAndValidateCoreOwnership(invalidDoc, version)) }) -test('Invalid coreOwnership docId and coreIds', (t) => { +test('Invalid coreOwnership docId and coreIds', () => { const validDoc = generateValidDoc() const version = parseVersionId(validDoc.versionId) @@ -59,17 +60,17 @@ test('Invalid coreOwnership docId and coreIds', (t) => { ...validDoc, [`${key}CoreId`]: randomBytes(32).toString('hex'), } - t.exception(() => mapAndValidateCoreOwnership(invalidDoc, version)) + assert.throws(() => mapAndValidateCoreOwnership(invalidDoc, version)) } const invalidDoc = { ...validDoc, docId: randomBytes(32).toString('hex'), } - t.exception(() => mapAndValidateCoreOwnership(invalidDoc, version)) + assert.throws(() => mapAndValidateCoreOwnership(invalidDoc, version)) }) -test('Invalid coreOwnership docId and coreIds (wrong length)', (t) => { +test('Invalid coreOwnership docId and coreIds (wrong length)', () => { const validDoc = generateValidDoc() const version = parseVersionId(validDoc.versionId) @@ -79,26 +80,26 @@ test('Invalid coreOwnership docId and coreIds (wrong length)', (t) => { ...validDoc, [`${namespace}CoreId`]: validDoc[`${namespace}CoreId`].slice(0, -1), } - t.exception(() => mapAndValidateCoreOwnership(invalidDoc, version)) + assert.throws(() => mapAndValidateCoreOwnership(invalidDoc, version)) } const invalidDoc = { ...validDoc, docId: validDoc.docId.slice(0, -1), } - t.exception(() => mapAndValidateCoreOwnership(invalidDoc, version)) + assert.throws(() => mapAndValidateCoreOwnership(invalidDoc, version)) }) -test('Invalid - different coreKey', (t) => { +test('Invalid - different coreKey', () => { const validDoc = generateValidDoc() const version = { ...parseVersionId(validDoc.versionId), coreDiscoveryKey: discoveryKey(randomBytes(32)), } - t.exception(() => mapAndValidateCoreOwnership(validDoc, version)) + assert.throws(() => mapAndValidateCoreOwnership(validDoc, version)) }) -test('getWinner (coreOwnership)', (t) => { +test('getWinner (coreOwnership)', () => { const validDoc = generateValidDoc() const version = parseVersionId(validDoc.versionId) @@ -111,11 +112,19 @@ test('getWinner (coreOwnership)', (t) => { versionId: getVersionId({ ...version, index: 6 }), } - t.is(getWinner(docA, docB), docA, 'Doc with lowest index picked as winner') - t.is(getWinner(docB, docA), docA, 'Doc with lowest index picked as winner') + assert.equal( + getWinner(docA, docB), + docA, + 'Doc with lowest index picked as winner' + ) + assert.equal( + getWinner(docB, docA), + docA, + 'Doc with lowest index picked as winner' + ) }) -test('getWinner (default)', (t) => { +test('getWinner (default)', () => { const docA = { docId: 'A', schemaName: 'other', @@ -130,13 +139,29 @@ test('getWinner (default)', (t) => { links: ['1'], updatedAt: new Date(1999, 0, 2).toISOString(), } - t.is(getWinner(docA, docB), docB, 'Doc with last updatedAt picked as winner') - t.is(getWinner(docB, docA), docB, 'Doc with last updatedAt picked as winner') + assert.equal( + getWinner(docA, docB), + docB, + 'Doc with last updatedAt picked as winner' + ) + assert.equal( + getWinner(docB, docA), + docB, + 'Doc with last updatedAt picked as winner' + ) docA.updatedAt = docB.updatedAt - t.is(getWinner(docA, docB), docA, 'Deterministic winner if same updatedAt') - t.is(getWinner(docB, docA), docA, 'Deterministic winner if same updatedAt') + assert.equal( + getWinner(docA, docB), + docA, + 'Deterministic winner if same updatedAt' + ) + assert.equal( + getWinner(docB, docA), + docA, + 'Deterministic winner if same updatedAt' + ) }) function generateValidDoc() { From b0ffea94e79c9083376652daa44de979dab3ce77 Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Thu, 16 May 2024 08:57:16 -0500 Subject: [PATCH 14/20] test: move RemoteBitfield tests to `node:test` (#646) This test-only change drops Brittle from our RemoteBitfield tests and replaces them with `node:test` and `node:assert`. --- tests/remote-bitfield.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/remote-bitfield.js b/tests/remote-bitfield.js index acdebadf7..cff7964ae 100644 --- a/tests/remote-bitfield.js +++ b/tests/remote-bitfield.js @@ -1,17 +1,18 @@ // @ts-check -import test from 'brittle' +import test from 'node:test' +import assert from 'node:assert/strict' import RemoteBitfield from '../src/core-manager/remote-bitfield.js' -test('remote bitfield - findFirst, findLast', function (t) { +test('remote bitfield - findFirst, findLast', () => { const b = new RemoteBitfield() b.set(1_000_000, true) - t.is(b.findFirst(true, 0), 1_000_000) - t.is(b.findLast(true, 2_000_000), 1_000_000) + assert.equal(b.findFirst(true, 0), 1_000_000) + assert.equal(b.findLast(true, 2_000_000), 1_000_000) }) -test('remote bitfield - findLast, findFirst from insert', function (t) { +test('remote bitfield - findLast, findFirst from insert', () => { // Regression test for bug in RemoteBitfield which was not updating the index // when inserting a new bitfield. const b = new RemoteBitfield() @@ -20,6 +21,6 @@ test('remote bitfield - findLast, findFirst from insert', function (t) { const arr = new Uint32Array(size).fill(2 ** 32 - 1) b.insert(0, arr) - t.is(b.findFirst(false, 0), size * 32) - t.is(b.findLast(true, size * 32 + 1_000_000), size * 32 - 1) + assert.equal(b.findFirst(false, 0), size * 32) + assert.equal(b.findLast(true, size * 32 + 1_000_000), size * 32 - 1) }) From 3763a1ba5cdb1a37fcdc927d0db0a24f36da4857 Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Thu, 16 May 2024 08:58:56 -0500 Subject: [PATCH 15/20] test: move test/lib/ to `node:test` (#645) This test-only change drops Brittle from test/lib/ and replaces it with `node:test` and `node:assert`. --- tests/lib/hashmap.js | 65 +++++++++++++++++----------------- tests/lib/ponyfills.js | 27 +++++++------- tests/lib/string.js | 9 ++--- tests/lib/timing-safe-equal.js | 30 ++++++++-------- 4 files changed, 67 insertions(+), 64 deletions(-) diff --git a/tests/lib/hashmap.js b/tests/lib/hashmap.js index 7a405df89..ce7c9d8e6 100644 --- a/tests/lib/hashmap.js +++ b/tests/lib/hashmap.js @@ -1,14 +1,15 @@ // @ts-check -import test from 'brittle' +import test from 'node:test' +import assert from 'node:assert/strict' import HashMap from '../../src/lib/hashmap.js' -test('empty init', (t) => { +test('empty init', () => { const map = new HashMap(join) - t.is(map.size, 0) + assert.equal(map.size, 0) }) -test('init from iterable', (t) => { +test('init from iterable', () => { const map = new HashMap( join, new Set([ @@ -17,71 +18,71 @@ test('init from iterable', (t) => { ]) ) - t.is(map.size, 2) - t.is(map.get([1, 2]), 3) - t.is(map.get([4, 5]), 6) - t.is(map.get([7, 8]), undefined) - t.ok(map.has([1, 2])) - t.ok(map.has([4, 5])) - t.absent(map.has([7, 8])) + assert.equal(map.size, 2) + assert.equal(map.get([1, 2]), 3) + assert.equal(map.get([4, 5]), 6) + assert.equal(map.get([7, 8]), undefined) + assert(map.has([1, 2])) + assert(map.has([4, 5])) + assert(!map.has([7, 8])) }) -test('creating, reading, updating, and deleting', (t) => { +test('creating, reading, updating, and deleting', () => { const map = new HashMap(join) map.set([1, 2], 3) - t.is(map.size, 1) - t.is(map.get([1, 2]), 3) - t.is(map.get([4, 5]), undefined) - t.ok(map.has([1, 2])) - t.absent(map.has([4, 5])) + assert.equal(map.size, 1) + assert.equal(map.get([1, 2]), 3) + assert.equal(map.get([4, 5]), undefined) + assert(map.has([1, 2])) + assert(!map.has([4, 5])) map.set([1, 2], 100) - t.is(map.get([1, 2]), 100) + assert.equal(map.get([1, 2]), 100) map.delete([1, 2]) - t.is(map.size, 0) - t.is(map.get([1, 2]), undefined) - t.absent(map.has([1, 2])) + assert.equal(map.size, 0) + assert.equal(map.get([1, 2]), undefined) + assert(!map.has([1, 2])) }) -test('uses same-value-zero equality for keys', (t) => { +test('uses same-value-zero equality for keys', () => { /** @param {number} n */ const identity = (n) => n const map = new HashMap(identity) map.set(0, 'foo') - t.is(map.get(0), 'foo') - t.is(map.get(0), map.get(-0)) + assert.equal(map.get(0), 'foo') + assert.equal(map.get(0), map.get(-0)) map.set(NaN, 'bar') - t.is(map.get(NaN), 'bar') + assert.equal(map.get(NaN), 'bar') }) -test('delete() returns whether the key was present', (t) => { +test('delete() returns whether the key was present', () => { const map = new HashMap(join) map.set([1, 2], 3) - t.ok(map.delete([1, 2])) - t.absent(map.delete([1, 2])) + assert(map.delete([1, 2])) + assert(!map.delete([1, 2])) }) -test('set() returns the map', (t) => { +test('set() returns the map', () => { const map = new HashMap(join) - t.is(map.set([1, 2], 3), map) + assert.equal(map.set([1, 2], 3), map) }) -test('values()', (t) => { +test('values()', () => { const map = new HashMap(join, [ [[1, 2], 3], [[4, 5], 6], ]) - t.alike(Array.from(map.values()), [3, 6]) + assert.deepEqual(Array.from(map.values()), [3, 6]) }) /** diff --git a/tests/lib/ponyfills.js b/tests/lib/ponyfills.js index 2bf2cd04d..d60e1d1de 100644 --- a/tests/lib/ponyfills.js +++ b/tests/lib/ponyfills.js @@ -1,13 +1,14 @@ // @ts-check -import test from 'brittle' +import test from 'node:test' +import assert from 'node:assert/strict' import { abortSignalAny } from '../../src/lib/ponyfills.js' -test('abortSignalAny() handles empty iterables', (t) => { - t.not(abortSignalAny([]).aborted, 'not immediately aborted') - t.not(abortSignalAny(new Set()).aborted, 'not immediately aborted') +test('abortSignalAny() handles empty iterables', () => { + assert.notEqual(abortSignalAny([]).aborted, 'not immediately aborted') + assert.notEqual(abortSignalAny(new Set()).aborted, 'not immediately aborted') }) -test('abortSignalAny() aborts immediately if one of the arguments was aborted', (t) => { +test('abortSignalAny() aborts immediately if one of the arguments was aborted', () => { const result = abortSignalAny([ new AbortController().signal, AbortSignal.abort('foo'), @@ -15,27 +16,27 @@ test('abortSignalAny() aborts immediately if one of the arguments was aborted', new AbortController().signal, ]) - t.ok(result.aborted, 'immediately aborted') - t.is(result.reason, 'foo', 'gets first abort reason') + assert(result.aborted, 'immediately aborted') + assert.equal(result.reason, 'foo', 'gets first abort reason') }) -test('abortSignalAny() aborts as soon as one of its arguments aborts', (t) => { +test('abortSignalAny() aborts as soon as one of its arguments aborts', () => { const a = new AbortController() const b = new AbortController() const c = new AbortController() const result = abortSignalAny([a.signal, b.signal, c.signal]) - t.not(result.aborted, 'not immediately aborted') + assert.notEqual(result.aborted, 'not immediately aborted') b.abort('foo') c.abort('ignored') - t.ok(result.aborted, 'aborted') - t.is(result.reason, 'foo', 'gets first abort reason') + assert(result.aborted, 'aborted') + assert.equal(result.reason, 'foo', 'gets first abort reason') }) -test('abortSignalAny() handles non-array iterables', (t) => { +test('abortSignalAny() handles non-array iterables', () => { const a = new AbortController() const b = new AbortController() const c = new AbortController() @@ -44,5 +45,5 @@ test('abortSignalAny() handles non-array iterables', (t) => { b.abort('foo') - t.ok(result.aborted, 'aborted') + assert(result.aborted, 'aborted') }) diff --git a/tests/lib/string.js b/tests/lib/string.js index a20851b11..d5f6d9dfe 100644 --- a/tests/lib/string.js +++ b/tests/lib/string.js @@ -1,8 +1,9 @@ // @ts-check -import test from 'brittle' +import test from 'node:test' +import assert from 'node:assert/strict' import { isBlank } from '../../src/lib/string.js' -test('isBlank()', (t) => { +test('isBlank()', () => { // See [what JavaScript considers white space][0]. // [0]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#white_space const unicodeWhitespace = [ @@ -16,7 +17,7 @@ test('isBlank()', (t) => { ...unicodeWhitespace.flatMap((a) => unicodeWhitespace.map((b) => a + b)), ] for (const str of blanks) { - t.ok(isBlank(str), `${formatCodePoints(str)} is blank`) + assert(isBlank(str), `${formatCodePoints(str)} is blank`) } const notBlanks = [ @@ -25,7 +26,7 @@ test('isBlank()', (t) => { ...unicodeWhitespace.map((c) => 'x' + c), ] for (const str of notBlanks) { - t.absent(isBlank(str), `${formatCodePoints(str)} is not blank`) + assert(!isBlank(str), `${formatCodePoints(str)} is not blank`) } }) diff --git a/tests/lib/timing-safe-equal.js b/tests/lib/timing-safe-equal.js index 061f75001..ee7509419 100644 --- a/tests/lib/timing-safe-equal.js +++ b/tests/lib/timing-safe-equal.js @@ -1,30 +1,30 @@ -// @ts-check -import test from 'brittle' +import test from 'node:test' +import assert from 'node:assert/strict' import timingSafeEqual from '../../src/lib/timing-safe-equal.js' -test('comparing buffers', (t) => { +test('comparing buffers', () => { const a = Buffer.from([1, 2, 3]) const b = Buffer.from([1, 2, 3]) const c = Buffer.from([4, 5, 6]) const d = Buffer.from([7]) - t.ok(timingSafeEqual(a, a)) - t.ok(timingSafeEqual(a, b)) + assert(timingSafeEqual(a, a)) + assert(timingSafeEqual(a, b)) - t.absent(timingSafeEqual(a, c)) - t.absent(timingSafeEqual(a, d)) + assert(!timingSafeEqual(a, c)) + assert(!timingSafeEqual(a, d)) }) -test('comparing strings', (t) => { - t.ok(timingSafeEqual('foo', 'foo')) - t.absent(timingSafeEqual('foo', 'bar')) - t.absent(timingSafeEqual('foo', 'x')) - t.absent( - timingSafeEqual(String.fromCharCode(0x49), String.fromCharCode(0x6c49)), +test('comparing strings', () => { + assert(timingSafeEqual('foo', 'foo')) + assert(!timingSafeEqual('foo', 'bar')) + assert(!timingSafeEqual('foo', 'x')) + assert( + !timingSafeEqual(String.fromCharCode(0x49), String.fromCharCode(0x6c49)), 'characters that might truncate the same are still different' ) - t.absent( - timingSafeEqual('\udc69', '\udc6a'), + assert( + !timingSafeEqual('\udc69', '\udc6a'), 'lone surrogates compare correctly' ) }) From a39cb5378052d8956f9f107873ea2f7fe53eda0b Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Thu, 16 May 2024 08:59:10 -0500 Subject: [PATCH 16/20] test: move invite API tests to `node:test` (#644) This test-only change drops Brittle from our invite API tests and replaces them with `node:test` and `node:assert`. --- tests/invite-api.js | 234 ++++++++++++++++++++++---------------------- 1 file changed, 119 insertions(+), 115 deletions(-) diff --git a/tests/invite-api.js b/tests/invite-api.js index c9fa72d03..3d4a7d539 100644 --- a/tests/invite-api.js +++ b/tests/invite-api.js @@ -1,5 +1,6 @@ // @ts-check -import test from 'brittle' +import test from 'node:test' +import assert from 'node:assert/strict' import { once } from 'node:events' import { compact, concat, map, every, first } from 'iterpal' import { onTimes } from './helpers/events.js' @@ -23,7 +24,7 @@ class MockLocalPeers extends LocalPeers { } } -test('has no invites to start', (t) => { +test('has no invites to start', () => { const { rpc } = setup() const inviteApi = new InviteApi({ @@ -31,15 +32,15 @@ test('has no invites to start', (t) => { queries: { isMember: () => false, addProject: async () => { - t.fail('should not be called') + assert.fail('should not be called') }, }, }) - t.alike(inviteApi.getPending(), []) + assert.deepEqual(inviteApi.getPending(), []) }) -test('invite-received event has expected payload', async (t) => { +test('invite-received event has expected payload', async () => { const { rpc, invitorPeerId, projectPublicId } = setup() const inviteApi = new InviteApi({ @@ -47,7 +48,7 @@ test('invite-received event has expected payload', async (t) => { queries: { isMember: () => false, addProject: async () => { - t.fail('should not be called') + assert.fail('should not be called') }, }, }) @@ -103,20 +104,18 @@ test('invite-received event has expected payload', async (t) => { ] const receivedInvitesArgs = (await invitesReceivedPromise).map(first) assertInvitesAlike( - t, receivedInvitesArgs, expectedInvites, 'received expected invites' ) assertInvitesAlike( - t, inviteApi.getPending(), expectedInvites, 'pending invites are expected' ) }) -test('Accept invite', async (t) => { +test('Accept invite', async () => { const { rpc, invitorPeerId, @@ -152,7 +151,6 @@ test('Accept invite', async (t) => { await inviteReceivedPromise assertInvitesAlike( - t, inviteApi.getPending(), [inviteExternal], 'has one pending invite' @@ -161,17 +159,17 @@ test('Accept invite', async (t) => { // Invitor: prepare to share project join details upon acceptance rpc.once('invite-response', (peerId, inviteResponse) => { - t.is( + assert.equal( peerId, invitorPeerId, 'received an invite response from the correct peer' ) - t.alike( + assert.deepEqual( inviteResponse.inviteId, invite.inviteId, 'received an invite response to this invite' ) - t.is( + assert.equal( inviteResponse.decision, InviteResponse_Decision.ACCEPT, 'received an accept' @@ -188,19 +186,23 @@ test('Accept invite', async (t) => { const acceptResult = await inviteApi.accept(inviteExternal) - t.is(acceptResult, projectPublicId, 'accept returns project public ID') - t.ok( + assert.equal( + acceptResult, + projectPublicId, + 'accept returns project public ID' + ) + assert( projectKeysFound.some((k) => k.equals(projectKey)), 'added to project' ) const [removedInvite, removalReason] = await inviteRemovedPromise - assertInvitesAlike(t, removedInvite, inviteExternal, 'invite was removed') - t.is(removalReason, 'accepted') - assertInvitesAlike(t, inviteApi.getPending(), [], 'no invites remain') + assertInvitesAlike(removedInvite, inviteExternal, 'invite was removed') + assert.equal(removalReason, 'accepted') + assertInvitesAlike(inviteApi.getPending(), [], 'no invites remain') }) -test('Reject invite', async (t) => { +test('Reject invite', async () => { const { rpc, invitorPeerId, invite, inviteExternal } = setup() const inviteApi = new InviteApi({ @@ -208,7 +210,7 @@ test('Reject invite', async (t) => { queries: { isMember: () => false, addProject: async () => { - t.fail('should not add project') + assert.fail('should not add project') }, }, }) @@ -224,7 +226,6 @@ test('Reject invite', async (t) => { await inviteReceivedPromise assertInvitesAlike( - t, inviteApi.getPending(), [inviteExternal], 'has one pending invite' @@ -241,25 +242,33 @@ test('Reject invite', async (t) => { inviteApi.reject(inviteExternal) const [removedInvite, removalReason] = await inviteRemovedPromise - assertInvitesAlike(t, removedInvite, inviteExternal, 'invite was removed') - t.is(removalReason, 'rejected') - assertInvitesAlike(t, inviteApi.getPending(), [], 'pending invites removed') + assertInvitesAlike(removedInvite, inviteExternal, 'invite was removed') + assert.equal(removalReason, 'rejected') + assertInvitesAlike(inviteApi.getPending(), [], 'pending invites removed') // Invitor: check rejection const [inviteResponsePeerId, inviteResponse] = await inviteResponseEventPromise - t.is(inviteResponsePeerId, invitorPeerId, 'got response from right peer') - t.alike( + assert.equal( + inviteResponsePeerId, + invitorPeerId, + 'got response from right peer' + ) + assert.deepEqual( inviteResponse.inviteId, invite.inviteId, 'got response for the right invite' ) - t.is(inviteResponse.decision, InviteResponse_Decision.REJECT, 'got rejection') + assert.equal( + inviteResponse.decision, + InviteResponse_Decision.REJECT, + 'got rejection' + ) }) test('Receiving invite for project that peer already belongs to', async (t) => { - t.test('was member prior to connection', async (t) => { + await t.test('was member prior to connection', async () => { const { rpc, invitorPeerId, projectPublicId, invite } = setup() const inviteApi = new InviteApi({ @@ -267,13 +276,13 @@ test('Receiving invite for project that peer already belongs to', async (t) => { queries: { isMember: (p) => p === projectPublicId, addProject: async () => { - t.fail('should not add project') + assert.fail('should not add project') }, }, }) inviteApi.on('invite-received', () => { - t.fail('should not emit a received invite') + assert.fail('should not emit a received invite') }) // Invitor: prepare to receive response @@ -284,30 +293,34 @@ test('Receiving invite for project that peer already belongs to', async (t) => { rpc.emit('invite', invitorPeerId, invite) - assertInvitesAlike(t, inviteApi.getPending(), [], 'has no pending invites') + assertInvitesAlike(inviteApi.getPending(), [], 'has no pending invites') // Invitor: check invite response const [inviteResponsePeerId, inviteResponse] = await inviteResponseEventPromise - t.is(inviteResponsePeerId, invitorPeerId, 'got response from right peer') - t.alike( + assert.equal( + inviteResponsePeerId, + invitorPeerId, + 'got response from right peer' + ) + assert.deepEqual( inviteResponse.inviteId, invite.inviteId, 'got response to right invite' ) - t.is( + assert.equal( inviteResponse.decision, InviteResponse_Decision.ALREADY, 'got "already" response' ) - assertInvitesAlike(t, inviteApi.getPending(), [], 'has no pending invites') + assertInvitesAlike(inviteApi.getPending(), [], 'has no pending invites') }) - t.test( + await t.test( 'became member (somehow!) between receiving invite and accepting', - async (t) => { + async () => { const { rpc, invitorPeerId, invite, inviteExternal } = setup() let isMember = false @@ -317,7 +330,7 @@ test('Receiving invite for project that peer already belongs to', async (t) => { queries: { isMember: () => isMember, addProject: async () => { - t.fail('should not add project') + assert.fail('should not add project') }, }, }) @@ -333,7 +346,6 @@ test('Receiving invite for project that peer already belongs to', async (t) => { await inviteReceivedPromise assertInvitesAlike( - t, inviteApi.getPending(), [inviteExternal], 'has a pending invite' @@ -352,26 +364,21 @@ test('Receiving invite for project that peer already belongs to', async (t) => { await inviteApi.accept(inviteExternal) const [removedInvite, removalReason] = await inviteRemovedPromise - assertInvitesAlike(t, removedInvite, inviteExternal, 'invite was removed') - t.is(removalReason, 'accepted') - assertInvitesAlike( - t, - inviteApi.getPending(), - [], - 'has no pending invites' - ) + assertInvitesAlike(removedInvite, inviteExternal, 'invite was removed') + assert.equal(removalReason, 'accepted') + assertInvitesAlike(inviteApi.getPending(), [], 'has no pending invites') // Invitor: check invite response const [inviteResponsePeerId, inviteResponse] = await inviteResponseEventPromise - t.is(inviteResponsePeerId, invitorPeerId) - t.alike(inviteResponse.inviteId, invite.inviteId) - t.is(inviteResponse.decision, InviteResponse_Decision.ALREADY) + assert.equal(inviteResponsePeerId, invitorPeerId) + assert.deepEqual(inviteResponse.inviteId, invite.inviteId) + assert.equal(inviteResponse.decision, InviteResponse_Decision.ALREADY) } ) - t.test('became member from accepting another invite', async (t) => { + await t.test('became member from accepting another invite', async () => { const { rpc, invitorPeerId: invitor1PeerId, @@ -388,7 +395,7 @@ test('Receiving invite for project that peer already belongs to', async (t) => { queries: { isMember: () => false, addProject: async ({ projectKey }) => { - t.absent(projectKeyAdded, 'only adds one project') + assert(!projectKeyAdded, 'only adds one project') projectKeyAdded = projectKey }, }, @@ -443,7 +450,6 @@ test('Receiving invite for project that peer already belongs to', async (t) => { await invitesReceivedPromise assertInvitesAlike( - t, inviteApi.getPending(), [ inviteExternal, @@ -466,14 +472,14 @@ test('Receiving invite for project that peer already belongs to', async (t) => { peerId === invitor1PeerId && inviteResponse.inviteId.equals(invite.inviteId) ) { - t.is(inviteResponse.decision, InviteResponse_Decision.ACCEPT) + assert.equal(inviteResponse.decision, InviteResponse_Decision.ACCEPT) rpc.emit('got-project-details', invitor1PeerId, { inviteId: invite.inviteId, projectKey, encryptionKeys, }) } else { - t.is(inviteResponse.decision, InviteResponse_Decision.ALREADY) + assert.equal(inviteResponse.decision, InviteResponse_Decision.ALREADY) } }) @@ -483,7 +489,7 @@ test('Receiving invite for project that peer already belongs to', async (t) => { await inviteApi.accept(inviteExternal) - t.alike( + assert.deepEqual( responses, new Set([ { @@ -532,7 +538,6 @@ test('Receiving invite for project that peer already belongs to', async (t) => { const allButLastRemoved = removedInvites.slice(0, -1) const lastRemoved = removedInvites[removedInvites.length - 1] assertInvitesAlike( - t, new Set(allButLastRemoved), new Set([ secondInviteExternalFromPeer1, @@ -543,18 +548,16 @@ test('Receiving invite for project that peer already belongs to', async (t) => { 'other invites are removed first, to avoid UI jitter' ) assertInvitesAlike( - t, lastRemoved, inviteExternal, 'accepted invite was removed last, to avoid UI jitter' ) assertInvitesAlike( - t, inviteApi.getPending(), [unrelatedInviteExternal], 'unaffected invites stick around' ) - t.alike( + assert.deepEqual( removalReasons, [ 'accepted other', @@ -568,7 +571,7 @@ test('Receiving invite for project that peer already belongs to', async (t) => { }) }) -test('trying to accept or reject non-existent invite throws', async (t) => { +test('trying to accept or reject non-existent invite throws', async () => { const { rpc, inviteExternal } = setup() const inviteApi = new InviteApi({ @@ -580,19 +583,19 @@ test('trying to accept or reject non-existent invite throws', async (t) => { }) inviteApi.on('invite-received', () => { - t.fail('should not emit an "added" event') + assert.fail('should not emit an "added" event') }) inviteApi.on('invite-removed', () => { - t.fail('should not emit an "removed" event') + assert.fail('should not emit an "removed" event') }) - await t.exception(() => inviteApi.accept(inviteExternal)) - t.exception(() => inviteApi.reject(inviteExternal)) + await assert.rejects(() => inviteApi.accept(inviteExternal)) + assert.throws(() => inviteApi.reject(inviteExternal)) - assertInvitesAlike(t, inviteApi.getPending(), [], 'has no pending invites') + assertInvitesAlike(inviteApi.getPending(), [], 'has no pending invites') }) -test('throws when quickly double-accepting the same invite', async (t) => { +test('throws when quickly double-accepting the same invite', async () => { const { rpc, invitorPeerId, @@ -630,7 +633,7 @@ test('throws when quickly double-accepting the same invite', async (t) => { rpc.on('invite-response', (_peerId, inviteResponse) => { inviteResponseCount++ - t.alike( + assert.deepEqual( inviteResponse.inviteId, invite.inviteId, 'responds to the correct invite' @@ -647,20 +650,20 @@ test('throws when quickly double-accepting the same invite', async (t) => { const firstAcceptPromise = inviteApi.accept(inviteExternal) - await t.exception( + await assert.rejects( () => inviteApi.accept(inviteExternal), 'second accept fails' ) await firstAcceptPromise - t.ok( + assert( projectKeysFound.some((k) => k.equals(projectKey)), 'added to project' ) - t.is(inviteResponseCount, 1, 'only sent one invite response') + assert.equal(inviteResponseCount, 1, 'only sent one invite response') }) -test('throws when quickly accepting and then rejecting an invite', async (t) => { +test('throws when quickly accepting and then rejecting an invite', async () => { const { rpc, invitorPeerId, @@ -706,12 +709,12 @@ test('throws when quickly accepting and then rejecting an invite', async (t) => const acceptPromise = inviteApi.accept(inviteExternal) - t.exception(() => inviteApi.reject(inviteExternal), 'reject fails') + assert.throws(() => inviteApi.reject(inviteExternal), 'reject fails') await acceptPromise }) -test('throws when quickly accepting two invites for the same project', async (t) => { +test('throws when quickly accepting two invites for the same project', async () => { const { rpc, invitorPeerId, @@ -753,7 +756,7 @@ test('throws when quickly accepting two invites for the same project', async (t) rpc.on('invite-response', (_peerId, inviteResponse) => { inviteResponseCount++ - t.alike( + assert.deepEqual( inviteResponse.inviteId, invite1.inviteId, 'responds to the correct invite' @@ -770,20 +773,20 @@ test('throws when quickly accepting two invites for the same project', async (t) const firstAcceptPromise = inviteApi.accept(invite1External) - await t.exception( + await assert.rejects( () => inviteApi.accept(invite2External), 'second accept fails' ) await firstAcceptPromise - t.ok( + assert( projectKeysFound.some((k) => k.equals(projectKey)), 'added to project' ) - t.is(inviteResponseCount, 1, 'only sent one invite response') + assert.equal(inviteResponseCount, 1, 'only sent one invite response') }) -test('receiving project join details from an unknown peer is a no-op', async (t) => { +test('receiving project join details from an unknown peer is a no-op', async () => { const { rpc, invitorPeerId, @@ -798,7 +801,7 @@ test('receiving project join details from an unknown peer is a no-op', async (t) queries: { isMember: () => false, addProject: async () => { - t.fail('should not be called') + assert.fail('should not be called') }, }, }) @@ -814,7 +817,6 @@ test('receiving project join details from an unknown peer is a no-op', async (t) await inviteReceivedPromise assertInvitesAlike( - t, inviteApi.getPending(), [inviteExternal], 'has one pending invite' @@ -848,14 +850,13 @@ test('receiving project join details from an unknown peer is a no-op', async (t) // The original invite should still be around assertInvitesAlike( - t, inviteApi.getPending(), [inviteExternal], 'has original pending invite' ) }) -test('receiving project join details for an unknown invite ID is a no-op', async (t) => { +test('receiving project join details for an unknown invite ID is a no-op', async () => { const { rpc, invitorPeerId, @@ -870,7 +871,7 @@ test('receiving project join details for an unknown invite ID is a no-op', async queries: { isMember: () => false, addProject: async () => { - t.fail('should not be called') + assert.fail('should not be called') }, }, }) @@ -886,7 +887,6 @@ test('receiving project join details for an unknown invite ID is a no-op', async await inviteReceivedPromise assertInvitesAlike( - t, inviteApi.getPending(), [inviteExternal], 'has one pending invite' @@ -919,14 +919,13 @@ test('receiving project join details for an unknown invite ID is a no-op', async // The original invite should still be around assertInvitesAlike( - t, inviteApi.getPending(), [inviteExternal], 'has original pending invite' ) }) -test('ignores duplicate invite IDs', async (t) => { +test('ignores duplicate invite IDs', async () => { const { rpc, invitorPeerId, invite } = setup() const { invite: invite2 } = setup() @@ -946,12 +945,12 @@ test('ignores duplicate invite IDs', async (t) => { await twoInvitesPromise const invites = inviteApi.getPending() - t.is(invites.length, 2, 'two invites') + assert.equal(invites.length, 2, 'two invites') const inviteIds = invites.map((i) => i.inviteId) - t.unlike(inviteIds[0], inviteIds[1], 'got different invite IDs') + assert.notDeepEqual(inviteIds[0], inviteIds[1], 'got different invite IDs') }) -test('failures to send acceptances cause accept to reject, no project to be added, and invite to be removed', async (t) => { +test('failures to send acceptances cause accept to reject, no project to be added, and invite to be removed', async () => { const { rpc, invitorPeerId, invite, inviteExternal } = setup() const inviteApi = new InviteApi({ @@ -959,15 +958,15 @@ test('failures to send acceptances cause accept to reject, no project to be adde queries: { isMember: () => false, addProject: async () => { - t.fail('should not try to add project if could not accept') + assert.fail('should not try to add project if could not accept') }, }, }) let acceptsAttempted = 0 rpc.sendInviteResponse = async (deviceId, inviteResponse) => { - t.is(deviceId, invitorPeerId) - t.is(inviteResponse.decision, InviteResponse_Decision.ACCEPT) + assert.equal(deviceId, invitorPeerId) + assert.equal(inviteResponse.decision, InviteResponse_Decision.ACCEPT) acceptsAttempted++ throw new Error('Failed to accept invite') } @@ -985,26 +984,28 @@ test('failures to send acceptances cause accept to reject, no project to be adde await inviteReceivedPromise assertInvitesAlike( - t, inviteApi.getPending(), [inviteExternal], 'has a pending invite' ) - await t.exception(() => inviteApi.accept(inviteExternal), 'fails to accept') + await assert.rejects( + () => inviteApi.accept(inviteExternal), + 'fails to accept' + ) - t.is(acceptsAttempted, 1) + assert.equal(acceptsAttempted, 1) const [removedInvite, removalReason] = await inviteRemovedPromise - assertInvitesAlike(t, removedInvite, inviteExternal, 'invite was removed') - t.is( + assertInvitesAlike(removedInvite, inviteExternal, 'invite was removed') + assert.equal( removalReason, 'connection error', 'invite was removed with connection error reason' ) - assertInvitesAlike(t, inviteApi.getPending(), [], 'has no pending invites') + assertInvitesAlike(inviteApi.getPending(), [], 'has no pending invites') }) -test('failures to send rejections are ignored, but invite is still removed', async (t) => { +test('failures to send rejections are ignored, but invite is still removed', async () => { const { rpc, invitorPeerId, invite, inviteExternal } = setup() const inviteApi = new InviteApi({ @@ -1012,15 +1013,15 @@ test('failures to send rejections are ignored, but invite is still removed', asy queries: { isMember: () => false, addProject: async () => { - t.fail('should not add project') + assert.fail('should not add project') }, }, }) let rejectionsAttempted = 0 rpc.sendInviteResponse = async (deviceId, inviteResponse) => { - t.is(deviceId, invitorPeerId) - t.is(inviteResponse.decision, InviteResponse_Decision.REJECT) + assert.equal(deviceId, invitorPeerId) + assert.equal(inviteResponse.decision, InviteResponse_Decision.REJECT) rejectionsAttempted++ throw new Error('Failed to reject invite') } @@ -1036,15 +1037,15 @@ test('failures to send rejections are ignored, but invite is still removed', asy const inviteRemovedPromise = once(inviteApi, 'invite-removed') await inviteReceivedPromise - t.execution(() => inviteApi.reject(inviteExternal)) + assert.doesNotThrow(() => inviteApi.reject(inviteExternal)) - t.is(rejectionsAttempted, 1) + assert.equal(rejectionsAttempted, 1) const [removedInvite, removalReason] = await inviteRemovedPromise - assertInvitesAlike(t, removedInvite, inviteExternal, 'invite was removed') - t.is(removalReason, 'rejected', 'removal reason was "rejected"') + assertInvitesAlike(removedInvite, inviteExternal, 'invite was removed') + assert.equal(removalReason, 'rejected', 'removal reason was "rejected"') }) -test('failures to add project cause accept() to reject and invite to be removed', async (t) => { +test('failures to add project cause accept() to reject and invite to be removed', async () => { const { rpc, invitorPeerId, @@ -1094,14 +1095,14 @@ test('failures to add project cause accept() to reject and invite to be removed' const inviteRemovedPromise = once(inviteApi, 'invite-removed') - await t.exception( + await assert.rejects( () => inviteApi.accept(inviteExternal), 'accept should fail' ) const [removedInvite, removalReason] = await inviteRemovedPromise - assertInvitesAlike(t, removedInvite, inviteExternal, 'invite was removed') - t.is( + assertInvitesAlike(removedInvite, inviteExternal, 'invite was removed') + assert.equal( removalReason, 'internal error', 'invite was removed with correct reason' @@ -1146,21 +1147,24 @@ function setup() { * 1. They are are ignored during equality comparison * 2. If present, the timestamps must be within the last 30 seconds * - * @param {import('brittle').TestInstance} t * @param {unknown} actual * @param {unknown} expected * @param {string} message * @returns {void} */ -function assertInvitesAlike(t, actual, expected, message) { - t.alike(removeReceivedAt(actual), removeReceivedAt(expected), message) +function assertInvitesAlike(actual, expected, message) { + assert.deepEqual( + removeReceivedAt(actual), + removeReceivedAt(expected), + message + ) const allReceivedAts = concat( getReceivedAts(actual), getReceivedAts(expected) ) const now = Date.now() - t.ok( + assert( every( allReceivedAts, (receivedAt) => receivedAt > now - 30_000 && receivedAt <= now From 128024577c8f1ce80f86581ba2b857d1e734071c Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Thu, 16 May 2024 10:06:39 -0500 Subject: [PATCH 17/20] test: move utils tests to `node:test` (#649) This test-only change drops Brittle from our utils tests and replaces them with `node:test` and `node:assert`. --- tests/utils.js | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/tests/utils.js b/tests/utils.js index e80a19537..88b53685b 100644 --- a/tests/utils.js +++ b/tests/utils.js @@ -1,15 +1,20 @@ // @ts-check -import test from 'brittle' -import { assert, ExhaustivenessError, setHas } from '../src/utils.js' +import test from 'node:test' +import assert from 'node:assert/strict' +import { + assert as utilsAssert, + ExhaustivenessError, + setHas, +} from '../src/utils.js' -test('assert()', (t) => { - t.execution(() => assert(true, 'should work')) - t.exception(() => assert(false, 'uh oh'), /uh oh/) +test('assert()', () => { + assert.doesNotThrow(() => utilsAssert(true, 'should work')) + assert.throws(() => utilsAssert(false, 'uh oh'), { message: 'uh oh' }) }) -test('ExhaustivenessError', (t) => { +test('ExhaustivenessError', () => { const bools = [true, false] - t.execution(() => { + assert.doesNotThrow(() => { bools.forEach((bool) => { switch (bool) { case true: @@ -22,8 +27,8 @@ test('ExhaustivenessError', (t) => { }) }) -test('setHas()', (t) => { +test('setHas()', () => { const set = new Set([1, 2, 3]) - t.ok(setHas(set)(1)) - t.absent(setHas(set)(9)) + assert(setHas(set)(1)) + assert(!setHas(set)(9)) }) From a36fc2bf9f1fd76d2386cae66a6b6264367c0332 Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Thu, 16 May 2024 10:11:33 -0500 Subject: [PATCH 18/20] test: move CoreSyncState tests to `node:test` This test-only change drops Brittle from our CoreSyncState tests and replaces them with `node:test` and `node:assert`. --- tests/sync/core-sync-state.js | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/tests/sync/core-sync-state.js b/tests/sync/core-sync-state.js index 6acff7b90..edc80807e 100644 --- a/tests/sync/core-sync-state.js +++ b/tests/sync/core-sync-state.js @@ -1,6 +1,7 @@ // @ts-check +import test from 'node:test' +import assert from 'node:assert/strict' import NoiseSecretStream from '@hyperswarm/secret-stream' -import test from 'brittle' import Hypercore from 'hypercore' import RAM from 'random-access-memory' import { @@ -238,7 +239,7 @@ const scenarios = [ }, ] -test('deriveState() scenarios', (t) => { +test('deriveState() scenarios', () => { for (const { state, expected, message } of scenarios) { const derivedState = deriveState({ length: state.length, @@ -249,11 +250,11 @@ test('deriveState() scenarios', (t) => { peerSyncControllers: new Map(), namespace: 'auth', }) - t.alike(derivedState, expected, message) + assert.deepEqual(derivedState, expected, message) } }) -test('deriveState() have at index beyond bitfield page size', (t) => { +test('deriveState() have at index beyond bitfield page size', () => { const localState = createState({ have: 2 ** 10 - 1 }) const remoteState = new PeerState() const remoteHaveBitfield = new RemoteBitfield() @@ -284,10 +285,10 @@ test('deriveState() have at index beyond bitfield page size', (t) => { }, }, } - t.alike(deriveState(state), expected) + assert.deepEqual(deriveState(state), expected) }) -test('CoreReplicationState', async (t) => { +test('CoreReplicationState', async () => { for (const { state, expected, message } of scenarios) { const localCore = await createCore() await localCore.ready() @@ -343,7 +344,7 @@ test('CoreReplicationState', async (t) => { }) ) await updateWithTimeout(emitter, 100) - t.alike( + assert.deepEqual( crs.getState(), { ...expected, remoteStates: expectedRemoteStates }, message @@ -351,7 +352,7 @@ test('CoreReplicationState', async (t) => { } }) -test('bitCount32', (t) => { +test('bitCount32', () => { const testCases = new Set([0, 2 ** 32 - 1]) for (let i = 0; i < 32; i++) { testCases.add(2 ** i) @@ -364,19 +365,23 @@ test('bitCount32', (t) => { for (const n of testCases) { const actual = bitCount32(n) const expected = slowBitCount(n) - t.is(actual, expected, `${n.toString(2)} has ${expected} bit(s) set`) + assert.equal( + actual, + expected, + `${n.toString(2)} has ${expected} bit(s) set` + ) } }) // This takes several hours to run on my M2 Macbook Pro (it's the slowBitCount // that takes a long time - bitCount32 takes about 23 seconds), so not running // this by default. The test did pass when I ran it though. -test.skip('bitCount32 (full test)', (t) => { +test.skip('bitCount32 (full test)', () => { for (let n = 0; n < 2 ** 32; n++) { if (n % 2 ** 28 === 0) console.log(n) const bitCount = bitCount32(n) const expected = slowBitCount(n) - if (bitCount !== expected) t.fail('bitcount is correct ' + n) + if (bitCount !== expected) assert.fail('bitcount is correct ' + n) } }) From 56bcda4167a199003ab1c94e672a60453ec35cde Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Thu, 16 May 2024 10:11:48 -0500 Subject: [PATCH 19/20] test: move schema tests to `node:test` (#647) This test-only change drops Brittle from our schema tests and replaces them with `node:test` and `node:assert`. --- tests/schema.js | 41 ++++++++----------- tests/schema/schema-to-drizzle.js | 65 ++++++++++++++++++------------- 2 files changed, 54 insertions(+), 52 deletions(-) diff --git a/tests/schema.js b/tests/schema.js index 7e0053ab6..4d24a8055 100644 --- a/tests/schema.js +++ b/tests/schema.js @@ -1,6 +1,6 @@ // @ts-check -/* eslint-disable no-unused-vars */ -import { test } from 'brittle' +import test from 'node:test' +import assert from 'node:assert/strict' import { getTableConfig } from 'drizzle-orm/sqlite-core' import * as clientTableSchemas from '../src/schema/client.js' import * as projectTableSchemas from '../src/schema/project.js' @@ -15,7 +15,7 @@ import { deNullify } from '../src/utils.js' const MAPEO_DATATYPE_NAMES = Object.keys(jsonSchemas) -test('Expected table config', (t) => { +test('Expected table config', () => { const allTableSchemas = [ ...Object.values(clientTableSchemas), ...Object.values(projectTableSchemas), @@ -28,24 +28,18 @@ test('Expected table config', (t) => { if (!MAPEO_DATATYPE_NAMES.includes(config.name)) continue const schemaName = config.name - if (!(schemaName in jsonSchemas)) { - t.fail() - continue - } + assert(schemaName in jsonSchemas) const jsonSchema = jsonSchemas[/** @type {keyof typeof jsonSchemas} */ (schemaName)] for (const [key, value] of Object.entries(jsonSchema.properties)) { const columnConfig = config.columns.find((v) => v.name === key) - if (!columnConfig) { - t.fail() - continue - } + assert(columnConfig) if (key === 'docId') { - t.is(columnConfig.primary, true, 'docId is primary key') + assert.equal(columnConfig.primary, true, 'docId is primary key') } else { - t.is(columnConfig.primary, false, key + ' is not primary key') + assert.equal(columnConfig.primary, false, key + ' is not primary key') } - t.is( + assert.equal( columnConfig.notNull, // @ts-ignore jsonSchema.required.includes(key), @@ -55,7 +49,7 @@ test('Expected table config', (t) => { const expectedDefault = // @ts-ignore jsonSchema.required.includes(key) ? value.default : undefined - t.is(columnConfig.default, expectedDefault, 'Default is correct') + assert.equal(columnConfig.default, expectedDefault, 'Default is correct') } } }) @@ -72,7 +66,7 @@ test('Expected table config', (t) => { * @typedef {Extract} MapeoType */ -test('Types match', { skip: true }, (t) => { +test('Types match', { skip: true }, () => { // No brittle tests here, it's the typescript that must pass // This fails at runtime anyway because we don't create tables in the db @@ -86,23 +80,20 @@ test('Types match', { skip: true }, (t) => { const fResult = db.select().from(fieldTable).get() if (!(oResult && pResult && fResult)) { - t.fail() - return + assert.fail() } /** @type {MapeoType<'observation'>} */ - const o = deNullify(oResult) + const _o = deNullify(oResult) /** @type {MapeoType<'preset'>} */ - const p = deNullify(pResult) + const _p = deNullify(pResult) /** @type {MapeoType<'field'>} */ - const f = deNullify(fResult) - - t.pass() + const _f = deNullify(fResult) }) -test('backlink table exists for every indexed data type', (t) => { +test('backlink table exists for every indexed data type', () => { // Every indexed datatype needs a backlink table, which is used by // sqlite-indexer to track backlinks const allTableNames = [ @@ -120,7 +111,7 @@ test('backlink table exists for every indexed data type', (t) => { ) for (const name of dataTypeTableNames) { - t.ok( + assert( backlinkTableNames.includes(getBacklinkTableName(name)), `backlink table for ${name}` ) diff --git a/tests/schema/schema-to-drizzle.js b/tests/schema/schema-to-drizzle.js index 6f9900956..a3593aacc 100644 --- a/tests/schema/schema-to-drizzle.js +++ b/tests/schema/schema-to-drizzle.js @@ -1,78 +1,89 @@ // @ts-check -import test from 'brittle' +import test from 'node:test' +import assert from 'node:assert/strict' import { jsonSchemaToDrizzleColumns } from '../../src/schema/schema-to-drizzle.js' import { sqliteTable } from 'drizzle-orm/sqlite-core' -test('throws if not passed an object schema', (t) => { - t.exception(() => { +test('throws if not passed an object schema', () => { + assert.throws(() => { jsonSchemaToDrizzleColumns({ type: 'number', properties: {} }) }) }) -test('always adds "forks" column', (t) => { - t.ok( +test('always adds "forks" column', () => { + assert( 'forks' in jsonSchemaToDrizzleColumns({ type: 'object', properties: {} }), 'forks column is added' ) }) -test('skips null', (t) => { - t.absent( - 'foo' in +test('skips null', () => { + assert( + !( + 'foo' in jsonSchemaToDrizzleColumns({ type: 'object', properties: { foo: { type: 'null' } }, }) + ) ) }) -test('boolean', (t) => { +test('boolean', () => { const col = getColumn({ type: 'boolean' }) - t.is(col.getSQLType(), 'integer', 'booleans are stored in INTEGER columns') + assert.equal( + col.getSQLType(), + 'integer', + 'booleans are stored in INTEGER columns' + ) }) -test('number', (t) => { +test('number', () => { const col = getColumn({ type: 'number' }) - t.is(col.getSQLType(), 'real', 'numbers are stored in REAL columns') + assert.equal(col.getSQLType(), 'real', 'numbers are stored in REAL columns') }) -test('integer', (t) => { +test('integer', () => { const col = getColumn({ type: 'integer' }) - t.is(col.getSQLType(), 'integer', 'integers are stored in INTEGER columns') + assert.equal( + col.getSQLType(), + 'integer', + 'integers are stored in INTEGER columns' + ) }) -test('string', (t) => { +test('string', () => { const col = getColumn({ type: 'string' }) - t.is(col.getSQLType(), 'text', 'strings are stored in TEXT columns') + assert.equal(col.getSQLType(), 'text', 'strings are stored in TEXT columns') }) -test('string with enum', (t) => { +test('string with enum', () => { const col = getColumn({ type: 'string', enum: ['foo', 'bar'] }) - t.is(col.getSQLType(), 'text', 'strings are stored in TEXT columns') - t.alike(col.enumValues, ['foo', 'bar'], 'enums are saved') + assert.equal(col.getSQLType(), 'text', 'strings are stored in TEXT columns') + assert.deepEqual(col.enumValues, ['foo', 'bar'], 'enums are saved') }) -test('array', (t) => { +test('array', () => { const col = getColumn({ type: 'array' }) - t.is(col.getSQLType(), 'text', 'arrays are stored in TEXT columns') + assert.equal(col.getSQLType(), 'text', 'arrays are stored in TEXT columns') }) -test('object', (t) => { +test('object', () => { const col = getColumn({ type: 'object' }) - t.is(col.getSQLType(), 'text', 'objects are stored in TEXT columns') + assert.equal(col.getSQLType(), 'text', 'objects are stored in TEXT columns') }) -test('required columns', (t) => { +test('required columns', () => { const col = getColumn({ type: 'number' }, { required: ['property'] }) - t.ok(col.notNull, 'required columns are NOT NULL') + assert(col.notNull, 'required columns are NOT NULL') }) -test('default values', (t) => { +test('default values', () => { const col = getColumn( { type: 'number', default: 123 }, { required: ['property'] } ) - t.is(col.default, 123, 'sets default value') + assert.equal(col.default, 123, 'sets default value') }) /** From 49397fda0d020974b7fb276de2f117efea74cdd9 Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Thu, 16 May 2024 16:24:12 +0000 Subject: [PATCH 20/20] Remove assert.doesNotReject --- tests/fastify-controller.js | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/tests/fastify-controller.js b/tests/fastify-controller.js index f82d7d2ad..8a12c8a21 100644 --- a/tests/fastify-controller.js +++ b/tests/fastify-controller.js @@ -1,6 +1,5 @@ // @ts-check import test from 'node:test' -import assert from 'node:assert/strict' import Fastify from 'fastify' import { FastifyController } from '../src/fastify-controller.js' @@ -16,17 +15,16 @@ test('lifecycle', async () => { { host: '0.0.0.0' }, ] - assert.doesNotReject(async () => { - for (const opts of startOptsFixtures) { - await fastifyController.start(opts) - await fastifyController.start(opts) - await fastifyController.stop() - await fastifyController.stop() + // This should run without errors. + for (const opts of startOptsFixtures) { + await fastifyController.start(opts) + await fastifyController.start(opts) + await fastifyController.stop() + await fastifyController.stop() - fastifyController.start(opts) - await fastifyController.started() - await fastifyController.started() - await fastifyController.stop() - } - }) + fastifyController.start(opts) + await fastifyController.started() + await fastifyController.started() + await fastifyController.stop() + } })