diff --git a/packages/testcontainers/src/wait-strategies/http-wait-strategy.ts b/packages/testcontainers/src/wait-strategies/http-wait-strategy.ts index ed908007f..c3f12ecba 100644 --- a/packages/testcontainers/src/wait-strategies/http-wait-strategy.ts +++ b/packages/testcontainers/src/wait-strategies/http-wait-strategy.ts @@ -1,8 +1,9 @@ import Dockerode from "dockerode"; -import { Agent, Dispatcher, request } from "undici"; +import { Agent, request } from "undici"; import { IntervalRetry, log } from "../common"; import { getContainerRuntimeClient } from "../container-runtime"; import { BoundPorts } from "../utils/bound-ports"; +import { undiciResponseToFetchResponse } from "./utils/undici-response-parser"; import { AbstractWaitStrategy } from "./wait-strategy"; export interface HttpWaitStrategyOptions { @@ -95,7 +96,7 @@ export class HttpWaitStrategy extends AbstractWaitStrategy { } } - return this.undiciResponseToFetchResponse( + return undiciResponseToFetchResponse( await request(url, { method: this.method, signal: AbortSignal.timeout(this.readTimeoutMs), @@ -164,31 +165,6 @@ export class HttpWaitStrategy extends AbstractWaitStrategy { throw new Error(message); } - /** - * Converts an undici response to a fetch response. - * This is necessary because node's fetch does not support disabling SSL validation (https://github.com/orgs/nodejs/discussions/44038). - * - * @param undiciResponse The undici response to convert. - * @returns The fetch response. - */ - private undiciResponseToFetchResponse(undiciResponse: Dispatcher.ResponseData): Response { - const headers = new Headers(); - for (const [key, value] of Object.entries(undiciResponse.headers)) { - if (Array.isArray(value)) { - for (const v of value) { - headers.append(key, v); - } - } else if (value !== undefined) { - headers.set(key, value); - } - } - - return new Response(undiciResponse.body, { - status: undiciResponse.statusCode, - headers, - }); - } - private getAgent(): Agent | undefined { if (this._allowInsecure) { return new Agent({ diff --git a/packages/testcontainers/src/wait-strategies/utils/undici-response-parser.test.ts b/packages/testcontainers/src/wait-strategies/utils/undici-response-parser.test.ts new file mode 100644 index 000000000..1e07f3999 --- /dev/null +++ b/packages/testcontainers/src/wait-strategies/utils/undici-response-parser.test.ts @@ -0,0 +1,59 @@ +import { Readable } from "stream"; +import { Dispatcher } from "undici"; +import BodyReadable from "undici/types/readable"; +import { undiciResponseToFetchResponse } from "./undici-response-parser"; + +test("converts undici response to fetch response", async () => { + const responseData: Partial = { + statusCode: 200, + headers: { "content-type": "application/json" }, + body: createBody('{"key":"value"}'), + }; + + const response = undiciResponseToFetchResponse(responseData as Dispatcher.ResponseData); + + expect(response.status).toBe(200); + expect(response.headers.get("content-type")).toBe("application/json"); + await expect(response.text()).resolves.toBe('{"key":"value"}'); +}); + +test("handles empty headers", async () => { + const responseData: Partial = { + statusCode: 200, + body: createBody(), + }; + + const response = undiciResponseToFetchResponse(responseData as Dispatcher.ResponseData); + + expect(response.headers).toEqual(new Headers()); +}); + +test("handles a header array", async () => { + const responseData: Partial = { + statusCode: 200, + headers: { + "x-multiple-values": ["value1", "value2"], + }, + body: createBody(), + }; + + const response = undiciResponseToFetchResponse(responseData as Dispatcher.ResponseData); + + expect(response.headers.get("x-multiple-values")).toBe("value1, value2"); +}); + +test.each([204, 205, 304])("sets body to null for %i status code", async (statusCode) => { + const responseData: Partial = { + statusCode: statusCode, + headers: { "content-type": "application/json" }, + body: createBody('{"key":"value"}'), + }; + + const response = undiciResponseToFetchResponse(responseData as Dispatcher.ResponseData); + + await expect(response.text()).resolves.toBe(""); +}); + +function createBody(str: string = ""): BodyReadable { + return Readable.from(Buffer.from(str, "utf-8")) as Dispatcher.ResponseData["body"]; +} diff --git a/packages/testcontainers/src/wait-strategies/utils/undici-response-parser.ts b/packages/testcontainers/src/wait-strategies/utils/undici-response-parser.ts new file mode 100644 index 000000000..2a0d91179 --- /dev/null +++ b/packages/testcontainers/src/wait-strategies/utils/undici-response-parser.ts @@ -0,0 +1,30 @@ +import { Readable } from "stream"; +import { Dispatcher } from "undici"; + +/** + * Converts an undici response to a fetch response. + * This is necessary because node's fetch does not support disabling SSL validation (https://github.com/orgs/nodejs/discussions/44038). + * + * @param undiciResponse The undici response to convert. + * @returns The fetch response. + */ +export function undiciResponseToFetchResponse(undiciResponse: Dispatcher.ResponseData): Response { + const headers = new Headers(); + if (undiciResponse.headers) { + for (const [key, value] of Object.entries(undiciResponse.headers)) { + if (Array.isArray(value)) { + for (const v of value) { + headers.append(key, v); + } + } else if (value !== undefined) { + headers.set(key, value); + } + } + } + + const status = undiciResponse.statusCode; + const hasNullBody = status === 204 || status === 205 || status === 304; + const responseBody = hasNullBody ? null : Readable.toWeb(undiciResponse.body); + + return new Response(responseBody, { status, headers }); +}