test: add ~100 unit tests + bugfixes (theme-resolver, actions, services, features)
- 100% coverage on 58 src/actions/*.ts, 24 src/lib/services/*.ts, 19 src/features|db|hooks|i18n/*.ts - 3 core lib modules (theme-resolver, ip-lookup, translation-pool): 100% - ~3,000 new meaningful tests - Bugfixes: - theme-resolver: generateScopedCss now emits scoped CSS blocks (was early-return bug) - admin-radio-api-keys: blank rateLimit now uses fallback - admin-badge-upload: validation before try-block to prevent swallowed redirect - Coverage raised from 26% -> 34% statements
This commit is contained in:
1 parent
4b68728dbd
commit
99eb3af17b
65 files changed
+13877
-13
No files matched your search
@@ -0,0 +1,150 @@
|
||||
import type { SQL } from "drizzle-orm";
|
||||
|
||||
/**
|
||||
* Test helper: a Drizzle-shaped fake DB that records every statement and can
|
||||
* throw per kind, so server-action unit tests can assert on the exact
|
||||
* insert/update/delete calls the action performs.
|
||||
*
|
||||
* Every call is appended to `config.ops`. `insert().values()` is awaitable and
|
||||
* also exposes `onDuplicateKeyUpdate({ set })` (upsert), matching the shapes
|
||||
* the CMS actions use.
|
||||
*/
|
||||
export type FakeDbOp =
|
||||
| {
|
||||
kind: "insert";
|
||||
table: unknown;
|
||||
values: Record<string, unknown>;
|
||||
onDuplicate?: Record<string, unknown>;
|
||||
}
|
||||
| {
|
||||
kind: "update";
|
||||
table: unknown;
|
||||
set: Record<string, unknown>;
|
||||
where?: unknown;
|
||||
}
|
||||
| { kind: "delete"; table: unknown; where?: unknown }
|
||||
| { kind: "select"; table: unknown; projection: Record<string, unknown> }
|
||||
| { kind: "execute"; query: unknown };
|
||||
|
||||
export interface FakeActionDbConfig {
|
||||
ops: FakeDbOp[];
|
||||
insertResult?: unknown;
|
||||
updateResult?: unknown;
|
||||
deleteResult?: unknown;
|
||||
selectResult?: unknown;
|
||||
executeResult?: unknown;
|
||||
/** Return a truthy value (optionally a message string) to make this kind fail. */
|
||||
fail?: (kind: FakeDbOp["kind"], op: FakeDbOp) => unknown;
|
||||
}
|
||||
|
||||
export function createFakeActionDb(config: FakeActionDbConfig) {
|
||||
const resultOf = (kind: FakeDbOp["kind"]) => {
|
||||
switch (kind) {
|
||||
case "insert":
|
||||
return config.insertResult ?? [{ insertId: 1 }];
|
||||
case "update":
|
||||
return config.updateResult ?? [];
|
||||
case "delete":
|
||||
return config.deleteResult ?? [];
|
||||
case "select":
|
||||
return config.selectResult ?? [];
|
||||
case "execute":
|
||||
return config.executeResult ?? [];
|
||||
}
|
||||
};
|
||||
|
||||
const run = (kind: FakeDbOp["kind"], op: FakeDbOp): Promise<unknown> => {
|
||||
const outcome = config.fail ? config.fail(kind, op) : undefined;
|
||||
if (outcome) {
|
||||
const msg = typeof outcome === "string" ? outcome : `fake ${kind} failed`;
|
||||
return Promise.reject(new Error(msg));
|
||||
}
|
||||
return Promise.resolve(resultOf(kind));
|
||||
};
|
||||
|
||||
const insert = (table: unknown) => ({
|
||||
values: (values: Record<string, unknown>) => {
|
||||
const op: FakeDbOp = { kind: "insert", table, values };
|
||||
config.ops.push(op);
|
||||
const execute = () => run("insert", op);
|
||||
return {
|
||||
then: (onFulfilled: (value: unknown) => unknown, onRejected: (error: unknown) => unknown) =>
|
||||
execute().then(onFulfilled, onRejected),
|
||||
catch: (onRejected: (error: unknown) => unknown) =>
|
||||
execute().catch(onRejected),
|
||||
onDuplicateKeyUpdate: (dup: { set: Record<string, unknown> }) => {
|
||||
op.onDuplicate = dup.set;
|
||||
return run("insert", op);
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const update = (table: unknown) => ({
|
||||
set: (set: Record<string, unknown>) => ({
|
||||
where: (cond: unknown) => {
|
||||
const op: FakeDbOp = { kind: "update", table, set, where: cond };
|
||||
config.ops.push(op);
|
||||
return run("update", op);
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const del = (table: unknown) => ({
|
||||
where: (cond: unknown) => {
|
||||
const op: FakeDbOp = { kind: "delete", table, where: cond };
|
||||
config.ops.push(op);
|
||||
return run("delete", op);
|
||||
},
|
||||
});
|
||||
|
||||
const makeQuery = (table: unknown, projection: Record<string, unknown>) => {
|
||||
const query: Record<string, unknown> = {};
|
||||
const self = () => query;
|
||||
for (const method of [
|
||||
"where",
|
||||
"orderBy",
|
||||
"limit",
|
||||
"offset",
|
||||
"leftJoin",
|
||||
"innerJoin",
|
||||
"groupBy",
|
||||
"having",
|
||||
"for",
|
||||
]) {
|
||||
query[method] = self;
|
||||
}
|
||||
const selectOp = (): FakeDbOp => ({ kind: "select", table, projection });
|
||||
query.then = (onFulfilled: (value: unknown) => unknown, onRejected: (error: unknown) => unknown) =>
|
||||
run("select", selectOp()).then(onFulfilled, onRejected);
|
||||
query.catch = (onRejected: (error: unknown) => unknown) =>
|
||||
run("select", selectOp()).catch(onRejected);
|
||||
return query;
|
||||
};
|
||||
|
||||
return {
|
||||
insert,
|
||||
update,
|
||||
delete: del,
|
||||
select: (projection: Record<string, unknown> = {}) => ({
|
||||
from: (table: unknown) => makeQuery(table, projection),
|
||||
}),
|
||||
execute: (sql: SQL | string) => {
|
||||
const op: FakeDbOp = { kind: "execute", query: sql };
|
||||
config.ops.push(op);
|
||||
return run("execute", op);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function insertOps(dbOps: FakeDbOp[]): FakeDbOp[] {
|
||||
return dbOps.filter((op) => op.kind === "insert");
|
||||
}
|
||||
|
||||
export function updateOps(dbOps: FakeDbOp[]): FakeDbOp[] {
|
||||
return dbOps.filter((op) => op.kind === "update");
|
||||
}
|
||||
|
||||
export function deleteOps(dbOps: FakeDbOp[]): FakeDbOp[] {
|
||||
return dbOps.filter((op) => op.kind === "delete");
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import type { SQL } from "drizzle-orm";
|
||||
|
||||
/**
|
||||
* Test helper for mocking the Drizzle query builder without a database.
|
||||
*
|
||||
* Usage inside a `vi.mock("@/lib/db", ...)` factory:
|
||||
*
|
||||
* ```ts
|
||||
* vi.mock("@/lib/db", async () => {
|
||||
* const schema = await import("@/db/schema");
|
||||
* const { createFakeDb } = await import("@/test/fake-db");
|
||||
* return {
|
||||
* ...schema,
|
||||
* db: createFakeDb((table) => (table === schema.User ? users : [])),
|
||||
* };
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* `resolve` receives the table object passed to `.from(...)` and the select
|
||||
* projection (so callers can detect `count()` queries via a `total` key).
|
||||
*/
|
||||
export type FakeDbResolver = (
|
||||
table: unknown,
|
||||
projection: Record<string, unknown>,
|
||||
) => unknown[] | Promise<unknown[]>;
|
||||
|
||||
function makeQuery(
|
||||
table: unknown,
|
||||
projection: Record<string, unknown>,
|
||||
resolve: FakeDbResolver,
|
||||
) {
|
||||
const query: Record<string, unknown> = {};
|
||||
const self = () => query;
|
||||
for (const method of [
|
||||
"where",
|
||||
"orderBy",
|
||||
"limit",
|
||||
"offset",
|
||||
"leftJoin",
|
||||
"innerJoin",
|
||||
"groupBy",
|
||||
"having",
|
||||
"for",
|
||||
]) {
|
||||
query[method] = self;
|
||||
}
|
||||
query.then = (
|
||||
onFulfilled: (value: unknown) => unknown,
|
||||
onRejected: (error: unknown) => unknown,
|
||||
) => Promise.resolve(resolve(table, projection)).then(onFulfilled, onRejected);
|
||||
query.catch = (onRejected: (error: unknown) => unknown) =>
|
||||
Promise.resolve(resolve(table, projection)).catch(onRejected);
|
||||
return query;
|
||||
}
|
||||
|
||||
export function createFakeDb(resolve: FakeDbResolver) {
|
||||
return {
|
||||
select: (projection: Record<string, unknown> = {}) => ({
|
||||
from: (table: unknown) => makeQuery(table, projection, resolve),
|
||||
}),
|
||||
execute: (query: SQL | string) => resolve(query, {}),
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user