// Copyright 2016 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 "net/nqe/network_qualities_prefs_manager.h"

#include <algorithm>
#include <map>
#include <memory>

#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/threading/thread_checker.h"
#include "base/values.h"
#include "net/base/network_change_notifier.h"
#include "net/nqe/effective_connection_type.h"
#include "net/nqe/network_id.h"
#include "net/nqe/network_quality_estimator_test_util.h"
#include "net/nqe/network_quality_store.h"
#include "net/test/test_with_scoped_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace net {

namespace {

class TestPrefDelegate : public NetworkQualitiesPrefsManager::PrefDelegate {
 public:
  TestPrefDelegate()
      : write_count_(0), read_count_(0), value_(new base::DictionaryValue) {}

  ~TestPrefDelegate() override {
    DCHECK(thread_checker_.CalledOnValidThread());
    value_->Clear();
    EXPECT_EQ(0U, value_->size());
  }

  void SetDictionaryValue(const base::DictionaryValue& value) override {
    DCHECK(thread_checker_.CalledOnValidThread());

    write_count_++;
    value_.reset(value.DeepCopy());
    ASSERT_EQ(value.size(), value_->size());
  }

  std::unique_ptr<base::DictionaryValue> GetDictionaryValue() override {
    DCHECK(thread_checker_.CalledOnValidThread());

    read_count_++;
    return value_->CreateDeepCopy();
  }

  size_t write_count() const {
    DCHECK(thread_checker_.CalledOnValidThread());
    return write_count_;
  }

  size_t read_count() const {
    DCHECK(thread_checker_.CalledOnValidThread());
    return read_count_;
  }

 private:
  // Number of times prefs were written and read, respectively..
  size_t write_count_;
  size_t read_count_;

  // Current value of the prefs.
  std::unique_ptr<base::DictionaryValue> value_;

  base::ThreadChecker thread_checker_;

  DISALLOW_COPY_AND_ASSIGN(TestPrefDelegate);
};

using NetworkQualitiesPrefManager = TestWithScopedTaskEnvironment;

TEST_F(NetworkQualitiesPrefManager, Write) {
  // Force set the ECT to Slow 2G so that the ECT does not match the default
  // ECT for the current connection type. This forces the prefs to be written
  // for the current connection.
  std::map<std::string, std::string> variation_params;
  variation_params["force_effective_connection_type"] = "Slow-2G";
  TestNetworkQualityEstimator estimator(variation_params);

  std::unique_ptr<TestPrefDelegate> prefs_delegate(new TestPrefDelegate());
  TestPrefDelegate* prefs_delegate_ptr = prefs_delegate.get();

  NetworkQualitiesPrefsManager manager(std::move(prefs_delegate));
  manager.InitializeOnNetworkThread(&estimator);
  base::RunLoop().RunUntilIdle();

  // Prefs must be read at when NetworkQualitiesPrefsManager is constructed.
  EXPECT_EQ(1u, prefs_delegate_ptr->read_count());

  estimator.SimulateNetworkChange(
      NetworkChangeNotifier::ConnectionType::CONNECTION_UNKNOWN, "test");
  EXPECT_EQ(1u, prefs_delegate_ptr->write_count());
  // Network quality generated from the default observation must be written.
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(3u, prefs_delegate_ptr->write_count());

  estimator.set_recent_effective_connection_type(EFFECTIVE_CONNECTION_TYPE_2G);
  // Run a request so that effective connection type is recomputed, and
  // observers are notified of change in the network quality.
  estimator.RunOneRequest();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(4u, prefs_delegate_ptr->write_count());

  estimator.set_recent_effective_connection_type(EFFECTIVE_CONNECTION_TYPE_3G);
  // Run a request so that effective connection type is recomputed, and
  // observers are notified of change in the network quality..
  estimator.RunOneRequest();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(5u, prefs_delegate_ptr->write_count());

  // Prefs should not be read again.
  EXPECT_EQ(1u, prefs_delegate_ptr->read_count());

  manager.ShutdownOnPrefSequence();
}

TEST_F(NetworkQualitiesPrefManager, WriteWhenMatchingExpectedECT) {
  // Force set the ECT to Slow 2G so that the ECT does not match the default
  // ECT for the current connection type. This forces the prefs to be written
  // for the current connection.
  std::map<std::string, std::string> variation_params;
  variation_params["force_effective_connection_type"] = "Slow-2G";
  TestNetworkQualityEstimator estimator(variation_params);

  std::unique_ptr<TestPrefDelegate> prefs_delegate(new TestPrefDelegate());
  TestPrefDelegate* prefs_delegate_ptr = prefs_delegate.get();

  NetworkQualitiesPrefsManager manager(std::move(prefs_delegate));
  manager.InitializeOnNetworkThread(&estimator);
  base::RunLoop().RunUntilIdle();

  // Prefs must be read at when NetworkQualitiesPrefsManager is constructed.
  EXPECT_EQ(1u, prefs_delegate_ptr->read_count());

  const nqe::internal::NetworkID network_id(
      NetworkChangeNotifier::ConnectionType::CONNECTION_4G, "test", INT32_MIN);

  estimator.SimulateNetworkChange(network_id.type, network_id.id);
  EXPECT_EQ(1u, prefs_delegate_ptr->write_count());
  // Network quality generated from the default observation must be written.
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(3u, prefs_delegate_ptr->write_count());

  estimator.set_recent_effective_connection_type(EFFECTIVE_CONNECTION_TYPE_2G);
  // Run a request so that effective connection type is recomputed, and
  // observers are notified of change in the network quality.
  estimator.RunOneRequest();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(4u, prefs_delegate_ptr->write_count());

  estimator.set_recent_effective_connection_type(EFFECTIVE_CONNECTION_TYPE_3G);
  // Run a request so that effective connection type is recomputed, and
  // observers are notified of change in the network quality..
  estimator.RunOneRequest();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(5u, prefs_delegate_ptr->write_count());

  // Prefs should not be read again.
  EXPECT_EQ(1u, prefs_delegate_ptr->read_count());

  EXPECT_EQ(2u, manager.ForceReadPrefsForTesting().size());
  EXPECT_EQ(EFFECTIVE_CONNECTION_TYPE_3G,
            manager.ForceReadPrefsForTesting()
                .find(network_id)
                ->second.effective_connection_type());

  estimator.set_recent_effective_connection_type(EFFECTIVE_CONNECTION_TYPE_4G);
  estimator.RunOneRequest();
  base::RunLoop().RunUntilIdle();

  // Network Quality should be persisted to disk even if it matches the typical
  // quality of the network. See crbug.com/890859.
  EXPECT_EQ(2u, manager.ForceReadPrefsForTesting().size());
  EXPECT_EQ(1u, manager.ForceReadPrefsForTesting().count(network_id));
  EXPECT_EQ(6u, prefs_delegate_ptr->write_count());

  manager.ShutdownOnPrefSequence();
}

TEST_F(NetworkQualitiesPrefManager, WriteAndReadWithMultipleNetworkIDs) {
  static const size_t kMaxCacheSize = 20u;

  // Force set the ECT to Slow 2G so that the ECT does not match the default
  // ECT for the current connection type. This forces the prefs to be written
  // for the current connection.
  std::map<std::string, std::string> variation_params;
  variation_params["force_effective_connection_type"] = "Slow-2G";
  TestNetworkQualityEstimator estimator(variation_params);

  std::unique_ptr<TestPrefDelegate> prefs_delegate(new TestPrefDelegate());

  NetworkQualitiesPrefsManager manager(std::move(prefs_delegate));
  manager.InitializeOnNetworkThread(&estimator);
  base::RunLoop().RunUntilIdle();

  estimator.SimulateNetworkChange(
      NetworkChangeNotifier::ConnectionType::CONNECTION_2G, "test");

  EXPECT_EQ(1u, manager.ForceReadPrefsForTesting().size());

  estimator.set_recent_effective_connection_type(
      EFFECTIVE_CONNECTION_TYPE_SLOW_2G);
  // Run a request so that effective connection type is recomputed, and
  // observers are notified of change in the network quality.
  estimator.RunOneRequest();
  base::RunLoop().RunUntilIdle();
  // Verify that the observer was notified, and the updated network quality was
  // written to the prefs.
  EXPECT_EQ(2u, manager.ForceReadPrefsForTesting().size());

  // Change the network ID.
  for (size_t i = 0; i < kMaxCacheSize; ++i) {
    estimator.SimulateNetworkChange(
        NetworkChangeNotifier::ConnectionType::CONNECTION_2G,
        "test" + base::IntToString(i));

    estimator.RunOneRequest();
    base::RunLoop().RunUntilIdle();

    EXPECT_EQ(std::min(i + 3, kMaxCacheSize),
              manager.ForceReadPrefsForTesting().size());
  }

  std::map<nqe::internal::NetworkID, nqe::internal::CachedNetworkQuality>
      read_prefs = manager.ForceReadPrefsForTesting();

  // Verify the contents of the prefs.
  size_t count_2g_entries = 0;
  for (std::map<nqe::internal::NetworkID,
                nqe::internal::CachedNetworkQuality>::const_iterator it =
           read_prefs.begin();
       it != read_prefs.end(); ++it) {
    if (it->first.type ==
        NetworkChangeNotifier::ConnectionType::CONNECTION_UNKNOWN) {
      continue;
    }
    EXPECT_EQ(0u, it->first.id.find("test", 0u));
    EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_2G,
              it->first.type);
    EXPECT_EQ(EFFECTIVE_CONNECTION_TYPE_SLOW_2G,
              it->second.effective_connection_type());
    ++count_2g_entries;
  }

  // At most one entry should be for the network with connection type
  // NetworkChangeNotifier::ConnectionType::CONNECTION_UNKNOWN.
  EXPECT_LE(kMaxCacheSize - 1, count_2g_entries);

  base::HistogramTester histogram_tester;
  estimator.OnPrefsRead(read_prefs);
  histogram_tester.ExpectUniqueSample("NQE.Prefs.ReadSize", kMaxCacheSize, 1);

  manager.ShutdownOnPrefSequence();
}

// Verifies that the prefs are cleared correctly.
TEST_F(NetworkQualitiesPrefManager, ClearPrefs) {
  // Force set the ECT to Slow 2G so that the ECT does not match the default
  // ECT for the current connection type. This forces the prefs to be written
  // for the current connection.
  std::map<std::string, std::string> variation_params;
  variation_params["force_effective_connection_type"] = "Slow-2G";
  TestNetworkQualityEstimator estimator(variation_params);

  std::unique_ptr<TestPrefDelegate> prefs_delegate(new TestPrefDelegate());

  NetworkQualitiesPrefsManager manager(std::move(prefs_delegate));
  manager.InitializeOnNetworkThread(&estimator);
  base::RunLoop().RunUntilIdle();

  estimator.SimulateNetworkChange(
      NetworkChangeNotifier::ConnectionType::CONNECTION_UNKNOWN, "test");

  EXPECT_EQ(1u, manager.ForceReadPrefsForTesting().size());

  estimator.set_recent_effective_connection_type(
      EFFECTIVE_CONNECTION_TYPE_SLOW_2G);
  // Run a request so that effective connection type is recomputed, and
  // observers are notified of change in the network quality.
  estimator.RunOneRequest();
  base::RunLoop().RunUntilIdle();
  // Verify that the observer was notified, and the updated network quality was
  // written to the prefs.
  EXPECT_EQ(2u, manager.ForceReadPrefsForTesting().size());

  // Prefs must be completely cleared.
  manager.ClearPrefs();
  EXPECT_EQ(0u, manager.ForceReadPrefsForTesting().size());
  estimator.set_recent_effective_connection_type(EFFECTIVE_CONNECTION_TYPE_2G);
  // Run a request so that effective connection type is recomputed, and
  // observers are notified of change in the network quality.
  estimator.RunOneRequest();
  base::RunLoop().RunUntilIdle();
  // Verify that the observer was notified, and the updated network quality was
  // written to the prefs.
  EXPECT_EQ(1u, manager.ForceReadPrefsForTesting().size());
  manager.ShutdownOnPrefSequence();
}

}  // namespace

}  // namespace net
