// Copyright 2022 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/ui/media_router/media_route_starter.h"

#include <algorithm>

#include "base/json/json_reader.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "chrome/browser/media/router/chrome_media_router_factory.h"
#include "chrome/browser/media/router/test/provider_test_helpers.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/sessions/session_tab_helper_factory.h"
#include "chrome/browser/ui/global_media_controls/test_helper.h"
#include "chrome/browser/ui/media_router/media_cast_mode.h"
#include "chrome/browser/ui/media_router/query_result_manager.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/media_router/browser/logger_impl.h"
#include "components/media_router/browser/media_router_factory.h"
#include "components/media_router/browser/media_sinks_observer.h"
#include "components/media_router/browser/presentation/web_contents_presentation_manager.h"
#include "components/media_router/browser/test/mock_media_router.h"
#include "components/media_router/browser/test/test_helper.h"
#include "components/media_router/common/discovery/media_sink_internal.h"
#include "components/media_router/common/media_sink.h"
#include "components/media_router/common/route_request_result.h"
#include "components/media_router/common/test/test_helper.h"
#include "components/sessions/content/session_tab_helper.h"
#include "components/sessions/core/session_id.h"
#include "content/public/browser/presentation_request.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"

#if BUILDFLAG(IS_MAC)
#include "base/mac/mac_util.h"
#include "ui/base/cocoa/permissions_utils.h"
#endif

using testing::_;
using testing::NiceMock;
using testing::Return;

namespace media_router {

namespace {
constexpr char kPresentationId[] = "session-67890";
constexpr char kLoggerComponent[] = "MediaRouteStarterTests";

const CastModeSet kDefaultModes = {MediaCastMode::PRESENTATION,
                                   MediaCastMode::TAB_MIRROR,
                                   MediaCastMode::DESKTOP_MIRROR};
const CastModeSet kMirroringOnly = {MediaCastMode::TAB_MIRROR,
                                    MediaCastMode::DESKTOP_MIRROR};
const CastModeSet kPresentationOnly = {MediaCastMode::PRESENTATION};
const CastModeSet kDestkopMirrorOnly = {MediaCastMode::DESKTOP_MIRROR};
const CastModeSet kRemotePlaybackOnly = {MediaCastMode::REMOTE_PLAYBACK};

constexpr char kDefaultPresentationUrl[] = "https://defaultpresentation.com/";
constexpr char kDefaultOriginUrl[] = "https://default.fakeurl/";

constexpr char kStartPresentationUrl[] = "https://startpresentrequest.com/";
constexpr char kStartOriginUrl[] = "https://start.fakeurl/";

constexpr char kRemotePlaybackUrl[] =
    "remote-playback:media-element?source=encoded_data&video_codec=vp8&audio_"
    "codec=mp3";

class MockPresentationRequestSourceObserver
    : public PresentationRequestSourceObserver {
 public:
  MockPresentationRequestSourceObserver() = default;
  explicit MockPresentationRequestSourceObserver(MediaRouteStarter* starter)
      : starter_(starter) {
    starter_->AddPresentationRequestSourceObserver(this);
  }

  ~MockPresentationRequestSourceObserver() override {
    if (starter_) {
      starter_->RemovePresentationRequestSourceObserver(this);
    }
  }

  MOCK_METHOD(void, OnSourceUpdated, (std::u16string&));

 private:
  raw_ptr<MediaRouteStarter> starter_ = nullptr;
};

// For demonstrating that presentation mode callbacks are made from the MRS
// d'tor
class PresentationRequestCallbacks {
 public:
  PresentationRequestCallbacks() = default;

  explicit PresentationRequestCallbacks(
      const blink::mojom::PresentationError& expected_error)
      : expected_error_(expected_error) {}
  ~PresentationRequestCallbacks() { EXPECT_TRUE(called_); }

  void Success(const blink::mojom::PresentationInfo&,
               mojom::RoutePresentationConnectionPtr,
               const MediaRoute&) {
    NOTREACHED();
  }

  void Error(const blink::mojom::PresentationError& error) {
    EXPECT_EQ(expected_error_.error_type, error.error_type);
    EXPECT_EQ(expected_error_.message, error.message);
    called_ = true;
  }

 private:
  blink::mojom::PresentationError expected_error_;
  bool called_ = false;
};

}  // namespace

class MediaRouteStarterTest : public ChromeRenderViewHostTestHarness {
 public:
  // For demonstrating that presentation mode casting callbacks are called.
  MOCK_METHOD(void,
              RequestSuccess,
              (const blink::mojom::PresentationInfo&,
               mojom::RoutePresentationConnectionPtr,
               const MediaRoute&));
  MOCK_METHOD(void,
              RequestError,
              (const blink::mojom::PresentationError& error));

 protected:
  void SetUp() override {
    ChromeRenderViewHostTestHarness::SetUp();

    profile_manager_ = std::make_unique<TestingProfileManager>(
        TestingBrowserProcess::GetGlobal());
    ASSERT_TRUE(profile_manager_->SetUp());

    presentation_manager_ =
        std::make_unique<NiceMock<MockWebContentsPresentationManager>>();
    WebContentsPresentationManager::SetTestInstance(presentation_manager());

    SetMediaRouterFactory();
    logger_ = std::make_unique<LoggerImpl>();

    CreateSessionServiceTabHelper(web_contents());

    cast_sink_ = CreateCastSink(1);
    dial_sink_ = CreateDialSink(1);

    CreateStarterForDefaultModes();
  }

  void TearDown() override {
    clear_screen_capture_allowed_for_testing();
    DestroyMediaRouteStarter();
    profile_manager_->DeleteAllTestingProfiles();
    profile_manager_.reset();
    WebContentsPresentationManager::SetTestInstance(nullptr);
    ChromeRenderViewHostTestHarness::TearDown();
  }

  virtual void SetMediaRouterFactory() {
    ChromeMediaRouterFactory::GetInstance()->SetTestingFactory(
        GetBrowserContext(), base::BindRepeating(&MockMediaRouter::Create));
    Profile* default_profile =
#if BUILDFLAG(IS_CHROMEOS)
        ProfileManager::GetActiveUserProfile();
#else
        ProfileManager::GetLastUsedProfile();
#endif  // BUILDFLAG(IS_CHROMEOS)

    ChromeMediaRouterFactory::GetInstance()->SetTestingFactory(
        default_profile, base::BindRepeating(&MockMediaRouter::Create));
  }

  void CreateStarterForDefaultModes() {
    CreateStarter(MediaRouterUIParameters(kDefaultModes, web_contents()));
  }

  void CreateStarter(MediaRouterUIParameters params) {
    starter_ = std::make_unique<MediaRouteStarter>(std::move(params));
    starter_->SetLoggerComponent(kLoggerComponent);
    ON_CALL(*media_router(), GetLogger()).WillByDefault(Return(logger_.get()));
    // Store sink observers so that they can be notified in tests.
    ON_CALL(*media_router(), RegisterMediaSinksObserver(_))
        .WillByDefault([this](MediaSinksObserver* observer) {
          media_sinks_observers_.push_back(observer);
          return true;
        });
    // Remove sink observers as appropriate (destructing handlers will cause
    // this to occur).
    ON_CALL(*media_router(), UnregisterMediaSinksObserver(_))
        .WillByDefault([this](MediaSinksObserver* observer) {
          auto it = std::ranges::find(media_sinks_observers_, observer);
          if (it != media_sinks_observers_.end()) {
            media_sinks_observers_.erase(it);
          }
        });
    // Handler so MockMediaRouter will respond to requests to create a route.
    // Will construct a RouteRequestResult based on the set result code and
    // then call the handler's callback.
    ON_CALL(*media_router(), CreateRouteInternal(_, _, _, _, _, _))
        .WillByDefault([this](const MediaSource::Id& source_id,
                              const MediaSink::Id& sink_id,
                              const url::Origin& origin,
                              content::WebContents* web_contents,
                              MediaRouteResponseCallback& callback,
                              base::TimeDelta timeout) {
          // This indicates the test did not properly set the expected result
          EXPECT_NE(mojom::RouteRequestResultCode::UNKNOWN_ERROR, result_code_);
          std::unique_ptr<RouteRequestResult> result;
          if (result_code_ == mojom::RouteRequestResultCode::OK) {
            result = GetSuccessResult(source_id, sink_id);
          } else {
            result = GetErrorResult(result_code_);
          }
          std::move(callback).Run(nullptr, *result);
        });
  }

  MediaRouteStarter* media_route_starter() { return starter_.get(); }
  TestingProfileManager* profile_manager() { return profile_manager_.get(); }
  MockMediaRouter* media_router() {
    return static_cast<MockMediaRouter*>(
        media_route_starter()->GetMediaRouter());
  }
  LoggerImpl* logger() { return logger_.get(); }

  const std::vector<raw_ptr<MediaSinksObserver, VectorExperimental>>
  media_sink_observers() {
    return media_sinks_observers_;
  }

  MockWebContentsPresentationManager* presentation_manager() {
    return presentation_manager_.get();
  }

  QueryResultManager* query_result_manager() {
    return media_route_starter()->GetQueryResultManager();
  }

  const RouteRequestResult* route_request_result() {
    return route_request_result_.get();
  }

  const MediaSinkInternal& cast_sink() { return cast_sink_; }
  const MediaSinkInternal& dial_sink() { return dial_sink_; }

  const content::PresentationRequest& default_presentation_request() {
    return default_presentation_request_;
  }
  const content::PresentationRequest& start_presentation_request() {
    return start_presentation_request_;
  }

  void set_expected_cast_result(mojom::RouteRequestResultCode code) {
    result_code_ = code;
  }

  std::unique_ptr<StartPresentationContext> CreateStartPresentationContext(
      const content::PresentationRequest& presentation_request) {
    return std::make_unique<StartPresentationContext>(
        presentation_request,
        base::BindOnce(&MediaRouteStarterTest::RequestSuccess,
                       base::Unretained(this)),
        base::BindOnce(&MediaRouteStarterTest::RequestError,
                       base::Unretained(this)));
  }

  // The caller must hold on to PresentationRequestCallbacks returned so that
  // a callback can later be called on it.
  std::unique_ptr<PresentationRequestCallbacks> ExpectPresentationError(
      const content::PresentationRequest& presentation_request,
      blink::mojom::PresentationErrorType error_type,
      const std::string& error_message) {
    blink::mojom::PresentationError expected_error(error_type, error_message);
    auto request_callbacks =
        std::make_unique<PresentationRequestCallbacks>(expected_error);
    auto start_presentation_context =
        std::make_unique<StartPresentationContext>(
            presentation_request,
            base::BindOnce(&PresentationRequestCallbacks::Success,
                           base::Unretained(request_callbacks.get())),
            base::BindOnce(&PresentationRequestCallbacks::Error,
                           base::Unretained(request_callbacks.get())));
    CreateStarter(
        MediaRouterUIParameters(kPresentationOnly, web_contents(),
                                std::move(start_presentation_context)));
    return request_callbacks;
  }

  void UpdateSinks(const std::vector<MediaSink>& sinks,
                   const std::vector<url::Origin>& origins) {
    for (MediaSinksObserver* sinks_observer : media_sink_observers()) {
      sinks_observer->OnSinksUpdated(sinks, origins);
    }
  }

  std::string GetLogEntry(const std::string& logs_json,
                          const std::string& attribute) {
    base::Value logs =
        base::JSONReader::Read(logs_json, base::JSON_PARSE_CHROMIUM_EXTENSIONS)
            .value();
    return *logs.GetList()[0].GetDict().FindString(attribute);
  }

  void DestroyMediaRouteStarter() { starter_.reset(); }

  std::unique_ptr<RouteRequestResult> GetSuccessResult(
      const MediaSource::Id& source_id,
      const MediaSink::Id& sink_id) {
    MediaSource source(source_id);
    MediaRoute route;
    route.set_media_route_id(source_id + "->" + sink_id);
    route.set_media_source(source);
    route.set_media_sink_id(sink_id);
    return RouteRequestResult::FromSuccess(
        route, IsValidPresentationUrl(source.url()) ? kPresentationId : "");
  }

  std::unique_ptr<RouteRequestResult> GetSuccessResult(
      const content::PresentationRequest& request,
      const MediaSink::Id& sink_id) {
    auto source =
        MediaSource::ForPresentationUrl(*(request.presentation_urls.begin()))
            .id();
    return GetSuccessResult(source, sink_id);
  }

  std::unique_ptr<RouteRequestResult> GetErrorResult(
      mojom::RouteRequestResultCode result_code) {
    return RouteRequestResult::FromError("unit test error", result_code);
  }

  void StartMirroring(const MediaSinkInternal& sink, MediaCastMode cast_mode) {
    EXPECT_NE(MediaCastMode::PRESENTATION, cast_mode);

    // Add a sink
    UpdateSinks({sink.sink()}, std::vector<url::Origin>());

    auto params =
        media_route_starter()->CreateRouteParameters(sink.id(), cast_mode);
    EXPECT_TRUE(params);

    if (cast_mode == MediaCastMode::DESKTOP_MIRROR) {
      set_screen_capture_allowed_for_testing(true);
    }

    StartRoute(std::move(params));
  }

  void StartPresentation(const MediaSinkInternal& sink,
                         const content::PresentationRequest& request) {
    // Add a presentation compatible sink
    UpdateSinks({sink.sink()}, {request.frame_origin});

    auto params = media_route_starter()->CreateRouteParameters(
        sink.id(), MediaCastMode::PRESENTATION);

    EXPECT_TRUE(params);

    StartRoute(std::move(params));
  }

  void StartRemotePlayback(const MediaSinkInternal& sink) {
    UpdateSinks({sink.sink()}, std::vector<url::Origin>());

    auto params = media_route_starter()->CreateRouteParameters(
        sink.id(), MediaCastMode::REMOTE_PLAYBACK);
    EXPECT_TRUE(params);
    StartRoute(std::move(params));
  }

  content::PresentationRequest CreatePresentationRequest(
      const std::string& presentation_url,
      const std::string& origin_url) {
    content::PresentationRequest presentation_request(
        {0, 0}, {GURL(presentation_url)},
        url::Origin::Create(GURL(origin_url)));
    return presentation_request;
  }

 private:
  void StartRoute(std::unique_ptr<RouteParameters> params) {
    // To demonstrate that MediaRouteResultCallbacks are called
    params->route_result_callbacks.emplace_back(
        base::BindOnce(&MediaRouteStarterTest::HandleMediaRouteResponse,
                       base::Unretained(this)));

    EXPECT_CALL(*media_router(),
                CreateRouteInternal(params->source_id, params->request->sink_id,
                                    params->origin, web_contents(), _,
                                    params->timeout));

    media_route_starter()->StartRoute(std::move(params));
  }

  // For demonstrating that the MediaRouteResultCallbacks are called.
  void HandleMediaRouteResponse(const RouteRequestResult& result) {
    // Store the response so tests can examine it.
    if (result.result_code() == mojom::RouteRequestResultCode::OK) {
      auto route = std::make_unique<MediaRoute>(*result.route());
      route_request_result_ = std::make_unique<RouteRequestResult>(
          std::move(route), result.presentation_id(), result.error(),
          result.result_code());
    } else {
      route_request_result_ =
          RouteRequestResult::FromError(result.error(), result.result_code());
    }
  }

  std::unique_ptr<MediaRouteStarter> starter_;

  std::unique_ptr<TestingProfileManager> profile_manager_;

  std::unique_ptr<LoggerImpl> logger_;
  std::vector<raw_ptr<MediaSinksObserver, VectorExperimental>>
      media_sinks_observers_;

  std::unique_ptr<MockWebContentsPresentationManager> presentation_manager_;

  MediaSinkInternal cast_sink_;
  MediaSinkInternal dial_sink_;

  content::PresentationRequest default_presentation_request_ =
      CreatePresentationRequest(kDefaultPresentationUrl, kDefaultOriginUrl);
  content::PresentationRequest start_presentation_request_ =
      CreatePresentationRequest(kStartPresentationUrl, kStartOriginUrl);

  mojom::RouteRequestResultCode result_code_ =
      mojom::RouteRequestResultCode::UNKNOWN_ERROR;
  std::unique_ptr<RouteRequestResult> route_request_result_;
};

// Demonstrates that when initialized with webcontents but no presentation
// source the supported modes are mirroring only.
TEST_F(MediaRouteStarterTest, Defaults_NoPresentation) {
  CreateStarterForDefaultModes();
  EXPECT_EQ(kMirroringOnly, query_result_manager()->GetSupportedCastModes());
}

// Demonstrates that when initialized with webcontents that has a default
// presentation that presentation mode is supported.
TEST_F(MediaRouteStarterTest, Defaults_WebContentPresentation) {
  presentation_manager()->SetDefaultPresentationRequest(
      default_presentation_request());

  CreateStarterForDefaultModes();
  EXPECT_EQ(kDefaultModes, query_result_manager()->GetSupportedCastModes());
}

// Demonstrates that if caller doesn't request mirroring that it is not
// available.
TEST_F(MediaRouteStarterTest, Defaults_WebContentPresentationOnly) {
  presentation_manager()->SetDefaultPresentationRequest(
      default_presentation_request());

  CreateStarter(MediaRouterUIParameters(kPresentationOnly, web_contents()));
  EXPECT_EQ(kPresentationOnly, query_result_manager()->GetSupportedCastModes());
}

// Demonstrates that when initialized with a presentation request that
// presentation mode is supported - even if the web contents has no default
// presentation.
TEST_F(MediaRouteStarterTest, Defaults_StartPresentationContext) {
  auto start_presentation_context =
      CreateStartPresentationContext(start_presentation_request());

  CreateStarter(MediaRouterUIParameters(kDefaultModes, web_contents(),
                                        std::move(start_presentation_context)));
  EXPECT_EQ(kDefaultModes, query_result_manager()->GetSupportedCastModes());

  // This is to deal with the error callback in the d'tor that's not part of
  // this test. See the Dtor_* tests below where this case is actually
  // validated.
  EXPECT_CALL(*this, RequestError(_));
}

// Demonstrates that when initialized with Remote Playback mode
TEST_F(MediaRouteStarterTest, Defaults_RemotePlayback) {
  CreateStarter(MediaRouterUIParameters(kRemotePlaybackOnly, web_contents(),
                                        nullptr, media::VideoCodec::kVP8,
                                        media::AudioCodec::kOpus));
  EXPECT_EQ(kRemotePlaybackOnly,
            query_result_manager()->GetSupportedCastModes());
}

// Demonstrates that when initialized with no webcontent or presentation source
// the supported modes are desktop mirroring only.
TEST_F(MediaRouteStarterTest, Defaults_NoWebContent) {
  CreateStarter(MediaRouterUIParameters(kDefaultModes, nullptr));
  EXPECT_EQ(kDestkopMirrorOnly,
            query_result_manager()->GetSupportedCastModes());
}

// Demonstrates that when MediaRouteStarter is notified that the presentation
// request source has changed, that it alerts observers with the name of that
// presentation request source.
TEST_F(MediaRouteStarterTest, OnPresentationRequestSourceUpdated) {
  CreateStarterForDefaultModes();
  EXPECT_EQ(kMirroringOnly, query_result_manager()->GetSupportedCastModes());

  MockPresentationRequestSourceObserver observer(media_route_starter());

  presentation_manager()->SetDefaultPresentationRequest(
      default_presentation_request());

  std::u16string expected_name(u"default.fakeurl");
  EXPECT_CALL(observer, OnSourceUpdated(expected_name));

  // Simulate the notification that the default presentation has changed.
  media_route_starter()->OnDefaultPresentationChanged(
      &presentation_manager()->GetDefaultPresentationRequest());

  // Now that a default presentation has been added the available modes should
  // include presentation.
  EXPECT_EQ(kDefaultModes, query_result_manager()->GetSupportedCastModes());
}

// Demonstrates that when MediaRouteStarter is notified that there is no
// presentation request source that it alerts observers that the name of the
// presentation source is empty.
TEST_F(MediaRouteStarterTest, OnPresentationRequestSourceRemoved) {
  presentation_manager()->SetDefaultPresentationRequest(
      default_presentation_request());

  CreateStarterForDefaultModes();
  EXPECT_EQ(kDefaultModes, query_result_manager()->GetSupportedCastModes());

  MockPresentationRequestSourceObserver observer(media_route_starter());

  // Simulate the notification that the default presentation has been removed.
  std::u16string expected_name(u"");
  EXPECT_CALL(observer, OnSourceUpdated(expected_name));

  media_route_starter()->OnDefaultPresentationChanged(nullptr);

  // Now that a default presentation has been added the available modes should
  // include presentation.
  EXPECT_EQ(kMirroringOnly, query_result_manager()->GetSupportedCastModes());
}

// Demonstrates that if MediaRouteStarter is destroyed without an attempt to
// create a route with the presentation source, that the expected error is
// reported to the presentation source.
TEST_F(MediaRouteStarterTest, Dtor_NotFoundError_NoSinks) {
  auto request_callbacks = ExpectPresentationError(
      default_presentation_request(),
      blink::mojom::PresentationErrorType::NO_AVAILABLE_SCREENS,
      "No screens found.");

  // Destroying the starter should return the expected error from above to the
  // error callback.
  DestroyMediaRouteStarter();
}

// Same as above, but demonstrates the same error reporting even if sinks exist,
// so long as none of the sinks were compatible with the presentation source.
TEST_F(MediaRouteStarterTest, Dtor_NotFoundError_NoCompatibleSinks) {
  auto request_callbacks = ExpectPresentationError(
      default_presentation_request(),
      blink::mojom::PresentationErrorType::NO_AVAILABLE_SCREENS,
      "No screens found.");
  // Send a sink to the UI that is compatible with sources other than the
  // presentation url to cause a NotFoundError.
  std::vector<MediaSink> sinks = {dial_sink().sink()};
  auto presentation_source = MediaSource::ForPresentationUrl(
      default_presentation_request().presentation_urls[0]);
  for (MediaSinksObserver* sinks_observer : media_sink_observers()) {
    if (!(sinks_observer->source() == presentation_source)) {
      sinks_observer->OnSinksUpdated(sinks, {});
    }
  }

  // Destroying the starter should return the expected error from above to the
  // error callback.
  DestroyMediaRouteStarter();
}

// Same as above, but demonstrates that if a compatible sink was present, then
// the error that is reported indicates that the request was cancelled.
TEST_F(MediaRouteStarterTest, Dtor_AbortError) {
  auto request_callbacks = ExpectPresentationError(
      default_presentation_request(),
      blink::mojom::PresentationErrorType::PRESENTATION_REQUEST_CANCELLED,
      "Dialog closed.");
  // Send a sink to the UI that is compatible with the presentation url to avoid
  // a NotFoundError.
  std::vector<MediaSink> sinks = {dial_sink().sink()};
  auto presentation_source = MediaSource::ForPresentationUrl(
      default_presentation_request().presentation_urls[0]);
  for (MediaSinksObserver* sinks_observer : media_sink_observers()) {
    if (sinks_observer->source() == presentation_source) {
      sinks_observer->OnSinksUpdated(sinks, {});
    }
  }

  // Destroying the starter should return the expected error from above to the
  // error callback.
  DestroyMediaRouteStarter();
}

// Demonstrates that if there are no sources available for the desired mode
// CreateRouteParameters returns nothing.
TEST_F(MediaRouteStarterTest, CreateRouteParameters_NoValidSource) {
  // No presentation available
  CreateStarter(MediaRouterUIParameters(kMirroringOnly, web_contents()));

  // Add a sink
  UpdateSinks({cast_sink().sink()}, std::vector<url::Origin>());

  auto params = media_route_starter()->CreateRouteParameters(
      cast_sink().id(), MediaCastMode::PRESENTATION);

  EXPECT_FALSE(params);
}

// Demonstrates that when desktop mirroring is available and requested that the
// RouteParameters are properly filled out.
TEST_F(MediaRouteStarterTest, CreateRouteParameters_DesktopMirroring) {
  CreateStarterForDefaultModes();

  // Add a sink
  UpdateSinks({cast_sink().sink()}, std::vector<url::Origin>());

  auto params = media_route_starter()->CreateRouteParameters(
      cast_sink().id(), MediaCastMode::DESKTOP_MIRROR);

  EXPECT_EQ(MediaCastMode::DESKTOP_MIRROR, params->cast_mode);
  EXPECT_EQ(MediaSource::ForUnchosenDesktop().id(), params->source_id);
  EXPECT_EQ(cast_sink().id(), params->request->sink_id);
  EXPECT_EQ(GURL(), params->origin.GetURL());
  // route_result_callbacks should only be filled in by caller
  EXPECT_EQ(0ul, params->route_result_callbacks.size());
  EXPECT_EQ(base::Seconds(120), params->timeout);
}

// Demonstrates that when tab mirroring is available and requested that the
// RouteParameters are properly filled out.
TEST_F(MediaRouteStarterTest, CreateRouteParameters_TabMirroring) {
  SessionID::id_type tab_id =
      sessions::SessionTabHelper::IdForTab(web_contents()).id();

  CreateStarterForDefaultModes();

  // Add a sink
  UpdateSinks({cast_sink().sink()}, std::vector<url::Origin>());

  auto params = media_route_starter()->CreateRouteParameters(
      cast_sink().id(), MediaCastMode::TAB_MIRROR);

  EXPECT_EQ(MediaCastMode::TAB_MIRROR, params->cast_mode);
  EXPECT_EQ(MediaSource::ForTab(tab_id).id(), params->source_id);
  EXPECT_EQ(cast_sink().id(), params->request->sink_id);
  EXPECT_EQ(GURL(), params->origin.GetURL());
  // route_result_callbacks should only be filled in by caller
  EXPECT_EQ(0ul, params->route_result_callbacks.size());
  EXPECT_EQ(base::Seconds(60), params->timeout);
}

// Demonstrates that when presentation mode is available for the default
// presentation and requested that the RouteParameters are properly filled out.
TEST_F(MediaRouteStarterTest, CreateRouteParameters_WebContentPresentation) {
  presentation_manager()->SetDefaultPresentationRequest(
      default_presentation_request());

  CreateStarterForDefaultModes();

  // Add a presentation compatible sink
  UpdateSinks({cast_sink().sink()},
              {default_presentation_request().frame_origin});

  auto params = media_route_starter()->CreateRouteParameters(
      cast_sink().id(), MediaCastMode::PRESENTATION);

  EXPECT_EQ(MediaCastMode::PRESENTATION, params->cast_mode);
  EXPECT_EQ(MediaSource::ForPresentationUrl(
                *(default_presentation_request()).presentation_urls.begin())
                .id(),
            params->source_id);
  EXPECT_EQ(cast_sink().id(), params->request->sink_id);
  EXPECT_EQ(default_presentation_request().frame_origin, params->origin);
  // route_result_callbacks should only be filled in by caller
  EXPECT_EQ(0ul, params->route_result_callbacks.size());
  EXPECT_EQ(base::Seconds(20), params->timeout);
}

// Demonstrates that when presentation mode is requested and a start
// presentation context is available that the RouteParameters are correctly
// filled out.
TEST_F(MediaRouteStarterTest, CreateRouteParameters_StartPresentationContext) {
  auto start_presentation_context =
      CreateStartPresentationContext(start_presentation_request());

  CreateStarter(MediaRouterUIParameters(kDefaultModes, web_contents(),
                                        std::move(start_presentation_context)));

  // Add a presentation compatible sink
  UpdateSinks({cast_sink().sink()},
              {start_presentation_request().frame_origin});

  auto params = media_route_starter()->CreateRouteParameters(
      cast_sink().id(), MediaCastMode::PRESENTATION);

  EXPECT_EQ(MediaCastMode::PRESENTATION, params->cast_mode);
  EXPECT_EQ(MediaSource::ForPresentationUrl(
                *(start_presentation_request().presentation_urls.begin()))
                .id(),
            params->source_id);
  EXPECT_EQ(cast_sink().id(), params->request->sink_id);
  EXPECT_EQ(start_presentation_request().frame_origin, params->origin);
  // route_result_callbacks should only be filled in by caller
  EXPECT_EQ(0ul, params->route_result_callbacks.size());
  EXPECT_EQ(base::Seconds(20), params->timeout);

  // This is to deal with the error callback in the d'tor that's not part of
  // this test. See the Dtor_* tests below where this case is actually
  // validated.
  EXPECT_CALL(*this, RequestError(_));
}

// Demonstrates that desktop mirroring routes are created correctly.
TEST_F(MediaRouteStarterTest, StartRoute_DesktopMirroring) {
  CreateStarterForDefaultModes();

  set_expected_cast_result(mojom::RouteRequestResultCode::OK);

  StartMirroring(cast_sink(), MediaCastMode::DESKTOP_MIRROR);

  EXPECT_EQ(mojom::RouteRequestResultCode::OK,
            route_request_result()->result_code());

  MediaSource expected_source = MediaSource::ForUnchosenDesktop();
  EXPECT_EQ(expected_source, route_request_result()->route()->media_source());
}

// Demonstrates that failures to create desktop mirroring routes are propagated.
TEST_F(MediaRouteStarterTest, StartRoute_DesktopMirroringError) {
  CreateStarterForDefaultModes();

  set_expected_cast_result(mojom::RouteRequestResultCode::ROUTE_NOT_FOUND);

  StartMirroring(cast_sink(), MediaCastMode::DESKTOP_MIRROR);

  EXPECT_EQ(mojom::RouteRequestResultCode::ROUTE_NOT_FOUND,
            route_request_result()->result_code());
}

// Demonstrates that tab mirroring routes are created correctly.
TEST_F(MediaRouteStarterTest, StartRoute_TabMirroring) {
  CreateStarterForDefaultModes();

  set_expected_cast_result(mojom::RouteRequestResultCode::OK);

  StartMirroring(cast_sink(), MediaCastMode::TAB_MIRROR);

  EXPECT_EQ(mojom::RouteRequestResultCode::OK,
            route_request_result()->result_code());

  MediaSource expected_source = MediaSource::ForTab(
      sessions::SessionTabHelper::IdForTab(web_contents()).id());
  EXPECT_EQ(expected_source, route_request_result()->route()->media_source());
}

// Demonstrates that failures to create tab mirroring routes are propagated.
TEST_F(MediaRouteStarterTest, StartRoute_TabMirroringError) {
  CreateStarterForDefaultModes();

  set_expected_cast_result(mojom::RouteRequestResultCode::INVALID_ORIGIN);

  StartMirroring(cast_sink(), MediaCastMode::DESKTOP_MIRROR);

  EXPECT_EQ(mojom::RouteRequestResultCode::INVALID_ORIGIN,
            route_request_result()->result_code());
}

// Demonstrates that presentations routes from web content are created
// correctly.
TEST_F(MediaRouteStarterTest, StartRoute_WebContentPresentation) {
  presentation_manager()->SetDefaultPresentationRequest(
      default_presentation_request());

  CreateStarterForDefaultModes();

  set_expected_cast_result(mojom::RouteRequestResultCode::OK);
  auto expected_result =
      GetSuccessResult(default_presentation_request(), cast_sink().id());

  EXPECT_CALL(*presentation_manager(),
              OnPresentationResponse(default_presentation_request(), _, _));

  StartPresentation(cast_sink(), default_presentation_request());

  EXPECT_EQ(mojom::RouteRequestResultCode::OK,
            route_request_result()->result_code());
  EXPECT_EQ(kDefaultPresentationUrl,
            route_request_result()->presentation_url());
}

// Demonstrates that failures to create presentation routes from web content are
// propagated correctly.
TEST_F(MediaRouteStarterTest, StartRoute_WebContentPresentationError) {
  presentation_manager()->SetDefaultPresentationRequest(
      default_presentation_request());

  CreateStarterForDefaultModes();

  set_expected_cast_result(mojom::RouteRequestResultCode::INVALID_ORIGIN);
  EXPECT_CALL(*presentation_manager(),
              OnPresentationResponse(default_presentation_request(), _, _));

  StartPresentation(cast_sink(), default_presentation_request());

  EXPECT_EQ(mojom::RouteRequestResultCode::INVALID_ORIGIN,
            route_request_result()->result_code());
}

// Demonstrates that presentations routes from start presentation contexts are
// created correctly.
TEST_F(MediaRouteStarterTest, StartRoute_StartPresentationContext_Cast) {
  auto start_presentation_context =
      CreateStartPresentationContext(start_presentation_request());

  CreateStarter(MediaRouterUIParameters(kDefaultModes, web_contents(),
                                        std::move(start_presentation_context)));

  set_expected_cast_result(mojom::RouteRequestResultCode::OK);
  auto expected_result =
      GetSuccessResult(start_presentation_request(), cast_sink().id());

  EXPECT_CALL(*this, RequestSuccess(_, _, *expected_result->route()));

  StartPresentation(cast_sink(), start_presentation_request());

  EXPECT_EQ(mojom::RouteRequestResultCode::OK,
            route_request_result()->result_code());
  EXPECT_EQ(kStartPresentationUrl, route_request_result()->presentation_url());
}

TEST_F(MediaRouteStarterTest,
       StartRoute_StartPresentationContext_RemotePlayback) {
  auto start_presentation_context = CreateStartPresentationContext(
      CreatePresentationRequest(kRemotePlaybackUrl, kStartOriginUrl));

  CreateStarter(MediaRouterUIParameters(kDefaultModes, web_contents(),
                                        std::move(start_presentation_context)));

  set_expected_cast_result(mojom::RouteRequestResultCode::OK);

  EXPECT_CALL(*this, RequestSuccess(_, _, _));

  StartRemotePlayback(cast_sink());

  // TODO(crbug.com/1491212): Update test case once `tab_id` is removed from the
  // Remote Playback presentation url.
  EXPECT_EQ(mojom::RouteRequestResultCode::OK,
            route_request_result()->result_code());
  EXPECT_EQ(
      base::StrCat(
          {kRemotePlaybackUrl, "&tab_id=",
           base::NumberToString(
               sessions::SessionTabHelper::IdForTab(web_contents()).id())}),
      route_request_result()->presentation_url());
}

// Demonstrates that failures to create presentation routes from start
// presentation contexts are created correctly.
TEST_F(MediaRouteStarterTest, StartRoute_StartPresentationContextError) {
  auto start_presentation_context =
      CreateStartPresentationContext(start_presentation_request());

  CreateStarter(MediaRouterUIParameters(kDefaultModes, web_contents(),
                                        std::move(start_presentation_context)));

  set_expected_cast_result(
      mojom::RouteRequestResultCode::NO_SUPPORTED_PROVIDER);

  EXPECT_CALL(*this, RequestError(_));

  StartPresentation(cast_sink(), start_presentation_request());

  EXPECT_EQ(mojom::RouteRequestResultCode::NO_SUPPORTED_PROVIDER,
            route_request_result()->result_code());
}

TEST_F(MediaRouteStarterTest, GetScreenCapturePermission) {
// TODO(crbug.com/40802122): Fix screen-capture tests on macOS.
#if BUILDFLAG(IS_MAC)
  bool screen_capture_is_allowed = false;
#else
  bool screen_capture_is_allowed = true;
#endif  // BUILDFLAG(IS_MAC)
  set_screen_capture_allowed_for_testing(true);
  // Always allowed for presentation mode and tab mirroring.
  EXPECT_TRUE(MediaRouteStarter::GetScreenCapturePermission(
      MediaCastMode::PRESENTATION));
  EXPECT_TRUE(
      MediaRouteStarter::GetScreenCapturePermission(MediaCastMode::TAB_MIRROR));
  // Always allowed for desktop mode if permission has been granted
  EXPECT_TRUE(MediaRouteStarter::GetScreenCapturePermission(
      MediaCastMode::DESKTOP_MIRROR));

  set_screen_capture_allowed_for_testing(false);
  // Always allowed for presentation mode and tab mirroring.
  EXPECT_TRUE(MediaRouteStarter::GetScreenCapturePermission(
      MediaCastMode::PRESENTATION));
  EXPECT_TRUE(
      MediaRouteStarter::GetScreenCapturePermission(MediaCastMode::TAB_MIRROR));
  // The question of whether permission needs to be granted depends on platform
  // and version.
  EXPECT_EQ(screen_capture_is_allowed,
            MediaRouteStarter::GetScreenCapturePermission(
                MediaCastMode::DESKTOP_MIRROR));
}

TEST_F(MediaRouteStarterTest,
       StartPresentationContextDoesNotObserveDefaultPresentationChanges) {
  const std::string kVictimUrl = "https://victim.example/recv";
  const std::string kVictimOrigin = "https://victim.example";
  content::PresentationRequest victim_request =
      CreatePresentationRequest(kVictimUrl, kVictimOrigin);
  auto start_presentation_context =
      CreateStartPresentationContext(victim_request);

  CreateStarter(MediaRouterUIParameters(kDefaultModes, web_contents(),
                                        std::move(start_presentation_context)));

  EXPECT_EQ(u"victim.example",
            media_route_starter()->GetPresentationRequestSourceName());

  const std::string kAttackerUrl = "https://attacker.example/recv";
  const std::string kAttackerOrigin = "https://attacker.example";
  content::PresentationRequest attacker_request =
      CreatePresentationRequest(kAttackerUrl, kAttackerOrigin);
  presentation_manager()->NotifyDefaultPresentationChanged(&attacker_request);

  // The source name should remain unchanged because MediaRouteStarter does not
  // observe default presentation request changes.
  EXPECT_EQ(u"victim.example",
            media_route_starter()->GetPresentationRequestSourceName());
  EXPECT_CALL(*this, RequestError(_));
}

}  // namespace media_router
