summaryrefslogtreecommitdiffstats
path: root/services/common/tests/unit/test_storage_server.js
diff options
context:
space:
mode:
Diffstat (limited to 'services/common/tests/unit/test_storage_server.js')
-rw-r--r--services/common/tests/unit/test_storage_server.js692
1 files changed, 692 insertions, 0 deletions
diff --git a/services/common/tests/unit/test_storage_server.js b/services/common/tests/unit/test_storage_server.js
new file mode 100644
index 000000000..04b4dfbbb
--- /dev/null
+++ b/services/common/tests/unit/test_storage_server.js
@@ -0,0 +1,692 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Cu.import("resource://services-common/async.js");
+Cu.import("resource://services-common/rest.js");
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://testing-common/services/common/storageserver.js");
+
+const DEFAULT_USER = "123";
+const DEFAULT_PASSWORD = "password";
+
+/**
+ * Helper function to prepare a RESTRequest against the server.
+ */
+function localRequest(server, path, user=DEFAULT_USER, password=DEFAULT_PASSWORD) {
+ _("localRequest: " + path);
+ let identity = server.server.identity;
+ let url = identity.primaryScheme + "://" + identity.primaryHost + ":" +
+ identity.primaryPort + path;
+ _("url: " + url);
+ let req = new RESTRequest(url);
+
+ let header = basic_auth_header(user, password);
+ req.setHeader("Authorization", header);
+ req.setHeader("Accept", "application/json");
+
+ return req;
+}
+
+/**
+ * Helper function to validate an HTTP response from the server.
+ */
+function validateResponse(response) {
+ do_check_true("x-timestamp" in response.headers);
+
+ if ("content-length" in response.headers) {
+ let cl = parseInt(response.headers["content-length"]);
+
+ if (cl != 0) {
+ do_check_true("content-type" in response.headers);
+ do_check_eq("application/json", response.headers["content-type"]);
+ }
+ }
+
+ if (response.status == 204 || response.status == 304) {
+ do_check_false("content-type" in response.headers);
+
+ if ("content-length" in response.headers) {
+ do_check_eq(response.headers["content-length"], "0");
+ }
+ }
+
+ if (response.status == 405) {
+ do_check_true("allow" in response.headers);
+ }
+}
+
+/**
+ * Helper function to synchronously wait for a response and validate it.
+ */
+function waitAndValidateResponse(cb, request) {
+ let error = cb.wait();
+
+ if (!error) {
+ validateResponse(request.response);
+ }
+
+ return error;
+}
+
+/**
+ * Helper function to synchronously perform a GET request.
+ *
+ * @return Error instance or null if no error.
+ */
+function doGetRequest(request) {
+ let cb = Async.makeSpinningCallback();
+ request.get(cb);
+
+ return waitAndValidateResponse(cb, request);
+}
+
+/**
+ * Helper function to synchronously perform a PUT request.
+ *
+ * @return Error instance or null if no error.
+ */
+function doPutRequest(request, data) {
+ let cb = Async.makeSpinningCallback();
+ request.put(data, cb);
+
+ return waitAndValidateResponse(cb, request);
+}
+
+/**
+ * Helper function to synchronously perform a DELETE request.
+ *
+ * @return Error or null if no error was encountered.
+ */
+function doDeleteRequest(request) {
+ let cb = Async.makeSpinningCallback();
+ request.delete(cb);
+
+ return waitAndValidateResponse(cb, request);
+}
+
+function run_test() {
+ Log.repository.getLogger("Services.Common.Test.StorageServer").level =
+ Log.Level.Trace;
+ initTestLogging();
+
+ run_next_test();
+}
+
+add_test(function test_creation() {
+ _("Ensure a simple server can be created.");
+
+ // Explicit callback for this one.
+ let server = new StorageServer({
+ __proto__: StorageServerCallback,
+ });
+ do_check_true(!!server);
+
+ server.start(-1, function () {
+ _("Started on " + server.port);
+ server.stop(run_next_test);
+ });
+});
+
+add_test(function test_synchronous_start() {
+ _("Ensure starting using startSynchronous works.");
+
+ let server = new StorageServer();
+ server.startSynchronous();
+ server.stop(run_next_test);
+});
+
+add_test(function test_url_parsing() {
+ _("Ensure server parses URLs properly.");
+
+ let server = new StorageServer();
+
+ // Check that we can parse a BSO URI.
+ let parts = server.pathRE.exec("/2.0/12345/storage/crypto/keys");
+ let [all, version, user, first, rest] = parts;
+ do_check_eq(all, "/2.0/12345/storage/crypto/keys");
+ do_check_eq(version, "2.0");
+ do_check_eq(user, "12345");
+ do_check_eq(first, "storage");
+ do_check_eq(rest, "crypto/keys");
+ do_check_eq(null, server.pathRE.exec("/nothing/else"));
+
+ // Check that we can parse a collection URI.
+ parts = server.pathRE.exec("/2.0/123/storage/crypto");
+ [all, version, user, first, rest] = parts;
+ do_check_eq(all, "/2.0/123/storage/crypto");
+ do_check_eq(version, "2.0");
+ do_check_eq(user, "123");
+ do_check_eq(first, "storage");
+ do_check_eq(rest, "crypto");
+
+ // We don't allow trailing slash on storage URI.
+ parts = server.pathRE.exec("/2.0/1234/storage/");
+ do_check_eq(parts, undefined);
+
+ // storage alone is a valid request.
+ parts = server.pathRE.exec("/2.0/123456/storage");
+ [all, version, user, first, rest] = parts;
+ do_check_eq(all, "/2.0/123456/storage");
+ do_check_eq(version, "2.0");
+ do_check_eq(user, "123456");
+ do_check_eq(first, "storage");
+ do_check_eq(rest, undefined);
+
+ parts = server.storageRE.exec("storage");
+ let storage, collection, id;
+ [all, storage, collection, id] = parts;
+ do_check_eq(all, "storage");
+ do_check_eq(collection, undefined);
+
+ run_next_test();
+});
+
+add_test(function test_basic_http() {
+ let server = new StorageServer();
+ server.registerUser("345", "password");
+ do_check_true(server.userExists("345"));
+ server.startSynchronous();
+
+ _("Started on " + server.port);
+ do_check_eq(server.requestCount, 0);
+ let req = localRequest(server, "/2.0/storage/crypto/keys");
+ _("req is " + req);
+ req.get(function (err) {
+ do_check_eq(null, err);
+ do_check_eq(server.requestCount, 1);
+ server.stop(run_next_test);
+ });
+});
+
+add_test(function test_info_collections() {
+ let server = new StorageServer();
+ server.registerUser("123", "password");
+ server.startSynchronous();
+
+ let path = "/2.0/123/info/collections";
+
+ _("info/collections on empty server should be empty object.");
+ let request = localRequest(server, path, "123", "password");
+ let error = doGetRequest(request);
+ do_check_eq(error, null);
+ do_check_eq(request.response.status, 200);
+ do_check_eq(request.response.body, "{}");
+
+ _("Creating an empty collection should result in collection appearing.");
+ let coll = server.createCollection("123", "col1");
+ request = localRequest(server, path, "123", "password");
+ error = doGetRequest(request);
+ do_check_eq(error, null);
+ do_check_eq(request.response.status, 200);
+ let info = JSON.parse(request.response.body);
+ do_check_attribute_count(info, 1);
+ do_check_true("col1" in info);
+ do_check_eq(info.col1, coll.timestamp);
+
+ server.stop(run_next_test);
+});
+
+add_test(function test_bso_get_existing() {
+ _("Ensure that BSO retrieval works.");
+
+ let server = new StorageServer();
+ server.registerUser("123", "password");
+ server.createContents("123", {
+ test: {"bso": {"foo": "bar"}}
+ });
+ server.startSynchronous();
+
+ let coll = server.user("123").collection("test");
+
+ let request = localRequest(server, "/2.0/123/storage/test/bso", "123",
+ "password");
+ let error = doGetRequest(request);
+ do_check_eq(error, null);
+ do_check_eq(request.response.status, 200);
+ do_check_eq(request.response.headers["content-type"], "application/json");
+ let bso = JSON.parse(request.response.body);
+ do_check_attribute_count(bso, 3);
+ do_check_eq(bso.id, "bso");
+ do_check_eq(bso.modified, coll.bso("bso").modified);
+ let payload = JSON.parse(bso.payload);
+ do_check_attribute_count(payload, 1);
+ do_check_eq(payload.foo, "bar");
+
+ server.stop(run_next_test);
+});
+
+add_test(function test_percent_decoding() {
+ _("Ensure query string arguments with percent encoded are handled.");
+
+ let server = new StorageServer();
+ server.registerUser("123", "password");
+ server.startSynchronous();
+
+ let coll = server.user("123").createCollection("test");
+ coll.insert("001", {foo: "bar"});
+ coll.insert("002", {bar: "foo"});
+
+ let request = localRequest(server, "/2.0/123/storage/test?ids=001%2C002",
+ "123", "password");
+ let error = doGetRequest(request);
+ do_check_null(error);
+ do_check_eq(request.response.status, 200);
+ let items = JSON.parse(request.response.body).items;
+ do_check_attribute_count(items, 2);
+
+ server.stop(run_next_test);
+});
+
+add_test(function test_bso_404() {
+ _("Ensure the server responds with a 404 if a BSO does not exist.");
+
+ let server = new StorageServer();
+ server.registerUser("123", "password");
+ server.createContents("123", {
+ test: {}
+ });
+ server.startSynchronous();
+
+ let request = localRequest(server, "/2.0/123/storage/test/foo");
+ let error = doGetRequest(request);
+ do_check_eq(error, null);
+
+ do_check_eq(request.response.status, 404);
+ do_check_false("content-type" in request.response.headers);
+
+ server.stop(run_next_test);
+});
+
+add_test(function test_bso_if_modified_since_304() {
+ _("Ensure the server responds properly to X-If-Modified-Since for BSOs.");
+
+ let server = new StorageServer();
+ server.registerUser("123", "password");
+ server.createContents("123", {
+ test: {bso: {foo: "bar"}}
+ });
+ server.startSynchronous();
+
+ let coll = server.user("123").collection("test");
+ do_check_neq(coll, null);
+
+ // Rewind clock just in case.
+ coll.timestamp -= 10000;
+ coll.bso("bso").modified -= 10000;
+
+ let request = localRequest(server, "/2.0/123/storage/test/bso",
+ "123", "password");
+ request.setHeader("X-If-Modified-Since", "" + server.serverTime());
+ let error = doGetRequest(request);
+ do_check_eq(null, error);
+
+ do_check_eq(request.response.status, 304);
+ do_check_false("content-type" in request.response.headers);
+
+ request = localRequest(server, "/2.0/123/storage/test/bso",
+ "123", "password");
+ request.setHeader("X-If-Modified-Since", "" + (server.serverTime() - 20000));
+ error = doGetRequest(request);
+ do_check_eq(null, error);
+ do_check_eq(request.response.status, 200);
+ do_check_eq(request.response.headers["content-type"], "application/json");
+
+ server.stop(run_next_test);
+});
+
+add_test(function test_bso_if_unmodified_since() {
+ _("Ensure X-If-Unmodified-Since works properly on BSOs.");
+
+ let server = new StorageServer();
+ server.registerUser("123", "password");
+ server.createContents("123", {
+ test: {bso: {foo: "bar"}}
+ });
+ server.startSynchronous();
+
+ let coll = server.user("123").collection("test");
+ do_check_neq(coll, null);
+
+ let time = coll.bso("bso").modified;
+
+ _("Ensure we get a 412 for specified times older than server time.");
+ let request = localRequest(server, "/2.0/123/storage/test/bso",
+ "123", "password");
+ request.setHeader("X-If-Unmodified-Since", time - 5000);
+ request.setHeader("Content-Type", "application/json");
+ let payload = JSON.stringify({"payload": "foobar"});
+ let error = doPutRequest(request, payload);
+ do_check_eq(null, error);
+ do_check_eq(request.response.status, 412);
+
+ _("Ensure we get a 204 if update goes through.");
+ request = localRequest(server, "/2.0/123/storage/test/bso",
+ "123", "password");
+ request.setHeader("Content-Type", "application/json");
+ request.setHeader("X-If-Unmodified-Since", time + 1);
+ error = doPutRequest(request, payload);
+ do_check_eq(null, error);
+ do_check_eq(request.response.status, 204);
+ do_check_true(coll.timestamp > time);
+
+ // Not sure why a client would send X-If-Unmodified-Since if a BSO doesn't
+ // exist. But, why not test it?
+ _("Ensure we get a 201 if creation goes through.");
+ request = localRequest(server, "/2.0/123/storage/test/none",
+ "123", "password");
+ request.setHeader("Content-Type", "application/json");
+ request.setHeader("X-If-Unmodified-Since", time);
+ error = doPutRequest(request, payload);
+ do_check_eq(null, error);
+ do_check_eq(request.response.status, 201);
+
+ server.stop(run_next_test);
+});
+
+add_test(function test_bso_delete_not_exist() {
+ _("Ensure server behaves properly when deleting a BSO that does not exist.");
+
+ let server = new StorageServer();
+ server.registerUser("123", "password");
+ server.user("123").createCollection("empty");
+ server.startSynchronous();
+
+ server.callback.onItemDeleted = function onItemDeleted(username, collection,
+ id) {
+ do_throw("onItemDeleted should not have been called.");
+ };
+
+ let request = localRequest(server, "/2.0/123/storage/empty/nada",
+ "123", "password");
+ let error = doDeleteRequest(request);
+ do_check_eq(error, null);
+ do_check_eq(request.response.status, 404);
+ do_check_false("content-type" in request.response.headers);
+
+ server.stop(run_next_test);
+});
+
+add_test(function test_bso_delete_exists() {
+ _("Ensure proper semantics when deleting a BSO that exists.");
+
+ let server = new StorageServer();
+ server.registerUser("123", "password");
+ server.startSynchronous();
+
+ let coll = server.user("123").createCollection("test");
+ let bso = coll.insert("myid", {foo: "bar"});
+ let timestamp = coll.timestamp;
+
+ server.callback.onItemDeleted = function onDeleted(username, collection, id) {
+ delete server.callback.onItemDeleted;
+ do_check_eq(username, "123");
+ do_check_eq(collection, "test");
+ do_check_eq(id, "myid");
+ };
+
+ let request = localRequest(server, "/2.0/123/storage/test/myid",
+ "123", "password");
+ let error = doDeleteRequest(request);
+ do_check_eq(error, null);
+ do_check_eq(request.response.status, 204);
+ do_check_eq(coll.bsos().length, 0);
+ do_check_true(coll.timestamp > timestamp);
+
+ _("On next request the BSO should not exist.");
+ request = localRequest(server, "/2.0/123/storage/test/myid",
+ "123", "password");
+ error = doGetRequest(request);
+ do_check_eq(error, null);
+ do_check_eq(request.response.status, 404);
+
+ server.stop(run_next_test);
+});
+
+add_test(function test_bso_delete_unmodified() {
+ _("Ensure X-If-Unmodified-Since works when deleting BSOs.");
+
+ let server = new StorageServer();
+ server.startSynchronous();
+ server.registerUser("123", "password");
+ let coll = server.user("123").createCollection("test");
+ let bso = coll.insert("myid", {foo: "bar"});
+
+ let modified = bso.modified;
+
+ _("Issuing a DELETE with an older time should fail.");
+ let path = "/2.0/123/storage/test/myid";
+ let request = localRequest(server, path, "123", "password");
+ request.setHeader("X-If-Unmodified-Since", modified - 1000);
+ let error = doDeleteRequest(request);
+ do_check_eq(error, null);
+ do_check_eq(request.response.status, 412);
+ do_check_false("content-type" in request.response.headers);
+ do_check_neq(coll.bso("myid"), null);
+
+ _("Issuing a DELETE with a newer time should work.");
+ request = localRequest(server, path, "123", "password");
+ request.setHeader("X-If-Unmodified-Since", modified + 1000);
+ error = doDeleteRequest(request);
+ do_check_eq(error, null);
+ do_check_eq(request.response.status, 204);
+ do_check_true(coll.bso("myid").deleted);
+
+ server.stop(run_next_test);
+});
+
+add_test(function test_collection_get_unmodified_since() {
+ _("Ensure conditional unmodified get on collection works when it should.");
+
+ let server = new StorageServer();
+ server.registerUser("123", "password");
+ server.startSynchronous();
+ let collection = server.user("123").createCollection("testcoll");
+ collection.insert("bso0", {foo: "bar"});
+
+ let serverModified = collection.timestamp;
+
+ let request1 = localRequest(server, "/2.0/123/storage/testcoll",
+ "123", "password");
+ request1.setHeader("X-If-Unmodified-Since", serverModified);
+ let error = doGetRequest(request1);
+ do_check_null(error);
+ do_check_eq(request1.response.status, 200);
+
+ let request2 = localRequest(server, "/2.0/123/storage/testcoll",
+ "123", "password");
+ request2.setHeader("X-If-Unmodified-Since", serverModified - 1);
+ error = doGetRequest(request2);
+ do_check_null(error);
+ do_check_eq(request2.response.status, 412);
+
+ server.stop(run_next_test);
+});
+
+add_test(function test_bso_get_unmodified_since() {
+ _("Ensure conditional unmodified get on BSO works appropriately.");
+
+ let server = new StorageServer();
+ server.registerUser("123", "password");
+ server.startSynchronous();
+ let collection = server.user("123").createCollection("testcoll");
+ let bso = collection.insert("bso0", {foo: "bar"});
+
+ let serverModified = bso.modified;
+
+ let request1 = localRequest(server, "/2.0/123/storage/testcoll/bso0",
+ "123", "password");
+ request1.setHeader("X-If-Unmodified-Since", serverModified);
+ let error = doGetRequest(request1);
+ do_check_null(error);
+ do_check_eq(request1.response.status, 200);
+
+ let request2 = localRequest(server, "/2.0/123/storage/testcoll/bso0",
+ "123", "password");
+ request2.setHeader("X-If-Unmodified-Since", serverModified - 1);
+ error = doGetRequest(request2);
+ do_check_null(error);
+ do_check_eq(request2.response.status, 412);
+
+ server.stop(run_next_test);
+});
+
+add_test(function test_missing_collection_404() {
+ _("Ensure a missing collection returns a 404.");
+
+ let server = new StorageServer();
+ server.registerUser("123", "password");
+ server.startSynchronous();
+
+ let request = localRequest(server, "/2.0/123/storage/none", "123", "password");
+ let error = doGetRequest(request);
+ do_check_eq(error, null);
+ do_check_eq(request.response.status, 404);
+ do_check_false("content-type" in request.response.headers);
+
+ server.stop(run_next_test);
+});
+
+add_test(function test_get_storage_405() {
+ _("Ensure that a GET on /storage results in a 405.");
+
+ let server = new StorageServer();
+ server.registerUser("123", "password");
+ server.startSynchronous();
+
+ let request = localRequest(server, "/2.0/123/storage", "123", "password");
+ let error = doGetRequest(request);
+ do_check_eq(error, null);
+ do_check_eq(request.response.status, 405);
+ do_check_eq(request.response.headers["allow"], "DELETE");
+
+ server.stop(run_next_test);
+});
+
+add_test(function test_delete_storage() {
+ _("Ensure that deleting all of storage works.");
+
+ let server = new StorageServer();
+ server.registerUser("123", "password");
+ server.createContents("123", {
+ foo: {a: {foo: "bar"}, b: {bar: "foo"}},
+ baz: {c: {bob: "law"}, blah: {law: "blog"}}
+ });
+
+ server.startSynchronous();
+
+ let request = localRequest(server, "/2.0/123/storage", "123", "password");
+ let error = doDeleteRequest(request);
+ do_check_eq(error, null);
+ do_check_eq(request.response.status, 204);
+ do_check_attribute_count(server.users["123"].collections, 0);
+
+ server.stop(run_next_test);
+});
+
+add_test(function test_x_num_records() {
+ let server = new StorageServer();
+ server.registerUser("123", "password");
+
+ server.createContents("123", {
+ crypto: {foos: {foo: "bar"},
+ bars: {foo: "baz"}}
+ });
+ server.startSynchronous();
+ let bso = localRequest(server, "/2.0/123/storage/crypto/foos");
+ bso.get(function (err) {
+ // BSO fetches don't have one.
+ do_check_false("x-num-records" in this.response.headers);
+ let col = localRequest(server, "/2.0/123/storage/crypto");
+ col.get(function (err) {
+ // Collection fetches do.
+ do_check_eq(this.response.headers["x-num-records"], "2");
+ server.stop(run_next_test);
+ });
+ });
+});
+
+add_test(function test_put_delete_put() {
+ _("Bug 790397: Ensure BSO deleted flag is reset on PUT.");
+
+ let server = new StorageServer();
+ server.registerUser("123", "password");
+ server.createContents("123", {
+ test: {bso: {foo: "bar"}}
+ });
+ server.startSynchronous();
+
+ _("Ensure we can PUT an existing record.");
+ let request1 = localRequest(server, "/2.0/123/storage/test/bso", "123", "password");
+ request1.setHeader("Content-Type", "application/json");
+ let payload1 = JSON.stringify({"payload": "foobar"});
+ let error1 = doPutRequest(request1, payload1);
+ do_check_eq(null, error1);
+ do_check_eq(request1.response.status, 204);
+
+ _("Ensure we can DELETE it.");
+ let request2 = localRequest(server, "/2.0/123/storage/test/bso", "123", "password");
+ let error2 = doDeleteRequest(request2);
+ do_check_eq(error2, null);
+ do_check_eq(request2.response.status, 204);
+ do_check_false("content-type" in request2.response.headers);
+
+ _("Ensure we can PUT a previously deleted record.");
+ let request3 = localRequest(server, "/2.0/123/storage/test/bso", "123", "password");
+ request3.setHeader("Content-Type", "application/json");
+ let payload3 = JSON.stringify({"payload": "foobar"});
+ let error3 = doPutRequest(request3, payload3);
+ do_check_eq(null, error3);
+ do_check_eq(request3.response.status, 201);
+
+ _("Ensure we can GET the re-uploaded record.");
+ let request4 = localRequest(server, "/2.0/123/storage/test/bso", "123", "password");
+ let error4 = doGetRequest(request4);
+ do_check_eq(error4, null);
+ do_check_eq(request4.response.status, 200);
+ do_check_eq(request4.response.headers["content-type"], "application/json");
+
+ server.stop(run_next_test);
+});
+
+add_test(function test_collection_get_newer() {
+ _("Ensure get with newer argument on collection works.");
+
+ let server = new StorageServer();
+ server.registerUser("123", "password");
+ server.startSynchronous();
+
+ let coll = server.user("123").createCollection("test");
+ let bso1 = coll.insert("001", {foo: "bar"});
+ let bso2 = coll.insert("002", {bar: "foo"});
+
+ // Don't want both records to have the same timestamp.
+ bso2.modified = bso1.modified + 1000;
+
+ function newerRequest(newer) {
+ return localRequest(server, "/2.0/123/storage/test?newer=" + newer,
+ "123", "password");
+ }
+
+ let request1 = newerRequest(0);
+ let error1 = doGetRequest(request1);
+ do_check_null(error1);
+ do_check_eq(request1.response.status, 200);
+ let items1 = JSON.parse(request1.response.body).items;
+ do_check_attribute_count(items1, 2);
+
+ let request2 = newerRequest(bso1.modified + 1);
+ let error2 = doGetRequest(request2);
+ do_check_null(error2);
+ do_check_eq(request2.response.status, 200);
+ let items2 = JSON.parse(request2.response.body).items;
+ do_check_attribute_count(items2, 1);
+
+ let request3 = newerRequest(bso2.modified + 1);
+ let error3 = doGetRequest(request3);
+ do_check_null(error3);
+ do_check_eq(request3.response.status, 200);
+ let items3 = JSON.parse(request3.response.body).items;
+ do_check_attribute_count(items3, 0);
+
+ server.stop(run_next_test);
+});