blob: 4c9ced4420efa10a9df62e150090be7a024833eb [file] [log] [blame]
// 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());
}