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

"use strict";

/**
 * @file Checks that the MLBF updating logic works reasonably.
 */

Services.prefs.setBoolPref("extensions.blocklist.useMLBF", true);
const ExtensionBlocklistMLBF = getExtensionBlocklistMLBF();

// This test needs to interact with the RemoteSettings client.
ExtensionBlocklistMLBF.ensureInitialized();

// Multiple internal calls to update should be coalesced and end up with the
// MLBF attachment from the last update call.
add_task(async function collapse_multiple_pending_update_requests() {
  const observed = [];

  // The first step of starting an update is to read from the RemoteSettings
  // collection. When a non-forced update is requested while another update is
  // pending, the non-forced update should return/await the previous call
  // instead of starting a new read/fetch from the RemoteSettings collection.
  // Add a spy to the RemoteSettings client, so we can verify that the number
  // of RemoteSettings accesses matches with what we expect.
  const originalClientGet = ExtensionBlocklistMLBF._client.get;
  const spyClientGet = tag => {
    ExtensionBlocklistMLBF._client.get = async function () {
      // Record the method call.
      observed.push(tag);
      // Clone a valid record and tag it so we can identify it below.
      let dummyRecord = JSON.parse(JSON.stringify(MLBF_RECORD));
      dummyRecord.tagged = tag;
      return [dummyRecord];
    };
  };

  // Another significant part of updating is fetching the MLBF attachment.
  // Add a spy too, so we can check which attachment is being requested.
  const originalFetchMLBF = ExtensionBlocklistMLBF._fetchMLBF;
  ExtensionBlocklistMLBF._fetchMLBF = async function (record) {
    observed.push(`fetchMLBF:${record.tagged}`);
    throw new Error(`Deliberately ignoring call to MLBF:${record.tagged}`);
  };

  spyClientGet("initial"); // Very first call = read RS.
  let update1 = ExtensionBlocklistMLBF._updateMLBF(false);
  spyClientGet("unexpected update2"); // Non-forced update = reuse update1.
  let update2 = ExtensionBlocklistMLBF._updateMLBF(false);
  spyClientGet("forced1"); // forceUpdate=true = supersede previous update.
  let forcedUpdate1 = ExtensionBlocklistMLBF._updateMLBF(true);
  spyClientGet("forced2"); // forceUpdate=true = supersede previous update.
  let forcedUpdate2 = ExtensionBlocklistMLBF._updateMLBF(true);

  let res = await Promise.all([update1, update2, forcedUpdate1, forcedUpdate2]);

  Assert.equal(observed.length, 4, "expected number of observed events");
  Assert.equal(observed[0], "initial", "First update should request records");
  Assert.equal(observed[1], "forced1", "Forced update supersedes initial");
  Assert.equal(observed[2], "forced2", "Forced update supersedes forced1");
  // We call the _updateMLBF methods immediately after each other. Every update
  // request starts with an asynchronous operation (looking up the RS records),
  // so the implementation should return early for all update requests except
  // for the last one. So we should only observe a fetch for the last request.
  Assert.equal(observed[3], "fetchMLBF:forced2", "expected fetch result");

  // All update requests should end up with the same result.
  Assert.equal(res[0], res[1], "update1 == update2");
  Assert.equal(res[1], res[2], "update2 == forcedUpdate1");
  Assert.equal(res[2], res[3], "forcedUpdate1 == forcedUpdate2");

  ExtensionBlocklistMLBF._client.get = originalClientGet;
  ExtensionBlocklistMLBF._fetchMLBF = originalFetchMLBF;
});
