@jterrazz/test 6.0.0 → 6.2.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 +9 -9
- package/dist/chunk.cjs +28 -0
- package/dist/index.cjs +78 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -16
- package/dist/index.d.ts +26 -16
- package/dist/index.js +73 -29
- package/dist/index.js.map +1 -1
- package/dist/mock-of.cjs +2 -29
- package/dist/mock-of.cjs.map +1 -1
- package/dist/services.cjs +4 -3
- package/dist/services.d.cts +2 -2
- package/dist/services.d.ts +2 -2
- package/dist/services.js +2 -2
- package/dist/{redis.adapter.cjs → sqlite.adapter.cjs} +138 -2
- package/dist/sqlite.adapter.cjs.map +1 -0
- package/dist/{redis.adapter.d.ts → sqlite.adapter.d.cts} +56 -2
- package/dist/{redis.adapter.d.cts → sqlite.adapter.d.ts} +56 -2
- package/dist/{redis.adapter.js → sqlite.adapter.js} +132 -3
- package/dist/sqlite.adapter.js.map +1 -0
- package/package.json +3 -1
- package/dist/redis.adapter.cjs.map +0 -1
- package/dist/redis.adapter.js.map +0 -1
package/README.md
CHANGED
|
@@ -35,13 +35,13 @@ afterAll(() => run.cleanup());
|
|
|
35
35
|
import { run } from '../../setup/integration.specification.js';
|
|
36
36
|
|
|
37
37
|
test('creates a user', async () => {
|
|
38
|
-
// Given
|
|
38
|
+
// Given - one existing user
|
|
39
39
|
const result = await run('creates user')
|
|
40
40
|
.seed('initial-users.sql')
|
|
41
41
|
.post('/users', 'new-user.json')
|
|
42
42
|
.run();
|
|
43
43
|
|
|
44
|
-
// Then
|
|
44
|
+
// Then - user created
|
|
45
45
|
expect(result.status).toBe(201);
|
|
46
46
|
await result.table('users').toMatch({
|
|
47
47
|
columns: ['name'],
|
|
@@ -67,10 +67,10 @@ export const run = await spec(command(resolve(import.meta.dirname, '../../bin/my
|
|
|
67
67
|
import { run } from '../../setup/cli.specification.js';
|
|
68
68
|
|
|
69
69
|
test('builds the project', async () => {
|
|
70
|
-
// Given
|
|
70
|
+
// Given - sample app project
|
|
71
71
|
const result = await run('build').project('sample-app').exec('build').run();
|
|
72
72
|
|
|
73
|
-
// Then
|
|
73
|
+
// Then - ESM output with source maps
|
|
74
74
|
expect(result.exitCode).toBe(0);
|
|
75
75
|
expect(result.stdout).toContain('Build completed');
|
|
76
76
|
expect(result.file('dist/index.js').exists).toBe(true);
|
|
@@ -83,7 +83,7 @@ test('builds the project', async () => {
|
|
|
83
83
|
|
|
84
84
|
Three modes, same builder API. Each handles infrastructure and cleanup automatically.
|
|
85
85
|
|
|
86
|
-
### `spec(app(...))`
|
|
86
|
+
### `spec(app(...))` - testcontainers + in-process app
|
|
87
87
|
|
|
88
88
|
Starts real containers via testcontainers. App runs in-process (Hono). Fastest feedback loop.
|
|
89
89
|
|
|
@@ -103,7 +103,7 @@ export const run = await spec(
|
|
|
103
103
|
);
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
### `spec(stack(...))`
|
|
106
|
+
### `spec(stack(...))` - docker compose up + real HTTP
|
|
107
107
|
|
|
108
108
|
Starts the full `docker/compose.test.yaml` stack. App URL and databases auto-detected.
|
|
109
109
|
|
|
@@ -113,7 +113,7 @@ import { spec, stack } from '@jterrazz/test';
|
|
|
113
113
|
export const run = await spec(stack('../../'));
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
-
### `spec(command(...))`
|
|
116
|
+
### `spec(command(...))` - local command execution
|
|
117
117
|
|
|
118
118
|
Runs CLI commands against fixture projects in temp directories. Optionally starts infrastructure.
|
|
119
119
|
|
|
@@ -331,13 +331,13 @@ Every test uses `// Given` and `// Then` comments. Always both, never one withou
|
|
|
331
331
|
|
|
332
332
|
```typescript
|
|
333
333
|
test('creates a user and returns 201', async () => {
|
|
334
|
-
// Given
|
|
334
|
+
// Given - two existing users
|
|
335
335
|
const result = await run('creates user')
|
|
336
336
|
.seed('initial-users.sql')
|
|
337
337
|
.post('/users', 'new-user.json')
|
|
338
338
|
.run();
|
|
339
339
|
|
|
340
|
-
// Then
|
|
340
|
+
// Then - user created with all three in table
|
|
341
341
|
expect(result.status).toBe(201);
|
|
342
342
|
await result.table('users').toMatch({
|
|
343
343
|
columns: ['name'],
|
package/dist/chunk.cjs
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__toESM", {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
get: function() {
|
|
26
|
+
return __toESM;
|
|
27
|
+
}
|
|
28
|
+
});
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
require("./chunk.cjs");
|
|
3
|
+
const require_sqlite_adapter = require("./sqlite.adapter.cjs");
|
|
2
4
|
const require_mock_of = require("./mock-of.cjs");
|
|
3
|
-
const require_redis_adapter = require("./redis.adapter.cjs");
|
|
4
5
|
let node_child_process = require("node:child_process");
|
|
5
6
|
let node_fs = require("node:fs");
|
|
6
7
|
let node_os = require("node:os");
|
|
@@ -114,22 +115,25 @@ var FetchAdapter = class {
|
|
|
114
115
|
constructor(url) {
|
|
115
116
|
this.baseUrl = url.replace(/\/$/, "");
|
|
116
117
|
}
|
|
117
|
-
async request(method, path, body) {
|
|
118
|
+
async request(method, path, body, headers) {
|
|
118
119
|
const init = {
|
|
119
120
|
method,
|
|
120
|
-
headers: {
|
|
121
|
+
headers: {
|
|
122
|
+
"Content-Type": "application/json",
|
|
123
|
+
...headers
|
|
124
|
+
}
|
|
121
125
|
};
|
|
122
126
|
if (body !== void 0) init.body = JSON.stringify(body);
|
|
123
127
|
const response = await fetch(`${this.baseUrl}${path}`, init);
|
|
124
128
|
const responseBody = await response.json().catch(() => null);
|
|
125
|
-
const
|
|
129
|
+
const responseHeaders = {};
|
|
126
130
|
response.headers.forEach((value, key) => {
|
|
127
|
-
|
|
131
|
+
responseHeaders[key] = value;
|
|
128
132
|
});
|
|
129
133
|
return {
|
|
130
134
|
status: response.status,
|
|
131
135
|
body: responseBody,
|
|
132
|
-
headers
|
|
136
|
+
headers: responseHeaders
|
|
133
137
|
};
|
|
134
138
|
}
|
|
135
139
|
};
|
|
@@ -144,22 +148,25 @@ var HonoAdapter = class {
|
|
|
144
148
|
constructor(app) {
|
|
145
149
|
this.app = app;
|
|
146
150
|
}
|
|
147
|
-
async request(method, path, body) {
|
|
151
|
+
async request(method, path, body, headers) {
|
|
148
152
|
const init = {
|
|
149
153
|
method,
|
|
150
|
-
headers: {
|
|
154
|
+
headers: {
|
|
155
|
+
"Content-Type": "application/json",
|
|
156
|
+
...headers
|
|
157
|
+
}
|
|
151
158
|
};
|
|
152
159
|
if (body !== void 0) init.body = JSON.stringify(body);
|
|
153
160
|
const response = await this.app.request(path, init);
|
|
154
161
|
const responseBody = await response.json().catch(() => null);
|
|
155
|
-
const
|
|
162
|
+
const responseHeaders = {};
|
|
156
163
|
response.headers.forEach((value, key) => {
|
|
157
|
-
|
|
164
|
+
responseHeaders[key] = value;
|
|
158
165
|
});
|
|
159
166
|
return {
|
|
160
167
|
status: response.status,
|
|
161
168
|
body: responseBody,
|
|
162
|
-
headers
|
|
169
|
+
headers: responseHeaders
|
|
163
170
|
};
|
|
164
171
|
}
|
|
165
172
|
};
|
|
@@ -589,6 +596,7 @@ var SpecificationBuilder = class {
|
|
|
589
596
|
mocks = [];
|
|
590
597
|
projectName = null;
|
|
591
598
|
request = null;
|
|
599
|
+
requestHeaders = {};
|
|
592
600
|
seeds = [];
|
|
593
601
|
spawnConfig = null;
|
|
594
602
|
testDir;
|
|
@@ -643,6 +651,19 @@ var SpecificationBuilder = class {
|
|
|
643
651
|
return this;
|
|
644
652
|
}
|
|
645
653
|
/**
|
|
654
|
+
* Set HTTP headers for the request. Multiple calls merge.
|
|
655
|
+
*
|
|
656
|
+
* @example
|
|
657
|
+
* spec("french").headers({ 'Accept-Language': 'fr' }).get("/articles").run();
|
|
658
|
+
*/
|
|
659
|
+
headers(headers) {
|
|
660
|
+
this.requestHeaders = {
|
|
661
|
+
...this.requestHeaders,
|
|
662
|
+
...headers
|
|
663
|
+
};
|
|
664
|
+
return this;
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
646
667
|
* Send a GET request to the server adapter.
|
|
647
668
|
*
|
|
648
669
|
* @example
|
|
@@ -763,7 +784,8 @@ var SpecificationBuilder = class {
|
|
|
763
784
|
if (!this.config.server) throw new Error("HTTP actions require a server adapter (use integration() or e2e())");
|
|
764
785
|
let body;
|
|
765
786
|
if (this.request.bodyFile) body = JSON.parse((0, node_fs.readFileSync)((0, node_path.resolve)(this.testDir, "requests", this.request.bodyFile), "utf8"));
|
|
766
|
-
const
|
|
787
|
+
const headers = Object.keys(this.requestHeaders).length > 0 ? this.requestHeaders : void 0;
|
|
788
|
+
const response = await this.config.server.request(this.request.method, this.request.path, body, headers);
|
|
767
789
|
return new SpecificationResult({
|
|
768
790
|
config: this.config,
|
|
769
791
|
requestInfo: {
|
|
@@ -1211,7 +1233,13 @@ var Orchestrator = class {
|
|
|
1211
1233
|
const composePath = findComposeFile(this.root);
|
|
1212
1234
|
const composeDir = composePath ? (0, node_path.dirname)(composePath) : this.root;
|
|
1213
1235
|
const composeConfig = composePath ? parseComposeFile(composePath) : null;
|
|
1214
|
-
const
|
|
1236
|
+
const containerServices = [];
|
|
1237
|
+
const embeddedServices = [];
|
|
1238
|
+
for (const handle of this.services) {
|
|
1239
|
+
if (handle.defaultPort === 0) {
|
|
1240
|
+
embeddedServices.push(handle);
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1215
1243
|
let image = handle.defaultImage;
|
|
1216
1244
|
let env = { ...handle.environment };
|
|
1217
1245
|
if (handle.composeName && composeConfig) {
|
|
@@ -1225,18 +1253,26 @@ var Orchestrator = class {
|
|
|
1225
1253
|
Object.assign(handle.environment, composeService.environment);
|
|
1226
1254
|
}
|
|
1227
1255
|
}
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1256
|
+
const container = new TestcontainersAdapter({
|
|
1257
|
+
image,
|
|
1258
|
+
port: handle.defaultPort,
|
|
1259
|
+
env
|
|
1260
|
+
});
|
|
1261
|
+
containerServices.push({
|
|
1262
|
+
container,
|
|
1234
1263
|
handle
|
|
1235
|
-
};
|
|
1236
|
-
}
|
|
1237
|
-
await Promise.all(
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
await Promise.all([...containerServices.map(({ container }) => container.start()), ...embeddedServices.map(async (handle) => {
|
|
1267
|
+
await handle.initialize(composeDir);
|
|
1268
|
+
handle.started = true;
|
|
1269
|
+
this.running.push({
|
|
1270
|
+
handle,
|
|
1271
|
+
container: null
|
|
1272
|
+
});
|
|
1273
|
+
})]);
|
|
1238
1274
|
const reports = [];
|
|
1239
|
-
for (const { container, handle } of
|
|
1275
|
+
for (const { container, handle } of containerServices) {
|
|
1240
1276
|
const serviceStartTime = Date.now();
|
|
1241
1277
|
try {
|
|
1242
1278
|
const host = container.getHost();
|
|
@@ -1302,7 +1338,7 @@ var Orchestrator = class {
|
|
|
1302
1338
|
for (const service of composeConfig.infraServices) {
|
|
1303
1339
|
const type = detectServiceType(service.image);
|
|
1304
1340
|
if (type === "postgres") {
|
|
1305
|
-
const handle =
|
|
1341
|
+
const handle = require_sqlite_adapter.postgres({
|
|
1306
1342
|
compose: service.name,
|
|
1307
1343
|
env: service.environment
|
|
1308
1344
|
});
|
|
@@ -1312,7 +1348,7 @@ var Orchestrator = class {
|
|
|
1312
1348
|
handle.started = true;
|
|
1313
1349
|
this.composeHandles.push(handle);
|
|
1314
1350
|
} else if (type === "redis") {
|
|
1315
|
-
const handle =
|
|
1351
|
+
const handle = require_sqlite_adapter.redis({ compose: service.name });
|
|
1316
1352
|
const port = this.composeStack.getMappedPort(service.name, 6379);
|
|
1317
1353
|
handle.connectionString = handle.buildConnectionString("localhost", port);
|
|
1318
1354
|
handle.started = true;
|
|
@@ -1450,7 +1486,12 @@ async function startApp(target, options) {
|
|
|
1450
1486
|
});
|
|
1451
1487
|
await orchestrator.start();
|
|
1452
1488
|
await acquireIsolation(services);
|
|
1453
|
-
const
|
|
1489
|
+
const servicesMap = {};
|
|
1490
|
+
for (const svc of services) {
|
|
1491
|
+
const key = svc.composeName ?? svc.type;
|
|
1492
|
+
servicesMap[key] = svc;
|
|
1493
|
+
}
|
|
1494
|
+
const honoApp = target.factory(servicesMap);
|
|
1454
1495
|
const database = orchestrator.getDatabase() ?? void 0;
|
|
1455
1496
|
const databases = orchestrator.getDatabases();
|
|
1456
1497
|
const runner = createSpecificationRunner({
|
|
@@ -1526,13 +1567,17 @@ async function startCommand(target, options) {
|
|
|
1526
1567
|
//#endregion
|
|
1527
1568
|
//#region src/runner/targets.ts
|
|
1528
1569
|
/**
|
|
1529
|
-
* Test against an in-process Hono app.
|
|
1530
|
-
*
|
|
1570
|
+
* Test against an in-process Hono app. The factory receives started services
|
|
1571
|
+
* so you can wire connection strings into your app/DI container.
|
|
1531
1572
|
*
|
|
1532
|
-
* @param factory - Function that returns a Hono app instance
|
|
1573
|
+
* @param factory - Function that receives services and returns a Hono app instance.
|
|
1533
1574
|
*
|
|
1534
1575
|
* @example
|
|
1535
|
-
*
|
|
1576
|
+
* const db = postgres({ compose: 'db' });
|
|
1577
|
+
* await spec(
|
|
1578
|
+
* app((services) => createApp({ databaseUrl: services.db.connectionString })),
|
|
1579
|
+
* { services: [db] },
|
|
1580
|
+
* );
|
|
1536
1581
|
*/
|
|
1537
1582
|
function app(factory) {
|
|
1538
1583
|
return {
|
|
@@ -1677,9 +1722,10 @@ exports.integration = integration;
|
|
|
1677
1722
|
exports.mockOf = require_mock_of.mockOf;
|
|
1678
1723
|
exports.mockOfDate = require_mock_of.mockOfDate;
|
|
1679
1724
|
exports.normalizeOutput = normalizeOutput;
|
|
1680
|
-
exports.postgres =
|
|
1681
|
-
exports.redis =
|
|
1725
|
+
exports.postgres = require_sqlite_adapter.postgres;
|
|
1726
|
+
exports.redis = require_sqlite_adapter.redis;
|
|
1682
1727
|
exports.spec = spec;
|
|
1728
|
+
exports.sqlite = require_sqlite_adapter.sqlite;
|
|
1683
1729
|
exports.stack = stack;
|
|
1684
1730
|
exports.stripAnsi = stripAnsi;
|
|
1685
1731
|
|