Skip to content

Commit d9b2c4e

Browse files
Fix undici response parsing when status code says no content but there is content (#1062)
1 parent 7e3969c commit d9b2c4e

File tree

3 files changed

+92
-27
lines changed

3 files changed

+92
-27
lines changed

packages/testcontainers/src/wait-strategies/http-wait-strategy.ts

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import Dockerode from "dockerode";
2-
import { Agent, Dispatcher, request } from "undici";
2+
import { Agent, request } from "undici";
33
import { IntervalRetry, log } from "../common";
44
import { getContainerRuntimeClient } from "../container-runtime";
55
import { BoundPorts } from "../utils/bound-ports";
6+
import { undiciResponseToFetchResponse } from "./utils/undici-response-parser";
67
import { AbstractWaitStrategy } from "./wait-strategy";
78

89
export interface HttpWaitStrategyOptions {
@@ -95,7 +96,7 @@ export class HttpWaitStrategy extends AbstractWaitStrategy {
9596
}
9697
}
9798

98-
return this.undiciResponseToFetchResponse(
99+
return undiciResponseToFetchResponse(
99100
await request(url, {
100101
method: this.method,
101102
signal: AbortSignal.timeout(this.readTimeoutMs),
@@ -164,31 +165,6 @@ export class HttpWaitStrategy extends AbstractWaitStrategy {
164165
throw new Error(message);
165166
}
166167

167-
/**
168-
* Converts an undici response to a fetch response.
169-
* This is necessary because node's fetch does not support disabling SSL validation (https://github.com/orgs/nodejs/discussions/44038).
170-
*
171-
* @param undiciResponse The undici response to convert.
172-
* @returns The fetch response.
173-
*/
174-
private undiciResponseToFetchResponse(undiciResponse: Dispatcher.ResponseData): Response {
175-
const headers = new Headers();
176-
for (const [key, value] of Object.entries(undiciResponse.headers)) {
177-
if (Array.isArray(value)) {
178-
for (const v of value) {
179-
headers.append(key, v);
180-
}
181-
} else if (value !== undefined) {
182-
headers.set(key, value);
183-
}
184-
}
185-
186-
return new Response(undiciResponse.body, {
187-
status: undiciResponse.statusCode,
188-
headers,
189-
});
190-
}
191-
192168
private getAgent(): Agent | undefined {
193169
if (this._allowInsecure) {
194170
return new Agent({
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Readable } from "stream";
2+
import { Dispatcher } from "undici";
3+
import BodyReadable from "undici/types/readable";
4+
import { undiciResponseToFetchResponse } from "./undici-response-parser";
5+
6+
test("converts undici response to fetch response", async () => {
7+
const responseData: Partial<Dispatcher.ResponseData> = {
8+
statusCode: 200,
9+
headers: { "content-type": "application/json" },
10+
body: createBody('{"key":"value"}'),
11+
};
12+
13+
const response = undiciResponseToFetchResponse(responseData as Dispatcher.ResponseData);
14+
15+
expect(response.status).toBe(200);
16+
expect(response.headers.get("content-type")).toBe("application/json");
17+
await expect(response.text()).resolves.toBe('{"key":"value"}');
18+
});
19+
20+
test("handles empty headers", async () => {
21+
const responseData: Partial<Dispatcher.ResponseData> = {
22+
statusCode: 200,
23+
body: createBody(),
24+
};
25+
26+
const response = undiciResponseToFetchResponse(responseData as Dispatcher.ResponseData);
27+
28+
expect(response.headers).toEqual(new Headers());
29+
});
30+
31+
test("handles a header array", async () => {
32+
const responseData: Partial<Dispatcher.ResponseData> = {
33+
statusCode: 200,
34+
headers: {
35+
"x-multiple-values": ["value1", "value2"],
36+
},
37+
body: createBody(),
38+
};
39+
40+
const response = undiciResponseToFetchResponse(responseData as Dispatcher.ResponseData);
41+
42+
expect(response.headers.get("x-multiple-values")).toBe("value1, value2");
43+
});
44+
45+
test.each([204, 205, 304])("sets body to null for %i status code", async (statusCode) => {
46+
const responseData: Partial<Dispatcher.ResponseData> = {
47+
statusCode: statusCode,
48+
headers: { "content-type": "application/json" },
49+
body: createBody('{"key":"value"}'),
50+
};
51+
52+
const response = undiciResponseToFetchResponse(responseData as Dispatcher.ResponseData);
53+
54+
await expect(response.text()).resolves.toBe("");
55+
});
56+
57+
function createBody(str: string = ""): BodyReadable {
58+
return Readable.from(Buffer.from(str, "utf-8")) as Dispatcher.ResponseData["body"];
59+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Readable } from "stream";
2+
import { Dispatcher } from "undici";
3+
4+
/**
5+
* Converts an undici response to a fetch response.
6+
* This is necessary because node's fetch does not support disabling SSL validation (https://github.com/orgs/nodejs/discussions/44038).
7+
*
8+
* @param undiciResponse The undici response to convert.
9+
* @returns The fetch response.
10+
*/
11+
export function undiciResponseToFetchResponse(undiciResponse: Dispatcher.ResponseData): Response {
12+
const headers = new Headers();
13+
if (undiciResponse.headers) {
14+
for (const [key, value] of Object.entries(undiciResponse.headers)) {
15+
if (Array.isArray(value)) {
16+
for (const v of value) {
17+
headers.append(key, v);
18+
}
19+
} else if (value !== undefined) {
20+
headers.set(key, value);
21+
}
22+
}
23+
}
24+
25+
const status = undiciResponse.statusCode;
26+
const hasNullBody = status === 204 || status === 205 || status === 304;
27+
const responseBody = hasNullBody ? null : Readable.toWeb(undiciResponse.body);
28+
29+
return new Response(responseBody, { status, headers });
30+
}

0 commit comments

Comments
 (0)