Skip to main content
Glama

Convex MCP server

Official
by get-convex
fetch.ts58.2 kB
// The initial set of tests were taken from Deno fetch unit test suite // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // https://github.com/denoland/deno/blob/main/LICENSE.md import { wrapInTests } from "./js_builtins/testHelpers"; import { assert, expect } from "chai"; import { action, ActionCtx, query } from "./_generated/server"; import { api } from "./_generated/api"; export const fromQuery = query(async () => { await fetch("http://localhost:4545/assets/fixture.json"); }); export const checkForAbort = query({ args: {}, handler: async (ctx) => { const abort = await ctx.db.query("triggerAbort" as any).first(); return abort !== null; }, }); async function fetchAbortTest(ctx: ActionCtx) { const controller = new AbortController(); void (async () => { while (!(await ctx.runQuery(api.fetch.checkForAbort))) { await new Promise((resolve) => setTimeout(resolve, 10)); } controller.abort(); })(); const responseFetch = fetch("http://localhost:4548/pause", { method: "GET", signal: controller.signal, }); await expect(responseFetch).to.be.rejectedWith("AbortError"); } export const fetchAbort = action(async (ctx) => { return await wrapInTests({ fetchAbortTest: () => fetchAbortTest(ctx), }); }); export default action(async () => { return await wrapInTests({ fetchConstructorClones, fetchRequiresOneArgument, fetchProtocolError, fetchDnsError, fetchInvalidUriError, fetchMalformedUriError, fetchJson, fetchURL, fetchHeaders, fetchBlob, fetchBodyUsedReader, // This does not currently pass because we're not tracking this state between // the Response and the ReadableStream // fetchBodyUsedCancelStream, fetchAsyncIterator, fetchBodyReader, fetchBodyReaderBigBody, fetchBodyReaderMultiPartBody, responseClone, fetchMultipartFormDataSuccess, fetchMultipartFormBadContentType, fetchURLEncodedFormDataSuccess, fetchInitFormDataBinaryFileBody, fetchInitFormDataMultipleFilesBody, fetchWithRedirection, // fetchWithRelativeRedirection, fetchRedirectPostToGet, fetchWithRelativeRedirectionUrl, fetchWithInfRedirection, fetchCrossOriginRedirectStripsAuthorization, fetchRedirectBody, // fetchRedirectBodyStream, fetchInitStringBody, fetchRequestInitStringBody, fetchSeparateInit, fetchInitTypedArrayBody, fetchInitArrayBufferBody, fetchInitURLSearchParamsBody, fetchInitBlobBody, fetchInitFormDataBody, fetchInitFormDataBlobFilenameBody, fetchInitFormDataTextFileBody, fetchUserAgent, // fetchRequest, // fetchRequestAcceptHeaders, // fetchPostBodyString, // fetchPostBodyTypedArray, // fetchUserSetContentLength, // fetchUserSetTransferEncoding, // fetchWithNonAsciiRedirection, fetchWithManualRedirection, fetchWithErrorRedirection, responseRedirect, responseRedirectTakeURLObjectAsParameter, responseWithoutBody, fetchBodyReadTwice, fetchBodyReaderAfterRead, fetchBodyReaderWithCancelAndNewReader, fetchBodyReaderWithReadCancelAndNewReader, // fetchResourceCloseAfterStreamCancel, // fetchNullBodyStatus, fetchResponseContentLength, // fetchResponseConstructorNullBody, // fetchResponseConstructorInvalidStatus, // fetchResponseEmptyConstructor, // fetchCustomHttpClientParamCertificateSuccess, // fetchPostBodyReadableStream, // fetchWritableRespProps, // fetchFilterOutCustomHostHeader // fetchHeadRespBody, // fetchClientCertWrongPrivateKey, // fetchClientCertBadPrivateKey, // fetchClientCertNotPrivateKey, // fetchCustomClientPrivateKey, // fetchAbortWhileUploadStreaming, // fetchAbortWhileUploadStreamingWithReason, // fetchAbortWhileUploadStreamingWithPrimitiveReason, // fetchHeaderValueShouldNotPanic, // fetchHeaderNameShouldNotPanic, // fetchSupportsHttp1Only, // fetchSupportsHttp2, // fetchPrefersHttp2, // fetchFilePerm, // fetchFilePermDoesNotExist, // fetchFileBadMethod, // fetchFileDoesNotExist, // fetchFile, // fetchContentLengthPost, // fetchContentLengthPut, // fetchContentLengthPatch, // fetchContentLengthPostWithStringBody, // fetchContentLengthPostWithBufferBody, // staticResponseJson, // fetchWithInvalidContentLengthAndTransferEncoding, // fetchWithInvalidContentLength, // fetchWithInvalidContentLength2, // fetchBlobUrl, fetchResponseStreamIsLockedWhileReading, fetchResponseStreamIsLockedWhileReadingBlob, fetchForbidden, fetchOlaf, fetchBodyTextDecoderStream, }); }); async function fetchRequiresOneArgument() { await expect( // @ts-expect-error intentionally pass no arguments fetch(), ).to.be.rejectedWith(TypeError, "Request URL is undefined"); } async function fetchProtocolError() { await expect(fetch("ftp://localhost:21/a/file")).to.be.rejectedWith( TypeError, "Unsupported URL scheme", ); } async function fetchDnsError() { await expect(fetch("http://invalid/")).to.be.rejectedWith( /dns error: failed to lookup address information/, ); } async function fetchInvalidUriError() { await expect(fetch("http://<invalid>/")).to.be.rejectedWith(/Invalid URL/); } async function fetchMalformedUriError() { const url = new URL("http://{{google/"); await expect(fetch(url)).to.be.rejectedWith(/Parsed Url is not a valid Uri/); } async function fetchJson() { const response = await fetch("http://localhost:4545/assets/fixture.json"); assert.strictEqual(response.ok, true); assert.strictEqual(response.status, 200); assert.strictEqual(response.statusText, "OK"); assert.strictEqual(response.type, "basic"); const json = await response.json(); assert.strictEqual(json.name, "convex"); } async function fetchURL() { const response = await fetch( new URL("http://localhost:4545/assets/fixture.json"), ); assert.strictEqual(response.url, "http://localhost:4545/assets/fixture.json"); const _json = await response.json(); } async function fetchHeaders() { const response = await fetch("http://localhost:4545/assets/fixture.json"); const headers = response.headers; assert.strictEqual(headers.get("Content-Type"), "application/json"); const _json = await response.json(); } async function fetchBlob() { const response = await fetch("http://localhost:4545/assets/fixture.json"); const headers = response.headers; const blob = await response.blob(); assert.strictEqual(blob.type, headers.get("Content-Type")); assert.strictEqual(blob.size, Number(headers.get("Content-Length"))); assert.strictEqual(await blob.text(), '{"name":"convex"}'); } async function fetchBodyUsedReader() { const response = await fetch("http://localhost:4545/assets/fixture.json"); assert(response.body !== null); const reader = response.body!.getReader(); // Getting a reader should lock the stream but does not consume the body // so bodyUsed should not be true assert.strictEqual(response.bodyUsed, false); reader.releaseLock(); await response.json(); assert.strictEqual(response.bodyUsed, true); } // async function fetchBodyUsedCancelStream() { // const response = await fetch("http://localhost:4545/assets/fixture.json"); // assert(response.body !== null); // assert.strictEqual(response.bodyUsed, false); // const promise = response.body.cancel(); // assert.strictEqual(response.bodyUsed, true); // await promise; // } async function fetchAsyncIterator() { const response = await fetch("http://localhost:4545/assets/fixture.json"); const headers = response.headers; assert(response.body !== null); let total = 0; // @ts-expect-error Type 'ReadableStream<Uint8Array>' must have a '[Symbol.asyncIterator]()' method that returns an async iterator. for await (const chunk of response.body) { assert(chunk instanceof Uint8Array); total += chunk.length; } assert.strictEqual(total, Number(headers.get("Content-Length"))); } async function fetchBodyReader() { const response = await fetch("http://localhost:4545/assets/fixture.json"); const headers = response.headers; assert(response.body !== null); const reader = response.body!.getReader(); let total = 0; // eslint-disable-next-line no-constant-condition while (true) { const { done, value } = await reader.read(); if (done) break; assert(value); assert(value instanceof Uint8Array); total += value.length; } assert.strictEqual(total, Number(headers.get("Content-Length"))); } async function fetchBodyReaderBigBody() { const data = "a".repeat(10 << 10); // 10mb const response = await fetch("http://localhost:4545/echo_server", { method: "POST", body: data, }); assert(response.body !== null); const reader = await response.body.getReader(); let total = 0; // eslint-disable-next-line no-constant-condition while (true) { const { done, value } = await reader.read(); if (done) break; assert(value); total += value.length; } assert.strictEqual(total, data.length); } // Covers streaming fetch, because /echo_server returns the request stream // as the response stream. async function fetchBodyReaderMultiPartBody() { const encoder = new TextEncoder(); const decoder = new TextDecoder(); const { readable, writable } = new TransformStream(); const writer = writable.getWriter(); const response = await fetch("http://localhost:4545/echo_server", { method: "POST", body: readable, }); // Note we're doing full-duplex streaming, with the response status coming // back before the request body has started sending. assert(response.ok); assert(response.body !== null); const reader = response.body.getReader(); await writer.write(encoder.encode("Hello ")); const chunk1 = await reader.read(); assert.strictEqual(decoder.decode(chunk1.value), "Hello "); assert.strictEqual(chunk1.done, false); await writer.write(encoder.encode("World!")); const chunk2 = await reader.read(); assert.strictEqual(decoder.decode(chunk2.value), "World!"); assert.strictEqual(chunk2.done, false); await writer.close(); const chunk3 = await reader.read(); assert.strictEqual(chunk3.value, undefined); assert.strictEqual(chunk3.done, true); } async function responseClone() { const response = await fetch("http://localhost:4545/assets/fixture.json"); const response1 = response.clone(); assert(response !== response1); assert.strictEqual(response.status, response1.status); assert.strictEqual(response.statusText, response1.statusText); const body = await response.arrayBuffer(); const body1 = await response1.arrayBuffer(); assert.deepEqual(body, body1); } async function fetchMultipartFormDataSuccess() { const response = await fetch("http://localhost:4545/multipart_form_data.txt"); const formData = await response.formData(); assert(formData.has("field_1")); assert.strictEqual(formData.get("field_1")!.toString(), "value_1 \r\n"); assert(formData.has("field_2")); const file = formData.get("field_2") as File; assert.strictEqual(file.name, "file.js"); assert.strictEqual(await file.text(), `console.log("Hi")`); } async function fetchMultipartFormBadContentType() { const response = await fetch( "http://localhost:4545/multipart_form_bad_content_type", ); // assert(response.body !== null); await expect(response.formData()).to.be.rejectedWith( TypeError, "Body cannot be decoded as form data", ); } async function fetchURLEncodedFormDataSuccess() { const response = await fetch( "http://localhost:4545/subdir/form_urlencoded.txt", ); const formData = await response.formData(); assert(formData.has("field_1")); assert.strictEqual(formData.get("field_1")!.toString(), "Hi"); assert(formData.has("field_2")); assert.strictEqual(formData.get("field_2")!.toString(), "<Convex>"); } async function fetchInitFormDataBinaryFileBody() { // Some random bytes const binaryFile = new Uint8Array([ 108, 2, 0, 0, 145, 22, 162, 61, 157, 227, 166, 77, 138, 75, 180, 56, 119, 188, 177, 183, ]); const response = await fetch("http://localhost:4545/echo_multipart_file", { method: "POST", body: binaryFile, }); const resultForm = await response.formData(); const resultFile = resultForm.get("file") as File; assert.strictEqual(resultFile.type, "application/octet-stream"); assert.strictEqual(resultFile.name, "file.bin"); assert.deepEqual(new Uint8Array(await resultFile.arrayBuffer()), binaryFile); } async function fetchInitFormDataMultipleFilesBody() { const files = [ { content: new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10, 137, 1, 25]), type: "image/png", name: "image", fileName: "some-image.png", }, { content: new Uint8Array([ 108, 2, 0, 0, 145, 22, 162, 61, 157, 227, 166, 77, 138, 75, 180, 56, 119, 188, 177, 183, ]), name: "file", fileName: "file.bin", expectedType: "application/octet-stream", }, { content: new TextEncoder().encode("deno land"), type: "text/plain", name: "text", fileName: "deno.txt", }, ]; const form = new FormData(); form.append("field", "value"); for (const file of files) { form.append( file.name, new Blob([file.content], { type: file.type }), file.fileName, ); } const response = await fetch("http://localhost:4545/echo_server", { method: "POST", body: form, }); const resultForm = await response.formData(); assert.strictEqual(form.get("field"), resultForm.get("field")); for (const file of files) { const inputFile = form.get(file.name) as File; const resultFile = resultForm.get(file.name) as File; assert.strictEqual(inputFile.size, resultFile.size); assert.strictEqual(inputFile.name, resultFile.name); assert.strictEqual(file.expectedType || file.type, resultFile.type); assert.deepEqual( new Uint8Array(await resultFile.arrayBuffer()), file.content, ); } } async function fetchWithRedirection() { const response = await fetch("http://localhost:4545/assets/hello.txt"); assert.strictEqual(response.status, 200); assert.strictEqual(response.statusText, "OK"); assert.strictEqual(response.url, "http://localhost:4545/assets/fixture.json"); const body = await response.text(); assert.strictEqual(body, '{"name":"convex"}'); } // async function fetchWithRelativeRedirection() { // const response = await fetch("http://localhost:4545/run/001_hello.js"); // assert.strictEqual(response.status, 200); // assert.strictEqual(response.statusText, "OK"); // const body = await response.text(); // assert(body.includes("Hello")); // } async function fetchCrossOriginRedirectStripsAuthorization() { const headers = { authorization: "Bearer convex-admin", "x-location": "http://localhost:4547/print_auth", }; const directResponse = await fetch("http://localhost:4547/print_auth", { headers, }); assert.strictEqual(directResponse.status, 200); const directBody = await directResponse.text(); assert.strictEqual(directBody, '{"auth":"Bearer convex-admin"}'); const response = await fetch("http://localhost:4545/assets/hello.txt", { headers, }); assert.strictEqual(response.status, 200); assert.strictEqual(response.url, "http://localhost:4547/print_auth"); const body = await response.text(); assert.strictEqual(body, '{"auth":"None"}'); } async function fetchRedirectBody() { const response = await fetch("http://localhost:4545/redirect_body", { method: "POST", body: "Should be forwarded", }); assert.strictEqual(response.status, 200); assert.strictEqual(response.url, "http://localhost:4545/echo_server"); const body = await response.text(); assert.strictEqual(body, "Should be forwarded"); } // // This test should pass, but it flakes very infrequently, for reasons that // // look like a race in the hyper library? // // When it flakes, the error is: // expected promise to be rejected with 'TypeError' but it was rejected with // 'Error: error sending request for url (http://localhost:4545/redirect_body): // error writing a body to connection: Broken pipe (os error 32)' // // async function fetchRedirectBodyStream() { // const stream = new Blob(["Should not be forwarded"]).stream(); // await expect( // fetch("http://localhost:4545/redirect_body", { // method: "POST", // body: stream, // }) // ).to.be.rejectedWith(TypeError, "fetch cannot redirect with streamed body"); // } async function fetchRedirectPostToGet() { const response = await fetch("http://localhost:4545/post_redirect_to_get", { method: "POST", headers: { "x-location": "/assets/fixture.json" }, }); assert.strictEqual(response.status, 200); assert.strictEqual(response.url, "http://localhost:4545/assets/fixture.json"); const body = await response.text(); assert(body.includes('{"name":"convex"}')); } async function fetchWithRelativeRedirectionUrl() { const cases = [ ["end", "http://localhost:4545/a/b/end"], ["/end", "http://localhost:4545/end"], ]; for (const [loc, redUrl] of cases) { const response = await fetch("http://localhost:4545/a/b/c", { headers: new Headers([["x-location", loc]]), }); assert.strictEqual(response.url, redUrl); assert.strictEqual(response.redirected, true); assert.strictEqual(response.status, 404); assert.strictEqual(await response.text(), ""); } } async function fetchWithInfRedirection() { await expect( fetch("http://localhost:4545/assets/hello.txt", { headers: { "x-location": "/assets/hello.txt" }, }), ).to.be.rejectedWith(TypeError, "redirect"); } async function fetchInitStringBody() { const data = "Hello World"; const response = await fetch("http://localhost:4545/echo_server", { method: "POST", body: data, }); const text = await response.text(); assert.strictEqual(text, data); assert(response.headers.get("content-type")!.startsWith("text/plain")); } async function fetchRequestInitStringBody() { const data = "Hello World"; const req = new Request("http://localhost:4545/echo_server", { method: "POST", body: data, }); const response = await fetch(req); const text = await response.text(); assert.strictEqual(text, data); } async function fetchSeparateInit() { // related to: https://github.com/denoland/deno/issues/10396 const req = new Request("http://localhost:4545/assets/fixture.json"); const init = { method: "GET", }; req.headers.set("foo", "bar"); const res = await fetch(req, init); assert.strictEqual(res.status, 200); await res.text(); } async function fetchInitTypedArrayBody() { const data = "Hello World"; const response = await fetch("http://localhost:4545/echo_server", { method: "POST", body: new TextEncoder().encode(data), }); const text = await response.text(); assert.strictEqual(text, data); } async function fetchInitArrayBufferBody() { const data = "Hello World"; const response = await fetch("http://localhost:4545/echo_server", { method: "POST", body: new TextEncoder().encode(data).buffer, }); const text = await response.text(); assert.strictEqual(text, data); } async function fetchInitURLSearchParamsBody() { const data = "param1=value1&param2=value2"; const params = new URLSearchParams(data); const response = await fetch("http://localhost:4545/echo_server", { method: "POST", body: params, }); const text = await response.text(); assert.strictEqual(text, data); assert( response.headers .get("content-type")! .startsWith("application/x-www-form-urlencoded"), ); } async function fetchInitBlobBody() { const data = "const a = 1"; const blob = new Blob([data], { type: "text/javascript", }); const response = await fetch("http://localhost:4545/echo_server", { method: "POST", body: blob, }); const text = await response.text(); assert.strictEqual(text, data); assert(response.headers.get("content-type")!.startsWith("text/javascript")); } async function fetchInitFormDataBody() { const form = new FormData(); form.append("field", "value"); const response = await fetch("http://localhost:4545/echo_server", { method: "POST", body: form, }); const resultForm = await response.formData(); assert.strictEqual(form.get("field"), resultForm.get("field")); } async function fetchInitFormDataBlobFilenameBody() { const form = new FormData(); form.append("field", "value"); form.append("file", new Blob([new TextEncoder().encode("convex")])); const response = await fetch("http://localhost:4545/echo_server", { method: "POST", body: form, }); const resultForm = await response.formData(); assert.strictEqual(form.get("field"), resultForm.get("field")); const file = resultForm.get("file"); assert(file instanceof File); assert.strictEqual(file.name, "blob"); } async function fetchInitFormDataTextFileBody() { const fileContent = "friends of convex"; const form = new FormData(); form.append("field", "value"); form.append( "file", new Blob([new TextEncoder().encode(fileContent)], { type: "text/plain", }), "convex.txt", ); const response = await fetch("http://localhost:4545/echo_server", { method: "POST", body: form, }); const resultForm = await response.formData(); assert.strictEqual(form.get("field"), resultForm.get("field")); const file = form.get("file") as File; const resultFile = resultForm.get("file") as File; assert.strictEqual(file.size, resultFile.size); assert.strictEqual(file.name, resultFile.name); assert.strictEqual(file.type, resultFile.type); assert.strictEqual(await file.text(), await resultFile.text()); } async function fetchUserAgent() { const data = "Hello World"; const response = await fetch("http://localhost:4545/echo_server", { method: "POST", body: new TextEncoder().encode(data), }); assert.equal(response.headers.get("user-agent"), `Convex/1.0`); await response.text(); } // function bufferServer(addr: string): Promise<Buffer> { // const [hostname, port] = addr.split(":"); // const listener = Deno.listen({ // hostname, // port: Number(port), // }) as Deno.Listener; // return listener.accept().then(async (conn: Deno.Conn) => { // const buf = new Buffer(); // const p1 = buf.readFrom(conn); // const p2 = conn.write( // new TextEncoder().encode( // "HTTP/1.0 404 Not Found\r\nContent-Length: 2\r\n\r\nNF" // ) // ); // // Wait for both an EOF on the read side of the socket and for the write to // // complete before closing it. Due to keep-alive, the EOF won't be sent // // until the Connection close (HTTP/1.0) response, so readFrom() can't // // proceed write. Conversely, if readFrom() is async, waiting for the // // write() to complete is not a guarantee that we've read the incoming // // request. // await Promise.all([p1, p2]); // conn.close(); // listener.close(); // return buf; // }); // } // async function fetchRequest() { // const addr = "127.0.0.1:4501"; // const bufPromise = bufferServer(addr); // const response = await fetch(`http://${addr}/blah`, { // method: "POST", // headers: [ // ["Hello", "World"], // ["Foo", "Bar"], // ], // }); // await response.arrayBuffer(); // assertEquals(response.status, 404); // assertEquals(response.headers.get("Content-Length"), "2"); // const actual = new TextDecoder().decode((await bufPromise).bytes()); // const expected = [ // "POST /blah HTTP/1.1\r\n", // "content-length: 0\r\n", // "hello: World\r\n", // "foo: Bar\r\n", // "accept: */*\r\n", // "accept-language: *\r\n", // `user-agent: Deno/${Deno.version.deno}\r\n`, // "accept-encoding: gzip, br\r\n", // `host: ${addr}\r\n\r\n`, // ].join(""); // assertEquals(actual, expected); // } // async function fetchRequestAcceptHeaders() { // const addr = "127.0.0.1:4501"; // const bufPromise = bufferServer(addr); // const response = await fetch(`http://${addr}/blah`, { // method: "POST", // headers: [ // ["Accept", "text/html"], // ["Accept-Language", "en-US"], // ], // }); // await response.arrayBuffer(); // assertEquals(response.status, 404); // assertEquals(response.headers.get("Content-Length"), "2"); // const actual = new TextDecoder().decode((await bufPromise).bytes()); // const expected = [ // "POST /blah HTTP/1.1\r\n", // "content-length: 0\r\n", // "accept: text/html\r\n", // "accept-language: en-US\r\n", // `user-agent: Deno/${Deno.version.deno}\r\n`, // "accept-encoding: gzip, br\r\n", // `host: ${addr}\r\n\r\n`, // ].join(""); // assertEquals(actual, expected); // } // async function fetchPostBodyString() { // const addr = "127.0.0.1:4511"; // const bufPromise = bufferServer(addr); // const body = "hello world"; // const response = await fetch(`http://${addr}/blah`, { // method: "POST", // headers: [ // ["Hello", "World"], // ["Foo", "Bar"], // ], // body, // }); // await response.arrayBuffer(); // assertEquals(response.status, 404); // assertEquals(response.headers.get("Content-Length"), "2"); // const actual = new TextDecoder().decode((await bufPromise).bytes()); // const expected = [ // "POST /blah HTTP/1.1\r\n", // "hello: World\r\n", // "foo: Bar\r\n", // "content-type: text/plain;charset=UTF-8\r\n", // "accept: */*\r\n", // "accept-language: *\r\n", // `user-agent: Deno/${Deno.version.deno}\r\n`, // "accept-encoding: gzip, br\r\n", // `host: ${addr}\r\n`, // `content-length: ${body.length}\r\n\r\n`, // body, // ].join(""); // assertEquals(actual, expected); // } // async function fetchPostBodyTypedArray() { // const addr = "127.0.0.1:4503"; // const bufPromise = bufferServer(addr); // const bodyStr = "hello world"; // const body = new TextEncoder().encode(bodyStr); // const response = await fetch(`http://${addr}/blah`, { // method: "POST", // headers: [ // ["Hello", "World"], // ["Foo", "Bar"], // ], // body, // }); // await response.arrayBuffer(); // assertEquals(response.status, 404); // assertEquals(response.headers.get("Content-Length"), "2"); // const actual = new TextDecoder().decode((await bufPromise).bytes()); // const expected = [ // "POST /blah HTTP/1.1\r\n", // "hello: World\r\n", // "foo: Bar\r\n", // "accept: */*\r\n", // "accept-language: *\r\n", // `user-agent: Deno/${Deno.version.deno}\r\n`, // "accept-encoding: gzip, br\r\n", // `host: ${addr}\r\n`, // `content-length: ${body.byteLength}\r\n\r\n`, // bodyStr, // ].join(""); // assertEquals(actual, expected); // } // async function fetchUserSetContentLength() { // const addr = "127.0.0.1:4501"; // const bufPromise = bufferServer(addr); // const response = await fetch(`http://${addr}/blah`, { // method: "POST", // headers: [["Content-Length", "10"]], // }); // await response.arrayBuffer(); // assertEquals(response.status, 404); // assertEquals(response.headers.get("Content-Length"), "2"); // const actual = new TextDecoder().decode((await bufPromise).bytes()); // const expected = [ // "POST /blah HTTP/1.1\r\n", // "content-length: 0\r\n", // "accept: */*\r\n", // "accept-language: *\r\n", // `user-agent: Deno/${Deno.version.deno}\r\n`, // "accept-encoding: gzip, br\r\n", // `host: ${addr}\r\n\r\n`, // ].join(""); // assertEquals(actual, expected); // } // async function fetchUserSetTransferEncoding() { // const addr = "127.0.0.1:4501"; // const bufPromise = bufferServer(addr); // const response = await fetch(`http://${addr}/blah`, { // method: "POST", // headers: [["Transfer-Encoding", "chunked"]], // }); // await response.arrayBuffer(); // assertEquals(response.status, 404); // assertEquals(response.headers.get("Content-Length"), "2"); // const actual = new TextDecoder().decode((await bufPromise).bytes()); // const expected = [ // "POST /blah HTTP/1.1\r\n", // "content-length: 0\r\n", // `host: ${addr}\r\n`, // "accept: */*\r\n", // "accept-language: *\r\n", // `user-agent: Deno/${Deno.version.deno}\r\n`, // "accept-encoding: gzip, br\r\n\r\n", // ].join(""); // assertEquals(actual, expected); // } // async function fetchWithNonAsciiRedirection() { // const response = await fetch("http://localhost:4545/non_ascii_redirect", { // redirect: "manual", // }); // assert.strictEqual(response.status, 301); // assert.strictEqual(response.headers.get("location"), "/redirect®"); // await response.text(); // } async function fetchWithManualRedirection() { const response = await fetch("http://localhost:4545/assets/hello.txt", { redirect: "manual", }); assert.strictEqual(response.status, 301); assert.strictEqual(response.statusText, "Moved Permanently"); assert.strictEqual(response.url, "http://localhost:4545/assets/hello.txt"); assert.strictEqual(response.type, "basic"); assert.strictEqual(response.headers.get("Location"), "/assets/fixture.json"); await response.body!.cancel(); } async function fetchWithErrorRedirection() { await expect( fetch("http://localhost:4545/assets/hello.txt", { redirect: "error", }), ).to.be.rejectedWith(TypeError, "redirect"); } function responseRedirect() { const redir = Response.redirect("http://example.com/newLocation", 301); assert.strictEqual(redir.status, 301); assert.strictEqual(redir.statusText, ""); assert.strictEqual(redir.url, ""); assert.strictEqual( redir.headers.get("Location"), "http://example.com/newLocation", ); assert.strictEqual(redir.type, "default"); } function responseRedirectTakeURLObjectAsParameter() { const redir = Response.redirect(new URL("https://example.com/")); assert.strictEqual(redir.headers.get("Location"), "https://example.com/"); } async function responseWithoutBody() { const response = new Response(); assert.deepEqual(await response.arrayBuffer(), new ArrayBuffer(0)); const blob = await response.blob(); assert.strictEqual(blob.size, 0); assert.deepEqual(await blob.arrayBuffer(), new ArrayBuffer(0)); assert.strictEqual(await response.text(), ""); await expect(response.json()).to.be.rejectedWith( "Unexpected end of JSON input", ); } async function fetchBodyReadTwice() { const response = await fetch("http://localhost:4545/assets/fixture.json"); // Read body const _json = await response.json(); assert(_json); // All calls after the body was consumed, should fail const methods = ["json", "text", /*"formData",*/ "arrayBuffer"] as const; for (const method of methods) { try { await response[method](); assert( false, "Reading body multiple times should failed, the stream should've been locked.", ); } catch { // pass } } } async function fetchBodyReaderAfterRead() { const response = await fetch("http://localhost:4545/assets/fixture.json"); assert(response.body !== null); const reader = await response.body.getReader(); // eslint-disable-next-line no-constant-condition while (true) { const { done, value } = await reader.read(); if (done) break; assert(value); } assert.throws( () => response.body!.getReader(), "This stream has already been locked", ); } async function fetchBodyReaderWithCancelAndNewReader() { const data = "a".repeat(1 << 10); const response = await fetch("http://localhost:4545/echo_server", { method: "POST", body: data, }); assert(response.body !== null); const firstReader = await response.body.getReader(); // Acquire reader without reading & release await firstReader.releaseLock(); const reader = await response.body.getReader(); let total = 0; // eslint-disable-next-line no-constant-condition while (true) { const { done, value } = await reader.read(); if (done) break; assert(value); total += value.length; } assert.strictEqual(total, data.length); } async function fetchBodyReaderWithReadCancelAndNewReader() { const data = "a".repeat(1 << 10); const response = await fetch("http://localhost:4545/echo_server", { method: "POST", body: data, }); assert(response.body !== null); const firstReader = await response.body.getReader(); // Do one single read with first reader const { value: firstValue } = await firstReader.read(); assert(firstValue); await firstReader.releaseLock(); // Continue read with second reader const reader = await response.body.getReader(); let total = firstValue.length || 0; // eslint-disable-next-line no-constant-condition while (true) { const { done, value } = await reader.read(); if (done) break; assert(value); total += value.length; } assert.strictEqual(total, data.length); } // async function fetchResourceCloseAfterStreamCancel() { // const res = await fetch("http://localhost:4545/assets/fixture.json"); // assert(res.body !== null); // // After ReadableStream.cancel is called, resource handle must be closed // // The test should not fail with: Test case is leaking resources // await res.body.cancel(); // } // async function fetchNullBodyStatus() { // const nullBodyStatus = [101, 204, 205, 304]; // for (const status of nullBodyStatus) { // const headers = new Headers([["x-status", String(status)]]); // const res = await fetch("http://localhost:4545/echo_server", { // body: "deno", // method: "POST", // headers, // }); // assertEquals(res.body, null); // assertEquals(res.status, status); // } // } async function fetchResponseContentLength() { const body = new Uint8Array(2 ** 16); const headers = new Headers([["content-type", "application/octet-stream"]]); const res = await fetch("http://localhost:4545/echo_server", { body: body, method: "POST", headers, }); assert.strictEqual( Number(res.headers.get("content-length")), body.byteLength, ); const blob = await res.blob(); // Make sure Body content-type is correctly set assert.strictEqual(blob.type, "application/octet-stream"); assert.strictEqual(blob.size, body.byteLength); } // function fetchResponseConstructorNullBody() { // const nullBodyStatus = [204, 205, 304]; // for (const status of nullBodyStatus) { // try { // new Response("deno", { status }); // fail("Response with null body status cannot have body"); // } catch (e) { // assert(e instanceof TypeError); // assertEquals( // e.message, // "Response with null body status cannot have body" // ); // } // } // } // function fetchResponseConstructorInvalidStatus() { // const invalidStatus = [100, 600, 199, null, "", NaN]; // for (const status of invalidStatus) { // try { // // deno-lint-ignore ban-ts-comment // // @ts-ignore // new Response("deno", { status }); // fail(`Invalid status: ${status}`); // } catch (e) { // assert(e instanceof RangeError); // assert( // e.message.endsWith( // "is not equal to 101 and outside the range [200, 599]." // ) // ); // } // } // } // function fetchResponseEmptyConstructor() { // const response = new Response(); // assertEquals(response.status, 200); // assertEquals(response.body, null); // assertEquals(response.type, "default"); // assertEquals(response.url, ""); // assertEquals(response.redirected, false); // assertEquals(response.ok, true); // assertEquals(response.bodyUsed, false); // assertEquals([...response.headers], []); // } // async function fetchCustomHttpClientParamCertificateSuccess(): Promise<void> { // const caCert = Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem"); // const client = Deno.createHttpClient({ caCerts: [caCert] }); // const response = await fetch("https://localhost:5545/assets/fixture.json", { // client, // }); // const json = await response.json(); // assertEquals(json.name, "deno"); // client.close(); // } // async function fetchCustomClientUserAgent(): Promise<void> { // const data = "Hello World"; // const client = Deno.createHttpClient({}); // const response = await fetch("http://localhost:4545/echo_server", { // client, // method: "POST", // body: new TextEncoder().encode(data), // }); // assertEquals(response.headers.get("user-agent"), `Deno/${Deno.version.deno}`); // await response.text(); // client.close(); // } // async function fetchPostBodyReadableStream() { // const addr = "127.0.0.1:4511"; // const bufPromise = bufferServer(addr); // const stream = new TransformStream(); // const writer = stream.writable.getWriter(); // // transformer writes don't resolve until they are read, so awaiting these // // will cause the transformer to hang, as the suspend the transformer, it // // is also illogical to await for the reads, as that is the whole point of // // streams is to have a "queue" which gets drained... // writer.write(new TextEncoder().encode("hello ")); // writer.write(new TextEncoder().encode("world")); // writer.close(); // const response = await fetch(`http://${addr}/blah`, { // method: "POST", // headers: [ // ["Hello", "World"], // ["Foo", "Bar"], // ], // body: stream.readable, // }); // await response.arrayBuffer(); // assertEquals(response.status, 404); // assertEquals(response.headers.get("Content-Length"), "2"); // const actual = new TextDecoder().decode((await bufPromise).bytes()); // const expected = [ // "POST /blah HTTP/1.1\r\n", // "hello: World\r\n", // "foo: Bar\r\n", // "accept: */*\r\n", // "accept-language: *\r\n", // `user-agent: Deno/${Deno.version.deno}\r\n`, // "accept-encoding: gzip, br\r\n", // `host: ${addr}\r\n`, // `transfer-encoding: chunked\r\n\r\n`, // "6\r\n", // "hello \r\n", // "5\r\n", // "world\r\n", // "0\r\n\r\n", // ].join(""); // assertEquals(actual, expected); // } // function fetchWritableRespProps() { // const original = new Response("https://deno.land", { // status: 404, // headers: { "x-deno": "foo" }, // }); // const new_ = new Response("https://deno.land", original); // assertEquals(original.status, new_.status); // assertEquals(new_.headers.get("x-deno"), "foo"); // } // async function fetchFilterOutCustomHostHeader(): Promise<void> { // const addr = "127.0.0.1:4511"; // const [hostname, port] = addr.split(":"); // const listener = Deno.listen({ // hostname, // port: Number(port), // }) as Deno.Listener; // let httpConn: Deno.HttpConn; // listener.accept().then(async (conn: Deno.Conn) => { // httpConn = Deno.serveHttp(conn); // await httpConn // .nextRequest() // .then(async (requestEvent: Deno.RequestEvent | null) => { // const hostHeader = requestEvent?.request.headers.get("Host"); // const headersToReturn = hostHeader ? { Host: hostHeader } : undefined; // await requestEvent?.respondWith( // new Response("", { // status: 200, // headers: headersToReturn, // }) // ); // }); // }); // const response = await fetch(`http://${addr}/`, { // headers: { Host: "example.com" }, // }); // await response.text(); // listener.close(); // httpConn!.close(); // assertEquals(response.headers.get("Host"), addr); // } // async function fetchNoServerReadableStreamBody() { // const done = deferred(); // const body = new ReadableStream({ // start(controller) { // controller.enqueue(new Uint8Array([1])); // setTimeout(() => { // controller.enqueue(new Uint8Array([2])); // done.resolve(); // }, 1000); // }, // }); // const nonExistantHostname = "http://localhost:47582"; // await assertRejects(async () => { // await fetch(nonExistantHostname, { body, method: "POST" }); // }, TypeError); // await done; // } // async function fetchHeadRespBody() { // const res = await fetch("http://localhost:4545/echo_server", { // method: "HEAD", // }); // assert.strictEqual(res.body, null); // } // async function fetchClientCertWrongPrivateKey(): Promise<void> { // await assertRejects(async () => { // const client = Deno.createHttpClient({ // certChain: "bad data", // privateKey: await Deno.readTextFile( // "cli/tests/testdata/tls/localhost.key" // ), // }); // await fetch("https://localhost:5552/assets/fixture.json", { // client, // }); // }, Deno.errors.InvalidData); // } // async function fetchClientCertBadPrivateKey(): Promise<void> { // await assertRejects(async () => { // const client = Deno.createHttpClient({ // certChain: await Deno.readTextFile( // "cli/tests/testdata/tls/localhost.crt" // ), // privateKey: "bad data", // }); // await fetch("https://localhost:5552/assets/fixture.json", { // client, // }); // }, Deno.errors.InvalidData); // } // async function fetchClientCertNotPrivateKey(): Promise<void> { // await assertRejects(async () => { // const client = Deno.createHttpClient({ // certChain: await Deno.readTextFile( // "cli/tests/testdata/tls/localhost.crt" // ), // privateKey: "", // }); // await fetch("https://localhost:5552/assets/fixture.json", { // client, // }); // }, Deno.errors.InvalidData); // } // async function fetchCustomClientPrivateKey(): Promise<void> { // const data = "Hello World"; // const caCert = await Deno.readTextFile("cli/tests/testdata/tls/RootCA.crt"); // const client = Deno.createHttpClient({ // certChain: await Deno.readTextFile("cli/tests/testdata/tls/localhost.crt"), // privateKey: await Deno.readTextFile("cli/tests/testdata/tls/localhost.key"), // caCerts: [caCert], // }); // const response = await fetch("https://localhost:5552/echo_server", { // client, // method: "POST", // body: new TextEncoder().encode(data), // }); // assertEquals(response.headers.get("user-agent"), `Deno/${Deno.version.deno}`); // await response.text(); // client.close(); // } // async function fetchAbortWhileUploadStreaming(): Promise<void> { // const abortController = new AbortController(); // try { // await fetch("http://localhost:4545/echo_server", { // method: "POST", // body: new ReadableStream({ // pull(_controller) { // abortController.abort(); // // controller.enqueue(new Uint8Array([1, 2, 3, 4])); // }, // }), // signal: abortController.signal, // }); // expect.fail("Fetch didn't reject."); // } catch (error) { // expect(error).to.be.instanceOf(Error); // if (error instanceof Error) { // expect(error.message).to.equal("AbortError"); // } // // expect(error).to.be.instanceOf(DOMException); // // expect(error.name).to.equal("AbortError"); // // expect(error.message).to.equal("The signal has been aborted"); // } // } // async function fetchAbortWhileUploadStreamingWithReason(): Promise<void> { // const abortController = new AbortController(); // const abortReason = new Error(); // try { // await fetch("http://localhost:5552/echo_server", { // method: "POST", // body: new ReadableStream({ // pull(controller) { // abortController.abort(abortReason); // controller.enqueue(new Uint8Array([1, 2, 3, 4])); // }, // }), // signal: abortController.signal, // }); // fail("Fetch didn't reject."); // } catch (error) { // assertEquals(error, abortReason); // } // } // async function fetchAbortWhileUploadStreamingWithPrimitiveReason(): Promise<void> { // const abortController = new AbortController(); // try { // await fetch("http://localhost:5552/echo_server", { // method: "POST", // body: new ReadableStream({ // pull(controller) { // abortController.abort("Abort reason"); // controller.enqueue(new Uint8Array([1, 2, 3, 4])); // }, // }), // signal: abortController.signal, // }); // fail("Fetch didn't reject."); // } catch (error) { // assertEquals(error, "Abort reason"); // } // } // async function fetchHeaderValueShouldNotPanic() { // for (let i = 0; i < 0x21; i++) { // if (i === 0x09 || i === 0x0a || i === 0x0d || i === 0x20) { // continue; // these header value will be normalized, will not cause an error. // } // // ensure there will be an error instead of panic. // await assertRejects( // () => // fetch("http://localhost:4545/echo_server", { // method: "HEAD", // headers: { val: String.fromCharCode(i) }, // }), // TypeError // ); // } // await assertRejects( // () => // fetch("http://localhost:4545/echo_server", { // method: "HEAD", // headers: { val: String.fromCharCode(127) }, // }), // TypeError // ); // } // async function fetchHeaderNameShouldNotPanic() { // const validTokens = // "!#$%&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUWVXYZ^_`abcdefghijklmnopqrstuvwxyz|~".split( // "" // ); // for (let i = 0; i <= 255; i++) { // const token = String.fromCharCode(i); // if (validTokens.includes(token)) { // continue; // } // // ensure there will be an error instead of panic. // await assertRejects( // () => // fetch("http://localhost:4545/echo_server", { // method: "HEAD", // headers: { [token]: "value" }, // }), // TypeError // ); // } // await assertRejects( // () => // fetch("http://localhost:4545/echo_server", { // method: "HEAD", // headers: { "": "value" }, // }), // TypeError // ); // } // async function fetchSupportsHttp1Only() { // const caCert = await Deno.readTextFile("cli/tests/testdata/tls/RootCA.pem"); // const client = Deno.createHttpClient({ caCerts: [caCert] }); // const res = await fetch("https://localhost:5546/http_version", { client }); // assert(res.ok); // assertEquals(await res.text(), "HTTP/1.1"); // client.close(); // } // async function fetchSupportsHttp2() { // const caCert = await Deno.readTextFile("cli/tests/testdata/tls/RootCA.pem"); // const client = Deno.createHttpClient({ caCerts: [caCert] }); // const res = await fetch("https://localhost:5547/http_version", { client }); // assert(res.ok); // assertEquals(await res.text(), "HTTP/2.0"); // client.close(); // } // async function fetchPrefersHttp2() { // const caCert = await Deno.readTextFile("cli/tests/testdata/tls/RootCA.pem"); // const client = Deno.createHttpClient({ caCerts: [caCert] }); // const res = await fetch("https://localhost:5545/http_version", { client }); // assert(res.ok); // assertEquals(await res.text(), "HTTP/2.0"); // client.close(); // } // async function fetchFilePerm() { // await assertRejects(async () => { // await fetch(import.meta.resolve("../testdata/subdir/json_1.json")); // }, Deno.errors.PermissionDenied); // } // async function fetchFilePermDoesNotExist() { // await assertRejects(async () => { // await fetch(import.meta.resolve("./bad.json")); // }, Deno.errors.PermissionDenied); // } // async function fetchFileBadMethod() { // await assertRejects( // async () => { // await fetch(import.meta.resolve("../testdata/subdir/json_1.json"), { // method: "POST", // }); // }, // TypeError, // "Fetching files only supports the GET method. Received POST." // ); // } // async function fetchFileDoesNotExist() { // await assertRejects(async () => { // await fetch(import.meta.resolve("./bad.json")); // }, TypeError); // } // async function fetchFile() { // const res = await fetch( // import.meta.resolve("../testdata/subdir/json_1.json") // ); // assert(res.ok); // const fixture = await Deno.readTextFile( // "cli/tests/testdata/subdir/json_1.json" // ); // assertEquals(await res.text(), fixture); // } // async function fetchContentLengthPost() { // const response = await fetch("http://localhost:4545/content_length", { // method: "POST", // }); // const length = await response.text(); // assertEquals(length, 'Some("0")'); // } // async function fetchContentLengthPut() { // const response = await fetch("http://localhost:4545/content_length", { // method: "PUT", // }); // const length = await response.text(); // assertEquals(length, 'Some("0")'); // } // async function fetchContentLengthPatch() { // const response = await fetch("http://localhost:4545/content_length", { // method: "PATCH", // }); // const length = await response.text(); // assertEquals(length, "None"); // } // async function fetchContentLengthPostWithStringBody() { // const response = await fetch("http://localhost:4545/content_length", { // method: "POST", // body: "Hey!", // }); // const length = await response.text(); // assertEquals(length, 'Some("4")'); // } // async function fetchContentLengthPostWithBufferBody() { // const response = await fetch("http://localhost:4545/content_length", { // method: "POST", // body: new TextEncoder().encode("Hey!"), // }); // const length = await response.text(); // assertEquals(length, 'Some("4")'); // } // async function staticResponseJson() { // const data = { hello: "world" }; // const resp = Response.json(data); // assertEquals(resp.status, 200); // assertEquals(resp.headers.get("content-type"), "application/json"); // const res = await resp.json(); // assertEquals(res, data); // } // function invalidServer(addr: string, body: Uint8Array): Deno.Listener { // const [hostname, port] = addr.split(":"); // const listener = Deno.listen({ // hostname, // port: Number(port), // }) as Deno.Listener; // (async () => { // for await (const conn of listener) { // const p1 = conn.read(new Uint8Array(2 ** 14)); // const p2 = conn.write(body); // await Promise.all([p1, p2]); // conn.close(); // } // })(); // return listener; // } // async function fetchWithInvalidContentLengthAndTransferEncoding(): Promise<void> { // const addr = "127.0.0.1:4516"; // const data = "a".repeat(10 << 10); // const body = new TextEncoder().encode( // `HTTP/1.1 200 OK\r\nContent-Length: ${Math.round( // data.length * 2 // )}\r\nTransfer-Encoding: chunked\r\n\r\n${data.length.toString( // 16 // )}\r\n${data}\r\n0\r\n\r\n` // ); // // if transfer-encoding is sent, content-length is ignored // // even if it has an invalid value (content-length > totalLength) // const listener = invalidServer(addr, body); // const response = await fetch(`http://${addr}/`); // const res = await response.arrayBuffer(); // const buf = new TextEncoder().encode(data); // assertEquals(res.byteLength, buf.byteLength); // assertEquals(new Uint8Array(res), buf); // listener.close(); // } // async function fetchWithInvalidContentLength(): Promise<void> { // const addr = "127.0.0.1:4518"; // const data = "a".repeat(10 << 10); // const contentLength = data.length / 2; // const body = new TextEncoder().encode( // `HTTP/1.1 200 OK\r\nContent-Length: ${contentLength}\r\n\r\n${data}` // ); // const listener = invalidServer(addr, body); // const response = await fetch(`http://${addr}/`); // // If content-length < totalLength, a maximum of content-length bytes // // should be returned. // const res = await response.arrayBuffer(); // const buf = new TextEncoder().encode(data); // assertEquals(res.byteLength, contentLength); // assertEquals(new Uint8Array(res), buf.subarray(contentLength)); // listener.close(); // } // async function fetchWithInvalidContentLength2(): Promise<void> { // const addr = "127.0.0.1:4519"; // const data = "a".repeat(10 << 10); // const contentLength = data.length * 2; // const body = new TextEncoder().encode( // `HTTP/1.1 200 OK\r\nContent-Length: ${contentLength}\r\n\r\n${data}` // ); // const listener = invalidServer(addr, body); // const response = await fetch(`http://${addr}/`); // // If content-length > totalLength, a maximum of content-length bytes // // should be returned. // await assertRejects( // async () => { // await response.arrayBuffer(); // }, // Error, // "end of file before message length reached" // ); // listener.close(); // } // async function fetchBlobUrl(): Promise<void> { // const blob = new Blob(["ok"], { type: "text/plain" }); // const url = URL.createObjectURL(blob); // const res = await fetch(url); // assert(res.url.startsWith("blob:http://js-unit-tests/")); // assertEquals(res.status, 200); // assertEquals(res.headers.get("content-length"), "2"); // assertEquals(res.headers.get("content-type"), "text/plain"); // assertEquals(await res.text(), "ok"); // } async function fetchResponseStreamIsLockedWhileReading() { const response = await fetch("http://localhost:4545/echo_server", { body: new Uint8Array(5000), method: "POST", }); assert.strictEqual(response.body!.locked, false); const promise = response.arrayBuffer(); assert.strictEqual(response.body!.locked, true); await promise; } async function fetchResponseStreamIsLockedWhileReadingBlob() { const response = await fetch("http://localhost:4545/echo_server", { body: new Uint8Array(5000), method: "POST", }); assert.strictEqual(response.body!.locked, false); const promise = response.blob(); assert.strictEqual(response.body!.locked, true); await promise; } async function fetchConstructorClones() { const req = new Request("https://example.com", { method: "POST", body: "foo", }); assert.strictEqual(await req.text(), "foo"); await expect(req.text()).to.be.rejectedWith( TypeError, /body stream already read/, ); const req2 = new Request(req, { method: "PUT", body: "bar" }); // should not have any impact on req assert.strictEqual(await req2.text(), "bar"); await expect(req2.text()).to.be.rejectedWith( TypeError, /body stream already read/, ); assert.strictEqual(req.method, "POST"); assert.strictEqual(req2.method, "PUT"); assert.strictEqual(req.headers.get("x-foo"), null); assert.strictEqual(req2.headers.get("x-foo"), null); req2.headers.set("x-foo", "bar"); // should not have any impact on req assert.strictEqual(req.headers.get("x-foo"), null); assert.strictEqual(req2.headers.get("x-foo"), "bar"); } async function fetchForbidden() { // TODO(presley): See if we can throw type error when we reject the promise // from rust await expect(fetch("http://localhost:4545/proxy_reject")).to.be.rejectedWith( "Request to http://localhost:4545/proxy_reject forbidden", ); } export const fetchInParallel = action(async () => { const parallelFetches = [ fetch("http://localhost:4546/timeout"), fetch("http://localhost:4546/echo_server", { method: "POST", body: new TextEncoder().encode("hello world"), }), ]; const response = await Promise.race(parallelFetches); assert(response.ok, await response.text()); }); // Regression test. export const fetchBlockedOnTimeouts = action(async () => { const fetchWithTimeout = async () => { setTimeout( () => { // AbortController stuff. // This never runs. }, 10 * 60 * 1000, ); await fetch("http://localhost:4546/echo_server", { method: "POST", body: new TextEncoder().encode("hello world"), }); }; // 10 setTimeouts queued up before the fetches. // When limited parallelism in actions applied to setTimeouts, the setTimeouts // would block the fetches. await Promise.all( Array(10) .fill(0) .map(() => fetchWithTimeout()), ); }); export const danglingFetch = action(() => { // eslint-disable-next-line @typescript-eslint/no-floating-promises fetch("http://localhost:4546/echo_server"); }); export const fetchTimeout = action(async () => { const request = new Request("http://localhost:4546/timeout"); const result = await fetch(request); throw new Error(`fetch should not complete: ${result}`); }); export const fetchUnendingRequest = action(async () => { const request = new Request("http://localhost:4546/echo_server", { method: "POST", body: new ReadableStream(), }); const response = await fetch(request); await response.text(); throw new Error(`fetch should not complete`); }); // Regression test for https://webtechsurvey.com/response-header/x-olaf async function fetchOlaf() { const response = await fetch("http://localhost:4545/echo_server", { method: "POST", headers: { "X-Olaf": "⛄" }, }); assert.strictEqual(response.headers.get("X-Olaf"), "â\x9B\x84"); } async function fetchBodyTextDecoderStream() { const data = JSON.stringify({ hello: "world", foo: "bar", baz: "qux", }); const response = await fetch("http://localhost:4545/echo_server", { method: "POST", body: data, }); assert(response.body !== null); const decodedStream = response.body!.pipeThrough(new TextDecoderStream()); const reader = decodedStream.getReader(); let decodedBody = ""; // eslint-disable-next-line no-constant-condition while (true) { const { done, value } = await reader.read(); if (done) break; assert(value); decodedBody += value; } assert.strictEqual(decodedBody, data); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/get-convex/convex-backend'

If you have feedback or need assistance with the MCP directory API, please join our Discord server