diff --git a/property_tests/arbitraries/http/request_arb.ts b/property_tests/arbitraries/http/request_arb.ts index 0120f5aac2..367ae5fb5a 100644 --- a/property_tests/arbitraries/http/request_arb.ts +++ b/property_tests/arbitraries/http/request_arb.ts @@ -23,7 +23,7 @@ const RequestMethodArb = fc.constantFrom( 'PUT', 'DELETE', 'OPTIONS', - 'TRACE', + // 'TRACE', 'PATCH' ); @@ -65,15 +65,23 @@ function HttpRequestValueArb() { return optCertVer === null ? None : Some(optCertVer); }) ) - .map(([method, url, headers, body, certificate_version]) => { - return { + .map( + ([ method, url, headers, body, certificate_version - }; - }); + ]): HttpRequest => { + return { + method, + url, + headers, + body, + certificate_version + }; + } + ); } export function HttpRequestArb(): fc.Arbitrary< diff --git a/property_tests/tests/canister_methods/http_request/test/fletch.ts b/property_tests/tests/canister_methods/http_request/test/fletch.ts index 323575bc6e..4eeb5813c3 100644 --- a/property_tests/tests/canister_methods/http_request/test/fletch.ts +++ b/property_tests/tests/canister_methods/http_request/test/fletch.ts @@ -1,56 +1,40 @@ -import { execSync } from 'child_process'; import { HttpRequest } from 'azle'; import { getCanisterId } from 'azle/test'; +import * as dns from 'node:dns'; +dns.setDefaultResultOrder('ipv4first'); + +type HttpResponse = { + status: number; + headers: string[][]; + body: string; +}; /** - * A synchronous "fetch" for canisters. + * An asynchronous "fetch" for canisters. */ -export function fletch(canisterName: string, options: HttpRequest) { +export async function fletch( + canisterName: string, + request: HttpRequest +): Promise { const canisterId = getCanisterId(canisterName); - const requestHeaders = toCurlHeadersString(options.headers); - - const curlCommand = `curl\ - --silent\ - --include\ - -X ${options.method}\ - ${requestHeaders}\ - --data "${options.body.join(',')}"\ - "${canisterId}.localhost:8000${options.url}" \ - --resolve "${canisterId}.localhost:8000:127.0.0.1"`; + const { method, body, headers, url: path } = request; - const responseBuffer = execSync(curlCommand); - - return toResponse(responseBuffer); -} + const url = `http://${canisterId}.localhost:8000${path}`; -function toCurlHeadersString(headers: [string, string][]) { - return headers - .map(([name, value]) => `-H ${singleQuotedString(`${name}: ${value}`)}`) - .join(' '); -} - -function singleQuotedString(input: string) { - const singleQuoteEscapedString = input.replace(/'/g, "'\\''"); + const fetchOptions = { + method, + headers, + body: method === 'GET' ? undefined : body + }; - return `'${singleQuoteEscapedString}'`; + return await toResponse(await fetch(url, fetchOptions)); } -function toResponse(buffer: Buffer) { - const responseString = new TextDecoder().decode(buffer); - - const [statusCodeAndHeaders, body] = responseString.split('\r\n\r\n'); - - const [statusCodeString, ...responseHeaderStrings] = - statusCodeAndHeaders.split('\r\n'); - - const status = Number(statusCodeString.split(' ')[1]); +async function toResponse(response: Response): Promise { + const headers: [string, string][] = Array.from(response.headers.entries()); + const status = response.status; + const body = await response.text(); - const headers = responseHeaderStrings.map((header) => header.split(': ')); - - return { - status, - headers, - body - }; + return { status, headers, body }; } diff --git a/property_tests/tests/canister_methods/http_request/test/generate_tests.ts b/property_tests/tests/canister_methods/http_request/test/generate_tests.ts index b98f7277f0..b49d79eb40 100644 --- a/property_tests/tests/canister_methods/http_request/test/generate_tests.ts +++ b/property_tests/tests/canister_methods/http_request/test/generate_tests.ts @@ -22,7 +22,7 @@ export function generateTests( { name: functionName, test: async () => { - const response = fletch('canister', request); + const response = await fletch('canister', request); const filteredHeaders = response.headers .filter( ([name]) => @@ -36,12 +36,11 @@ export function generateTests( headers: filteredHeaders, body: response.body }; - const filteredExpectedHeaders = + const sortedExpectedHeaders = expectedResponse.headers.sort(); const processedExpectedResponse = { - status: expectedResponse.status, - headers: filteredExpectedHeaders, - body: expectedResponse.body + ...expectedResponse, + headers: sortedExpectedHeaders }; const valuesAreEqual = deepEqual( processedResponse, diff --git a/property_tests/tests/canister_methods/http_request_update/test/fletch.ts b/property_tests/tests/canister_methods/http_request_update/test/fletch.ts index 323575bc6e..4eeb5813c3 100644 --- a/property_tests/tests/canister_methods/http_request_update/test/fletch.ts +++ b/property_tests/tests/canister_methods/http_request_update/test/fletch.ts @@ -1,56 +1,40 @@ -import { execSync } from 'child_process'; import { HttpRequest } from 'azle'; import { getCanisterId } from 'azle/test'; +import * as dns from 'node:dns'; +dns.setDefaultResultOrder('ipv4first'); + +type HttpResponse = { + status: number; + headers: string[][]; + body: string; +}; /** - * A synchronous "fetch" for canisters. + * An asynchronous "fetch" for canisters. */ -export function fletch(canisterName: string, options: HttpRequest) { +export async function fletch( + canisterName: string, + request: HttpRequest +): Promise { const canisterId = getCanisterId(canisterName); - const requestHeaders = toCurlHeadersString(options.headers); - - const curlCommand = `curl\ - --silent\ - --include\ - -X ${options.method}\ - ${requestHeaders}\ - --data "${options.body.join(',')}"\ - "${canisterId}.localhost:8000${options.url}" \ - --resolve "${canisterId}.localhost:8000:127.0.0.1"`; + const { method, body, headers, url: path } = request; - const responseBuffer = execSync(curlCommand); - - return toResponse(responseBuffer); -} + const url = `http://${canisterId}.localhost:8000${path}`; -function toCurlHeadersString(headers: [string, string][]) { - return headers - .map(([name, value]) => `-H ${singleQuotedString(`${name}: ${value}`)}`) - .join(' '); -} - -function singleQuotedString(input: string) { - const singleQuoteEscapedString = input.replace(/'/g, "'\\''"); + const fetchOptions = { + method, + headers, + body: method === 'GET' ? undefined : body + }; - return `'${singleQuoteEscapedString}'`; + return await toResponse(await fetch(url, fetchOptions)); } -function toResponse(buffer: Buffer) { - const responseString = new TextDecoder().decode(buffer); - - const [statusCodeAndHeaders, body] = responseString.split('\r\n\r\n'); - - const [statusCodeString, ...responseHeaderStrings] = - statusCodeAndHeaders.split('\r\n'); - - const status = Number(statusCodeString.split(' ')[1]); +async function toResponse(response: Response): Promise { + const headers: [string, string][] = Array.from(response.headers.entries()); + const status = response.status; + const body = await response.text(); - const headers = responseHeaderStrings.map((header) => header.split(': ')); - - return { - status, - headers, - body - }; + return { status, headers, body }; } diff --git a/property_tests/tests/canister_methods/http_request_update/test/generate_body.ts b/property_tests/tests/canister_methods/http_request_update/test/generate_body.ts index 4ea22e06e2..f5634934d4 100644 --- a/property_tests/tests/canister_methods/http_request_update/test/generate_body.ts +++ b/property_tests/tests/canister_methods/http_request_update/test/generate_body.ts @@ -67,10 +67,12 @@ function generateHeadersMap( } function generateHeaderChecks(headers: [string, string][]) { - return headers - .filter(([_, value]) => value !== '') // An empty header value is the same as a none existent header - .map( - ([name, value]) => ` + return ( + headers + .filter(([_, value]) => value !== '') // An empty header value is the same as a none existent header + // TODO wouldn't it be great instead of throwing away that test to actually verify that any empty values are undefined? + .map( + ([name, value]) => ` if (headers['${escape(name).toLowerCase()}'] !== '${escape( value )}') { @@ -85,8 +87,9 @@ function generateHeaderChecks(headers: [string, string][]) { ); } ` - ) - .join('\n'); + ) + .join('\n') + ); } function escape(input: string) { diff --git a/property_tests/tests/canister_methods/http_request_update/test/generate_tests.ts b/property_tests/tests/canister_methods/http_request_update/test/generate_tests.ts index acebd69a3b..6984c6d439 100644 --- a/property_tests/tests/canister_methods/http_request_update/test/generate_tests.ts +++ b/property_tests/tests/canister_methods/http_request_update/test/generate_tests.ts @@ -34,21 +34,30 @@ export function generateTests( { name: functionName, test: async () => { - const response = fletch('canister', request); - const filteredHeaders = response.headers.filter( - ([name]) => - name !== 'x-ic-streaming-response' && - name !== 'content-length' && - name !== 'date' - ); + const response = await fletch('canister', request); + + const filteredHeaders = response.headers + .filter( + ([name]) => + name !== 'x-ic-streaming-response' && + name !== 'content-length' && + name !== 'date' + ) + .sort(); + const sortedExpectedHeaders = + expectedResponse.headers.sort(); const processedResponse = { status: response.status, headers: filteredHeaders, body: response.body }; + const processedExpectedResponse = { + ...expectedResponse, + headers: sortedExpectedHeaders + }; const valuesAreEqual = deepEqual( processedResponse, - expectedResponse + processedExpectedResponse ); return { Ok: valuesAreEqual };