Repository: remix-run/remix
Pull request #1120 (2021-12-17 19:43:13)

fix: request clone + tests


Patch:
diff --git a/packages/remix-node/fetch.ts b/packages/remix-node/fetch.ts
index d0219f442..0ca71d1ea 100644
--- a/packages/remix-node/fetch.ts
+++ b/packages/remix-node/fetch.ts
@@ -3,7 +3,7 @@ import { PassThrough } from "stream";
 import type AbortController from "abort-controller";
 import FormStream from "form-data";
 import type { RequestInfo, RequestInit, Response } from "node-fetch";
-import nodeFetch, { Request as NodeRequest } from "node-fetch";
+import nodeFetch, { Request as BaseNodeRequest } from "node-fetch";
 
 import { FormData as NodeFormData, isFile } from "./formData";
 import type { UploadHandler } from "./formData";
@@ -63,14 +63,14 @@ function formDataToStream(formData: NodeFormData): FormStream {
   return formStream;
 }
 
-interface RemixRequestInit extends RequestInit {
+interface NodeRequestInit extends RequestInit {
   abortController?: AbortController;
 }
 
-class RemixRequest extends NodeRequest {
+class NodeRequest extends BaseNodeRequest {
   private abortController?: AbortController;
 
-  constructor(input: RequestInfo, init?: RemixRequestInit | undefined) {
+  constructor(input: RequestInfo, init?: NodeRequestInit | undefined) {
     if (init?.body instanceof NodeFormData) {
       init = {
         ...init,
@@ -101,12 +101,12 @@ class RemixRequest extends NodeRequest {
     throw new Error("Invalid MIME type");
   }
 
-  clone() {
-    return new RemixRequest(super.clone());
+  clone(): NodeRequest {
+    return new NodeRequest(this);
   }
 }
 
-export { RemixRequest as Request, RemixRequestInit as RequestInit };
+export { NodeRequest as Request, NodeRequestInit as RequestInit };
 
 /**
  * A `fetch` function for node that matches the web Fetch API. Based on


Test patch:
diff --git a/packages/remix-architect/__tests__/server-test.ts b/packages/remix-architect/__tests__/server-test.ts
index 239bbd60c..021889491 100644
--- a/packages/remix-architect/__tests__/server-test.ts
+++ b/packages/remix-architect/__tests__/server-test.ts
@@ -242,7 +242,7 @@ describe("architect createRemixRequest", () => {
         })
       )
     ).toMatchInlineSnapshot(`
-      RemixRequest {
+      NodeRequest {
         "abortController": undefined,
         "agent": undefined,
         "compress": true,
diff --git a/packages/remix-express/__tests__/server-test.ts b/packages/remix-express/__tests__/server-test.ts
index 75ccadce0..4053393c3 100644
--- a/packages/remix-express/__tests__/server-test.ts
+++ b/packages/remix-express/__tests__/server-test.ts
@@ -211,7 +211,7 @@ describe("express createRemixRequest", () => {
     });
 
     expect(createRemixRequest(expressRequest)).toMatchInlineSnapshot(`
-      RemixRequest {
+      NodeRequest {
         "abortController": undefined,
         "agent": undefined,
         "compress": true,
diff --git a/packages/remix-netlify/__tests__/server-test.ts b/packages/remix-netlify/__tests__/server-test.ts
index d8e452161..365a777f2 100644
--- a/packages/remix-netlify/__tests__/server-test.ts
+++ b/packages/remix-netlify/__tests__/server-test.ts
@@ -228,7 +228,7 @@ describe("netlify createRemixRequest", () => {
         })
       )
     ).toMatchInlineSnapshot(`
-      RemixRequest {
+      NodeRequest {
         "abortController": undefined,
         "agent": undefined,
         "compress": true,
diff --git a/packages/remix-node/__tests__/fetch-test.ts b/packages/remix-node/__tests__/fetch-test.ts
new file mode 100644
index 000000000..0297b0893
--- /dev/null
+++ b/packages/remix-node/__tests__/fetch-test.ts
@@ -0,0 +1,110 @@
+import { PassThrough } from "stream";
+import { Request } from "../fetch";
+import { createMemoryUploadHandler } from "../upload/memoryUploadHandler";
+
+let test = {
+  source: [
+    [
+      "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k",
+      'Content-Disposition: form-data; name="file_name_0"',
+      "",
+      "super alpha file",
+      "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k",
+      'Content-Disposition: form-data; name="file_name_1"',
+      "",
+      "super beta file",
+      "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k",
+      'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"',
+      "Content-Type: application/octet-stream",
+      "",
+      "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
+      "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k",
+      'Content-Disposition: form-data; name="upload_file_1"; filename="1k_b.dat"',
+      "Content-Type: application/octet-stream",
+      "",
+      "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
+      "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--"
+    ].join("\r\n")
+  ],
+  boundary: "---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k",
+  expected: [
+    [
+      "field",
+      "file_name_0",
+      "super alpha file",
+      false,
+      false,
+      "7bit",
+      "text/plain"
+    ],
+    [
+      "field",
+      "file_name_1",
+      "super beta file",
+      false,
+      false,
+      "7bit",
+      "text/plain"
+    ],
+    [
+      "file",
+      "upload_file_0",
+      1023,
+      0,
+      "1k_a.dat",
+      "7bit",
+      "application/octet-stream"
+    ],
+    [
+      "file",
+      "upload_file_1",
+      1023,
+      0,
+      "1k_b.dat",
+      "7bit",
+      "application/octet-stream"
+    ]
+  ],
+  what: "Fields and files"
+};
+
+describe("Request", () => {
+  let uploadHandler = createMemoryUploadHandler({});
+
+  it("clones", async () => {
+    let body = new PassThrough();
+    test.source.forEach(chunk => body.write(chunk));
+
+    let req = new Request("http://test.com", {
+      method: "post",
+      body,
+      headers: {
+        "Content-Type": "multipart/form-data; boundary=" + test.boundary
+      }
+    });
+
+    let cloned = req.clone();
+    expect(Object.getPrototypeOf(req)).toBe(Object.getPrototypeOf(cloned));
+
+    let formData = await req.formData(uploadHandler);
+    let clonedFormData = await cloned.formData(uploadHandler);
+
+    expect(formData.get("file_name_0")).toBe("super alpha file");
+    expect(clonedFormData.get("file_name_0")).toBe("super alpha file");
+    expect(formData.get("file_name_1")).toBe("super beta file");
+    expect(clonedFormData.get("file_name_1")).toBe("super beta file");
+    let file = formData.get("upload_file_0") as File;
+    expect(file.name).toBe("1k_a.dat");
+    expect(file.size).toBe(1023);
+    file = clonedFormData.get("upload_file_0") as File;
+    expect(file.name).toBe("1k_a.dat");
+    expect(file.size).toBe(1023);
+
+    file = formData.get("upload_file_1") as File;
+    expect(file.name).toBe("1k_b.dat");
+    expect(file.size).toBe(1023);
+    file = clonedFormData.get("upload_file_1") as File;
+    expect(file.name).toBe("1k_b.dat");
+    expect(file.size).toBe(1023);
+  });
+});
diff --git a/packages/remix-vercel/__tests__/server-test.ts b/packages/remix-vercel/__tests__/server-test.ts
index 2470353a1..d9ea9dc5e 100644
--- a/packages/remix-vercel/__tests__/server-test.ts
+++ b/packages/remix-vercel/__tests__/server-test.ts
@@ -215,7 +215,7 @@ describe("vercel createRemixRequest", () => {
     }) as VercelRequest;
 
     expect(createRemixRequest(request)).toMatchInlineSnapshot(`
-      RemixRequest {
+      NodeRequest {
         "abortController": undefined,
         "agent": undefined,
         "compress": true,