Add @reviq/test-helpers package with e2e tests for admin, auth, orgs, and webauthn. Move test utilities to shared package. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
8
.ast-grep/rules/no-countall-number.yml
Normal file
8
.ast-grep/rules/no-countall-number.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
id: no-countall-number
|
||||
language: typescript
|
||||
severity: error
|
||||
message: "Don't use countAll<number>() - use countAll() instead. PostgreSQL COUNT returns bigint (string), so the type annotation is misleading."
|
||||
note: "Use Number() to convert the result if you need a number type."
|
||||
rule:
|
||||
pattern: countAll<number>()
|
||||
fix: countAll()
|
||||
@@ -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:"
|
||||
}
|
||||
|
||||
1947
apps/api-server/src/__tests__/e2e/admin.test.ts
Normal file
1947
apps/api-server/src/__tests__/e2e/admin.test.ts
Normal file
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
1844
apps/api-server/src/__tests__/e2e/orgs.test.ts
Normal file
1844
apps/api-server/src/__tests__/e2e/orgs.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -52,17 +52,28 @@ export async function signupWithPassword(
|
||||
// Hash password
|
||||
const passwordHash = await hashPassword(password);
|
||||
|
||||
// Create user
|
||||
const user = await db
|
||||
.insertInto("users")
|
||||
.values({
|
||||
email,
|
||||
password_hash: passwordHash,
|
||||
})
|
||||
.returning(["id"])
|
||||
.executeTakeFirstOrThrow();
|
||||
// Create user (handle race condition if concurrent signup with same email)
|
||||
try {
|
||||
const user = await db
|
||||
.insertInto("users")
|
||||
.values({
|
||||
email,
|
||||
password_hash: passwordHash,
|
||||
})
|
||||
.returning(["id"])
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
return user.id;
|
||||
return user.id;
|
||||
} catch (error) {
|
||||
// Handle duplicate email (unique constraint violation)
|
||||
// Use generic error to prevent email enumeration
|
||||
if (error instanceof Error && error.message.includes("users_email_key")) {
|
||||
throw new ORPCError("BAD_REQUEST", {
|
||||
message: "Unable to create account",
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,55 +157,66 @@ export async function signupWithPasskey(
|
||||
});
|
||||
}
|
||||
|
||||
// Create user and passkey in a transaction
|
||||
const result = await db.transaction().execute(async (trx) => {
|
||||
// Create user
|
||||
const user = await trx
|
||||
.insertInto("users")
|
||||
.values({
|
||||
email,
|
||||
password_hash: null,
|
||||
})
|
||||
.returning(["id"])
|
||||
.executeTakeFirstOrThrow();
|
||||
// Create user and passkey in a transaction (handle race condition if concurrent signup)
|
||||
try {
|
||||
const result = await db.transaction().execute(async (trx) => {
|
||||
// Create user
|
||||
const user = await trx
|
||||
.insertInto("users")
|
||||
.values({
|
||||
email,
|
||||
password_hash: null,
|
||||
})
|
||||
.returning(["id"])
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
const newUserId = user.id;
|
||||
const newUserId = user.id;
|
||||
|
||||
// Get friendly name from AAGUID
|
||||
const guidName = KNOWN_AAGUIDS[registrationInfo.aaguid];
|
||||
const passkeyName = guidName ?? "Default";
|
||||
// Get friendly name from AAGUID
|
||||
const guidName = KNOWN_AAGUIDS[registrationInfo.aaguid];
|
||||
const passkeyName = guidName ?? "Default";
|
||||
|
||||
// Store the passkey
|
||||
const { credential, credentialDeviceType, credentialBackedUp } =
|
||||
registrationInfo;
|
||||
// Store the passkey
|
||||
const { credential, credentialDeviceType, credentialBackedUp } =
|
||||
registrationInfo;
|
||||
|
||||
await trx
|
||||
.insertInto("passkeys")
|
||||
.values({
|
||||
user_id: newUserId,
|
||||
credential_id: Buffer.from(credential.id, "base64url"),
|
||||
public_key: Buffer.from(credential.publicKey),
|
||||
webauthn_user_id: options.user.id,
|
||||
counter: BigInt(credential.counter),
|
||||
device_type: credentialDeviceType as "singleDevice" | "multiDevice",
|
||||
backup_eligible: registrationInfo.credentialBackedUp,
|
||||
backup_status: credentialBackedUp,
|
||||
transports: JSON.stringify(response.response.transports ?? []),
|
||||
rpid: rpInfo.rpID,
|
||||
name: passkeyName,
|
||||
})
|
||||
.execute();
|
||||
await trx
|
||||
.insertInto("passkeys")
|
||||
.values({
|
||||
user_id: newUserId,
|
||||
credential_id: Buffer.from(credential.id, "base64url"),
|
||||
public_key: Buffer.from(credential.publicKey),
|
||||
webauthn_user_id: options.user.id,
|
||||
counter: BigInt(credential.counter),
|
||||
device_type: credentialDeviceType as "singleDevice" | "multiDevice",
|
||||
backup_eligible: registrationInfo.credentialBackedUp,
|
||||
backup_status: credentialBackedUp,
|
||||
transports: JSON.stringify(response.response.transports ?? []),
|
||||
rpid: rpInfo.rpID,
|
||||
name: passkeyName,
|
||||
})
|
||||
.execute();
|
||||
|
||||
// Delete the challenge
|
||||
await trx
|
||||
.deleteFrom("webauthn_challenges")
|
||||
.where("id", "=", String(challengeId))
|
||||
.execute();
|
||||
// Delete the challenge
|
||||
await trx
|
||||
.deleteFrom("webauthn_challenges")
|
||||
.where("id", "=", String(challengeId))
|
||||
.execute();
|
||||
|
||||
return { userId: newUserId };
|
||||
});
|
||||
return { userId: newUserId };
|
||||
});
|
||||
|
||||
return result.userId;
|
||||
return result.userId;
|
||||
} catch (error) {
|
||||
// Handle duplicate email (unique constraint violation)
|
||||
// Use generic error to prevent email enumeration
|
||||
if (error instanceof Error && error.message.includes("users_email_key")) {
|
||||
throw new ORPCError("BAD_REQUEST", {
|
||||
message: "Unable to create account",
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,7 +263,7 @@ export const signup = os.auth.signup.handler(async ({ input, context }) => {
|
||||
);
|
||||
userId = await signupWithPasskey(context.db, email, passkeyInfo, rpInfo);
|
||||
} else {
|
||||
// Should never reach here due to schema validation
|
||||
// Unreachable - schema validation requires password or passkeyInfo
|
||||
throw new ORPCError("BAD_REQUEST", {
|
||||
message: "Either password or passkeyInfo is required",
|
||||
});
|
||||
|
||||
@@ -115,10 +115,11 @@ export async function countOwners(
|
||||
): Promise<number> {
|
||||
const result = await db
|
||||
.selectFrom("org_members")
|
||||
.select((eb) => eb.fn.countAll<number>().as("count"))
|
||||
.select((eb) => eb.fn.countAll().as("count"))
|
||||
.where("org_id", "=", orgId)
|
||||
.where("role", "=", "owner")
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
return result.count;
|
||||
// PostgreSQL COUNT returns bigint (string), convert to number
|
||||
return Number(result.count);
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
25
bun.lock
25
bun.lock
@@ -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:",
|
||||
},
|
||||
@@ -192,6 +191,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",
|
||||
@@ -201,7 +218,7 @@
|
||||
"devDependencies": {
|
||||
"@macalinao/eslint-config": "catalog:",
|
||||
"@macalinao/tsconfig": "catalog:",
|
||||
"@types/bun": "latest",
|
||||
"@types/bun": "catalog:",
|
||||
"@types/node": "^25.0.3",
|
||||
"eslint": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
@@ -439,6 +456,8 @@
|
||||
|
||||
"@reviq/frontend-utils": ["@reviq/frontend-utils@workspace:packages/frontend-utils"],
|
||||
|
||||
"@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
5
bunfig.toml
Normal file
@@ -0,0 +1,5 @@
|
||||
[test]
|
||||
coveragePathIgnorePatterns = [
|
||||
"**/dist/**",
|
||||
"**/node_modules/**",
|
||||
]
|
||||
@@ -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": {
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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:",
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
12
packages/testing/test-helpers/eslint.config.js
Normal file
12
packages/testing/test-helpers/eslint.config.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { configs } from "@macalinao/eslint-config";
|
||||
|
||||
export default [
|
||||
...configs.fast,
|
||||
{
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
33
packages/testing/test-helpers/package.json
Normal file
33
packages/testing/test-helpers/package.json
Normal 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:"
|
||||
}
|
||||
}
|
||||
18
packages/testing/test-helpers/src/index.ts
Normal file
18
packages/testing/test-helpers/src/index.ts
Normal 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";
|
||||
18
packages/testing/test-helpers/src/skip-db-tests.ts
Normal file
18
packages/testing/test-helpers/src/skip-db-tests.ts
Normal 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);
|
||||
}
|
||||
@@ -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 /
|
||||
};
|
||||
6
packages/testing/test-helpers/tsconfig.json
Normal file
6
packages/testing/test-helpers/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "@macalinao/tsconfig/tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"types": ["bun"]
|
||||
}
|
||||
}
|
||||
@@ -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:"
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
ruleDirs:
|
||||
- /Users/igm/proj/reviq/publisher-dashboard/.ast-grep/rules/
|
||||
- .ast-grep/rules/
|
||||
testConfigs:
|
||||
- testDir: /Users/igm/proj/reviq/publisher-dashboard/.ast-grep/rule-tests/
|
||||
- testDir: .ast-grep/rule-tests/
|
||||
utilDirs:
|
||||
- /Users/igm/proj/reviq/publisher-dashboard/.ast-grep/utils/
|
||||
- .ast-grep/utils/
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user