Files
Ivan Pereira bbb5623f25 refactor(runtime): replace Bun dependency with Node fetch
Run Perplexity search and auth requests through pi's Node runtime instead of
shelling out to Bun. Move development and CI commands to npm, add a Node test
build path, and keep OTP auth fail-fast when Set-Cookie headers are not
available.
2026-06-23 15:51:16 +01:00

275 lines
8.4 KiB
TypeScript

import { AssertionError } from "node:assert";
import { afterEach, beforeEach, describe, mock as nodeMock, test } from "node:test";
import { isDeepStrictEqual } from "node:util";
export { afterEach, beforeEach, describe, test };
type AnyFunction = (...args: any[]) => any;
type MockFunction<T extends AnyFunction> = ((...args: Parameters<T>) => ReturnType<T>) & {
mock: { calls: Parameters<T>[] };
};
interface MockApi {
<T extends AnyFunction>(implementation?: T): MockFunction<T>;
module(specifier: string, factory: () => Record<string, unknown>): unknown;
restore(): void;
}
function fail(message: string): never {
throw new AssertionError({ message });
}
function format(value: unknown): string {
return typeof value === "string" ? JSON.stringify(value) : String(value);
}
function createMock<T extends AnyFunction>(implementation?: T): MockFunction<T> {
const calls: Parameters<T>[] = [];
const fn = function (this: unknown, ...args: Parameters<T>): ReturnType<T> {
calls.push(args);
return implementation?.apply(this, args) as ReturnType<T>;
} as MockFunction<T>;
fn.mock = { calls };
return fn;
}
function callerUrl(): string | null {
const stack = new Error().stack?.split("\n") ?? [];
for (const line of stack) {
if (line.includes("test-helpers.js") || line.includes("test-helpers.ts")) {
continue;
}
const match = line.match(/(file:\/\/.*):(\d+):(\d+)/);
if (match) {
return match[1];
}
}
return null;
}
function resolveMockSpecifier(specifier: string): string {
if (!specifier.startsWith(".") && !specifier.startsWith("/")) {
return specifier;
}
const base = callerUrl();
return base ? new URL(specifier, base).href : specifier;
}
export const mock: MockApi = Object.assign(
<T extends AnyFunction>(implementation?: T) => createMock(implementation),
{
module(specifier: string, factory: () => Record<string, unknown>) {
const moduleMock = nodeMock.module as unknown as (
specifier: string,
options: { exports: Record<string, unknown> },
) => unknown;
return moduleMock.call(nodeMock, resolveMockSpecifier(specifier), { exports: factory() });
},
restore() {
nodeMock.restoreAll();
},
},
);
function getCalls(value: unknown): unknown[][] {
const calls = (value as { mock?: { calls?: unknown[] } }).mock?.calls;
if (!Array.isArray(calls)) {
return [];
}
return calls.map((call) => {
if (Array.isArray(call)) {
return call;
}
if (call && typeof call === "object" && "arguments" in call) {
return Array.from((call as { arguments: Iterable<unknown> }).arguments);
}
return [];
});
}
function matchesObject(actual: unknown, expected: unknown): boolean {
if (!expected || typeof expected !== "object") {
return isDeepStrictEqual(actual, expected);
}
if (!actual || typeof actual !== "object") {
return false;
}
for (const [key, expectedValue] of Object.entries(expected)) {
if (!(key in actual)) {
return false;
}
const actualValue = (actual as Record<string, unknown>)[key];
if (!matchesObject(actualValue, expectedValue)) {
return false;
}
}
return true;
}
function hasProperty(actual: unknown, property: string): boolean {
return actual !== null && actual !== undefined && property in Object(actual);
}
function matchesThrown(error: unknown, expected?: unknown): boolean {
if (expected === undefined) {
return true;
}
if (typeof expected === "string") {
return error instanceof Error && error.message.includes(expected);
}
if (expected instanceof RegExp) {
return error instanceof Error && expected.test(error.message);
}
if (typeof expected === "function") {
return error instanceof (expected as new (...args: never[]) => Error);
}
return matchesObject(error, expected);
}
function assertPass(pass: boolean, negated: boolean, message: string): void {
if (negated ? pass : !pass) {
fail(`${negated ? "Expected not: " : "Expected: "}${message}`);
}
}
interface Matchers {
not: Matchers;
rejects: {
toMatchObject(expected: unknown): Promise<void>;
toThrow(expected?: unknown): Promise<void>;
};
toBe(expected: unknown): void;
toEqual(expected: unknown): void;
toContain(expected: unknown): void;
toContainEqual(expected: unknown): void;
toBeNull(): void;
toBeDefined(): void;
toBeTruthy(): void;
toBeFalsy(): void;
toHaveLength(expected: number): void;
toHaveProperty(property: string): void;
toBeInstanceOf(expected: new (...args: any[]) => unknown): void;
toBeLessThan(expected: number): void;
toMatchObject(expected: unknown): void;
toHaveBeenCalledTimes(expected: number): void;
toHaveBeenCalledWith(...expectedArgs: unknown[]): void;
toThrow(expected?: unknown): void;
}
function createMatchers(actual: unknown, negated = false): Matchers {
const matchers = {
get not() {
return createMatchers(actual, !negated);
},
rejects: {
async toMatchObject(expected: unknown) {
let rejected: unknown;
try {
await actual;
} catch (error) {
rejected = error;
}
assertPass(rejected !== undefined && matchesObject(rejected, expected), negated, "promise to reject with matching object");
},
async toThrow(expected?: unknown) {
let rejected: unknown;
try {
await actual;
} catch (error) {
rejected = error;
}
assertPass(rejected !== undefined && matchesThrown(rejected, expected), negated, "promise to reject with matching error");
},
},
toBe(expected: unknown) {
assertPass(Object.is(actual, expected), negated, `${format(actual)} to be ${format(expected)}`);
},
toEqual(expected: unknown) {
assertPass(isDeepStrictEqual(actual, expected), negated, `${format(actual)} to equal ${format(expected)}`);
},
toContain(expected: unknown) {
const pass =
typeof actual === "string"
? actual.includes(String(expected))
: Array.isArray(actual) && actual.includes(expected);
assertPass(pass, negated, `${format(actual)} to contain ${format(expected)}`);
},
toContainEqual(expected: unknown) {
const pass = Array.isArray(actual) && actual.some((value) => isDeepStrictEqual(value, expected));
assertPass(pass, negated, `${format(actual)} to contain equal ${format(expected)}`);
},
toBeNull() {
assertPass(actual === null, negated, `${format(actual)} to be null`);
},
toBeDefined() {
assertPass(actual !== undefined, negated, `${format(actual)} to be defined`);
},
toBeTruthy() {
assertPass(Boolean(actual), negated, `${format(actual)} to be truthy`);
},
toBeFalsy() {
assertPass(!actual, negated, `${format(actual)} to be falsy`);
},
toHaveLength(expected: number) {
assertPass((actual as { length?: number })?.length === expected, negated, `${format(actual)} to have length ${expected}`);
},
toHaveProperty(property: string) {
assertPass(hasProperty(actual, property), negated, `${format(actual)} to have property ${property}`);
},
toBeInstanceOf(expected: new (...args: any[]) => unknown) {
assertPass(actual instanceof expected, negated, `${format(actual)} to be instance of ${expected.name}`);
},
toBeLessThan(expected: number) {
assertPass(typeof actual === "number" && actual < expected, negated, `${format(actual)} to be less than ${expected}`);
},
toMatchObject(expected: unknown) {
assertPass(matchesObject(actual, expected), negated, `${format(actual)} to match object ${format(expected)}`);
},
toHaveBeenCalledTimes(expected: number) {
assertPass(getCalls(actual).length === expected, negated, `mock to be called ${expected} times`);
},
toHaveBeenCalledWith(...expectedArgs: unknown[]) {
const pass = getCalls(actual).some((args) => isDeepStrictEqual(args, expectedArgs));
assertPass(pass, negated, `mock to be called with ${format(expectedArgs)}`);
},
toThrow(expected?: unknown) {
if (typeof actual !== "function") {
fail("Expected value to be a function");
}
let thrown: unknown;
try {
actual();
} catch (error) {
thrown = error;
}
assertPass(thrown !== undefined && matchesThrown(thrown, expected), negated, "function to throw matching error");
},
} satisfies Matchers;
return matchers;
}
export function expect(actual: unknown): Matchers {
return createMatchers(actual);
}