chromium/chrome/browser/chromeos/extensions/telemetry/api/routines/diagnostic_routine_observation_browsertest.cc

// Copyright 2023 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/chromeos/extensions/telemetry/api/routines/diagnostic_routine_observation.h"

#include <memory>
#include <string>
#include <tuple>

#include "base/functional/callback.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/test_future.h"
#include "base/uuid.h"
#include "chrome/browser/chromeos/extensions/telemetry/api/common/base_telemetry_extension_browser_test.h"
#include "chrome/browser/chromeos/extensions/telemetry/api/routines/diagnostic_routine_info.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chromeos/extensions/api/diagnostics.h"
#include "chromeos/crosapi/mojom/telemetry_diagnostic_routine_service.mojom.h"
#include "content/public/browser/browser_context.h"
#include "content/public/test/browser_test.h"
#include "extensions/browser/event_router.h"
#include "mojo/public/cpp/bindings/remote.h"

namespace chromeos {
namespace {

namespace crosapi = ::crosapi::mojom;

// An `EventRouterObserver` that runs a callback once a callback is registered
// for the provided `event_name`. This class is used in tests to execute code
// once the callback is properly set up. That way we can make sure to dispatch
// events in the right moment. This class also makes sure that the passed
// callback is only executed once by unregistering itself as an observer in case
// the correct event occurs.
class EventRegistrationObserver : public extensions::EventRouter::Observer {
 public:
  explicit EventRegistrationObserver(std::string event_name,
                                     base::OnceClosure on_event_added,
                                     content::BrowserContext* context)
      : context_(context),
        event_name_(event_name),
        on_event_added_(std::move(on_event_added)) {}
  ~EventRegistrationObserver() override = default;

  void OnListenerAdded(const extensions::EventListenerInfo& details) override {
    if (details.event_name.compare(event_name_) == 0) {
      extensions::EventRouter::Get(context_)->UnregisterObserver(this);
      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE, std::move(on_event_added_));
    }
  }

 private:
  raw_ptr<content::BrowserContext> context_;
  std::string event_name_;
  base::OnceClosure on_event_added_;
};

class TelemetryExtensionDiagnosticRoutineObserverBrowserTest
    : public BaseTelemetryExtensionBrowserTest {
 public:
  void SetUpOnMainThread() override {
    BaseTelemetryExtensionBrowserTest::SetUpOnMainThread();
  }

  void TearDownOnMainThread() override {
    registration_observer_.reset();
    observation_.reset();
    BaseTelemetryExtensionBrowserTest::TearDownOnMainThread();
  }

 protected:
  void SetRoutineObservation() {
    // Use an arbitrary value for `argument_tag_for_legacy_finished_events`.
    DiagnosticRoutineInfo info(
        extension_id(), uuid_, profile(),
        crosapi::TelemetryDiagnosticRoutineArgument::Tag::kMemory);
    observation_ = std::make_unique<DiagnosticRoutineObservation>(
        info, on_finished_future_.GetCallback(),
        remote_.BindNewPipeAndPassReceiver());
  }

  void SetLegacyFinishedEventRoutineObservation(
      crosapi::TelemetryDiagnosticRoutineArgument::Tag
          argument_tag_for_legacy_finished_events) {
    DiagnosticRoutineInfo info(extension_id(), uuid_, profile(),
                               argument_tag_for_legacy_finished_events);
    observation_ = std::make_unique<DiagnosticRoutineObservation>(
        info, on_finished_future_.GetCallback(),
        remote_.BindNewPipeAndPassReceiver());
  }

  void RegisterEventObserver(std::string event_name,
                             base::OnceClosure on_event_added) {
    registration_observer_ = std::make_unique<EventRegistrationObserver>(
        event_name, std::move(on_event_added), profile());

    extensions::EventRouter::Get(profile())->RegisterObserver(
        registration_observer_.get(), event_name);
  }

  DiagnosticRoutineInfo WaitForFinishedReport() {
    return on_finished_future_.Take();
  }

  base::Uuid uuid_{base::Uuid::GenerateRandomV4()};
  mojo::Remote<crosapi::TelemetryDiagnosticRoutineObserver> remote_;

 private:
  base::test::TestFuture<DiagnosticRoutineInfo> on_finished_future_;
  std::unique_ptr<EventRegistrationObserver> registration_observer_;
  std::unique_ptr<DiagnosticRoutineObservation> observation_;
};

}  // namespace

IN_PROC_BROWSER_TEST_F(TelemetryExtensionDiagnosticRoutineObserverBrowserTest,
                       CanObserveOnRoutineInitialized) {
  SetRoutineObservation();
  RegisterEventObserver(
      api::os_diagnostics::OnRoutineInitialized::kEventName,
      base::BindLambdaForTesting([this] {
        auto init_state = crosapi::TelemetryDiagnosticRoutineState::New();
        init_state->state_union =
            crosapi::TelemetryDiagnosticRoutineStateUnion::NewInitialized(
                crosapi::TelemetryDiagnosticRoutineStateInitialized::New());
        init_state->percentage = 0;

        remote_->OnRoutineStateChange(std::move(init_state));
      }));

  CreateExtensionAndRunServiceWorker(
      base::StringPrintf(R"(
    chrome.test.runTests([
      async function canObserveOnRoutineInitialized() {
        chrome.os.diagnostics.onRoutineInitialized.addListener((event) => {
          chrome.test.assertEq(event, {
            uuid: "%s",
          });

          chrome.test.succeed();
        });
      }
    ]);
  )",
                         uuid_.AsLowercaseString().c_str()));
}

IN_PROC_BROWSER_TEST_F(TelemetryExtensionDiagnosticRoutineObserverBrowserTest,
                       CanObserveOnRoutineRunning) {
  SetRoutineObservation();
  RegisterEventObserver(
      api::os_diagnostics::OnRoutineRunning::kEventName,
      base::BindLambdaForTesting([this] {
        auto running_state = crosapi::TelemetryDiagnosticRoutineState::New();
        running_state->state_union =
            crosapi::TelemetryDiagnosticRoutineStateUnion::NewRunning(
                crosapi::TelemetryDiagnosticRoutineStateRunning::New());
        running_state->percentage = 50;

        remote_->OnRoutineStateChange(std::move(running_state));
      }));

  CreateExtensionAndRunServiceWorker(
      base::StringPrintf(R"(
    chrome.test.runTests([
      async function canObserveOnRoutineRunning() {
        chrome.os.diagnostics.onRoutineRunning.addListener((event) => {
          chrome.test.assertEq(event, {
            percentage: 50,
            uuid: "%s",
          });

          chrome.test.succeed();
        });
      }
    ]);
  )",
                         uuid_.AsLowercaseString().c_str()));
}

IN_PROC_BROWSER_TEST_F(TelemetryExtensionDiagnosticRoutineObserverBrowserTest,
                       CanObserveOnRoutineRunningWithNetworkBandwidthInfo) {
  SetRoutineObservation();
  RegisterEventObserver(
      api::os_diagnostics::OnRoutineRunning::kEventName,
      base::BindLambdaForTesting([this] {
        auto network_bandwidth_info = crosapi::
            TelemetryDiagnosticNetworkBandwidthRoutineRunningInfo::New();
        network_bandwidth_info->type =
            crosapi::TelemetryDiagnosticNetworkBandwidthRoutineRunningInfo::
                Type::kDownload;
        network_bandwidth_info->speed_kbps = 100.0;

        auto running_state =
            crosapi::TelemetryDiagnosticRoutineStateRunning::New();
        running_state->info =
            crosapi::TelemetryDiagnosticRoutineRunningInfo::NewNetworkBandwidth(
                std::move(network_bandwidth_info));

        auto state = crosapi::TelemetryDiagnosticRoutineState::New();
        state->state_union =
            crosapi::TelemetryDiagnosticRoutineStateUnion::NewRunning(
                std::move(running_state));
        state->percentage = 50;

        remote_->OnRoutineStateChange(std::move(state));
      }));

  CreateExtensionAndRunServiceWorker(
      base::StringPrintf(R"(
    chrome.test.runTests([
      async function canObserveOnRoutineRunningWithNetworkBandwidthInfo() {
        chrome.os.diagnostics.onRoutineRunning.addListener((event) => {
          chrome.test.assertEq(event, {
            info: {
              "networkBandwidth": {
                "speedKbps": 100.0,
                "type": "download",
              }
            },
            percentage: 50,
            uuid:"%s",
          });

          chrome.test.succeed();
        });
      }
    ]);
  )",
                         uuid_.AsLowercaseString().c_str()));
}

IN_PROC_BROWSER_TEST_F(TelemetryExtensionDiagnosticRoutineObserverBrowserTest,
                       CanObserveOnRoutineWaiting) {
  SetRoutineObservation();
  RegisterEventObserver(
      api::os_diagnostics::OnRoutineWaiting::kEventName,
      base::BindLambdaForTesting([this] {
        auto waiting_state = crosapi::TelemetryDiagnosticRoutineState::New();
        waiting_state->state_union =
            crosapi::TelemetryDiagnosticRoutineStateUnion::NewWaiting(
                crosapi::TelemetryDiagnosticRoutineStateWaiting::New(
                    crosapi::TelemetryDiagnosticRoutineStateWaiting::Reason::
                        kWaitingToBeScheduled,
                    "TEST"));
        waiting_state->percentage = 50;

        remote_->OnRoutineStateChange(std::move(waiting_state));
      }));

  CreateExtensionAndRunServiceWorker(
      base::StringPrintf(R"(
    chrome.test.runTests([
      async function canObserveOnRoutineWaiting() {
        chrome.os.diagnostics.onRoutineWaiting.addListener((event) => {
          chrome.test.assertEq(event, {
            message: "TEST",
            percentage: 50,
            reason: "waiting_to_be_scheduled",
            uuid: "%s",
          });

          chrome.test.succeed();
        });
      }
    ]);
  )",
                         uuid_.AsLowercaseString().c_str()));
}

IN_PROC_BROWSER_TEST_F(TelemetryExtensionDiagnosticRoutineObserverBrowserTest,
                       CanObserveLegacyOnMemoryRoutineFinished) {
  SetLegacyFinishedEventRoutineObservation(
      crosapi::TelemetryDiagnosticRoutineArgument::Tag::kMemory);
  RegisterEventObserver(
      api::os_diagnostics::OnMemoryRoutineFinished::kEventName,
      base::BindLambdaForTesting([this] {
        auto memtester_result =
            crosapi::TelemetryDiagnosticMemtesterResult::New();
        memtester_result->passed_items = {
            crosapi::TelemetryDiagnosticMemtesterTestItemEnum::kCompareDIV,
            crosapi::TelemetryDiagnosticMemtesterTestItemEnum::kCompareMUL};
        memtester_result->failed_items = {
            crosapi::TelemetryDiagnosticMemtesterTestItemEnum::kCompareAND,
            crosapi::TelemetryDiagnosticMemtesterTestItemEnum::kCompareSUB};

        auto memory_detail =
            crosapi::TelemetryDiagnosticMemoryRoutineDetail::New();
        memory_detail->bytes_tested = 500;
        memory_detail->result = std::move(memtester_result);

        auto finished_detail =
            crosapi::TelemetryDiagnosticRoutineDetail::NewMemory(
                std::move(memory_detail));

        auto finished_state = crosapi::TelemetryDiagnosticRoutineState::New();
        finished_state->state_union =
            crosapi::TelemetryDiagnosticRoutineStateUnion::NewFinished(
                crosapi::TelemetryDiagnosticRoutineStateFinished::New(
                    /*has_passed=*/true, std::move(finished_detail)));
        finished_state->percentage = 100;

        remote_->OnRoutineStateChange(std::move(finished_state));
      }));

  CreateExtensionAndRunServiceWorker(
      base::StringPrintf(R"(
    chrome.test.runTests([
      async function canObserveOnMemoryRoutineFinished() {
        chrome.os.diagnostics.onMemoryRoutineFinished.addListener((event) => {
          chrome.test.assertEq(event, {
            "bytesTested": 500,
            "has_passed": true,
            "result": {
              "failed_items": [
                "compare_and",
                "compare_sub"
              ],
              "passed_items": [
                "compare_div",
                "compare_mul"
              ]
            },
            "uuid":"%s"
          });

          chrome.test.succeed();
        });
      }
    ]);
  )",
                         uuid_.AsLowercaseString().c_str()));

  auto info = WaitForFinishedReport();
  EXPECT_EQ(info.extension_id, extension_id());
  EXPECT_EQ(info.uuid, uuid_);
}

// In newer implementation of healthd, a finished volume button routine does not
// contain the routine detail.
IN_PROC_BROWSER_TEST_F(
    TelemetryExtensionDiagnosticRoutineObserverBrowserTest,
    CanObserveLegacyOnVolumeButtonRoutineFinishedWithoutRoutineDetail) {
  SetLegacyFinishedEventRoutineObservation(
      crosapi::TelemetryDiagnosticRoutineArgument::Tag::kVolumeButton);
  RegisterEventObserver(
      api::os_diagnostics::OnVolumeButtonRoutineFinished::kEventName,
      base::BindLambdaForTesting([this] {
        auto finished_state = crosapi::TelemetryDiagnosticRoutineState::New();
        finished_state->state_union =
            crosapi::TelemetryDiagnosticRoutineStateUnion::NewFinished(
                crosapi::TelemetryDiagnosticRoutineStateFinished::New(
                    /*has_passed=*/true, /*detail=*/nullptr));
        finished_state->percentage = 100;

        remote_->OnRoutineStateChange(std::move(finished_state));
      }));

  CreateExtensionAndRunServiceWorker(
      base::StringPrintf(R"(
    chrome.test.runTests([
      async function canObserveOnVolumeButtonRoutineFinished() {
        chrome.os.diagnostics.onVolumeButtonRoutineFinished.addListener(
          (event) => {
            chrome.test.assertEq(event, {
              "has_passed": true,
              "uuid":"%s"
            });

            chrome.test.succeed();
        });
      }
    ]);
  )",
                         uuid_.AsLowercaseString().c_str()));

  auto info = WaitForFinishedReport();
  EXPECT_EQ(info.extension_id, extension_id());
  EXPECT_EQ(info.uuid, uuid_);
}

// In older implementation of healthd, a finished volume button routine contains
// a routine detail.
IN_PROC_BROWSER_TEST_F(
    TelemetryExtensionDiagnosticRoutineObserverBrowserTest,
    CanObserveLegacyOnVolumeButtonRoutineFinishedWithRoutineDetail) {
  SetLegacyFinishedEventRoutineObservation(
      crosapi::TelemetryDiagnosticRoutineArgument::Tag::kVolumeButton);
  RegisterEventObserver(
      api::os_diagnostics::OnVolumeButtonRoutineFinished::kEventName,
      base::BindLambdaForTesting([this] {
        auto volume_button_detail =
            crosapi::TelemetryDiagnosticVolumeButtonRoutineDetail::New();

        auto finished_detail =
            crosapi::TelemetryDiagnosticRoutineDetail::NewVolumeButton(
                std::move(volume_button_detail));

        auto finished_state = crosapi::TelemetryDiagnosticRoutineState::New();
        finished_state->state_union =
            crosapi::TelemetryDiagnosticRoutineStateUnion::NewFinished(
                crosapi::TelemetryDiagnosticRoutineStateFinished::New(
                    /*has_passed=*/true, std::move(finished_detail)));
        finished_state->percentage = 100;

        remote_->OnRoutineStateChange(std::move(finished_state));
      }));

  CreateExtensionAndRunServiceWorker(
      base::StringPrintf(R"(
    chrome.test.runTests([
      async function canObserveOnVolumeButtonRoutineFinished() {
        chrome.os.diagnostics.onVolumeButtonRoutineFinished.addListener(
          (event) => {
            chrome.test.assertEq(event, {
              "has_passed": true,
              "uuid":"%s"
            });

            chrome.test.succeed();
        });
      }
    ]);
  )",
                         uuid_.AsLowercaseString().c_str()));

  auto info = WaitForFinishedReport();
  EXPECT_EQ(info.extension_id, extension_id());
  EXPECT_EQ(info.uuid, uuid_);
}

IN_PROC_BROWSER_TEST_F(TelemetryExtensionDiagnosticRoutineObserverBrowserTest,
                       CanObserveLegacyOnFanRoutineFinished) {
  SetLegacyFinishedEventRoutineObservation(
      crosapi::TelemetryDiagnosticRoutineArgument::Tag::kFan);
  RegisterEventObserver(
      api::os_diagnostics::OnFanRoutineFinished::kEventName,
      base::BindLambdaForTesting([this] {
        auto fan_detail = crosapi::TelemetryDiagnosticFanRoutineDetail::New();
        fan_detail->passed_fan_ids = {0};
        fan_detail->failed_fan_ids = {1};
        fan_detail->fan_count_status =
            crosapi::TelemetryDiagnosticHardwarePresenceStatus::kMatched;

        auto finished_detail =
            crosapi::TelemetryDiagnosticRoutineDetail::NewFan(
                std::move(fan_detail));

        auto finished_state = crosapi::TelemetryDiagnosticRoutineState::New();
        finished_state->state_union =
            crosapi::TelemetryDiagnosticRoutineStateUnion::NewFinished(
                crosapi::TelemetryDiagnosticRoutineStateFinished::New(
                    /*has_passed=*/true, std::move(finished_detail)));
        finished_state->percentage = 100;

        remote_->OnRoutineStateChange(std::move(finished_state));
      }));

  CreateExtensionAndRunServiceWorker(
      base::StringPrintf(R"(
    chrome.test.runTests([
      async function canObserveOnFanRoutineFinished() {
        chrome.os.diagnostics.onFanRoutineFinished.addListener((event) => {
          chrome.test.assertEq(event, {
            "passed_fan_ids": [0],
            "failed_fan_ids": [1],
            "fan_count_status": "matched",
            "has_passed": true,
            "uuid":"%s"
          });

          chrome.test.succeed();
        });
      }
    ]);
  )",
                         uuid_.AsLowercaseString().c_str()));

  auto info = WaitForFinishedReport();
  EXPECT_EQ(info.extension_id, extension_id());
  EXPECT_EQ(info.uuid, uuid_);
}

IN_PROC_BROWSER_TEST_F(TelemetryExtensionDiagnosticRoutineObserverBrowserTest,
                       CanObserveOnRoutineFinishedWithNullDetail) {
  SetRoutineObservation();
  RegisterEventObserver(
      api::os_diagnostics::OnRoutineFinished::kEventName,
      base::BindLambdaForTesting([this] {
        auto finished_state = crosapi::TelemetryDiagnosticRoutineState::New();
        finished_state->state_union =
            crosapi::TelemetryDiagnosticRoutineStateUnion::NewFinished(
                crosapi::TelemetryDiagnosticRoutineStateFinished::New(
                    /*has_passed=*/true, /*detail=*/nullptr));
        finished_state->percentage = 100;

        remote_->OnRoutineStateChange(std::move(finished_state));
      }));

  CreateExtensionAndRunServiceWorker(
      base::StringPrintf(R"(
    chrome.test.runTests([
      async function canObserveOnRoutineFinishedWithNullDetail() {
        chrome.os.diagnostics.onRoutineFinished.addListener((event) => {
          chrome.test.assertEq(event, {
            "hasPassed": true,
            "uuid":"%s"
          });

          chrome.test.succeed();
        });
      }
    ]);
  )",
                         uuid_.AsLowercaseString().c_str()));

  auto info = WaitForFinishedReport();
  EXPECT_EQ(info.extension_id, extension_id());
  EXPECT_EQ(info.uuid, uuid_);
}

IN_PROC_BROWSER_TEST_F(TelemetryExtensionDiagnosticRoutineObserverBrowserTest,
                       CanObserveOnRoutineFinishedWithMemoryDetail) {
  SetRoutineObservation();
  RegisterEventObserver(
      api::os_diagnostics::OnRoutineFinished::kEventName,
      base::BindLambdaForTesting([this] {
        auto memtester_result =
            crosapi::TelemetryDiagnosticMemtesterResult::New();
        memtester_result->passed_items = {
            crosapi::TelemetryDiagnosticMemtesterTestItemEnum::kCompareDIV,
            crosapi::TelemetryDiagnosticMemtesterTestItemEnum::kCompareMUL};
        memtester_result->failed_items = {
            crosapi::TelemetryDiagnosticMemtesterTestItemEnum::kCompareAND,
            crosapi::TelemetryDiagnosticMemtesterTestItemEnum::kCompareSUB};

        auto memory_detail =
            crosapi::TelemetryDiagnosticMemoryRoutineDetail::New();
        memory_detail->bytes_tested = 500;
        memory_detail->result = std::move(memtester_result);

        auto finished_detail =
            crosapi::TelemetryDiagnosticRoutineDetail::NewMemory(
                std::move(memory_detail));

        auto finished_state = crosapi::TelemetryDiagnosticRoutineState::New();
        finished_state->state_union =
            crosapi::TelemetryDiagnosticRoutineStateUnion::NewFinished(
                crosapi::TelemetryDiagnosticRoutineStateFinished::New(
                    /*has_passed=*/true, std::move(finished_detail)));
        finished_state->percentage = 100;

        remote_->OnRoutineStateChange(std::move(finished_state));
      }));

  CreateExtensionAndRunServiceWorker(
      base::StringPrintf(R"(
    chrome.test.runTests([
      async function canObserveOnRoutineFinishedWithMemoryDetail() {
        chrome.os.diagnostics.onRoutineFinished.addListener((event) => {
          chrome.test.assertEq(event, {
            "hasPassed": true,
            "detail": {
              "memory": {
                "bytesTested": 500,
                "result": {
                  "failedItems": [
                    "compare_and",
                    "compare_sub"
                  ],
                  "passedItems": [
                    "compare_div",
                    "compare_mul"
                  ]
                },
              }
            },
            "uuid":"%s"
          });

          chrome.test.succeed();
        });
      }
    ]);
  )",
                         uuid_.AsLowercaseString().c_str()));

  auto info = WaitForFinishedReport();
  EXPECT_EQ(info.extension_id, extension_id());
  EXPECT_EQ(info.uuid, uuid_);
}

// In older implementation of healthd, a finished volume button routine contains
// a routine detail.
IN_PROC_BROWSER_TEST_F(TelemetryExtensionDiagnosticRoutineObserverBrowserTest,
                       CanObserveOnRoutineFinishedWithVolumeButtonDetail) {
  SetRoutineObservation();
  RegisterEventObserver(
      api::os_diagnostics::OnRoutineFinished::kEventName,
      base::BindLambdaForTesting([this] {
        auto volume_button_detail =
            crosapi::TelemetryDiagnosticVolumeButtonRoutineDetail::New();

        auto finished_detail =
            crosapi::TelemetryDiagnosticRoutineDetail::NewVolumeButton(
                std::move(volume_button_detail));

        auto finished_state = crosapi::TelemetryDiagnosticRoutineState::New();
        finished_state->state_union =
            crosapi::TelemetryDiagnosticRoutineStateUnion::NewFinished(
                crosapi::TelemetryDiagnosticRoutineStateFinished::New(
                    /*has_passed=*/true, std::move(finished_detail)));
        finished_state->percentage = 100;

        remote_->OnRoutineStateChange(std::move(finished_state));
      }));

  CreateExtensionAndRunServiceWorker(
      base::StringPrintf(R"(
    chrome.test.runTests([
      async function canObserveOnRoutineFinishedWithVolumeButtonDetail() {
        chrome.os.diagnostics.onRoutineFinished.addListener(
          (event) => {
            chrome.test.assertEq(event, {
              "hasPassed": true,
              "uuid":"%s"
            });

            chrome.test.succeed();
        });
      }
    ]);
  )",
                         uuid_.AsLowercaseString().c_str()));

  auto info = WaitForFinishedReport();
  EXPECT_EQ(info.extension_id, extension_id());
  EXPECT_EQ(info.uuid, uuid_);
}

IN_PROC_BROWSER_TEST_F(TelemetryExtensionDiagnosticRoutineObserverBrowserTest,
                       CanObserveOnRoutineFinishedWithFanDetail) {
  SetRoutineObservation();
  RegisterEventObserver(
      api::os_diagnostics::OnRoutineFinished::kEventName,
      base::BindLambdaForTesting([this] {
        auto fan_detail = crosapi::TelemetryDiagnosticFanRoutineDetail::New();
        fan_detail->passed_fan_ids = {0};
        fan_detail->failed_fan_ids = {1};
        fan_detail->fan_count_status =
            crosapi::TelemetryDiagnosticHardwarePresenceStatus::kMatched;

        auto finished_detail =
            crosapi::TelemetryDiagnosticRoutineDetail::NewFan(
                std::move(fan_detail));

        auto finished_state = crosapi::TelemetryDiagnosticRoutineState::New();
        finished_state->state_union =
            crosapi::TelemetryDiagnosticRoutineStateUnion::NewFinished(
                crosapi::TelemetryDiagnosticRoutineStateFinished::New(
                    /*has_passed=*/true, std::move(finished_detail)));
        finished_state->percentage = 100;

        remote_->OnRoutineStateChange(std::move(finished_state));
      }));

  CreateExtensionAndRunServiceWorker(
      base::StringPrintf(R"(
    chrome.test.runTests([
      async function canObserveOnRoutineFinishedWithFanDetail() {
        chrome.os.diagnostics.onRoutineFinished.addListener((event) => {
          chrome.test.assertEq(event, {
            "detail": {
              "fan": {
                "passedFanIds": [0],
                "failedFanIds": [1],
                "fanCountStatus": "matched"
              }
            },
            "hasPassed": true,
            "uuid":"%s"
          });

          chrome.test.succeed();
        });
      }
    ]);
  )",
                         uuid_.AsLowercaseString().c_str()));

  auto info = WaitForFinishedReport();
  EXPECT_EQ(info.extension_id, extension_id());
  EXPECT_EQ(info.uuid, uuid_);
}

IN_PROC_BROWSER_TEST_F(TelemetryExtensionDiagnosticRoutineObserverBrowserTest,
                       CanObserveOnRoutineFinishedWithNetworkBandwidthDetail) {
  SetRoutineObservation();
  RegisterEventObserver(
      api::os_diagnostics::OnRoutineFinished::kEventName,
      base::BindLambdaForTesting([this] {
        auto network_bandwidth_detail =
            crosapi::TelemetryDiagnosticNetworkBandwidthRoutineDetail::New();
        network_bandwidth_detail->download_speed_kbps = 123.0;
        network_bandwidth_detail->upload_speed_kbps = 456.0;

        auto finished_state =
            crosapi::TelemetryDiagnosticRoutineStateFinished::New();
        finished_state->detail =
            crosapi::TelemetryDiagnosticRoutineDetail::NewNetworkBandwidth(
                std::move(network_bandwidth_detail));
        finished_state->has_passed = true;

        auto state = crosapi::TelemetryDiagnosticRoutineState::New();
        state->state_union =
            crosapi::TelemetryDiagnosticRoutineStateUnion::NewFinished(
                std::move(finished_state));
        state->percentage = 100;

        remote_->OnRoutineStateChange(std::move(state));
      }));

  CreateExtensionAndRunServiceWorker(
      base::StringPrintf(R"(
    chrome.test.runTests([
      async function canObserveOnRoutineFinishedWithNetworkBandwidthDetail() {
        chrome.os.diagnostics.onRoutineFinished.addListener((event) => {
          chrome.test.assertEq(event, {
            "detail": {
              "networkBandwidth": {
                "downloadSpeedKbps": 123.0,
                "uploadSpeedKbps": 456.0
              }
            },
            "hasPassed": true,
            "uuid":"%s"
          });

          chrome.test.succeed();
        });
      }
    ]);
  )",
                         uuid_.AsLowercaseString().c_str()));

  auto info = WaitForFinishedReport();
  EXPECT_EQ(info.extension_id, extension_id());
  EXPECT_EQ(info.uuid, uuid_);
}

}  // namespace chromeos