chromium/components/media_router/browser/android/media_router_android_unittest.cc

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>

#include "base/android/jni_android.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/test/mock_callback.h"
#include "components/media_router/browser/android/media_router_android.h"
#include "components/media_router/browser/android/media_router_android_bridge.h"
#include "components/media_router/browser/test/test_helper.h"
#include "content/public/browser/presentation_service_delegate.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/origin.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "components/media_router/browser/android/test_jni_headers/TestMediaRouterClient_jni.h"

using blink::mojom::PresentationConnectionState;
using testing::_;
using testing::Expectation;
using testing::Return;

namespace media_router {

class MockMediaRouterAndroidBridge : public MediaRouterAndroidBridge {
 public:
  MockMediaRouterAndroidBridge() : MediaRouterAndroidBridge(nullptr) {}
  ~MockMediaRouterAndroidBridge() override = default;

  MOCK_METHOD6(CreateRoute,
               void(const MediaSource::Id&,
                    const MediaSink::Id&,
                    const std::string&,
                    const url::Origin&,
                    content::WebContents*,
                    int));
  MOCK_METHOD5(JoinRoute,
               void(const MediaSource::Id&,
                    const std::string&,
                    const url::Origin&,
                    content::WebContents*,
                    int));
  MOCK_METHOD1(TerminateRoute, void(const MediaRoute::Id&));
  MOCK_METHOD2(SendRouteMessage,
               void(const MediaRoute::Id&, const std::string&));
  MOCK_METHOD1(DetachRoute, void(const MediaRoute::Id&));
  MOCK_METHOD1(StartObservingMediaSinks, bool(const MediaSource::Id&));
  MOCK_METHOD1(StopObservingMediaSinks, void(const MediaSource::Id&));
};

class MediaRouterAndroidTest : public testing::Test {
 public:
  void SetUp() override {
    Java_TestMediaRouterClient_initialize(base::android::AttachCurrentThread());
    mock_bridge_ = new MockMediaRouterAndroidBridge();
    router_ = base::WrapUnique(new MediaRouterAndroid());
    router_->SetMediaRouterBridgeForTest(mock_bridge_);
  }

 protected:
  // For the checks that MediaRouter calls are running on the UI thread.
  // Needs to be the first member variable to be destroyed last.
  content::BrowserTaskEnvironment task_environment_;

  std::unique_ptr<MediaRouterAndroid> router_;
  raw_ptr<MockMediaRouterAndroidBridge> mock_bridge_;
};

TEST_F(MediaRouterAndroidTest, DetachRoute) {
  base::MockCallback<content::PresentationConnectionStateChangedCallback>
      callback;
  content::PresentationConnectionStateChangeInfo change_info_closed(
      PresentationConnectionState::CLOSED);
  change_info_closed.close_reason =
      blink::mojom::PresentationConnectionCloseReason::CLOSED;
  change_info_closed.message = "Route closed normally";
  EXPECT_CALL(callback, Run(StateChangeInfoEquals(change_info_closed)));

  Expectation createRouteExpectation =
      EXPECT_CALL(*mock_bridge_, CreateRoute(_, _, _, _, _, 1))
          .WillOnce(Return());
  EXPECT_CALL(*mock_bridge_, DetachRoute("route"))
      .After(createRouteExpectation)
      .WillOnce(Return());

  router_->CreateRoute("source", "sink", url::Origin(), nullptr,
                       base::DoNothing(), base::TimeDelta());
  router_->OnRouteCreated("route", "sink", 1, false);

  EXPECT_NE(nullptr, router_->FindRouteBySource("source"));

  base::CallbackListSubscription subscription =
      router_->AddPresentationConnectionStateChangedCallback("route",
                                                             callback.Get());
  router_->DetachRoute("route");

  EXPECT_EQ(nullptr, router_->FindRouteBySource("source"));
}

TEST_F(MediaRouterAndroidTest, OnRouteTerminated) {
  Expectation createRouteExpectation =
      EXPECT_CALL(*mock_bridge_, CreateRoute(_, _, _, _, _, 1))
          .WillOnce(Return());

  router_->CreateRoute("source", "sink", url::Origin(), nullptr,
                       base::DoNothing(), base::TimeDelta());
  router_->OnRouteCreated("route", "sink", 1, false);

  EXPECT_NE(nullptr, router_->FindRouteBySource("source"));

  // Route termination on Android results in the PresentationConnectionPtr
  // directly being messaged, and therefore there is no
  // PresentationConnectionStateChangedCallback that can be intercepted for
  // test verification purposes.
  router_->OnRouteTerminated("route");

  EXPECT_EQ(nullptr, router_->FindRouteBySource("source"));
}

TEST_F(MediaRouterAndroidTest, OnRouteClosed) {
  base::MockCallback<content::PresentationConnectionStateChangedCallback>
      callback;
  content::PresentationConnectionStateChangeInfo change_info_closed(
      PresentationConnectionState::CLOSED);
  change_info_closed.close_reason =
      blink::mojom::PresentationConnectionCloseReason::CLOSED;
  change_info_closed.message = "Remove route";
  EXPECT_CALL(callback, Run(StateChangeInfoEquals(change_info_closed)));

  Expectation createRouteExpectation =
      EXPECT_CALL(*mock_bridge_, CreateRoute(_, _, _, _, _, 1))
          .WillOnce(Return());

  router_->CreateRoute("source", "sink", url::Origin(), nullptr,
                       base::DoNothing(), base::TimeDelta());
  router_->OnRouteCreated("route", "sink", 1, false);

  EXPECT_NE(nullptr, router_->FindRouteBySource("source"));

  base::CallbackListSubscription subscription =
      router_->AddPresentationConnectionStateChangedCallback("route",
                                                             callback.Get());
  router_->OnRouteClosed("route", std::nullopt);

  EXPECT_EQ(nullptr, router_->FindRouteBySource("source"));
}

TEST_F(MediaRouterAndroidTest, OnRouteClosedWithError) {
  base::MockCallback<content::PresentationConnectionStateChangedCallback>
      callback;
  content::PresentationConnectionStateChangeInfo change_info_closed(
      PresentationConnectionState::CLOSED);
  change_info_closed.close_reason =
      blink::mojom::PresentationConnectionCloseReason::CONNECTION_ERROR;
  change_info_closed.message = "Some failure";
  EXPECT_CALL(callback, Run(StateChangeInfoEquals(change_info_closed)));

  Expectation createRouteExpectation =
      EXPECT_CALL(*mock_bridge_, CreateRoute(_, _, _, _, _, 1))
          .WillOnce(Return());

  router_->CreateRoute("source", "sink", url::Origin(), nullptr,
                       base::DoNothing(), base::TimeDelta());
  router_->OnRouteCreated("route", "sink", 1, false);

  EXPECT_NE(nullptr, router_->FindRouteBySource("source"));

  base::CallbackListSubscription subscription =
      router_->AddPresentationConnectionStateChangedCallback("route",
                                                             callback.Get());
  router_->OnRouteClosed("route", "Some failure");

  EXPECT_EQ(nullptr, router_->FindRouteBySource("source"));
}

TEST_F(MediaRouterAndroidTest, OnRouteMediaSourceUpdated) {
  const std::string route_id = "route-id";
  const std::string sink_id = "sink-id";
  const std::string source_id = "source-id";
  const std::string source_id2 = "source-id2";
  const url::Origin origin;

  EXPECT_CALL(*mock_bridge_,
              CreateRoute(source_id, sink_id, _, origin, nullptr, 1))
      .WillOnce(Return());

  router_->CreateRoute(source_id, sink_id, origin, nullptr, base::DoNothing(),
                       base::TimeDelta());
  router_->OnRouteCreated(route_id, sink_id, 1, false);

  EXPECT_NE(nullptr, router_->FindRouteBySource(source_id));
  EXPECT_EQ(nullptr, router_->FindRouteBySource(source_id2));

  router_->OnRouteMediaSourceUpdated(route_id, source_id2);

  EXPECT_EQ(nullptr, router_->FindRouteBySource(source_id));
  EXPECT_NE(nullptr, router_->FindRouteBySource(source_id2));
}

}  // namespace media_router