From c777b79431786087178bf14d93e176b3d488aab6 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 11 Nov 2025 20:19:08 -0500 Subject: [PATCH] implement assert.isNotNullish, assert.throws, assert.throwsAsync, and assert.rejectsAsync testing utilities --- .../backend/test/misc/custom-assertions.ts | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 packages/backend/test/misc/custom-assertions.ts diff --git a/packages/backend/test/misc/custom-assertions.ts b/packages/backend/test/misc/custom-assertions.ts new file mode 100644 index 0000000000..7118b247f6 --- /dev/null +++ b/packages/backend/test/misc/custom-assertions.ts @@ -0,0 +1,90 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { inspect } from 'node:util'; +import { isError } from '@/misc/is-error.js'; + +export function isNotNullish(value: T): NonNullable { + expect(value).not.toBe(undefined); + expect(value).not.toBe(null); + return value as NonNullable; +} + +export function throws(callback: SyncCallback): unknown; +export function throws(errorClass: TError, callback: SyncCallback): InstanceType; +export function throws(errorClassOrCallback: TError | (SyncCallback), callbackOrUndefined?: SyncCallback): InstanceType | unknown { + const callback = (callbackOrUndefined ?? errorClassOrCallback) as SyncCallback; + const errorClass = callbackOrUndefined !== undefined ? (errorClassOrCallback as TError) : undefined; + + let result: unknown = undefined; + + try { + result = callback(); + } catch (error) { + assertIsErrorOfType(errorClass, error); + return error as InstanceType; + } + + const callbackName = callback.name || 'callback'; + const errorName = errorClass?.name || 'Error'; + const resultSummary = inspect(result); + throw new Error(`assert.throws: expected ${callbackName} to throw ${errorName}, but instead it returned: ${resultSummary}`, { cause: result }); +} + +export async function throwsAsync(callback: AsyncCallback): Promise; +export async function throwsAsync(errorClass: TError, callback: AsyncCallback): Promise>; +export async function throwsAsync(errorClassOrCallback: TError | AsyncCallback, callbackOrUndefined?: AsyncCallback): Promise | unknown> { + const callback = (callbackOrUndefined ?? errorClassOrCallback) as AsyncCallback; + const errorClass = callbackOrUndefined !== undefined ? (errorClassOrCallback as TError) : undefined; + + let result: unknown = undefined; + + try { + result = await callback(); + } catch (error) { + assertIsErrorOfType(errorClass, error); + return error as InstanceType; + } + + const callbackName = callback.name || 'callback'; + const errorName = errorClass?.name || 'Error'; + const resultSummary = inspect(result); + throw new Error(`assert.throwsAsync: expected ${callbackName} to throw ${errorName}, but instead it returned: ${resultSummary}`, { cause: result }); +} + +export async function rejectsAsync(promise: AnyPromise): Promise; +export async function rejectsAsync(errorClass: TError, promise: AnyPromise): Promise>; +export async function rejectsAsync(errorClassOrPromise: TError | AnyPromise, promiseOrUndefined?: AnyPromise): Promise | unknown> { + const promise = (promiseOrUndefined ?? errorClassOrPromise) as AnyPromise; + const errorClass = promiseOrUndefined !== undefined ? (errorClassOrPromise as TError) : undefined; + + let result: unknown = undefined; + + try { + result = await promise; + } catch (error) { + assertIsErrorOfType(errorClass, error); + return error as InstanceType; + } + + const errorName = errorClass?.name || 'Error'; + const resultSummary = inspect(result); + throw new Error(`assert.rejectsAsync: expected promise to reject with ${errorName}, but instead it resolved with ${resultSummary}`, { cause: result }); +} + +function assertIsErrorOfType(errorClass: AnyConstructor | undefined, error: unknown): void { + if (errorClass === Error) { + expect(error).toBeDefined(); + expect(isError(error)).toBe(true); + } else if (errorClass !== undefined) { + expect(error).toBeDefined(); + expect(error).toBeInstanceOf(errorClass); + } +} + +type AnyConstructor = abstract new(...args: unknown[]) => unknown; +type AnyPromise = Promise; +type SyncCallback = () => unknown; +type AsyncCallback = () => Promise;