/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

Cu.import("resource://services-common/storageservice.js");
Cu.import("resource://testing-common/services-common/storageserver.js");

function run_test() {
  initTestLogging("Trace");

  run_next_test();
}

function getRandomUser() {
  return "" + (Math.floor(Math.random() * 100000) + 1);
}

function getEmptyServer(user=getRandomUser(), password="password") {
  let users = {};
  users[user] = password;

  return storageServerForUsers(users, {
    meta:    {},
    clients: {},
    crypto:  {},
  });
}

function getClient(server, user=getRandomUser(), password="password") {
  let identity = server.server.identity;
  let url = identity.primaryScheme + "://" + identity.primaryHost + ":" +
            identity.primaryPort + "/2.0/" + user;
  let client = new StorageServiceClient(url);
  client.addListener({
    onDispatch: function onDispatch(request) {
      let up = user + ":" + password;
      request.request.setHeader("authorization", "Basic " + btoa(up));
    }
  });

  return client;
}

function getServerAndClient(user=getRandomUser(), password="password") {
  let server = getEmptyServer(user, password);
  let client = getClient(server, user, password);

  return [server, client, user, password];
}

add_test(function test_auth_failure_listener() {
  _("Ensure the onAuthFailure listener is invoked.");

  let server = getEmptyServer();
  let client = getClient(server, "324", "INVALID");
  client.addListener({
    onAuthFailure: function onAuthFailure(client, request) {
      _("onAuthFailure");
      server.stop(run_next_test);
    }
  });

  let request = client.getCollectionInfo();
  request.dispatch();
});

add_test(function test_duplicate_listeners() {
  _("Ensure that duplicate listeners aren't installed multiple times.");

  let server = getEmptyServer();
  let client = getClient(server, "1234567", "BAD_PASSWORD");

  let invokeCount = 0;
  let listener = {
    onAuthFailure: function onAuthFailure() {
      invokeCount++;
    }
  };

  client.addListener(listener);
  // No error expected.
  client.addListener(listener);

  let request = client.getCollectionInfo();
  request.dispatch(function onComplete() {
    do_check_eq(invokeCount, 1);

    server.stop(run_next_test);
  });
});

add_test(function test_handler_object() {
  _("Ensure that installed handlers get their callbacks invoked.");

  let [server, client] = getServerAndClient();

  let onCompleteCount = 0;
  let onDispatchCount = 0;

  let handler = {
    onComplete: function onComplete() {
      onCompleteCount++;

      do_check_eq(onDispatchCount, 1);
      do_check_eq(onCompleteCount, 1);

      server.stop(run_next_test);
    },

    onDispatch: function onDispatch() {
      onDispatchCount++;
    },
  };
  let request = client.getCollectionInfo();
  request.handler = handler;
  request.dispatch();
});

add_test(function test_info_collections() {
  _("Ensure requests to /info/collections work as expected.");

  let [server, client] = getServerAndClient();

  let request = client.getCollectionInfo();
  request.dispatch(function onComplete(error, req) {
    do_check_null(error);
    do_check_eq("object", typeof req.resultObj);
    do_check_attribute_count(req.resultObj, 3);
    do_check_true("meta" in req.resultObj);

    server.stop(run_next_test);
  });
});

add_test(function test_info_collections_conditional_not_modified() {
  _("Ensure conditional getCollectionInfo requests work.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  let now = Date.now();

  user.createCollection("testcoll", {
    foo: new ServerBSO("foo", "payload", now)
  });

  let request = client.getCollectionInfo();
  request.locallyModifiedVersion = now + 10;
  request.dispatch(function onComplete(error, req) {
    do_check_null(error);
    do_check_true(req.notModified);

    server.stop(run_next_test);
  });
});

add_test(function test_info_collections_conditional_modified() {
  _("Ensure conditional getCollectionInfo requests work.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  let now = Date.now();

  user.createCollection("testcoll", {
    foo: new ServerBSO("foo", "payload", now)
  });

  let request = client.getCollectionInfo();
  request.locallyModifiedVersion = now - 10;
  request.dispatch(function onComplete(error, req) {
    do_check_null(error);
    do_check_false(req.notModified);

    server.stop(run_next_test);
  });
});

add_test(function test_get_quota() {
  _("Ensure quota requests work.");

  let [server, client] = getServerAndClient();

  let request = client.getQuota();
  request.dispatch(function onComplete(error, req) {
    do_check_null(error);

    do_check_eq(req.resultObj.quota, 1048576);

    server.stop(run_next_test);
  });
});

add_test(function test_get_quota_conditional_not_modified() {
  _("Ensure conditional getQuota requests work.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  let now = Date.now();

  user.createCollection("testcoll", {
    foo: new ServerBSO("foo", "payload", now)
  });

  let request = client.getQuota();
  request.locallyModifiedVersion = now + 10;
  request.dispatch(function onComplete(error, req) {
    do_check_null(error);
    do_check_true(req.notModified);

    server.stop(run_next_test);
  });
});

add_test(function test_get_quota_conditional_modified() {
  _("Ensure conditional getQuota requests work.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  let now = Date.now();

  user.createCollection("testcoll", {
    foo: new ServerBSO("foo", "payload", now)
  });

  let request = client.getQuota();
  request.locallyModifiedVersion = now - 10;
  request.dispatch(function onComplete(error, req) {
    do_check_null(error);
    do_check_false(req.notModified);

    server.stop(run_next_test);
  });
});

add_test(function test_get_collection_usage() {
  _("Ensure info/collection_usage requests work.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  user.createCollection("testcoll", {
    abc123: new ServerBSO("abc123", "payload", Date.now())
  });

  let request = client.getCollectionUsage();
  request.dispatch(function onComplete(error, req) {
    do_check_null(error);

    let usage = req.resultObj;
    do_check_true("testcoll" in usage);
    do_check_eq(usage.testcoll, "payload".length);

    server.stop(run_next_test);
  });
});

add_test(function test_get_usage_conditional_not_modified() {
  _("Ensure conditional getCollectionUsage requests work.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  let now = Date.now();

  user.createCollection("testcoll", {
    foo: new ServerBSO("foo", "payload", now)
  });

  let request = client.getCollectionUsage();
  request.locallyModifiedVersion = now + 10;
  request.dispatch(function onComplete(error, req) {
    do_check_null(error);
    do_check_true(req.notModified);

    server.stop(run_next_test);
  });
});

add_test(function test_get_usage_conditional_modified() {
  _("Ensure conditional getCollectionUsage requests work.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  let now = Date.now();

  user.createCollection("testcoll", {
    foo: new ServerBSO("foo", "payload", now)
  });

  let request = client.getCollectionUsage();
  request.locallyModifiedVersion = now - 10;
  request.dispatch(function onComplete(error, req) {
    do_check_null(error);
    do_check_false(req.notModified);

    server.stop(run_next_test);
  });
});

add_test(function test_get_collection_counts() {
  _("Ensure info/collection_counts requests work.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  user.createCollection("testcoll", {
    foo: new ServerBSO("foo", "payload0", Date.now()),
    bar: new ServerBSO("bar", "payload1", Date.now())
  });

  let request = client.getCollectionCounts();
  request.dispatch(function onComplete(error, req) {
    do_check_null(error);

    let counts = req.resultObj;
    do_check_true("testcoll" in counts);
    do_check_eq(counts.testcoll, 2);

    server.stop(run_next_test);
  });
});

add_test(function test_get_counts_conditional_not_modified() {
  _("Ensure conditional getCollectionCounts requests work.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  let now = Date.now();

  user.createCollection("testcoll", {
    foo: new ServerBSO("foo", "payload", now)
  });

  let request = client.getCollectionCounts();
  request.locallyModifiedVersion = now + 10;
  request.dispatch(function onComplete(error, req) {
    do_check_null(error);
    do_check_true(req.notModified);

    server.stop(run_next_test);
  });
});

add_test(function test_get_counts_conditional_modified() {
  _("Ensure conditional getCollectionCounts requests work.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  let now = Date.now();

  user.createCollection("testcoll", {
    foo: new ServerBSO("foo", "payload", now)
  });

  let request = client.getCollectionCounts();
  request.locallyModifiedVersion = now - 10;
  request.dispatch(function onComplete(error, req) {
    do_check_null(error);
    do_check_false(req.notModified);

    server.stop(run_next_test);
  });
});

add_test(function test_get_collection_simple() {
  _("Ensure basic collection retrieval works.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  user.createCollection("testcoll", {
    foo: new ServerBSO("foo", "payload0", Date.now()),
    bar: new ServerBSO("bar", "payload1", Date.now())
  });

  let request = client.getCollection("testcoll");
  let bsos = [];
  request.handler = {
    onBSORecord: function onBSORecord(request, bso) {
      bsos.push(bso);
    },

    onComplete: function onComplete(error, request) {
      do_check_null(error);

      do_check_eq(bsos.length, 2);
      do_check_eq(bsos[0], "foo");
      do_check_eq(bsos[1], "bar");

      server.stop(run_next_test);
    }
  };
  request.dispatch();
});

add_test(function test_get_collection_conditional_not_modified() {
  _("Ensure conditional requests with no new data to getCollection work.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  let now = Date.now();

  user.createCollection("testcoll", {
    foo: new ServerBSO("foo", "payload0", now)
  });

  let request = client.getCollection("testcoll");
  request.locallyModifiedVersion = now + 1;

  request.dispatch(function onComplete(error, req) {
    do_check_null(error);

    do_check_true(req.notModified);

    server.stop(run_next_test);
  });
});

add_test(function test_get_collection_conditional_modified() {
  _("Ensure conditional requests with new data to getCollection work.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  let now = Date.now();

  user.createCollection("testcoll", {
    foo: new ServerBSO("foo", "payload0", now)
  });

  let request = client.getCollection("testcoll");
  request.locallyModifiedVersion = now - 1;

  let bsoCount = 0;
  request.handler = {
    onBSORecord: function onBSORecord() {
      bsoCount++;
    },

    onComplete: function onComplete(error, req) {
      do_check_null(error);

      do_check_false(req.notModified);
      do_check_eq(bsoCount, 1);

      server.stop(run_next_test);
    }
  };
  request.dispatch();
});

// This is effectively a sanity test for query string generation.
add_test(function test_get_collection_newer() {
  _("Ensure query string for newer and full work together.");

  let [server, client, username] = getServerAndClient();

  let date0 = Date.now();
  let date1 = date0 + 500;

  let user = server.user(username);
  user.createCollection("testcoll", {
    foo: new ServerBSO("foo", "payload0", date0),
    bar: new ServerBSO("bar", "payload1", date1)
  });

  let request = client.getCollection("testcoll");
  request.full = true;
  request.newer = date0;

  let bsos = [];
  request.handler = {
    onBSORecord: function onBSORecord(request, bso) {
      bsos.push(bso);
    },

    onComplete: function onComplete(error, req) {
      do_check_null(error);

      do_check_eq(bsos.length, 1);
      let bso = bsos[0];

      do_check_eq(bso.id, "bar");
      do_check_eq(bso.payload, "payload1");

      server.stop(run_next_test);
    }
  };
  request.dispatch();
});

add_test(function test_get_bso() {
  _("Ensure that simple BSO fetches work.");

  let [server, client, username] = getServerAndClient();

  server.createCollection(username, "testcoll", {
    abc123: new ServerBSO("abc123", "payload", Date.now())
  });

  let request = client.getBSO("testcoll", "abc123");
  request.dispatch(function(error, req) {
    do_check_null(error);
    do_check_true(req.resultObj instanceof BasicStorageObject);

    let bso = req.resultObj;
    do_check_eq(bso.id, "abc123");
    do_check_eq(bso.payload, "payload");

    server.stop(run_next_test);
  });
});

add_test(function test_bso_conditional() {
  _("Ensure conditional requests for an individual BSO work.");

  let [server, client, username] = getServerAndClient();

  let user = server.user(username);
  let now = Date.now();
  user.createCollection("testcoll", {
    foo: new ServerBSO("foo", "payload", now)
  });

  let request = client.getBSO("testcoll", "foo");
  request.locallyModifiedVersion = now;

  request.dispatch(function onComplete(error, req) {
    do_check_null(error);
    do_check_true(req.notModified);

    server.stop(run_next_test);
  });
});

add_test(function test_set_bso() {
  _("Ensure simple BSO PUT works.");

  let [server, client] = getServerAndClient();

  let id = "mnas08h3f3r2351";

  let bso = new BasicStorageObject(id, "testcoll");
  bso.payload = "my test payload";

  let request = client.setBSO(bso);
  request.dispatch(function(error, req) {
    do_check_eq(error, null);
    do_check_eq(req.resultObj, null);

    server.stop(run_next_test);
  });
});


add_test(function test_set_bso_conditional() {
  _("Ensure conditional setting a BSO is properly rejected.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  let now = Date.now();
  user.createCollection("testcoll", {
    foo: new ServerBSO("foo", "payload0", now + 1000)
  });

  // Should get an mtime newer than server's.
  let bso = new BasicStorageObject("foo", "testcoll");
  bso.payload = "payload1";

  let request = client.setBSO(bso);
  request.locallyModifiedVersion = now;
  request.dispatch(function onComplete(error, req) {
    do_check_true(error instanceof StorageServiceRequestError);
    do_check_true(error.serverModified);

    server.stop(run_next_test);
  });
});

add_test(function test_set_bso_argument_errors() {
  _("Ensure BSO set detects invalid arguments.");

  let server = getEmptyServer();
  let bso = new BasicStorageObject();
  let client = getClient(server);

  let threw = false;
  try {
    client.setBSO(bso);
  } catch (ex) {
    threw = true;
    do_check_eq(ex.name, "Error");
    do_check_neq(ex.message.indexOf("does not have collection defined"), -1);
  } finally {
    do_check_true(threw);
    threw = false;
  }

  bso = new BasicStorageObject("id");
  try {
    client.setBSO(bso);
  } catch (ex) {
    threw = true;
    do_check_eq(ex.name, "Error");
    do_check_neq(ex.message.indexOf("does not have collection defined"), -1);
  } finally {
    do_check_true(threw);
    threw = false;
  }

  bso = new BasicStorageObject(null, "coll");
  try {
    client.setBSO(bso);
  } catch (ex) {
    threw = true;
    do_check_eq(ex.name, "Error");
    do_check_neq(ex.message.indexOf("does not have ID defined"), -1);
  } finally {
    do_check_true(threw);
    threw = false;
  }

  server.stop(run_next_test);
});

add_test(function test_set_bsos_simple() {
  _("Ensure setBSOs with basic options works.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  let bso0 = new BasicStorageObject("foo");
  bso0.payload = "payload0";

  let bso1 = new BasicStorageObject("bar");
  bso1.payload = "payload1";

  let request = client.setBSOs("testcollection");
  request.addBSO(bso0);
  request.addBSO(bso1);

  request.dispatch(function onComplete(error, req) {
    do_check_null(error);

    let successful = req.successfulIDs;
    do_check_eq(successful.length, 2);
    do_check_eq(successful.indexOf(bso0.id), 0);
    do_check_true(successful.indexOf(bso1.id), 1);

    server.stop(run_next_test);
  });
});

add_test(function test_set_bsos_invalid_bso() {
  _("Ensure that adding an invalid BSO throws.");

  let server = getEmptyServer();
  let client = getClient(server);
  let request = client.setBSOs("testcoll");

  let threw = false;

  // Empty argument is invalid.
  try {
    request.addBSO(null);
  } catch (ex) {
    threw = true;
  } finally {
    do_check_true(threw);
    threw = false;
  }

  try {
    let bso = new BasicStorageObject();
    request.addBSO(bso);
  } catch (ex) {
    threw = true;
  } finally {
    do_check_true(threw);
    threw = false;
  }

  server.stop(run_next_test);
});

add_test(function test_set_bsos_newline() {
  _("Ensure that newlines in BSO payloads are formatted properly.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  let request = client.setBSOs("testcoll");

  let bso0 = new BasicStorageObject("bso0");
  bso0.payload = "hello\nworld";
  request.addBSO(bso0);

  let bso1 = new BasicStorageObject("bso1");
  bso1.payload = "foobar";
  request.addBSO(bso1);

  request.dispatch(function onComplete(error, request) {
    do_check_null(error);
    do_check_eq(request.successfulIDs.length, 2);

    let coll = user.collection("testcoll");
    do_check_eq(coll.bso("bso0").payload, bso0.payload);
    do_check_eq(coll.bso("bso1").payload, bso1.payload);

    server.stop(run_next_test);
  });
});

add_test(function test_delete_bso_simple() {
  _("Ensure deletion of individual BSOs works.");

  let [server, client, username] = getServerAndClient();

  let user = server.user(username);
  let coll = user.createCollection("testcoll", {
    foo: new ServerBSO("foo", "payload0", Date.now()),
    bar: new ServerBSO("bar", "payload1", Date.now())
  });

  let request = client.deleteBSO("testcoll", "foo");
  request.dispatch(function onComplete(error, req) {
    do_check_null(error);
    do_check_eq(req.statusCode, 204);

    do_check_eq(coll.count(), 1);

    server.stop(run_next_test);
  });
});

add_test(function test_delete_bso_conditional_failed() {
  _("Ensure deletion of an individual BSO with older modification fails.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  let now = Date.now();
  user.createCollection("testcoll", {
    foo: new ServerBSO("foo", "payload0", now)
  });

  let request = client.deleteBSO("testcoll", "foo");
  request.locallyModifiedVersion = now - 10;

  request.dispatch(function onComplete(error, req) {
    do_check_true(error instanceof StorageServiceRequestError);
    do_check_true(error.serverModified);

    server.stop(run_next_test);
  });
});

add_test(function test_delete_bso_conditional_success() {
  _("Ensure deletion of an individual BSO with newer modification works.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  let now = Date.now();
  user.createCollection("testcoll", {
    foo: new ServerBSO("foo", "payload0", now)
  });

  let request = client.deleteBSO("testcoll", "foo");
  request.locallyModifiedVersion = now;

  request.dispatch(function onComplete(error, req) {
    do_check_null(error);
    do_check_eq(req.statusCode, 204);

    server.stop(run_next_test);
  });
});

add_test(function test_delete_bsos_simple() {
  _("Ensure deletion of multiple BSOs works.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  let coll = user.createCollection("testcoll", {
    foo: new ServerBSO("foo", "payload0", Date.now()),
    bar: new ServerBSO("bar", "payload1", Date.now()),
    baz: new ServerBSO("baz", "payload2", Date.now())
  });

  let request = client.deleteBSOs("testcoll", ["foo", "baz"]);
  request.dispatch(function onComplete(error, req) {
    do_check_null(error);
    do_check_eq(req.statusCode, 204);

    do_check_eq(coll.count(), 1);

    server.stop(run_next_test);
  });
});

add_test(function test_delete_bsos_conditional_failed() {
  _("Ensure deletion of BSOs with server modifications fails.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  let now = Date.now();
  let coll = user.createCollection("testcoll", {
    foo: new ServerBSO("foo", "payload0", now)
  });

  let request = client.deleteBSOs("testcoll", ["foo"]);
  request.locallyModifiedVersion = coll.timestamp - 1;

  request.dispatch(function onComplete(error, req) {
    do_check_true(error instanceof StorageServiceRequestError);
    do_check_true(error.serverModified);

    server.stop(run_next_test);
  });
});

add_test(function test_delete_bsos_conditional_success() {
  _("Ensure conditional deletion of BSOs without server modifications works.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  let now = Date.now();
  let coll = user.createCollection("testcoll", {
    foo: new ServerBSO("foo", "payload0", now),
    bar: new ServerBSO("bar", "payload1", now - 10)
  });

  let request = client.deleteBSOs("testcoll", ["bar"]);
  request.locallyModifiedVersion = coll.timestamp;

  request.dispatch(function onComplete(error, req) {
    do_check_null(error);

    do_check_eq(req.statusCode, 204);

    server.stop(run_next_test);
  });
});

add_test(function test_delete_collection() {
  _("Ensure deleteCollection() works.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  user.createCollection("testcoll", {
    foo: new ServerBSO("foo", "payload0", Date.now())
  });

  let request = client.deleteCollection("testcoll");
  request.dispatch(function onComplete(error, req) {
    do_check_null(error);

    do_check_eq(user.collection("testcoll", undefined));

    server.stop(run_next_test);
  });
});

add_test(function test_delete_collection_conditional_failed() {
  _("Ensure conditional deletes with server modifications fail.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  let now = Date.now();

  let coll = user.createCollection("testcoll", {
    foo: new ServerBSO("foo", "payload0", now)
  });

  let request = client.deleteCollection("testcoll");
  request.locallyModifiedVersion = coll.timestamp - 1;

  request.dispatch(function onComplete(error, req) {
    do_check_true(error instanceof StorageServiceRequestError);
    do_check_true(error.serverModified);

    server.stop(run_next_test);
  });
});

add_test(function test_delete_collection_conditional_success() {
  _("Ensure conditional delete of collection works when it's supposed to.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  let now = Date.now();

  let coll = user.createCollection("testcoll", {
    foo: new ServerBSO("foo", "payload0", now)
  });

  let request = client.deleteCollection("testcoll");
  request.locallyModifiedVersion = coll.timestamp;

  request.dispatch(function onComplete(error, req) {
    do_check_null(error);

    do_check_eq(user.collection("testcoll"), undefined);

    server.stop(run_next_test);
  });
});

add_test(function test_delete_collections() {
  _("Ensure deleteCollections() works.");

  let [server, client, username] = getServerAndClient();
  let user = server.user(username);

  user.createCollection("testColl", {
    foo: new ServerBSO("foo", "payload0", Date.now())
  });

  let request = client.deleteCollections();
  request.dispatch(function onComplete(error, req) {
    do_check_null(error);

    do_check_eq(user.collection("testcoll"), undefined);

    server.stop(run_next_test);
  });
});

add_test(function test_network_error_captured() {
  _("Ensure network errors are captured.");

  // Network errors should result in .networkError being set on request.
  let client = new StorageServiceClient("http://rnewman-is-splendid.badtld/");

  let request = client.getCollectionInfo();
  request.dispatch(function(error, req) {
    do_check_neq(error, null);
    do_check_neq(error.network, null);

    run_next_test();
  });
});

add_test(function test_network_error_listener() {
  _("Ensure the onNetworkError listener is invoked on network errors.");

  let listenerCalled = false;

  let client = new StorageServiceClient("http://philikon-is-too.badtld/");
  client.addListener({
    onNetworkError: function(client, request) {
      listenerCalled = true;
    }
  });
  let request = client.getCollectionInfo();
  request.dispatch(function() {
    do_check_true(listenerCalled);
    run_next_test();
  });
});

add_test(function test_batching_set_too_large() {
  _("Ensure we throw when attempting to add a BSO that is too large to fit.");

  let [server, client, username] = getServerAndClient();

  let request = client.setBSOsBatching("testcoll");
  let payload = "";

  // The actual length of the payload is a little less. But, this ensures we
  // exceed it.
  for (let i = 0; i < client.REQUEST_SIZE_LIMIT; i++) {
    payload += i;
  }

  let bso = new BasicStorageObject("bso");
  bso.payload = payload;
  do_check_throws(function add() { request.addBSO(bso); });

  server.stop(run_next_test);
});

add_test(function test_batching_set_basic() {
  _("Ensure batching set works with single requests.");

  let [server, client, username] = getServerAndClient();

  let request = client.setBSOsBatching("testcoll");
  for (let i = 0; i < 10; i++) {
    let bso = new BasicStorageObject("bso" + i);
    bso.payload = "payload" + i;
    request.addBSO(bso);
  }

  request.finish(function onFinish(request) {
    do_check_eq(request.successfulIDs.length, 10);

    let collection = server.user(username).collection("testcoll");
    do_check_eq(collection.timestamp, request.serverModifiedVersion);

    server.stop(run_next_test);
  });
});

add_test(function test_batching_set_batch_count() {
  _("Ensure multiple outgoing request batching works when count is exceeded.");

  let [server, client, username] = getServerAndClient();
  let requestCount = 0;
  server.callback.onRequest = function onRequest() {
    requestCount++;
  }

  let request = client.setBSOsBatching("testcoll");
  for (let i = 1; i <= 300; i++) {
    let bso = new BasicStorageObject("bso" + i);
    bso.payload = "XXXXXXX";
    request.addBSO(bso);
  }

  request.finish(function onFinish(request) {
    do_check_eq(request.successfulIDs.length, 300);
    do_check_eq(requestCount, 3);

    let collection = server.user(username).collection("testcoll");
    do_check_eq(collection.timestamp, request.serverModifiedVersion);

    server.stop(run_next_test);
  });
});

add_test(function test_batching_set_batch_size() {
  _("Ensure outgoing requests batch when size is exceeded.");

  let [server, client, username] = getServerAndClient();
  let requestCount = 0;
  server.callback.onRequest = function onRequest() {
    requestCount++;
  };

  let limit = client.REQUEST_SIZE_LIMIT;

  let request = client.setBSOsBatching("testcoll");

  // JavaScript: Y U NO EASY REPETITION FUNCTIONALITY?
  let data = [];
  for (let i = (limit / 2) - 100; i; i -= 1) {
    data.push("X");
  }

  let payload = data.join("");

  for (let i = 0; i < 4; i++) {
    let bso = new BasicStorageObject("bso" + i);
    bso.payload = payload;
    request.addBSO(bso);
  }

  request.finish(function onFinish(request) {
    do_check_eq(request.successfulIDs.length, 4);
    do_check_eq(requestCount, 2);

    let collection = server.user(username).collection("testcoll");
    do_check_eq(collection.timestamp, request.serverModifiedVersion);

    server.stop(run_next_test);
  });
});

add_test(function test_batching_set_flush() {
  _("Ensure flushing batch sets works.");

  let [server, client, username] = getServerAndClient();

  let requestCount = 0;
  server.callback.onRequest = function onRequest() {
    requestCount++;
  }

  let request = client.setBSOsBatching("testcoll");
  for (let i = 1; i < 101; i++) {
    let bso = new BasicStorageObject("bso" + i);
    bso.payload = "foo";
    request.addBSO(bso);

    if (i % 10 == 0) {
      request.flush();
    }
  }

  request.finish(function onFinish(request) {
    do_check_eq(request.successfulIDs.length, 100);
    do_check_eq(requestCount, 10);

    let collection = server.user(username).collection("testcoll");
    do_check_eq(collection.timestamp, request.serverModifiedVersion);

    server.stop(run_next_test);
  });
});

add_test(function test_batching_set_conditional_success() {
  _("Ensure conditional requests for batched sets work properly.");

  let [server, client, username] = getServerAndClient();

  let collection = server.user(username).createCollection("testcoll");

  let lastServerVersion = Date.now();
  collection.insertBSO(new ServerBSO("foo", "bar", lastServerVersion));
  collection.timestamp = lastServerVersion;
  do_check_eq(collection.timestamp, lastServerVersion);

  let requestCount = 0;
  server.callback.onRequest = function onRequest() {
    requestCount++;
  }

  let request = client.setBSOsBatching("testcoll");
  request.locallyModifiedVersion = collection.timestamp;

  for (let i = 1; i < 251; i++) {
    let bso = new BasicStorageObject("bso" + i);
    bso.payload = "foo" + i;
    request.addBSO(bso);
  }

  request.finish(function onFinish(request) {
    do_check_eq(requestCount, 3);

    do_check_eq(collection.timestamp, request.serverModifiedVersion);
    do_check_eq(collection.timestamp, request.locallyModifiedVersion);

    server.stop(run_next_test);
  });
});

add_test(function test_batching_set_initial_failure() {
  _("Ensure that an initial request failure setting BSOs is handled properly.");

  let [server, client, username] = getServerAndClient();

  let collection = server.user(username).createCollection("testcoll");
  collection.timestamp = Date.now();

  let requestCount = 0;
  server.callback.onRequest = function onRequest() {
    requestCount++;
  }

  let request = client.setBSOsBatching("testcoll");
  request.locallyModifiedVersion = collection.timestamp - 1;

  for (let i = 1; i < 250; i++) {
    let bso = new BasicStorageObject("bso" + i);
    bso.payload = "foo" + i;
    request.addBSO(bso);
  }

  request.finish(function onFinish(request) {
    do_check_eq(requestCount, 1);

    do_check_eq(request.successfulIDs.length, 0);
    do_check_eq(Object.keys(request.failures).length, 0);

    server.stop(run_next_test);
  });
});

add_test(function test_batching_set_subsequent_failure() {
  _("Ensure a non-initial failure during batching set is handled properly.");

  let [server, client, username] = getServerAndClient();
  let collection = server.user(username).createCollection("testcoll");
  collection.timestamp = Date.now();

  let requestCount = 0;
  server.callback.onRequest = function onRequest() {
    requestCount++;

    if (requestCount == 1) {
      return;
    }

    collection.timestamp++;
  }

  let request = client.setBSOsBatching("testcoll");
  request.locallyModifiedVersion = collection.timestamp;

  for (let i = 0; i < 250; i++) {
    let bso = new BasicStorageObject("bso" + i);
    bso.payload = "foo" + i;
    request.addBSO(bso);
  }

  request.finish(function onFinish(request) {
    do_check_eq(requestCount, 2);
    do_check_eq(request.successfulIDs.length, 100);
    do_check_eq(Object.keys(request.failures).length, 0);

    server.stop(run_next_test);
  });
});

function getBatchedDeleteData(collection="testcoll") {
  let [server, client, username] = getServerAndClient();

  let serverBSOs = {};
  for (let i = 1000; i; i -= 1) {
    serverBSOs["bso" + i] = new ServerBSO("bso" + i, "payload" + i);
  }

  let user = server.user(username);
  user.createCollection(collection, serverBSOs);

  return [server, client, username, collection];
}

add_test(function test_batched_delete_single() {
  _("Ensure batched delete with single request works.");

  let [server, client, username, collection] = getBatchedDeleteData();

  let requestCount = 0;
  server.callback.onRequest = function onRequest() {
    requestCount += 1;
  }

  let request = client.deleteBSOsBatching(collection);
  for (let i = 1; i < 51; i += 1) {
    request.addID("bso" + i);
  }

  request.finish(function onFinish(request) {
    do_check_eq(requestCount, 1);
    do_check_eq(request.errors.length, 0);

    let coll = server.user(username).collection(collection);
    do_check_eq(coll.count(), 950);

    do_check_eq(request.serverModifiedVersion, coll.timestamp);

    server.stop(run_next_test);
  });
});

add_test(function test_batched_delete_multiple() {
  _("Ensure batched delete splits requests properly.");

  let [server, client, username, collection] = getBatchedDeleteData();

  let requestCount = 0;
  server.callback.onRequest = function onRequest() {
    requestCount += 1;
  }

  let request = client.deleteBSOsBatching(collection);
  for (let i = 1; i < 251; i += 1) {
    request.addID("bso" + i);
  }

  request.finish(function onFinish(request) {
    do_check_eq(requestCount, 3);
    do_check_eq(request.errors.length, 0);

    let coll = server.user(username).collection(collection);
    do_check_eq(coll.count(), 750);

    do_check_eq(request.serverModifiedVersion, coll.timestamp);

    server.stop(run_next_test);
  });
});

add_test(function test_batched_delete_conditional_success() {
  _("Ensure conditional batched delete all work.");

  let [server, client, username, collection] = getBatchedDeleteData();

  let requestCount = 0;
  server.callback.onRequest = function onRequest() {
    requestCount++;
  }

  let serverCollection = server.user(username).collection(collection);
  let initialTimestamp = serverCollection.timestamp;

  let request = client.deleteBSOsBatching(collection);
  request.locallyModifiedVersion = initialTimestamp;

  for (let i = 1; i < 251; i += 1) {
    request.addID("bso" + 1);
  }

  request.finish(function onFinish(request) {
    do_check_eq(requestCount, 3);
    do_check_eq(request.errors.length, 0);

    do_check_true(request.locallyModifiedVersion > initialTimestamp);

    server.stop(run_next_test);
  });
});

add_test(function test_batched_delete_conditional_initial_failure() {
  _("Ensure conditional batched delete failure on initial request works.");

  // The client needs to issue multiple requests but the first one was
  // rejected. The client should only issue that initial request.
  let [server, client, username, collection] = getBatchedDeleteData();

  let requestCount = 0;
  server.callback.onRequest = function onRequest() {
    requestCount++;
  }

  let serverCollection = server.user(username).collection(collection);
  let request = client.deleteBSOsBatching(collection);
  request.locallyModifiedVersion = serverCollection.timestamp - 1;

  for (let i = 1; i < 251; i += 1) {
    request.addID("bso" + i);
  }

  request.finish(function onFinish(request) {
    do_check_eq(requestCount, 1);
    do_check_eq(request.errors.length, 1);

    server.stop(run_next_test);
  });
});

add_test(function test_batched_delete_conditional_subsequent_failure() {
  _("Ensure conditional batched delete failure on non-initial request.");

  let [server, client, username, collection] = getBatchedDeleteData();

  let serverCollection = server.user(username).collection(collection);

  let requestCount = 0;
  server.callback.onRequest = function onRequest() {
    requestCount++;

    if (requestCount <= 1) {
      return;
    }

    // Advance collection's timestamp on subsequent requests so request is
    // rejected.
    serverCollection.timestamp++;
  }

  let request = client.deleteBSOsBatching(collection);
  request.locallyModifiedVersion = serverCollection.timestamp;

  for (let i = 1; i < 251; i += 1) {
    request.addID("bso" + i);
  }

  request.finish(function onFinish(request) {
    do_check_eq(requestCount, 2);
    do_check_eq(request.errors.length, 1);

    server.stop(run_next_test);
  });
});
