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

#include "chrome/browser/extensions/activity_log/database_string_table.h"

#include <stddef.h>
#include <stdint.h>

#include "base/files/scoped_temp_dir.h"
#include "base/strings/stringprintf.h"
#include "extensions/buildflags/buildflags.h"
#include "sql/database.h"
#include "sql/statement.h"
#include "sql/test/test_helpers.h"
#include "sql/transaction.h"
#include "testing/gtest/include/gtest/gtest.h"

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

namespace extensions {

class DatabaseStringTableTest : public testing::Test {
 protected:
  void SetUp() override {
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
    base::FilePath db_file = temp_dir_.GetPath().AppendASCII("StringTable.db");

    ASSERT_TRUE(db_.Open(db_file));
  }

  void TearDown() override { db_.Close(); }

  base::ScopedTempDir temp_dir_;
  sql::Database db_{sql::test::kTestTag};
};

// Check that initializing the database works.
TEST_F(DatabaseStringTableTest, Init) {
  DatabaseStringTable table("test");
  table.Initialize(&db_);
  ASSERT_TRUE(db_.DoesTableExist("test"));
  ASSERT_TRUE(db_.DoesIndexExist("test_index"));
}

// Insert a new mapping into the table, then verify the table contents.
TEST_F(DatabaseStringTableTest, Insert) {
  DatabaseStringTable table("test");
  table.Initialize(&db_);
  int64_t id;
  ASSERT_TRUE(table.StringToInt(&db_, "abc", &id));

  sql::Statement query(
      db_.GetUniqueStatement("SELECT id FROM test WHERE value = 'abc'"));
  ASSERT_TRUE(query.Step());
  int64_t raw_id = query.ColumnInt64(0);
  ASSERT_EQ(id, raw_id);
}

// Check that different strings are mapped to different values, and the same
// string is mapped to the same value repeatably.
TEST_F(DatabaseStringTableTest, InsertMultiple) {
  DatabaseStringTable table("test");
  table.Initialize(&db_);

  int64_t id1;
  int64_t id2;
  ASSERT_TRUE(table.StringToInt(&db_, "string1", &id1));
  ASSERT_TRUE(table.StringToInt(&db_, "string2", &id2));
  ASSERT_NE(id1, id2);

  int64_t id1a;
  ASSERT_TRUE(table.StringToInt(&db_, "string1", &id1a));
  ASSERT_EQ(id1, id1a);
}

// Check that values can be read back from the database even after the
// in-memory cache is cleared.
TEST_F(DatabaseStringTableTest, CacheCleared) {
  DatabaseStringTable table("test");
  table.Initialize(&db_);

  int64_t id1;
  ASSERT_TRUE(table.StringToInt(&db_, "string1", &id1));

  table.ClearCache();

  int64_t id2;
  ASSERT_TRUE(table.StringToInt(&db_, "string1", &id2));
  ASSERT_EQ(id1, id2);
}

// Check that direct database modifications are picked up after the cache is
// cleared.
TEST_F(DatabaseStringTableTest, DatabaseModified) {
  DatabaseStringTable table("test");
  table.Initialize(&db_);

  int64_t id1;
  ASSERT_TRUE(table.StringToInt(&db_, "modified", &id1));

  ASSERT_TRUE(
      db_.Execute("UPDATE test SET id = id + 1 WHERE value = 'modified'"));

  int64_t id2;
  ASSERT_TRUE(table.StringToInt(&db_, "modified", &id2));
  ASSERT_EQ(id1, id2);

  table.ClearCache();

  int64_t id3;
  ASSERT_TRUE(table.StringToInt(&db_, "modified", &id3));
  ASSERT_EQ(id1 + 1, id3);
}

// Check that looking up an unknown id returns an error.
TEST_F(DatabaseStringTableTest, BadLookup) {
  DatabaseStringTable table("test");
  table.Initialize(&db_);
  std::string value;
  ASSERT_FALSE(table.IntToString(&db_, 1, &value));
}

// Check looking up an inserted value, both cached and not cached.
TEST_F(DatabaseStringTableTest, Lookup) {
  DatabaseStringTable table("test");
  table.Initialize(&db_);
  int64_t id;
  ASSERT_TRUE(table.StringToInt(&db_, "abc", &id));

  std::string value;
  ASSERT_TRUE(table.IntToString(&db_, id, &value));
  ASSERT_EQ("abc", value);

  table.ClearCache();
  value = "";
  ASSERT_TRUE(table.IntToString(&db_, id, &value));
  ASSERT_EQ("abc", value);
}

// Check that the in-memory cache for the string table does not become too
// large, even if many items are inserted.
TEST_F(DatabaseStringTableTest, Prune) {
  DatabaseStringTable table("size_test");
  table.Initialize(&db_);

  // Wrap the lookups in a transaction to improve performance.
  sql::Transaction transaction(&db_);

  ASSERT_TRUE(transaction.Begin());
  for (int i = 0; i < 2000; i++) {
    int64_t id;
    ASSERT_TRUE(table.StringToInt(&db_, base::StringPrintf("value-%d", i),
                                  &id));
  }
  transaction.Commit();

  // The maximum size below should correspond to kMaximumCacheSize in
  // database_string_table.cc, with a small amount of additional slop (an entry
  // might be inserted after doing the pruning).
  ASSERT_LE(table.id_to_value_.size(), 1005U);
  ASSERT_LE(table.value_to_id_.size(), 1005U);
}

}  // namespace extensions
