@dunx/testing 3.2.1 → 3.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/dist/http2.d.ts +36 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +93 -0
- package/dist/server.d.ts +5 -0
- package/package.json +5 -5
package/dist/http2.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { JsonInit, JsonResponse } from './client.js';
|
|
2
|
+
/**
|
|
3
|
+
* One HTTP/2 request against a cleartext origin, and the JSON round-trip beside
|
|
4
|
+
* it - the same pair `testClient` gives for HTTP/1.1.
|
|
5
|
+
*
|
|
6
|
+
* It exists because **Bun's `fetch` cannot call an h2c origin**: both
|
|
7
|
+
* `protocol: 'http2'` and `protocol: 'h2'` reject with `HTTP2Unsupported`
|
|
8
|
+
* against any plain-HTTP peer, whatever that peer serves. `node:http2` opens
|
|
9
|
+
* with the connection preface instead, which is the "prior knowledge" path
|
|
10
|
+
* `Bun.serve({ http2: true })` answers.
|
|
11
|
+
*
|
|
12
|
+
* A connection per call, which is what a test wants: the assertion is about the
|
|
13
|
+
* server, and a pooled session would carry state between cases.
|
|
14
|
+
*/
|
|
15
|
+
export interface Http2Client {
|
|
16
|
+
/** The origin these requests go to. */
|
|
17
|
+
readonly url: string;
|
|
18
|
+
/** Status, headers and the raw body text. */
|
|
19
|
+
request(path?: string, init?: JsonInit): Promise<Http2Response>;
|
|
20
|
+
/** Status, headers and the parsed body, for the common assertion. */
|
|
21
|
+
json<T = unknown>(path?: string, init?: JsonInit): Promise<JsonResponse<T>>;
|
|
22
|
+
}
|
|
23
|
+
export interface Http2Response {
|
|
24
|
+
readonly status: number;
|
|
25
|
+
readonly headers: Headers;
|
|
26
|
+
readonly text: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* ```ts
|
|
30
|
+
* const server = await createTestServer({ modules: [AppModule], http2: true });
|
|
31
|
+
* const h2 = http2Client(server.url);
|
|
32
|
+
*
|
|
33
|
+
* expect((await h2.json('/users')).status).toBe(200);
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare const http2Client: (url: string, timeoutMs?: number) => Http2Client;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { createTestApp, testRoot, type TestAppOptions } from './app.js';
|
|
2
2
|
export { testClient, type JsonInit, type JsonResponse, type TestClient, } from './client.js';
|
|
3
|
+
export { http2Client, type Http2Client, type Http2Response } from './http2.js';
|
|
3
4
|
export { RecordingLogger, type RecordedLog } from './logger.js';
|
|
4
5
|
export { createTestServer, type TestServer, type TestServerOptions, } from './server.js';
|
package/dist/index.js
CHANGED
|
@@ -47,6 +47,97 @@ That is not JSON - use request() ` + "for a response that is not.");
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
});
|
|
50
|
+
// src/http2.ts
|
|
51
|
+
import http2 from "http2";
|
|
52
|
+
var toBody = (init) => {
|
|
53
|
+
if (init.json !== undefined)
|
|
54
|
+
return JSON.stringify(init.json);
|
|
55
|
+
const { body } = init;
|
|
56
|
+
if (body === undefined || body === null)
|
|
57
|
+
return;
|
|
58
|
+
if (typeof body === "string" || ArrayBuffer.isView(body))
|
|
59
|
+
return body;
|
|
60
|
+
if (body instanceof ArrayBuffer)
|
|
61
|
+
return new Uint8Array(body);
|
|
62
|
+
throw new TypeError("http2Client takes a string, bytes, or `json` - not " + `${body.constructor.name}. Serialise it first, or use \`testClient\` ` + "over HTTP/1.1.");
|
|
63
|
+
};
|
|
64
|
+
var headerBlock = (path, init, body) => {
|
|
65
|
+
const headers = new Headers(init.headers);
|
|
66
|
+
if (init.json !== undefined && !headers.has("content-type")) {
|
|
67
|
+
headers.set("content-type", "application/json");
|
|
68
|
+
}
|
|
69
|
+
const block = {
|
|
70
|
+
":path": path,
|
|
71
|
+
":method": init.method ?? (body === undefined ? "GET" : "POST")
|
|
72
|
+
};
|
|
73
|
+
headers.forEach((value, key) => {
|
|
74
|
+
block[key] = value;
|
|
75
|
+
});
|
|
76
|
+
return block;
|
|
77
|
+
};
|
|
78
|
+
var send = (origin, path, init, timeoutMs) => new Promise((resolve, reject) => {
|
|
79
|
+
let body;
|
|
80
|
+
try {
|
|
81
|
+
body = toBody(init);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
reject(error);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const client = http2.connect(origin);
|
|
87
|
+
const timer = setTimeout(() => {
|
|
88
|
+
client.destroy();
|
|
89
|
+
reject(new Error(`HTTP/2 ${path} timed out after ${timeoutMs}ms`));
|
|
90
|
+
}, timeoutMs);
|
|
91
|
+
const fail = (error) => {
|
|
92
|
+
clearTimeout(timer);
|
|
93
|
+
client.destroy();
|
|
94
|
+
reject(error);
|
|
95
|
+
};
|
|
96
|
+
client.on("error", fail);
|
|
97
|
+
const request = client.request(headerBlock(path, init, body));
|
|
98
|
+
const headers = new Headers;
|
|
99
|
+
let status = 0;
|
|
100
|
+
let text = "";
|
|
101
|
+
request.setEncoding("utf8");
|
|
102
|
+
request.on("response", (received) => {
|
|
103
|
+
status = Number(received[":status"]);
|
|
104
|
+
for (const [key, value] of Object.entries(received)) {
|
|
105
|
+
if (key.startsWith(":") || value === undefined)
|
|
106
|
+
continue;
|
|
107
|
+
headers.set(key, Array.isArray(value) ? value.join(", ") : value);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
request.on("data", (chunk) => {
|
|
111
|
+
text += chunk;
|
|
112
|
+
});
|
|
113
|
+
request.on("error", fail);
|
|
114
|
+
request.on("end", () => {
|
|
115
|
+
clearTimeout(timer);
|
|
116
|
+
client.close();
|
|
117
|
+
resolve({ status, headers, text });
|
|
118
|
+
});
|
|
119
|
+
request.end(body);
|
|
120
|
+
});
|
|
121
|
+
var http2Client = (url, timeoutMs = 4000) => {
|
|
122
|
+
const origin = new URL(url).origin;
|
|
123
|
+
const at = (path) => {
|
|
124
|
+
const target = new URL(path, url);
|
|
125
|
+
return `${target.pathname}${target.search}`;
|
|
126
|
+
};
|
|
127
|
+
const request = (path = "/", init = {}) => send(origin, at(path), init, timeoutMs);
|
|
128
|
+
return {
|
|
129
|
+
url: origin,
|
|
130
|
+
request,
|
|
131
|
+
async json(path = "/", init = {}) {
|
|
132
|
+
const response = await request(path, init);
|
|
133
|
+
return {
|
|
134
|
+
status: response.status,
|
|
135
|
+
headers: response.headers,
|
|
136
|
+
body: response.text === "" ? undefined : JSON.parse(response.text)
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
};
|
|
50
141
|
// src/logger.ts
|
|
51
142
|
import { Logger, LogLevel } from "@dunx/core";
|
|
52
143
|
|
|
@@ -146,6 +237,7 @@ var createTestServer = async (options) => {
|
|
|
146
237
|
return {
|
|
147
238
|
...testClient(await app.listen()),
|
|
148
239
|
app,
|
|
240
|
+
gatewayUrl: app.gatewayUrl,
|
|
149
241
|
close: () => app.shutdown()
|
|
150
242
|
};
|
|
151
243
|
};
|
|
@@ -153,6 +245,7 @@ export {
|
|
|
153
245
|
RecordingLogger,
|
|
154
246
|
createTestApp,
|
|
155
247
|
createTestServer,
|
|
248
|
+
http2Client,
|
|
156
249
|
testClient,
|
|
157
250
|
testRoot
|
|
158
251
|
};
|
package/dist/server.d.ts
CHANGED
|
@@ -14,6 +14,11 @@ export interface TestServerOptions extends TestAppOptions, Omit<HttpOptions, 'po
|
|
|
14
14
|
}
|
|
15
15
|
export interface TestServer extends TestClient {
|
|
16
16
|
readonly app: HttpApp;
|
|
17
|
+
/**
|
|
18
|
+
* Where the gateways answer, when `gatewayPort` split them off `url`.
|
|
19
|
+
* `undefined` otherwise, which is when they are on `url` itself.
|
|
20
|
+
*/
|
|
21
|
+
readonly gatewayUrl: string | undefined;
|
|
17
22
|
/** `app.shutdown()` - stops the server, then tears the container down. */
|
|
18
23
|
close(): Promise<void>;
|
|
19
24
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dunx/testing",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Test harness for dunx apps: a container with providers replaced in place, and a real Bun.serve on port 0",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
"@dunx/http": "workspace:*"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
|
-
"@dunx/core": "^3.
|
|
55
|
-
"@dunx/http": "^3.
|
|
56
|
-
"@types/bun": ">=1.
|
|
54
|
+
"@dunx/core": "^3.3.0",
|
|
55
|
+
"@dunx/http": "^3.3.0",
|
|
56
|
+
"@types/bun": ">=1.4.1"
|
|
57
57
|
},
|
|
58
58
|
"peerDependenciesMeta": {
|
|
59
59
|
"@types/bun": {
|
|
@@ -61,6 +61,6 @@
|
|
|
61
61
|
}
|
|
62
62
|
},
|
|
63
63
|
"engines": {
|
|
64
|
-
"bun": ">=1.4.
|
|
64
|
+
"bun": ">=1.4.1"
|
|
65
65
|
}
|
|
66
66
|
}
|