@checkstack/test-utils-backend 0.1.45 → 0.1.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/package.json +9 -6
- package/src/index.ts +6 -0
- package/src/with-test-db.ts +130 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @checkstack/test-utils-backend
|
|
2
2
|
|
|
3
|
+
## 0.1.47
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [2e20792]
|
|
8
|
+
- Updated dependencies [2e20792]
|
|
9
|
+
- @checkstack/backend-api@0.26.0
|
|
10
|
+
- @checkstack/signal-common@0.2.12
|
|
11
|
+
- @checkstack/common@0.17.0
|
|
12
|
+
- @checkstack/queue-api@0.3.14
|
|
13
|
+
|
|
14
|
+
## 0.1.46
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- Updated dependencies [8cad340]
|
|
19
|
+
- Updated dependencies [8cad340]
|
|
20
|
+
- Updated dependencies [8cad340]
|
|
21
|
+
- Updated dependencies [8cad340]
|
|
22
|
+
- Updated dependencies [8cad340]
|
|
23
|
+
- Updated dependencies [8cad340]
|
|
24
|
+
- Updated dependencies [8cad340]
|
|
25
|
+
- @checkstack/backend-api@0.25.0
|
|
26
|
+
- @checkstack/common@0.17.0
|
|
27
|
+
- @checkstack/queue-api@0.3.14
|
|
28
|
+
- @checkstack/signal-common@0.2.11
|
|
29
|
+
|
|
3
30
|
## 0.1.45
|
|
4
31
|
|
|
5
32
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/test-utils-backend",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.47",
|
|
4
4
|
"license": "Elastic-2.0",
|
|
5
5
|
"checkstack": {
|
|
6
6
|
"type": "tooling"
|
|
@@ -13,15 +13,18 @@
|
|
|
13
13
|
"lint:code": "eslint . --max-warnings 0"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@checkstack/backend-api": "0.
|
|
17
|
-
"@checkstack/common": "0.
|
|
18
|
-
"@checkstack/queue-api": "0.3.
|
|
19
|
-
"@checkstack/signal-common": "0.2.
|
|
16
|
+
"@checkstack/backend-api": "0.26.0",
|
|
17
|
+
"@checkstack/common": "0.17.0",
|
|
18
|
+
"@checkstack/queue-api": "0.3.14",
|
|
19
|
+
"@checkstack/signal-common": "0.2.12",
|
|
20
|
+
"drizzle-orm": "^0.45.0",
|
|
21
|
+
"pg": "^8.21.0"
|
|
20
22
|
},
|
|
21
23
|
"devDependencies": {
|
|
24
|
+
"@checkstack/scripts": "0.6.4",
|
|
22
25
|
"@checkstack/tsconfig": "0.0.7",
|
|
23
|
-
"@checkstack/scripts": "0.6.2",
|
|
24
26
|
"@types/bun": "latest",
|
|
27
|
+
"@types/pg": "^8.20.0",
|
|
25
28
|
"zod": "^4.0.0"
|
|
26
29
|
}
|
|
27
30
|
}
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { drizzle, type NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
2
|
+
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
|
3
|
+
import { Pool } from "pg";
|
|
4
|
+
import type { SafeDatabase } from "@checkstack/backend-api";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A real-Postgres test database, isolated in a throwaway schema, for
|
|
8
|
+
* `*.it.test.ts` query-correctness tests. Use this when the SQL itself is the
|
|
9
|
+
* risk (team-scoping/visibility filters, joins, aggregations) — the
|
|
10
|
+
* `createMockDb()` stub returns canned empty results and cannot verify query
|
|
11
|
+
* shape, so it proves branch logic only, not the SQL.
|
|
12
|
+
*
|
|
13
|
+
* Mirrors `core/e2e/scripts/start-e2e-server.ts` and the existing IT tests'
|
|
14
|
+
* per-run schema isolation: every call creates a uniquely named schema, runs
|
|
15
|
+
* the package's Drizzle migrations into it with a strict `search_path`, hands
|
|
16
|
+
* back a `SafeDatabase` bound to that schema, and tears it all down on
|
|
17
|
+
* `dispose()`.
|
|
18
|
+
*/
|
|
19
|
+
export interface TestDb<S extends Record<string, unknown>> {
|
|
20
|
+
/** A `SafeDatabase` bound to the isolated schema (relational API omitted). */
|
|
21
|
+
readonly db: SafeDatabase<S>;
|
|
22
|
+
/** The unique, throwaway schema name this database lives in. */
|
|
23
|
+
readonly schema: string;
|
|
24
|
+
/** Drop the schema and close the pool. Always call this in `afterAll`/`afterEach`. */
|
|
25
|
+
dispose(): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface WithTestDbOptions<S extends Record<string, unknown>> {
|
|
29
|
+
/**
|
|
30
|
+
* The package's Drizzle schema object (the `* as schema` import). Passed to
|
|
31
|
+
* `drizzle({ schema })` so the returned `db` is fully typed.
|
|
32
|
+
*/
|
|
33
|
+
schema: S;
|
|
34
|
+
/**
|
|
35
|
+
* Absolute path to the package's `drizzle/` migrations folder (the one with
|
|
36
|
+
* the `.sql` files + `meta/`). Run the package generator to keep it in sync;
|
|
37
|
+
* never hand-edit applied migrations (see `.claude/rules/migrations.md`).
|
|
38
|
+
*/
|
|
39
|
+
migrationsFolder: string;
|
|
40
|
+
/**
|
|
41
|
+
* Postgres connection string. Defaults to `CHECKSTACK_IT_PG_URL`, matching the
|
|
42
|
+
* existing IT lane + the CI `integration` job.
|
|
43
|
+
*/
|
|
44
|
+
connectionString?: string;
|
|
45
|
+
/** Override the generated schema name (mostly for deterministic debugging). */
|
|
46
|
+
schemaName?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const DEFAULT_PG_URL = "postgres://checkstack:checkstack@localhost:5432/checkstack";
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* True only when the integration lane is enabled. Suites that use
|
|
53
|
+
* {@link withTestDb} MUST wrap themselves in
|
|
54
|
+
* `describe.skipIf(!isIntegrationEnabled())(...)` (or the `CHECKSTACK_IT`
|
|
55
|
+
* env check) so the default `bun test` never tries to reach a real database.
|
|
56
|
+
*/
|
|
57
|
+
export function isIntegrationEnabled(): boolean {
|
|
58
|
+
return Boolean(process.env.CHECKSTACK_IT);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Provision an isolated, migration-fresh test database. Throws if the
|
|
63
|
+
* integration lane is not enabled, so a misconfigured suite fails loudly
|
|
64
|
+
* instead of silently hitting a developer's working database.
|
|
65
|
+
*/
|
|
66
|
+
export async function withTestDb<S extends Record<string, unknown>>(
|
|
67
|
+
options: WithTestDbOptions<S>,
|
|
68
|
+
): Promise<TestDb<S>> {
|
|
69
|
+
if (!isIntegrationEnabled()) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
"withTestDb() requires CHECKSTACK_IT=1 and a real Postgres. Wrap the " +
|
|
72
|
+
"suite in describe.skipIf(!isIntegrationEnabled()) so the default " +
|
|
73
|
+
"`bun test` lane never reaches a database.",
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const connectionString =
|
|
78
|
+
options.connectionString ??
|
|
79
|
+
process.env.CHECKSTACK_IT_PG_URL ??
|
|
80
|
+
DEFAULT_PG_URL;
|
|
81
|
+
|
|
82
|
+
const schema =
|
|
83
|
+
options.schemaName ??
|
|
84
|
+
`it_${crypto.randomUUID().replaceAll("-", "")}`;
|
|
85
|
+
|
|
86
|
+
// A dedicated pool whose every connection resolves the throwaway schema
|
|
87
|
+
// first. Binding via the pool `options` keeps the search_path on every
|
|
88
|
+
// physical connection the pool hands out (a session-level SET on a Pool is
|
|
89
|
+
// not guaranteed to land on the same connection a later query checks out).
|
|
90
|
+
const pool = new Pool({
|
|
91
|
+
connectionString,
|
|
92
|
+
options: `-c search_path=${schema}`,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// The migrator must run with the schema present and the search_path pinned to
|
|
96
|
+
// a single connection (Drizzle wraps all pending migrations in one
|
|
97
|
+
// transaction), mirroring runPluginMigrations().
|
|
98
|
+
const migrationClient = await pool.connect();
|
|
99
|
+
try {
|
|
100
|
+
await migrationClient.query(`CREATE SCHEMA IF NOT EXISTS "${schema}"`);
|
|
101
|
+
await migrationClient.query(`SET search_path = "${schema}"`);
|
|
102
|
+
await migrate(drizzle(migrationClient), {
|
|
103
|
+
migrationsFolder: options.migrationsFolder,
|
|
104
|
+
migrationsSchema: schema,
|
|
105
|
+
});
|
|
106
|
+
} finally {
|
|
107
|
+
migrationClient.release();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// `drizzle()` returns a NodePgDatabase<S>; SafeDatabase<S> is that minus the
|
|
111
|
+
// relational `query` API. The cast strips it at the type level (the same
|
|
112
|
+
// pattern the existing IT tests use); the scoped-db runtime proxy blocks the
|
|
113
|
+
// relational API in production, and tests simply never call it.
|
|
114
|
+
const fullDb: NodePgDatabase<S> = drizzle({ client: pool, schema: options.schema });
|
|
115
|
+
const db = fullDb as unknown as SafeDatabase<S>;
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
db,
|
|
119
|
+
schema,
|
|
120
|
+
dispose: async () => {
|
|
121
|
+
const cleanup = await pool.connect();
|
|
122
|
+
try {
|
|
123
|
+
await cleanup.query(`DROP SCHEMA IF EXISTS "${schema}" CASCADE`);
|
|
124
|
+
} finally {
|
|
125
|
+
cleanup.release();
|
|
126
|
+
}
|
|
127
|
+
await pool.end();
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|