| // Copyright 2015 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/test/media_router_mojo_test.h" |
| |
| #include <utility> |
| |
| #include "base/compiler_specific.h" |
| #include "base/functional/bind.h" |
| #include "base/run_loop.h" |
| |
| using testing::_; |
| using testing::ByRef; |
| using testing::NiceMock; |
| using testing::Not; |
| using testing::Pointee; |
| using testing::WithArg; |
| |
| namespace media_router { |
| |
| namespace { |
| |
| const char kDescription[] = "description"; |
| const char kMessage[] = "message"; |
| const char kOrigin[] = "http://origin/"; |
| const char kPresentationId[] = "presentationId"; |
| const char kRouteId[] = "routeId"; |
| const char kSource[] = "source1"; |
| const char kSinkId[] = "sink"; |
| const int kInvalidFrameTreeNodeId = -1; |
| const int kTimeoutMillis = 5 * 1000; |
| const uint8_t kBinaryMessage[] = {0x01, 0x02, 0x03, 0x04}; |
| |
| MATCHER_P(Equals, value, "") { |
| return arg.Equals(value.get()); |
| } |
| |
| // Creates a media route whose ID is |kRouteId|. |
| MediaRoute CreateMediaRoute() { |
| MediaRoute route(kRouteId, MediaSource(kSource), kSinkId, kDescription, true); |
| route.set_presentation_id(kPresentationId); |
| route.set_controller_type(RouteControllerType::kGeneric); |
| return route; |
| } |
| |
| class SendMessageCallbackHandler { |
| public: |
| MOCK_METHOD1(Invoke, void(bool)); |
| }; |
| |
| class SinkResponseCallbackHandler { |
| public: |
| MOCK_METHOD1(Invoke, void(const std::string& sink_id)); |
| }; |
| |
| } // namespace |
| |
| MockMediaRouteProvider::MockMediaRouteProvider() = default; |
| |
| MockMediaRouteProvider::~MockMediaRouteProvider() = default; |
| |
| void MockMediaRouteProvider::RouteRequestSuccess(RouteCallback& cb) const { |
| DCHECK(route_); |
| std::move(cb).Run(route_, nullptr, std::string(), |
| mojom::RouteRequestResultCode::OK); |
| } |
| |
| void MockMediaRouteProvider::RouteRequestTimeout(RouteCallback& cb) const { |
| std::move(cb).Run(std::nullopt, nullptr, std::string("error"), |
| mojom::RouteRequestResultCode::TIMED_OUT); |
| } |
| |
| void MockMediaRouteProvider::TerminateRouteSuccess( |
| TerminateRouteCallback& cb) const { |
| std::move(cb).Run(std::string(), mojom::RouteRequestResultCode::OK); |
| } |
| |
| void MockMediaRouteProvider::BindMediaControllerSuccess( |
| BindMediaControllerCallback& cb) const { |
| std::move(cb).Run(true); |
| } |
| |
| void MockMediaRouteProvider::SetRouteToReturn(const MediaRoute& route) { |
| route_ = route; |
| } |
| |
| MockMediaStatusObserver::MockMediaStatusObserver( |
| mojo::PendingReceiver<mojom::MediaStatusObserver> receiver) |
| : receiver_(this, std::move(receiver)) {} |
| |
| MockMediaStatusObserver::~MockMediaStatusObserver() = default; |
| |
| MockMediaController::MockMediaController() = default; |
| |
| MockMediaController::~MockMediaController() = default; |
| |
| void MockMediaController::Bind( |
| mojo::PendingReceiver<mojom::MediaController> receiver) { |
| receiver_.reset(); |
| receiver_.Bind(std::move(receiver)); |
| } |
| |
| mojo::PendingRemote<mojom::MediaController> |
| MockMediaController::BindInterfaceRemote() { |
| return receiver_.BindNewPipeAndPassRemote(); |
| } |
| |
| void MockMediaController::CloseReceiver() { |
| receiver_.reset(); |
| } |
| |
| MediaRouterMojoTest::MediaRouterMojoTest() = default; |
| |
| MediaRouterMojoTest::~MediaRouterMojoTest() = default; |
| |
| void MediaRouterMojoTest::RegisterCastProvider() { |
| RegisterMediaRouteProvider(&mock_cast_provider_, |
| mojom::MediaRouteProviderId::CAST); |
| } |
| |
| void MediaRouterMojoTest::RegisterWiredDisplayProvider() { |
| RegisterMediaRouteProvider(&mock_wired_display_provider_, |
| mojom::MediaRouteProviderId::WIRED_DISPLAY); |
| } |
| |
| void MediaRouterMojoTest::SetUp() { |
| media_router_ = CreateMediaRouter(); |
| RegisterCastProvider(); |
| media_router_->Initialize(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void MediaRouterMojoTest::TearDown() { |
| sinks_observer_.reset(); |
| routes_observer_.reset(); |
| media_router_->Shutdown(); |
| media_router_.reset(); |
| } |
| |
| void MediaRouterMojoTest::ProvideTestRoute( |
| mojom::MediaRouteProviderId provider_id, |
| const MediaRoute::Id& route_id) { |
| if (!routes_observer_) { |
| routes_observer_ = std::make_unique<MediaRoutesObserver>(router()); |
| } |
| MediaRoute route = CreateMediaRoute(); |
| route.set_media_route_id(route_id); |
| router()->OnRoutesUpdated(provider_id, {route}); |
| } |
| |
| void MediaRouterMojoTest::ProvideTestSink( |
| mojom::MediaRouteProviderId provider_id, |
| const MediaSink::Id& sink_id) { |
| if (!sinks_observer_) { |
| sinks_observer_ = std::make_unique<NiceMock<MockMediaSinksObserver>>( |
| router(), MediaSource(kSource), url::Origin::Create(GURL(kOrigin))); |
| router()->RegisterMediaSinksObserver(sinks_observer_.get()); |
| } |
| |
| MediaSinkInternal test_sink; |
| test_sink.sink().set_sink_id(sink_id); |
| test_sink.sink().set_provider_id(provider_id); |
| router()->OnSinksReceived(provider_id, kSource, {test_sink}, {}); |
| } |
| |
| void MediaRouterMojoTest::TestCreateRoute() { |
| MediaSource media_source(kSource); |
| MediaRoute expected_route(kRouteId, media_source, kSinkId, kDescription, |
| true); |
| expected_route.set_presentation_id(kPresentationId); |
| expected_route.set_controller_type(RouteControllerType::kGeneric); |
| |
| ProvideTestSink(mojom::MediaRouteProviderId::CAST, kSinkId); |
| |
| // Use a lambda function as an invocation target here to work around |
| // a limitation with GMock::Invoke that prevents it from using move-only types |
| // in runnable parameter lists. |
| EXPECT_CALL(mock_cast_provider_, |
| CreateRouteInternal(kSource, kSinkId, _, |
| url::Origin::Create(GURL(kOrigin)), |
| kInvalidFrameTreeNodeId, _, _)) |
| .WillOnce( |
| WithArg<6>([](mojom::MediaRouteProvider::CreateRouteCallback& cb) { |
| std::move(cb).Run(CreateMediaRoute(), nullptr, std::string(), |
| mojom::RouteRequestResultCode::OK); |
| })); |
| |
| RouteResponseCallbackHandler handler; |
| EXPECT_CALL(handler, DoInvoke(Pointee(expected_route), Not(""), "", |
| mojom::RouteRequestResultCode::OK, _)); |
| router()->CreateRoute(kSource, kSinkId, url::Origin::Create(GURL(kOrigin)), |
| nullptr, |
| base::BindOnce(&RouteResponseCallbackHandler::Invoke, |
| base::Unretained(&handler)), |
| base::Milliseconds(kTimeoutMillis)); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void MediaRouterMojoTest::TestJoinRoute(const std::string& presentation_id) { |
| MediaSource media_source(kSource); |
| MediaRoute expected_route(kRouteId, media_source, kSinkId, kDescription, |
| true); |
| expected_route.set_presentation_id(kPresentationId); |
| expected_route.set_controller_type(RouteControllerType::kGeneric); |
| |
| MediaRoute route = CreateMediaRoute(); |
| // Make sure the MR has received an update with the route, so it knows there |
| // is a route to join. |
| std::vector<MediaRoute> routes; |
| routes.push_back(route); |
| router()->OnRoutesUpdated(mojom::MediaRouteProviderId::CAST, routes); |
| EXPECT_TRUE(router()->HasJoinableRoute()); |
| |
| // Use a lambda function as an invocation target here to work around |
| // a limitation with GMock::Invoke that prevents it from using move-only types |
| // in runnable parameter lists. |
| EXPECT_CALL(mock_cast_provider_, |
| JoinRouteInternal(kSource, presentation_id, |
| url::Origin::Create(GURL(kOrigin)), |
| kInvalidFrameTreeNodeId, |
| base::Milliseconds(kTimeoutMillis), _)) |
| .WillOnce(WithArg<5>( |
| [&route](mojom::MediaRouteProvider::JoinRouteCallback& cb) { |
| std::move(cb).Run(route, nullptr, std::string(), |
| mojom::RouteRequestResultCode::OK); |
| })); |
| |
| RouteResponseCallbackHandler handler; |
| EXPECT_CALL(handler, DoInvoke(Pointee(expected_route), Not(""), "", |
| mojom::RouteRequestResultCode::OK, _)); |
| router()->JoinRoute(kSource, presentation_id, |
| url::Origin::Create(GURL(kOrigin)), nullptr, |
| base::BindOnce(&RouteResponseCallbackHandler::Invoke, |
| base::Unretained(&handler)), |
| base::Milliseconds(kTimeoutMillis)); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void MediaRouterMojoTest::TestTerminateRoute() { |
| ProvideTestRoute(mojom::MediaRouteProviderId::CAST, kRouteId); |
| EXPECT_CALL(mock_cast_provider_, TerminateRouteInternal(kRouteId, _)) |
| .WillOnce([](const std::string& route_id, |
| mojom::MediaRouteProvider::TerminateRouteCallback& cb) { |
| std::move(cb).Run(std::nullopt, mojom::RouteRequestResultCode::OK); |
| }); |
| router()->TerminateRoute(kRouteId); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void MediaRouterMojoTest::TestSendRouteMessage() { |
| ProvideTestRoute(mojom::MediaRouteProviderId::CAST, kRouteId); |
| EXPECT_CALL(mock_cast_provider_, SendRouteMessage(kRouteId, kMessage)); |
| router()->SendRouteMessage(kRouteId, kMessage); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void MediaRouterMojoTest::TestSendRouteBinaryMessage() { |
| ProvideTestRoute(mojom::MediaRouteProviderId::CAST, kRouteId); |
| auto expected_binary_data = std::make_unique<std::vector<uint8_t>>( |
| std::begin(kBinaryMessage), std::end(kBinaryMessage)); |
| EXPECT_CALL(mock_cast_provider_, SendRouteBinaryMessage(kRouteId, _)) |
| .WillOnce([](const MediaRoute::Id& route_id, |
| const std::vector<uint8_t>& data) { |
| UNSAFE_TODO(EXPECT_EQ( |
| 0, memcmp(kBinaryMessage, &(data[0]), std::size(kBinaryMessage)))); |
| }); |
| |
| router()->SendRouteBinaryMessage(kRouteId, std::move(expected_binary_data)); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void MediaRouterMojoTest::TestDetachRoute() { |
| ProvideTestRoute(mojom::MediaRouteProviderId::CAST, kRouteId); |
| EXPECT_CALL(mock_cast_provider_, DetachRoute(kRouteId)); |
| router()->DetachRoute(kRouteId); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void MediaRouterMojoTest::RegisterMediaRouteProvider( |
| mojom::MediaRouteProvider* provider, |
| mojom::MediaRouteProviderId provider_id) { |
| mojo::PendingRemote<mojom::MediaRouteProvider> mojo_provider; |
| provider_receivers_.Add(provider, |
| mojo_provider.InitWithNewPipeAndPassReceiver()); |
| media_router_->RegisterMediaRouteProvider(provider_id, |
| std::move(mojo_provider)); |
| } |
| |
| RouteResponseCallbackHandler::RouteResponseCallbackHandler() = default; |
| RouteResponseCallbackHandler::~RouteResponseCallbackHandler() = default; |
| |
| void RouteResponseCallbackHandler::Invoke( |
| mojom::RoutePresentationConnectionPtr connection, |
| const RouteRequestResult& result) { |
| DoInvoke(result.route(), result.presentation_id(), result.error(), |
| result.result_code(), connection); |
| } |
| |
| } // namespace media_router |