summaryrefslogtreecommitdiffstats
path: root/services/common/tests/unit/test_restrequest.js
blob: 9ae5e9429a4d374763d7eb9bf26ebdf3a80a98ca (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

const { RESTRequest } = ChromeUtils.importESModule(
  "resource://services-common/rest.sys.mjs"
);

function run_test() {
  Log.repository.getLogger("Services.Common.RESTRequest").level =
    Log.Level.Trace;
  initTestLogging("Trace");

  run_next_test();
}

/**
 * Initializing a RESTRequest with an invalid URI throws
 * NS_ERROR_MALFORMED_URI.
 */
add_test(function test_invalid_uri() {
  do_check_throws(function () {
    new RESTRequest("an invalid URI");
  }, Cr.NS_ERROR_MALFORMED_URI);
  run_next_test();
});

/**
 * Verify initial values for attributes.
 */
add_test(function test_attributes() {
  let uri = "http://foo.com/bar/baz";
  let request = new RESTRequest(uri);

  Assert.ok(request.uri instanceof Ci.nsIURI);
  Assert.equal(request.uri.spec, uri);
  Assert.equal(request.response, null);
  Assert.equal(request.status, request.NOT_SENT);
  let expectedLoadFlags =
    Ci.nsIRequest.LOAD_BYPASS_CACHE |
    Ci.nsIRequest.INHIBIT_CACHING |
    Ci.nsIRequest.LOAD_ANONYMOUS;
  Assert.equal(request.loadFlags, expectedLoadFlags);

  run_next_test();
});

/**
 * Verify that a proxy auth redirect doesn't break us. This has to be the first
 * request made in the file!
 */
add_task(async function test_proxy_auth_redirect() {
  let pacFetched = false;
  function pacHandler(metadata, response) {
    pacFetched = true;
    let body = 'function FindProxyForURL(url, host) { return "DIRECT"; }';
    response.setStatusLine(metadata.httpVersion, 200, "OK");
    response.setHeader(
      "Content-Type",
      "application/x-ns-proxy-autoconfig",
      false
    );
    response.bodyOutputStream.write(body, body.length);
  }

  let fetched = false;
  function original(metadata, response) {
    fetched = true;
    let body = "TADA!";
    response.setStatusLine(metadata.httpVersion, 200, "OK");
    response.bodyOutputStream.write(body, body.length);
  }

  let server = httpd_setup({
    "/original": original,
    "/pac3": pacHandler,
  });
  PACSystemSettings.PACURI = server.baseURI + "/pac3";
  installFakePAC();

  let req = new RESTRequest(server.baseURI + "/original");
  await req.get();

  Assert.ok(pacFetched);
  Assert.ok(fetched);

  Assert.ok(req.response.success);
  Assert.equal("TADA!", req.response.body);
  uninstallFakePAC();
  await promiseStopServer(server);
});

/**
 * Ensure that failures that cause asyncOpen to throw
 * result in callbacks being invoked.
 * Bug 826086.
 */
add_task(async function test_forbidden_port() {
  let request = new RESTRequest("http://localhost:6000/");

  await Assert.rejects(
    request.get(),
    error => error.result == Cr.NS_ERROR_PORT_ACCESS_NOT_ALLOWED
  );
});

/**
 * Demonstrate API short-hand: create a request and dispatch it immediately.
 */
add_task(async function test_simple_get() {
  let handler = httpd_handler(200, "OK", "Huzzah!");
  let server = httpd_setup({ "/resource": handler });
  let request = new RESTRequest(server.baseURI + "/resource");
  let promiseResponse = request.get();

  Assert.equal(request.status, request.SENT);
  Assert.equal(request.method, "GET");

  let response = await promiseResponse;
  Assert.equal(response, request.response);

  Assert.equal(request.status, request.COMPLETED);
  Assert.ok(response.success);
  Assert.equal(response.status, 200);
  Assert.equal(response.body, "Huzzah!");
  await promiseStopServer(server);
});

/**
 * Test HTTP GET with all bells and whistles.
 */
add_task(async function test_get() {
  let handler = httpd_handler(200, "OK", "Huzzah!");
  let server = httpd_setup({ "/resource": handler });

  let request = new RESTRequest(server.baseURI + "/resource");
  Assert.equal(request.status, request.NOT_SENT);

  let promiseResponse = request.get();

  Assert.equal(request.status, request.SENT);
  Assert.equal(request.method, "GET");

  Assert.ok(!!(request.channel.loadFlags & Ci.nsIRequest.LOAD_BYPASS_CACHE));
  Assert.ok(!!(request.channel.loadFlags & Ci.nsIRequest.INHIBIT_CACHING));

  let response = await promiseResponse;

  Assert.equal(response, request.response);
  Assert.equal(request.status, request.COMPLETED);
  Assert.ok(request.response.success);
  Assert.equal(request.response.status, 200);
  Assert.equal(request.response.body, "Huzzah!");
  Assert.equal(handler.request.method, "GET");

  await Assert.rejects(request.get(), /Request has already been sent/);

  await promiseStopServer(server);
});

/**
 * Test HTTP GET with UTF-8 content, and custom Content-Type.
 */
add_task(async function test_get_utf8() {
  let response = "Hello World or Καλημέρα κόσμε or こんにちは 世界 😺";

  let contentType = "text/plain";
  let charset = true;
  let charsetSuffix = "; charset=UTF-8";

  let server = httpd_setup({
    "/resource": function (req, res) {
      res.setStatusLine(req.httpVersion, 200, "OK");
      res.setHeader(
        "Content-Type",
        contentType + (charset ? charsetSuffix : "")
      );

      let converter = Cc[
        "@mozilla.org/intl/converter-output-stream;1"
      ].createInstance(Ci.nsIConverterOutputStream);
      converter.init(res.bodyOutputStream, "UTF-8");
      converter.writeString(response);
      converter.close();
    },
  });

  // Check if charset in Content-Type is propertly interpreted.
  let request1 = new RESTRequest(server.baseURI + "/resource");
  await request1.get();

  Assert.equal(request1.response.status, 200);
  Assert.equal(request1.response.body, response);
  Assert.equal(
    request1.response.headers["content-type"],
    contentType + charsetSuffix
  );

  // Check that we default to UTF-8 if Content-Type doesn't have a charset
  charset = false;
  let request2 = new RESTRequest(server.baseURI + "/resource");
  await request2.get();
  Assert.equal(request2.response.status, 200);
  Assert.equal(request2.response.body, response);
  Assert.equal(request2.response.headers["content-type"], contentType);
  Assert.equal(request2.response.charset, "utf-8");

  let request3 = new RESTRequest(server.baseURI + "/resource");

  // With the test server we tend to get onDataAvailable in chunks of 8192 (in
  // real network requests there doesn't appear to be any pattern to the size of
  // the data `onDataAvailable` is called with), the smiling cat emoji encodes as
  // 4 bytes, and so when utf8 encoded, the `"a" + "😺".repeat(2048)` will not be
  // aligned onto a codepoint.
  //
  // Since 8192 isn't guaranteed and could easily change, the following string is
  // a) very long, and b) misaligned on roughly 3/4 of the bytes, as a safety
  // measure.
  response = ("a" + "😺".repeat(2048)).repeat(10);

  await request3.get();

  Assert.equal(request3.response.status, 200);

  // Make sure it came through ok, despite the misalignment.
  Assert.equal(request3.response.body, response);

  await promiseStopServer(server);
});

/**
 * Test HTTP POST data is encoded as UTF-8 by default.
 */
add_task(async function test_post_utf8() {
  // We setup a handler that responds with exactly what it received.
  // Given we've already tested above that responses are correctly utf-8
  // decoded we can surmise that the correct response coming back means the
  // input must also have been encoded.
  let server = httpd_setup({
    "/echo": function (req, res) {
      res.setStatusLine(req.httpVersion, 200, "OK");
      res.setHeader("Content-Type", req.getHeader("content-type"));
      // Get the body as bytes and write them back without touching them
      let sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
        Ci.nsIScriptableInputStream
      );
      sis.init(req.bodyInputStream);
      let body = sis.read(sis.available());
      sis.close();
      res.write(body);
    },
  });

  let data = {
    copyright: "©",
    // See the comment in test_get_utf8 about this string.
    long: ("a" + "😺".repeat(2048)).repeat(10),
  };
  let request1 = new RESTRequest(server.baseURI + "/echo");
  await request1.post(data);

  Assert.equal(request1.response.status, 200);
  deepEqual(JSON.parse(request1.response.body), data);
  Assert.equal(
    request1.response.headers["content-type"],
    "application/json; charset=utf-8"
  );

  await promiseStopServer(server);
});

/**
 * Test more variations of charset handling.
 */
add_task(async function test_charsets() {
  let response = "Hello World, I can't speak Russian";

  let contentType = "text/plain";
  let charset = true;
  let charsetSuffix = "; charset=us-ascii";

  let server = httpd_setup({
    "/resource": function (req, res) {
      res.setStatusLine(req.httpVersion, 200, "OK");
      res.setHeader(
        "Content-Type",
        contentType + (charset ? charsetSuffix : "")
      );

      let converter = Cc[
        "@mozilla.org/intl/converter-output-stream;1"
      ].createInstance(Ci.nsIConverterOutputStream);
      converter.init(res.bodyOutputStream, "us-ascii");
      converter.writeString(response);
      converter.close();
    },
  });

  // Check that provided charset overrides hint.
  let request1 = new RESTRequest(server.baseURI + "/resource");
  request1.charset = "not-a-charset";
  await request1.get();
  Assert.equal(request1.response.status, 200);
  Assert.equal(request1.response.body, response);
  Assert.equal(
    request1.response.headers["content-type"],
    contentType + charsetSuffix
  );
  Assert.equal(request1.response.charset, "us-ascii");

  // Check that hint is used if Content-Type doesn't have a charset.
  charset = false;
  let request2 = new RESTRequest(server.baseURI + "/resource");
  request2.charset = "us-ascii";
  await request2.get();

  Assert.equal(request2.response.status, 200);
  Assert.equal(request2.response.body, response);
  Assert.equal(request2.response.headers["content-type"], contentType);
  Assert.equal(request2.response.charset, "us-ascii");

  await promiseStopServer(server);
});

/**
 * Used for testing PATCH/PUT/POST methods.
 */
async function check_posting_data(method) {
  let funcName = method.toLowerCase();
  let handler = httpd_handler(200, "OK", "Got it!");
  let server = httpd_setup({ "/resource": handler });

  let request = new RESTRequest(server.baseURI + "/resource");
  Assert.equal(request.status, request.NOT_SENT);
  let responsePromise = request[funcName]("Hullo?");
  Assert.equal(request.status, request.SENT);
  Assert.equal(request.method, method);

  let response = await responsePromise;

  Assert.equal(response, request.response);

  Assert.equal(request.status, request.COMPLETED);
  Assert.ok(request.response.success);
  Assert.equal(request.response.status, 200);
  Assert.equal(request.response.body, "Got it!");

  Assert.equal(handler.request.method, method);
  Assert.equal(handler.request.body, "Hullo?");
  Assert.equal(handler.request.getHeader("Content-Type"), "text/plain");

  await Assert.rejects(
    request[funcName]("Hai!"),
    /Request has already been sent/
  );

  await promiseStopServer(server);
}

/**
 * Test HTTP PATCH with a simple string argument and default Content-Type.
 */
add_task(async function test_patch() {
  await check_posting_data("PATCH");
});

/**
 * Test HTTP PUT with a simple string argument and default Content-Type.
 */
add_task(async function test_put() {
  await check_posting_data("PUT");
});

/**
 * Test HTTP POST with a simple string argument and default Content-Type.
 */
add_task(async function test_post() {
  await check_posting_data("POST");
});

/**
 * Test HTTP DELETE.
 */
add_task(async function test_delete() {
  let handler = httpd_handler(200, "OK", "Got it!");
  let server = httpd_setup({ "/resource": handler });

  let request = new RESTRequest(server.baseURI + "/resource");
  Assert.equal(request.status, request.NOT_SENT);
  let responsePromise = request.delete();
  Assert.equal(request.status, request.SENT);
  Assert.equal(request.method, "DELETE");

  let response = await responsePromise;
  Assert.equal(response, request.response);

  Assert.equal(request.status, request.COMPLETED);
  Assert.ok(request.response.success);
  Assert.equal(request.response.status, 200);
  Assert.equal(request.response.body, "Got it!");
  Assert.equal(handler.request.method, "DELETE");

  await Assert.rejects(request.delete(), /Request has already been sent/);

  await promiseStopServer(server);
});

/**
 * Test an HTTP response with a non-200 status code.
 */
add_task(async function test_get_404() {
  let handler = httpd_handler(404, "Not Found", "Cannae find it!");
  let server = httpd_setup({ "/resource": handler });

  let request = new RESTRequest(server.baseURI + "/resource");
  await request.get();

  Assert.equal(request.status, request.COMPLETED);
  Assert.ok(!request.response.success);
  Assert.equal(request.response.status, 404);
  Assert.equal(request.response.body, "Cannae find it!");

  await promiseStopServer(server);
});

/**
 * The 'data' argument to PUT, if not a string already, is automatically
 * stringified as JSON.
 */
add_task(async function test_put_json() {
  let handler = httpd_handler(200, "OK");
  let server = httpd_setup({ "/resource": handler });

  let sample_data = {
    some: "sample_data",
    injson: "format",
    number: 42,
  };
  let request = new RESTRequest(server.baseURI + "/resource");
  await request.put(sample_data);

  Assert.equal(request.status, request.COMPLETED);
  Assert.ok(request.response.success);
  Assert.equal(request.response.status, 200);
  Assert.equal(request.response.body, "");

  Assert.equal(handler.request.method, "PUT");
  Assert.equal(handler.request.body, JSON.stringify(sample_data));
  Assert.equal(
    handler.request.getHeader("Content-Type"),
    "application/json; charset=utf-8"
  );

  await promiseStopServer(server);
});

/**
 * The 'data' argument to POST, if not a string already, is automatically
 * stringified as JSON.
 */
add_task(async function test_post_json() {
  let handler = httpd_handler(200, "OK");
  let server = httpd_setup({ "/resource": handler });

  let sample_data = {
    some: "sample_data",
    injson: "format",
    number: 42,
  };
  let request = new RESTRequest(server.baseURI + "/resource");
  await request.post(sample_data);

  Assert.equal(request.status, request.COMPLETED);
  Assert.ok(request.response.success);
  Assert.equal(request.response.status, 200);
  Assert.equal(request.response.body, "");

  Assert.equal(handler.request.method, "POST");
  Assert.equal(handler.request.body, JSON.stringify(sample_data));
  Assert.equal(
    handler.request.getHeader("Content-Type"),
    "application/json; charset=utf-8"
  );

  await promiseStopServer(server);
});

/**
 * The content-type will be text/plain without a charset if the 'data' argument
 * to POST is already a string.
 */
add_task(async function test_post_json() {
  let handler = httpd_handler(200, "OK");
  let server = httpd_setup({ "/resource": handler });

  let sample_data = "hello";
  let request = new RESTRequest(server.baseURI + "/resource");
  await request.post(sample_data);
  Assert.equal(request.status, request.COMPLETED);
  Assert.ok(request.response.success);
  Assert.equal(request.response.status, 200);
  Assert.equal(request.response.body, "");

  Assert.equal(handler.request.method, "POST");
  Assert.equal(handler.request.body, sample_data);
  Assert.equal(handler.request.getHeader("Content-Type"), "text/plain");

  await promiseStopServer(server);
});

/**
 * HTTP PUT with a custom Content-Type header.
 */
add_task(async function test_put_override_content_type() {
  let handler = httpd_handler(200, "OK");
  let server = httpd_setup({ "/resource": handler });

  let request = new RESTRequest(server.baseURI + "/resource");
  request.setHeader("Content-Type", "application/lolcat");
  await request.put("O HAI!!1!");

  Assert.equal(request.status, request.COMPLETED);
  Assert.ok(request.response.success);
  Assert.equal(request.response.status, 200);
  Assert.equal(request.response.body, "");

  Assert.equal(handler.request.method, "PUT");
  Assert.equal(handler.request.body, "O HAI!!1!");
  Assert.equal(handler.request.getHeader("Content-Type"), "application/lolcat");

  await promiseStopServer(server);
});

/**
 * HTTP POST with a custom Content-Type header.
 */
add_task(async function test_post_override_content_type() {
  let handler = httpd_handler(200, "OK");
  let server = httpd_setup({ "/resource": handler });

  let request = new RESTRequest(server.baseURI + "/resource");
  request.setHeader("Content-Type", "application/lolcat");
  await request.post("O HAI!!1!");

  Assert.equal(request.status, request.COMPLETED);
  Assert.ok(request.response.success);
  Assert.equal(request.response.status, 200);
  Assert.equal(request.response.body, "");

  Assert.equal(handler.request.method, "POST");
  Assert.equal(handler.request.body, "O HAI!!1!");
  Assert.equal(handler.request.getHeader("Content-Type"), "application/lolcat");

  await promiseStopServer(server);
});

/**
 * No special headers are sent by default on a GET request.
 */
add_task(async function test_get_no_headers() {
  let handler = httpd_handler(200, "OK");
  let server = httpd_setup({ "/resource": handler });

  let ignore_headers = [
    "host",
    "user-agent",
    "accept",
    "accept-language",
    "accept-encoding",
    "accept-charset",
    "keep-alive",
    "connection",
    "pragma",
    "cache-control",
    "content-length",
    "sec-fetch-dest",
    "sec-fetch-mode",
    "sec-fetch-site",
    "sec-fetch-user",
  ];
  let request = new RESTRequest(server.baseURI + "/resource");
  await request.get();

  Assert.equal(request.response.status, 200);
  Assert.equal(request.response.body, "");

  let server_headers = handler.request.headers;
  while (server_headers.hasMoreElements()) {
    let header = server_headers.getNext().toString();
    if (!ignore_headers.includes(header)) {
      do_throw("Got unexpected header!");
    }
  }

  await promiseStopServer(server);
});

/**
 * Client includes default Accept header in API requests
 */
add_task(async function test_default_accept_headers() {
  let handler = httpd_handler(200, "OK");
  let server = httpd_setup({ "/resource": handler });

  let request = new RESTRequest(server.baseURI + "/resource");
  await request.get();

  Assert.equal(request.response.status, 200);
  Assert.equal(request.response.body, "");

  let accept_header = handler.request.getHeader("accept");

  Assert.ok(!accept_header.includes("text/html"));
  Assert.ok(!accept_header.includes("application/xhtml+xml"));
  Assert.ok(!accept_header.includes("applcation/xml"));

  Assert.ok(
    accept_header.includes("application/json") ||
      accept_header.includes("application/newlines")
  );

  await promiseStopServer(server);
});

/**
 * Test changing the URI after having created the request.
 */
add_task(async function test_changing_uri() {
  let handler = httpd_handler(200, "OK");
  let server = httpd_setup({ "/resource": handler });

  let request = new RESTRequest("http://localhost:1234/the-wrong-resource");
  request.uri = CommonUtils.makeURI(server.baseURI + "/resource");
  let response = await request.get();
  Assert.equal(response.status, 200);
  await promiseStopServer(server);
});

/**
 * Test setting HTTP request headers.
 */
add_task(async function test_request_setHeader() {
  let handler = httpd_handler(200, "OK");
  let server = httpd_setup({ "/resource": handler });

  let request = new RESTRequest(server.baseURI + "/resource");

  request.setHeader("X-What-Is-Weave", "awesome");
  request.setHeader("X-WHAT-is-Weave", "more awesomer");
  request.setHeader("Another-Header", "Hello World");
  await request.get();

  Assert.equal(request.response.status, 200);
  Assert.equal(request.response.body, "");

  Assert.equal(handler.request.getHeader("X-What-Is-Weave"), "more awesomer");
  Assert.equal(handler.request.getHeader("another-header"), "Hello World");

  await promiseStopServer(server);
});

/**
 * Test receiving HTTP response headers.
 */
add_task(async function test_response_headers() {
  function handler(request, response) {
    response.setHeader("X-What-Is-Weave", "awesome");
    response.setHeader("Another-Header", "Hello World");
    response.setStatusLine(request.httpVersion, 200, "OK");
  }
  let server = httpd_setup({ "/resource": handler });
  let request = new RESTRequest(server.baseURI + "/resource");
  await request.get();

  Assert.equal(request.response.status, 200);
  Assert.equal(request.response.body, "");

  Assert.equal(request.response.headers["x-what-is-weave"], "awesome");
  Assert.equal(request.response.headers["another-header"], "Hello World");

  await promiseStopServer(server);
});

/**
 * The onComplete() handler gets called in case of any network errors
 * (e.g. NS_ERROR_CONNECTION_REFUSED).
 */
add_task(async function test_connection_refused() {
  let request = new RESTRequest("http://localhost:1234/resource");

  // Fail the test if we resolve, return the error if we reject
  await Assert.rejects(
    request.get(),
    error =>
      error.result == Cr.NS_ERROR_CONNECTION_REFUSED &&
      error.message == "NS_ERROR_CONNECTION_REFUSED"
  );

  Assert.equal(request.status, request.COMPLETED);
});

/**
 * Abort a request that just sent off.
 */
add_task(async function test_abort() {
  function handler() {
    do_throw("Shouldn't have gotten here!");
  }
  let server = httpd_setup({ "/resource": handler });

  let request = new RESTRequest(server.baseURI + "/resource");

  // Aborting a request that hasn't been sent yet is pointless and will throw.
  do_check_throws(function () {
    request.abort();
  });

  let responsePromise = request.get();
  request.abort();

  // Aborting an already aborted request is pointless and will throw.
  do_check_throws(function () {
    request.abort();
  });

  Assert.equal(request.status, request.ABORTED);

  await Assert.rejects(responsePromise, /NS_BINDING_ABORTED/);

  await promiseStopServer(server);
});

/**
 * A non-zero 'timeout' property specifies the amount of seconds to wait after
 * channel activity until the request is automatically canceled.
 */
add_task(async function test_timeout() {
  let server = new HttpServer();
  let server_connection;
  server._handler.handleResponse = function (connection) {
    // This is a handler that doesn't do anything, just keeps the connection
    // open, thereby mimicking a timing out connection. We keep a reference to
    // the open connection for later so it can be properly disposed of. That's
    // why you really only want to make one HTTP request to this server ever.
    server_connection = connection;
  };
  server.start();
  let identity = server.identity;
  let uri =
    identity.primaryScheme +
    "://" +
    identity.primaryHost +
    ":" +
    identity.primaryPort;

  let request = new RESTRequest(uri + "/resource");
  request.timeout = 0.1; // 100 milliseconds

  await Assert.rejects(
    request.get(),
    error => error.result == Cr.NS_ERROR_NET_TIMEOUT
  );

  Assert.equal(request.status, request.ABORTED);

  // server_connection is undefined on the Android emulator for reasons
  // unknown. Yet, we still get here. If this test is refactored, we should
  // investigate the reason why the above callback is behaving differently.
  if (server_connection) {
    _("Closing connection.");
    server_connection.close();
  }
  await promiseStopServer(server);
});

add_task(async function test_new_channel() {
  _("Ensure a redirect to a new channel is handled properly.");

  function checkUA(metadata) {
    let ua = metadata.getHeader("User-Agent");
    _("User-Agent is " + ua);
    Assert.equal("foo bar", ua);
  }

  let redirectRequested = false;
  let redirectURL;
  function redirectHandler(metadata, response) {
    checkUA(metadata);
    redirectRequested = true;

    let body = "Redirecting";
    response.setStatusLine(metadata.httpVersion, 307, "TEMPORARY REDIRECT");
    response.setHeader("Location", redirectURL);
    response.bodyOutputStream.write(body, body.length);
  }

  let resourceRequested = false;
  function resourceHandler(metadata, response) {
    checkUA(metadata);
    resourceRequested = true;

    let body = "Test";
    response.setHeader("Content-Type", "text/plain");
    response.bodyOutputStream.write(body, body.length);
  }

  let server1 = httpd_setup({ "/redirect": redirectHandler });
  let server2 = httpd_setup({ "/resource": resourceHandler });
  redirectURL = server2.baseURI + "/resource";

  let request = new RESTRequest(server1.baseURI + "/redirect");
  request.setHeader("User-Agent", "foo bar");

  // Swizzle in our own fakery, because this redirect is neither
  // internal nor URI-preserving. RESTRequest's policy is to only
  // copy headers under certain circumstances.
  let protoMethod = request.shouldCopyOnRedirect;
  request.shouldCopyOnRedirect = function wrapped(o, n, f) {
    // Check the default policy.
    Assert.ok(!protoMethod.call(this, o, n, f));
    return true;
  };

  let response = await request.get();

  Assert.equal(200, response.status);
  Assert.equal("Test", response.body);
  Assert.ok(redirectRequested);
  Assert.ok(resourceRequested);

  await promiseStopServer(server1);
  await promiseStopServer(server2);
});

add_task(async function test_not_sending_cookie() {
  function handler(metadata, response) {
    let body = "COOKIE!";
    response.setStatusLine(metadata.httpVersion, 200, "OK");
    response.bodyOutputStream.write(body, body.length);
    Assert.ok(!metadata.hasHeader("Cookie"));
  }
  let server = httpd_setup({ "/test": handler });

  let uri = CommonUtils.makeURI(server.baseURI);
  let channel = NetUtil.newChannel({
    uri,
    loadUsingSystemPrincipal: true,
    contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
  });
  Services.cookies.setCookieStringFromHttp(uri, "test=test; path=/;", channel);

  let res = new RESTRequest(server.baseURI + "/test");
  let response = await res.get();

  Assert.ok(response.success);
  Assert.equal("COOKIE!", response.body);

  await promiseStopServer(server);
});