<!doctype html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1.0">
    <title>Assignment 7 - Tests for Tasks 1 and 2</title>
    <link rel="stylesheet" href="qunit/qunit-2.9.2.css">
</head>

<body>
    <script src="js/fetch.js"></script>
    <script src="qunit/qunit-2.9.2.js"></script>
    <div id="qunit"></div>
    <div id="qunit-fixture"></div>
    <script>

    //some test payloads
    const headers = {};
    const baseURL = "/test/fetch";
    const jsonURL = baseURL + "/json/ok";
    const body = "test body as a string"
    const jsonBody_req = {
        json: body,
        rnd: Math.random()
    };
    const jsonBody_resp = (text) => {
      return {
        text, body: jsonBody_req
      }
    }

    var notValidJSON = () => {};

    /*
     * Make sure the fetch_test/router.js is installed under /test/fetch and the server is running for these tests to work
     *
     */

    function testOk(f,method,url,headers,body) {
      let msg = f.name+"('"+method+"','"+url+"','"+JSON.stringify(headers)+","+JSON.stringify(body)+") should accept the input";

      return async function(assert) {
        try {
          await f(method,url,headers,body);
          result = true;
        } catch (error) {
          console.log(msg, error);
          msg += "\n but \n" + error;
          result = false;
        }
        assert.ok(result, msg);
      }
    }

    function testFail(f,method,url,headers,body) {
      return async function(assert) {
        try{
          await f(method,url,headers,body);
          result = false;
        }
        catch(error) {
          result = true;
        }
        assert.ok(result,  f.name+"("+JSON.stringify(method)+","+JSON.stringify(url)+","+JSON.stringify(headers)+","+JSON.stringify(body)+") should reject the input");
      }
    }

    function testStatusBodyHeaders(f,method,url,headers,body,status,text,resp_headers) {
      return async function(assert) {
        return f(method,url,headers,body)
        .then((res)=>{
          assert.equal(res.status, status, method+" "+url+" should return "+status+" status code");
          Object.keys(resp_headers).forEach((k)=>{
            assert.ok(res.headers.has(k), method+" "+url+" response includes header "+k);
            assert.equal(res.headers.get(k), resp_headers[k], method+" "+url+" response header "+k+" has expected value "+JSON.stringify(resp_headers[k]));
          });
          return res.text();
        }).then((resp)=>{
          assert.equal(resp, text, method+" "+url+" returns expected response body "+JSON.stringify(text));
        });
      }
    }

    function testStatusBody(f,method,url,headers,body,status,text) {
      return async function(assert) {
        return f(method,url,headers,body)
        .then((res)=>{
          assert.equal(res.status, status, method+" "+url+" should return "+status+" status code");
          return res.text();
        }).then((resp)=>{
          assert.equal(resp, text, method+" "+url+" returns expected response body "+JSON.stringify(text));
        });
      }
    }

    function testStatusJSONBody(f,method,url,headers,body,status,json) {
      return async function(assert) {
        return f(method,url,headers,body)
        .then((res)=>{
          //assert.equal(res.status, status, method+" "+url+" should return "+status+" status code");
          assert.deepEqual(res, json, method+" "+url+" returns expected response body "+JSON.stringify(json));
        });
      }
    }

    function testStatus(f,method,url,headers,body,status,text) {
      return async function(assert) {
        return f(method,url,headers,body)
        .then((res)=>{
          assert.equal(res.status, status, method+" "+url+" should return "+status+" status code");
        });
      }
    }

    function testRedirect(f,method,url,headers,body,text) {
      return async function(assert) {
        return f(method,url,headers,body)
        .then((res)=>{
          assert.ok(res.redirected, method+" "+url+" should have been redirected");
          return res.text();
        }).then((resp)=>{
          assert.equal(resp, text, method+" "+url+" has expected response body after redirection "+JSON.stringify(text));
        });
      }
    }

    function testAll(a){
      return function(assert) {
        let b = [];
        a.forEach((f)=>{b.push(f(assert))});
        return Promise.all(b);
      }
    }

    QUnit.module("Task 1: doFetchRequest Request Validation", {});

    QUnit.test("reject incorrect methods", testAll(
      [
       testFail(doFetchRequest,"WRONG", baseURL,headers),
       testFail(doFetchRequest,"RANDOM",  baseURL,headers),
       testFail(doFetchRequest,"",baseURL,headers),
       testFail(doFetchRequest,undefined,baseURL,headers),
       testFail(doFetchRequest,404,baseURL,headers)
      ]));

    QUnit.test("GET, HEAD, OPTIONS, DELETE should not have a body", testAll(
      [
       testFail(doFetchRequest,"GET", baseURL,headers,body),
       testFail(doFetchRequest,"HEAD",  baseURL,headers,body),
       testFail(doFetchRequest,"OPTIONS",baseURL,headers,body),
       testFail(doFetchRequest,"DELETE",baseURL,headers,body),
         testOk(doFetchRequest,"GET", baseURL,headers),
         testOk(doFetchRequest,"HEAD",  baseURL,headers),
         testOk(doFetchRequest,"OPTIONS",baseURL,headers),
         testOk(doFetchRequest,"DELETE",baseURL,headers)
      ]));

    QUnit.test("POST, PUT, PATCH require the body", testAll(
      [
       testFail(doFetchRequest,"POST", baseURL,headers),
       testFail(doFetchRequest,"PUT",  baseURL,headers),
       testFail(doFetchRequest,"PATCH",baseURL,headers),
         testOk(doFetchRequest,"POST", baseURL,headers,body),
         testOk(doFetchRequest,"PUT",  baseURL,headers,body),
         testOk(doFetchRequest,"PATCH",baseURL,headers,body)
      ]));

    QUnit.test("POST, PUT, PATCH require a String body", testAll(
      [
       testFail(doFetchRequest,"POST", baseURL,headers,{}),
       testFail(doFetchRequest,"PUT",  baseURL,headers,{}),
       testFail(doFetchRequest,"PATCH",baseURL,headers,{}),
       testFail(doFetchRequest,"POST", baseURL,headers,[]),
       testFail(doFetchRequest,"PUT",  baseURL,headers,[]),
       testFail(doFetchRequest,"PATCH",baseURL,headers,[]),
       testFail(doFetchRequest,"POST", baseURL,headers,999),
       testFail(doFetchRequest,"PUT",  baseURL,headers,999),
       testFail(doFetchRequest,"PATCH",baseURL,headers,999)
      ]));

    QUnit.module("Task 1: doFetchRequest Response Validation", {});

    QUnit.test("GET/DELETE return expected status and body", testAll(
      [
       testStatusBody(doFetchRequest,"GET",baseURL,{"Accept": 'text/html'},undefined,200,"GET Working"),
       testStatus(doFetchRequest,"GET",baseURL+"/not/found",{"Accept": 'text/html'},undefined,404),
       testStatusBody(doFetchRequest,"GET",baseURL,{"Accept": 'application/json'},undefined,200,"{\"text\":\"GET Working\"}"),
       testStatusBody(doFetchRequest,"DELETE",baseURL,{"Accept": 'text/html'},undefined,204,"")
      ]));

    QUnit.test("GET follows redirects", testAll(
      [
       testRedirect(doFetchRequest,"GET",baseURL+"/redirect",{"Accept": 'text/html'},undefined,"Redirect Works")
      ]));

    QUnit.test("POST returns expected status, body and headers", testAll(
      [
       testStatusBodyHeaders(doFetchRequest,"POST",baseURL+"/new",{"Accept": 'text/html'},"X",201,"POST Working\nX",{'Location':'42'}),
       testStatusBodyHeaders(doFetchRequest,"POST",baseURL+"/new",{"Accept": 'application/json'},"JSON",201,"{\"text\":\"POST Working\",\"body\":\"JSON\"}",{'Location':'24', 'Content-Type': 'application/json; charset=utf-8'})
      ]));

    QUnit.test("PUT returns expected status, body and headers", testAll(
      [
       testStatusBody(doFetchRequest,"PUT",baseURL+"/echo",{"Accept": 'text/html'},"X",200,"X"),
       testStatusBodyHeaders(doFetchRequest,"PUT",baseURL+"/echo",{"Accept": 'application/json'},"JSON",200,"{\"text\":\"PUT Working\",\"body\":\"JSON\"}",{'Content-Type': 'application/json; charset=utf-8'})
      ]));

    QUnit.module("Task 2: doJSONRequest Request Validation", {});

    QUnit.test("reject incorrect methods", testAll(
      [
       testFail(doJSONRequest,"WRONG",   jsonURL,headers),
       testFail(doJSONRequest,"RANDOM",  jsonURL,headers),
       testFail(doJSONRequest,"",        jsonURL,headers),
       testFail(doJSONRequest,undefined, jsonURL,headers),
       testFail(doJSONRequest,404,       jsonURL,headers)
      ]));

    QUnit.test("GET, HEAD, OPTIONS, DELETE should not have a body", testAll(
      [
       testFail(doJSONRequest,"GET",    jsonURL,headers,body),
       testFail(doJSONRequest,"HEAD",   jsonURL,headers,body),
       testFail(doJSONRequest,"OPTIONS",jsonURL,headers,body),
       testFail(doJSONRequest,"DELETE", jsonURL,headers,body),
         testOk(doJSONRequest,"GET",    jsonURL,headers),
         //HEAD will also have an empty response body
         //testOk(doJSONRequest,"HEAD",   jsonURL,headers),
         testOk(doJSONRequest,"OPTIONS",jsonURL,headers),
         testOk(doJSONRequest,"DELETE", jsonURL,headers)
      ]));

    QUnit.test("POST, PUT, PATCH should not have a String body", testAll(
      [
       testFail(doJSONRequest,"POST", jsonURL,headers,body),
       testFail(doJSONRequest,"PUT",  jsonURL,headers,body),
       testFail(doJSONRequest,"PATCH",jsonURL,headers,body),
       testFail(doJSONRequest,"POST", jsonURL,headers,999),
       testFail(doJSONRequest,"PUT",  jsonURL,headers,999),
       testFail(doJSONRequest,"PATCH",jsonURL,headers,999),
       testFail(doJSONRequest,"POST", jsonURL,headers,notValidJSON),
       testFail(doJSONRequest,"PUT",  jsonURL,headers,notValidJSON),
       testFail(doJSONRequest,"PATCH",jsonURL,headers,notValidJSON)

      ]));

    QUnit.test("POST, PUT, PATCH require an Object/Array body", testAll(
      [
       testOk(doJSONRequest,"POST", jsonURL,headers,{}),
       testOk(doJSONRequest,"PUT",  jsonURL,headers,{}),
       testOk(doJSONRequest,"PATCH",jsonURL,headers,{}),
       testOk(doJSONRequest,"POST", jsonURL,headers,[]),
       testOk(doJSONRequest,"PUT",  jsonURL,headers,[]),
       testOk(doJSONRequest,"PATCH",jsonURL,headers,[])
      ]));

    QUnit.test("ignore correct accept/content-type headers", testAll(
      [
       testOk(doJSONRequest,"POST", jsonURL,{'Accept': 'application/json'},{}),
       testOk(doJSONRequest,"PUT",  jsonURL,{'Accept': 'application/json'},{}),
       testOk(doJSONRequest,"PATCH",jsonURL,{'Accept': 'application/json'},{}),
       testOk(doJSONRequest,"POST", jsonURL,{'Accept': 'application/json', 'Content-Type': 'application/json'},{}),
       testOk(doJSONRequest,"PUT", jsonURL,{'Accept': 'application/json', 'Content-Type': 'application/json'},{}),
       testOk(doJSONRequest,"PATCH", jsonURL,{'Accept': 'application/json', 'Content-Type': 'application/json'},{}),
       testOk(doJSONRequest,"PUT",  jsonURL,{'Accept': 'application/json'},{}),
       testOk(doJSONRequest,"PATCH",jsonURL,{'Accept': 'application/json'},{}),
       testOk(doJSONRequest,"GET", jsonURL,{'Accept': 'application/json'}),
       testOk(doJSONRequest,"OPTIONS",  jsonURL,{'Accept': 'application/json'}),
       testOk(doJSONRequest,"DELETE",jsonURL,{'Accept': 'application/json'})
      ]));

    QUnit.test("reject incorrect accept/content-type headers", testAll(
      [
       testFail(doJSONRequest,"POST", jsonURL,{'Accept': 'text/plain'},{}),
       testFail(doJSONRequest,"PUT",  jsonURL,{'Accept': 'text/plain'},{}),
       testFail(doJSONRequest,"PATCH",jsonURL,{'Accept': 'text/plain'},{}),
       testFail(doJSONRequest,"POST", jsonURL,{'Accept': 'text/plain', 'Content-Type': 'text/plain'},{}),
       testFail(doJSONRequest,"PUT", jsonURL,{'Accept': 'text/plain', 'Content-Type': 'text/plain'},{}),
       testFail(doJSONRequest,"PATCH", jsonURL,{'Accept': 'text/plain', 'Content-Type': 'text/plain'},{}),
       testFail(doJSONRequest,"PUT",  jsonURL,{'Accept': 'text/plain'},{}),
       testFail(doJSONRequest,"PATCH",jsonURL,{'Accept': 'text/plain'},{}),
       testFail(doJSONRequest,"GET", jsonURL,{'Accept': 'text/plain'}),
       testFail(doJSONRequest,"OPTIONS",  jsonURL,{'Accept': 'text/plain'}),
       testFail(doJSONRequest,"DELETE",jsonURL,{'Accept': 'text/plain'})
      ]));

  QUnit.module("Task 2: doJSONRequest Response Validation", {});

    QUnit.test("returns expected JSON body", testAll(
      [
       testStatusJSONBody(doJSONRequest,"PUT",baseURL+"/echo",headers,jsonBody_req,200,jsonBody_resp("PUT Working")),
       testStatusJSONBody(doJSONRequest,"POST",baseURL+"/new",headers,jsonBody_req,201,jsonBody_resp("POST Working")),
      testStatusJSONBody(doJSONRequest,"DELETE",baseURL,headers,undefined,204,{status: 204, text: 'DELETE Working'}),
      testStatusJSONBody(doJSONRequest,"GET",jsonURL,headers,undefined,200,{text: 'ok'}),
      testStatusJSONBody(doJSONRequest,"PATCH",jsonURL,headers,{},200,{text: 'ok'}),
      testStatusJSONBody(doJSONRequest,"GET",baseURL,headers,undefined,200,{text: 'GET Working'})
      ]));



    </script>
</body>

</html>