| // Copyright 2023 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <memory> |
| #include <optional> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/files/scoped_temp_dir.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/values.h> |
| #include <brillo/compression/mock_compressor.h> |
| #include <brillo/strings/string_utils.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include "dlcservice/metadata/metadata.h" |
| #include "dlcservice/metadata/metadata_interface.h" |
| |
| using testing::_; |
| using testing::Return; |
| |
| namespace dlcservice::metadata { |
| |
| constexpr char kFirstDlc[] = "first-dlc"; |
| constexpr char kSecondDlc[] = "second-dlc"; |
| constexpr char kThirdDlc[] = "third-dlc"; |
| constexpr char kMetadataTemplate[] = R"("%s":{"manifest":%s,"table":"%s"},)"; |
| |
| class MetadataTest : public testing::Test { |
| public: |
| void SetUp() override { |
| testing::Test::SetUp(); |
| CHECK(scoped_temp_dir_.CreateUniqueTempDir()); |
| metadata_path_ = scoped_temp_dir_.GetPath(); |
| base::WriteFile( |
| metadata_path_.Append(std::string(kMetadataPrefix).append(kFirstDlc)), |
| "Test metadata file."); |
| |
| auto compressor = std::make_unique<brillo::MockCompressor>(); |
| compressor_ptr_ = compressor.get(); |
| |
| auto decompressor = std::make_unique<brillo::MockCompressor>(); |
| decompressor_ptr_ = decompressor.get(); |
| |
| metadata_ = std::make_unique<Metadata>(metadata_path_, kMaxMetadataFileSize, |
| std::move(compressor), |
| std::move(decompressor)); |
| EXPECT_CALL(*compressor_ptr_, Initialize).WillOnce(Return(true)); |
| EXPECT_CALL(*decompressor_ptr_, Initialize).WillOnce(Return(true)); |
| metadata_->Initialize(); |
| } |
| |
| protected: |
| base::ScopedTempDir scoped_temp_dir_; |
| base::FilePath metadata_path_; |
| |
| std::unique_ptr<Metadata> metadata_; |
| brillo::MockCompressor* compressor_ptr_; |
| brillo::MockCompressor* decompressor_ptr_; |
| }; |
| |
| TEST_F(MetadataTest, GetMetadata) { |
| std::string mock_metadata = |
| base::StringPrintf(kMetadataTemplate, kFirstDlc, "{}", kFirstDlc); |
| EXPECT_CALL(*decompressor_ptr_, Reset).WillOnce(Return(true)); |
| EXPECT_CALL(*decompressor_ptr_, Process) |
| .WillOnce(Return(brillo::string_utils::GetStringAsBytes(mock_metadata))); |
| |
| auto entry = metadata_->Get(kFirstDlc); |
| EXPECT_TRUE(entry); |
| } |
| |
| TEST_F(MetadataTest, GetUnsupportedMetadata) { |
| std::string mock_metadata = |
| base::StringPrintf(kMetadataTemplate, kFirstDlc, "{}", kFirstDlc); |
| EXPECT_CALL(*decompressor_ptr_, Reset).WillOnce(Return(true)); |
| EXPECT_CALL(*decompressor_ptr_, Process) |
| .WillOnce(Return(brillo::string_utils::GetStringAsBytes(mock_metadata))); |
| |
| EXPECT_FALSE(metadata_->Get("unsupported-dlc")); |
| } |
| |
| TEST_F(MetadataTest, GetMetadataDecompressionFailure) { |
| EXPECT_CALL(*decompressor_ptr_, Reset).WillOnce(Return(true)); |
| EXPECT_CALL(*decompressor_ptr_, Process).WillOnce(Return(std::nullopt)); |
| |
| EXPECT_FALSE(metadata_->Get(kFirstDlc)); |
| } |
| |
| TEST_F(MetadataTest, ModifyMetadata) { |
| std::string mock_metadata_first = |
| base::StringPrintf(kMetadataTemplate, kFirstDlc, "{}", kFirstDlc); |
| std::string mock_metadata_second = |
| base::StringPrintf(kMetadataTemplate, kSecondDlc, "{}", kSecondDlc); |
| EXPECT_CALL(*decompressor_ptr_, Reset).WillOnce(Return(true)); |
| EXPECT_CALL(*decompressor_ptr_, Process) |
| .WillOnce(Return(brillo::string_utils::GetStringAsBytes( |
| mock_metadata_first + mock_metadata_second))); |
| |
| // Mock modifying to a small data that still fits inside one file. |
| std::string modified("Modified data."); |
| EXPECT_CALL(*compressor_ptr_, Process(_, true)) |
| .WillRepeatedly(Return(brillo::string_utils::GetStringAsBytes(""))); |
| EXPECT_CALL(*compressor_ptr_, Process(_, false)) |
| .WillRepeatedly(Return(brillo::string_utils::GetStringAsBytes(modified))); |
| EXPECT_CALL(*compressor_ptr_, Reset).WillRepeatedly(Return(true)); |
| |
| std::unique_ptr<brillo::MockCompressor> clones[2]; |
| for (auto&& clone : clones) { |
| clone = std::make_unique<brillo::MockCompressor>(); |
| EXPECT_CALL(*clone, Process(_, true)) |
| .WillOnce(Return(brillo::string_utils::GetStringAsBytes(modified))); |
| } |
| EXPECT_CALL(*compressor_ptr_, Clone) |
| .WillOnce(Return(std::move(clones[0]))) |
| .WillOnce(Return(std::move(clones[1]))); |
| |
| // Test setting metadata with mocked compressor and decompressor. |
| Metadata::Entry entry{base::Value::Dict(), "table"}; |
| EXPECT_TRUE(metadata_->Set(kFirstDlc, entry)); |
| |
| // The metadata file id list should be unchanged. |
| const auto& file_ids = metadata_->GetFileIds(); |
| EXPECT_EQ(file_ids.size(), 1); |
| |
| std::string modified_file = *file_ids.begin(); |
| EXPECT_TRUE(base::ReadFileToString( |
| metadata_path_.Append(std::string(kMetadataPrefix).append(kFirstDlc)), |
| &modified_file)); |
| |
| EXPECT_EQ(modified_file, modified + modified); |
| } |
| |
| TEST_F(MetadataTest, ModifyMetadataToLargerContent) { |
| std::string mock_metadata_first = |
| base::StringPrintf(kMetadataTemplate, kFirstDlc, "{}", kFirstDlc); |
| std::string mock_metadata_second = |
| base::StringPrintf(kMetadataTemplate, kSecondDlc, "{}", kSecondDlc); |
| EXPECT_CALL(*decompressor_ptr_, Reset).WillOnce(Return(true)); |
| EXPECT_CALL(*decompressor_ptr_, Process) |
| .WillOnce(Return(brillo::string_utils::GetStringAsBytes( |
| mock_metadata_first + mock_metadata_second))); |
| |
| // Mock modifying to larger data that causes new file to be created. |
| std::string modified(kMaxMetadataFileSize / 2 + 1, 'x'); |
| EXPECT_CALL(*compressor_ptr_, Process(_, true)) |
| .WillRepeatedly(Return(brillo::string_utils::GetStringAsBytes(""))); |
| EXPECT_CALL(*compressor_ptr_, Process(_, false)) |
| .WillRepeatedly(Return(brillo::string_utils::GetStringAsBytes(modified))); |
| EXPECT_CALL(*compressor_ptr_, Reset).WillRepeatedly(Return(true)); |
| |
| std::unique_ptr<brillo::MockCompressor> clones[3]; |
| for (auto&& clone : clones) { |
| clone = std::make_unique<brillo::MockCompressor>(); |
| EXPECT_CALL(*clone, Process(_, true)) |
| .WillOnce(Return(brillo::string_utils::GetStringAsBytes(modified))); |
| } |
| EXPECT_CALL(*compressor_ptr_, Clone) |
| .WillOnce(Return(std::move(clones[0]))) |
| .WillOnce(Return(std::move(clones[1]))) |
| .WillOnce(Return(std::move(clones[2]))); |
| |
| Metadata::Entry entry{base::Value::Dict(), "table"}; |
| EXPECT_TRUE(metadata_->Set(kFirstDlc, entry)); |
| |
| // Verified that a new file is created. |
| const auto& file_ids = metadata_->GetFileIds(); |
| EXPECT_GT(file_ids.size(), 1); |
| |
| for (const auto& f_id : file_ids) { |
| std::string modified_file; |
| EXPECT_TRUE(base::ReadFileToString( |
| metadata_path_.Append(std::string(kMetadataPrefix).append(f_id)), |
| &modified_file)); |
| EXPECT_EQ(modified_file, modified); |
| } |
| } |
| |
| TEST_F(MetadataTest, ListAndFilterDlcIds) { |
| std::string mock_metadata1 = |
| base::StringPrintf(kMetadataTemplate, kFirstDlc, |
| "{\"factory-install\":\"str_val\"}", kFirstDlc); |
| std::string mock_metadata2 = base::StringPrintf( |
| kMetadataTemplate, kSecondDlc, "{\"preload-allowed\":true}", kSecondDlc); |
| std::string mock_metadata3 = base::StringPrintf( |
| kMetadataTemplate, kThirdDlc, "{\"powerwash-safe\":123}", kThirdDlc); |
| EXPECT_CALL(*decompressor_ptr_, Reset).WillOnce(Return(true)); |
| EXPECT_CALL(*decompressor_ptr_, Process) |
| .WillRepeatedly(Return(brillo::string_utils::GetStringAsBytes( |
| mock_metadata1 + mock_metadata2 + mock_metadata3))); |
| |
| EXPECT_EQ(metadata_->ListDlcIds(Metadata::FilterKey::kNone, base::Value()), |
| std::vector<DlcId>({kFirstDlc, kSecondDlc, kThirdDlc})); |
| EXPECT_EQ(metadata_->ListDlcIds(Metadata::FilterKey::kFactoryInstall, |
| base::Value("str_val")), |
| std::vector<DlcId>({kFirstDlc})); |
| EXPECT_EQ(metadata_->ListDlcIds(Metadata::FilterKey::kPreloadAllowed, |
| base::Value(true)), |
| std::vector<DlcId>({kSecondDlc})); |
| EXPECT_EQ(metadata_->ListDlcIds(Metadata::FilterKey::kPowerwashSafe, |
| base::Value(123)), |
| std::vector<DlcId>({kThirdDlc})); |
| } |
| |
| } // namespace dlcservice::metadata |