// 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 "components/persistent_cache/persistent_cache.h"

#include <stdint.h>

#include <memory>

#include "base/containers/span.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/test/gmock_expected_support.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "components/persistent_cache/backend_storage.h"
#include "components/persistent_cache/backend_type.h"
#include "components/persistent_cache/client.h"
#include "components/persistent_cache/mock/mock_backend.h"
#include "components/persistent_cache/pending_backend.h"
#include "components/persistent_cache/sqlite/sqlite_backend_impl.h"
#include "components/persistent_cache/test_utils.h"
#include "components/persistent_cache/transaction_error.h"
#include "components/sqlite_vfs/client.h"
#include "components/sqlite_vfs/file_type.h"
#include "components/sqlite_vfs/vfs_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_map.h"

namespace {

using base::test::ErrorIs;
using base::test::HasValue;
using base::test::ValueIs;
using testing::_;
using testing::AllOf;
using testing::Eq;
using testing::Field;
using testing::Ge;
using testing::Le;
using testing::Ne;
using testing::Optional;
using testing::Return;

// Note: byte_span_from_cstring isn't constexpr.
constexpr auto kKey = base::span_from_cstring("foo");

class PersistentCacheMockedBackendTest : public testing::Test {
 protected:
  void SetUp() override {
    backend_ = std::make_unique<persistent_cache::MockBackend>();
  }

  void CreateCache() {
    cache_ = std::make_unique<persistent_cache::PersistentCache>(
        persistent_cache::Client::kTest, std::move(backend_));
  }

  persistent_cache::MockBackend* GetBackend() {
    // Can't be called without a cache.
    CHECK(cache_);
    return static_cast<persistent_cache::MockBackend*>(
        cache_->GetBackendForTesting());
  }

  std::unique_ptr<persistent_cache::MockBackend> backend_;
  std::unique_ptr<persistent_cache::PersistentCache> cache_;
};

}  // namespace

namespace persistent_cache {

TEST_F(PersistentCacheMockedBackendTest, CacheFindCallsBackendFind) {
  CreateCache();
  // Jump through a hoop to pass a dynamic span as a matcher.
  EXPECT_CALL(*GetBackend(), Find(base::as_bytes(kKey.first(kKey.size())), _))
      .WillOnce(Return(base::ok(std::nullopt)));

  EXPECT_THAT(cache_->Find(base::as_bytes(kKey),
                           [](size_t) { return base::span<uint8_t>(); }),
              ValueIs(Eq(std::nullopt)));
}

TEST_F(PersistentCacheMockedBackendTest, FindReturnsBackendError) {
  CreateCache();
  // Jump through a hoop to pass a dynamic span as a matcher.
  EXPECT_CALL(*GetBackend(), Find(base::as_bytes(kKey.first(kKey.size())), _))
      .WillOnce(Return(base::unexpected(TransactionError::kTransient)));
  EXPECT_THAT(cache_->Find(base::as_bytes(kKey),
                           [](size_t) { return base::span<uint8_t>(); }),
              ErrorIs(TransactionError::kTransient));
}

TEST_F(PersistentCacheMockedBackendTest, InsertReturnsBackendError) {
  CreateCache();
  EXPECT_CALL(*GetBackend(), Insert(_, _, _))
      .WillOnce(Return(base::unexpected(TransactionError::kTransient)));
  EXPECT_THAT(
      cache_->Insert(base::as_bytes(kKey), base::byte_span_from_cstring("1")),
      ErrorIs(TransactionError::kTransient));
}

TEST_F(PersistentCacheMockedBackendTest, CacheInsertCallsBackendInsert) {
  CreateCache();
  // Jump through a hoop to pass a dynamic span as a matcher.
  EXPECT_CALL(*GetBackend(),
              Insert(base::as_bytes(kKey.first(kKey.size())), _, _));
  EXPECT_THAT(
      cache_->Insert(base::as_bytes(kKey), base::byte_span_from_cstring("1")),
      HasValue());
}

#if !BUILDFLAG(IS_FUCHSIA)

class PersistentCacheTest : public testing::Test,
                            public testing::WithParamInterface<BackendType> {
 protected:
  void SetUp() override {
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
    backend_storage_.emplace(Client::kTest, GetParam(), temp_dir_.GetPath());
  }

  // Returns the cache base name for a new cache and the new cache itself.
  std::pair<base::FilePath, std::unique_ptr<PersistentCache>> OpenCache(
      bool single_connection = false,
      bool journal_mode_wal = false) {
    auto [cache_name, pending_backend] =
        MakePendingBackend(single_connection, journal_mode_wal);
    ASSIGN_OR_RETURN(
        auto cache,
        PersistentCache::Bind(Client::kTest, *std::move(pending_backend)),
        [](TransactionError error)
            -> std::pair<base::FilePath, std::unique_ptr<PersistentCache>> {
          ADD_FAILURE() << "Failed to Bind PersistentCache";
          return {};
        });
    return {std::move(cache_name), std::move(cache)};
  }

  // Returns the cache base name for a new cache and a pending backend for it.
  std::pair<base::FilePath, std::optional<PendingBackend>> MakePendingBackend(
      bool single_connection,
      bool journal_mode_wal) {
    auto cache_name = base::FilePath::FromASCII(
        base::StrCat({"Cache", base::NumberToString(next_backend_index_++)}));
    auto pending_backend = backend_storage_->MakePendingBackend(
        cache_name, single_connection, journal_mode_wal);
    if (!pending_backend.has_value()) {
      ADD_FAILURE() << "Failed to make PendingBackend: "
                    << base::ToString(pending_backend.error());
      return {};
    }
    return {std::move(cache_name), *std::move(pending_backend)};
  }

  BackendStorage& backend_storage() { return *backend_storage_; }

 private:
  base::ScopedTempDir temp_dir_;
  std::optional<BackendStorage> backend_storage_;
  int next_backend_index_ = 0;
};

TEST_P(PersistentCacheTest, FindReturnsNullWhenEmpty) {
  auto [cache_name, cache] = OpenCache();
  EXPECT_THAT(cache->Find(base::as_bytes(kKey),
                          [](size_t) { return base::span<uint8_t>(); }),
              ValueIs(Eq(std::nullopt)));
}

TEST_P(PersistentCacheTest, FindReturnsValueWhenPresent) {
  auto [cache_name, cache] = OpenCache();
  for (int i = 0; i < 20; ++i) {
    std::string key = base::NumberToString(i);
    auto value = base::as_byte_span(key);

    EXPECT_THAT(FindEntry(*cache, base::as_byte_span(key)),
                ValueIs(Eq(std::nullopt)));

    EXPECT_THAT(cache->Insert(base::as_byte_span(key), value), HasValue());
    ASSERT_THAT(FindEntry(*cache, base::as_byte_span(key)),
                ValueIs(Optional(ContentEq(value))));
  }
}

TEST_P(PersistentCacheTest, EmptyValueIsStorable) {
  auto [cache_name, cache] = OpenCache();
  EXPECT_THAT(
      cache->Insert(base::as_bytes(kKey), base::byte_span_from_cstring("")),
      HasValue());
  ASSERT_THAT(FindEntry(*cache, base::as_bytes(kKey)),
              ValueIs(Optional(ContentEq(base::span<uint8_t>()))));
}

TEST_P(PersistentCacheTest, ValueContainingNullCharIsStorable) {
  auto [cache_name, cache] = OpenCache();
  constexpr std::array<std::uint8_t, 5> value_array{'\0', 'a', 'b', 'c', '\0'};
  const base::span<const std::uint8_t> value_span(value_array);
  CHECK_EQ(value_span.size(), value_array.size())
      << "All characters must be included in span";

  EXPECT_THAT(cache->Insert(base::as_bytes(kKey), value_span), HasValue());
  ASSERT_THAT(FindEntry(*cache, base::as_bytes(kKey)),
              ValueIs(Optional(ContentEq(value_span))));
}

TEST_P(PersistentCacheTest, ValueContainingInvalidUtf8IsStorable) {
  auto [cache_name, cache] = OpenCache();
  constexpr std::array<std::uint8_t, 4> value_array{0x20, 0x0F, 0xFF, 0xFF};
  const base::span<const std::uint8_t> value_span(value_array);
  CHECK(
      !base::IsStringUTF8(std::string(value_array.begin(), value_array.end())))
      << "Test needs invalid utf8";

  EXPECT_THAT(cache->Insert(base::as_bytes(kKey), value_span), HasValue());
  ASSERT_THAT(FindEntry(*cache, base::as_bytes(kKey)),
              ValueIs(Optional(ContentEq(value_span))));
}

TEST_P(PersistentCacheTest, OverwritingChangesValue) {
  auto [cache_name, cache] = OpenCache();
  EXPECT_THAT(
      cache->Insert(base::as_bytes(kKey), base::byte_span_from_cstring("1")),
      HasValue());
  EXPECT_THAT(
      cache->Insert(base::as_bytes(kKey), base::byte_span_from_cstring("2")),
      HasValue());

  ASSERT_THAT(FindEntry(*cache, base::as_bytes(kKey)),
              ValueIs(Optional(ContentEq(base::byte_span_from_cstring("2")))));
}

TEST_P(PersistentCacheTest, OverwritingChangesValueVaryingSizes) {
  auto [cache_name, cache] = OpenCache();
  EXPECT_THAT(
      cache->Insert(base::as_bytes(kKey), base::byte_span_from_cstring("1")),
      HasValue());
  EXPECT_THAT(cache->Insert(base::as_bytes(kKey),
                            base::as_byte_span(std::string(1024 * 7, 'b'))),
              HasValue());

  ASSERT_THAT(FindEntry(*cache, base::as_bytes(kKey)),
              ValueIs(Optional(
                  ContentEq(base::as_byte_span(std::string(1024 * 7, 'b'))))));
}

TEST_P(PersistentCacheTest, MetadataIsRetrievable) {
  EntryMetadata metadata{.input_signature =
                             base::Time::Now().InMillisecondsSinceUnixEpoch()};

  auto [cache_name, cache] = OpenCache();

  base::TimeDelta delta_since_epoch =
      base::Time::Now().ToDeltaSinceWindowsEpoch();

  EXPECT_THAT(cache->Insert(base::as_bytes(kKey),
                            base::byte_span_from_cstring("1"), metadata),
              HasValue());

  EXPECT_THAT(FindEntry(*cache, base::as_bytes(kKey)),
              ValueIs(Optional(Field(
                  &Entry::metadata,
                  AllOf(Field(&EntryMetadata::input_signature,
                              metadata.input_signature),
                        Field(&EntryMetadata::write_timestamp,
                              AllOf(Ge(delta_since_epoch.InMicroseconds()),
                                    // The test is supposed to time out before
                                    // it takes this long to insert a value.
                                    Le((delta_since_epoch + base::Seconds(30))
                                           .InMicroseconds()))))))));
}

TEST_P(PersistentCacheTest, OverwritingChangesMetadata) {
  EntryMetadata metadata{.input_signature =
                             base::Time::Now().InMillisecondsSinceUnixEpoch()};

  auto [cache_name, cache] = OpenCache();
  EXPECT_THAT(cache->Insert(base::as_bytes(kKey),
                            base::byte_span_from_cstring("1"), metadata),
              HasValue());

  EXPECT_THAT(FindEntry(*cache, base::as_bytes(kKey)),
              ValueIs(Optional(
                  Field(&Entry::metadata, Field(&EntryMetadata::input_signature,
                                                metadata.input_signature)))));

  EXPECT_THAT(cache->Insert(base::as_bytes(kKey),
                            base::byte_span_from_cstring("1"), EntryMetadata{}),
              HasValue());

  EXPECT_THAT(
      FindEntry(*cache, base::as_bytes(kKey)),
      ValueIs(Optional(
          Field(&Entry::metadata, Field(&EntryMetadata::input_signature, 0)))));
}

TEST_P(PersistentCacheTest, MultipleEphemeralCachesAreIndependent) {
  for (int i = 0; i < 3; ++i) {
    auto [cache_name, cache] = OpenCache();

    // `kKey` never inserted in this cache so not found.
    EXPECT_THAT(FindEntry(*cache, base::as_bytes(kKey)),
                ValueIs(Eq(std::nullopt)));

    EXPECT_THAT(
        cache->Insert(base::as_bytes(kKey), base::byte_span_from_cstring("1")),
        HasValue());

    // `kKey` now present.
    EXPECT_THAT(FindEntry(*cache, base::as_bytes(kKey)), HasValue());
  }
}

TEST_P(PersistentCacheTest, MultipleLiveCachesAreIndependent) {
  std::vector<std::unique_ptr<PersistentCache>> caches;
  for (int i = 0; i < 3; ++i) {
    auto [cache_name, new_cache] = OpenCache();
    caches.push_back(std::move(new_cache));
    std::unique_ptr<PersistentCache>& cache = caches.back();

    // `kKey` never inserted in this cache so not found.
    EXPECT_THAT(FindEntry(*cache, base::as_bytes(kKey)),
                ValueIs(Eq(std::nullopt)));

    EXPECT_THAT(
        cache->Insert(base::as_bytes(kKey), base::byte_span_from_cstring("1")),
        HasValue());
    // `kKey` now present.
    EXPECT_THAT(FindEntry(*cache, base::as_bytes(kKey)),
                ValueIs(Ne(std::nullopt)));
  }
}

TEST_P(PersistentCacheTest, EphemeralCachesSharingParamsShareData) {
  auto [cache_name, main_cache] = OpenCache();
  ASSERT_TRUE(main_cache);
  for (int i = 0; i < 3; ++i) {
    ASSERT_OK_AND_ASSIGN(
        auto pending_backend,
        backend_storage().ShareReadWriteConnection(cache_name, *main_cache));
    ASSERT_OK_AND_ASSIGN(
        auto cache,
        PersistentCache::Bind(Client::kTest, std::move(pending_backend)));
    ASSERT_TRUE(cache);

    // First run, setup.
    if (i == 0) {
      // `kKey` never inserted so not found.
      EXPECT_THAT(FindEntry(*cache, base::as_bytes(kKey)),
                  ValueIs(Eq(std::nullopt)));

      EXPECT_THAT(cache->Insert(base::as_bytes(kKey),
                                base::byte_span_from_cstring("1")),
                  HasValue());

      // `kKey` now present.
      EXPECT_THAT(FindEntry(*cache, base::as_bytes(kKey)),
                  ValueIs(Ne(std::nullopt)));
    } else {
      // `kKey` is present because data is shared.
      EXPECT_THAT(FindEntry(*cache, base::as_bytes(kKey)),
                  ValueIs(Ne(std::nullopt)));
    }
  }
}

TEST_P(PersistentCacheTest, LiveCachesSharingParamsShareData) {
  auto [cache_name, main_cache] = OpenCache();
  std::vector<std::unique_ptr<PersistentCache>> caches;

  for (int i = 0; i < 3; ++i) {
    ASSERT_OK_AND_ASSIGN(
        auto pending_backend,
        backend_storage().ShareReadWriteConnection(cache_name, *main_cache));
    std::unique_ptr<PersistentCache>& cache = caches.emplace_back(nullptr);
    ASSERT_OK_AND_ASSIGN(cache, PersistentCache::Bind(
                                    Client::kTest, std::move(pending_backend)));
    ASSERT_TRUE(cache);

    // First run, setup.
    if (i == 0) {
      // `kKey` never inserted so not found.
      EXPECT_THAT(FindEntry(*cache, base::as_bytes(kKey)),
                  ValueIs(Eq(std::nullopt)));

      EXPECT_THAT(cache->Insert(base::as_bytes(kKey),
                                base::byte_span_from_cstring("1")),
                  HasValue());

      // `kKey` now present.
      EXPECT_THAT(FindEntry(*cache, base::as_bytes(kKey)),
                  ValueIs(Ne(std::nullopt)));
    } else {
      EXPECT_THAT(FindEntry(*cache, base::as_bytes(kKey)),
                  ValueIs(Ne(std::nullopt)));
    }
  }
}

// Create an instance and share it for read-only access to others.
TEST_P(PersistentCacheTest, MultipleInstancesShareData) {
  // The main read-write instance.
  auto [cache_name, main_cache] = OpenCache();

  std::vector<std::unique_ptr<PersistentCache>> caches;
  for (int i = 0; i < 3; ++i) {
    // Export a read-only view to the main instance.
    ASSERT_OK_AND_ASSIGN(
        auto pending_backend,
        backend_storage().ShareReadOnlyConnection(cache_name, *main_cache));
    // Create a new instance that will read from the original.
    std::unique_ptr<PersistentCache>& ro_cache = caches.emplace_back(nullptr);
    ASSERT_OK_AND_ASSIGN(
        ro_cache,
        PersistentCache::Bind(Client::kTest, std::move(pending_backend)));
    ASSERT_TRUE(ro_cache);

    if (i == 0) {
      // The db is empty when the first client connects.
      EXPECT_THAT(FindEntry(*ro_cache, base::as_bytes(kKey)),
                  ValueIs(Eq(std::nullopt)));

      // Insert a value via the read-write instance.
      EXPECT_THAT(main_cache->Insert(base::as_bytes(kKey),
                                     base::byte_span_from_cstring("1")),
                  HasValue());

      // It should be there.
      EXPECT_THAT(FindEntry(*ro_cache, base::as_bytes(kKey)),
                  ValueIs(Ne(std::nullopt)));
    }

    // The new read-only client should see the value that was previously
    // inserted.
    EXPECT_THAT(FindEntry(*ro_cache, base::as_bytes(kKey)),
                ValueIs(Ne(std::nullopt)));
  }
}

// Create an instance and share it for read-write access to others.
TEST_P(PersistentCacheTest, MultipleInstancesCanWriteData) {
  static constexpr char kThisKeyPrefix[] = "thiskey-";
  static constexpr char kOtherKeyPrefix[] = "otherkey-";

  // The main read-write instance.
  auto [cache_name, main_cache] = OpenCache();

  std::vector<std::unique_ptr<PersistentCache>> caches;
  for (int i = 0; i < 3; ++i) {
    // Share a read-write view to the main instance.
    ASSERT_OK_AND_ASSIGN(
        auto pending_backend,
        backend_storage().ShareReadWriteConnection(cache_name, *main_cache));
    // Create a new instance that will read/write from/to the original.
    std::unique_ptr<PersistentCache>& rw_cache = caches.emplace_back(nullptr);
    ASSERT_OK_AND_ASSIGN(
        rw_cache,
        PersistentCache::Bind(Client::kTest, std::move(pending_backend)));
    ASSERT_TRUE(rw_cache);

    // This new cache has access to all previous values.
    for (int j = 0; j < i; ++j) {
      std::string value = base::NumberToString(j);

      std::string key = base::StrCat({kThisKeyPrefix, value});
      EXPECT_THAT(FindEntry(*rw_cache, base::as_byte_span(key)),
                  ValueIs(Ne(std::nullopt)));

      key = base::StrCat({kOtherKeyPrefix, value});
      EXPECT_THAT(FindEntry(*rw_cache, base::as_byte_span(key)),
                  ValueIs(Ne(std::nullopt)));
    }

    // A new value added from the original is seen here.
    std::string value = base::NumberToString(i);
    std::string other_key = base::StrCat({kOtherKeyPrefix, value});

    EXPECT_THAT(FindEntry(*main_cache, base::as_byte_span(other_key)),
                ValueIs(Eq(std::nullopt)));
    EXPECT_THAT(FindEntry(*rw_cache, base::as_byte_span(other_key)),
                ValueIs(Eq(std::nullopt)));

    EXPECT_THAT(
        main_cache->Insert(base::as_byte_span(base::as_byte_span(other_key)),
                           base::as_byte_span(value)),
        HasValue());

    EXPECT_THAT(FindEntry(*main_cache, base::as_byte_span(other_key)),
                ValueIs(Ne(std::nullopt)));
    EXPECT_THAT(FindEntry(*rw_cache, base::as_byte_span(other_key)),
                ValueIs(Ne(std::nullopt)));

    // A new value added here is seen in the original.
    std::string this_key = base::StrCat({kThisKeyPrefix, value});

    EXPECT_THAT(FindEntry(*main_cache, base::as_byte_span(this_key)),
                ValueIs(Eq(std::nullopt)));
    EXPECT_THAT(FindEntry(*rw_cache, base::as_byte_span(this_key)),
                ValueIs(Eq(std::nullopt)));

    EXPECT_THAT(
        rw_cache->Insert(base::as_byte_span(base::as_byte_span(this_key)),
                         base::as_byte_span(value)),
        HasValue());

    EXPECT_THAT(FindEntry(*main_cache, base::as_byte_span(this_key)),
                ValueIs(Ne(std::nullopt)));
    EXPECT_THAT(FindEntry(*rw_cache, base::as_byte_span(this_key)),
                ValueIs(Ne(std::nullopt)));
  }
}

TEST_P(PersistentCacheTest, ThreadSafeAccess) {
  base::test::TaskEnvironment env;

  // Create the cache and insert on this sequence.
  auto value = base::byte_span_from_cstring("1");
  auto [cache_name, main_cache] = OpenCache();
  EXPECT_THAT(main_cache->Insert(base::as_bytes(kKey), value), HasValue());

  // FindEntry() on ThreadPool. Result should be expected and there are no
  // sequence checkers tripped.
  base::test::TestFuture<base::expected<std::optional<Entry>, TransactionError>>
      future_entry;
  base::ThreadPool::PostTask(
      FROM_HERE, {base::MayBlock()},
      base::BindOnce(
          [](PersistentCache* cache,
             base::OnceCallback<void(
                 base::expected<std::optional<Entry>, TransactionError>)>
                 on_entry) {
            auto entry = FindEntry(*cache, base::as_bytes(kKey));
            std::move(on_entry).Run(std::move(entry));
          },
          main_cache.get(), future_entry.GetSequenceBoundCallback()));

  // Wait for result availability and check.
  ASSERT_OK_AND_ASSIGN(std::optional<Entry> entry, future_entry.Take());
  ASSERT_THAT(entry, Optional(ContentEq(value)));
}

TEST_P(PersistentCacheTest, MultipleLiveEntries) {
  auto [cache_name, cache] = OpenCache();
  absl::flat_hash_map<std::string, std::optional<Entry>> entries;

  for (size_t i = 0; i < 20; ++i) {
    std::string key = base::NumberToString(i);
    auto value = base::as_byte_span(key);
    EXPECT_THAT(cache->Insert(base::as_byte_span(key), value), HasValue());
    // Create an entry where the value is equal to the key.
    ASSERT_OK_AND_ASSIGN(entries[key],
                         FindEntry(*cache, base::as_byte_span(key)));
  }

  // Verify that entries have the expected content.
  for (auto& [key, entry] : entries) {
    ASSERT_THAT(entry, Optional(ContentEq(base::as_byte_span(key))));
  }
}

TEST_P(PersistentCacheTest, MultipleLiveEntriesWithVaryingLifetime) {
  static constexpr size_t kNumberOfEntries = 40;

  auto [cache_name, cache] = OpenCache();
  absl::flat_hash_map<std::string, std::optional<Entry>> entries;

  for (size_t i = 0; i < kNumberOfEntries; ++i) {
    std::string key = base::NumberToString(i);
    auto value = base::as_byte_span(key);
    EXPECT_THAT(cache->Insert(base::as_byte_span(key), value), HasValue());
    // Create an entry where the value is equal to the key.
    ASSERT_OK_AND_ASSIGN(entries[key],
                         FindEntry(*cache, base::as_byte_span(key)));

    // Every other iteration delete an entry that came before.
    if (i && i % 2 == 0) {
      entries.erase(base::NumberToString(i / 2));
    }
  }

  // Assert that some entries remain to be verified in the next loop.
  ASSERT_GE(entries.size(), kNumberOfEntries / 2);

  // Verify that entries have the expected content.
  for (auto& [key, entry] : entries) {
    ASSERT_THAT(entry, Optional(ContentEq(base::as_byte_span(key))));
  }
}

TEST_P(PersistentCacheTest, AbandonementDetected) {
  auto [cache_name, cache] = OpenCache();

  // Value is correctly inserted.
  EXPECT_THAT(cache->Insert(base::as_bytes(kKey),
                            base::byte_span_from_cstring("1"), EntryMetadata{}),
              HasValue());
  ASSERT_OK_AND_ASSIGN(auto entry, FindEntry(*cache, base::as_bytes(kKey)));
  EXPECT_NE(entry, std::nullopt);

  // Abandon cache, no further operations will succeed.
  EXPECT_EQ(cache->Abandon(), LockState::kNotHeld);

  // Calling FindEntry() is no longer successful.
  EXPECT_THAT(FindEntry(*cache, base::as_bytes(kKey)),
              ErrorIs(TransactionError::kConnectionError));

  // Calling Insert() is no longer successful.
  EXPECT_THAT(cache->Insert(base::as_bytes(kKey),
                            base::byte_span_from_cstring("1"), EntryMetadata{}),
              ErrorIs(TransactionError::kConnectionError));
}

TEST_P(PersistentCacheTest, RecoveryFromTransientError) {
  auto [cache_name, cache] = OpenCache();

  ASSERT_OK_AND_ASSIGN(
      auto pending_reader,
      backend_storage().ShareReadOnlyConnection(cache_name, *cache));

  // Baseline insert works.
  EXPECT_THAT(cache->Insert(base::as_bytes(kKey),
                            base::byte_span_from_cstring("1"), EntryMetadata{}),
              HasValue());

  // Lock the db file in shared mode.
  ASSERT_OK_AND_ASSIGN(auto reader_vfs_file_set,
                       sqlite_vfs::SqliteVfsFileSet::Bind(
                           sqlite_vfs::Client::kTest,
                           std::move(pending_reader.pending_file_set)));
  sqlite_vfs::SandboxedFile* reader_db_file =
      reader_vfs_file_set.GetSandboxedDbFile();
  reader_db_file->OnFileOpened(
      reader_db_file->TakeUnderlyingFile(sqlite_vfs::FileType::kMainDb));
  ASSERT_EQ(reader_db_file->Lock(SQLITE_LOCK_SHARED), SQLITE_OK);

  // Held lock causes transient error.
  EXPECT_THAT(cache->Insert(base::as_bytes(kKey),
                            base::byte_span_from_cstring("1"), EntryMetadata{}),
              ErrorIs(TransactionError::kTransient));

  // Unlock works.
  ASSERT_EQ(reader_db_file->Unlock(SQLITE_LOCK_NONE), SQLITE_OK);
  ASSERT_EQ(reader_db_file->LockModeForTesting(), SQLITE_LOCK_NONE);
  reader_db_file->Close();

  // Insert now succeeds.
  EXPECT_THAT(cache->Insert(base::as_bytes(kKey),
                            base::byte_span_from_cstring("1"), EntryMetadata{}),
              HasValue());
}

INSTANTIATE_TEST_SUITE_P(All,
                         PersistentCacheTest,
                         testing::Values(BackendType::kSqlite));
#endif

}  // namespace persistent_cache
