var boundary = "1234567891011121314151617";

// fn(body) should create a Body subclass with content body treated as
// FormData and return it.
function testFormDataParsing(fn) {
  function makeTest(shouldPass, input, testFn) {
    var obj = fn(input);
    return obj.formData().then(
      function (fd) {
        ok(shouldPass, "Expected test to be valid FormData for " + input);
        if (testFn) {
          return testFn(fd);
        }
        return undefined;
      },
      function (e) {
        if (shouldPass) {
          ok(false, "Expected test to pass for " + input);
        } else {
          ok(e.name == "TypeError", "Error should be a TypeError.");
        }
      }
    );
  }

  // [shouldPass?, input, testFn]
  var tests = [
    [
      true,

      boundary +
        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
        boundary +
        "-",

      function (fd) {
        is(fd.get("greeting"), '"hello"');
      },
    ],
    [
      false,

      // Invalid disposition.
      boundary +
        '\r\nContent-Disposition: form-datafoobar; name="greeting"\r\n\r\n"hello"\r\n' +
        boundary +
        "-",
    ],
    [
      true,

      "--" +
        boundary +
        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
        boundary +
        "-",

      function (fd) {
        is(fd.get("greeting"), '"hello"');
      },
    ],
    [false, boundary + "\r\n\r\n" + boundary + "-"],
    [
      false,
      // No valid ending.
      boundary + "\r\n\r\n" + boundary,
    ],
    [
      false,

      // One '-' prefix is not allowed. 2 or none.
      "-" +
        boundary +
        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
        boundary +
        "-",
    ],
    [
      false,

      "invalid" +
        boundary +
        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
        boundary +
        "-",
    ],
    [
      false,

      boundary +
        "suffix" +
        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
        boundary +
        "-",
    ],
    [
      false,

      boundary +
        "suffix" +
        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
        boundary +
        "-",
    ],
    [
      false,

      // Partial boundary
      boundary.substr(3) +
        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
        boundary +
        "-",
    ],
    [
      false,

      boundary +
        // Missing '\n' at beginning.
        '\rContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
        boundary +
        "-",
    ],
    [
      false,

      boundary +
        // No form-data.
        '\r\nContent-Disposition: mixed; name="greeting"\r\n\r\n"hello"\r\n' +
        boundary +
        "-",
    ],
    [
      false,

      boundary +
        // No headers.
        '\r\n\r\n"hello"\r\n' +
        boundary +
        "-",
    ],
    [
      false,

      boundary +
        // No content-disposition.
        '\r\nContent-Dispositypo: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
        boundary +
        "-",
    ],
    [
      false,

      boundary +
        // No name.
        '\r\nContent-Disposition: form-data;\r\n\r\n"hello"\r\n' +
        boundary +
        "-",
    ],
    [
      false,

      boundary +
        // Missing empty line between headers and body.
        '\r\nContent-Disposition: form-data; name="greeting"\r\n"hello"\r\n' +
        boundary +
        "-",
    ],
    [
      false,

      // Empty entry followed by valid entry.
      boundary +
        "\r\n\r\n" +
        boundary +
        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
        boundary +
        "-",
    ],
    [
      false,

      boundary +
        // Header followed by empty line, but empty body not followed by
        // newline.
        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n' +
        boundary +
        "-",
    ],
    [
      true,

      boundary +
        // Empty body followed by newline.
        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n\r\n' +
        boundary +
        "-",

      function (fd) {
        is(fd.get("greeting"), "", "Empty value is allowed.");
      },
    ],
    [
      false,
      boundary +
        // Value is boundary itself.
        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n' +
        boundary +
        "\r\n" +
        boundary +
        "-",
    ],
    [
      false,
      boundary +
        // Variant of above with no valid ending boundary.
        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n' +
        boundary,
    ],
    [
      true,
      boundary +
        // Unquoted filename with empty body.
        '\r\nContent-Disposition: form-data; name="file"; filename=file1.txt\r\n\r\n\r\n' +
        boundary +
        "-",

      function (fd) {
        var f = fd.get("file");
        ok(
          f instanceof File,
          "Entry with filename attribute should be read as File."
        );
        is(f.name, "file1.txt", "Filename should match.");
        is(f.type, "text/plain", "Default content-type should be text/plain.");
        return readAsText(f).then(function (text) {
          is(text, "", "File should be empty.");
        });
      },
    ],
    [
      true,
      boundary +
        // Quoted filename with empty body.
        '\r\nContent-Disposition: form-data; name="file"; filename="file1.txt"\r\n\r\n\r\n' +
        boundary +
        "-",

      function (fd) {
        var f = fd.get("file");
        ok(
          f instanceof File,
          "Entry with filename attribute should be read as File."
        );
        is(f.name, "file1.txt", "Filename should match.");
        is(f.type, "text/plain", "Default content-type should be text/plain.");
        return readAsText(f).then(function (text) {
          is(text, "", "File should be empty.");
        });
      },
    ],
    [
      false,
      boundary +
        // Invalid filename
        '\r\nContent-Disposition: form-data; name="file"; filename="[\n@;xt"\r\n\r\n\r\n' +
        boundary +
        "-",
    ],
    [
      true,
      boundary +
        '\r\nContent-Disposition: form-data; name="file"; filename="[@;xt"\r\n\r\n\r\n' +
        boundary +
        "-",

      function (fd) {
        var f = fd.get("file");
        ok(
          f instanceof File,
          "Entry with filename attribute should be read as File."
        );
        is(f.name, "[@", "Filename should match.");
      },
    ],
    [
      true,
      boundary +
        '\r\nContent-Disposition: form-data; name="file"; filename="file with   spaces"\r\n\r\n\r\n' +
        boundary +
        "-",

      function (fd) {
        var f = fd.get("file");
        ok(
          f instanceof File,
          "Entry with filename attribute should be read as File."
        );
        is(f.name, "file with spaces", "Filename should match.");
      },
    ],
    [
      true,
      boundary +
        "\r\n" +
        'Content-Disposition: form-data; name="file"; filename="xml.txt"\r\n' +
        "content-type       : application/xml\r\n" +
        "\r\n" +
        "<body>foobar\r\n\r\n</body>\r\n" +
        boundary +
        "-",

      function (fd) {
        var f = fd.get("file");
        ok(
          f instanceof File,
          "Entry with filename attribute should be read as File."
        );
        is(f.name, "xml.txt", "Filename should match.");
        is(
          f.type,
          "application/xml",
          "content-type should be application/xml."
        );
        return readAsText(f).then(function (text) {
          is(
            text,
            "<body>foobar\r\n\r\n</body>",
            "File should have correct text."
          );
        });
      },
    ],
  ];

  var promises = [];
  for (var i = 0; i < tests.length; ++i) {
    var test = tests[i];
    promises.push(makeTest(test[0], test[1], test[2]));
  }

  return Promise.all(promises);
}

function makeRequest(body) {
  var req = new Request("", {
    method: "post",
    body,
    headers: {
      "Content-Type": "multipart/form-data; boundary=" + boundary,
    },
  });
  return req;
}

function makeResponse(body) {
  var res = new Response(body, {
    headers: {
      "Content-Type": "multipart/form-data; boundary=" + boundary,
    },
  });
  return res;
}

function runTest() {
  return Promise.all([
    testFormDataParsing(makeRequest),
    testFormDataParsing(makeResponse),
  ]);
}
