chromium/chrome/browser/media/router/discovery/access_code/access_code_cast_pref_updater_lacros_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/media/router/discovery/access_code/access_code_cast_pref_updater_impl.h"

#include "base/json/values_util.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/scoped_mock_time_message_loop_task_runner.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/media/router/discovery/access_code/access_code_cast_feature.h"
#include "chrome/browser/media/router/discovery/access_code/access_code_cast_pref_updater_lacros.h"
#include "chrome/browser/media/router/discovery/access_code/access_code_media_sink_util.h"
#include "chrome/browser/media/router/test/provider_test_helpers.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chromeos/crosapi/mojom/prefs.mojom.h"
#include "chromeos/lacros/lacros_service.h"
#include "content/public/test/browser_test.h"
#include "services/preferences/public/cpp/dictionary_value_update.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

base::Value::Dict GetDictFromPrefService(crosapi::mojom::PrefPath pref_path) {
  base::test::TestFuture<std::optional<base::Value>> future;
  chromeos::LacrosService::Get()->GetRemote<crosapi::mojom::Prefs>()->GetPref(
      pref_path, future.GetCallback());
  auto out_value = future.Take();
  if (out_value && out_value->is_dict()) {
    return std::move(out_value.value()).TakeDict();
  }

  return base::Value::Dict();
}
}  // namespace

namespace media_router {

class AccessCodeCastPrefUpdaterLacrosTest : public InProcessBrowserTest {
 public:
  AccessCodeCastPrefUpdaterLacrosTest() {
    pref_updater_ = std::make_unique<AccessCodeCastPrefUpdaterLacros>();
  }

  void PreRunTestOnMainThread() override {
    InProcessBrowserTest::PreRunTestOnMainThread();
    auto* lacros_service = chromeos::LacrosService::Get();
    ASSERT_TRUE(lacros_service);
    ASSERT_TRUE(lacros_service->IsAvailable<crosapi::mojom::Prefs>());

    base::test::TestFuture<std::optional<base::Value>> future;
    chromeos::LacrosService::Get()->GetRemote<crosapi::mojom::Prefs>()->GetPref(
        crosapi::mojom::PrefPath::kAccessCodeCastDevices, future.GetCallback());

    auto pref_value = future.Take();
    // If the pref cannot be fetched, the ash version may be too old.
    if (!pref_value.has_value()) {
      GTEST_SKIP() << "Skipping as the prefs are not available in the "
                      "current version of Ash";
    }
  }

  void TearDownOnMainThread() override {
    // Remove all stored devices from prefs because the same ash-chrome instance
    // is used for each test and prefs stored in ash won't be reset after each
    // test finishes.
    auto& prefs =
        chromeos::LacrosService::Get()->GetRemote<crosapi::mojom::Prefs>();
    {
      base::test::TestFuture<void> future;
      prefs->SetPref(crosapi::mojom::PrefPath::kAccessCodeCastDevices,
                     base::Value(base::Value::Type::DICT),
                     future.GetCallback());
      ASSERT_TRUE(future.Wait());
    }
    {
      base::test::TestFuture<void> future;
      prefs->SetPref(
          crosapi::mojom::PrefPath::kAccessCodeCastDeviceAdditionTime,
          base::Value(base::Value::Type::DICT), future.GetCallback());
      ASSERT_TRUE(future.Wait());
    }

    ASSERT_TRUE(GetDevicesDictFromPrefService().empty());
  }

  void UpdateDevicesDict(const MediaSinkInternal& sink) {
    base::RunLoop loop;
    pref_updater()->UpdateDevicesDict(sink, loop.QuitClosure());
    loop.Run();
  }

  void UpdateDeviceAddedTimeDict(const MediaSink::Id& sink_id) {
    base::RunLoop loop;
    pref_updater()->UpdateDeviceAddedTimeDict(sink_id, loop.QuitClosure());
    loop.Run();
  }

  void RemoveSinkIdFromDevicesDict(const MediaSink::Id& sink_id) {
    base::RunLoop loop;
    pref_updater()->RemoveSinkIdFromDevicesDict(sink_id, loop.QuitClosure());
    loop.Run();
  }

  void RemoveSinkIdFromDeviceAddedTimeDict(const MediaSink::Id& sink_id) {
    base::RunLoop loop;
    pref_updater()->RemoveSinkIdFromDeviceAddedTimeDict(sink_id,
                                                        loop.QuitClosure());
    loop.Run();
  }

  base::Value::Dict GetDevicesDictFromPrefService() {
    return GetDictFromPrefService(
        crosapi::mojom::PrefPath::kAccessCodeCastDevices);
  }

  base::Value::Dict GetDeviceAddedTimeDictFromPrefService() {
    return GetDictFromPrefService(
        crosapi::mojom::PrefPath::kAccessCodeCastDeviceAdditionTime);
  }

  AccessCodeCastPrefUpdaterLacros* pref_updater() {
    return pref_updater_.get();
  }

 private:
  std::unique_ptr<AccessCodeCastPrefUpdaterLacros> pref_updater_;
};

IN_PROC_BROWSER_TEST_F(AccessCodeCastPrefUpdaterLacrosTest,
                       TestUpdateDevicesDictRecorded) {
  MediaSinkInternal cast_sink = CreateCastSink(1);
  UpdateDevicesDict(cast_sink);

  auto devices_dict = GetDevicesDictFromPrefService();
  const auto* sink_id_dict = devices_dict.FindDict(cast_sink.id());
  EXPECT_TRUE(sink_id_dict);
  EXPECT_EQ(*sink_id_dict, CreateValueDictFromMediaSinkInternal(cast_sink));
}

IN_PROC_BROWSER_TEST_F(AccessCodeCastPrefUpdaterLacrosTest,
                       TestUpdateDevicesDictOverwrite) {
  MediaSinkInternal cast_sink = CreateCastSink(1);
  UpdateDevicesDict(cast_sink);

  // Make new cast_sink with same id, but change display name.
  MediaSinkInternal cast_sink1 = CreateCastSink(1);
  auto sink1 = cast_sink1.sink();
  sink1.set_name("new_name");
  cast_sink1.set_sink(sink1);

  // Store new cast_sink1 with same id as cast_sink, it should overwrite the
  // existing pref.
  UpdateDevicesDict(cast_sink1);

  auto devices_dict = GetDevicesDictFromPrefService();
  const auto* sink_id_dict = devices_dict.FindDict(cast_sink.id());
  EXPECT_NE(*sink_id_dict, CreateValueDictFromMediaSinkInternal(cast_sink));
  EXPECT_EQ(*sink_id_dict, CreateValueDictFromMediaSinkInternal(cast_sink1));
}

IN_PROC_BROWSER_TEST_F(AccessCodeCastPrefUpdaterLacrosTest,
                       TestUpdateDeviceAddedTimeDict) {
  MediaSinkInternal cast_sink = CreateCastSink(1);

  UpdateDeviceAddedTimeDict(cast_sink.id());
  auto devices_added_time_dict = GetDeviceAddedTimeDictFromPrefService();
  EXPECT_TRUE(devices_added_time_dict.Find(cast_sink.id()));
}

IN_PROC_BROWSER_TEST_F(AccessCodeCastPrefUpdaterLacrosTest,
                       TestUpdateDeviceAddedTimeDictOverwrite) {
  base::ScopedMockTimeMessageLoopTaskRunner mocked_task_runner;

  MediaSinkInternal cast_sink = CreateCastSink(1);

  UpdateDeviceAddedTimeDict(cast_sink.id());
  auto devices_added_time_dict = GetDeviceAddedTimeDictFromPrefService();
  auto initial_time_of_addition =
      base::ValueToTime(devices_added_time_dict.Find(cast_sink.id())).value();

  mocked_task_runner->FastForwardBy(base::Seconds(10));
  UpdateDeviceAddedTimeDict(cast_sink.id());
  devices_added_time_dict = GetDeviceAddedTimeDictFromPrefService();
  auto final_time_of_addition =
      base::ValueToTime(devices_added_time_dict.Find(cast_sink.id())).value();

  // Expect the two times of addition to be different, and the second time to be
  // greater.
  EXPECT_GE(final_time_of_addition, initial_time_of_addition);
}

IN_PROC_BROWSER_TEST_F(AccessCodeCastPrefUpdaterLacrosTest,
                       TestGetMediaSinkInternalValueBySinkId) {
  MediaSinkInternal cast_sink = CreateCastSink(1);
  MediaSinkInternal cast_sink2 = CreateCastSink(2);

  UpdateDevicesDict(cast_sink);
  base::test::TestFuture<base::Value::Dict> cast_sink_from_dict;
  pref_updater()->GetMediaSinkInternalValueBySinkId(
      cast_sink.id(), cast_sink_from_dict.GetCallback());
  EXPECT_EQ(CreateValueDictFromMediaSinkInternal(cast_sink),
            cast_sink_from_dict.Get());

  base::test::TestFuture<base::Value::Dict> cast_sink_from_dict2;
  pref_updater()->GetMediaSinkInternalValueBySinkId(
      cast_sink2.id(), cast_sink_from_dict2.GetCallback());
  EXPECT_TRUE(cast_sink_from_dict2.Get().empty());
}

IN_PROC_BROWSER_TEST_F(AccessCodeCastPrefUpdaterLacrosTest,
                       TestRemoveSinkIdFromDevicesDict) {
  MediaSinkInternal cast_sink = CreateCastSink(1);
  MediaSinkInternal cast_sink2 = CreateCastSink(2);

  UpdateDevicesDict(cast_sink);

  RemoveSinkIdFromDevicesDict(cast_sink.id());
  EXPECT_FALSE(GetDevicesDictFromPrefService().FindDict(cast_sink.id()));

  RemoveSinkIdFromDevicesDict(cast_sink.id());
  EXPECT_FALSE(GetDevicesDictFromPrefService().FindDict(cast_sink2.id()));
}

IN_PROC_BROWSER_TEST_F(AccessCodeCastPrefUpdaterLacrosTest,
                       TestRemoveSinkIdFromDeviceAddedTimeDict) {
  MediaSinkInternal cast_sink = CreateCastSink(1);
  MediaSinkInternal cast_sink2 = CreateCastSink(2);

  UpdateDeviceAddedTimeDict(cast_sink.id());

  RemoveSinkIdFromDeviceAddedTimeDict(cast_sink.id());
  EXPECT_FALSE(
      GetDeviceAddedTimeDictFromPrefService().contains(cast_sink.id()));

  RemoveSinkIdFromDeviceAddedTimeDict(cast_sink2.id());
  EXPECT_FALSE(
      GetDeviceAddedTimeDictFromPrefService().contains(cast_sink2.id()));
}

IN_PROC_BROWSER_TEST_F(AccessCodeCastPrefUpdaterLacrosTest,
                       TestGetDeviceAddedTime) {
  MediaSinkInternal cast_sink = CreateCastSink(1);
  MediaSinkInternal cast_sink2 = CreateCastSink(2);

  UpdateDeviceAddedTimeDict(cast_sink.id());
  auto devices_added_time_dict = GetDeviceAddedTimeDictFromPrefService();
  EXPECT_TRUE(devices_added_time_dict.contains(cast_sink.id()));
  EXPECT_FALSE(devices_added_time_dict.contains(cast_sink2.id()));
}

IN_PROC_BROWSER_TEST_F(AccessCodeCastPrefUpdaterLacrosTest,
                       TestClearDevicesDict) {
  MediaSinkInternal cast_sink = CreateCastSink(1);
  MediaSinkInternal cast_sink2 = CreateCastSink(2);

  UpdateDevicesDict(cast_sink);
  UpdateDevicesDict(cast_sink2);
  EXPECT_FALSE(GetDevicesDictFromPrefService().empty());

  base::RunLoop loop;
  pref_updater()->ClearDevicesDict(loop.QuitClosure());
  loop.Run();
  EXPECT_TRUE(GetDevicesDictFromPrefService().empty());
}

IN_PROC_BROWSER_TEST_F(AccessCodeCastPrefUpdaterLacrosTest,
                       TestClearDeviceAddedTimeDict) {
  MediaSinkInternal cast_sink = CreateCastSink(1);
  MediaSinkInternal cast_sink2 = CreateCastSink(2);

  UpdateDeviceAddedTimeDict(cast_sink.id());
  UpdateDeviceAddedTimeDict(cast_sink2.id());
  EXPECT_FALSE(GetDeviceAddedTimeDictFromPrefService().empty());

  base::RunLoop loop;
  pref_updater()->ClearDeviceAddedTimeDict(loop.QuitClosure());
  loop.Run();
  EXPECT_TRUE(GetDeviceAddedTimeDictFromPrefService().empty());
}

IN_PROC_BROWSER_TEST_F(AccessCodeCastPrefUpdaterLacrosTest,
                       TestUpdateDevicesDictIdenticalIPs) {
  MediaSinkInternal cast_sink = CreateCastSink(1);
  MediaSinkInternal cast_sink2 = CreateCastSink(2);
  MediaSinkInternal cast_sink3 = CreateCastSink(3);

  // Set the ip_endpoint of cast_sink2 to the ip_endpoint of cast_sink.
  cast_sink2.set_cast_data(cast_sink.cast_data());

  UpdateDevicesDict(cast_sink);

  // This add will overwrite the original storage of the cast_sink, since
  // cast_sink2 has the same ip_endpoint.
  UpdateDevicesDict(cast_sink2);
  UpdateDevicesDict(cast_sink3);

  // There should only be two devices stored since two ip_endpoints were
  // identical.
  base::Value::Dict devices_dict = GetDevicesDictFromPrefService();
  EXPECT_EQ(devices_dict.size(), 2u);
  EXPECT_TRUE(devices_dict.contains(cast_sink2.id()));
  EXPECT_TRUE(devices_dict.contains(cast_sink3.id()));
}

IN_PROC_BROWSER_TEST_F(AccessCodeCastPrefUpdaterLacrosTest,
                       TestUpdateDevicesDictDifferentIPs) {
  MediaSinkInternal cast_sink = CreateCastSink(1);
  MediaSinkInternal cast_sink2 = CreateCastSink(2);
  MediaSinkInternal cast_sink3 = CreateCastSink(3);

  UpdateDevicesDict(cast_sink);
  UpdateDevicesDict(cast_sink2);
  UpdateDevicesDict(cast_sink3);

  // There should only be two devices stored since two ip_endpoints were
  // identical.
  base::Value::Dict devices_dict = GetDevicesDictFromPrefService();
  EXPECT_EQ(devices_dict.size(), 3u);
  EXPECT_TRUE(devices_dict.contains(cast_sink.id()));
  EXPECT_TRUE(devices_dict.contains(cast_sink2.id()));
  EXPECT_TRUE(devices_dict.contains(cast_sink3.id()));
}

}  // namespace media_router