@jterrazz/test 6.1.0 → 6.3.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/index.cjs +66 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -4
- package/dist/index.d.ts +30 -4
- package/dist/index.js +66 -14
- package/dist/index.js.map +1 -1
- package/dist/intercept.cjs +240 -0
- package/dist/intercept.cjs.map +1 -0
- package/dist/intercept.d.cts +89 -0
- package/dist/intercept.d.ts +89 -0
- package/dist/intercept.js +237 -0
- package/dist/intercept.js.map +1 -0
- package/dist/server.cjs +79 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.js +79 -0
- package/dist/server.js.map +1 -0
- package/dist/sqlite.adapter.cjs +22 -3
- package/dist/sqlite.adapter.cjs.map +1 -1
- package/dist/sqlite.adapter.js +22 -3
- package/dist/sqlite.adapter.js.map +1 -1
- package/dist/types.d.cts +35 -0
- package/dist/types.d.ts +35 -0
- package/package.json +6 -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/index.cjs
CHANGED
|
@@ -115,22 +115,25 @@ var FetchAdapter = class {
|
|
|
115
115
|
constructor(url) {
|
|
116
116
|
this.baseUrl = url.replace(/\/$/, "");
|
|
117
117
|
}
|
|
118
|
-
async request(method, path, body) {
|
|
118
|
+
async request(method, path, body, headers) {
|
|
119
119
|
const init = {
|
|
120
120
|
method,
|
|
121
|
-
headers: {
|
|
121
|
+
headers: {
|
|
122
|
+
"Content-Type": "application/json",
|
|
123
|
+
...headers
|
|
124
|
+
}
|
|
122
125
|
};
|
|
123
126
|
if (body !== void 0) init.body = JSON.stringify(body);
|
|
124
127
|
const response = await fetch(`${this.baseUrl}${path}`, init);
|
|
125
128
|
const responseBody = await response.json().catch(() => null);
|
|
126
|
-
const
|
|
129
|
+
const responseHeaders = {};
|
|
127
130
|
response.headers.forEach((value, key) => {
|
|
128
|
-
|
|
131
|
+
responseHeaders[key] = value;
|
|
129
132
|
});
|
|
130
133
|
return {
|
|
131
134
|
status: response.status,
|
|
132
135
|
body: responseBody,
|
|
133
|
-
headers
|
|
136
|
+
headers: responseHeaders
|
|
134
137
|
};
|
|
135
138
|
}
|
|
136
139
|
};
|
|
@@ -145,22 +148,25 @@ var HonoAdapter = class {
|
|
|
145
148
|
constructor(app) {
|
|
146
149
|
this.app = app;
|
|
147
150
|
}
|
|
148
|
-
async request(method, path, body) {
|
|
151
|
+
async request(method, path, body, headers) {
|
|
149
152
|
const init = {
|
|
150
153
|
method,
|
|
151
|
-
headers: {
|
|
154
|
+
headers: {
|
|
155
|
+
"Content-Type": "application/json",
|
|
156
|
+
...headers
|
|
157
|
+
}
|
|
152
158
|
};
|
|
153
159
|
if (body !== void 0) init.body = JSON.stringify(body);
|
|
154
160
|
const response = await this.app.request(path, init);
|
|
155
161
|
const responseBody = await response.json().catch(() => null);
|
|
156
|
-
const
|
|
162
|
+
const responseHeaders = {};
|
|
157
163
|
response.headers.forEach((value, key) => {
|
|
158
|
-
|
|
164
|
+
responseHeaders[key] = value;
|
|
159
165
|
});
|
|
160
166
|
return {
|
|
161
167
|
status: response.status,
|
|
162
168
|
body: responseBody,
|
|
163
|
-
headers
|
|
169
|
+
headers: responseHeaders
|
|
164
170
|
};
|
|
165
171
|
}
|
|
166
172
|
};
|
|
@@ -586,10 +592,12 @@ var SpecificationBuilder = class {
|
|
|
586
592
|
commandEnv = {};
|
|
587
593
|
config;
|
|
588
594
|
fixtures = [];
|
|
595
|
+
intercepts = [];
|
|
589
596
|
label;
|
|
590
597
|
mocks = [];
|
|
591
598
|
projectName = null;
|
|
592
599
|
request = null;
|
|
600
|
+
requestHeaders = {};
|
|
593
601
|
seeds = [];
|
|
594
602
|
spawnConfig = null;
|
|
595
603
|
testDir;
|
|
@@ -644,6 +652,41 @@ var SpecificationBuilder = class {
|
|
|
644
652
|
return this;
|
|
645
653
|
}
|
|
646
654
|
/**
|
|
655
|
+
* Set HTTP headers for the request. Multiple calls merge.
|
|
656
|
+
*
|
|
657
|
+
* @example
|
|
658
|
+
* spec("french").headers({ 'Accept-Language': 'fr' }).get("/articles").run();
|
|
659
|
+
*/
|
|
660
|
+
headers(headers) {
|
|
661
|
+
this.requestHeaders = {
|
|
662
|
+
...this.requestHeaders,
|
|
663
|
+
...headers
|
|
664
|
+
};
|
|
665
|
+
return this;
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Intercept an outgoing HTTP request and return a controlled response.
|
|
669
|
+
* Uses MSW under the hood. Intercepts are queued — multiple calls with the
|
|
670
|
+
* same trigger fire sequentially (first match consumed first).
|
|
671
|
+
*
|
|
672
|
+
* @param trigger - What to match (use openai.chat(), anthropic.messages(), http.get(), etc.)
|
|
673
|
+
* @param response - What to return (use openai.response(), http.json(), etc.)
|
|
674
|
+
*
|
|
675
|
+
* @example
|
|
676
|
+
* spec('pipeline')
|
|
677
|
+
* .intercept(openai.chat(), openai.response({ categories: ['TECH'] }))
|
|
678
|
+
* .intercept(openai.chat(), openai.response({ headline: 'AI News' }))
|
|
679
|
+
* .exec('process')
|
|
680
|
+
* .run();
|
|
681
|
+
*/
|
|
682
|
+
intercept(trigger, response) {
|
|
683
|
+
this.intercepts.push({
|
|
684
|
+
trigger,
|
|
685
|
+
response
|
|
686
|
+
});
|
|
687
|
+
return this;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
647
690
|
* Send a GET request to the server adapter.
|
|
648
691
|
*
|
|
649
692
|
* @example
|
|
@@ -737,9 +780,17 @@ var SpecificationBuilder = class {
|
|
|
737
780
|
await db.seed(sql);
|
|
738
781
|
}
|
|
739
782
|
if (this.fixtures.length > 0 && workDir) for (const entry of this.fixtures) (0, node_fs.cpSync)((0, node_path.resolve)(this.testDir, "fixtures", entry.file), (0, node_path.resolve)(workDir, entry.file), { recursive: true });
|
|
740
|
-
|
|
741
|
-
if (
|
|
742
|
-
|
|
783
|
+
let cleanupIntercepts = null;
|
|
784
|
+
if (this.intercepts.length > 0) {
|
|
785
|
+
const { registerIntercepts } = await Promise.resolve().then(() => require("./server.cjs"));
|
|
786
|
+
cleanupIntercepts = await registerIntercepts(this.intercepts);
|
|
787
|
+
}
|
|
788
|
+
try {
|
|
789
|
+
if (hasHttpAction) return await this.runHttpAction();
|
|
790
|
+
return await this.runCliAction(workDir);
|
|
791
|
+
} finally {
|
|
792
|
+
if (cleanupIntercepts) cleanupIntercepts();
|
|
793
|
+
}
|
|
743
794
|
}
|
|
744
795
|
resolveEnv(workDir) {
|
|
745
796
|
const keys = Object.keys(this.commandEnv);
|
|
@@ -764,7 +815,8 @@ var SpecificationBuilder = class {
|
|
|
764
815
|
if (!this.config.server) throw new Error("HTTP actions require a server adapter (use integration() or e2e())");
|
|
765
816
|
let body;
|
|
766
817
|
if (this.request.bodyFile) body = JSON.parse((0, node_fs.readFileSync)((0, node_path.resolve)(this.testDir, "requests", this.request.bodyFile), "utf8"));
|
|
767
|
-
const
|
|
818
|
+
const headers = Object.keys(this.requestHeaders).length > 0 ? this.requestHeaders : void 0;
|
|
819
|
+
const response = await this.config.server.request(this.request.method, this.request.path, body, headers);
|
|
768
820
|
return new SpecificationResult({
|
|
769
821
|
config: this.config,
|
|
770
822
|
requestInfo: {
|