// Copyright 2024 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/ash/dbus/arc_crosh_service_provider.h"

#include <memory>
#include <string>
#include <utility>

#include "ash/test/ash_test_base.h"
#include "base/functional/bind.h"
#include "base/test/scoped_command_line.h"
#include "chrome/browser/ash/arc/session/arc_session_manager.h"
#include "chrome/browser/ash/arc/test/test_arc_session_manager.h"
#include "chrome/browser/ash/login/users/scoped_account_id_annotator.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/ash/components/dbus/concierge/concierge_client.h"
#include "chromeos/ash/components/dbus/dlcservice/dlcservice_client.h"
#include "chromeos/ash/components/dbus/services/service_provider_test_helper.h"
#include "chromeos/ash/components/dbus/upstart/upstart_client.h"
#include "chromeos/ash/experiences/arc/dlc_installer/arc_dlc_installer.h"
#include "chromeos/ash/experiences/arc/mojom/crosh.mojom.h"
#include "chromeos/ash/experiences/arc/session/arc_bridge_service.h"
#include "chromeos/ash/experiences/arc/session/arc_service_manager.h"
#include "chromeos/ash/experiences/arc/test/connection_holder_util.h"
#include "chromeos/ash/experiences/arc/test/fake_arc_session.h"
#include "components/account_id/account_id.h"
#include "components/session_manager/test/test_user_session_manager.h"
#include "components/user_manager/test_helper.h"
#include "components/user_manager/user.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/browser_task_environment.h"
#include "dbus/message.h"
#include "google_apis/gaia/gaia_id.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

namespace ash {
namespace {

// Arrows controlling the result of Exec() from Mojo.
class MockArcShellExecutionInstance
    : public arc::mojom::ArcShellExecutionInstance {
 public:
  void Exec(arc::mojom::ArcShellExecutionRequestPtr request,
            ExecCallback callback) override {
    EXPECT_TRUE(!request.is_null());
    recieved_exec_request_ = std::move(request);

    EXPECT_TRUE(!callback.is_null());
    EXPECT_TRUE(!exec_result_.is_null());

    std::move(callback).Run(std::move(exec_result_));
  }

  arc::mojom::ArcShellExecutionRequestPtr RecievedExecRequest() {
    return std::move(recieved_exec_request_);
  }

  void SetNextExecResult(arc::mojom::ArcShellExecutionResultPtr result) {
    exec_result_ = std::move(result);
  }

 private:
  arc::mojom::ArcShellExecutionResultPtr exec_result_;
  arc::mojom::ArcShellExecutionRequestPtr recieved_exec_request_;
};

constexpr char kPrimaryUserProfileName[] = "primary@gmail.com";
constexpr char kSecondaryUserProfileName[] = "secondary@gmail.com";

class ArcCroshServiceProviderTest : public testing::Test {
 public:
  ArcCroshServiceProviderTest() = default;
  ArcCroshServiceProviderTest(const ArcCroshServiceProviderTest&) = delete;
  ArcCroshServiceProviderTest& operator=(const ArcCroshServiceProviderTest&) =
      delete;
  ~ArcCroshServiceProviderTest() override = default;

  void SetUp() override {
    ash::UpstartClient::InitializeFake();
    ash::ConciergeClient::InitializeFake(/*fake_cicerone_client=*/nullptr);
    ash::DlcserviceClient::InitializeFake();

    base::CommandLine* command_line =
        scoped_command_line.GetProcessCommandLine();
    command_line->InitFromArgv(
        {"", "--arc-availability=officially-supported", "--enable-arcvm"});

    test_user_session_manager_ =
        std::make_unique<ash::test::TestUserSessionManager>(
            TestingBrowserProcess::GetGlobal()->local_state());
    profile_manager_ = std::make_unique<TestingProfileManager>(
        TestingBrowserProcess::GetGlobal());
    ASSERT_TRUE(profile_manager_->SetUp());
    arc_service_manager_ = std::make_unique<arc::ArcServiceManager>();
    arc_dlc_installer_ = std::make_unique<arc::ArcDlcInstaller>();
    // Make the session manager skip creating UI.
    arc::ArcSessionManager::SetUiEnabledForTesting(/*enabled=*/false);
    arc_session_manager_ = arc::CreateTestArcSessionManager(
        std::make_unique<arc::ArcSessionRunner>(
            base::BindRepeating(arc::FakeArcSession::Create)),
        arc_dlc_installer_.get());
    service_provider_ = std::make_unique<ArcCroshServiceProvider>();

    // Add users before login.
    const AccountId primary_user_account_id = AccountId::FromUserEmailGaiaId(
        kPrimaryUserProfileName, GaiaId("1111111111"));
    user_manager::User* primary_user =
        test_user_session_manager_->AddRegularUser(primary_user_account_id);
    ASSERT_TRUE(primary_user);
    primary_username_hash_ =
        user_manager::TestHelper::GetFakeUsernameHash(primary_user_account_id);

    const AccountId secondary_user_account_id = AccountId::FromUserEmailGaiaId(
        kSecondaryUserProfileName, GaiaId("2222222222"));
    user_manager::User* secondary_user =
        test_user_session_manager_->AddRegularUser(secondary_user_account_id);
    ASSERT_TRUE(secondary_user);
    secondary_username_hash_ = user_manager::TestHelper::GetFakeUsernameHash(
        secondary_user_account_id);

    test_user_session_manager_->LogIn(primary_user_account_id);

    Profile* primary_user_profile;
    {
      ash::ScopedAccountIdAnnotator annotator1(
          profile_manager_->profile_manager(), primary_user_account_id);
      primary_user_profile =
          profile_manager_->CreateTestingProfile(kPrimaryUserProfileName);
    }

    {
      ash::ScopedAccountIdAnnotator annotator2(
          profile_manager_->profile_manager(), secondary_user_account_id);
      profile_manager_->CreateTestingProfile(kSecondaryUserProfileName);
    }

    arc_session_manager_->SetProfile(primary_user_profile);

    arc_session_manager_->Initialize();
    arc_session_manager_->RequestEnable();
    arc_session_manager_->StartArcForTesting();

    arc_bridge_service()->arc_shell_execution()->SetInstance(
        &mock_arc_shell_execution_instance_);
    arc::WaitForInstanceReady(arc_bridge_service()->arc_shell_execution());

    test_helper_.SetUp(arc::crosh::kArcCroshServiceName,
                       dbus::ObjectPath(arc::crosh::kArcCroshServicePath),
                       arc::crosh::kArcCroshInterfaceName,
                       arc::crosh::kArcCroshRequest, service_provider_.get());
  }

  void TearDown() override {
    test_helper_.TearDown();

    arc_bridge_service()->arc_shell_execution()->CloseInstance(
        &mock_arc_shell_execution_instance_);
    arc_session_manager_->Shutdown();
    service_provider_.reset();
    arc_session_manager_.reset();
    arc_dlc_installer_.reset();
    arc_service_manager_.reset();
    profile_manager_.reset();
    test_user_session_manager_.reset();
    ash::DlcserviceClient::Shutdown();
    ash::ConciergeClient::Shutdown();
    ash::UpstartClient::Shutdown();
  }

 protected:
  std::unique_ptr<dbus::Response> CallExec(
      arc::ArcShellExecutionRequest message) {
    dbus::MethodCall method_call{arc::crosh::kArcCroshServiceName,
                                 arc::crosh::kArcCroshRequest};
    dbus::MessageWriter writer(&method_call);
    writer.AppendProtoAsArrayOfBytes(message);

    std::unique_ptr<dbus::Response> response =
        test_helper_.CallMethod(&method_call);
    return response;
  }

  content::BrowserTaskEnvironment& task_environment() {
    return task_environment_;
  }

  MockArcShellExecutionInstance& mock_arc_shell_execution_instance() {
    return mock_arc_shell_execution_instance_;
  }

  arc::ArcBridgeService* arc_bridge_service() {
    return arc_service_manager_->arc_bridge_service();
  }

  const std::string& primary_username_hash() const {
    return primary_username_hash_;
  }
  const std::string& secondary_username_hash() const {
    return secondary_username_hash_;
  }

 private:
  content::BrowserTaskEnvironment task_environment_;
  ServiceProviderTestHelper test_helper_;
  MockArcShellExecutionInstance mock_arc_shell_execution_instance_;
  std::unique_ptr<ash::test::TestUserSessionManager> test_user_session_manager_;
  std::unique_ptr<TestingProfileManager> profile_manager_;
  std::unique_ptr<arc::ArcServiceManager> arc_service_manager_;
  std::unique_ptr<arc::ArcDlcInstaller> arc_dlc_installer_;
  std::unique_ptr<arc::ArcSessionManager> arc_session_manager_;
  std::unique_ptr<ArcCroshServiceProvider> service_provider_;
  std::string primary_username_hash_;
  std::string secondary_username_hash_;
  base::test::ScopedCommandLine scoped_command_line;
};

TEST_F(ArcCroshServiceProviderTest, Success) {
  arc::mojom::ArcShellExecutionResultPtr mock_result =
      arc::mojom::ArcShellExecutionResult::NewStdout("top command result");
  mock_arc_shell_execution_instance().SetNextExecResult(std::move(mock_result));

  arc::ArcShellExecutionRequest request;
  request.set_command(arc::ArcShellExecutionRequest_ArcShellCommand::
                          ArcShellExecutionRequest_ArcShellCommand_TOP);
  request.set_user_id(primary_username_hash());

  std::unique_ptr<dbus::Response> response = CallExec(request);

  arc::mojom::ArcShellExecutionRequestPtr sent_mojo_request =
      mock_arc_shell_execution_instance().RecievedExecRequest();
  EXPECT_EQ(sent_mojo_request->command, arc::mojom::ArcShellCommand::kTop);

  arc::ArcShellExecutionResult result;
  dbus::MessageReader reader(response.get());
  ASSERT_TRUE(reader.PopArrayOfBytesAsProto(&result));

  EXPECT_FALSE(result.has_error());
  ASSERT_TRUE(result.has_stdout());
  EXPECT_EQ("top command result", result.stdout());
}

TEST_F(ArcCroshServiceProviderTest, EmptyCommand) {
  arc::ArcShellExecutionRequest request;
  request.set_user_id(primary_username_hash());

  std::unique_ptr<dbus::Response> response = CallExec(request);

  EXPECT_EQ(DBUS_ERROR_INVALID_ARGS, response->GetErrorName());

  std::string error_message;
  dbus::MessageReader reader(response.get());
  ASSERT_TRUE(reader.PopString(&error_message));

  EXPECT_EQ("command should not be empty", error_message);
}

TEST_F(ArcCroshServiceProviderTest, RequestWithoutUserId) {
  arc::ArcShellExecutionRequest request;
  request.set_command(arc::ArcShellExecutionRequest_ArcShellCommand::
                          ArcShellExecutionRequest_ArcShellCommand_TOP);

  std::unique_ptr<dbus::Response> response = CallExec(request);

  EXPECT_EQ(DBUS_ERROR_INVALID_ARGS, response->GetErrorName());

  std::string error_message;
  dbus::MessageReader reader(response.get());
  ASSERT_TRUE(reader.PopString(&error_message));

  EXPECT_EQ("user_id should not be empty", error_message);
}

TEST_F(ArcCroshServiceProviderTest, RequestFromSecondaryUser) {
  arc::ArcShellExecutionRequest request;
  request.set_command(arc::ArcShellExecutionRequest_ArcShellCommand::
                          ArcShellExecutionRequest_ArcShellCommand_TOP);
  request.set_user_id(secondary_username_hash());

  std::unique_ptr<dbus::Response> response = CallExec(request);

  EXPECT_EQ(DBUS_ERROR_ACCESS_DENIED, response->GetErrorName());

  std::string error_message;
  dbus::MessageReader reader(response.get());
  ASSERT_TRUE(reader.PopString(&error_message));

  EXPECT_EQ("Request from not primary user is prohibited", error_message);
}

}  // namespace
}  // namespace ash
