Add test infrastructure with coverage and DB test skipping

- Create @reviq/test-helpers package with shared test utilities
- Add describeE2E helper that auto-prefixes test names with [e2e]
- Support SKIP_DB_TESTS=1 to skip database-dependent tests
- Add unix socket support for TEST_DATABASE_URL
- Add root commands: test:unit, test:all, test:cov, test:unit:cov
- Configure bunfig.toml to exclude dist/ from coverage reports
- Clean up tsconfig.json files to remove redundant settings

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
igm
2026-01-12 13:03:41 +08:00
parent 44a480179b
commit b2fba6e150
25 changed files with 3854 additions and 3688 deletions

View File

@@ -9,9 +9,7 @@
"typecheck": "tsc --noEmit",
"lint": "eslint . --cache",
"clean": "rm -rf dist .eslintcache",
"test:e2e": "bun test src/__tests__/e2e --no-parallel --coverage",
"test:unit": "bun test src/__tests__/unit",
"test": "bun test --coverage src/utils"
"test": "bun test src/ --no-parallel"
},
"dependencies": {
"@formatjs/intl-durationformat": "^0.9.2",
@@ -34,12 +32,11 @@
"devDependencies": {
"@macalinao/eslint-config": "catalog:",
"@macalinao/tsconfig": "catalog:",
"@reviq/test-helpers": "workspace:*",
"@reviq/virtual-authenticator": "workspace:*",
"@types/bun": "catalog:",
"@types/pg": "^8.16.0",
"@types/zxcvbn": "^4.4.5",
"eslint": "catalog:",
"pg": "^8.16.3",
"pino-pretty": "^13.1.3",
"typescript": "catalog:"
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,7 @@
"typecheck": "tsc --noEmit",
"lint": "eslint . --cache",
"clean": "rm -rf dist .eslintcache",
"test": "bun test"
"test": "bun test src/"
},
"dependencies": {
"@noble/hashes": "^2.0.1",

View File

@@ -35,12 +35,11 @@
"devDependencies": {
"@macalinao/eslint-config": "catalog:",
"@macalinao/tsconfig": "catalog:",
"@reviq/test-helpers": "workspace:*",
"@reviq/virtual-authenticator": "workspace:*",
"@types/bun": "catalog:",
"@types/pg": "^8.16.0",
"@types/zxcvbn": "^4.4.5",
"eslint": "catalog:",
"pg": "^8.16.3",
"pino-pretty": "^13.1.3",
"typescript": "catalog:",
},
@@ -180,6 +179,24 @@
"typescript": "catalog:",
},
},
"packages/testing/test-helpers": {
"name": "@reviq/test-helpers",
"version": "0.0.1",
"dependencies": {
"@reviq/db": "workspace:*",
"@reviq/db-schema": "workspace:*",
"kysely": "^0.28.2",
"pg": "^8.16.3",
},
"devDependencies": {
"@macalinao/eslint-config": "catalog:",
"@macalinao/tsconfig": "catalog:",
"@types/bun": "catalog:",
"@types/pg": "^8.16.0",
"eslint": "catalog:",
"typescript": "catalog:",
},
},
"packages/testing/virtual-authenticator": {
"name": "@reviq/virtual-authenticator",
"version": "0.0.1",
@@ -189,7 +206,7 @@
"devDependencies": {
"@macalinao/eslint-config": "catalog:",
"@macalinao/tsconfig": "catalog:",
"@types/bun": "latest",
"@types/bun": "catalog:",
"@types/node": "^25.0.3",
"eslint": "catalog:",
"typescript": "catalog:",
@@ -425,6 +442,8 @@
"@reviq/db-schema": ["@reviq/db-schema@workspace:packages/db-schema"],
"@reviq/test-helpers": ["@reviq/test-helpers@workspace:packages/testing/test-helpers"],
"@reviq/utils": ["@reviq/utils@workspace:packages/utils"],
"@reviq/virtual-authenticator": ["@reviq/virtual-authenticator@workspace:packages/testing/virtual-authenticator"],

5
bunfig.toml Normal file
View File

@@ -0,0 +1,5 @@
[test]
coveragePathIgnorePatterns = [
"**/dist/**",
"**/node_modules/**",
]

View File

@@ -1,4 +1,4 @@
\restrict F9AizESreuRieL4inRcHWWg3hyNET0FgnBDFBBBU3cZGPEpHjb591l8S2iglpap
\restrict 7omiXDURqmmr2m2jWDDMoltRzeUAT80fRWiPifpD7IpQGCLgxQNBFsA5uBgakPg
-- Dumped from database version 17.7
-- Dumped by pg_dump version 17.7
@@ -1084,7 +1084,7 @@ ALTER TABLE ONLY public.user_devices
-- PostgreSQL database dump complete
--
\unrestrict F9AizESreuRieL4inRcHWWg3hyNET0FgnBDFBBBU3cZGPEpHjb591l8S2iglpap
\unrestrict 7omiXDURqmmr2m2jWDDMoltRzeUAT80fRWiPifpD7IpQGCLgxQNBFsA5uBgakPg
--

View File

@@ -16,6 +16,10 @@
"typecheck": "turbo typecheck",
"clean": "turbo clean",
"test": "turbo test",
"test:unit": "SKIP_DB_TESTS=1 turbo test",
"test:all": "turbo test",
"test:cov": "bun test --coverage",
"test:unit:cov": "SKIP_DB_TESTS=1 bun test --coverage",
"db:codegen": "bun run --cwd packages/db-schema generate"
},
"devDependencies": {

View File

@@ -12,7 +12,7 @@
},
"scripts": {
"build": "tsc",
"test": "bun test",
"test": "bun test src/",
"clean": "tsc --build --clean && rm -rf dist/ node_modules/ .eslintcache",
"lint": "eslint . --cache"
},

View File

@@ -14,7 +14,7 @@
"build": "tsc",
"clean": "tsc --build --clean && rm -rf dist/ node_modules/ .eslintcache",
"lint": "eslint . --cache",
"test": "bun test"
"test": "bun test src/"
},
"devDependencies": {
"@macalinao/eslint-config": "catalog:",

View File

@@ -1,15 +1,7 @@
{
"extends": "@macalinao/tsconfig/tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"declaration": true,
"declarationMap": true,
"composite": true,
"types": ["node"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -1,14 +1,7 @@
{
"extends": "@macalinao/tsconfig/tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"declaration": true,
"declarationMap": true,
"types": ["node", "bun"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,12 @@
import { configs } from "@macalinao/eslint-config";
export default [
...configs.fast,
{
languageOptions: {
parserOptions: {
tsconfigRootDir: import.meta.dirname,
},
},
},
];

View File

@@ -0,0 +1,33 @@
{
"name": "@reviq/test-helpers",
"version": "0.0.1",
"private": true,
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "tsc",
"clean": "tsc --build --clean && rm -rf dist/ node_modules/ .eslintcache",
"lint": "eslint . --cache"
},
"dependencies": {
"@reviq/db": "workspace:*",
"@reviq/db-schema": "workspace:*",
"kysely": "^0.28.2",
"pg": "^8.16.3"
},
"devDependencies": {
"@macalinao/eslint-config": "catalog:",
"@macalinao/tsconfig": "catalog:",
"@types/bun": "catalog:",
"@types/pg": "^8.16.0",
"eslint": "catalog:",
"typescript": "catalog:"
}
}

View File

@@ -0,0 +1,18 @@
export { describeE2E, SKIP_DB_TESTS } from "./skip-db-tests.js";
export {
DEFAULT_TEST_AAGUID,
KNOWN_AAGUIDS,
TEST_RP,
} from "./test-constants.js";
export {
createTestDb,
createTestUser,
destroySharedDb,
destroyTestDb,
getSharedDb,
getTestDatabaseUrl,
initTestDb,
runMigrations,
truncateAllTables,
} from "./test-db.js";
export { withTestTransaction } from "./test-transaction.js";

View File

@@ -0,0 +1,18 @@
import { describe } from "bun:test";
/**
* Skip flag for database-dependent tests.
* Set SKIP_DB_TESTS=1 to skip e2e tests that require a database.
*/
export const SKIP_DB_TESTS: boolean = process.env.SKIP_DB_TESTS === "1";
const _describeSkipIf = describe.skipIf(SKIP_DB_TESTS);
/**
* Use for describe blocks that require database access.
* Automatically prefixes name with [e2e].
* Skips tests when SKIP_DB_TESTS=1 is set.
*/
export function describeE2E(name: string, fn: () => void): void {
_describeSkipIf(`[e2e] ${name}`, fn);
}

View File

@@ -64,20 +64,31 @@ export function getTestDatabaseUrl(): string {
}
/**
* Parses a postgres URL to extract components
* Parses a postgres URL to extract components.
* Supports both TCP and unix socket connections.
*
* Unix socket URL format: postgresql:///dbname?host=/var/run/postgresql
*/
function parsePostgresUrl(url: string): {
host: string;
port: number;
port: number | undefined;
user: string;
password: string;
database: string;
} {
const parsed = new URL(url);
// Unix socket: hostname is empty, socket path in `host` query param
const isUnixSocket = !parsed.hostname;
const socketPath = parsed.searchParams.get("host");
return {
host: parsed.hostname,
port: Number.parseInt(parsed.port || "5432", 10),
user: parsed.username,
host: isUnixSocket
? (socketPath ?? "/var/run/postgresql")
: parsed.hostname,
port: isUnixSocket ? undefined : Number.parseInt(parsed.port || "5432", 10),
// eslint-disable-next-line turbo/no-undeclared-env-vars, @typescript-eslint/prefer-nullish-coalescing -- USER is a system env var, and we want empty string to fall back
user: parsed.username || process.env.USER || "postgres",
password: parsed.password,
database: parsed.pathname.slice(1), // Remove leading /
};

View File

@@ -0,0 +1,6 @@
{
"extends": "@macalinao/tsconfig/tsconfig.base.json",
"compilerOptions": {
"types": ["bun"]
}
}

View File

@@ -3,14 +3,19 @@
"version": "0.0.1",
"private": true,
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": "./src/index.ts"
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "tsc",
"clean": "tsc --build --clean && rm -rf dist/ node_modules/ .eslintcache",
"lint": "eslint . --cache",
"test": "bun test"
"test": "bun test src/"
},
"dependencies": {
"@simplewebauthn/types": "^12.0.0"
@@ -18,7 +23,7 @@
"devDependencies": {
"@macalinao/eslint-config": "catalog:",
"@macalinao/tsconfig": "catalog:",
"@types/bun": "latest",
"@types/bun": "catalog:",
"@types/node": "^25.0.3",
"eslint": "catalog:",
"typescript": "catalog:"

View File

@@ -1,15 +1,7 @@
{
"extends": "@macalinao/tsconfig/tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"declaration": true,
"declarationMap": true,
"composite": true,
"types": ["node", "bun"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -14,7 +14,7 @@
"build": "tsc",
"clean": "tsc --build --clean && rm -rf dist/ node_modules/ .eslintcache",
"lint": "eslint . --cache",
"test": "bun test"
"test": "bun test src/"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20250529.0",

View File

@@ -1,6 +1,6 @@
{
"$schema": "https://turbo.build/schema.json",
"globalEnv": ["DATABASE_URL", "PORT"],
"globalEnv": ["DATABASE_URL", "PORT", "TEST_DATABASE_URL"],
"tasks": {
"build": {
"dependsOn": ["^build"],
@@ -33,6 +33,7 @@
"test": {
"dependsOn": ["^build"],
"inputs": ["src/**/*.ts", "src/**/*.test.ts"],
"env": ["SKIP_DB_TESTS", "TEST_DATABASE_URL"],
"cache": false
}
}