910e62b5创建于 1月15日历史提交
// Copyright 2018 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/devtools/protocol/cast_handler.h"

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/media/router/chrome_media_router_factory.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "components/media_router/browser/media_sinks_observer.h"
#include "components/media_router/browser/presentation/controller_presentation_service_delegate_impl.h"
#include "components/media_router/browser/test/mock_media_router.h"
#include "components/media_router/common/media_route_provider_helper.h"
#include "components/media_router/common/media_sink.h"
#include "components/media_router/common/media_source.h"
#include "components/media_router/common/test/test_helper.h"
#include "components/sessions/content/session_tab_helper.h"
#include "content/public/browser/presentation_request.h"

using media_router::CreateCastSink;
using testing::_;
using testing::DoAll;
using testing::Return;
using testing::SaveArg;
using testing::WithArg;

namespace {

constexpr char kRouteId1[] = "route1";
constexpr char kSinkId1[] = "sink1";
constexpr char kSinkId2[] = "sink2";
constexpr char kSinkName1[] = "Sink 1";
constexpr char kSinkName2[] = "Sink 2";

const media_router::MediaSink sink1{CreateCastSink(kSinkId1, kSinkName1)};
const media_router::MediaSink sink2{CreateCastSink(kSinkId2, kSinkName2)};

media_router::MediaRoute Route1() {
  return media_router::MediaRoute(
      kRouteId1, media_router::MediaSource("https://example.com/"), kSinkId1,
      "", true);
}

class MockStartDesktopMirroringCallback
    : public CastHandler::StartDesktopMirroringCallback {
 public:
  MOCK_METHOD(void, sendSuccess, ());
  MOCK_METHOD(void, sendFailure, (const protocol::DispatchResponse&));
  MOCK_METHOD(void, fallThrough, ());
};

class MockStartTabMirroringCallback
    : public CastHandler::StartTabMirroringCallback {
 public:
  MOCK_METHOD(void, sendSuccess, ());
  MOCK_METHOD(void, sendFailure, (const protocol::DispatchResponse&));
  MOCK_METHOD(void, fallThrough, ());
};

}  // namespace

class CastHandlerTest : public ChromeRenderViewHostTestHarness {
 public:
  void SetUp() override {
    ChromeRenderViewHostTestHarness::SetUp();
    router_ = static_cast<media_router::MockMediaRouter*>(
        media_router::ChromeMediaRouterFactory::GetInstance()
            ->SetTestingFactoryAndUse(
                web_contents()->GetBrowserContext(),
                base::BindRepeating(&media_router::MockMediaRouter::Create)));

    // We cannot use std::make_unique<>() here because we're calling a private
    // constructor of CastHandler.
    handler_.reset(new CastHandler(web_contents()));
    EnableHandler();
  }

  void TearDown() override {
    handler_.reset();
    ChromeRenderViewHostTestHarness::TearDown();
  }

 protected:
  void EnableHandler() {
    EXPECT_CALL(*router_, RegisterMediaSinksObserver(_))
        .WillRepeatedly(
            WithArg<0>([this](media_router::MediaSinksObserver* observer) {
              if (observer->source()) {
                if (observer->source()->IsDesktopMirroringSource()) {
                  desktop_sinks_observer_ = observer;
                } else {
                  sinks_observer_ = observer;
                }
              }
              return true;
            }));
    handler_->Enable(std::nullopt);
  }

  std::unique_ptr<CastHandler> handler_;
  raw_ptr<media_router::MockMediaRouter, DanglingUntriaged> router_ = nullptr;
  raw_ptr<media_router::MediaSinksObserver, DanglingUntriaged>
      desktop_sinks_observer_ = nullptr;
  raw_ptr<media_router::MediaSinksObserver, DanglingUntriaged> sinks_observer_ =
      nullptr;
};

TEST_F(CastHandlerTest, SetSinkToUse) {
  sinks_observer_->OnSinksUpdated({sink1, sink2}, {});
  EXPECT_TRUE(handler_->SetSinkToUse(kSinkName1).IsSuccess());

  const std::string presentation_url("https://example.com/");
  content::PresentationRequest request(content::GlobalRenderFrameHostId(),
                                       {GURL(presentation_url)}, url::Origin());
  // Return sinks when asked for those compatible with |presentation_url|.
  EXPECT_CALL(*router_, RegisterMediaSinksObserver(_))
      .WillOnce(WithArg<0>(
          [presentation_url](media_router::MediaSinksObserver* observer) {
            EXPECT_EQ(presentation_url, observer->source()->id());
            observer->OnSinksUpdated({sink1, sink2}, {});
            return true;
          }));

  EXPECT_CALL(*router_,
              CreateRouteInternal(presentation_url, kSinkId1, _, _, _, _));
  media_router::ControllerPresentationServiceDelegateImpl::
      GetOrCreateForWebContents(web_contents())
          ->StartPresentation(request, base::DoNothing(), base::DoNothing());
}

TEST_F(CastHandlerTest, StartDesktopMirroring) {
  desktop_sinks_observer_->OnSinksUpdated({sink1, sink2}, {});
  auto callback = std::make_unique<MockStartDesktopMirroringCallback>();
  auto* callback_ptr = callback.get();

  // Make |router_| return a successful result. |callback| should be notified of
  // the success.
  EXPECT_CALL(
      *router_,
      CreateRouteInternal(media_router::MediaSource::ForUnchosenDesktop().id(),
                          kSinkId1, _, _, _, _))
      .WillOnce(
          WithArg<4>([](media_router::MediaRouteResponseCallback& callback) {
            std::move(callback).Run(
                media_router::mojom::RoutePresentationConnectionPtr(),
                media_router::RouteRequestResult(
                    std::make_unique<media_router::MediaRoute>(Route1()), "id",
                    "", media_router::mojom::RouteRequestResultCode::OK));
          }));
  EXPECT_CALL(*callback_ptr, sendSuccess());
  handler_->StartDesktopMirroring(kSinkName1, std::move(callback));
}

TEST_F(CastHandlerTest, StartDesktopMirroringWithInvalidName) {
  sinks_observer_->OnSinksUpdated({sink1}, {});
  auto callback = std::make_unique<MockStartDesktopMirroringCallback>();

  // Attempting to start casting with a name different from that of the
  // discovered sink should fail.
  EXPECT_CALL(*callback.get(), sendFailure(_));
  handler_->StartDesktopMirroring(kSinkName2, std::move(callback));
}

TEST_F(CastHandlerTest, StartTabMirroring) {
  sinks_observer_->OnSinksUpdated({sink1, sink2}, {});
  auto callback = std::make_unique<MockStartTabMirroringCallback>();
  auto* callback_ptr = callback.get();

  // Make |router_| return a successful result. |callback| should be notified of
  // the success.
  EXPECT_CALL(*router_,
              CreateRouteInternal(
                  media_router::MediaSource::ForTab(
                      sessions::SessionTabHelper::IdForTab(web_contents()).id())
                      .id(),
                  kSinkId1, _, _, _, _))
      .WillOnce(
          WithArg<4>([](media_router::MediaRouteResponseCallback& callback) {
            std::move(callback).Run(
                media_router::mojom::RoutePresentationConnectionPtr(),
                media_router::RouteRequestResult(
                    std::make_unique<media_router::MediaRoute>(Route1()), "id",
                    "", media_router::mojom::RouteRequestResultCode::OK));
          }));
  EXPECT_CALL(*callback_ptr, sendSuccess());
  handler_->StartTabMirroring(kSinkName1, std::move(callback));
}

TEST_F(CastHandlerTest, StartTabMirroringWithInvalidName) {
  sinks_observer_->OnSinksUpdated({sink1}, {});
  auto callback = std::make_unique<MockStartTabMirroringCallback>();
  auto* callback_ptr = callback.get();

  // Attempting to start casting with a name different from that of the
  // discovered sink should fail.
  EXPECT_CALL(*callback_ptr, sendFailure(_));
  handler_->StartTabMirroring(kSinkName2, std::move(callback));
}

TEST_F(CastHandlerTest, StopCasting) {
  sinks_observer_->OnSinksUpdated({sink1, sink2}, {});
  router_->routes_observers().begin()->OnRoutesUpdated({Route1()});
  EXPECT_CALL(*router_, TerminateRoute(kRouteId1));
  EXPECT_TRUE(handler_->StopCasting(kSinkName1).IsSuccess());
}

TEST_F(CastHandlerTest, StopCastingWithInvalidName) {
  sinks_observer_->OnSinksUpdated({sink1, sink2}, {});
  router_->routes_observers().begin()->OnRoutesUpdated({Route1()});
  // Attempting to stop casting to a sink without a route should fail.
  EXPECT_TRUE(handler_->StopCasting(kSinkName2).IsError());
}