chromium/chromeos/ash/components/dbus/oobe_config/oobe_configuration_client_unittest.cc

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

#include "chromeos/ash/components/dbus/oobe_config/oobe_configuration_client.h"

#include <dbus/dbus-protocol.h>
#include <memory>
#include <string>

#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/components/dbus/oobe_config/oobe_config.pb.h"
#include "chromeos/ash/components/dbus/oobe_config/oobe_configuration_metrics.h"
#include "dbus/message.h"
#include "dbus/mock_bus.h"
#include "dbus/mock_object_proxy.h"
#include "dbus/object_path.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/oobe_config/dbus-constants.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

using ::testing::_;
using ::testing::Invoke;
using ::testing::Return;

namespace ash {

namespace {

// A mock ConfigurationCallback
class MockConfigurationCallback {
 public:
  MOCK_METHOD2(Run, void(bool, const std::string&));
};

// Expect the reader to be empty.
void ExpectNoArgument(dbus::MessageReader* reader) {
  EXPECT_FALSE(reader->HasMoreData());
}

}  // namespace

class OobeConfigurationClientTest : public testing::Test {
 public:
  OobeConfigurationClientTest()
      : interface_name_(oobe_config::kOobeConfigRestoreInterface),
        response_(nullptr) {}

  void SetUp() override {
    // Create a mock bus.
    dbus::Bus::Options options;
    options.bus_type = dbus::Bus::SYSTEM;
    mock_bus_ = new dbus::MockBus(options);

    // Create a mock oobe config proxy.
    mock_proxy_ = new dbus::MockObjectProxy(
        mock_bus_.get(), oobe_config::kOobeConfigRestoreServiceName,
        dbus::ObjectPath(oobe_config::kOobeConfigRestoreServicePath));

    // Set an expectation so mock_proxy's CallMethod() and
    // CallMethodWithErrorResponse() will use OnCallMethod() and
    // OnCallMethodWithErrorResponse() to return responses.
    EXPECT_CALL(*mock_proxy_.get(), DoCallMethod(_, _, _))
        .WillRepeatedly(
            Invoke(this, &OobeConfigurationClientTest::OnCallMethod));
    EXPECT_CALL(*mock_proxy_.get(), DoCallMethodWithErrorResponse(_, _, _))
        .WillRepeatedly(Invoke(
            this, &OobeConfigurationClientTest::OnCallMethodWithErrorResponse));

    // Set an expectation so mock_bus's GetObjectProxy() for the given
    // service name and the object path will return mock_proxy_.
    EXPECT_CALL(*mock_bus_.get(),
                GetObjectProxy(oobe_config::kOobeConfigRestoreServiceName,
                               dbus::ObjectPath(
                                   oobe_config::kOobeConfigRestoreServicePath)))
        .WillOnce(Return(mock_proxy_.get()));

    // ShutdownAndBlock() will be called in TearDown().
    EXPECT_CALL(*mock_bus_.get(), ShutdownAndBlock()).WillOnce(Return());

    // Create a client with the mock bus.
    OobeConfigurationClient::Initialize(mock_bus_.get());
    client_ = OobeConfigurationClient::Get();
    // Run the message loop to run the signal connection result callback.
    base::RunLoop().RunUntilIdle();
  }

  void TearDown() override {
    OobeConfigurationClient::Shutdown();
    mock_bus_->ShutdownAndBlock();
  }

 protected:
  // A callback to intercept and check the method call arguments.
  typedef base::RepeatingCallback<void(dbus::MessageReader* reader)>
      ArgumentCheckCallback;

  // Sets expectations for called method name and arguments, and sets response.
  void PrepareForMethodCall(const std::string& method_name,
                            const ArgumentCheckCallback& argument_checker,
                            std::unique_ptr<dbus::Response> response) {
    expected_method_name_ = method_name;
    argument_checker_ = argument_checker;
    response_ = std::move(response);
  }

  // Sets expectations for called method name and arguments, and sets response
  // and error response.
  void PrepareForMethodCallWithErrorResponse(
      const std::string& method_name,
      const ArgumentCheckCallback& argument_checker,
      std::unique_ptr<dbus::Response> response,
      std::unique_ptr<dbus::ErrorResponse> error_response) {
    expected_method_name_ = method_name;
    argument_checker_ = argument_checker;
    response_ = std::move(response);
    error_response_ = std::move(error_response);
  }

  // The interface name.
  const std::string interface_name_;
  // The client to be tested.
  raw_ptr<OobeConfigurationClient, DanglingUntriaged> client_ = nullptr;
  // A message loop to emulate asynchronous behavior.
  base::test::SingleThreadTaskEnvironment task_environment_;
  // The mock bus.
  scoped_refptr<dbus::MockBus> mock_bus_;
  // The mock object proxy.
  scoped_refptr<dbus::MockObjectProxy> mock_proxy_;

  // The name of the method which is expected to be called.
  std::string expected_method_name_;
  // The response which the mock proxy returns.
  std::unique_ptr<dbus::Response> response_;
  // The error response that the mock proxy returns.
  std::unique_ptr<dbus::ErrorResponse> error_response_;
  // A callback to intercept and check the method call arguments.
  ArgumentCheckCallback argument_checker_;

 private:
  // Checks the content of the method call and returns the response.
  // Used to implement the mock oobe config proxy.
  void OnCallMethod(dbus::MethodCall* method_call,
                    int timeout_ms,
                    dbus::ObjectProxy::ResponseCallback* response) {
    EXPECT_EQ(interface_name_, method_call->GetInterface());
    EXPECT_EQ(expected_method_name_, method_call->GetMember());
    dbus::MessageReader reader(method_call);
    argument_checker_.Run(&reader);
    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(std::move(*response), response_.get()));
  }

  // Checks the content of the method call and returns the response and error
  // response. Used to implement the mock oobe config proxy.
  void OnCallMethodWithErrorResponse(
      dbus::MethodCall* method_call,
      int timeout_ms,
      dbus::ObjectProxy::ResponseOrErrorCallback* callback) {
    EXPECT_EQ(interface_name_, method_call->GetInterface());
    EXPECT_EQ(expected_method_name_, method_call->GetMember());
    dbus::MessageReader reader(method_call);
    argument_checker_.Run(&reader);
    task_environment_.GetMainThreadTaskRunner()->PostTask(
        FROM_HERE, base::BindOnce(std::move(*callback), response_.get(),
                                  error_response_.get()));
  }
};

TEST_F(OobeConfigurationClientTest, CheckEmptyConfiguration) {
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  oobe_config::OobeRestoreData proto;
  proto.set_chrome_config_json(std::string());
  dbus::MessageWriter writer(response.get());
  writer.AppendInt32(0);
  writer.AppendProtoAsArrayOfBytes(proto);

  // Set expectations.
  PrepareForMethodCall(oobe_config::kProcessAndGetOobeAutoConfigMethod,
                       base::BindRepeating(&ExpectNoArgument),
                       std::move(response));

  MockConfigurationCallback callback;
  EXPECT_CALL(callback, Run(false, _)).Times(1);

  client_->CheckForOobeConfiguration(base::BindOnce(
      &MockConfigurationCallback::Run, base::Unretained(&callback)));

  base::RunLoop().RunUntilIdle();
}

TEST_F(OobeConfigurationClientTest, CheckServiceError) {
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  oobe_config::OobeRestoreData proto;
  proto.set_chrome_config_json("{key:true}");
  dbus::MessageWriter writer(response.get());
  writer.AppendInt32(1);
  writer.AppendProtoAsArrayOfBytes(proto);

  // Set expectations.
  PrepareForMethodCall(oobe_config::kProcessAndGetOobeAutoConfigMethod,
                       base::BindRepeating(&ExpectNoArgument),
                       std::move(response));

  MockConfigurationCallback callback;
  EXPECT_CALL(callback, Run(false, _)).Times(1);

  client_->CheckForOobeConfiguration(base::BindOnce(
      &MockConfigurationCallback::Run, base::Unretained(&callback)));

  base::RunLoop().RunUntilIdle();
}

TEST_F(OobeConfigurationClientTest, CheckConfigurationExists) {
  // Create response.
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  oobe_config::OobeRestoreData proto;
  proto.set_chrome_config_json("{key:true}");
  dbus::MessageWriter writer(response.get());
  writer.AppendInt32(0);
  writer.AppendProtoAsArrayOfBytes(proto);

  // Set expectations.
  PrepareForMethodCall(oobe_config::kProcessAndGetOobeAutoConfigMethod,
                       base::BindRepeating(&ExpectNoArgument),
                       std::move(response));

  MockConfigurationCallback callback;
  EXPECT_CALL(callback, Run(true, "{key:true}")).Times(1);

  client_->CheckForOobeConfiguration(base::BindOnce(
      &MockConfigurationCallback::Run, base::Unretained(&callback)));

  base::RunLoop().RunUntilIdle();
}

TEST_F(OobeConfigurationClientTest, DeleteFlexOobeConfigSuccess) {
  base::HistogramTester histogram_tester;
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());
  PrepareForMethodCallWithErrorResponse(
      oobe_config::kDeleteFlexOobeConfigMethod,
      base::BindRepeating(&ExpectNoArgument), std::move(response), nullptr);

  client_->DeleteFlexOobeConfig();
  base::RunLoop().RunUntilIdle();

  histogram_tester.ExpectUniqueSample(
      "OOBE.OobeConfig.DeleteFlexOobeConfig.DBusResult",
      DeleteFlexOobeConfigDBusResult::kSuccess, 1);
}

TEST_F(OobeConfigurationClientTest, DeleteFlexOobeConfigNoResponse) {
  base::HistogramTester histogram_tester;
  PrepareForMethodCallWithErrorResponse(
      oobe_config::kDeleteFlexOobeConfigMethod,
      base::BindRepeating(&ExpectNoArgument), nullptr, nullptr);

  client_->DeleteFlexOobeConfig();
  base::RunLoop().RunUntilIdle();

  histogram_tester.ExpectUniqueSample(
      "OOBE.OobeConfig.DeleteFlexOobeConfig.DBusResult",
      DeleteFlexOobeConfigDBusResult::kErrorUnknown, 1);
}

struct DeleteFlexOobeConfigErrorTestCase {
  // DBus error name, also called error code.
  std::string error_name;
  DeleteFlexOobeConfigDBusResult expected_dbus_result;
};

class DeleteFlexOobeConfigErrorTest
    : public OobeConfigurationClientTest,
      public testing::WithParamInterface<DeleteFlexOobeConfigErrorTestCase> {};

std::vector<DeleteFlexOobeConfigErrorTestCase> test_cases = {
    {DBUS_ERROR_ACCESS_DENIED,
     DeleteFlexOobeConfigDBusResult::kErrorAccessDenied},
    {DBUS_ERROR_NOT_SUPPORTED,
     DeleteFlexOobeConfigDBusResult::kErrorNotSupported},
    {DBUS_ERROR_FILE_NOT_FOUND,
     DeleteFlexOobeConfigDBusResult::kErrorConfigNotFound},
    {DBUS_ERROR_IO_ERROR, DeleteFlexOobeConfigDBusResult::kErrorIOError},
    // Arbitrary unhandled error that should map to kErrorUnknown.
    {DBUS_ERROR_SPAWN_CONFIG_INVALID,
     DeleteFlexOobeConfigDBusResult::kErrorUnknown}};

TEST_P(DeleteFlexOobeConfigErrorTest, DeleteFlexOobeConfigDBusError) {
  base::HistogramTester histogram_tester;
  dbus::MethodCall method_call(oobe_config::kOobeConfigRestoreInterface,
                               oobe_config::kDeleteFlexOobeConfigMethod);
  method_call.SetSerial(123);
  std::unique_ptr<dbus::ErrorResponse> error_response(
      dbus::ErrorResponse::FromMethodCall(&method_call, GetParam().error_name,
                                          "Arbitrary error message"));
  PrepareForMethodCallWithErrorResponse(
      oobe_config::kDeleteFlexOobeConfigMethod,
      base::BindRepeating(&ExpectNoArgument), nullptr,
      std::move(error_response));

  client_->DeleteFlexOobeConfig();
  base::RunLoop().RunUntilIdle();

  histogram_tester.ExpectUniqueSample(
      "OOBE.OobeConfig.DeleteFlexOobeConfig.DBusResult",
      GetParam().expected_dbus_result, 1);
}

INSTANTIATE_TEST_SUITE_P(DeleteFlexOobeConfigDBusErrors,
                         DeleteFlexOobeConfigErrorTest,
                         testing::ValuesIn(test_cases));
}  // namespace ash