512 lines
16 KiB
JavaScript
512 lines
16 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
const { Kinto } = ChromeUtils.importESModule(
|
|
"resource://services-common/kinto-offline-client.sys.mjs"
|
|
);
|
|
const { FirefoxAdapter } = ChromeUtils.importESModule(
|
|
"resource://services-common/kinto-storage-adapter.sys.mjs"
|
|
);
|
|
|
|
var server;
|
|
|
|
// set up what we need to make storage adapters
|
|
const kintoFilename = "kinto.sqlite";
|
|
|
|
function do_get_kinto_sqliteHandle() {
|
|
return FirefoxAdapter.openConnection({ path: kintoFilename });
|
|
}
|
|
|
|
function do_get_kinto_collection(sqliteHandle, collection = "test_collection") {
|
|
let config = {
|
|
remote: `http://localhost:${server.identity.primaryPort}/v1/`,
|
|
headers: { Authorization: "Basic " + btoa("user:pass") },
|
|
adapter: FirefoxAdapter,
|
|
adapterOptions: { sqliteHandle },
|
|
};
|
|
return new Kinto(config).collection(collection);
|
|
}
|
|
|
|
async function clear_collection() {
|
|
let sqliteHandle;
|
|
try {
|
|
sqliteHandle = await do_get_kinto_sqliteHandle();
|
|
const collection = do_get_kinto_collection(sqliteHandle);
|
|
await collection.clear();
|
|
} finally {
|
|
await sqliteHandle.close();
|
|
}
|
|
}
|
|
|
|
// test some operations on a local collection
|
|
add_task(async function test_kinto_add_get() {
|
|
let sqliteHandle;
|
|
try {
|
|
sqliteHandle = await do_get_kinto_sqliteHandle();
|
|
const collection = do_get_kinto_collection(sqliteHandle);
|
|
|
|
let newRecord = { foo: "bar" };
|
|
// check a record is created
|
|
let createResult = await collection.create(newRecord);
|
|
Assert.equal(createResult.data.foo, newRecord.foo);
|
|
// check getting the record gets the same info
|
|
let getResult = await collection.get(createResult.data.id);
|
|
deepEqual(createResult.data, getResult.data);
|
|
// check what happens if we create the same item again (it should throw
|
|
// since you can't create with id)
|
|
try {
|
|
await collection.create(createResult.data);
|
|
do_throw("Creation of a record with an id should fail");
|
|
} catch (err) {}
|
|
// try a few creates without waiting for the first few to resolve
|
|
let promises = [];
|
|
promises.push(collection.create(newRecord));
|
|
promises.push(collection.create(newRecord));
|
|
promises.push(collection.create(newRecord));
|
|
await collection.create(newRecord);
|
|
await Promise.all(promises);
|
|
} finally {
|
|
await sqliteHandle.close();
|
|
}
|
|
});
|
|
|
|
add_task(clear_collection);
|
|
|
|
// test some operations on multiple connections
|
|
add_task(async function test_kinto_add_get() {
|
|
let sqliteHandle;
|
|
try {
|
|
sqliteHandle = await do_get_kinto_sqliteHandle();
|
|
const collection1 = do_get_kinto_collection(sqliteHandle);
|
|
const collection2 = do_get_kinto_collection(
|
|
sqliteHandle,
|
|
"test_collection_2"
|
|
);
|
|
|
|
let newRecord = { foo: "bar" };
|
|
|
|
// perform several write operations alternately without waiting for promises
|
|
// to resolve
|
|
let promises = [];
|
|
for (let i = 0; i < 10; i++) {
|
|
promises.push(collection1.create(newRecord));
|
|
promises.push(collection2.create(newRecord));
|
|
}
|
|
|
|
// ensure subsequent operations still work
|
|
await Promise.all([
|
|
collection1.create(newRecord),
|
|
collection2.create(newRecord),
|
|
]);
|
|
await Promise.all(promises);
|
|
} finally {
|
|
await sqliteHandle.close();
|
|
}
|
|
});
|
|
|
|
add_task(clear_collection);
|
|
|
|
add_task(async function test_kinto_update() {
|
|
let sqliteHandle;
|
|
try {
|
|
sqliteHandle = await do_get_kinto_sqliteHandle();
|
|
const collection = do_get_kinto_collection(sqliteHandle);
|
|
const newRecord = { foo: "bar" };
|
|
// check a record is created
|
|
let createResult = await collection.create(newRecord);
|
|
Assert.equal(createResult.data.foo, newRecord.foo);
|
|
Assert.equal(createResult.data._status, "created");
|
|
// check we can update this OK
|
|
let copiedRecord = Object.assign(createResult.data, {});
|
|
deepEqual(createResult.data, copiedRecord);
|
|
copiedRecord.foo = "wibble";
|
|
let updateResult = await collection.update(copiedRecord);
|
|
// check the field was updated
|
|
Assert.equal(updateResult.data.foo, copiedRecord.foo);
|
|
// check the status is still "created", since we haven't synced
|
|
// the record
|
|
Assert.equal(updateResult.data._status, "created");
|
|
} finally {
|
|
await sqliteHandle.close();
|
|
}
|
|
});
|
|
|
|
add_task(clear_collection);
|
|
|
|
add_task(async function test_kinto_clear() {
|
|
let sqliteHandle;
|
|
try {
|
|
sqliteHandle = await do_get_kinto_sqliteHandle();
|
|
const collection = do_get_kinto_collection(sqliteHandle);
|
|
|
|
// create an expected number of records
|
|
const expected = 10;
|
|
const newRecord = { foo: "bar" };
|
|
for (let i = 0; i < expected; i++) {
|
|
await collection.create(newRecord);
|
|
}
|
|
// check the collection contains the correct number
|
|
let list = await collection.list();
|
|
Assert.equal(list.data.length, expected);
|
|
// clear the collection and check again - should be 0
|
|
await collection.clear();
|
|
list = await collection.list();
|
|
Assert.equal(list.data.length, 0);
|
|
} finally {
|
|
await sqliteHandle.close();
|
|
}
|
|
});
|
|
|
|
add_task(clear_collection);
|
|
|
|
add_task(async function test_kinto_delete() {
|
|
let sqliteHandle;
|
|
try {
|
|
sqliteHandle = await do_get_kinto_sqliteHandle();
|
|
const collection = do_get_kinto_collection(sqliteHandle);
|
|
const newRecord = { foo: "bar" };
|
|
// check a record is created
|
|
let createResult = await collection.create(newRecord);
|
|
Assert.equal(createResult.data.foo, newRecord.foo);
|
|
// check getting the record gets the same info
|
|
let getResult = await collection.get(createResult.data.id);
|
|
deepEqual(createResult.data, getResult.data);
|
|
// delete that record
|
|
let deleteResult = await collection.delete(createResult.data.id);
|
|
// check the ID is set on the result
|
|
Assert.equal(getResult.data.id, deleteResult.data.id);
|
|
// and check that get no longer returns the record
|
|
try {
|
|
getResult = await collection.get(createResult.data.id);
|
|
do_throw("there should not be a result");
|
|
} catch (e) {}
|
|
} finally {
|
|
await sqliteHandle.close();
|
|
}
|
|
});
|
|
|
|
add_task(async function test_kinto_list() {
|
|
let sqliteHandle;
|
|
try {
|
|
sqliteHandle = await do_get_kinto_sqliteHandle();
|
|
const collection = do_get_kinto_collection(sqliteHandle);
|
|
const expected = 10;
|
|
const created = [];
|
|
for (let i = 0; i < expected; i++) {
|
|
let newRecord = { foo: "test " + i };
|
|
let createResult = await collection.create(newRecord);
|
|
created.push(createResult.data);
|
|
}
|
|
// check the collection contains the correct number
|
|
let list = await collection.list();
|
|
Assert.equal(list.data.length, expected);
|
|
|
|
// check that all created records exist in the retrieved list
|
|
for (let createdRecord of created) {
|
|
let found = false;
|
|
for (let retrievedRecord of list.data) {
|
|
if (createdRecord.id == retrievedRecord.id) {
|
|
deepEqual(createdRecord, retrievedRecord);
|
|
found = true;
|
|
}
|
|
}
|
|
Assert.ok(found);
|
|
}
|
|
} finally {
|
|
await sqliteHandle.close();
|
|
}
|
|
});
|
|
|
|
add_task(clear_collection);
|
|
|
|
add_task(async function test_importBulk_ignores_already_imported_records() {
|
|
let sqliteHandle;
|
|
try {
|
|
sqliteHandle = await do_get_kinto_sqliteHandle();
|
|
const collection = do_get_kinto_collection(sqliteHandle);
|
|
const record = {
|
|
id: "41b71c13-17e9-4ee3-9268-6a41abf9730f",
|
|
title: "foo",
|
|
last_modified: 1457896541,
|
|
};
|
|
await collection.importBulk([record]);
|
|
let impactedRecords = await collection.importBulk([record]);
|
|
Assert.equal(impactedRecords.length, 0);
|
|
} finally {
|
|
await sqliteHandle.close();
|
|
}
|
|
});
|
|
|
|
add_task(clear_collection);
|
|
|
|
add_task(async function test_loadDump_should_overwrite_old_records() {
|
|
let sqliteHandle;
|
|
try {
|
|
sqliteHandle = await do_get_kinto_sqliteHandle();
|
|
const collection = do_get_kinto_collection(sqliteHandle);
|
|
const record = {
|
|
id: "41b71c13-17e9-4ee3-9268-6a41abf9730f",
|
|
title: "foo",
|
|
last_modified: 1457896541,
|
|
};
|
|
await collection.loadDump([record]);
|
|
const updated = Object.assign({}, record, { last_modified: 1457896543 });
|
|
let impactedRecords = await collection.loadDump([updated]);
|
|
Assert.equal(impactedRecords.length, 1);
|
|
} finally {
|
|
await sqliteHandle.close();
|
|
}
|
|
});
|
|
|
|
add_task(clear_collection);
|
|
|
|
add_task(async function test_loadDump_should_not_overwrite_unsynced_records() {
|
|
let sqliteHandle;
|
|
try {
|
|
sqliteHandle = await do_get_kinto_sqliteHandle();
|
|
const collection = do_get_kinto_collection(sqliteHandle);
|
|
const recordId = "41b71c13-17e9-4ee3-9268-6a41abf9730f";
|
|
await collection.create(
|
|
{ id: recordId, title: "foo" },
|
|
{ useRecordId: true }
|
|
);
|
|
const record = { id: recordId, title: "bar", last_modified: 1457896541 };
|
|
let impactedRecords = await collection.loadDump([record]);
|
|
Assert.equal(impactedRecords.length, 0);
|
|
} finally {
|
|
await sqliteHandle.close();
|
|
}
|
|
});
|
|
|
|
add_task(clear_collection);
|
|
|
|
add_task(
|
|
async function test_loadDump_should_not_overwrite_records_without_last_modified() {
|
|
let sqliteHandle;
|
|
try {
|
|
sqliteHandle = await do_get_kinto_sqliteHandle();
|
|
const collection = do_get_kinto_collection(sqliteHandle);
|
|
const recordId = "41b71c13-17e9-4ee3-9268-6a41abf9730f";
|
|
await collection.create({ id: recordId, title: "foo" }, { synced: true });
|
|
const record = { id: recordId, title: "bar", last_modified: 1457896541 };
|
|
let impactedRecords = await collection.loadDump([record]);
|
|
Assert.equal(impactedRecords.length, 0);
|
|
} finally {
|
|
await sqliteHandle.close();
|
|
}
|
|
}
|
|
);
|
|
|
|
add_task(clear_collection);
|
|
|
|
// Now do some sanity checks against a server - we're not looking to test
|
|
// core kinto.js functionality here (there is excellent test coverage in
|
|
// kinto.js), more making sure things are basically working as expected.
|
|
add_task(async function test_kinto_sync() {
|
|
const configPath = "/v1/";
|
|
const metadataPath = "/v1/buckets/default/collections/test_collection";
|
|
const recordsPath = "/v1/buckets/default/collections/test_collection/records";
|
|
// register a handler
|
|
function handleResponse(request, response) {
|
|
try {
|
|
const sampled = getSampleResponse(request, server.identity.primaryPort);
|
|
if (!sampled) {
|
|
do_throw(
|
|
`unexpected ${request.method} request for ${request.path}?${request.queryString}`
|
|
);
|
|
}
|
|
|
|
response.setStatusLine(
|
|
null,
|
|
sampled.status.status,
|
|
sampled.status.statusText
|
|
);
|
|
// send the headers
|
|
for (let headerLine of sampled.sampleHeaders) {
|
|
let headerElements = headerLine.split(":");
|
|
response.setHeader(headerElements[0], headerElements[1].trimLeft());
|
|
}
|
|
response.setHeader("Date", new Date().toUTCString());
|
|
|
|
response.write(sampled.responseBody);
|
|
} catch (e) {
|
|
dump(`${e}\n`);
|
|
}
|
|
}
|
|
server.registerPathHandler(configPath, handleResponse);
|
|
server.registerPathHandler(metadataPath, handleResponse);
|
|
server.registerPathHandler(recordsPath, handleResponse);
|
|
|
|
// create an empty collection, sync to populate
|
|
let sqliteHandle;
|
|
try {
|
|
let result;
|
|
sqliteHandle = await do_get_kinto_sqliteHandle();
|
|
const collection = do_get_kinto_collection(sqliteHandle);
|
|
|
|
result = await collection.sync();
|
|
Assert.ok(result.ok);
|
|
|
|
// our test data has a single record; it should be in the local collection
|
|
let list = await collection.list();
|
|
Assert.equal(list.data.length, 1);
|
|
|
|
// now sync again; we should now have 2 records
|
|
result = await collection.sync();
|
|
Assert.ok(result.ok);
|
|
list = await collection.list();
|
|
Assert.equal(list.data.length, 2);
|
|
|
|
// sync again; the second records should have been modified
|
|
const before = list.data[0].title;
|
|
result = await collection.sync();
|
|
Assert.ok(result.ok);
|
|
list = await collection.list();
|
|
const after = list.data[1].title;
|
|
Assert.notEqual(before, after);
|
|
|
|
const manualID = list.data[0].id;
|
|
Assert.equal(list.data.length, 3);
|
|
Assert.equal(manualID, "some-manually-chosen-id");
|
|
} finally {
|
|
await sqliteHandle.close();
|
|
}
|
|
});
|
|
|
|
function run_test() {
|
|
// Set up an HTTP Server
|
|
server = new HttpServer();
|
|
server.start(-1);
|
|
|
|
run_next_test();
|
|
|
|
registerCleanupFunction(function () {
|
|
server.stop(function () {});
|
|
});
|
|
}
|
|
|
|
// get a response for a given request from sample data
|
|
function getSampleResponse(req, port) {
|
|
const responses = {
|
|
OPTIONS: {
|
|
sampleHeaders: [
|
|
"Access-Control-Allow-Headers: Content-Length,Expires,Backoff,Retry-After,Last-Modified,Total-Records,ETag,Pragma,Cache-Control,authorization,content-type,if-none-match,Alert,Next-Page",
|
|
"Access-Control-Allow-Methods: GET,HEAD,OPTIONS,POST,DELETE,OPTIONS",
|
|
"Access-Control-Allow-Origin: *",
|
|
"Content-Type: application/json; charset=UTF-8",
|
|
"Server: waitress",
|
|
],
|
|
status: { status: 200, statusText: "OK" },
|
|
responseBody: "null",
|
|
},
|
|
"GET:/v1/?": {
|
|
sampleHeaders: [
|
|
"Access-Control-Allow-Origin: *",
|
|
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
|
"Content-Type: application/json; charset=UTF-8",
|
|
"Server: waitress",
|
|
],
|
|
status: { status: 200, statusText: "OK" },
|
|
responseBody: JSON.stringify({
|
|
settings: {
|
|
batch_max_requests: 25,
|
|
},
|
|
url: `http://localhost:${port}/v1/`,
|
|
documentation: "https://kinto.readthedocs.org/",
|
|
version: "1.5.1",
|
|
commit: "cbc6f58",
|
|
hello: "kinto",
|
|
}),
|
|
},
|
|
"GET:/v1/buckets/default/collections/test_collection": {
|
|
sampleHeaders: [
|
|
"Access-Control-Allow-Origin: *",
|
|
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
|
"Content-Type: application/json; charset=UTF-8",
|
|
"Server: waitress",
|
|
'Etag: "1234"',
|
|
],
|
|
status: { status: 200, statusText: "OK" },
|
|
responseBody: JSON.stringify({
|
|
data: {
|
|
id: "test_collection",
|
|
last_modified: 1234,
|
|
},
|
|
}),
|
|
},
|
|
"GET:/v1/buckets/default/collections/test_collection/records?_sort=-last_modified":
|
|
{
|
|
sampleHeaders: [
|
|
"Access-Control-Allow-Origin: *",
|
|
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
|
"Content-Type: application/json; charset=UTF-8",
|
|
"Server: waitress",
|
|
'Etag: "1445606341071"',
|
|
],
|
|
status: { status: 200, statusText: "OK" },
|
|
responseBody: JSON.stringify({
|
|
data: [
|
|
{
|
|
last_modified: 1445606341071,
|
|
done: false,
|
|
id: "68db8313-686e-4fff-835e-07d78ad6f2af",
|
|
title: "New test",
|
|
},
|
|
],
|
|
}),
|
|
},
|
|
"GET:/v1/buckets/default/collections/test_collection/records?_sort=-last_modified&_since=1445606341071":
|
|
{
|
|
sampleHeaders: [
|
|
"Access-Control-Allow-Origin: *",
|
|
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
|
"Content-Type: application/json; charset=UTF-8",
|
|
"Server: waitress",
|
|
'Etag: "1445607941223"',
|
|
],
|
|
status: { status: 200, statusText: "OK" },
|
|
responseBody: JSON.stringify({
|
|
data: [
|
|
{
|
|
last_modified: 1445607941223,
|
|
done: false,
|
|
id: "901967b0-f729-4b30-8d8d-499cba7f4b1d",
|
|
title: "Another new test",
|
|
},
|
|
],
|
|
}),
|
|
},
|
|
"GET:/v1/buckets/default/collections/test_collection/records?_sort=-last_modified&_since=1445607941223":
|
|
{
|
|
sampleHeaders: [
|
|
"Access-Control-Allow-Origin: *",
|
|
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
|
"Content-Type: application/json; charset=UTF-8",
|
|
"Server: waitress",
|
|
'Etag: "1445607541267"',
|
|
],
|
|
status: { status: 200, statusText: "OK" },
|
|
responseBody: JSON.stringify({
|
|
data: [
|
|
{
|
|
last_modified: 1445607541265,
|
|
done: false,
|
|
id: "901967b0-f729-4b30-8d8d-499cba7f4b1d",
|
|
title: "Modified title",
|
|
},
|
|
{
|
|
last_modified: 1445607541267,
|
|
done: true,
|
|
id: "some-manually-chosen-id",
|
|
title: "New record with custom ID",
|
|
},
|
|
],
|
|
}),
|
|
},
|
|
};
|
|
return (
|
|
responses[`${req.method}:${req.path}?${req.queryString}`] ||
|
|
responses[`${req.method}:${req.path}`] ||
|
|
responses[req.method]
|
|
);
|
|
}
|