/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 https://mozilla.org/MPL/2.0/. */

#define MOZ_USE_LAUNCHER_ERROR

#include "mozilla/LauncherRegistryInfo.h"
#include "mozilla/NativeNt.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Unused.h"
#include "nsWindowsHelpers.h"

#include "LauncherRegistryInfo.cpp"

#include <string>

static const char kMsgStart[] = "TEST-FAILED | LauncherRegistryInfo | ";

static const wchar_t kRegKeyPath[] = L"SOFTWARE\\" EXPAND_STRING_MACRO(
    MOZ_APP_VENDOR) L"\\" EXPAND_STRING_MACRO(MOZ_APP_BASENAME) L"\\Launcher";
static const wchar_t kBrowserSuffix[] = L"|Browser";
static const wchar_t kLauncherSuffix[] = L"|Launcher";
static const wchar_t kImageSuffix[] = L"|Image";
static const wchar_t kTelemetrySuffix[] = L"|Telemetry";

static std::wstring gBrowserValue;
static std::wstring gLauncherValue;
static std::wstring gImageValue;
static std::wstring gTelemetryValue;

static DWORD gMyImageTimestamp;

#define RUN_TEST(result, fn)                                                 \
  if ((result = fn()).isErr()) {                                             \
    const mozilla::LauncherError& err = result.inspectErr();                 \
    printf("%s%s | %08lx (%s:%d)\n", kMsgStart, #fn, err.mError.AsHResult(), \
           err.mFile, err.mLine);                                            \
    return 1;                                                                \
  }

#define EXPECT_COMMIT_IS_OK()                        \
  do {                                               \
    mozilla::LauncherVoidResult vr2 = info.Commit(); \
    if (vr2.isErr()) {                               \
      return vr2;                                    \
    }                                                \
  } while (0)

#define EXPECT_CHECK_RESULT_IS(desired, expected)                       \
  do {                                                                  \
    mozilla::LauncherResult<mozilla::LauncherRegistryInfo::ProcessType> \
        result = info.Check(mozilla::LauncherRegistryInfo::desired);    \
    if (result.isErr()) {                                               \
      return result.propagateErr();                                     \
    }                                                                   \
    if (result.unwrap() != mozilla::LauncherRegistryInfo::expected) {   \
      return LAUNCHER_ERROR_FROM_HRESULT(E_FAIL);                       \
    }                                                                   \
  } while (0)

#define EXPECT_ENABLED_STATE_IS(expected)                                \
  do {                                                                   \
    mozilla::LauncherResult<mozilla::LauncherRegistryInfo::EnabledState> \
        enabled = info.IsEnabled();                                      \
    if (enabled.isErr()) {                                               \
      return enabled.propagateErr();                                     \
    }                                                                    \
    if (enabled.unwrap() != mozilla::LauncherRegistryInfo::expected) {   \
      return LAUNCHER_ERROR_FROM_HRESULT(E_UNEXPECTED);                  \
    }                                                                    \
  } while (0)

#define EXPECT_TELEMETRY_IS_ENABLED(expected)                          \
  do {                                                                 \
    mozilla::LauncherResult<bool> enabled = info.IsTelemetryEnabled(); \
    if (enabled.isErr()) {                                             \
      return enabled.propagateErr();                                   \
    }                                                                  \
    if (enabled.unwrap() != expected) {                                \
      return LAUNCHER_ERROR_FROM_HRESULT(E_UNEXPECTED);                \
    }                                                                  \
  } while (0)

#define EXPECT_REG_DWORD_EXISTS_AND_EQ(name, expected)      \
  do {                                                      \
    mozilla::LauncherResult<mozilla::Maybe<DWORD>> result = \
        ReadRegistryValueData<DWORD>(name, REG_DWORD);      \
    if (result.isErr()) {                                   \
      return result.propagateErr();                         \
    }                                                       \
    if (result.inspect().isNothing() ||                     \
        result.inspect().value() != expected) {             \
      return LAUNCHER_ERROR_FROM_HRESULT(E_UNEXPECTED);     \
    }                                                       \
  } while (0)

#define EXPECT_REG_QWORD_EXISTS(name)                          \
  do {                                                         \
    mozilla::LauncherResult<mozilla::Maybe<uint64_t>> result = \
        ReadRegistryValueData<uint64_t>(name, REG_QWORD);      \
    if (result.isErr()) {                                      \
      return result.propagateErr();                            \
    }                                                          \
    if (result.inspect().isNothing()) {                        \
      return LAUNCHER_ERROR_FROM_HRESULT(E_UNEXPECTED);        \
    }                                                          \
  } while (0)

#define EXPECT_REG_QWORD_EXISTS_AND_EQ(name, expected)         \
  do {                                                         \
    mozilla::LauncherResult<mozilla::Maybe<uint64_t>> result = \
        ReadRegistryValueData<uint64_t>(name, REG_QWORD);      \
    if (result.isErr()) {                                      \
      return result.propagateErr();                            \
    }                                                          \
    if (result.inspect().isNothing() ||                        \
        result.inspect().value() != expected) {                \
      return LAUNCHER_ERROR_FROM_HRESULT(E_UNEXPECTED);        \
    }                                                          \
  } while (0)

#define EXPECT_REG_DWORD_DOES_NOT_EXIST(name)               \
  do {                                                      \
    mozilla::LauncherResult<mozilla::Maybe<DWORD>> result = \
        ReadRegistryValueData<DWORD>(name, REG_DWORD);      \
    if (result.isErr()) {                                   \
      return result.propagateErr();                         \
    }                                                       \
    if (result.inspect().isSome()) {                        \
      return LAUNCHER_ERROR_FROM_HRESULT(E_UNEXPECTED);     \
    }                                                       \
  } while (0)

#define EXPECT_REG_QWORD_DOES_NOT_EXIST(name)                  \
  do {                                                         \
    mozilla::LauncherResult<mozilla::Maybe<uint64_t>> result = \
        ReadRegistryValueData<uint64_t>(name, REG_QWORD);      \
    if (result.isErr()) {                                      \
      return result.propagateErr();                            \
    }                                                          \
    if (result.inspect().isSome()) {                           \
      return LAUNCHER_ERROR_FROM_HRESULT(E_UNEXPECTED);        \
    }                                                          \
  } while (0)

template <typename T>
static mozilla::LauncherResult<mozilla::Maybe<T>> ReadRegistryValueData(
    const std::wstring& name, DWORD expectedType) {
  T data;
  DWORD dataLen = sizeof(data);
  DWORD type;
  LSTATUS status = ::RegGetValueW(HKEY_CURRENT_USER, kRegKeyPath, name.c_str(),
                                  RRF_RT_ANY, &type, &data, &dataLen);
  if (status == ERROR_FILE_NOT_FOUND) {
    return mozilla::Maybe<T>();
  }

  if (status != ERROR_SUCCESS) {
    return LAUNCHER_ERROR_FROM_WIN32(status);
  }

  if (type != expectedType) {
    return LAUNCHER_ERROR_FROM_WIN32(ERROR_DATATYPE_MISMATCH);
  }

  return mozilla::Some(data);
}

template <typename T>
static mozilla::LauncherVoidResult WriteRegistryValueData(
    const std::wstring& name, DWORD type, T data) {
  LSTATUS status = ::RegSetKeyValueW(HKEY_CURRENT_USER, kRegKeyPath,
                                     name.c_str(), type, &data, sizeof(T));
  if (status != ERROR_SUCCESS) {
    return LAUNCHER_ERROR_FROM_WIN32(status);
  }

  return mozilla::Ok();
}

static mozilla::LauncherVoidResult DeleteRegistryValueData(
    const std::wstring& name) {
  LSTATUS status =
      ::RegDeleteKeyValueW(HKEY_CURRENT_USER, kRegKeyPath, name.c_str());
  if (status == ERROR_SUCCESS || status == ERROR_FILE_NOT_FOUND) {
    return mozilla::Ok();
  }

  return LAUNCHER_ERROR_FROM_WIN32(status);
}

static mozilla::LauncherVoidResult DeleteAllRegstryValues() {
  // Unblock commit via ReflectPrefToRegistry
  // (We need to set false, and then true to bypass the early return)
  mozilla::LauncherRegistryInfo info;
  mozilla::LauncherVoidResult vr = info.ReflectPrefToRegistry(false);
  vr = info.ReflectPrefToRegistry(true);
  if (vr.isErr()) {
    return vr;
  }

  vr = DeleteRegistryValueData(gImageValue);
  if (vr.isErr()) {
    return vr;
  }

  vr = DeleteRegistryValueData(gLauncherValue);
  if (vr.isErr()) {
    return vr;
  }

  vr = DeleteRegistryValueData(gBrowserValue);
  if (vr.isErr()) {
    return vr;
  }

  return DeleteRegistryValueData(gTelemetryValue);
}

bool GetInstallHash(const char16_t*, mozilla::UniquePtr<NS_tchar[]>& result) {
  return true;
}

static mozilla::LauncherVoidResult SetupEnabledScenario() {
  // Reset the registry state to an enabled state. First, we delete all existing
  // registry values (if any).
  mozilla::LauncherVoidResult vr = DeleteAllRegstryValues();
  if (vr.isErr()) {
    return vr;
  }

  // Now we run Check(Launcher)...
  mozilla::LauncherRegistryInfo info;
  EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Launcher);
  EXPECT_COMMIT_IS_OK();
  // ...and Check(Browser)
  EXPECT_CHECK_RESULT_IS(ProcessType::Browser, ProcessType::Browser);
  EXPECT_COMMIT_IS_OK();

  // By this point we are considered to be fully enabled.
  return mozilla::Ok();
}

static mozilla::LauncherVoidResult TestEmptyRegistry() {
  mozilla::LauncherVoidResult vr = DeleteAllRegstryValues();
  if (vr.isErr()) {
    return vr;
  }

  mozilla::LauncherRegistryInfo info;
  EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Launcher);
  EXPECT_COMMIT_IS_OK();

  // LauncherRegistryInfo should have created Launcher and Image values
  EXPECT_REG_DWORD_EXISTS_AND_EQ(gImageValue, gMyImageTimestamp);
  EXPECT_REG_QWORD_EXISTS(gLauncherValue);
  EXPECT_REG_QWORD_DOES_NOT_EXIST(gBrowserValue);

  EXPECT_ENABLED_STATE_IS(EnabledState::FailDisabled);

  return mozilla::Ok();
}

static mozilla::LauncherVoidResult TestNormal() {
  mozilla::LauncherVoidResult vr = DeleteAllRegstryValues();
  if (vr.isErr()) {
    return vr;
  }
  vr = WriteRegistryValueData<DWORD>(gImageValue, REG_DWORD, gMyImageTimestamp);
  if (vr.isErr()) {
    return vr;
  }
  vr = WriteRegistryValueData<uint64_t>(gLauncherValue, REG_QWORD, QPCNowRaw());
  if (vr.isErr()) {
    return vr;
  }

  mozilla::LauncherRegistryInfo info;
  EXPECT_CHECK_RESULT_IS(ProcessType::Browser, ProcessType::Browser);
  EXPECT_COMMIT_IS_OK();

  // Make sure the browser timestamp is newer than the launcher's
  mozilla::LauncherResult<mozilla::Maybe<uint64_t>> launcherTs =
      ReadRegistryValueData<uint64_t>(gLauncherValue, REG_QWORD);
  if (launcherTs.isErr()) {
    return launcherTs.propagateErr();
  }
  mozilla::LauncherResult<mozilla::Maybe<uint64_t>> browserTs =
      ReadRegistryValueData<uint64_t>(gBrowserValue, REG_QWORD);
  if (browserTs.isErr()) {
    return browserTs.propagateErr();
  }
  if (launcherTs.inspect().isNothing() || browserTs.inspect().isNothing() ||
      browserTs.inspect().value() <= launcherTs.inspect().value()) {
    return LAUNCHER_ERROR_FROM_HRESULT(E_FAIL);
  }

  EXPECT_ENABLED_STATE_IS(EnabledState::Enabled);

  return mozilla::Ok();
}

static mozilla::LauncherVoidResult TestBrowserNoLauncher() {
  mozilla::LauncherVoidResult vr = SetupEnabledScenario();
  if (vr.isErr()) {
    return vr;
  }
  vr = DeleteRegistryValueData(gLauncherValue);
  if (vr.isErr()) {
    return vr;
  }

  mozilla::LauncherRegistryInfo info;
  EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Browser);
  EXPECT_COMMIT_IS_OK();

  // Verify that we still don't have a launcher timestamp
  EXPECT_REG_QWORD_DOES_NOT_EXIST(gLauncherValue);
  // Verify that the browser timestamp is now zero
  EXPECT_REG_QWORD_EXISTS_AND_EQ(gBrowserValue, 0ULL);

  EXPECT_ENABLED_STATE_IS(EnabledState::ForceDisabled);

  return mozilla::Ok();
}

static mozilla::LauncherVoidResult TestLauncherNoBrowser() {
  constexpr uint64_t launcherTs = 0x77777777;
  mozilla::LauncherVoidResult vr = DeleteAllRegstryValues();
  if (vr.isErr()) {
    return vr;
  }
  vr = WriteRegistryValueData<DWORD>(gImageValue, REG_DWORD, gMyImageTimestamp);
  if (vr.isErr()) {
    return vr;
  }
  vr = WriteRegistryValueData<uint64_t>(gLauncherValue, REG_QWORD, launcherTs);
  if (vr.isErr()) {
    return vr;
  }

  mozilla::LauncherRegistryInfo info;
  EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Browser);
  EXPECT_COMMIT_IS_OK();

  // Launcher's timestamps is kept intact while browser's is set to 0.
  EXPECT_REG_QWORD_EXISTS_AND_EQ(gLauncherValue, launcherTs);
  EXPECT_REG_QWORD_EXISTS_AND_EQ(gBrowserValue, 0ULL);

  EXPECT_ENABLED_STATE_IS(EnabledState::FailDisabled);

  return mozilla::Ok();
}

static mozilla::LauncherVoidResult TestBrowserLessThanLauncher() {
  constexpr uint64_t launcherTs = 0x77777777, browserTs = 0x66666666;
  mozilla::LauncherVoidResult vr = DeleteAllRegstryValues();
  if (vr.isErr()) {
    return vr;
  }
  vr = WriteRegistryValueData<DWORD>(gImageValue, REG_DWORD, gMyImageTimestamp);
  if (vr.isErr()) {
    return vr;
  }
  vr = WriteRegistryValueData<uint64_t>(gLauncherValue, REG_QWORD, launcherTs);
  if (vr.isErr()) {
    return vr;
  }
  vr = WriteRegistryValueData<uint64_t>(gBrowserValue, REG_QWORD, browserTs);
  if (vr.isErr()) {
    return vr;
  }

  mozilla::LauncherRegistryInfo info;
  EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Browser);
  EXPECT_COMMIT_IS_OK();

  // Launcher's timestamps is kept intact while browser's is set to 0.
  EXPECT_REG_QWORD_EXISTS_AND_EQ(gLauncherValue, launcherTs);
  EXPECT_REG_QWORD_EXISTS_AND_EQ(gBrowserValue, 0ULL);

  EXPECT_ENABLED_STATE_IS(EnabledState::FailDisabled);

  return mozilla::Ok();
}

static mozilla::LauncherVoidResult TestImageTimestampChange() {
  // This should reset the timestamps and then essentially run like
  // TestEmptyRegistry
  mozilla::LauncherVoidResult vr = DeleteAllRegstryValues();
  if (vr.isErr()) {
    return vr;
  }
  vr = WriteRegistryValueData<DWORD>(gImageValue, REG_DWORD, 0x12345678);
  if (vr.isErr()) {
    return vr;
  }
  vr = WriteRegistryValueData<uint64_t>(gLauncherValue, REG_QWORD, 1ULL);
  if (vr.isErr()) {
    return vr;
  }
  vr = WriteRegistryValueData<uint64_t>(gBrowserValue, REG_QWORD, 2ULL);
  if (vr.isErr()) {
    return vr;
  }

  mozilla::LauncherRegistryInfo info;
  EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Launcher);
  EXPECT_COMMIT_IS_OK();

  EXPECT_REG_DWORD_EXISTS_AND_EQ(gImageValue, gMyImageTimestamp);
  EXPECT_REG_QWORD_EXISTS(gLauncherValue);
  EXPECT_REG_QWORD_DOES_NOT_EXIST(gBrowserValue);

  return mozilla::Ok();
}

static mozilla::LauncherVoidResult TestImageTimestampChangeWhenDisabled() {
  mozilla::LauncherVoidResult vr = DeleteAllRegstryValues();
  if (vr.isErr()) {
    return vr;
  }
  vr = WriteRegistryValueData<DWORD>(gImageValue, REG_DWORD, 0x12345678);
  if (vr.isErr()) {
    return vr;
  }
  vr = WriteRegistryValueData<uint64_t>(gBrowserValue, REG_QWORD, 0ULL);
  if (vr.isErr()) {
    return vr;
  }

  mozilla::LauncherRegistryInfo info;
  EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Browser);
  EXPECT_COMMIT_IS_OK();

  EXPECT_REG_DWORD_EXISTS_AND_EQ(gImageValue, gMyImageTimestamp);
  EXPECT_REG_QWORD_DOES_NOT_EXIST(gLauncherValue);
  EXPECT_REG_QWORD_EXISTS_AND_EQ(gBrowserValue, 0);

  EXPECT_ENABLED_STATE_IS(EnabledState::ForceDisabled);

  return mozilla::Ok();
}

static mozilla::LauncherVoidResult TestDisableDueToFailure() {
  mozilla::LauncherVoidResult vr = SetupEnabledScenario();
  if (vr.isErr()) {
    return vr;
  }

  // Check that we are indeed enabled.
  mozilla::LauncherRegistryInfo info;
  EXPECT_ENABLED_STATE_IS(EnabledState::Enabled);

  // Now call DisableDueToFailure
  mozilla::LauncherVoidResult lvr = info.DisableDueToFailure();
  if (lvr.isErr()) {
    return lvr.propagateErr();
  }

  // We should now be FailDisabled
  EXPECT_ENABLED_STATE_IS(EnabledState::FailDisabled);

  // If we delete the launcher timestamp, IsEnabled should then return
  // ForceDisabled.
  vr = DeleteRegistryValueData(gLauncherValue);
  if (vr.isErr()) {
    return vr;
  }
  EXPECT_ENABLED_STATE_IS(EnabledState::ForceDisabled);

  return mozilla::Ok();
}

static mozilla::LauncherVoidResult TestPrefReflection() {
  // Reset the registry to a known good state.
  mozilla::LauncherVoidResult vr = SetupEnabledScenario();
  if (vr.isErr()) {
    return vr;
  }

  // Let's see what happens when we flip the pref to OFF.
  mozilla::LauncherRegistryInfo info;
  mozilla::LauncherVoidResult reflectOk = info.ReflectPrefToRegistry(false);
  if (reflectOk.isErr()) {
    return reflectOk.propagateErr();
  }

  // Launcher timestamp should be non-existent.
  EXPECT_REG_QWORD_DOES_NOT_EXIST(gLauncherValue);
  // Browser timestamp should be zero
  EXPECT_REG_QWORD_EXISTS_AND_EQ(gBrowserValue, 0ULL);
  // IsEnabled should give us ForceDisabled
  EXPECT_ENABLED_STATE_IS(EnabledState::ForceDisabled);

  // Now test to see what happens when the pref is set to ON.
  reflectOk = info.ReflectPrefToRegistry(true);
  if (reflectOk.isErr()) {
    return reflectOk.propagateErr();
  }

  // Launcher and browser timestamps should be non-existent.
  EXPECT_REG_QWORD_DOES_NOT_EXIST(gLauncherValue);
  EXPECT_REG_QWORD_DOES_NOT_EXIST(gBrowserValue);

  // IsEnabled should give us Enabled.
  EXPECT_ENABLED_STATE_IS(EnabledState::Enabled);

  return mozilla::Ok();
}

static mozilla::LauncherVoidResult TestTelemetryConfig() {
  mozilla::LauncherVoidResult vr = DeleteAllRegstryValues();
  if (vr.isErr()) {
    return vr;
  }

  mozilla::LauncherRegistryInfo info;
  EXPECT_TELEMETRY_IS_ENABLED(false);

  mozilla::LauncherVoidResult reflectOk =
      info.ReflectTelemetryPrefToRegistry(false);
  if (reflectOk.isErr()) {
    return reflectOk.propagateErr();
  }
  EXPECT_TELEMETRY_IS_ENABLED(false);

  reflectOk = info.ReflectTelemetryPrefToRegistry(true);
  if (reflectOk.isErr()) {
    return reflectOk.propagateErr();
  }
  EXPECT_TELEMETRY_IS_ENABLED(true);

  return mozilla::Ok();
}

static mozilla::LauncherVoidResult TestCommitAbort() {
  mozilla::LauncherVoidResult vr = SetupEnabledScenario();
  if (vr.isErr()) {
    return vr;
  }

  // Retrieve the current timestamps to compare later
  mozilla::LauncherResult<mozilla::Maybe<uint64_t>> launcherValue =
      ReadRegistryValueData<uint64_t>(gLauncherValue, REG_QWORD);
  if (launcherValue.isErr() || launcherValue.inspect().isNothing()) {
    return launcherValue.propagateErr();
  }
  mozilla::LauncherResult<mozilla::Maybe<uint64_t>> browserValue =
      ReadRegistryValueData<uint64_t>(gBrowserValue, REG_QWORD);
  if (browserValue.isErr() || browserValue.inspect().isNothing()) {
    return browserValue.propagateErr();
  }
  uint64_t launcherTs = launcherValue.inspect().value();
  uint64_t browserTs = browserValue.inspect().value();

  vr = []() -> mozilla::LauncherVoidResult {
    mozilla::LauncherRegistryInfo info;
    EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Launcher);
    // No commit
    return mozilla::Ok();
  }();
  if (vr.isErr()) {
    return vr;
  }

  // Exiting the scope discards the change.
  mozilla::LauncherRegistryInfo info;
  EXPECT_REG_DWORD_EXISTS_AND_EQ(gImageValue, gMyImageTimestamp);
  EXPECT_REG_QWORD_EXISTS_AND_EQ(gLauncherValue, launcherTs);
  EXPECT_REG_QWORD_EXISTS_AND_EQ(gBrowserValue, browserTs);

  // Commit -> Check -> Abort -> Commit
  EXPECT_COMMIT_IS_OK();
  EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Launcher);
  info.Abort();
  EXPECT_COMMIT_IS_OK();

  // Nothing is changed.
  EXPECT_REG_DWORD_EXISTS_AND_EQ(gImageValue, gMyImageTimestamp);
  EXPECT_REG_QWORD_EXISTS_AND_EQ(gLauncherValue, launcherTs);
  EXPECT_REG_QWORD_EXISTS_AND_EQ(gBrowserValue, browserTs);
  EXPECT_ENABLED_STATE_IS(EnabledState::Enabled);

  return mozilla::Ok();
}

static mozilla::LauncherVoidResult TestDisableDuringLauncherLaunch() {
  mozilla::LauncherVoidResult vr = SetupEnabledScenario();
  if (vr.isErr()) {
    return vr;
  }

  mozilla::LauncherResult<mozilla::Maybe<uint64_t>> launcherTs =
      ReadRegistryValueData<uint64_t>(gLauncherValue, REG_QWORD);
  if (launcherTs.isErr()) {
    return launcherTs.propagateErr();
  }
  if (launcherTs.inspect().isNothing()) {
    return LAUNCHER_ERROR_FROM_HRESULT(E_UNEXPECTED);
  }

  vr = []() -> mozilla::LauncherVoidResult {
    mozilla::LauncherRegistryInfo info;
    EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Launcher);

    // Call DisableDueToFailure with a different instance
    mozilla::LauncherVoidResult vr = []() -> mozilla::LauncherVoidResult {
      mozilla::LauncherRegistryInfo info;
      mozilla::LauncherVoidResult vr = info.DisableDueToFailure();
      if (vr.isErr()) {
        return vr.propagateErr();
      }
      return mozilla::Ok();
    }();
    if (vr.isErr()) {
      return vr;
    }

    // Commit after disable.
    EXPECT_COMMIT_IS_OK();

    return mozilla::Ok();
  }();
  if (vr.isErr()) {
    return vr;
  }

  // Make sure we're still FailDisabled and the launcher's timestamp is not
  // updated
  mozilla::LauncherRegistryInfo info;
  EXPECT_ENABLED_STATE_IS(EnabledState::FailDisabled);
  EXPECT_REG_QWORD_EXISTS_AND_EQ(gLauncherValue, launcherTs.inspect().value());

  return mozilla::Ok();
}

static mozilla::LauncherVoidResult TestDisableDuringBrowserLaunch() {
  mozilla::LauncherVoidResult vr = SetupEnabledScenario();
  if (vr.isErr()) {
    return vr;
  }

  mozilla::LauncherRegistryInfo info;
  EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Launcher);
  EXPECT_COMMIT_IS_OK();

  vr = []() -> mozilla::LauncherVoidResult {
    mozilla::LauncherRegistryInfo info;
    EXPECT_CHECK_RESULT_IS(ProcessType::Browser, ProcessType::Browser);

    // Call DisableDueToFailure with a different instance
    mozilla::LauncherVoidResult vr = []() -> mozilla::LauncherVoidResult {
      mozilla::LauncherRegistryInfo info;
      mozilla::LauncherVoidResult vr = info.DisableDueToFailure();
      if (vr.isErr()) {
        return vr.propagateErr();
      }
      return mozilla::Ok();
    }();
    if (vr.isErr()) {
      return vr;
    }

    // Commit after disable.
    EXPECT_COMMIT_IS_OK();

    return mozilla::Ok();
  }();
  if (vr.isErr()) {
    return vr;
  }

  // Make sure we're still FailDisabled
  EXPECT_ENABLED_STATE_IS(EnabledState::FailDisabled);

  return mozilla::Ok();
}

static mozilla::LauncherVoidResult TestReEnable() {
  mozilla::LauncherVoidResult vr = SetupEnabledScenario();
  if (vr.isErr()) {
    return vr;
  }

  // Make FailDisabled
  mozilla::LauncherRegistryInfo info;
  vr = info.DisableDueToFailure();
  if (vr.isErr()) {
    return vr.propagateErr();
  }
  EXPECT_ENABLED_STATE_IS(EnabledState::FailDisabled);

  // Attempt to launch when FailDisabled: Still be FailDisabled
  EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Browser);
  EXPECT_COMMIT_IS_OK();
  EXPECT_ENABLED_STATE_IS(EnabledState::FailDisabled);

  // Change the timestamp
  vr = WriteRegistryValueData<DWORD>(gImageValue, REG_DWORD, 0x12345678);
  if (vr.isErr()) {
    return vr;
  }

  // Attempt to launch again: Launcher comes back
  EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Launcher);
  EXPECT_COMMIT_IS_OK();

  // Make ForceDisabled
  vr = info.ReflectPrefToRegistry(false);
  if (vr.isErr()) {
    return vr.propagateErr();
  }
  EXPECT_ENABLED_STATE_IS(EnabledState::ForceDisabled);

  // Attempt to launch when ForceDisabled: Still be ForceDisabled
  EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Browser);
  EXPECT_COMMIT_IS_OK();
  EXPECT_ENABLED_STATE_IS(EnabledState::ForceDisabled);

  // Change the timestamp
  vr = WriteRegistryValueData<DWORD>(gImageValue, REG_DWORD, 0x12345678);
  if (vr.isErr()) {
    return vr;
  }

  // Attempt to launch again: Still be ForceDisabled
  EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Browser);
  EXPECT_COMMIT_IS_OK();
  EXPECT_ENABLED_STATE_IS(EnabledState::ForceDisabled);

  return mozilla::Ok();
}

int main(int argc, char* argv[]) {
  auto fullPath = mozilla::GetFullBinaryPath();
  if (!fullPath) {
    return 1;
  }

  // Global setup for all tests
  gBrowserValue = fullPath.get();
  gBrowserValue += kBrowserSuffix;

  gLauncherValue = fullPath.get();
  gLauncherValue += kLauncherSuffix;

  gImageValue = fullPath.get();
  gImageValue += kImageSuffix;

  gTelemetryValue = fullPath.get();
  gTelemetryValue += kTelemetrySuffix;

  mozilla::LauncherResult<DWORD> timestamp = 0;
  RUN_TEST(timestamp, GetCurrentImageTimestamp);
  gMyImageTimestamp = timestamp.unwrap();

  auto onExit = mozilla::MakeScopeExit(
      []() { mozilla::Unused << DeleteAllRegstryValues(); });

  mozilla::LauncherVoidResult vr = mozilla::Ok();

  // All testcases should call SetupEnabledScenario() or
  // DeleteAllRegstryValues() to be order-independent
  RUN_TEST(vr, TestEmptyRegistry);
  RUN_TEST(vr, TestNormal);
  RUN_TEST(vr, TestBrowserNoLauncher);
  RUN_TEST(vr, TestLauncherNoBrowser);
  RUN_TEST(vr, TestBrowserLessThanLauncher);
  RUN_TEST(vr, TestImageTimestampChange);
  RUN_TEST(vr, TestImageTimestampChangeWhenDisabled);
  RUN_TEST(vr, TestDisableDueToFailure);
  RUN_TEST(vr, TestPrefReflection);
  RUN_TEST(vr, TestTelemetryConfig);
  RUN_TEST(vr, TestCommitAbort);
  RUN_TEST(vr, TestDisableDuringLauncherLaunch);
  RUN_TEST(vr, TestDisableDuringBrowserLaunch);
  RUN_TEST(vr, TestReEnable);

  return 0;
}