// Copyright 2021 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/fwupd/fwupd_client.h"
#include <cstdint>
#include <optional>
#include "ash/constants/ash_features.h"
#include "base/files/scoped_file.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/components/dbus/fwupd/dbus_constants.h"
#include "chromeos/ash/components/dbus/fwupd/fwupd_properties.h"
#include "chromeos/ash/components/dbus/fwupd/fwupd_request.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"
using ::testing::_;
using ::testing::Invoke;
namespace {
const char kFakeDeviceIdForTesting[] = "0123";
const char kFakeDeviceNameForTesting[] = "Fake Device";
const char kFakeUpdateVersionForTesting[] = "1.0.0";
const char kFakeUpdateDescriptionForTesting[] =
"This is a fake update for testing.";
const uint32_t kFakeUpdatePriorityForTesting = 1;
const char kFakeUpdateUriForTesting[] =
"file:///usr/share/fwupd/remotes.d/vendor/firmware/testFirmwarePath-V1.cab";
const char kFakeSha256ForTesting[] =
"3fab34cfa1ef97238fb24c5e40a979bc544bb2b0967b863e43e7d58e0d9a923f";
const uint64_t kFakeReportFlagForTesting = 1llu << 8;
const char kNameKey[] = "Name";
const char kIdKey[] = "DeviceId";
const char kVersionKey[] = "Version";
const char kDescriptionKey[] = "Description";
const char kPriorityKey[] = "Urgency";
const char kUriKey[] = "Uri";
const char kChecksumKey[] = "Checksum";
const char kTrustFlagsKey[] = "TrustFlags";
const char kFakeRemoteIdForTesting[] = "test-remote";
void RunResponseOrErrorCallback(
dbus::ObjectProxy::ResponseOrErrorCallback callback,
std::unique_ptr<dbus::Response> response,
std::unique_ptr<dbus::ErrorResponse> error_response) {
std::move(callback).Run(response.get(), error_response.get());
}
class MockObserver : public ash::FwupdClient::Observer {
public:
MOCK_METHOD(void,
OnDeviceListResponse,
(ash::FwupdDeviceList * devices),
(override));
MOCK_METHOD(void,
OnUpdateListResponse,
(const std::string& device_id, ash::FwupdUpdateList* updates),
(override));
MOCK_METHOD(void,
OnPropertiesChangedResponse,
(ash::FwupdProperties * properties),
(override));
MOCK_METHOD(void,
OnDeviceRequestResponse,
(ash::FwupdRequest request),
(override));
};
} // namespace
namespace ash {
class FwupdClientTest : public testing::Test {
public:
FwupdClientTest() {
dbus::Bus::Options options;
options.bus_type = dbus::Bus::SYSTEM;
bus_ = base::MakeRefCounted<dbus::MockBus>(options);
dbus::ObjectPath fwupd_service_path(kFwupdServicePath);
proxy_ = base::MakeRefCounted<dbus::MockObjectProxy>(
bus_.get(), kFwupdServiceName, fwupd_service_path);
EXPECT_CALL(*bus_.get(),
GetObjectProxy(kFwupdServiceName, fwupd_service_path))
.WillRepeatedly(testing::Return(proxy_.get()));
EXPECT_CALL(*proxy_, DoConnectToSignal(_, _, _, _))
.WillRepeatedly(Invoke(this, &FwupdClientTest::ConnectToSignal));
expected_properties_ = std::make_unique<FwupdDbusProperties>(
bus_->GetObjectProxy(kFwupdServiceName, fwupd_service_path),
base::DoNothing());
FwupdClient::Initialize(bus_.get());
fwupd_client_ = FwupdClient::Get();
fwupd_client_->client_is_in_testing_mode_ = true;
}
FwupdClientTest(const FwupdClientTest&) = delete;
FwupdClientTest& operator=(const FwupdClientTest&) = delete;
~FwupdClientTest() override { FwupdClient::Shutdown(); }
int GetDeviceSignalCallCount() {
return fwupd_client_->device_signal_call_count_for_testing_;
}
void DisableFeatureFlag(const base::Feature& feature) {
scoped_feature_list_.InitAndDisableFeature(feature);
}
void EnableFeatureFlag(const base::Feature& feature) {
scoped_feature_list_.InitAndEnableFeature(feature);
}
// This helper method is used to invoke the protected method
// SetFwupdFeatureFlags() from this friend class.
void CallSetFwupdFeatureFlags() { fwupd_client_->SetFwupdFeatureFlags(); }
void OnMethodCalled(dbus::MethodCall* method_call,
int timeout_ms,
dbus::ObjectProxy::ResponseOrErrorCallback* callback) {
ASSERT_FALSE(dbus_method_call_simulated_results_.empty());
MethodCallResult result =
std::move(dbus_method_call_simulated_results_.front());
dbus_method_call_simulated_results_.pop_front();
task_environment_.GetMainThreadTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&RunResponseOrErrorCallback, std::move(*callback),
std::move(result.first), std::move(result.second)));
}
std::unique_ptr<dbus::Response> CreateOneUpdateResponseWithChecksum(
const std::string& checksum) {
auto response = dbus::Response::CreateEmpty();
dbus::MessageWriter response_writer(response.get());
dbus::MessageWriter response_array_writer(nullptr);
dbus::MessageWriter device_array_writer(nullptr);
dbus::MessageWriter dict_writer(nullptr);
// The response is an array of arrays of dictionaries. Each dictionary is
// one device description.
response_writer.OpenArray("a{sv}", &response_array_writer);
response_array_writer.OpenArray("{sv}", &device_array_writer);
device_array_writer.OpenDictEntry(&dict_writer);
dict_writer.AppendString(kDescriptionKey);
dict_writer.AppendVariantOfString(kFakeUpdateDescriptionForTesting);
device_array_writer.CloseContainer(&dict_writer);
SetExpectedDescription(kFakeUpdateDescriptionForTesting);
device_array_writer.OpenDictEntry(&dict_writer);
dict_writer.AppendString(kVersionKey);
dict_writer.AppendVariantOfString(kFakeUpdateVersionForTesting);
device_array_writer.CloseContainer(&dict_writer);
device_array_writer.OpenDictEntry(&dict_writer);
dict_writer.AppendString(kPriorityKey);
dict_writer.AppendVariantOfUint32(kFakeUpdatePriorityForTesting);
device_array_writer.CloseContainer(&dict_writer);
device_array_writer.OpenDictEntry(&dict_writer);
dict_writer.AppendString(kUriKey);
dict_writer.AppendVariantOfString(kFakeUpdateUriForTesting);
device_array_writer.CloseContainer(&dict_writer);
device_array_writer.OpenDictEntry(&dict_writer);
dict_writer.AppendString(kChecksumKey);
dict_writer.AppendVariantOfString(checksum);
device_array_writer.CloseContainer(&dict_writer);
device_array_writer.OpenDictEntry(&dict_writer);
dict_writer.AppendString(kTrustFlagsKey);
dict_writer.AppendVariantOfUint64(kFakeReportFlagForTesting);
device_array_writer.CloseContainer(&dict_writer);
response_array_writer.CloseContainer(&device_array_writer);
response_writer.CloseContainer(&response_array_writer);
return response;
}
std::unique_ptr<dbus::Response> CreateOneUpdateResponseWithNoDescription() {
auto response = dbus::Response::CreateEmpty();
dbus::MessageWriter response_writer(response.get());
dbus::MessageWriter response_array_writer(nullptr);
dbus::MessageWriter device_array_writer(nullptr);
dbus::MessageWriter dict_writer(nullptr);
// The response is an array of arrays of dictionaries. Each dictionary is
// one device description.
response_writer.OpenArray("a{sv}", &response_array_writer);
response_array_writer.OpenArray("{sv}", &device_array_writer);
device_array_writer.OpenDictEntry(&dict_writer);
dict_writer.AppendString(kVersionKey);
dict_writer.AppendVariantOfString(kFakeUpdateVersionForTesting);
device_array_writer.CloseContainer(&dict_writer);
device_array_writer.OpenDictEntry(&dict_writer);
dict_writer.AppendString(kPriorityKey);
dict_writer.AppendVariantOfUint32(kFakeUpdatePriorityForTesting);
device_array_writer.CloseContainer(&dict_writer);
device_array_writer.OpenDictEntry(&dict_writer);
dict_writer.AppendString(kUriKey);
dict_writer.AppendVariantOfString(kFakeUpdateUriForTesting);
device_array_writer.CloseContainer(&dict_writer);
device_array_writer.OpenDictEntry(&dict_writer);
dict_writer.AppendString(kChecksumKey);
dict_writer.AppendVariantOfString(kFakeSha256ForTesting);
device_array_writer.CloseContainer(&dict_writer);
SetExpectedChecksum(kFakeSha256ForTesting);
device_array_writer.OpenDictEntry(&dict_writer);
dict_writer.AppendString(kTrustFlagsKey);
dict_writer.AppendVariantOfUint64(kFakeReportFlagForTesting);
device_array_writer.CloseContainer(&dict_writer);
response_array_writer.CloseContainer(&device_array_writer);
response_writer.CloseContainer(&response_array_writer);
return response;
}
void CheckDevices(FwupdDeviceList* devices) {
EXPECT_EQ(kFakeDeviceNameForTesting, (*devices)[0].device_name);
EXPECT_EQ(kFakeDeviceIdForTesting, (*devices)[0].id);
}
void CheckUpdates(const std::string& device_id, FwupdUpdateList* updates) {
if (updates->empty()) {
EXPECT_TRUE(expect_no_updates_);
return;
}
EXPECT_EQ(kFakeDeviceIdForTesting, device_id);
EXPECT_EQ(kFakeUpdateVersionForTesting, (*updates)[0].version);
EXPECT_EQ(expected_description_, (*updates)[0].description);
// This value is returned by DBus as a uint32_t and is added to a dictionary
// that doesn't support unsigned numbers. So it needs to be casted to int.
EXPECT_EQ(expected_priority_, (*updates)[0].priority);
EXPECT_EQ(kFakeUpdateUriForTesting, (*updates)[0].filepath.value());
EXPECT_EQ(expected_checksum_, (*updates)[0].checksum);
}
void CheckInstallState(bool success) { EXPECT_EQ(install_success_, success); }
void SetInstallState(bool success) { install_success_ = success; }
void SetExpectedChecksum(const std::string& checksum) {
expected_checksum_ = checksum;
}
void SetExpectedDescription(const std::string& description) {
expected_description_ = description;
}
void SetExpectedPriority(const int priority) {
expected_priority_ = priority;
}
void SetExpectNoUpdates(bool no_updates) { expect_no_updates_ = no_updates; }
void CheckPropertyChanged(FwupdProperties* properties) {
if (properties->IsPercentageValid()) {
EXPECT_EQ(expected_properties_->GetPercentage(),
properties->GetPercentage());
}
if (properties->IsStatusValid()) {
EXPECT_EQ(expected_properties_->GetStatus(), properties->GetStatus());
}
}
void AddDbusMethodCallResultSimulation(
std::unique_ptr<dbus::Response> response,
std::unique_ptr<dbus::ErrorResponse> error_response) {
dbus_method_call_simulated_results_.emplace_back(std::move(response),
std::move(error_response));
}
FwupdProperties* GetProperties() { return fwupd_client_->properties_.get(); }
protected:
// Creates a signal called |signal_name|, then simulates the signal being
// emitted by fwupd.
void EmitSignalByName(const std::string& signal_name) {
dbus::Signal signal(kFwupdServiceName, signal_name);
EmitSignal(signal_name, signal);
}
// Synchronously passes |signal| called |signal_name| to |client_|'s handler,
// simulating the signal being emitted by fwupd.
void EmitSignal(const std::string& signal_name, dbus::Signal& signal) {
const auto callback = signal_callbacks_.find(signal_name);
ASSERT_TRUE(callback != signal_callbacks_.end())
<< "Client didn't register for signal " << signal_name;
callback->second.Run(&signal);
}
scoped_refptr<dbus::MockObjectProxy> proxy_;
raw_ptr<FwupdClient, DanglingUntriaged> fwupd_client_ = nullptr;
std::unique_ptr<FwupdProperties> expected_properties_;
private:
// Handles calls to |proxy_|'s ConnectToSignal() method.
void ConnectToSignal(
const std::string& interface_name,
const std::string& signal_name,
dbus::ObjectProxy::SignalCallback signal_callback,
dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
signal_callbacks_[signal_name] = signal_callback;
task_environment_.GetMainThreadTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(std::move(*on_connected_callback), interface_name,
signal_name, true /* success */));
}
// Maps from fwupd signal name to the corresponding callback provided by
// |client_|.
base::flat_map<std::string, dbus::ObjectProxy::SignalCallback>
signal_callbacks_;
base::test::SingleThreadTaskEnvironment task_environment_;
// Mock bus for simulating calls.
scoped_refptr<dbus::MockBus> bus_;
using MethodCallResult = std::pair<std::unique_ptr<dbus::Response>,
std::unique_ptr<dbus::ErrorResponse>>;
std::deque<MethodCallResult> dbus_method_call_simulated_results_;
bool install_success_ = false;
bool expect_no_updates_ = false;
std::string expected_checksum_;
std::string expected_description_;
int expected_priority_ = kFakeUpdatePriorityForTesting;
base::test::ScopedFeatureList scoped_feature_list_;
};
// TODO (swifton): Rewrite this test with an observer when it's available.
TEST_F(FwupdClientTest, AddOneDevice) {
EmitSignalByName(kFwupdDeviceAddedSignalName);
EXPECT_EQ(1, GetDeviceSignalCallCount());
}
TEST_F(FwupdClientTest, RequestDevices) {
// The observer will check that the device description is parsed and passed
// correctly.
MockObserver observer;
EXPECT_CALL(observer, OnDeviceListResponse(_))
.Times(1)
.WillRepeatedly(Invoke(this, &FwupdClientTest::CheckDevices));
fwupd_client_->AddObserver(&observer);
EXPECT_CALL(*proxy_, DoCallMethodWithErrorResponse(_, _, _))
.WillRepeatedly(Invoke(this, &FwupdClientTest::OnMethodCalled));
// Create a response simulation that contains one device description.
auto response = dbus::Response::CreateEmpty();
dbus::MessageWriter response_writer(response.get());
dbus::MessageWriter response_array_writer(nullptr);
dbus::MessageWriter device_array_writer(nullptr);
dbus::MessageWriter dict_writer(nullptr);
// The response is an array of arrays of dictionaries. Each dictionary is one
// device description.
response_writer.OpenArray("a{sv}", &response_array_writer);
response_array_writer.OpenArray("{sv}", &device_array_writer);
device_array_writer.OpenDictEntry(&dict_writer);
dict_writer.AppendString(kNameKey);
dict_writer.AppendVariantOfString(kFakeDeviceNameForTesting);
device_array_writer.CloseContainer(&dict_writer);
device_array_writer.OpenDictEntry(&dict_writer);
dict_writer.AppendString(kIdKey);
dict_writer.AppendVariantOfString(kFakeDeviceIdForTesting);
device_array_writer.CloseContainer(&dict_writer);
response_array_writer.CloseContainer(&device_array_writer);
response_writer.CloseContainer(&response_array_writer);
AddDbusMethodCallResultSimulation(std::move(response), nullptr);
fwupd_client_->RequestDevices();
base::RunLoop().RunUntilIdle();
}
TEST_F(FwupdClientTest, RequestUpgrades) {
// The observer will check that the update description is parsed and passed
// correctly.
MockObserver observer;
EXPECT_CALL(observer, OnUpdateListResponse(_, _))
.Times(1)
.WillRepeatedly(Invoke(this, &FwupdClientTest::CheckUpdates));
fwupd_client_->AddObserver(&observer);
EXPECT_CALL(*proxy_, DoCallMethodWithErrorResponse(_, _, _))
.WillRepeatedly(Invoke(this, &FwupdClientTest::OnMethodCalled));
auto response = dbus::Response::CreateEmpty();
dbus::MessageWriter response_writer(response.get());
dbus::MessageWriter response_array_writer(nullptr);
dbus::MessageWriter device_array_writer(nullptr);
dbus::MessageWriter dict_writer(nullptr);
// The response is an array of arrays of dictionaries. Each dictionary is one
// update description.
response_writer.OpenArray("a{sv}", &response_array_writer);
response_array_writer.OpenArray("{sv}", &device_array_writer);
device_array_writer.OpenDictEntry(&dict_writer);
dict_writer.AppendString(kDescriptionKey);
dict_writer.AppendVariantOfString(kFakeUpdateDescriptionForTesting);
device_array_writer.CloseContainer(&dict_writer);
SetExpectedDescription(kFakeUpdateDescriptionForTesting);
device_array_writer.OpenDictEntry(&dict_writer);
dict_writer.AppendString(kVersionKey);
dict_writer.AppendVariantOfString(kFakeUpdateVersionForTesting);
device_array_writer.CloseContainer(&dict_writer);
device_array_writer.OpenDictEntry(&dict_writer);
dict_writer.AppendString(kPriorityKey);
dict_writer.AppendVariantOfUint32(kFakeUpdatePriorityForTesting);
device_array_writer.CloseContainer(&dict_writer);
device_array_writer.OpenDictEntry(&dict_writer);
dict_writer.AppendString(kUriKey);
dict_writer.AppendVariantOfString(kFakeUpdateUriForTesting);
device_array_writer.CloseContainer(&dict_writer);
device_array_writer.OpenDictEntry(&dict_writer);
dict_writer.AppendString(kTrustFlagsKey);
dict_writer.AppendVariantOfUint64(kFakeReportFlagForTesting);
device_array_writer.CloseContainer(&dict_writer);
device_array_writer.OpenDictEntry(&dict_writer);
dict_writer.AppendString(kChecksumKey);
dict_writer.AppendVariantOfString(kFakeSha256ForTesting);
device_array_writer.CloseContainer(&dict_writer);
SetExpectedChecksum(kFakeSha256ForTesting);
response_array_writer.CloseContainer(&device_array_writer);
response_writer.CloseContainer(&response_array_writer);
AddDbusMethodCallResultSimulation(std::move(response), nullptr);
fwupd_client_->RequestUpdates(kFakeDeviceIdForTesting);
base::RunLoop().RunUntilIdle();
}
TEST_F(FwupdClientTest, RequestUpgradesWithoutPriority) {
// The observer will check that the update description is parsed and passed
// correctly.
MockObserver observer;
EXPECT_CALL(observer, OnUpdateListResponse(_, _))
.Times(1)
.WillRepeatedly(Invoke(this, &FwupdClientTest::CheckUpdates));
fwupd_client_->AddObserver(&observer);
EXPECT_CALL(*proxy_, DoCallMethodWithErrorResponse(_, _, _))
.WillRepeatedly(Invoke(this, &FwupdClientTest::OnMethodCalled));
auto response = dbus::Response::CreateEmpty();
dbus::MessageWriter response_writer(response.get());
dbus::MessageWriter response_array_writer(nullptr);
dbus::MessageWriter device_array_writer(nullptr);
dbus::MessageWriter dict_writer(nullptr);
// The response is an array of arrays of dictionaries. Each dictionary is one
// update description.
response_writer.OpenArray("a{sv}", &response_array_writer);
response_array_writer.OpenArray("{sv}", &device_array_writer);
device_array_writer.OpenDictEntry(&dict_writer);
dict_writer.AppendString(kDescriptionKey);
dict_writer.AppendVariantOfString(kFakeUpdateDescriptionForTesting);
device_array_writer.CloseContainer(&dict_writer);
SetExpectedDescription(kFakeUpdateDescriptionForTesting);
device_array_writer.OpenDictEntry(&dict_writer);
dict_writer.AppendString(kVersionKey);
dict_writer.AppendVariantOfString(kFakeUpdateVersionForTesting);
device_array_writer.CloseContainer(&dict_writer);
device_array_writer.OpenDictEntry(&dict_writer);
dict_writer.AppendString(kUriKey);
dict_writer.AppendVariantOfString(kFakeUpdateUriForTesting);
device_array_writer.CloseContainer(&dict_writer);
device_array_writer.OpenDictEntry(&dict_writer);
dict_writer.AppendString(kChecksumKey);
dict_writer.AppendVariantOfString(kFakeSha256ForTesting);
device_array_writer.CloseContainer(&dict_writer);
SetExpectedChecksum(kFakeSha256ForTesting);
device_array_writer.OpenDictEntry(&dict_writer);
dict_writer.AppendString(kTrustFlagsKey);
dict_writer.AppendVariantOfUint64(kFakeReportFlagForTesting);
device_array_writer.CloseContainer(&dict_writer);
response_array_writer.CloseContainer(&device_array_writer);
response_writer.CloseContainer(&response_array_writer);
AddDbusMethodCallResultSimulation(std::move(response), nullptr);
// Since priority is not specified, we want to use lowest priority
SetExpectedPriority(0);
fwupd_client_->RequestUpdates(kFakeDeviceIdForTesting);
base::RunLoop().RunUntilIdle();
}
TEST_F(FwupdClientTest, TwoChecksumAvailable) {
// The observer will check that the update description is parsed and passed
// correctly.
MockObserver observer;
EXPECT_CALL(observer, OnUpdateListResponse(_, _))
.Times(1)
.WillRepeatedly(Invoke(this, &FwupdClientTest::CheckUpdates));
fwupd_client_->AddObserver(&observer);
EXPECT_CALL(*proxy_, DoCallMethodWithErrorResponse(_, _, _))
.WillRepeatedly(Invoke(this, &FwupdClientTest::OnMethodCalled));
const std::string checksum = std::string(kFakeSha256ForTesting) +
",badbbadbad1ef97238fb24c5e40a979bc544bb2b";
AddDbusMethodCallResultSimulation(
CreateOneUpdateResponseWithChecksum(checksum), nullptr);
SetExpectedChecksum(kFakeSha256ForTesting);
fwupd_client_->RequestUpdates(kFakeDeviceIdForTesting);
base::RunLoop().RunUntilIdle();
}
TEST_F(FwupdClientTest, TwoChecksumAvailableInverse) {
// The observer will check that the update description is parsed and passed
// correctly.
MockObserver observer;
EXPECT_CALL(observer, OnUpdateListResponse(_, _))
.Times(1)
.WillRepeatedly(Invoke(this, &FwupdClientTest::CheckUpdates));
fwupd_client_->AddObserver(&observer);
EXPECT_CALL(*proxy_, DoCallMethodWithErrorResponse(_, _, _))
.WillRepeatedly(Invoke(this, &FwupdClientTest::OnMethodCalled));
const std::string checksum = "badbbadbad1ef97238fb24c5e40a979bc544bb2b," +
std::string(kFakeSha256ForTesting);
AddDbusMethodCallResultSimulation(
CreateOneUpdateResponseWithChecksum(checksum), nullptr);
SetExpectedChecksum(kFakeSha256ForTesting);
fwupd_client_->RequestUpdates(kFakeDeviceIdForTesting);
base::RunLoop().RunUntilIdle();
}
TEST_F(FwupdClientTest, MissingChecksum) {
// The observer will check that the update description is parsed and passed
// correctly.
MockObserver observer;
EXPECT_CALL(observer, OnUpdateListResponse(_, _))
.Times(1)
.WillRepeatedly(Invoke(this, &FwupdClientTest::CheckUpdates));
fwupd_client_->AddObserver(&observer);
EXPECT_CALL(*proxy_, DoCallMethodWithErrorResponse(_, _, _))
.WillRepeatedly(Invoke(this, &FwupdClientTest::OnMethodCalled));
AddDbusMethodCallResultSimulation(CreateOneUpdateResponseWithChecksum(""),
nullptr);
SetExpectNoUpdates(/*expect_no_updates=*/true);
fwupd_client_->RequestUpdates(kFakeDeviceIdForTesting);
base::RunLoop().RunUntilIdle();
}
TEST_F(FwupdClientTest, BadFormatChecksum) {
// The observer will check that the update description is parsed and passed
// correctly.
MockObserver observer;
EXPECT_CALL(observer, OnUpdateListResponse(_, _))
.Times(1)
.WillRepeatedly(Invoke(this, &FwupdClientTest::CheckUpdates));
fwupd_client_->AddObserver(&observer);
EXPECT_CALL(*proxy_, DoCallMethodWithErrorResponse(_, _, _))
.WillRepeatedly(Invoke(this, &FwupdClientTest::OnMethodCalled));
const std::string checksum = std::string(kFakeSha256ForTesting) + ",";
AddDbusMethodCallResultSimulation(
CreateOneUpdateResponseWithChecksum(checksum), nullptr);
SetExpectNoUpdates(/*expect_no_updates=*/true);
fwupd_client_->RequestUpdates(kFakeDeviceIdForTesting);
base::RunLoop().RunUntilIdle();
}
TEST_F(FwupdClientTest, BadFormatChecksumOnlyComma) {
// The observer will check that the update description is parsed and passed
// correctly.
MockObserver observer;
EXPECT_CALL(observer, OnUpdateListResponse(_, _))
.Times(1)
.WillRepeatedly(Invoke(this, &FwupdClientTest::CheckUpdates));
fwupd_client_->AddObserver(&observer);
EXPECT_CALL(*proxy_, DoCallMethodWithErrorResponse(_, _, _))
.WillRepeatedly(Invoke(this, &FwupdClientTest::OnMethodCalled));
AddDbusMethodCallResultSimulation(CreateOneUpdateResponseWithChecksum(","),
nullptr);
SetExpectNoUpdates(/*expect_no_updates=*/true);
fwupd_client_->RequestUpdates(kFakeDeviceIdForTesting);
base::RunLoop().RunUntilIdle();
}
TEST_F(FwupdClientTest, Install) {
EXPECT_CALL(*proxy_, DoCallMethodWithErrorResponse(_, _, _))
.WillRepeatedly(Invoke(this, &FwupdClientTest::OnMethodCalled));
auto response = dbus::Response::CreateEmpty();
dbus::MessageWriter response_writer(response.get());
// The response is an boolean for whether the install request was successful
// or not.
const bool install_success = true;
SetInstallState(install_success);
response_writer.AppendBool(install_success);
AddDbusMethodCallResultSimulation(std::move(response), nullptr);
base::RunLoop run_loop;
fwupd_client_->InstallUpdate(
kFakeDeviceIdForTesting, base::ScopedFD(0), std::map<std::string, bool>(),
base::BindLambdaForTesting([&](FwupdDbusResult result) {
EXPECT_EQ(result, FwupdDbusResult::kSuccess);
run_loop.Quit();
}));
run_loop.Run();
}
TEST_F(FwupdClientTest, PropertiesChanged) {
const uint32_t expected_percentage = 50u;
const uint32_t expected_status = 1u;
expected_properties_->SetPercentage(expected_percentage);
expected_properties_->SetStatus(expected_status);
MockObserver observer;
EXPECT_CALL(observer, OnPropertiesChangedResponse(_))
.Times(2)
.WillRepeatedly(Invoke(this, &FwupdClientTest::CheckPropertyChanged));
fwupd_client_->AddObserver(&observer);
GetProperties()->SetPercentage(expected_percentage);
GetProperties()->SetStatus(expected_status);
}
TEST_F(FwupdClientTest, NoDescription) {
// The observer will check that the update description is parsed and passed
// correctly.
MockObserver observer;
EXPECT_CALL(observer, OnUpdateListResponse(_, _))
.Times(1)
.WillRepeatedly(Invoke(this, &FwupdClientTest::CheckUpdates));
fwupd_client_->AddObserver(&observer);
EXPECT_CALL(*proxy_, DoCallMethodWithErrorResponse(_, _, _))
.WillRepeatedly(Invoke(this, &FwupdClientTest::OnMethodCalled));
AddDbusMethodCallResultSimulation(CreateOneUpdateResponseWithNoDescription(),
nullptr);
SetExpectedDescription("");
fwupd_client_->RequestUpdates(kFakeDeviceIdForTesting);
base::RunLoop().RunUntilIdle();
}
TEST_F(FwupdClientTest, SetFeatureFlagsWithV2FlagDisabled) {
// Fwupd feature flags should not be set if the v2 flag is disabled.
// To test this, verify that no D-Bus method calls are made.
EXPECT_CALL(*proxy_, DoCallMethodWithErrorResponse(_, _, _)).Times(0);
DisableFeatureFlag(ash::features::kFirmwareUpdateUIV2);
CallSetFwupdFeatureFlags();
}
TEST_F(FwupdClientTest, SetFeatureFlagsWithV2FlagEnabled) {
// Expect that the D-Bus method "SetFeatureFlags" is called when the Firmware
// Updates v2 flag is enabled.
// Helper function to get the uint64 args passed to the given method_call.
auto GetUint64ArgumentOfMethod =
[](dbus::MethodCall* method_call) -> std::optional<uint64_t> {
dbus::MessageReader reader(method_call);
if (!reader.HasMoreData()) {
return std::nullopt;
}
uint64_t feature_flag_arguments;
if (!reader.PopUint64(&feature_flag_arguments)) {
return std::nullopt;
}
return feature_flag_arguments;
};
const uint64_t kRequestsFeatureFlag = 1llu << 4;
EXPECT_CALL(
*proxy_,
DoCallMethodWithErrorResponse(
testing::AllOf(
testing::ResultOf("method name",
std::mem_fn(&dbus::MethodCall::GetMember),
testing::StrEq("SetFeatureFlags")),
testing::ResultOf("feature flag passed to the method call",
GetUint64ArgumentOfMethod,
testing::Eq(kRequestsFeatureFlag))),
_, _))
.Times(1);
EnableFeatureFlag(ash::features::kFirmwareUpdateUIV2);
CallSetFwupdFeatureFlags();
}
struct FwupdClientTest_DeviceRequestParam {
std::string device_request_id_key;
int expected_index_of_request_id;
};
class FwupdClientTest_DeviceRequest
: public FwupdClientTest,
public testing::WithParamInterface<FwupdClientTest_DeviceRequestParam> {};
INSTANTIATE_TEST_SUITE_P(
/* no prefix */,
FwupdClientTest_DeviceRequest,
testing::ValuesIn<FwupdClientTest_DeviceRequestParam>({
{/*device_request_id_key=*/kFwupdDeviceRequestId_DoNotPowerOff,
/*expected_index_of_request_id=*/0},
{/*device_request_id_key=*/kFwupdDeviceRequestId_ReplugInstall,
/*expected_index_of_request_id=*/1},
{/*device_request_id_key=*/kFwupdDeviceRequestId_InsertUSBCable,
/*expected_index_of_request_id=*/2},
{/*device_request_id_key=*/kFwupdDeviceRequestId_RemoveUSBCable,
/*expected_index_of_request_id=*/3},
{/*device_request_id_key=*/kFwupdDeviceRequestId_PressUnlock,
/*expected_index_of_request_id=*/4},
{/*device_request_id_key=*/kFwupdDeviceRequestId_RemoveReplug,
/*expected_index_of_request_id=*/5},
{/*device_request_id_key=*/kFwupdDeviceRequestId_ReplugPower,
/*expected_index_of_request_id=*/6},
}));
// Test that the DeviceRequest signal is parsed correctly and the
// DeviceRequestObserver is called with the correct information.
TEST_P(FwupdClientTest_DeviceRequest, OnDeviceRequestReceived) {
// Create a mock "DeviceRequest" signal
dbus::Signal signal(kFwupdServiceName, kFwupdDeviceRequestReceivedSignalName);
dbus::MessageWriter writer(&signal);
dbus::MessageWriter sub_writer(nullptr);
writer.OpenArray("{sv}", &sub_writer);
dbus::MessageWriter entry_writer(nullptr);
// Create an entry for each key found in a DeviceRequest signal, and populate
// it with fake data
sub_writer.OpenDictEntry(&entry_writer);
entry_writer.AppendString(kFwupdDeviceRequestKey_AppstreamId);
entry_writer.AppendVariantOfString(GetParam().device_request_id_key);
sub_writer.CloseContainer(&entry_writer);
sub_writer.OpenDictEntry(&entry_writer);
entry_writer.AppendString(kFwupdDeviceRequestKey_Created);
entry_writer.AppendVariantOfUint64(1024);
sub_writer.CloseContainer(&entry_writer);
sub_writer.OpenDictEntry(&entry_writer);
entry_writer.AppendString(kFwupdDeviceRequestKey_DeviceId);
entry_writer.AppendVariantOfString(kFakeDeviceIdForTesting);
sub_writer.CloseContainer(&entry_writer);
sub_writer.OpenDictEntry(&entry_writer);
entry_writer.AppendString(kFwupdDeviceRequestKey_UpdateMessage);
entry_writer.AppendVariantOfString("Fake update message");
sub_writer.CloseContainer(&entry_writer);
sub_writer.OpenDictEntry(&entry_writer);
entry_writer.AppendString(kFwupdDeviceRequestKey_RequestKind);
entry_writer.AppendVariantOfUint32(2);
sub_writer.CloseContainer(&entry_writer);
writer.CloseContainer(&sub_writer);
MockObserver observer;
auto GetRequestId = [](FwupdRequest request) -> std::optional<uint32_t> {
return request.id;
};
auto GetRequestKind = [](FwupdRequest request) -> std::optional<uint32_t> {
return request.kind;
};
EXPECT_CALL(
observer,
OnDeviceRequestResponse(testing::AllOf(
// Ensure that the resulting observer is triggered with the
// correctly-parsed DeviceRequestId.
testing::ResultOf(
"Request ID", GetRequestId,
testing::Eq(GetParam().expected_index_of_request_id)),
testing::ResultOf("Request Kind", GetRequestKind, testing::Eq(2)))))
.Times(1);
fwupd_client_->AddObserver(&observer);
EmitSignal(kFwupdDeviceRequestReceivedSignalName, signal);
base::RunLoop().RunUntilIdle();
}
TEST_F(FwupdClientTest, UpdateMetadata) {
EXPECT_CALL(*proxy_, DoCallMethodWithErrorResponse(_, _, _))
.WillRepeatedly(Invoke(this, &FwupdClientTest::OnMethodCalled));
auto response = dbus::Response::CreateEmpty();
dbus::MessageWriter response_writer(response.get());
const bool update_success = true;
response_writer.AppendBool(update_success);
AddDbusMethodCallResultSimulation(std::move(response), nullptr);
base::RunLoop run_loop;
fwupd_client_->UpdateMetadata(
kFakeRemoteIdForTesting, base::ScopedFD(0), base::ScopedFD(1),
base::BindLambdaForTesting([&](FwupdDbusResult result) {
EXPECT_EQ(result, FwupdDbusResult::kSuccess);
run_loop.Quit();
}));
run_loop.Run();
}
} // namespace ash