// META: global=window,worker // META: script=../resources/utils.js // META: script=/common/utils.js // META: script=/common/get-host-info.sub.js const duplex = "half"; async function assertUpload(url, method, createBody, expectedBody) { const requestInit = {method}; const body = createBody(); if (body) { requestInit["body"] = body; requestInit.duplex = "half"; } const resp = await fetch(url, requestInit); const text = await resp.text(); assert_equals(text, expectedBody); } function testUpload(desc, url, method, createBody, expectedBody) { promise_test(async () => { await assertUpload(url, method, createBody, expectedBody); }, desc); } function createStream(chunks) { return new ReadableStream({ start: (controller) => { for (const chunk of chunks) { controller.enqueue(chunk); } controller.close(); } }); } const url = RESOURCES_DIR + "echo-content.h2.py" testUpload("Fetch with POST with empty ReadableStream", url, "POST", () => { return new ReadableStream({start: controller => { controller.close(); }}) }, ""); testUpload("Fetch with POST with ReadableStream", url, "POST", () => { return new ReadableStream({start: controller => { const encoder = new TextEncoder(); controller.enqueue(encoder.encode("Test")); controller.close(); }}) }, "Test"); promise_test(async (test) => { const body = new ReadableStream({start: controller => { const encoder = new TextEncoder(); controller.enqueue(encoder.encode("Test")); controller.close(); }}); const resp = await fetch( "/fetch/connection-pool/resources/network-partition-key.py?" + `status=421&uuid=${token()}&partition_id=${self.origin}` + `&dispatch=check_partition&addcounter=true`, {method: "POST", body: body, duplex}); assert_equals(resp.status, 421); const text = await resp.text(); assert_equals(text, "ok. Request was sent 1 times. 1 connections were created."); }, "Fetch with POST with ReadableStream on 421 response should return the response and not retry."); promise_test(async (test) => { const request = new Request('', { body: new ReadableStream(), method: 'POST', duplex, }); assert_equals(request.headers.get('Content-Type'), null, `Request should not have a content-type set`); const response = await fetch('data:a/a;charset=utf-8,test', { method: 'POST', body: new ReadableStream(), duplex, }); assert_equals(await response.text(), 'test', `Response has correct body`); }, "Feature detect for POST with ReadableStream"); promise_test(async (test) => { const request = new Request('data:a/a;charset=utf-8,test', { body: new ReadableStream(), method: 'POST', duplex, }); assert_equals(request.headers.get('Content-Type'), null, `Request should not have a content-type set`); const response = await fetch(request); assert_equals(await response.text(), 'test', `Response has correct body`); }, "Feature detect for POST with ReadableStream, using request object"); test(() => { let duplexAccessed = false; const request = new Request("", { body: new ReadableStream(), method: "POST", get duplex() { duplexAccessed = true; return "half"; }, }); assert_equals( request.headers.get("Content-Type"), null, `Request should not have a content-type set` ); assert_true(duplexAccessed, `duplex dictionary property should be accessed`); }, "Synchronous feature detect"); // The asserts the synchronousFeatureDetect isn't broken by a partial implementation. // An earlier feature detect was broken by Safari implementing streaming bodies as part of Request, // but it failed when passed to fetch(). // This tests ensures that UAs must not implement RequestInit.duplex and streaming request bodies without also implementing the fetch() parts. promise_test(async () => { let duplexAccessed = false; const request = new Request("", { body: new ReadableStream(), method: "POST", get duplex() { duplexAccessed = true; return "half"; }, }); const supported = request.headers.get("Content-Type") === null && duplexAccessed; // If the feature detect fails, assume the browser is being truthful (other tests pick up broken cases here) if (!supported) return false; await assertUpload( url, "POST", () => new ReadableStream({ start: (controller) => { const encoder = new TextEncoder(); controller.enqueue(encoder.encode("Test")); controller.close(); }, }), "Test" ); }, "Synchronous feature detect fails if feature unsupported"); promise_test(async (t) => { const body = createStream(["hello"]); const method = "POST"; await promise_rejects_js(t, TypeError, fetch(url, { method, body, duplex })); }, "Streaming upload with body containing a String"); promise_test(async (t) => { const body = createStream([null]); const method = "POST"; await promise_rejects_js(t, TypeError, fetch(url, { method, body, duplex })); }, "Streaming upload with body containing null"); promise_test(async (t) => { const body = createStream([33]); const method = "POST"; await promise_rejects_js(t, TypeError, fetch(url, { method, body, duplex })); }, "Streaming upload with body containing a number"); promise_test(async (t) => { const url = "/fetch/api/resources/authentication.py?realm=test"; const body = createStream([]); const method = "POST"; await promise_rejects_js(t, TypeError, fetch(url, { method, body, duplex })); }, "Streaming upload should fail on a 401 response");