Wed, 31 Dec 2014 07:53:36 +0100
Correct small whitespace inconsistency, lost while renaming variables.
1 /* Any copyright is dedicated to the Public Domain.
2 http://creativecommons.org/publicdomain/zero/1.0/ */
4 Cu.import("resource://services-common/async.js");
5 Cu.import("resource://services-common/rest.js");
6 Cu.import("resource://services-common/utils.js");
7 Cu.import("resource://testing-common/services-common/storageserver.js");
9 const DEFAULT_USER = "123";
10 const DEFAULT_PASSWORD = "password";
12 /**
13 * Helper function to prepare a RESTRequest against the server.
14 */
15 function localRequest(server, path, user=DEFAULT_USER, password=DEFAULT_PASSWORD) {
16 _("localRequest: " + path);
17 let identity = server.server.identity;
18 let url = identity.primaryScheme + "://" + identity.primaryHost + ":" +
19 identity.primaryPort + path;
20 _("url: " + url);
21 let req = new RESTRequest(url);
23 let header = basic_auth_header(user, password);
24 req.setHeader("Authorization", header);
25 req.setHeader("Accept", "application/json");
27 return req;
28 }
30 /**
31 * Helper function to validate an HTTP response from the server.
32 */
33 function validateResponse(response) {
34 do_check_true("x-timestamp" in response.headers);
36 if ("content-length" in response.headers) {
37 let cl = parseInt(response.headers["content-length"]);
39 if (cl != 0) {
40 do_check_true("content-type" in response.headers);
41 do_check_eq("application/json", response.headers["content-type"]);
42 }
43 }
45 if (response.status == 204 || response.status == 304) {
46 do_check_false("content-type" in response.headers);
48 if ("content-length" in response.headers) {
49 do_check_eq(response.headers["content-length"], "0");
50 }
51 }
53 if (response.status == 405) {
54 do_check_true("allow" in response.headers);
55 }
56 }
58 /**
59 * Helper function to synchronously wait for a response and validate it.
60 */
61 function waitAndValidateResponse(cb, request) {
62 let error = cb.wait();
64 if (!error) {
65 validateResponse(request.response);
66 }
68 return error;
69 }
71 /**
72 * Helper function to synchronously perform a GET request.
73 *
74 * @return Error instance or null if no error.
75 */
76 function doGetRequest(request) {
77 let cb = Async.makeSpinningCallback();
78 request.get(cb);
80 return waitAndValidateResponse(cb, request);
81 }
83 /**
84 * Helper function to synchronously perform a PUT request.
85 *
86 * @return Error instance or null if no error.
87 */
88 function doPutRequest(request, data) {
89 let cb = Async.makeSpinningCallback();
90 request.put(data, cb);
92 return waitAndValidateResponse(cb, request);
93 }
95 /**
96 * Helper function to synchronously perform a DELETE request.
97 *
98 * @return Error or null if no error was encountered.
99 */
100 function doDeleteRequest(request) {
101 let cb = Async.makeSpinningCallback();
102 request.delete(cb);
104 return waitAndValidateResponse(cb, request);
105 }
107 function run_test() {
108 Log.repository.getLogger("Services.Common.Test.StorageServer").level =
109 Log.Level.Trace;
110 initTestLogging();
112 run_next_test();
113 }
115 add_test(function test_creation() {
116 _("Ensure a simple server can be created.");
118 // Explicit callback for this one.
119 let server = new StorageServer({
120 __proto__: StorageServerCallback,
121 });
122 do_check_true(!!server);
124 server.start(-1, function () {
125 _("Started on " + server.port);
126 server.stop(run_next_test);
127 });
128 });
130 add_test(function test_synchronous_start() {
131 _("Ensure starting using startSynchronous works.");
133 let server = new StorageServer();
134 server.startSynchronous();
135 server.stop(run_next_test);
136 });
138 add_test(function test_url_parsing() {
139 _("Ensure server parses URLs properly.");
141 let server = new StorageServer();
143 // Check that we can parse a BSO URI.
144 let parts = server.pathRE.exec("/2.0/12345/storage/crypto/keys");
145 let [all, version, user, first, rest] = parts;
146 do_check_eq(all, "/2.0/12345/storage/crypto/keys");
147 do_check_eq(version, "2.0");
148 do_check_eq(user, "12345");
149 do_check_eq(first, "storage");
150 do_check_eq(rest, "crypto/keys");
151 do_check_eq(null, server.pathRE.exec("/nothing/else"));
153 // Check that we can parse a collection URI.
154 parts = server.pathRE.exec("/2.0/123/storage/crypto");
155 let [all, version, user, first, rest] = parts;
156 do_check_eq(all, "/2.0/123/storage/crypto");
157 do_check_eq(version, "2.0");
158 do_check_eq(user, "123");
159 do_check_eq(first, "storage");
160 do_check_eq(rest, "crypto");
162 // We don't allow trailing slash on storage URI.
163 parts = server.pathRE.exec("/2.0/1234/storage/");
164 do_check_eq(parts, undefined);
166 // storage alone is a valid request.
167 parts = server.pathRE.exec("/2.0/123456/storage");
168 let [all, version, user, first, rest] = parts;
169 do_check_eq(all, "/2.0/123456/storage");
170 do_check_eq(version, "2.0");
171 do_check_eq(user, "123456");
172 do_check_eq(first, "storage");
173 do_check_eq(rest, undefined);
175 parts = server.storageRE.exec("storage");
176 let [all, storage, collection, id] = parts;
177 do_check_eq(all, "storage");
178 do_check_eq(collection, undefined);
180 run_next_test();
181 });
183 add_test(function test_basic_http() {
184 let server = new StorageServer();
185 server.registerUser("345", "password");
186 do_check_true(server.userExists("345"));
187 server.startSynchronous();
189 _("Started on " + server.port);
190 do_check_eq(server.requestCount, 0);
191 let req = localRequest(server, "/2.0/storage/crypto/keys");
192 _("req is " + req);
193 req.get(function (err) {
194 do_check_eq(null, err);
195 do_check_eq(server.requestCount, 1);
196 server.stop(run_next_test);
197 });
198 });
200 add_test(function test_info_collections() {
201 let server = new StorageServer();
202 server.registerUser("123", "password");
203 server.startSynchronous();
205 let path = "/2.0/123/info/collections";
207 _("info/collections on empty server should be empty object.");
208 let request = localRequest(server, path, "123", "password");
209 let error = doGetRequest(request);
210 do_check_eq(error, null);
211 do_check_eq(request.response.status, 200);
212 do_check_eq(request.response.body, "{}");
214 _("Creating an empty collection should result in collection appearing.");
215 let coll = server.createCollection("123", "col1");
216 let request = localRequest(server, path, "123", "password");
217 let error = doGetRequest(request);
218 do_check_eq(error, null);
219 do_check_eq(request.response.status, 200);
220 let info = JSON.parse(request.response.body);
221 do_check_attribute_count(info, 1);
222 do_check_true("col1" in info);
223 do_check_eq(info.col1, coll.timestamp);
225 server.stop(run_next_test);
226 });
228 add_test(function test_bso_get_existing() {
229 _("Ensure that BSO retrieval works.");
231 let server = new StorageServer();
232 server.registerUser("123", "password");
233 server.createContents("123", {
234 test: {"bso": {"foo": "bar"}}
235 });
236 server.startSynchronous();
238 let coll = server.user("123").collection("test");
240 let request = localRequest(server, "/2.0/123/storage/test/bso", "123",
241 "password");
242 let error = doGetRequest(request);
243 do_check_eq(error, null);
244 do_check_eq(request.response.status, 200);
245 do_check_eq(request.response.headers["content-type"], "application/json");
246 let bso = JSON.parse(request.response.body);
247 do_check_attribute_count(bso, 3);
248 do_check_eq(bso.id, "bso");
249 do_check_eq(bso.modified, coll.bso("bso").modified);
250 let payload = JSON.parse(bso.payload);
251 do_check_attribute_count(payload, 1);
252 do_check_eq(payload.foo, "bar");
254 server.stop(run_next_test);
255 });
257 add_test(function test_percent_decoding() {
258 _("Ensure query string arguments with percent encoded are handled.");
260 let server = new StorageServer();
261 server.registerUser("123", "password");
262 server.startSynchronous();
264 let coll = server.user("123").createCollection("test");
265 coll.insert("001", {foo: "bar"});
266 coll.insert("002", {bar: "foo"});
268 let request = localRequest(server, "/2.0/123/storage/test?ids=001%2C002",
269 "123", "password");
270 let error = doGetRequest(request);
271 do_check_null(error);
272 do_check_eq(request.response.status, 200);
273 let items = JSON.parse(request.response.body).items;
274 do_check_attribute_count(items, 2);
276 server.stop(run_next_test);
277 });
279 add_test(function test_bso_404() {
280 _("Ensure the server responds with a 404 if a BSO does not exist.");
282 let server = new StorageServer();
283 server.registerUser("123", "password");
284 server.createContents("123", {
285 test: {}
286 });
287 server.startSynchronous();
289 let request = localRequest(server, "/2.0/123/storage/test/foo");
290 let error = doGetRequest(request);
291 do_check_eq(error, null);
293 do_check_eq(request.response.status, 404);
294 do_check_false("content-type" in request.response.headers);
296 server.stop(run_next_test);
297 });
299 add_test(function test_bso_if_modified_since_304() {
300 _("Ensure the server responds properly to X-If-Modified-Since for BSOs.");
302 let server = new StorageServer();
303 server.registerUser("123", "password");
304 server.createContents("123", {
305 test: {bso: {foo: "bar"}}
306 });
307 server.startSynchronous();
309 let coll = server.user("123").collection("test");
310 do_check_neq(coll, null);
312 // Rewind clock just in case.
313 coll.timestamp -= 10000;
314 coll.bso("bso").modified -= 10000;
316 let request = localRequest(server, "/2.0/123/storage/test/bso",
317 "123", "password");
318 request.setHeader("X-If-Modified-Since", "" + server.serverTime());
319 let error = doGetRequest(request);
320 do_check_eq(null, error);
322 do_check_eq(request.response.status, 304);
323 do_check_false("content-type" in request.response.headers);
325 let request = localRequest(server, "/2.0/123/storage/test/bso",
326 "123", "password");
327 request.setHeader("X-If-Modified-Since", "" + (server.serverTime() - 20000));
328 let error = doGetRequest(request);
329 do_check_eq(null, error);
330 do_check_eq(request.response.status, 200);
331 do_check_eq(request.response.headers["content-type"], "application/json");
333 server.stop(run_next_test);
334 });
336 add_test(function test_bso_if_unmodified_since() {
337 _("Ensure X-If-Unmodified-Since works properly on BSOs.");
339 let server = new StorageServer();
340 server.registerUser("123", "password");
341 server.createContents("123", {
342 test: {bso: {foo: "bar"}}
343 });
344 server.startSynchronous();
346 let coll = server.user("123").collection("test");
347 do_check_neq(coll, null);
349 let time = coll.bso("bso").modified;
351 _("Ensure we get a 412 for specified times older than server time.");
352 let request = localRequest(server, "/2.0/123/storage/test/bso",
353 "123", "password");
354 request.setHeader("X-If-Unmodified-Since", time - 5000);
355 request.setHeader("Content-Type", "application/json");
356 let payload = JSON.stringify({"payload": "foobar"});
357 let error = doPutRequest(request, payload);
358 do_check_eq(null, error);
359 do_check_eq(request.response.status, 412);
361 _("Ensure we get a 204 if update goes through.");
362 let request = localRequest(server, "/2.0/123/storage/test/bso",
363 "123", "password");
364 request.setHeader("Content-Type", "application/json");
365 request.setHeader("X-If-Unmodified-Since", time + 1);
366 let error = doPutRequest(request, payload);
367 do_check_eq(null, error);
368 do_check_eq(request.response.status, 204);
369 do_check_true(coll.timestamp > time);
371 // Not sure why a client would send X-If-Unmodified-Since if a BSO doesn't
372 // exist. But, why not test it?
373 _("Ensure we get a 201 if creation goes through.");
374 let request = localRequest(server, "/2.0/123/storage/test/none",
375 "123", "password");
376 request.setHeader("Content-Type", "application/json");
377 request.setHeader("X-If-Unmodified-Since", time);
378 let error = doPutRequest(request, payload);
379 do_check_eq(null, error);
380 do_check_eq(request.response.status, 201);
382 server.stop(run_next_test);
383 });
385 add_test(function test_bso_delete_not_exist() {
386 _("Ensure server behaves properly when deleting a BSO that does not exist.");
388 let server = new StorageServer();
389 server.registerUser("123", "password");
390 server.user("123").createCollection("empty");
391 server.startSynchronous();
393 server.callback.onItemDeleted = function onItemDeleted(username, collection,
394 id) {
395 do_throw("onItemDeleted should not have been called.");
396 };
398 let request = localRequest(server, "/2.0/123/storage/empty/nada",
399 "123", "password");
400 let error = doDeleteRequest(request);
401 do_check_eq(error, null);
402 do_check_eq(request.response.status, 404);
403 do_check_false("content-type" in request.response.headers);
405 server.stop(run_next_test);
406 });
408 add_test(function test_bso_delete_exists() {
409 _("Ensure proper semantics when deleting a BSO that exists.");
411 let server = new StorageServer();
412 server.registerUser("123", "password");
413 server.startSynchronous();
415 let coll = server.user("123").createCollection("test");
416 let bso = coll.insert("myid", {foo: "bar"});
417 let timestamp = coll.timestamp;
419 server.callback.onItemDeleted = function onDeleted(username, collection, id) {
420 delete server.callback.onItemDeleted;
421 do_check_eq(username, "123");
422 do_check_eq(collection, "test");
423 do_check_eq(id, "myid");
424 };
426 let request = localRequest(server, "/2.0/123/storage/test/myid",
427 "123", "password");
428 let error = doDeleteRequest(request);
429 do_check_eq(error, null);
430 do_check_eq(request.response.status, 204);
431 do_check_eq(coll.bsos().length, 0);
432 do_check_true(coll.timestamp > timestamp);
434 _("On next request the BSO should not exist.");
435 let request = localRequest(server, "/2.0/123/storage/test/myid",
436 "123", "password");
437 let error = doGetRequest(request);
438 do_check_eq(error, null);
439 do_check_eq(request.response.status, 404);
441 server.stop(run_next_test);
442 });
444 add_test(function test_bso_delete_unmodified() {
445 _("Ensure X-If-Unmodified-Since works when deleting BSOs.");
447 let server = new StorageServer();
448 server.startSynchronous();
449 server.registerUser("123", "password");
450 let coll = server.user("123").createCollection("test");
451 let bso = coll.insert("myid", {foo: "bar"});
453 let modified = bso.modified;
455 _("Issuing a DELETE with an older time should fail.");
456 let path = "/2.0/123/storage/test/myid";
457 let request = localRequest(server, path, "123", "password");
458 request.setHeader("X-If-Unmodified-Since", modified - 1000);
459 let error = doDeleteRequest(request);
460 do_check_eq(error, null);
461 do_check_eq(request.response.status, 412);
462 do_check_false("content-type" in request.response.headers);
463 do_check_neq(coll.bso("myid"), null);
465 _("Issuing a DELETE with a newer time should work.");
466 let request = localRequest(server, path, "123", "password");
467 request.setHeader("X-If-Unmodified-Since", modified + 1000);
468 let error = doDeleteRequest(request);
469 do_check_eq(error, null);
470 do_check_eq(request.response.status, 204);
471 do_check_true(coll.bso("myid").deleted);
473 server.stop(run_next_test);
474 });
476 add_test(function test_collection_get_unmodified_since() {
477 _("Ensure conditional unmodified get on collection works when it should.");
479 let server = new StorageServer();
480 server.registerUser("123", "password");
481 server.startSynchronous();
482 let collection = server.user("123").createCollection("testcoll");
483 collection.insert("bso0", {foo: "bar"});
485 let serverModified = collection.timestamp;
487 let request1 = localRequest(server, "/2.0/123/storage/testcoll",
488 "123", "password");
489 request1.setHeader("X-If-Unmodified-Since", serverModified);
490 let error = doGetRequest(request1);
491 do_check_null(error);
492 do_check_eq(request1.response.status, 200);
494 let request2 = localRequest(server, "/2.0/123/storage/testcoll",
495 "123", "password");
496 request2.setHeader("X-If-Unmodified-Since", serverModified - 1);
497 let error = doGetRequest(request2);
498 do_check_null(error);
499 do_check_eq(request2.response.status, 412);
501 server.stop(run_next_test);
502 });
504 add_test(function test_bso_get_unmodified_since() {
505 _("Ensure conditional unmodified get on BSO works appropriately.");
507 let server = new StorageServer();
508 server.registerUser("123", "password");
509 server.startSynchronous();
510 let collection = server.user("123").createCollection("testcoll");
511 let bso = collection.insert("bso0", {foo: "bar"});
513 let serverModified = bso.modified;
515 let request1 = localRequest(server, "/2.0/123/storage/testcoll/bso0",
516 "123", "password");
517 request1.setHeader("X-If-Unmodified-Since", serverModified);
518 let error = doGetRequest(request1);
519 do_check_null(error);
520 do_check_eq(request1.response.status, 200);
522 let request2 = localRequest(server, "/2.0/123/storage/testcoll/bso0",
523 "123", "password");
524 request2.setHeader("X-If-Unmodified-Since", serverModified - 1);
525 let error = doGetRequest(request2);
526 do_check_null(error);
527 do_check_eq(request2.response.status, 412);
529 server.stop(run_next_test);
530 });
532 add_test(function test_missing_collection_404() {
533 _("Ensure a missing collection returns a 404.");
535 let server = new StorageServer();
536 server.registerUser("123", "password");
537 server.startSynchronous();
539 let request = localRequest(server, "/2.0/123/storage/none", "123", "password");
540 let error = doGetRequest(request);
541 do_check_eq(error, null);
542 do_check_eq(request.response.status, 404);
543 do_check_false("content-type" in request.response.headers);
545 server.stop(run_next_test);
546 });
548 add_test(function test_get_storage_405() {
549 _("Ensure that a GET on /storage results in a 405.");
551 let server = new StorageServer();
552 server.registerUser("123", "password");
553 server.startSynchronous();
555 let request = localRequest(server, "/2.0/123/storage", "123", "password");
556 let error = doGetRequest(request);
557 do_check_eq(error, null);
558 do_check_eq(request.response.status, 405);
559 do_check_eq(request.response.headers["allow"], "DELETE");
561 server.stop(run_next_test);
562 });
564 add_test(function test_delete_storage() {
565 _("Ensure that deleting all of storage works.");
567 let server = new StorageServer();
568 server.registerUser("123", "password");
569 server.createContents("123", {
570 foo: {a: {foo: "bar"}, b: {bar: "foo"}},
571 baz: {c: {bob: "law"}, blah: {law: "blog"}}
572 });
574 server.startSynchronous();
576 let request = localRequest(server, "/2.0/123/storage", "123", "password");
577 let error = doDeleteRequest(request);
578 do_check_eq(error, null);
579 do_check_eq(request.response.status, 204);
580 do_check_attribute_count(server.users["123"].collections, 0);
582 server.stop(run_next_test);
583 });
585 add_test(function test_x_num_records() {
586 let server = new StorageServer();
587 server.registerUser("123", "password");
589 server.createContents("123", {
590 crypto: {foos: {foo: "bar"},
591 bars: {foo: "baz"}}
592 });
593 server.startSynchronous();
594 let bso = localRequest(server, "/2.0/123/storage/crypto/foos");
595 bso.get(function (err) {
596 // BSO fetches don't have one.
597 do_check_false("x-num-records" in this.response.headers);
598 let col = localRequest(server, "/2.0/123/storage/crypto");
599 col.get(function (err) {
600 // Collection fetches do.
601 do_check_eq(this.response.headers["x-num-records"], "2");
602 server.stop(run_next_test);
603 });
604 });
605 });
607 add_test(function test_put_delete_put() {
608 _("Bug 790397: Ensure BSO deleted flag is reset on PUT.");
610 let server = new StorageServer();
611 server.registerUser("123", "password");
612 server.createContents("123", {
613 test: {bso: {foo: "bar"}}
614 });
615 server.startSynchronous();
617 _("Ensure we can PUT an existing record.");
618 let request1 = localRequest(server, "/2.0/123/storage/test/bso", "123", "password");
619 request1.setHeader("Content-Type", "application/json");
620 let payload1 = JSON.stringify({"payload": "foobar"});
621 let error1 = doPutRequest(request1, payload1);
622 do_check_eq(null, error1);
623 do_check_eq(request1.response.status, 204);
625 _("Ensure we can DELETE it.");
626 let request2 = localRequest(server, "/2.0/123/storage/test/bso", "123", "password");
627 let error2 = doDeleteRequest(request2);
628 do_check_eq(error2, null);
629 do_check_eq(request2.response.status, 204);
630 do_check_false("content-type" in request2.response.headers);
632 _("Ensure we can PUT a previously deleted record.");
633 let request3 = localRequest(server, "/2.0/123/storage/test/bso", "123", "password");
634 request3.setHeader("Content-Type", "application/json");
635 let payload3 = JSON.stringify({"payload": "foobar"});
636 let error3 = doPutRequest(request3, payload3);
637 do_check_eq(null, error3);
638 do_check_eq(request3.response.status, 201);
640 _("Ensure we can GET the re-uploaded record.");
641 let request4 = localRequest(server, "/2.0/123/storage/test/bso", "123", "password");
642 let error4 = doGetRequest(request4);
643 do_check_eq(error4, null);
644 do_check_eq(request4.response.status, 200);
645 do_check_eq(request4.response.headers["content-type"], "application/json");
647 server.stop(run_next_test);
648 });
650 add_test(function test_collection_get_newer() {
651 _("Ensure get with newer argument on collection works.");
653 let server = new StorageServer();
654 server.registerUser("123", "password");
655 server.startSynchronous();
657 let coll = server.user("123").createCollection("test");
658 let bso1 = coll.insert("001", {foo: "bar"});
659 let bso2 = coll.insert("002", {bar: "foo"});
661 // Don't want both records to have the same timestamp.
662 bso2.modified = bso1.modified + 1000;
664 function newerRequest(newer) {
665 return localRequest(server, "/2.0/123/storage/test?newer=" + newer,
666 "123", "password");
667 }
669 let request1 = newerRequest(0);
670 let error1 = doGetRequest(request1);
671 do_check_null(error1);
672 do_check_eq(request1.response.status, 200);
673 let items1 = JSON.parse(request1.response.body).items;
674 do_check_attribute_count(items1, 2);
676 let request2 = newerRequest(bso1.modified + 1);
677 let error2 = doGetRequest(request2);
678 do_check_null(error2);
679 do_check_eq(request2.response.status, 200);
680 let items2 = JSON.parse(request2.response.body).items;
681 do_check_attribute_count(items2, 1);
683 let request3 = newerRequest(bso2.modified + 1);
684 let error3 = doGetRequest(request3);
685 do_check_null(error3);
686 do_check_eq(request3.response.status, 200);
687 let items3 = JSON.parse(request3.response.body).items;
688 do_check_attribute_count(items3, 0);
690 server.stop(run_next_test);
691 });