@jterrazz/test 5.3.2 → 6.0.0
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/README.md +66 -56
- package/dist/index.cjs +561 -452
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +341 -246
- package/dist/index.d.ts +341 -246
- package/dist/index.js +555 -427
- package/dist/index.js.map +1 -1
- package/dist/mock-of.cjs +59 -0
- package/dist/mock-of.cjs.map +1 -0
- package/dist/mock-of.d.cts +27 -0
- package/dist/mock-of.d.ts +27 -0
- package/dist/mock-of.js +19 -0
- package/dist/mock-of.js.map +1 -0
- package/dist/mock.cjs +4 -0
- package/dist/mock.d.cts +2 -0
- package/dist/mock.d.ts +2 -0
- package/dist/mock.js +2 -0
- package/dist/redis.adapter.cjs +214 -0
- package/dist/redis.adapter.cjs.map +1 -0
- package/dist/redis.adapter.d.cts +155 -0
- package/dist/redis.adapter.d.ts +155 -0
- package/dist/redis.adapter.js +202 -0
- package/dist/redis.adapter.js.map +1 -0
- package/dist/services.cjs +4 -0
- package/dist/services.d.cts +2 -0
- package/dist/services.d.ts +2 -0
- package/dist/services.js +2 -0
- package/package.json +14 -8
package/dist/mock-of.cjs
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
//#endregion
|
|
23
|
+
let mockdate = require("mockdate");
|
|
24
|
+
mockdate = __toESM(mockdate, 1);
|
|
25
|
+
let vitest_mock_extended = require("vitest-mock-extended");
|
|
26
|
+
//#region src/mocking/mock-of-date.ts
|
|
27
|
+
/**
|
|
28
|
+
* Freeze or reset the global Date for deterministic time-dependent tests.
|
|
29
|
+
* Wraps the `mockdate` package.
|
|
30
|
+
*/
|
|
31
|
+
const mockOfDate = mockdate.default;
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/mocking/mock-of.ts
|
|
34
|
+
/**
|
|
35
|
+
* Create a deep mock proxy for a given type.
|
|
36
|
+
* Wraps `vitest-mock-extended`'s `mockDeep` for convenient port mocking.
|
|
37
|
+
*/
|
|
38
|
+
const mockOf = vitest_mock_extended.mockDeep;
|
|
39
|
+
//#endregion
|
|
40
|
+
Object.defineProperty(exports, "__toESM", {
|
|
41
|
+
enumerable: true,
|
|
42
|
+
get: function() {
|
|
43
|
+
return __toESM;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
Object.defineProperty(exports, "mockOf", {
|
|
47
|
+
enumerable: true,
|
|
48
|
+
get: function() {
|
|
49
|
+
return mockOf;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
Object.defineProperty(exports, "mockOfDate", {
|
|
53
|
+
enumerable: true,
|
|
54
|
+
get: function() {
|
|
55
|
+
return mockOfDate;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=mock-of.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-of.cjs","names":["MockDatePackage","mockDeep"],"sources":["../src/mocking/mock-of-date.ts","../src/mocking/mock-of.ts"],"sourcesContent":["import MockDatePackage from 'mockdate';\n\n/** Interface for freezing and resetting the global Date in tests. */\nexport interface MockDatePort {\n /** Restore the real Date object. */\n reset: () => void;\n /** Freeze `Date.now()` and `new Date()` to the given value. */\n set: (date: Date | number | string) => void;\n}\n\n/**\n * Freeze or reset the global Date for deterministic time-dependent tests.\n * Wraps the `mockdate` package.\n */\nexport const mockOfDate: MockDatePort = MockDatePackage;\n","import { type DeepMockProxy, mockDeep } from 'vitest-mock-extended';\n\n/** Factory signature that creates a deep mock proxy for any interface. */\nexport type MockPort = <T>() => DeepMockProxy<T>;\n\n/**\n * Create a deep mock proxy for a given type.\n * Wraps `vitest-mock-extended`'s `mockDeep` for convenient port mocking.\n */\nexport const mockOf: MockPort = mockDeep;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAcA,MAAa,aAA2BA,SAAAA;;;;;;;ACLxC,MAAa,SAAmBC,qBAAAA"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { DeepMockProxy } from "vitest-mock-extended";
|
|
2
|
+
|
|
3
|
+
//#region src/mocking/mock-of-date.d.ts
|
|
4
|
+
/** Interface for freezing and resetting the global Date in tests. */
|
|
5
|
+
interface MockDatePort {
|
|
6
|
+
/** Restore the real Date object. */
|
|
7
|
+
reset: () => void;
|
|
8
|
+
/** Freeze `Date.now()` and `new Date()` to the given value. */
|
|
9
|
+
set: (date: Date | number | string) => void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Freeze or reset the global Date for deterministic time-dependent tests.
|
|
13
|
+
* Wraps the `mockdate` package.
|
|
14
|
+
*/
|
|
15
|
+
declare const mockOfDate: MockDatePort;
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/mocking/mock-of.d.ts
|
|
18
|
+
/** Factory signature that creates a deep mock proxy for any interface. */
|
|
19
|
+
type MockPort = <T>() => DeepMockProxy<T>;
|
|
20
|
+
/**
|
|
21
|
+
* Create a deep mock proxy for a given type.
|
|
22
|
+
* Wraps `vitest-mock-extended`'s `mockDeep` for convenient port mocking.
|
|
23
|
+
*/
|
|
24
|
+
declare const mockOf: MockPort;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { mockOfDate as i, mockOf as n, MockDatePort as r, MockPort as t };
|
|
27
|
+
//# sourceMappingURL=mock-of.d.cts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { DeepMockProxy } from "vitest-mock-extended";
|
|
2
|
+
|
|
3
|
+
//#region src/mocking/mock-of-date.d.ts
|
|
4
|
+
/** Interface for freezing and resetting the global Date in tests. */
|
|
5
|
+
interface MockDatePort {
|
|
6
|
+
/** Restore the real Date object. */
|
|
7
|
+
reset: () => void;
|
|
8
|
+
/** Freeze `Date.now()` and `new Date()` to the given value. */
|
|
9
|
+
set: (date: Date | number | string) => void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Freeze or reset the global Date for deterministic time-dependent tests.
|
|
13
|
+
* Wraps the `mockdate` package.
|
|
14
|
+
*/
|
|
15
|
+
declare const mockOfDate: MockDatePort;
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/mocking/mock-of.d.ts
|
|
18
|
+
/** Factory signature that creates a deep mock proxy for any interface. */
|
|
19
|
+
type MockPort = <T>() => DeepMockProxy<T>;
|
|
20
|
+
/**
|
|
21
|
+
* Create a deep mock proxy for a given type.
|
|
22
|
+
* Wraps `vitest-mock-extended`'s `mockDeep` for convenient port mocking.
|
|
23
|
+
*/
|
|
24
|
+
declare const mockOf: MockPort;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { mockOfDate as i, mockOf as n, MockDatePort as r, MockPort as t };
|
|
27
|
+
//# sourceMappingURL=mock-of.d.ts.map
|
package/dist/mock-of.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import MockDatePackage from "mockdate";
|
|
2
|
+
import { mockDeep } from "vitest-mock-extended";
|
|
3
|
+
//#region src/mocking/mock-of-date.ts
|
|
4
|
+
/**
|
|
5
|
+
* Freeze or reset the global Date for deterministic time-dependent tests.
|
|
6
|
+
* Wraps the `mockdate` package.
|
|
7
|
+
*/
|
|
8
|
+
const mockOfDate = MockDatePackage;
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/mocking/mock-of.ts
|
|
11
|
+
/**
|
|
12
|
+
* Create a deep mock proxy for a given type.
|
|
13
|
+
* Wraps `vitest-mock-extended`'s `mockDeep` for convenient port mocking.
|
|
14
|
+
*/
|
|
15
|
+
const mockOf = mockDeep;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { mockOfDate as n, mockOf as t };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=mock-of.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-of.js","names":[],"sources":["../src/mocking/mock-of-date.ts","../src/mocking/mock-of.ts"],"sourcesContent":["import MockDatePackage from 'mockdate';\n\n/** Interface for freezing and resetting the global Date in tests. */\nexport interface MockDatePort {\n /** Restore the real Date object. */\n reset: () => void;\n /** Freeze `Date.now()` and `new Date()` to the given value. */\n set: (date: Date | number | string) => void;\n}\n\n/**\n * Freeze or reset the global Date for deterministic time-dependent tests.\n * Wraps the `mockdate` package.\n */\nexport const mockOfDate: MockDatePort = MockDatePackage;\n","import { type DeepMockProxy, mockDeep } from 'vitest-mock-extended';\n\n/** Factory signature that creates a deep mock proxy for any interface. */\nexport type MockPort = <T>() => DeepMockProxy<T>;\n\n/**\n * Create a deep mock proxy for a given type.\n * Wraps `vitest-mock-extended`'s `mockDeep` for convenient port mocking.\n */\nexport const mockOf: MockPort = mockDeep;\n"],"mappings":";;;;;;;AAcA,MAAa,aAA2B;;;;;;;ACLxC,MAAa,SAAmB"}
|
package/dist/mock.cjs
ADDED
package/dist/mock.d.cts
ADDED
package/dist/mock.d.ts
ADDED
package/dist/mock.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
require("./mock-of.cjs");
|
|
2
|
+
let node_fs = require("node:fs");
|
|
3
|
+
let node_path = require("node:path");
|
|
4
|
+
let pg = require("pg");
|
|
5
|
+
//#region src/adapters/postgres.adapter.ts
|
|
6
|
+
var PostgresHandle = class {
|
|
7
|
+
type = "postgres";
|
|
8
|
+
composeName;
|
|
9
|
+
defaultPort = 5432;
|
|
10
|
+
defaultImage;
|
|
11
|
+
environment;
|
|
12
|
+
connectionString = "";
|
|
13
|
+
started = false;
|
|
14
|
+
client = null;
|
|
15
|
+
originalConnectionString = "";
|
|
16
|
+
schema = "public";
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
this.composeName = options.compose ?? null;
|
|
19
|
+
this.defaultImage = options.image ?? "postgres:17";
|
|
20
|
+
this.environment = {
|
|
21
|
+
POSTGRES_DB: "test",
|
|
22
|
+
POSTGRES_PASSWORD: "test",
|
|
23
|
+
POSTGRES_USER: "test",
|
|
24
|
+
...options.env
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
buildConnectionString(host, port) {
|
|
28
|
+
return `postgresql://${this.environment.POSTGRES_USER ?? "test"}:${this.environment.POSTGRES_PASSWORD ?? "test"}@${host}:${port}/${this.environment.POSTGRES_DB ?? "test"}`;
|
|
29
|
+
}
|
|
30
|
+
createDatabaseAdapter() {
|
|
31
|
+
return this;
|
|
32
|
+
}
|
|
33
|
+
async healthcheck() {
|
|
34
|
+
if (!this.connectionString) throw new Error("postgres: cannot healthcheck — no connection string");
|
|
35
|
+
try {
|
|
36
|
+
const client = new pg.Client({ connectionString: this.connectionString });
|
|
37
|
+
await client.connect();
|
|
38
|
+
await client.query("SELECT 1");
|
|
39
|
+
await client.end();
|
|
40
|
+
} catch (error) {
|
|
41
|
+
throw new Error(`postgres healthcheck failed: ${error.message || error.code || String(error)}`, { cause: error });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async initialize(composeDir) {
|
|
45
|
+
if (!this.composeName) return;
|
|
46
|
+
const initPaths = [(0, node_path.resolve)(composeDir, `${this.composeName}/init.sql`), (0, node_path.resolve)(composeDir, "postgres/init.sql")];
|
|
47
|
+
for (const initPath of initPaths) if ((0, node_fs.existsSync)(initPath)) {
|
|
48
|
+
const sql = (0, node_fs.readFileSync)(initPath, "utf8");
|
|
49
|
+
try {
|
|
50
|
+
await this.seed(sql);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
throw new Error(`postgres init script failed (${initPath}):\n${error.message}`, { cause: error });
|
|
53
|
+
}
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async getClient() {
|
|
58
|
+
if (this.client) return this.client;
|
|
59
|
+
const client = new pg.Client({ connectionString: this.connectionString });
|
|
60
|
+
client.on("error", () => {
|
|
61
|
+
this.client = null;
|
|
62
|
+
});
|
|
63
|
+
await client.connect();
|
|
64
|
+
this.client = client;
|
|
65
|
+
return client;
|
|
66
|
+
}
|
|
67
|
+
async seed(sql) {
|
|
68
|
+
await (await this.getClient()).query(sql);
|
|
69
|
+
}
|
|
70
|
+
async reset() {
|
|
71
|
+
const client = await this.getClient();
|
|
72
|
+
const result = await client.query(`
|
|
73
|
+
SELECT tablename FROM pg_tables
|
|
74
|
+
WHERE schemaname = '${this.schema}'
|
|
75
|
+
AND tablename NOT LIKE '_prisma%'
|
|
76
|
+
`);
|
|
77
|
+
for (const row of result.rows) await client.query(`TRUNCATE "${this.schema}"."${row.tablename}" CASCADE`);
|
|
78
|
+
}
|
|
79
|
+
async query(table, columns) {
|
|
80
|
+
const client = await this.getClient();
|
|
81
|
+
const columnList = columns.join(", ");
|
|
82
|
+
return (await client.query(`SELECT ${columnList} FROM "${this.schema}"."${table}" ORDER BY 1`)).rows.map((row) => columns.map((col) => row[col]));
|
|
83
|
+
}
|
|
84
|
+
isolation() {
|
|
85
|
+
return {
|
|
86
|
+
acquire: async (workerId) => {
|
|
87
|
+
const workerSchema = `worker_${workerId}`;
|
|
88
|
+
this.originalConnectionString = this.connectionString;
|
|
89
|
+
const client = await this.getClient();
|
|
90
|
+
await client.query(`DROP SCHEMA IF EXISTS "${workerSchema}" CASCADE`);
|
|
91
|
+
await client.query(`CREATE SCHEMA "${workerSchema}"`);
|
|
92
|
+
const tables = await client.query(`
|
|
93
|
+
SELECT tablename FROM pg_tables
|
|
94
|
+
WHERE schemaname = 'public'
|
|
95
|
+
AND tablename NOT LIKE '_prisma%'
|
|
96
|
+
`);
|
|
97
|
+
for (const row of tables.rows) await client.query(`CREATE TABLE "${workerSchema}"."${row.tablename}" (LIKE "public"."${row.tablename}" INCLUDING ALL)`);
|
|
98
|
+
this.schema = workerSchema;
|
|
99
|
+
await client.query(`SET search_path TO "${workerSchema}", public`);
|
|
100
|
+
const url = new URL(this.connectionString);
|
|
101
|
+
url.searchParams.set("options", `-c search_path=${workerSchema},public`);
|
|
102
|
+
this.connectionString = url.toString();
|
|
103
|
+
},
|
|
104
|
+
reset: async () => {
|
|
105
|
+
await this.reset();
|
|
106
|
+
},
|
|
107
|
+
release: async () => {
|
|
108
|
+
const client = await this.getClient();
|
|
109
|
+
const workerSchema = this.schema;
|
|
110
|
+
this.schema = "public";
|
|
111
|
+
this.connectionString = this.originalConnectionString;
|
|
112
|
+
await client.query(`SET search_path TO public`);
|
|
113
|
+
await client.query(`DROP SCHEMA IF EXISTS "${workerSchema}" CASCADE`);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Create a PostgreSQL service handle.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* const db = postgres({ compose: "db" });
|
|
123
|
+
* // After start: db.connectionString is populated
|
|
124
|
+
*/
|
|
125
|
+
function postgres(options = {}) {
|
|
126
|
+
return new PostgresHandle(options);
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/adapters/redis.adapter.ts
|
|
130
|
+
var RedisHandle = class {
|
|
131
|
+
type = "redis";
|
|
132
|
+
composeName;
|
|
133
|
+
defaultPort = 6379;
|
|
134
|
+
defaultImage;
|
|
135
|
+
environment = {};
|
|
136
|
+
connectionString = "";
|
|
137
|
+
started = false;
|
|
138
|
+
dbIndex = 0;
|
|
139
|
+
constructor(options = {}) {
|
|
140
|
+
this.composeName = options.compose ?? null;
|
|
141
|
+
this.defaultImage = options.image ?? "redis:7";
|
|
142
|
+
}
|
|
143
|
+
buildConnectionString(host, port) {
|
|
144
|
+
return `redis://${host}:${port}`;
|
|
145
|
+
}
|
|
146
|
+
createDatabaseAdapter() {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
async healthcheck() {
|
|
150
|
+
if (!this.connectionString) throw new Error("redis: cannot healthcheck — no connection string");
|
|
151
|
+
try {
|
|
152
|
+
const { createClient } = await import("redis");
|
|
153
|
+
const client = createClient({ url: this.connectionString });
|
|
154
|
+
await client.connect();
|
|
155
|
+
await client.ping();
|
|
156
|
+
await client.disconnect();
|
|
157
|
+
} catch (error) {
|
|
158
|
+
throw new Error(`redis healthcheck failed: ${error.message || error.code || String(error)}`, { cause: error });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async initialize() {}
|
|
162
|
+
async reset() {
|
|
163
|
+
const { createClient } = await import("redis");
|
|
164
|
+
const client = createClient({
|
|
165
|
+
url: this.connectionString,
|
|
166
|
+
database: this.dbIndex
|
|
167
|
+
});
|
|
168
|
+
await client.connect();
|
|
169
|
+
try {
|
|
170
|
+
await client.flushDb();
|
|
171
|
+
} finally {
|
|
172
|
+
await client.disconnect();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
isolation() {
|
|
176
|
+
return {
|
|
177
|
+
acquire: async (workerId) => {
|
|
178
|
+
this.dbIndex = (Number.parseInt(workerId, 10) || 0) % 15 + 1;
|
|
179
|
+
},
|
|
180
|
+
reset: async () => {
|
|
181
|
+
await this.reset();
|
|
182
|
+
},
|
|
183
|
+
release: async () => {
|
|
184
|
+
await this.reset();
|
|
185
|
+
this.dbIndex = 0;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
/**
|
|
191
|
+
* Create a Redis service handle.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* const cache = redis({ compose: "cache" });
|
|
195
|
+
* // After start: cache.connectionString is populated
|
|
196
|
+
*/
|
|
197
|
+
function redis(options = {}) {
|
|
198
|
+
return new RedisHandle(options);
|
|
199
|
+
}
|
|
200
|
+
//#endregion
|
|
201
|
+
Object.defineProperty(exports, "postgres", {
|
|
202
|
+
enumerable: true,
|
|
203
|
+
get: function() {
|
|
204
|
+
return postgres;
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
Object.defineProperty(exports, "redis", {
|
|
208
|
+
enumerable: true,
|
|
209
|
+
get: function() {
|
|
210
|
+
return redis;
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
//# sourceMappingURL=redis.adapter.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis.adapter.cjs","names":["Client"],"sources":["../src/adapters/postgres.adapter.ts","../src/adapters/redis.adapter.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { Client } from 'pg';\n\nimport type { DatabasePort } from '../ports/database.port.js';\nimport type { IsolationStrategy } from '../ports/isolation.port.js';\nimport type { ServiceHandle } from '../ports/service.port.js';\n\nexport interface PostgresOptions {\n /** Map to a service in docker-compose.test.yaml. */\n compose?: string;\n /** Override image. */\n image?: string;\n /** Override environment variables. */\n env?: Record<string, string>;\n}\n\nexport class PostgresHandle implements DatabasePort, ServiceHandle {\n readonly type = 'postgres';\n readonly composeName: null | string;\n readonly defaultPort = 5432;\n readonly defaultImage: string;\n readonly environment: Record<string, string>;\n\n connectionString = '';\n started = false;\n\n private client: Client | null = null;\n private originalConnectionString = '';\n private schema = 'public';\n\n constructor(options: PostgresOptions = {}) {\n this.composeName = options.compose ?? null;\n this.defaultImage = options.image ?? 'postgres:17';\n this.environment = {\n POSTGRES_DB: 'test',\n POSTGRES_PASSWORD: 'test',\n POSTGRES_USER: 'test',\n ...options.env,\n };\n }\n\n buildConnectionString(host: string, port: number): string {\n const user = this.environment.POSTGRES_USER ?? 'test';\n const password = this.environment.POSTGRES_PASSWORD ?? 'test';\n const db = this.environment.POSTGRES_DB ?? 'test';\n return `postgresql://${user}:${password}@${host}:${port}/${db}`;\n }\n\n createDatabaseAdapter(): DatabasePort {\n return this;\n }\n\n async healthcheck(): Promise<void> {\n if (!this.connectionString) {\n throw new Error('postgres: cannot healthcheck — no connection string');\n }\n\n // Healthcheck uses a throwaway client (connection might not be established yet)\n try {\n const client = new Client({ connectionString: this.connectionString });\n await client.connect();\n await client.query('SELECT 1');\n await client.end();\n } catch (error: any) {\n throw new Error(\n `postgres healthcheck failed: ${error.message || error.code || String(error)}`,\n { cause: error },\n );\n }\n }\n\n async initialize(composeDir: string): Promise<void> {\n if (!this.composeName) {\n return;\n }\n\n const initPaths = [\n resolve(composeDir, `${this.composeName}/init.sql`),\n resolve(composeDir, 'postgres/init.sql'),\n ];\n\n for (const initPath of initPaths) {\n if (existsSync(initPath)) {\n const sql = readFileSync(initPath, 'utf8');\n try {\n await this.seed(sql);\n } catch (error: any) {\n throw new Error(\n `postgres init script failed (${initPath}):\\n${error.message}`,\n {\n cause: error,\n },\n );\n }\n return;\n }\n }\n }\n\n private async getClient(): Promise<Client> {\n if (this.client) {\n return this.client;\n }\n const client = new Client({ connectionString: this.connectionString });\n client.on('error', () => {\n // Connection dropped (container stopped) — reset so next call reconnects\n this.client = null;\n });\n await client.connect();\n this.client = client;\n return client;\n }\n\n async seed(sql: string): Promise<void> {\n const client = await this.getClient();\n await client.query(sql);\n }\n\n async reset(): Promise<void> {\n const client = await this.getClient();\n const result = await client.query(`\n SELECT tablename FROM pg_tables\n WHERE schemaname = '${this.schema}'\n AND tablename NOT LIKE '_prisma%'\n `);\n for (const row of result.rows) {\n await client.query(`TRUNCATE \"${this.schema}\".\"${row.tablename}\" CASCADE`);\n }\n }\n\n async query(table: string, columns: string[]): Promise<unknown[][]> {\n const client = await this.getClient();\n const columnList = columns.join(', ');\n const result = await client.query(\n `SELECT ${columnList} FROM \"${this.schema}\".\"${table}\" ORDER BY 1`,\n );\n return result.rows.map((row: Record<string, unknown>) => columns.map((col) => row[col]));\n }\n\n isolation(): IsolationStrategy {\n return {\n acquire: async (workerId: string) => {\n const workerSchema = `worker_${workerId}`;\n this.originalConnectionString = this.connectionString;\n const client = await this.getClient();\n\n // Create schema by cloning all tables from public\n await client.query(`DROP SCHEMA IF EXISTS \"${workerSchema}\" CASCADE`);\n await client.query(`CREATE SCHEMA \"${workerSchema}\"`);\n\n // Copy table structures (no data) from public\n const tables = await client.query(`\n SELECT tablename FROM pg_tables\n WHERE schemaname = 'public'\n AND tablename NOT LIKE '_prisma%'\n `);\n for (const row of tables.rows) {\n await client.query(\n `CREATE TABLE \"${workerSchema}\".\"${row.tablename}\" (LIKE \"public\".\"${row.tablename}\" INCLUDING ALL)`,\n );\n }\n\n // Switch this handle to use the worker schema\n this.schema = workerSchema;\n await client.query(`SET search_path TO \"${workerSchema}\", public`);\n\n // Update connectionString so app connections also use the worker schema\n const url = new URL(this.connectionString);\n url.searchParams.set('options', `-c search_path=${workerSchema},public`);\n this.connectionString = url.toString();\n },\n\n reset: async () => {\n await this.reset();\n },\n\n release: async () => {\n const client = await this.getClient();\n const workerSchema = this.schema;\n this.schema = 'public';\n this.connectionString = this.originalConnectionString;\n await client.query(`SET search_path TO public`);\n await client.query(`DROP SCHEMA IF EXISTS \"${workerSchema}\" CASCADE`);\n },\n };\n }\n}\n\n/**\n * Create a PostgreSQL service handle.\n *\n * @example\n * const db = postgres({ compose: \"db\" });\n * // After start: db.connectionString is populated\n */\nexport function postgres(options: PostgresOptions = {}): PostgresHandle {\n return new PostgresHandle(options);\n}\n","import type { DatabasePort } from '../ports/database.port.js';\nimport type { IsolationStrategy } from '../ports/isolation.port.js';\nimport type { ServiceHandle } from '../ports/service.port.js';\n\nexport interface RedisOptions {\n /** Map to a service in docker-compose.test.yaml. */\n compose?: string;\n /** Override image. */\n image?: string;\n}\n\nexport class RedisHandle implements ServiceHandle {\n readonly type = 'redis';\n readonly composeName: null | string;\n readonly defaultPort = 6379;\n readonly defaultImage: string;\n readonly environment: Record<string, string> = {};\n\n connectionString = '';\n started = false;\n\n private dbIndex = 0;\n\n constructor(options: RedisOptions = {}) {\n this.composeName = options.compose ?? null;\n this.defaultImage = options.image ?? 'redis:7';\n }\n\n buildConnectionString(host: string, port: number): string {\n return `redis://${host}:${port}`;\n }\n\n createDatabaseAdapter(): DatabasePort | null {\n return null;\n }\n\n async healthcheck(): Promise<void> {\n if (!this.connectionString) {\n throw new Error('redis: cannot healthcheck — no connection string');\n }\n\n try {\n const { createClient } = await import('redis');\n const client = createClient({ url: this.connectionString });\n await client.connect();\n await client.ping();\n await client.disconnect();\n } catch (error: any) {\n throw new Error(\n `redis healthcheck failed: ${error.message || error.code || String(error)}`,\n {\n cause: error,\n },\n );\n }\n }\n\n async initialize(): Promise<void> {\n // Redis doesn't need initialization scripts\n }\n\n async reset(): Promise<void> {\n const { createClient } = await import('redis');\n const client = createClient({ url: this.connectionString, database: this.dbIndex });\n await client.connect();\n try {\n await client.flushDb();\n } finally {\n await client.disconnect();\n }\n }\n\n isolation(): IsolationStrategy {\n return {\n acquire: async (workerId: string) => {\n // Use Redis database index 1-15 for workers (0 is default/shared)\n const numericId = Number.parseInt(workerId, 10) || 0;\n this.dbIndex = (numericId % 15) + 1;\n },\n\n reset: async () => {\n await this.reset();\n },\n\n release: async () => {\n await this.reset();\n this.dbIndex = 0;\n },\n };\n }\n}\n\n/**\n * Create a Redis service handle.\n *\n * @example\n * const cache = redis({ compose: \"cache\" });\n * // After start: cache.connectionString is populated\n */\nexport function redis(options: RedisOptions = {}): RedisHandle {\n return new RedisHandle(options);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
//#region src/ports/database.port.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Abstract database interface for specification runners.
|
|
4
|
+
* Implement this to plug in your database stack (e.g. Postgres, SQLite).
|
|
5
|
+
*/
|
|
6
|
+
interface DatabasePort {
|
|
7
|
+
/** Execute raw SQL (for seeding test data). */
|
|
8
|
+
seed(sql: string): Promise<void>;
|
|
9
|
+
/** Query a table and return rows as arrays of values. */
|
|
10
|
+
query(table: string, columns: string[]): Promise<unknown[][]>;
|
|
11
|
+
/** Reset database to clean state between tests. */
|
|
12
|
+
reset(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/ports/isolation.port.d.ts
|
|
16
|
+
/**
|
|
17
|
+
* Strategy for isolating service state across parallel test workers.
|
|
18
|
+
*
|
|
19
|
+
* Each service handle provides an isolation strategy. The framework
|
|
20
|
+
* calls `acquire()` once per vitest worker, `reset()` before each
|
|
21
|
+
* `spec.run()`, and `release()` when the worker shuts down.
|
|
22
|
+
*
|
|
23
|
+
* Implement this interface to support new service types (e.g. MongoDB,
|
|
24
|
+
* Elasticsearch, S3).
|
|
25
|
+
*/
|
|
26
|
+
interface IsolationStrategy {
|
|
27
|
+
/**
|
|
28
|
+
* Create an isolated namespace for this worker.
|
|
29
|
+
* Called once when the worker starts — e.g. clone a template database,
|
|
30
|
+
* set a Redis key prefix.
|
|
31
|
+
*
|
|
32
|
+
* @param workerId - Unique identifier for this vitest worker.
|
|
33
|
+
*/
|
|
34
|
+
acquire(workerId: string): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Fast reset within the namespace between `spec.run()` calls.
|
|
37
|
+
* E.g. truncate tables (without dropping the database).
|
|
38
|
+
*/
|
|
39
|
+
reset(): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Tear down the isolated namespace.
|
|
42
|
+
* Called once when the worker shuts down — e.g. drop the cloned database.
|
|
43
|
+
*/
|
|
44
|
+
release(): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/ports/service.port.d.ts
|
|
48
|
+
/**
|
|
49
|
+
* A service handle — returned by factory functions like postgres(), redis().
|
|
50
|
+
* Mutable: connectionString is populated after the orchestrator starts containers.
|
|
51
|
+
*/
|
|
52
|
+
interface ServiceHandle {
|
|
53
|
+
/** Service type identifier. */
|
|
54
|
+
readonly type: string;
|
|
55
|
+
/** Compose service name (if linked). */
|
|
56
|
+
readonly composeName: null | string;
|
|
57
|
+
/** Default container port for this service type. */
|
|
58
|
+
readonly defaultPort: number;
|
|
59
|
+
/** Default Docker image for this service type. */
|
|
60
|
+
readonly defaultImage: string;
|
|
61
|
+
/** Environment variables to pass to the container. */
|
|
62
|
+
readonly environment: Record<string, string>;
|
|
63
|
+
/** Connection string — populated after start. */
|
|
64
|
+
connectionString: string;
|
|
65
|
+
/** Whether this service has been started. */
|
|
66
|
+
started: boolean;
|
|
67
|
+
/** Build the connection string from host and port. */
|
|
68
|
+
buildConnectionString(host: string, port: number): string;
|
|
69
|
+
/** Create a DatabasePort adapter (if this is a database). Returns null otherwise. */
|
|
70
|
+
createDatabaseAdapter(): DatabasePort | null;
|
|
71
|
+
/** Verify the service is ready and accepting connections. Throws with context if not. */
|
|
72
|
+
healthcheck(): Promise<void>;
|
|
73
|
+
/** Run initialization scripts (e.g., init.sql). Throws with SQL error context if it fails. */
|
|
74
|
+
initialize(composeDir: string): Promise<void>;
|
|
75
|
+
/** Reset state between tests (truncate tables, flush cache, etc.) */
|
|
76
|
+
reset(): Promise<void>;
|
|
77
|
+
/** Get the isolation strategy for parallel test execution. */
|
|
78
|
+
isolation(): IsolationStrategy;
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/adapters/postgres.adapter.d.ts
|
|
82
|
+
interface PostgresOptions {
|
|
83
|
+
/** Map to a service in docker-compose.test.yaml. */
|
|
84
|
+
compose?: string;
|
|
85
|
+
/** Override image. */
|
|
86
|
+
image?: string;
|
|
87
|
+
/** Override environment variables. */
|
|
88
|
+
env?: Record<string, string>;
|
|
89
|
+
}
|
|
90
|
+
declare class PostgresHandle implements DatabasePort, ServiceHandle {
|
|
91
|
+
readonly type = "postgres";
|
|
92
|
+
readonly composeName: null | string;
|
|
93
|
+
readonly defaultPort = 5432;
|
|
94
|
+
readonly defaultImage: string;
|
|
95
|
+
readonly environment: Record<string, string>;
|
|
96
|
+
connectionString: string;
|
|
97
|
+
started: boolean;
|
|
98
|
+
private client;
|
|
99
|
+
private originalConnectionString;
|
|
100
|
+
private schema;
|
|
101
|
+
constructor(options?: PostgresOptions);
|
|
102
|
+
buildConnectionString(host: string, port: number): string;
|
|
103
|
+
createDatabaseAdapter(): DatabasePort;
|
|
104
|
+
healthcheck(): Promise<void>;
|
|
105
|
+
initialize(composeDir: string): Promise<void>;
|
|
106
|
+
private getClient;
|
|
107
|
+
seed(sql: string): Promise<void>;
|
|
108
|
+
reset(): Promise<void>;
|
|
109
|
+
query(table: string, columns: string[]): Promise<unknown[][]>;
|
|
110
|
+
isolation(): IsolationStrategy;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Create a PostgreSQL service handle.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* const db = postgres({ compose: "db" });
|
|
117
|
+
* // After start: db.connectionString is populated
|
|
118
|
+
*/
|
|
119
|
+
declare function postgres(options?: PostgresOptions): PostgresHandle;
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/adapters/redis.adapter.d.ts
|
|
122
|
+
interface RedisOptions {
|
|
123
|
+
/** Map to a service in docker-compose.test.yaml. */
|
|
124
|
+
compose?: string;
|
|
125
|
+
/** Override image. */
|
|
126
|
+
image?: string;
|
|
127
|
+
}
|
|
128
|
+
declare class RedisHandle implements ServiceHandle {
|
|
129
|
+
readonly type = "redis";
|
|
130
|
+
readonly composeName: null | string;
|
|
131
|
+
readonly defaultPort = 6379;
|
|
132
|
+
readonly defaultImage: string;
|
|
133
|
+
readonly environment: Record<string, string>;
|
|
134
|
+
connectionString: string;
|
|
135
|
+
started: boolean;
|
|
136
|
+
private dbIndex;
|
|
137
|
+
constructor(options?: RedisOptions);
|
|
138
|
+
buildConnectionString(host: string, port: number): string;
|
|
139
|
+
createDatabaseAdapter(): DatabasePort | null;
|
|
140
|
+
healthcheck(): Promise<void>;
|
|
141
|
+
initialize(): Promise<void>;
|
|
142
|
+
reset(): Promise<void>;
|
|
143
|
+
isolation(): IsolationStrategy;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Create a Redis service handle.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* const cache = redis({ compose: "cache" });
|
|
150
|
+
* // After start: cache.connectionString is populated
|
|
151
|
+
*/
|
|
152
|
+
declare function redis(options?: RedisOptions): RedisHandle;
|
|
153
|
+
//#endregion
|
|
154
|
+
export { ServiceHandle as a, postgres as i, redis as n, IsolationStrategy as o, PostgresOptions as r, DatabasePort as s, RedisOptions as t };
|
|
155
|
+
//# sourceMappingURL=redis.adapter.d.cts.map
|