// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stdint.h>

#include <map>

#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/test/test_simple_task_runner.h"
#include "base/threading/thread.h"
#include "content/browser/browser_thread_impl.h"
#include "content/browser/indexed_db/indexed_db_context_impl.h"
#include "content/browser/indexed_db/indexed_db_quota_client.h"
#include "content/browser/quota/mock_quota_manager.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "storage/common/database/database_identifier.h"
#include "testing/gtest/include/gtest/gtest.h"

// Declared to shorten the line lengths.
static const storage::StorageType kTemp = storage::kStorageTypeTemporary;
static const storage::StorageType kPerm = storage::kStorageTypePersistent;

namespace content {

// Base class for our test fixtures.
class IndexedDBQuotaClientTest : public testing::Test {
 public:
  const GURL kOriginA;
  const GURL kOriginB;
  const GURL kOriginOther;

  IndexedDBQuotaClientTest()
      : kOriginA("http://host"),
        kOriginB("http://host:8000"),
        kOriginOther("http://other"),
        usage_(0),
        task_runner_(new base::TestSimpleTaskRunner),
        weak_factory_(this) {
    browser_context_.reset(new TestBrowserContext());

    scoped_refptr<storage::QuotaManager> quota_manager =
        new MockQuotaManager(false /*in_memory*/, browser_context_->GetPath(),
                             base::MessageLoop::current()->task_runner(),
                             base::MessageLoop::current()->task_runner(),
                             browser_context_->GetSpecialStoragePolicy());

    idb_context_ =
        new IndexedDBContextImpl(browser_context_->GetPath(),
                                 browser_context_->GetSpecialStoragePolicy(),
                                 quota_manager->proxy(),
                                 task_runner_.get());
    base::MessageLoop::current()->RunUntilIdle();
    setup_temp_dir();
  }

  void FlushIndexedDBTaskRunner() { task_runner_->RunUntilIdle(); }

  void setup_temp_dir() {
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
    base::FilePath indexeddb_dir =
        temp_dir_.path().Append(IndexedDBContextImpl::kIndexedDBDirectory);
    ASSERT_TRUE(base::CreateDirectory(indexeddb_dir));
    idb_context()->set_data_path_for_testing(indexeddb_dir);
  }

  ~IndexedDBQuotaClientTest() override {
    FlushIndexedDBTaskRunner();
    idb_context_ = NULL;
    browser_context_.reset();
    base::MessageLoop::current()->RunUntilIdle();
  }

  int64_t GetOriginUsage(storage::QuotaClient* client,
                         const GURL& origin,
                         storage::StorageType type) {
    usage_ = -1;
    client->GetOriginUsage(
        origin,
        type,
        base::Bind(&IndexedDBQuotaClientTest::OnGetOriginUsageComplete,
                   weak_factory_.GetWeakPtr()));
    FlushIndexedDBTaskRunner();
    base::MessageLoop::current()->RunUntilIdle();
    EXPECT_GT(usage_, -1);
    return usage_;
  }

  const std::set<GURL>& GetOriginsForType(storage::QuotaClient* client,
                                          storage::StorageType type) {
    origins_.clear();
    client->GetOriginsForType(
        type,
        base::Bind(&IndexedDBQuotaClientTest::OnGetOriginsComplete,
                   weak_factory_.GetWeakPtr()));
    FlushIndexedDBTaskRunner();
    base::MessageLoop::current()->RunUntilIdle();
    return origins_;
  }

  const std::set<GURL>& GetOriginsForHost(storage::QuotaClient* client,
                                          storage::StorageType type,
                                          const std::string& host) {
    origins_.clear();
    client->GetOriginsForHost(
        type,
        host,
        base::Bind(&IndexedDBQuotaClientTest::OnGetOriginsComplete,
                   weak_factory_.GetWeakPtr()));
    FlushIndexedDBTaskRunner();
    base::MessageLoop::current()->RunUntilIdle();
    return origins_;
  }

  storage::QuotaStatusCode DeleteOrigin(storage::QuotaClient* client,
                                        const GURL& origin_url) {
    delete_status_ = storage::kQuotaStatusUnknown;
    client->DeleteOriginData(
        origin_url,
        kTemp,
        base::Bind(&IndexedDBQuotaClientTest::OnDeleteOriginComplete,
                   weak_factory_.GetWeakPtr()));
    FlushIndexedDBTaskRunner();
    base::MessageLoop::current()->RunUntilIdle();
    return delete_status_;
  }

  IndexedDBContextImpl* idb_context() { return idb_context_.get(); }

  void SetFileSizeTo(const base::FilePath& path, int size) {
    std::string junk(size, 'a');
    ASSERT_EQ(size, base::WriteFile(path, junk.c_str(), size));
  }

  void AddFakeIndexedDB(const GURL& origin, int size) {
    base::FilePath file_path_origin = idb_context()->GetFilePathForTesting(
        storage::GetIdentifierFromOrigin(origin));
    if (!base::CreateDirectory(file_path_origin)) {
      LOG(ERROR) << "failed to base::CreateDirectory "
                 << file_path_origin.value();
    }
    file_path_origin = file_path_origin.Append(FILE_PATH_LITERAL("fake_file"));
    SetFileSizeTo(file_path_origin, size);
    idb_context()->ResetCaches();
  }

 private:
  void OnGetOriginUsageComplete(int64_t usage) { usage_ = usage; }

  void OnGetOriginsComplete(const std::set<GURL>& origins) {
    origins_ = origins;
  }

  void OnDeleteOriginComplete(storage::QuotaStatusCode code) {
    delete_status_ = code;
  }

  base::ScopedTempDir temp_dir_;
  int64_t usage_;
  std::set<GURL> origins_;
  scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
  scoped_refptr<IndexedDBContextImpl> idb_context_;
  content::TestBrowserThreadBundle thread_bundle_;
  scoped_ptr<TestBrowserContext> browser_context_;
  storage::QuotaStatusCode delete_status_;
  base::WeakPtrFactory<IndexedDBQuotaClientTest> weak_factory_;

  DISALLOW_COPY_AND_ASSIGN(IndexedDBQuotaClientTest);
};

TEST_F(IndexedDBQuotaClientTest, GetOriginUsage) {
  IndexedDBQuotaClient client(idb_context());

  AddFakeIndexedDB(kOriginA, 6);
  AddFakeIndexedDB(kOriginB, 3);
  EXPECT_EQ(6, GetOriginUsage(&client, kOriginA, kTemp));
  EXPECT_EQ(0, GetOriginUsage(&client, kOriginA, kPerm));
  EXPECT_EQ(3, GetOriginUsage(&client, kOriginB, kTemp));
  EXPECT_EQ(0, GetOriginUsage(&client, kOriginB, kPerm));

  AddFakeIndexedDB(kOriginA, 1000);
  EXPECT_EQ(1000, GetOriginUsage(&client, kOriginA, kTemp));
  EXPECT_EQ(0, GetOriginUsage(&client, kOriginA, kPerm));
  EXPECT_EQ(3, GetOriginUsage(&client, kOriginB, kTemp));
  EXPECT_EQ(0, GetOriginUsage(&client, kOriginB, kPerm));
}

TEST_F(IndexedDBQuotaClientTest, GetOriginsForHost) {
  IndexedDBQuotaClient client(idb_context());

  EXPECT_EQ(kOriginA.host(), kOriginB.host());
  EXPECT_NE(kOriginA.host(), kOriginOther.host());

  std::set<GURL> origins = GetOriginsForHost(&client, kTemp, kOriginA.host());
  EXPECT_TRUE(origins.empty());

  AddFakeIndexedDB(kOriginA, 1000);
  origins = GetOriginsForHost(&client, kTemp, kOriginA.host());
  EXPECT_EQ(origins.size(), 1ul);
  EXPECT_TRUE(origins.find(kOriginA) != origins.end());

  AddFakeIndexedDB(kOriginB, 1000);
  origins = GetOriginsForHost(&client, kTemp, kOriginA.host());
  EXPECT_EQ(origins.size(), 2ul);
  EXPECT_TRUE(origins.find(kOriginA) != origins.end());
  EXPECT_TRUE(origins.find(kOriginB) != origins.end());

  EXPECT_TRUE(GetOriginsForHost(&client, kPerm, kOriginA.host()).empty());
  EXPECT_TRUE(GetOriginsForHost(&client, kTemp, kOriginOther.host()).empty());
}

TEST_F(IndexedDBQuotaClientTest, GetOriginsForType) {
  IndexedDBQuotaClient client(idb_context());

  EXPECT_TRUE(GetOriginsForType(&client, kTemp).empty());
  EXPECT_TRUE(GetOriginsForType(&client, kPerm).empty());

  AddFakeIndexedDB(kOriginA, 1000);
  std::set<GURL> origins = GetOriginsForType(&client, kTemp);
  EXPECT_EQ(origins.size(), 1ul);
  EXPECT_TRUE(origins.find(kOriginA) != origins.end());

  EXPECT_TRUE(GetOriginsForType(&client, kPerm).empty());
}

TEST_F(IndexedDBQuotaClientTest, DeleteOrigin) {
  IndexedDBQuotaClient client(idb_context());

  AddFakeIndexedDB(kOriginA, 1000);
  AddFakeIndexedDB(kOriginB, 50);
  EXPECT_EQ(1000, GetOriginUsage(&client, kOriginA, kTemp));
  EXPECT_EQ(50, GetOriginUsage(&client, kOriginB, kTemp));

  storage::QuotaStatusCode delete_status = DeleteOrigin(&client, kOriginA);
  EXPECT_EQ(storage::kQuotaStatusOk, delete_status);
  EXPECT_EQ(0, GetOriginUsage(&client, kOriginA, kTemp));
  EXPECT_EQ(50, GetOriginUsage(&client, kOriginB, kTemp));
}

}  // namespace content
