/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

async function run_test() {
  setupTestCommon();

  debugDump(
    "testing addition of a successful update to " +
      FILE_UPDATES_XML +
      " and verification of update properties including the format " +
      "prior to bug 530872"
  );

  setUpdateChannel("test_channel");

  let patchProps1 = {
    type: "partial",
    url: "http://partial/",
    size: "86",
    selected: "true",
    state: STATE_PENDING,
    custom1: 'custom1_attr="custom1 patch value"',
    custom2: 'custom2_attr="custom2 patch value"',
  };
  let patches1 = getLocalPatchString(patchProps1);
  let updateProps1 = {
    type: "major",
    name: "New",
    displayVersion: "version 4",
    appVersion: "4.0",
    platformVersion: "4.0",
    buildID: "20070811053724",
    detailsURL: "http://details1/",
    serviceURL: "http://service1/",
    installDate: "1238441300314",
    statusText: "test status text",
    isCompleteUpdate: "false",
    channel: "test_channel",
    foregroundDownload: "true",
    promptWaitTime: "345600",
    previousAppVersion: "3.0",
    custom1: 'custom1_attr="custom1 value"',
    custom2: 'custom2_attr="custom2 value"',
  };
  let updates1 = getLocalUpdateString(updateProps1, patches1);
  writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates1), true);
  writeStatusFile(STATE_SUCCEEDED);

  let patchProps2 = {
    type: "complete",
    url: "http://complete/",
    size: "75",
    selected: "true",
    state: STATE_FAILED,
    custom1: 'custom3_attr="custom3 patch value"',
    custom2: 'custom4_attr="custom4 patch value"',
  };
  let patches2 = getLocalPatchString(patchProps2);
  let updateProps2 = {
    type: "minor",
    name: "Existing",
    appVersion: "3.0",
    platformVersion: "3.0",
    detailsURL: "http://details2/",
    serviceURL: "http://service2/",
    statusText: getString("patchApplyFailure"),
    isCompleteUpdate: "true",
    channel: "test_channel",
    foregroundDownload: "false",
    promptWaitTime: "691200",
    custom1: 'custom3_attr="custom3 value"',
    custom2: 'custom4_attr="custom4 value"',
  };
  let updates2 = getLocalUpdateString(updateProps2, patches2);
  writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates2), false);

  await standardInit();

  Assert.ok(
    !(await gUpdateManager.getDownloadingUpdate()),
    "there should not be a downloading update"
  );
  Assert.ok(
    !(await gUpdateManager.getReadyUpdate()),
    "there should not be a ready update"
  );
  const history = await gUpdateManager.getHistory();
  Assert.equal(
    history.length,
    2,
    "the update manager updateCount attribute" + MSG_SHOULD_EQUAL
  );

  debugDump("checking the first update properties");
  let update = history[0].QueryInterface(Ci.nsIWritablePropertyBag);
  Assert.equal(
    update.state,
    STATE_SUCCEEDED,
    "the update state attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.type,
    "major",
    "the update type attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.name,
    "New",
    "the update name attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.displayVersion,
    "version 4",
    "the update displayVersion attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.appVersion,
    "4.0",
    "the update appVersion attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.buildID,
    "20070811053724",
    "the update buildID attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.detailsURL,
    "http://details1/",
    "the update detailsURL attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.serviceURL,
    "http://service1/",
    "the update serviceURL attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.installDate,
    "1238441300314",
    "the update installDate attribute" + MSG_SHOULD_EQUAL
  );
  // statusText is updated
  Assert.equal(
    update.statusText,
    getString("installSuccess"),
    "the update statusText attribute" + MSG_SHOULD_EQUAL
  );
  Assert.ok(
    !update.isCompleteUpdate,
    "the update isCompleteUpdate attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.channel,
    "test_channel",
    "the update channel attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.promptWaitTime,
    "345600",
    "the update promptWaitTime attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.previousAppVersion,
    "3.0",
    "the update previousAppVersion attribute" + MSG_SHOULD_EQUAL
  );
  // Custom attributes
  Assert.equal(
    update.getProperty("custom1_attr"),
    "custom1 value",
    "the update custom1_attr property" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.getProperty("custom2_attr"),
    "custom2 value",
    "the update custom2_attr property" + MSG_SHOULD_EQUAL
  );
  // nsIPropertyBag enumerator
  debugDump("checking the first update enumerator");
  Assert.ok(
    update.enumerator instanceof Ci.nsISimpleEnumerator,
    "update enumerator should be an instance of nsISimpleEnumerator"
  );
  let results = Array.from(update.enumerator);
  Assert.equal(
    results.length,
    3,
    "the length of the array created from the update enumerator" +
      MSG_SHOULD_EQUAL
  );
  Assert.ok(
    results.every(prop => prop instanceof Ci.nsIProperty),
    "the objects in the array created from the update enumerator " +
      "should all be an instance of nsIProperty"
  );
  Assert.equal(
    results[0].name,
    "custom1_attr",
    "the first property name" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    results[0].value,
    "custom1 value",
    "the first property value" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    results[1].name,
    "custom2_attr",
    "the second property name" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    results[1].value,
    "custom2 value",
    "the second property value" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    results[2].name,
    "foregroundDownload",
    "the second property name" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    results[2].value,
    "true",
    "the third property value" + MSG_SHOULD_EQUAL
  );

  debugDump("checking the first update patch properties");
  let patch = update.selectedPatch.QueryInterface(Ci.nsIWritablePropertyBag);
  Assert.equal(
    patch.type,
    "partial",
    "the update patch type attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    patch.URL,
    "http://partial/",
    "the update patch URL attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    patch.size,
    "86",
    "the update patch size attribute" + MSG_SHOULD_EQUAL
  );
  Assert.ok(
    !!patch.selected,
    "the update patch selected attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    patch.state,
    STATE_SUCCEEDED,
    "the update patch state attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    patch.getProperty("custom1_attr"),
    "custom1 patch value",
    "the update patch custom1_attr property" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    patch.getProperty("custom2_attr"),
    "custom2 patch value",
    "the update patch custom2_attr property" + MSG_SHOULD_EQUAL
  );
  // nsIPropertyBag enumerator
  debugDump("checking the first update patch enumerator");
  Assert.ok(
    patch.enumerator instanceof Ci.nsISimpleEnumerator,
    "patch enumerator should be an instance of nsISimpleEnumerator"
  );
  results = Array.from(patch.enumerator);

  // Two properties from this test, one from _postUpdateProcessing
  // (see Bug 1853821)
  Assert.equal(
    results.length,
    3,
    "the length of the array created from the patch enumerator" +
      MSG_SHOULD_EQUAL
  );
  Assert.ok(
    results.every(prop => prop instanceof Ci.nsIProperty),
    "the objects in the array created from the patch enumerator " +
      "should all be an instance of nsIProperty"
  );
  Assert.equal(
    results[0].name,
    "custom1_attr",
    "the first property name" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    results[0].value,
    "custom1 patch value",
    "the first property value" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    results[1].name,
    "custom2_attr",
    "the second property name" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    results[1].value,
    "custom2 patch value",
    "the second property value" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    results[2].name,
    "numTotalInstallAttempts",
    "the third property name" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    results[2].value,
    "1",
    "the third property value" + MSG_SHOULD_EQUAL
  );

  debugDump("checking the second update properties");
  update = history[1].QueryInterface(Ci.nsIWritablePropertyBag);
  Assert.equal(
    update.state,
    STATE_FAILED,
    "the update state attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.name,
    "Existing",
    "the update name attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.type,
    "minor",
    "the update type attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.displayVersion,
    "3.0",
    "the update displayVersion attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.appVersion,
    "3.0",
    "the update appVersion attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.platformVersion,
    "3.0",
    "the update platformVersion attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.detailsURL,
    "http://details2/",
    "the update detailsURL attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.serviceURL,
    "http://service2/",
    "the update serviceURL attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.installDate,
    "1238441400314",
    "the update installDate attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.statusText,
    getString("patchApplyFailure"),
    "the update statusText attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.buildID,
    "20080811053724",
    "the update buildID attribute" + MSG_SHOULD_EQUAL
  );
  Assert.ok(
    !!update.isCompleteUpdate,
    "the update isCompleteUpdate attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.channel,
    "test_channel",
    "the update channel attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.promptWaitTime,
    "691200",
    "the update promptWaitTime attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.previousAppVersion,
    "1.0",
    "the update previousAppVersion attribute" + MSG_SHOULD_EQUAL
  );
  // Custom attributes
  Assert.equal(
    update.getProperty("custom3_attr"),
    "custom3 value",
    "the update custom3_attr property" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    update.getProperty("custom4_attr"),
    "custom4 value",
    "the update custom4_attr property" + MSG_SHOULD_EQUAL
  );
  // nsIPropertyBag enumerator
  debugDump("checking the second update enumerator");
  Assert.ok(
    update.enumerator instanceof Ci.nsISimpleEnumerator,
    "update enumerator should be an instance of nsISimpleEnumerator"
  );
  results = Array.from(update.enumerator);
  Assert.equal(
    results.length,
    3,
    "the length of the array created from the update enumerator" +
      MSG_SHOULD_EQUAL
  );
  Assert.ok(
    results.every(prop => prop instanceof Ci.nsIProperty),
    "the objects in the array created from the update enumerator " +
      "should all be an instance of nsIProperty"
  );
  Assert.equal(
    results[0].name,
    "custom3_attr",
    "the first property name" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    results[0].value,
    "custom3 value",
    "the first property value" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    results[1].name,
    "custom4_attr",
    "the second property name" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    results[1].value,
    "custom4 value",
    "the second property value" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    results[2].name,
    "foregroundDownload",
    "the third property name" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    results[2].value,
    "false",
    "the third property value" + MSG_SHOULD_EQUAL
  );

  debugDump("checking the second update patch properties");
  patch = update.selectedPatch.QueryInterface(Ci.nsIWritablePropertyBag);
  Assert.equal(
    patch.type,
    "complete",
    "the update patch type attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    patch.URL,
    "http://complete/",
    "the update patch URL attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    patch.size,
    "75",
    "the update patch size attribute" + MSG_SHOULD_EQUAL
  );
  Assert.ok(
    !!patch.selected,
    "the update patch selected attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    patch.state,
    STATE_FAILED,
    "the update patch state attribute" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    patch.getProperty("custom3_attr"),
    "custom3 patch value",
    "the update patch custom3_attr property" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    patch.getProperty("custom4_attr"),
    "custom4 patch value",
    "the update patch custom4_attr property" + MSG_SHOULD_EQUAL
  );
  // nsIPropertyBag enumerator
  debugDump("checking the second update patch enumerator");
  Assert.ok(
    patch.enumerator instanceof Ci.nsISimpleEnumerator,
    "patch enumerator should be an instance of nsISimpleEnumerator"
  );
  results = Array.from(patch.enumerator);
  Assert.equal(
    results.length,
    2,
    "the length of the array created from the patch enumerator" +
      MSG_SHOULD_EQUAL
  );
  Assert.ok(
    results.every(prop => prop instanceof Ci.nsIProperty),
    "the objects in the array created from the patch enumerator " +
      "should all be an instance of nsIProperty"
  );
  Assert.equal(
    results[0].name,
    "custom3_attr",
    "the first property name" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    results[0].value,
    "custom3 patch value",
    "the first property value" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    results[1].name,
    "custom4_attr",
    "the second property name" + MSG_SHOULD_EQUAL
  );
  Assert.equal(
    results[1].value,
    "custom4 patch value",
    "the second property value" + MSG_SHOULD_EQUAL
  );

  let attrNames = [
    "appVersion",
    "platformVersion",
    "buildID",
    "channel",
    "detailsURL",
    "displayVersion",
    "elevationFailure",
    "errorCode",
    "installDate",
    "isCompleteUpdate",
    "name",
    "previousAppVersion",
    "promptWaitTime",
    "serviceURL",
    "state",
    "statusText",
    "type",
    "unsupported",
  ];
  checkIllegalProperties(update, attrNames);

  attrNames = [
    "errorCode",
    "finalURL",
    "selected",
    "size",
    "state",
    "type",
    "URL",
  ];
  checkIllegalProperties(patch, attrNames);
  writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates1), false);
  await reloadUpdateManagerData(false);
  const updateHistory = await gUpdateManager.getHistory();
  Assert.equal(
    updateHistory.length,
    1,
    "the length of the refreshed update history should be one"
  );
  Assert.equal(
    updateHistory[0].platformVersion,
    "4.0",
    "the platformVersion of the update should be 4.0"
  );

  executeSoon(doTestFinish);
}

function checkIllegalProperties(object, propertyNames) {
  let objectName =
    object instanceof Ci.nsIUpdate ? "nsIUpdate" : "nsIUpdatePatch";
  propertyNames.forEach(function (name) {
    // Check that calling getProperty, setProperty, and deleteProperty on an
    // nsIUpdate attribute throws NS_ERROR_ILLEGAL_VALUE
    let result = 0;
    try {
      object.getProperty(name);
    } catch (e) {
      result = e.result;
    }
    Assert.equal(
      result,
      Cr.NS_ERROR_ILLEGAL_VALUE,
      "calling getProperty using an " +
        objectName +
        " attribute " +
        "name should throw NS_ERROR_ILLEGAL_VALUE"
    );

    result = 0;
    try {
      object.setProperty(name, "value");
    } catch (e) {
      result = e.result;
    }
    Assert.equal(
      result,
      Cr.NS_ERROR_ILLEGAL_VALUE,
      "calling setProperty using an " +
        objectName +
        " attribute " +
        "name should throw NS_ERROR_ILLEGAL_VALUE"
    );

    result = 0;
    try {
      object.deleteProperty(name);
    } catch (e) {
      result = e.result;
    }
    Assert.equal(
      result,
      Cr.NS_ERROR_ILLEGAL_VALUE,
      "calling deleteProperty using an " +
        objectName +
        " attribute " +
        "name should throw NS_ERROR_ILLEGAL_VALUE"
    );
  });
}
