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

#include "content/browser/indexed_db/file_path_util.h"

#include <string>

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "components/services/storage/public/cpp/buckets/bucket_locator.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace content::indexed_db {

TEST(FilePathUtilTest, GetSqliteDbDirectory) {
  // First party, default bucket: need to append an origin.
  storage::BucketLocator bucket_locator(
      storage::BucketId::FromUnsafeValue(1),
      blink::StorageKey::CreateFirstParty(
          url::Origin::Create(GURL("https://example.com/"))),
      /*is_default=*/true);
  EXPECT_EQ(base::FilePath::FromASCII("IndexedDB")
                .Append(GetSqliteDbDirectory(bucket_locator)),
            base::FilePath::FromASCII("IndexedDB")
                .AppendASCII("https_example.com_0"));

  // Non-default bucket: no origin, since the base path includes the bucket ID.
  storage::BucketLocator bucket_locator_non_default(
      storage::BucketId::FromUnsafeValue(2),
      blink::StorageKey::CreateFirstParty(
          url::Origin::Create(GURL("https://example.com/"))),
      /*is_default=*/false);
  EXPECT_EQ(base::FilePath::FromASCII("2")
                .AppendASCII("IndexedDB")
                .Append(GetSqliteDbDirectory(bucket_locator_non_default)),
            base::FilePath::FromASCII("2").AppendASCII("IndexedDB"));

  // Third party bucket: no origin, since the base path includes the bucket ID.
  storage::BucketLocator bucket_locator_third_party(
      storage::BucketId::FromUnsafeValue(3),
      blink::StorageKey::Create(
          url::Origin::Create(GURL("https://example.com/")),
          net::SchemefulSite(GURL("https://foo.com/")),
          blink::mojom::AncestorChainBit::kCrossSite,
          /*third_party_partitioning_allowed=*/true),
      /*is_default=*/true);
  EXPECT_EQ(base::FilePath::FromASCII("3")
                .AppendASCII("IndexedDB")
                .Append(GetSqliteDbDirectory(bucket_locator_third_party)),
            base::FilePath::FromASCII("3").AppendASCII("IndexedDB"));
}

TEST(FilePathUtilTest, DatabaseNameToFileName) {
  struct {
    std::u16string dom_string_input;
    base::FilePath::StringType expected_file_name;
  } test_cases[] = {
      {u"basic_name",
       FILE_PATH_LITERAL(
           "6ZV5AGIHZJWHPZ2UQ4IQUJJWBTQPYXGGRWTDQLBFA7AK4XTIXGCQ")},

      // Output is case sensitive.
      {u"BASIC_name",
       FILE_PATH_LITERAL(
           "PMOE3RT3OU4BAFK6RL7E4MHR7N5Q65X2POQZUH3CRZCE6IXHRXTA")},

      // Empty string.
      {u"", FILE_PATH_LITERAL("0")},

      // Characters that aren't valid filename characters.
      {u"invalid/name",
       FILE_PATH_LITERAL(
           "EYZCOEWM37YXW6VHDSELIIN7IGHYYHWW2FHMD6PJJO7ROTVYRBRQ")},
      {u"invalid\\name",
       FILE_PATH_LITERAL(
           "J46FRRIBKKJPJYXWUUGN252JKBAI2HMZAP4TAU2NYPAM36S74OJQ")},
      {u"invalid name",
       FILE_PATH_LITERAL(
           "IGMS2QMW3GNPSJXWTHHFO5VSHBL6IMGXYDD5JSKAZSXH44T4SO6A")},
      {u"sneaky.text",
       FILE_PATH_LITERAL(
           "IE4ONQ2VLLMXKWXWB6B4HBN3JICQDPQBXBDYUUPBJX6UU26VD2SQ")},

      // Valid UTF16.
      {u"\x4f60\x597d ",
       FILE_PATH_LITERAL(
           "NDE2443GY5Z36EMF2LPWR54H47YOVZ3EEF4V5J4JIAXO4O2RCS5A")},

      // Invalid UTF16. The first character is a truncated UTF-16 character.
      {u"\xd800\x597d",
       FILE_PATH_LITERAL(
           "VYO3RQVZ43IVZ3MZLKNC7BOZULNVR2S4EPLOBM527U4WG5MISGZQ")},

      // Long string with invalid characters.
      {u"too_long_name_too_long_name_too_long_name_too_long_name\xd800\x597d",
       FILE_PATH_LITERAL(
           "IDYIO2S422KGRTC2OQ24OSGW3HSSSF6RWM3O64SKMLFIPZ5ICCZQ")},
  };

  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  const base::FilePath& dir = temp_dir.GetPath();
  std::set<base::FilePath> expected_files;
  std::set<base::FilePath> enumerated_files;

  for (const auto& test_case : test_cases) {
    // Encode it.
    base::FilePath file_name =
        DatabaseNameToFileName(test_case.dom_string_input);
    EXPECT_TRUE(!file_name.IsAbsolute());
    EXPECT_EQ(file_name.BaseName(), file_name);
    EXPECT_EQ(file_name.value(), test_case.expected_file_name);
    expected_files.insert(file_name);

    // Write the file in a directory so we can enumerate it later.
    ASSERT_TRUE(base::WriteFile(dir.Append(file_name),
                                base::byte_span_from_cstring("42")));
    // Also write another file which is generated by SQLite.
    ASSERT_TRUE(base::WriteFile(
        dir.Append(file_name).InsertBeforeExtensionASCII("-wal"),
        base::byte_span_from_cstring("42")));
  }

  EnumerateDatabasesInDirectory(dir, [&](const base::FilePath& path) {
    enumerated_files.insert(path.BaseName());
  });
  EXPECT_EQ(expected_files, enumerated_files);
}

}  // namespace content::indexed_db
