chromium/chrome/browser/ash/net/network_diagnostics/dns_latency_routine_unittest.cc

// Copyright 2020 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/ash/net/network_diagnostics/dns_latency_routine.h"

#include "base/memory/raw_ptr.h"
#include "base/test/test_future.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
#include "chrome/browser/ash/net/network_diagnostics/fake_network_context.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/session_manager/core/session_manager.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"

namespace ash::network_diagnostics {

namespace {

namespace mojom = ::chromeos::network_diagnostics::mojom;

const int kFakePortNumber = 1234;
const char kFakeTestProfile[] = "test";
const base::TimeDelta kSuccessfulDnsResolutionDelayMs = base::Milliseconds(100);
const base::TimeDelta kSlightlyAboveThresholdDelayMs = base::Milliseconds(450);
const base::TimeDelta kSignificantlyAboveThresholdDelayMs =
    base::Milliseconds(550);

class FakeTickClock : public base::TickClock {
 public:
  // The |dns_resolution_delay| fakes the duration of a DNS resolution.
  explicit FakeTickClock(
      const base::TimeDelta& dns_resolution_delay = base::TimeDelta())
      : dns_resolution_delay_(dns_resolution_delay) {}

  ~FakeTickClock() override = default;

  base::TimeTicks NowTicks() const override {
    base::TimeTicks current = current_time_;
    // Advance the current time by |dns_resolution_delay_| so that each
    // NowTicks() invocation will have this delay. This allows tests to mimic
    // realistic time conditions.
    current_time_ = current_time_ + dns_resolution_delay_;
    return current;
  }

 private:
  mutable base::TimeTicks current_time_ = base::TimeTicks::Now();
  const base::TimeDelta dns_resolution_delay_;
};

net::IPEndPoint FakeIPAddress() {
  return net::IPEndPoint(net::IPAddress::IPv4Localhost(), kFakePortNumber);
}

}  // namespace

class DnsLatencyRoutineTest : public ::testing::Test {
 public:
  DnsLatencyRoutineTest()
      : profile_manager_(TestingBrowserProcess::GetGlobal()) {
    session_manager::SessionManager::Get()->SetSessionState(
        session_manager::SessionState::LOGIN_PRIMARY);
  }

  DnsLatencyRoutineTest(const DnsLatencyRoutineTest&) = delete;
  DnsLatencyRoutineTest& operator=(const DnsLatencyRoutineTest&) = delete;

  void SetUpFakeProperties(
      std::unique_ptr<FakeNetworkContext::DnsResult> fake_dns_result,
      const base::TimeDelta response_latency) {
    ASSERT_TRUE(profile_manager()->SetUp());

    fake_network_context_ = std::make_unique<FakeNetworkContext>();
    fake_network_context_->set_fake_dns_result(std::move(fake_dns_result));
    test_profile_ = profile_manager()->CreateTestingProfile(kFakeTestProfile);
    fake_tick_clock_ = std::make_unique<FakeTickClock>(response_latency);
  }

  void SetUpDnsLatencyRoutine() {
    dns_latency_routine_ = std::make_unique<DnsLatencyRoutine>(
        mojom::RoutineCallSource::kDiagnosticsUI);
    dns_latency_routine_->set_network_context_for_testing(
        fake_network_context_.get());
    dns_latency_routine_->set_profile_for_testing(test_profile_);
    dns_latency_routine_->set_tick_clock_for_testing(fake_tick_clock_.get());
  }

  void RunRoutine(
      mojom::RoutineVerdict expected_routine_verdict,
      const std::vector<mojom::DnsLatencyProblem>& expected_problems) {
    base::test::TestFuture<mojom::RoutineResultPtr> future;
    dns_latency_routine_->RunRoutine(future.GetCallback());
    auto result = future.Take();
    EXPECT_EQ(expected_routine_verdict, result->verdict);
    EXPECT_EQ(expected_problems, result->problems->get_dns_latency_problems());
  }

  // Sets up required properties (via fakes) and runs the test.
  //
  // Parameters:
  // |fake_dns_result|: Represents the result of a DNS resolution.
  // |response_latency|: Represents the time elapsed while performing the DNS
  // resolution.
  // |routine_verdict|: Represents the expected verdict reported by this test.
  // |expected_problems|: Represents the expected problem reported by this test.
  void SetUpAndRunRoutine(
      std::unique_ptr<FakeNetworkContext::DnsResult> fake_dns_result,
      const base::TimeDelta response_latency,
      mojom::RoutineVerdict expected_routine_verdict,
      const std::vector<mojom::DnsLatencyProblem>& expected_problems) {
    SetUpFakeProperties(std::move(fake_dns_result), response_latency);
    SetUpDnsLatencyRoutine();
    RunRoutine(expected_routine_verdict, expected_problems);
  }

  TestingProfileManager* profile_manager() { return &profile_manager_; }

 protected:
  base::WeakPtr<DnsLatencyRoutineTest> weak_ptr() {
    return weak_factory_.GetWeakPtr();
  }

 private:
  content::BrowserTaskEnvironment task_environment_;
  std::unique_ptr<FakeNetworkContext> fake_network_context_;
  raw_ptr<Profile, DanglingUntriaged> test_profile_;
  std::unique_ptr<FakeTickClock> fake_tick_clock_;
  session_manager::SessionManager session_manager_;
  TestingProfileManager profile_manager_;
  std::unique_ptr<DnsLatencyRoutine> dns_latency_routine_;
  base::WeakPtrFactory<DnsLatencyRoutineTest> weak_factory_{this};
};

// A passing routine requires an error code of net::OK and a non-empty
// net::AddressList for each DNS resolution and an averagelatency below the
// allowed threshold.
TEST_F(DnsLatencyRoutineTest, TestSuccessfulResolutions) {
  auto fake_dns_result = std::make_unique<FakeNetworkContext::DnsResult>(
      net::OK, net::ResolveErrorInfo(net::OK),
      net::AddressList(FakeIPAddress()),
      /*endpoint_results_with_metadata=*/std::nullopt);
  SetUpAndRunRoutine(std::move(fake_dns_result),
                     kSuccessfulDnsResolutionDelayMs,
                     mojom::RoutineVerdict::kNoProblem, {});
}

// Set up the |fake_dns_result| to return a DnsResult with an error code
// net::ERR_NAME_NOT_RESOLVED, faking a failed resolution attempt.
TEST_F(DnsLatencyRoutineTest, TestUnsuccessfulResolution) {
  auto fake_dns_result = std::make_unique<FakeNetworkContext::DnsResult>(
      net::ERR_NAME_NOT_RESOLVED,
      net::ResolveErrorInfo(net::ERR_NAME_NOT_RESOLVED),
      /*resolved_addresses=*/std::nullopt,
      /*endpoint_results_with_metadata=*/std::nullopt);
  // The time taken to complete the resolution is not important for this test,
  // because a failed resolution attempt already results in a problem.
  SetUpAndRunRoutine(std::move(fake_dns_result),
                     kSuccessfulDnsResolutionDelayMs,
                     mojom::RoutineVerdict::kProblem,
                     {mojom::DnsLatencyProblem::kHostResolutionFailure});
}

// This test case represents the scenario where a DNS resolution was successful;
// however, the average resolution latency was slightly above the allowed
// threshold.
TEST_F(DnsLatencyRoutineTest, TestLatencySlightlyAboveThreshold) {
  auto fake_dns_result = std::make_unique<FakeNetworkContext::DnsResult>(
      net::OK, net::ResolveErrorInfo(net::OK),
      net::AddressList(FakeIPAddress()),
      /*endpoint_results_with_metadata=*/std::nullopt);
  SetUpAndRunRoutine(std::move(fake_dns_result), kSlightlyAboveThresholdDelayMs,
                     mojom::RoutineVerdict::kProblem,
                     {mojom::DnsLatencyProblem::kSlightlyAboveThreshold});
}

// This test case represents the scenario where the DNS resolutions were
// successful; however, the average resolution latency was significantly above
// the allowed threshold.
TEST_F(DnsLatencyRoutineTest, TestLatencySignificantlyAboveThreshold) {
  auto fake_dns_result = std::make_unique<FakeNetworkContext::DnsResult>(
      net::OK, net::ResolveErrorInfo(net::OK),
      net::AddressList(FakeIPAddress()),
      /*endpoint_results_with_metadata=*/std::nullopt);
  SetUpAndRunRoutine(std::move(fake_dns_result),
                     kSignificantlyAboveThresholdDelayMs,
                     mojom::RoutineVerdict::kProblem,
                     {mojom::DnsLatencyProblem::kSignificantlyAboveThreshold});
}

}  // namespace ash::network_diagnostics