tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_kinto.js (16745B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 const { Kinto } = ChromeUtils.importESModule(
      5  "resource://services-common/kinto-offline-client.sys.mjs"
      6 );
      7 const { FirefoxAdapter } = ChromeUtils.importESModule(
      8  "resource://services-common/kinto-storage-adapter.sys.mjs"
      9 );
     10 
     11 var server;
     12 
     13 // set up what we need to make storage adapters
     14 const kintoFilename = "kinto.sqlite";
     15 
     16 function do_get_kinto_sqliteHandle() {
     17  return FirefoxAdapter.openConnection({ path: kintoFilename });
     18 }
     19 
     20 function do_get_kinto_collection(sqliteHandle, collection = "test_collection") {
     21  let config = {
     22    remote: `http://localhost:${server.identity.primaryPort}/v1/`,
     23    headers: { Authorization: "Basic " + btoa("user:pass") },
     24    adapter: FirefoxAdapter,
     25    adapterOptions: { sqliteHandle },
     26  };
     27  return new Kinto(config).collection(collection);
     28 }
     29 
     30 async function clear_collection() {
     31  let sqliteHandle;
     32  try {
     33    sqliteHandle = await do_get_kinto_sqliteHandle();
     34    const collection = do_get_kinto_collection(sqliteHandle);
     35    await collection.clear();
     36  } finally {
     37    await sqliteHandle.close();
     38  }
     39 }
     40 
     41 // test some operations on a local collection
     42 add_task(async function test_kinto_add_get() {
     43  let sqliteHandle;
     44  try {
     45    sqliteHandle = await do_get_kinto_sqliteHandle();
     46    const collection = do_get_kinto_collection(sqliteHandle);
     47 
     48    let newRecord = { foo: "bar" };
     49    // check a record is created
     50    let createResult = await collection.create(newRecord);
     51    Assert.equal(createResult.data.foo, newRecord.foo);
     52    // check getting the record gets the same info
     53    let getResult = await collection.get(createResult.data.id);
     54    deepEqual(createResult.data, getResult.data);
     55    // check what happens if we create the same item again (it should throw
     56    // since you can't create with id)
     57    try {
     58      await collection.create(createResult.data);
     59      do_throw("Creation of a record with an id should fail");
     60    } catch (err) {}
     61    // try a few creates without waiting for the first few to resolve
     62    let promises = [];
     63    promises.push(collection.create(newRecord));
     64    promises.push(collection.create(newRecord));
     65    promises.push(collection.create(newRecord));
     66    await collection.create(newRecord);
     67    await Promise.all(promises);
     68  } finally {
     69    await sqliteHandle.close();
     70  }
     71 });
     72 
     73 add_task(clear_collection);
     74 
     75 // test some operations on multiple connections
     76 add_task(async function test_kinto_add_get() {
     77  let sqliteHandle;
     78  try {
     79    sqliteHandle = await do_get_kinto_sqliteHandle();
     80    const collection1 = do_get_kinto_collection(sqliteHandle);
     81    const collection2 = do_get_kinto_collection(
     82      sqliteHandle,
     83      "test_collection_2"
     84    );
     85 
     86    let newRecord = { foo: "bar" };
     87 
     88    // perform several write operations alternately without waiting for promises
     89    // to resolve
     90    let promises = [];
     91    for (let i = 0; i < 10; i++) {
     92      promises.push(collection1.create(newRecord));
     93      promises.push(collection2.create(newRecord));
     94    }
     95 
     96    // ensure subsequent operations still work
     97    await Promise.all([
     98      collection1.create(newRecord),
     99      collection2.create(newRecord),
    100    ]);
    101    await Promise.all(promises);
    102  } finally {
    103    await sqliteHandle.close();
    104  }
    105 });
    106 
    107 add_task(clear_collection);
    108 
    109 add_task(async function test_kinto_update() {
    110  let sqliteHandle;
    111  try {
    112    sqliteHandle = await do_get_kinto_sqliteHandle();
    113    const collection = do_get_kinto_collection(sqliteHandle);
    114    const newRecord = { foo: "bar" };
    115    // check a record is created
    116    let createResult = await collection.create(newRecord);
    117    Assert.equal(createResult.data.foo, newRecord.foo);
    118    Assert.equal(createResult.data._status, "created");
    119    // check we can update this OK
    120    let copiedRecord = Object.assign(createResult.data, {});
    121    deepEqual(createResult.data, copiedRecord);
    122    copiedRecord.foo = "wibble";
    123    let updateResult = await collection.update(copiedRecord);
    124    // check the field was updated
    125    Assert.equal(updateResult.data.foo, copiedRecord.foo);
    126    // check the status is still "created", since we haven't synced
    127    // the record
    128    Assert.equal(updateResult.data._status, "created");
    129  } finally {
    130    await sqliteHandle.close();
    131  }
    132 });
    133 
    134 add_task(clear_collection);
    135 
    136 add_task(async function test_kinto_clear() {
    137  let sqliteHandle;
    138  try {
    139    sqliteHandle = await do_get_kinto_sqliteHandle();
    140    const collection = do_get_kinto_collection(sqliteHandle);
    141 
    142    // create an expected number of records
    143    const expected = 10;
    144    const newRecord = { foo: "bar" };
    145    for (let i = 0; i < expected; i++) {
    146      await collection.create(newRecord);
    147    }
    148    // check the collection contains the correct number
    149    let list = await collection.list();
    150    Assert.equal(list.data.length, expected);
    151    // clear the collection and check again - should be 0
    152    await collection.clear();
    153    list = await collection.list();
    154    Assert.equal(list.data.length, 0);
    155  } finally {
    156    await sqliteHandle.close();
    157  }
    158 });
    159 
    160 add_task(clear_collection);
    161 
    162 add_task(async function test_kinto_delete() {
    163  let sqliteHandle;
    164  try {
    165    sqliteHandle = await do_get_kinto_sqliteHandle();
    166    const collection = do_get_kinto_collection(sqliteHandle);
    167    const newRecord = { foo: "bar" };
    168    // check a record is created
    169    let createResult = await collection.create(newRecord);
    170    Assert.equal(createResult.data.foo, newRecord.foo);
    171    // check getting the record gets the same info
    172    let getResult = await collection.get(createResult.data.id);
    173    deepEqual(createResult.data, getResult.data);
    174    // delete that record
    175    let deleteResult = await collection.delete(createResult.data.id);
    176    // check the ID is set on the result
    177    Assert.equal(getResult.data.id, deleteResult.data.id);
    178    // and check that get no longer returns the record
    179    try {
    180      getResult = await collection.get(createResult.data.id);
    181      do_throw("there should not be a result");
    182    } catch (e) {}
    183  } finally {
    184    await sqliteHandle.close();
    185  }
    186 });
    187 
    188 add_task(async function test_kinto_list() {
    189  let sqliteHandle;
    190  try {
    191    sqliteHandle = await do_get_kinto_sqliteHandle();
    192    const collection = do_get_kinto_collection(sqliteHandle);
    193    const expected = 10;
    194    const created = [];
    195    for (let i = 0; i < expected; i++) {
    196      let newRecord = { foo: "test " + i };
    197      let createResult = await collection.create(newRecord);
    198      created.push(createResult.data);
    199    }
    200    // check the collection contains the correct number
    201    let list = await collection.list();
    202    Assert.equal(list.data.length, expected);
    203 
    204    // check that all created records exist in the retrieved list
    205    for (let createdRecord of created) {
    206      let found = false;
    207      for (let retrievedRecord of list.data) {
    208        if (createdRecord.id == retrievedRecord.id) {
    209          deepEqual(createdRecord, retrievedRecord);
    210          found = true;
    211        }
    212      }
    213      Assert.ok(found);
    214    }
    215  } finally {
    216    await sqliteHandle.close();
    217  }
    218 });
    219 
    220 add_task(clear_collection);
    221 
    222 add_task(async function test_importBulk_ignores_already_imported_records() {
    223  let sqliteHandle;
    224  try {
    225    sqliteHandle = await do_get_kinto_sqliteHandle();
    226    const collection = do_get_kinto_collection(sqliteHandle);
    227    const record = {
    228      id: "41b71c13-17e9-4ee3-9268-6a41abf9730f",
    229      title: "foo",
    230      last_modified: 1457896541,
    231    };
    232    await collection.importBulk([record]);
    233    let impactedRecords = await collection.importBulk([record]);
    234    Assert.equal(impactedRecords.length, 0);
    235  } finally {
    236    await sqliteHandle.close();
    237  }
    238 });
    239 
    240 add_task(clear_collection);
    241 
    242 add_task(async function test_loadDump_should_overwrite_old_records() {
    243  let sqliteHandle;
    244  try {
    245    sqliteHandle = await do_get_kinto_sqliteHandle();
    246    const collection = do_get_kinto_collection(sqliteHandle);
    247    const record = {
    248      id: "41b71c13-17e9-4ee3-9268-6a41abf9730f",
    249      title: "foo",
    250      last_modified: 1457896541,
    251    };
    252    await collection.loadDump([record]);
    253    const updated = Object.assign({}, record, { last_modified: 1457896543 });
    254    let impactedRecords = await collection.loadDump([updated]);
    255    Assert.equal(impactedRecords.length, 1);
    256  } finally {
    257    await sqliteHandle.close();
    258  }
    259 });
    260 
    261 add_task(clear_collection);
    262 
    263 add_task(async function test_loadDump_should_not_overwrite_unsynced_records() {
    264  let sqliteHandle;
    265  try {
    266    sqliteHandle = await do_get_kinto_sqliteHandle();
    267    const collection = do_get_kinto_collection(sqliteHandle);
    268    const recordId = "41b71c13-17e9-4ee3-9268-6a41abf9730f";
    269    await collection.create(
    270      { id: recordId, title: "foo" },
    271      { useRecordId: true }
    272    );
    273    const record = { id: recordId, title: "bar", last_modified: 1457896541 };
    274    let impactedRecords = await collection.loadDump([record]);
    275    Assert.equal(impactedRecords.length, 0);
    276  } finally {
    277    await sqliteHandle.close();
    278  }
    279 });
    280 
    281 add_task(clear_collection);
    282 
    283 add_task(
    284  async function test_loadDump_should_not_overwrite_records_without_last_modified() {
    285    let sqliteHandle;
    286    try {
    287      sqliteHandle = await do_get_kinto_sqliteHandle();
    288      const collection = do_get_kinto_collection(sqliteHandle);
    289      const recordId = "41b71c13-17e9-4ee3-9268-6a41abf9730f";
    290      await collection.create({ id: recordId, title: "foo" }, { synced: true });
    291      const record = { id: recordId, title: "bar", last_modified: 1457896541 };
    292      let impactedRecords = await collection.loadDump([record]);
    293      Assert.equal(impactedRecords.length, 0);
    294    } finally {
    295      await sqliteHandle.close();
    296    }
    297  }
    298 );
    299 
    300 add_task(clear_collection);
    301 
    302 // Now do some sanity checks against a server - we're not looking to test
    303 // core kinto.js functionality here (there is excellent test coverage in
    304 // kinto.js), more making sure things are basically working as expected.
    305 add_task(async function test_kinto_sync() {
    306  const configPath = "/v1/";
    307  const metadataPath = "/v1/buckets/default/collections/test_collection";
    308  const recordsPath = "/v1/buckets/default/collections/test_collection/records";
    309  // register a handler
    310  function handleResponse(request, response) {
    311    try {
    312      const sampled = getSampleResponse(request, server.identity.primaryPort);
    313      if (!sampled) {
    314        do_throw(
    315          `unexpected ${request.method} request for ${request.path}?${request.queryString}`
    316        );
    317      }
    318 
    319      response.setStatusLine(
    320        null,
    321        sampled.status.status,
    322        sampled.status.statusText
    323      );
    324      // send the headers
    325      for (let headerLine of sampled.sampleHeaders) {
    326        let headerElements = headerLine.split(":");
    327        response.setHeader(headerElements[0], headerElements[1].trimLeft());
    328      }
    329      response.setHeader("Date", new Date().toUTCString());
    330 
    331      response.write(sampled.responseBody);
    332    } catch (e) {
    333      dump(`${e}\n`);
    334    }
    335  }
    336  server.registerPathHandler(configPath, handleResponse);
    337  server.registerPathHandler(metadataPath, handleResponse);
    338  server.registerPathHandler(recordsPath, handleResponse);
    339 
    340  // create an empty collection, sync to populate
    341  let sqliteHandle;
    342  try {
    343    let result;
    344    sqliteHandle = await do_get_kinto_sqliteHandle();
    345    const collection = do_get_kinto_collection(sqliteHandle);
    346 
    347    result = await collection.sync();
    348    Assert.ok(result.ok);
    349 
    350    // our test data has a single record; it should be in the local collection
    351    let list = await collection.list();
    352    Assert.equal(list.data.length, 1);
    353 
    354    // now sync again; we should now have 2 records
    355    result = await collection.sync();
    356    Assert.ok(result.ok);
    357    list = await collection.list();
    358    Assert.equal(list.data.length, 2);
    359 
    360    // sync again; the second records should have been modified
    361    const before = list.data[0].title;
    362    result = await collection.sync();
    363    Assert.ok(result.ok);
    364    list = await collection.list();
    365    const after = list.data[1].title;
    366    Assert.notEqual(before, after);
    367 
    368    const manualID = list.data[0].id;
    369    Assert.equal(list.data.length, 3);
    370    Assert.equal(manualID, "some-manually-chosen-id");
    371  } finally {
    372    await sqliteHandle.close();
    373  }
    374 });
    375 
    376 function run_test() {
    377  // Set up an HTTP Server
    378  server = new HttpServer();
    379  server.start(-1);
    380 
    381  run_next_test();
    382 
    383  registerCleanupFunction(function () {
    384    server.stop(function () {});
    385  });
    386 }
    387 
    388 // get a response for a given request from sample data
    389 function getSampleResponse(req, port) {
    390  const responses = {
    391    OPTIONS: {
    392      sampleHeaders: [
    393        "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",
    394        "Access-Control-Allow-Methods: GET,HEAD,OPTIONS,POST,DELETE,OPTIONS",
    395        "Access-Control-Allow-Origin: *",
    396        "Content-Type: application/json; charset=UTF-8",
    397        "Server: waitress",
    398      ],
    399      status: { status: 200, statusText: "OK" },
    400      responseBody: "null",
    401    },
    402    "GET:/v1/?": {
    403      sampleHeaders: [
    404        "Access-Control-Allow-Origin: *",
    405        "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
    406        "Content-Type: application/json; charset=UTF-8",
    407        "Server: waitress",
    408      ],
    409      status: { status: 200, statusText: "OK" },
    410      responseBody: JSON.stringify({
    411        settings: {
    412          batch_max_requests: 25,
    413        },
    414        url: `http://localhost:${port}/v1/`,
    415        documentation: "https://kinto.readthedocs.org/",
    416        version: "1.5.1",
    417        commit: "cbc6f58",
    418        hello: "kinto",
    419      }),
    420    },
    421    "GET:/v1/buckets/default/collections/test_collection": {
    422      sampleHeaders: [
    423        "Access-Control-Allow-Origin: *",
    424        "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
    425        "Content-Type: application/json; charset=UTF-8",
    426        "Server: waitress",
    427        'Etag: "1234"',
    428      ],
    429      status: { status: 200, statusText: "OK" },
    430      responseBody: JSON.stringify({
    431        data: {
    432          id: "test_collection",
    433          last_modified: 1234,
    434        },
    435      }),
    436    },
    437    "GET:/v1/buckets/default/collections/test_collection/records?_sort=-last_modified":
    438      {
    439        sampleHeaders: [
    440          "Access-Control-Allow-Origin: *",
    441          "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
    442          "Content-Type: application/json; charset=UTF-8",
    443          "Server: waitress",
    444          'Etag: "1445606341071"',
    445        ],
    446        status: { status: 200, statusText: "OK" },
    447        responseBody: JSON.stringify({
    448          data: [
    449            {
    450              last_modified: 1445606341071,
    451              done: false,
    452              id: "68db8313-686e-4fff-835e-07d78ad6f2af",
    453              title: "New test",
    454            },
    455          ],
    456        }),
    457      },
    458    "GET:/v1/buckets/default/collections/test_collection/records?_sort=-last_modified&_since=1445606341071":
    459      {
    460        sampleHeaders: [
    461          "Access-Control-Allow-Origin: *",
    462          "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
    463          "Content-Type: application/json; charset=UTF-8",
    464          "Server: waitress",
    465          'Etag: "1445607941223"',
    466        ],
    467        status: { status: 200, statusText: "OK" },
    468        responseBody: JSON.stringify({
    469          data: [
    470            {
    471              last_modified: 1445607941223,
    472              done: false,
    473              id: "901967b0-f729-4b30-8d8d-499cba7f4b1d",
    474              title: "Another new test",
    475            },
    476          ],
    477        }),
    478      },
    479    "GET:/v1/buckets/default/collections/test_collection/records?_sort=-last_modified&_since=1445607941223":
    480      {
    481        sampleHeaders: [
    482          "Access-Control-Allow-Origin: *",
    483          "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
    484          "Content-Type: application/json; charset=UTF-8",
    485          "Server: waitress",
    486          'Etag: "1445607541267"',
    487        ],
    488        status: { status: 200, statusText: "OK" },
    489        responseBody: JSON.stringify({
    490          data: [
    491            {
    492              last_modified: 1445607541265,
    493              done: false,
    494              id: "901967b0-f729-4b30-8d8d-499cba7f4b1d",
    495              title: "Modified title",
    496            },
    497            {
    498              last_modified: 1445607541267,
    499              done: true,
    500              id: "some-manually-chosen-id",
    501              title: "New record with custom ID",
    502            },
    503          ],
    504        }),
    505      },
    506  };
    507  return (
    508    responses[`${req.method}:${req.path}?${req.queryString}`] ||
    509    responses[`${req.method}:${req.path}`] ||
    510    responses[req.method]
    511  );
    512 }