Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
- Manifest: services/common/tests/unit/xpcshell.toml
/* Any copyright is dedicated to the Public Domain.
const { Kinto } = ChromeUtils.importESModule(
);
const { FirefoxAdapter } = ChromeUtils.importESModule(
);
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 = {
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,
},
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]
);
}