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

// Import common head.
{
  /* import-globals-from ../head_common.js */
  let commonFile = do_get_file("../head_common.js", false);
  let uri = Services.io.newFileURI(commonFile);
  Services.scriptloader.loadSubScriptWithOptions(uri.spec, {
    target: this,
    allowUnsafeURL: true,
  });
}

// Put any other stuff relative to this test folder below.

ChromeUtils.defineESModuleGetters(this, {
  PlacesDBUtils: "resource://gre/modules/PlacesDBUtils.sys.mjs",
});

async function createCorruptDb(filename) {
  let path = PathUtils.join(PathUtils.profileDir, filename);
  await IOUtils.remove(path, { ignoreAbsent: true });
  // Create a corrupt database.
  let dir = do_get_cwd().path;
  let src = PathUtils.join(dir, "corruptDB.sqlite");
  await IOUtils.copy(src, path);
}

/**
 * Used in _replaceOnStartup_ tests as common test code. It checks whether we
 * are properly cloning or replacing a corrupt database.
 *
 * @param {string[]} src
 *        Array of strings which form a path to a test database, relative to
 *        the parent of this test folder.
 * @param {string} filename
 *        Database file name
 * @param {boolean} shouldClone
 *        Whether we expect the database to be cloned
 * @param {boolean} dbStatus
 *        The expected final database status
 */
async function test_database_replacement(src, filename, shouldClone, dbStatus) {
  registerCleanupFunction(() => {
    Services.prefs.clearUserPref("places.database.cloneOnCorruption");
  });
  Services.prefs.setBoolPref("places.database.cloneOnCorruption", shouldClone);

  // Only the main database file (places.sqlite) will be cloned, because
  // attached databased would break due to OS file lockings.
  let willClone = shouldClone && filename == DB_FILENAME;

  // Ensure that our databases don't exist yet.
  let dest = PathUtils.join(PathUtils.profileDir, filename);
  Assert.ok(
    !(await IOUtils.exists(dest)),
    `"${filename} should not exist initially`
  );
  let corrupt = PathUtils.join(PathUtils.profileDir, `${filename}.corrupt`);
  Assert.ok(
    !(await IOUtils.exists(corrupt)),
    `${filename}.corrupt should not exist initially`
  );

  let dir = PathUtils.parent(do_get_cwd().path);
  src = PathUtils.join(dir, ...src);
  await IOUtils.copy(src, dest);

  // Create some unique stuff to check later.
  let db = await Sqlite.openConnection({ path: dest });
  await db.execute(`CREATE TABLE moz_cloned (id INTEGER PRIMARY KEY)`);
  await db.execute(`CREATE TABLE not_cloned (id INTEGER PRIMARY KEY)`);
  await db.execute(`DELETE FROM moz_cloned`); // Shouldn't throw.
  await db.close();

  // Open the database with Places.
  Services.prefs.setCharPref(
    "places.database.replaceDatabaseOnStartup",
    filename
  );
  Assert.equal(PlacesUtils.history.databaseStatus, dbStatus);

  Assert.ok(await IOUtils.exists(dest), "The database should exist");

  // Check the new database still contains our special data.
  db = await Sqlite.openConnection({ path: dest });
  if (willClone) {
    await db.execute(`DELETE FROM moz_cloned`); // Shouldn't throw.
  }

  // Check the new database is really a new one.
  await Assert.rejects(
    db.execute(`DELETE FROM not_cloned`),
    /no such table/,
    "The database should have been replaced"
  );
  await db.close();

  if (willClone) {
    Assert.ok(
      !(await IOUtils.exists(corrupt)),
      "The corrupt db should not exist"
    );
    Assert.ok(
      !(await IOUtils.exists(corrupt + "-wal")),
      "The corrupt db wal should not exist"
    );
    Assert.ok(
      !(await IOUtils.exists(corrupt + "-shm")),
      "The corrupt db shm should not exist"
    );
  } else {
    Assert.ok(await IOUtils.exists(corrupt), "The corrupt db should exist");
  }

  Assert.equal(
    Services.prefs.getCharPref("places.database.replaceDatabaseOnStartup", ""),
    "",
    "The replaceDatabaseOnStartup pref should have been unset"
  );
}
