chromium/chromeos/dbus/regmon/regmon_client_unittest.cc

// 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 "chromeos/dbus/regmon/regmon_client.h"

#include "base/test/mock_log.h"
#include "base/test/task_environment.h"
#include "chromeos/dbus/regmon/regmon_service.pb.h"
#include "dbus/bus.h"
#include "dbus/message.h"
#include "dbus/mock_bus.h"
#include "dbus/mock_object_proxy.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/regmon/dbus-constants.h"

using ::logging::LOGGING_ERROR;

using ::testing::_;
using ::testing::EndsWith;
using ::testing::HasSubstr;
using ::testing::Invoke;
using ::testing::Return;

namespace chromeos {

class RegmonClientTest : public testing::Test {
 public:
  RegmonClientTest() = default;

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

  ~RegmonClientTest() override = default;

  void SetUp() override {
    bus_ = new dbus::MockBus(dbus::Bus::Options());

    // Setup MockObjectProxy. This is used to mock out the D-Bus calls made via
    // CallMethod(). Each test creates its own D-Bus response and invokes the
    // passed in ResponseCallback with the response being tested.
    proxy_ =
        new dbus::MockObjectProxy(bus_.get(), regmon::kRegmonServiceName,
                                  dbus::ObjectPath(regmon::kRegmonServicePath));
    EXPECT_CALL(*bus_,
                GetObjectProxy(regmon::kRegmonServiceName,
                               dbus::ObjectPath(regmon::kRegmonServicePath)))
        .WillRepeatedly(Return(proxy_.get()));

    RegmonClient::Initialize(bus_.get());

    mock_log_.StartCapturingLogs();
  }

  void TearDown() override {
    RegmonClient::Shutdown();
    mock_log_.StopCapturingLogs();
  }

 protected:
  base::test::SingleThreadTaskEnvironment task_environment_;

  // Mock bus and proxy for simulating calls to regmond.
  scoped_refptr<dbus::MockBus> bus_;
  scoped_refptr<dbus::MockObjectProxy> proxy_;

  // Mock log for capturing and verifying error output.
  base::test::MockLog mock_log_;
};

TEST_F(RegmonClientTest, RecordPolicyViolation_SuccessTest) {
  const int32_t kTestHashCode = 123;

  EXPECT_CALL(*proxy_, DoCallMethod)
      .WillOnce(Invoke([&](dbus::MethodCall* method_call, int timeout_ms,
                           dbus::ObjectProxy::ResponseCallback* callback) {
        // First verify that the request proto was included in the dbus call.
        dbus::MessageReader reader(method_call);
        regmon::RecordPolicyViolationRequest request;
        if (!reader.PopArrayOfBytesAsProto(&request)) {
          FAIL() << "Failed to parse RecordPolicyViolationRequest";
        }
        EXPECT_EQ(request.violation().annotation_hash(), kTestHashCode);

        // Run callback with successful dbus response (no errors).
        auto response = dbus::Response::CreateEmpty();
        dbus::MessageWriter writer(response.get());
        writer.AppendProtoAsArrayOfBytes(
            regmon::RecordPolicyViolationResponse());
        std::move(*callback).Run(response.get());
      }));

  EXPECT_CALL(mock_log_, Log(LOGGING_ERROR, _, _, _, _)).Times(0);

  regmon::RecordPolicyViolationRequest request;
  request.mutable_violation()->set_annotation_hash(kTestHashCode);
  RegmonClient::Get()->RecordPolicyViolation(request);
}

TEST_F(RegmonClientTest, RecordPolicyViolation_ErrorTest) {
  const std::string kRegmonErrorMessage = "Mock error from regmon";

  EXPECT_CALL(*proxy_, DoCallMethod)
      .WillOnce(Invoke([&](dbus::MethodCall* method_call, int timeout_ms,
                           dbus::ObjectProxy::ResponseCallback* callback) {
        // Run callback with a dbus response containing an error from regmond.
        auto response = dbus::Response::CreateEmpty();
        dbus::MessageWriter writer(response.get());

        regmon::RecordPolicyViolationResponse regmon_response;
        auto* status = regmon_response.mutable_status();
        status->set_error_message(kRegmonErrorMessage);

        writer.AppendProtoAsArrayOfBytes(regmon_response);
        std::move(*callback).Run(response.get());
      }));

  EXPECT_CALL(mock_log_, Log(LOGGING_ERROR, _, _, _, _))
      .Times(testing::AnyNumber());  // ignore uninteresting log calls
  EXPECT_CALL(mock_log_, Log(LOGGING_ERROR, EndsWith("regmon_client.cc"), _, _,
                             HasSubstr(kRegmonErrorMessage)))
      .Times(1);

  RegmonClient::Get()->RecordPolicyViolation(
      regmon::RecordPolicyViolationRequest());
}

TEST_F(RegmonClientTest, RecordPolicyViolation_NoResponseTest) {
  EXPECT_CALL(*proxy_, DoCallMethod)
      .WillOnce(Invoke([](dbus::MethodCall* method_call, int timeout_ms,
                          dbus::ObjectProxy::ResponseCallback* callback) {
        // Run callback with null dbus response. This should cause a no-response
        // error in the callback.
        std::move(*callback).Run(nullptr);
      }));

  EXPECT_CALL(mock_log_, Log(LOGGING_ERROR, _, _, _, _))
      .Times(testing::AnyNumber());  // ignore uninteresting log calls
  EXPECT_CALL(mock_log_,
              Log(LOGGING_ERROR, EndsWith("regmon_client.cc"), _, _,
                  HasSubstr("No response message received from regmon")))
      .Times(1);

  RegmonClient::Get()->RecordPolicyViolation(
      regmon::RecordPolicyViolationRequest());
}

TEST_F(RegmonClientTest, RecordPolicyViolation_ResponseParseFailureTest) {
  EXPECT_CALL(*proxy_, DoCallMethod)
      .WillOnce(Invoke([](dbus::MethodCall* method_call, int timeout_ms,
                          dbus::ObjectProxy::ResponseCallback* callback) {
        // Run callback with empty dbus response. This should cause a response
        // parsing error in the callback.
        std::move(*callback).Run(dbus::Response::CreateEmpty().get());
      }));

  EXPECT_CALL(mock_log_, Log(LOGGING_ERROR, _, _, _, _))
      .Times(testing::AnyNumber());  // ignore uninteresting log calls
  EXPECT_CALL(mock_log_, Log(LOGGING_ERROR, EndsWith("regmon_client.cc"), _, _,
                             HasSubstr("Failed to parse response message")))
      .Times(1);

  RegmonClient::Get()->RecordPolicyViolation(
      regmon::RecordPolicyViolationRequest());
}

}  // namespace chromeos