chromium/chrome/browser/ui/views/crostini/crostini_uninstaller_view_browsertest.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/views/crostini/crostini_uninstaller_view.h"

#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_base.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/metrics/histogram_tester.h"
#include "chrome/browser/ash/app_list/app_list_client_impl.h"
#include "chrome/browser/ash/app_list/test/chrome_app_list_test_support.h"
#include "chrome/browser/ash/crostini/crostini_manager.h"
#include "chrome/browser/ash/crostini/crostini_pref_names.h"
#include "chrome/browser/ash/crostini/crostini_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/views/crostini/crostini_dialogue_browser_test_util.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chromeos/ash/components/dbus/cicerone/cicerone_client.h"
#include "chromeos/ash/components/dbus/concierge/fake_concierge_client.h"
#include "components/crx_file/id_util.h"
#include "components/prefs/pref_service.h"
#include "content/public/test/browser_test.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/mojom/dialog_button.mojom.h"

class CrostiniUninstallerViewBrowserTest : public CrostiniDialogBrowserTest {
 public:
  class WaitingFakeConciergeClient : public ash::FakeConciergeClient {
   public:
    explicit WaitingFakeConciergeClient(ash::FakeCiceroneClient* client)
        : ash::FakeConciergeClient(client) {}

    void StopVm(
        const vm_tools::concierge::StopVmRequest& request,
        chromeos::DBusMethodCallback<vm_tools::concierge::StopVmResponse>
            callback) override {
      ash::FakeConciergeClient::StopVm(request, std::move(callback));
      if (closure_) {
        base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
            FROM_HERE, std::move(closure_));
      }
    }

    void WaitForStopVmCalled() {
      base::RunLoop loop;
      closure_ = loop.QuitClosure();
      loop.Run();
      EXPECT_GE(stop_vm_call_count(), 1);
    }

   private:
    base::OnceClosure closure_;
  };

  CrostiniUninstallerViewBrowserTest()
      : CrostiniUninstallerViewBrowserTest(true /*register_termina*/) {}

  explicit CrostiniUninstallerViewBrowserTest(bool register_termina)
      : CrostiniDialogBrowserTest(register_termina) {
    ash::ConciergeClient::Shutdown();
    // After the browser's mainloop is terminated. chromeos::ShutdownDBus() will
    // delete the object.
    waiting_fake_concierge_client_ = new WaitingFakeConciergeClient(nullptr);
  }

  CrostiniUninstallerViewBrowserTest(
      const CrostiniUninstallerViewBrowserTest&) = delete;
  CrostiniUninstallerViewBrowserTest& operator=(
      const CrostiniUninstallerViewBrowserTest&) = delete;

  // DialogBrowserTest:
  void ShowUi(const std::string& name) override {
    crostini::ShowCrostiniUninstallerView(browser()->profile());
  }

  CrostiniUninstallerView* ActiveView() {
    return CrostiniUninstallerView::GetActiveViewForTesting();
  }

  bool HasAcceptButton() { return ActiveView()->GetOkButton() != nullptr; }

  bool HasCancelButton() { return ActiveView()->GetCancelButton() != nullptr; }

  void WaitForViewDestroyed() {
    base::RunLoop run_loop;
    ActiveView()->set_destructor_callback_for_testing(run_loop.QuitClosure());
    run_loop.Run();
    EXPECT_EQ(nullptr, ActiveView());
  }

 protected:
  raw_ptr<WaitingFakeConciergeClient, DanglingUntriaged>
      waiting_fake_concierge_client_;
};

class CrostiniUninstalledUninstallerViewBrowserTest
    : public CrostiniUninstallerViewBrowserTest {
 public:
  CrostiniUninstalledUninstallerViewBrowserTest()
      : CrostiniUninstallerViewBrowserTest(false /*register_termina*/) {}

  CrostiniUninstalledUninstallerViewBrowserTest(
      const CrostiniUninstalledUninstallerViewBrowserTest&) = delete;
  CrostiniUninstalledUninstallerViewBrowserTest& operator=(
      const CrostiniUninstalledUninstallerViewBrowserTest&) = delete;
};

// Test the dialog is actually launched from the app launcher.
IN_PROC_BROWSER_TEST_F(CrostiniUninstallerViewBrowserTest, InvokeUi_default) {
  ShowAndVerifyUi();
}

IN_PROC_BROWSER_TEST_F(CrostiniUninstallerViewBrowserTest, UninstallFlow) {
  base::HistogramTester histogram_tester;

  ShowUi("default");
  EXPECT_NE(nullptr, ActiveView());
  EXPECT_EQ(static_cast<int>(ui::mojom::DialogButton::kOk) |
                static_cast<int>(ui::mojom::DialogButton::kCancel),
            ActiveView()->buttons());

  EXPECT_TRUE(HasAcceptButton());
  EXPECT_TRUE(HasCancelButton());

  ActiveView()->AcceptDialog();
  EXPECT_FALSE(ActiveView()->GetWidget()->IsClosed());
  EXPECT_FALSE(HasAcceptButton());
  EXPECT_FALSE(HasCancelButton());

  WaitForViewDestroyed();

  histogram_tester.ExpectUniqueSample(
      "Crostini.UninstallResult",
      static_cast<base::HistogramBase::Sample>(
          CrostiniUninstallerView::UninstallResult::kSuccess),
      1);
}

IN_PROC_BROWSER_TEST_F(CrostiniUninstalledUninstallerViewBrowserTest,
                       OfflineUninstallFlowWithoutTermina) {
  base::HistogramTester histogram_tester;

  SetConnectionType(network::mojom::ConnectionType::CONNECTION_NONE);

  ShowUi("default");
  EXPECT_NE(nullptr, ActiveView());
  EXPECT_FALSE(ActiveView()->GetWidget()->IsClosed());
  EXPECT_EQ(static_cast<int>(ui::mojom::DialogButton::kOk) |
                static_cast<int>(ui::mojom::DialogButton::kCancel),
            ActiveView()->buttons());

  EXPECT_TRUE(HasAcceptButton());
  EXPECT_TRUE(HasCancelButton());

  ActiveView()->AcceptDialog();

  WaitForViewDestroyed();

  histogram_tester.ExpectUniqueSample(
      "Crostini.UninstallResult",
      static_cast<base::HistogramBase::Sample>(
          CrostiniUninstallerView::UninstallResult::kSuccess),
      1);
}

IN_PROC_BROWSER_TEST_F(CrostiniUninstallerViewBrowserTest, Cancel) {
  base::HistogramTester histogram_tester;

  ShowUi("default");
  EXPECT_NE(nullptr, ActiveView());
  ActiveView()->CancelDialog();
  EXPECT_TRUE(ActiveView()->GetWidget()->IsClosed());
  WaitForViewDestroyed();

  histogram_tester.ExpectUniqueSample(
      "Crostini.UninstallResult",
      static_cast<base::HistogramBase::Sample>(
          CrostiniUninstallerView::UninstallResult::kCancelled),
      1);
}

IN_PROC_BROWSER_TEST_F(CrostiniUninstallerViewBrowserTest, ErrorThenCancel) {
  base::HistogramTester histogram_tester;
  ShowUi("default");
  EXPECT_NE(nullptr, ActiveView());
  vm_tools::concierge::StopVmResponse response;
  response.set_success(false);
  waiting_fake_concierge_client_->set_stop_vm_response(std::move(response));

  ActiveView()->AcceptDialog();
  EXPECT_FALSE(ActiveView()->GetWidget()->IsClosed());
  waiting_fake_concierge_client_->WaitForStopVmCalled();
  EXPECT_TRUE(HasCancelButton());
  ActiveView()->CancelDialog();
  WaitForViewDestroyed();

  histogram_tester.ExpectUniqueSample(
      "Crostini.UninstallResult",
      static_cast<base::HistogramBase::Sample>(
          CrostiniUninstallerView::UninstallResult::kError),
      1);
}