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

#include "content/browser/presentation/presentation_service_impl.h"

#include <stddef.h>
#include <stdint.h>
#include <string>
#include <utility>
#include <vector>

#include "base/location.h"
#include "base/memory/scoped_ptr.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/test/test_timeouts.h"
#include "base/thread_task_runner_handle.h"
#include "content/public/browser/presentation_service_delegate.h"
#include "content/public/browser/presentation_session.h"
#include "content/public/common/presentation_constants.h"
#include "content/test/test_render_frame_host.h"
#include "content/test/test_render_view_host.h"
#include "content/test/test_web_contents.h"
#include "mojo/public/cpp/bindings/interface_ptr.h"
#include "mojo/public/cpp/bindings/string.h"
#include "testing/gmock/include/gmock/gmock.h"

using ::testing::_;
using ::testing::Eq;
using ::testing::InvokeWithoutArgs;
using ::testing::Mock;
using ::testing::Return;
using ::testing::SaveArg;

namespace content {

namespace {

// Matches mojo structs.
MATCHER_P(Equals, expected, "") {
  return expected.Equals(arg);
}

const char *const kPresentationId = "presentationId";
const char *const kPresentationUrl = "http://foo.com/index.html";

bool ArePresentationSessionMessagesEqual(const mojom::SessionMessage* expected,
                                         const mojom::SessionMessage* actual) {
  return expected->type == actual->type &&
         expected->message == actual->message &&
         expected->data.Equals(actual->data);
}

void DoNothing(mojom::PresentationSessionInfoPtr info,
               mojom::PresentationErrorPtr error) {}

}  // namespace

class MockPresentationServiceDelegate : public PresentationServiceDelegate {
 public:
  MOCK_METHOD3(AddObserver,
      void(int render_process_id,
           int render_frame_id,
           PresentationServiceDelegate::Observer* observer));
  MOCK_METHOD2(RemoveObserver,
      void(int render_process_id, int render_frame_id));

  bool AddScreenAvailabilityListener(
      int render_process_id,
      int routing_id,
      PresentationScreenAvailabilityListener* listener) override {
    if (!screen_availability_listening_supported_)
      listener->OnScreenAvailabilityNotSupported();

    return AddScreenAvailabilityListener();
  }
  MOCK_METHOD0(AddScreenAvailabilityListener, bool());

  MOCK_METHOD3(RemoveScreenAvailabilityListener,
      void(
          int render_process_id,
          int routing_id,
          PresentationScreenAvailabilityListener* listener));
  MOCK_METHOD2(Reset,
      void(
          int render_process_id,
          int routing_id));
  MOCK_METHOD4(SetDefaultPresentationUrl,
               void(int render_process_id,
                    int routing_id,
                    const std::string& default_presentation_url,
                    const PresentationSessionStartedCallback& callback));
  MOCK_METHOD5(StartSession,
               void(int render_process_id,
                    int render_frame_id,
                    const std::string& presentation_url,
                    const PresentationSessionStartedCallback& success_cb,
                    const PresentationSessionErrorCallback& error_cb));
  MOCK_METHOD6(JoinSession,
               void(int render_process_id,
                    int render_frame_id,
                    const std::string& presentation_url,
                    const std::string& presentation_id,
                    const PresentationSessionStartedCallback& success_cb,
                    const PresentationSessionErrorCallback& error_cb));
  MOCK_METHOD3(CloseConnection,
               void(int render_process_id,
                    int render_frame_id,
                    const std::string& presentation_id));
  MOCK_METHOD3(Terminate,
               void(int render_process_id,
                    int render_frame_id,
                    const std::string& presentation_id));
  MOCK_METHOD4(ListenForSessionMessages,
               void(int render_process_id,
                    int render_frame_id,
                    const content::PresentationSessionInfo& session,
                    const PresentationSessionMessageCallback& message_cb));
  MOCK_METHOD5(SendMessageRawPtr,
               void(int render_process_id,
                    int render_frame_id,
                    const content::PresentationSessionInfo& session,
                    PresentationSessionMessage* message_request,
                    const SendMessageCallback& send_message_cb));
  void SendMessage(int render_process_id,
                   int render_frame_id,
                   const content::PresentationSessionInfo& session,
                   scoped_ptr<PresentationSessionMessage> message_request,
                   const SendMessageCallback& send_message_cb) override {
    SendMessageRawPtr(render_process_id, render_frame_id, session,
                      message_request.release(), send_message_cb);
  }
  MOCK_METHOD4(ListenForConnectionStateChange,
               void(int render_process_id,
                    int render_frame_id,
                    const content::PresentationSessionInfo& connection,
                    const content::PresentationConnectionStateChangedCallback&
                        state_changed_cb));

  void set_screen_availability_listening_supported(bool value) {
    screen_availability_listening_supported_ = value;
  }

 private:
  bool screen_availability_listening_supported_ = true;
};

class MockPresentationServiceClient : public mojom::PresentationServiceClient {
 public:
  MOCK_METHOD2(OnScreenAvailabilityUpdated,
      void(const mojo::String& url, bool available));
  void OnConnectionStateChanged(
      mojom::PresentationSessionInfoPtr connection,
      mojom::PresentationConnectionState new_state) override {
    OnConnectionStateChanged(*connection, new_state);
  }
  MOCK_METHOD2(OnConnectionStateChanged,
               void(const mojom::PresentationSessionInfo& connection,
                    mojom::PresentationConnectionState new_state));

  void OnConnectionClosed(mojom::PresentationSessionInfoPtr connection,
                          mojom::PresentationConnectionCloseReason reason,
                          const mojo::String& message) override {
    OnConnectionClosed(*connection, reason, message);
  }
  MOCK_METHOD3(OnConnectionClosed,
               void(const mojom::PresentationSessionInfo& connection,
                    mojom::PresentationConnectionCloseReason reason,
                    const mojo::String& message));

  MOCK_METHOD1(OnScreenAvailabilityNotSupported, void(const mojo::String& url));

  void OnSessionMessagesReceived(
      mojom::PresentationSessionInfoPtr session_info,
      mojo::Array<mojom::SessionMessagePtr> messages) override {
    messages_received_ = std::move(messages);
    MessagesReceived();
  }
  MOCK_METHOD0(MessagesReceived, void());

  void OnDefaultSessionStarted(
      mojom::PresentationSessionInfoPtr session_info) override {
    OnDefaultSessionStarted(*session_info);
  }
  MOCK_METHOD1(OnDefaultSessionStarted,
               void(const mojom::PresentationSessionInfo& session_info));

  mojo::Array<mojom::SessionMessagePtr> messages_received_;
};

class PresentationServiceImplTest : public RenderViewHostImplTestHarness {
 public:
  PresentationServiceImplTest() : default_session_started_count_(0) {}

  void SetUp() override {
    RenderViewHostImplTestHarness::SetUp();

    auto request = mojo::GetProxy(&service_ptr_);
    EXPECT_CALL(mock_delegate_, AddObserver(_, _, _)).Times(1);
    service_impl_.reset(new PresentationServiceImpl(
        contents()->GetMainFrame(), contents(), &mock_delegate_));
    service_impl_->Bind(std::move(request));

    mojom::PresentationServiceClientPtr client_ptr;
    client_binding_.reset(new mojo::Binding<mojom::PresentationServiceClient>(
        &mock_client_, mojo::GetProxy(&client_ptr)));
    service_impl_->SetClient(std::move(client_ptr));
  }

  void TearDown() override {
    service_ptr_.reset();
    if (service_impl_.get()) {
      EXPECT_CALL(mock_delegate_, RemoveObserver(_, _)).Times(1);
      service_impl_.reset();
    }
    RenderViewHostImplTestHarness::TearDown();
  }

  void ListenForScreenAvailabilityAndWait(
      const mojo::String& url, bool delegate_success) {
    base::RunLoop run_loop;
    // This will call to |service_impl_| via mojo. Process the message
    // using RunLoop.
    // The callback shouldn't be invoked since there is no availability
    // result yet.
    EXPECT_CALL(mock_delegate_, AddScreenAvailabilityListener())
        .WillOnce(DoAll(
            InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit),
            Return(delegate_success)));
    service_ptr_->ListenForScreenAvailability(url);
    run_loop.Run();

    EXPECT_TRUE(Mock::VerifyAndClearExpectations(&mock_delegate_));
  }

  void RunLoopFor(base::TimeDelta duration) {
    base::RunLoop run_loop;
    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
        FROM_HERE, run_loop.QuitClosure(), duration);
    run_loop.Run();
  }

  void SaveQuitClosureAndRunLoop() {
    base::RunLoop run_loop;
    run_loop_quit_closure_ = run_loop.QuitClosure();
    run_loop.Run();
    run_loop_quit_closure_.Reset();
  }

  void SimulateScreenAvailabilityChangeAndWait(
      const std::string& url, bool available) {
    auto listener_it = service_impl_->screen_availability_listeners_.find(url);
    ASSERT_TRUE(listener_it->second);

    base::RunLoop run_loop;
    EXPECT_CALL(mock_client_,
                OnScreenAvailabilityUpdated(mojo::String(url), available))
      .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
    listener_it->second->OnScreenAvailabilityChanged(available);
    run_loop.Run();
  }

  void ExpectReset() {
    EXPECT_CALL(mock_delegate_, Reset(_, _)).Times(1);
  }

  void ExpectCleanState() {
    EXPECT_TRUE(service_impl_->default_presentation_url_.empty());
    EXPECT_EQ(
        service_impl_->screen_availability_listeners_.find(kPresentationUrl),
        service_impl_->screen_availability_listeners_.end());
    EXPECT_FALSE(service_impl_->on_session_messages_callback_.get());
  }

  void ExpectNewSessionMojoCallbackSuccess(
      mojom::PresentationSessionInfoPtr info,
      mojom::PresentationErrorPtr error) {
    EXPECT_FALSE(info.is_null());
    EXPECT_TRUE(error.is_null());
    if (!run_loop_quit_closure_.is_null())
      run_loop_quit_closure_.Run();
  }

  void ExpectNewSessionMojoCallbackError(mojom::PresentationSessionInfoPtr info,
                                         mojom::PresentationErrorPtr error) {
    EXPECT_TRUE(info.is_null());
    EXPECT_FALSE(error.is_null());
    if (!run_loop_quit_closure_.is_null())
      run_loop_quit_closure_.Run();
  }

  void ExpectSessionMessages(
      const mojo::Array<mojom::SessionMessagePtr>& expected_msgs,
      const mojo::Array<mojom::SessionMessagePtr>& actual_msgs) {
    EXPECT_EQ(expected_msgs.size(), actual_msgs.size());
    for (size_t i = 0; i < actual_msgs.size(); ++i) {
      EXPECT_TRUE(ArePresentationSessionMessagesEqual(expected_msgs[i].get(),
                                                      actual_msgs[i].get()));
    }
  }

  void ExpectSendMessageMojoCallback(bool success) {
    EXPECT_TRUE(success);
    EXPECT_FALSE(service_impl_->send_message_callback_);
    if (!run_loop_quit_closure_.is_null())
      run_loop_quit_closure_.Run();
  }

  void RunListenForSessionMessages(const std::string& text_msg,
                                   const std::vector<uint8_t>& binary_data,
                                   bool pass_ownership) {
    mojo::Array<mojom::SessionMessagePtr> expected_msgs(2);
    expected_msgs[0] = mojom::SessionMessage::New();
    expected_msgs[0]->type = mojom::PresentationMessageType::TEXT;
    expected_msgs[0]->message = text_msg;
    expected_msgs[1] = mojom::SessionMessage::New();
    expected_msgs[1]->type = mojom::PresentationMessageType::ARRAY_BUFFER;
    expected_msgs[1]->data = mojo::Array<uint8_t>::From(binary_data);

    mojom::PresentationSessionInfoPtr session(
        mojom::PresentationSessionInfo::New());
    session->url = kPresentationUrl;
    session->id = kPresentationId;

    PresentationSessionMessageCallback message_cb;
    {
    base::RunLoop run_loop;
    EXPECT_CALL(mock_delegate_, ListenForSessionMessages(_, _, _, _))
        .WillOnce(DoAll(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit),
                        SaveArg<3>(&message_cb)));
    service_ptr_->ListenForSessionMessages(session.Clone());
    run_loop.Run();
    }

    ScopedVector<PresentationSessionMessage> messages;
    scoped_ptr<content::PresentationSessionMessage> message;
    message.reset(
        new content::PresentationSessionMessage(PresentationMessageType::TEXT));
    message->message = text_msg;
    messages.push_back(std::move(message));
    message.reset(new content::PresentationSessionMessage(
        PresentationMessageType::ARRAY_BUFFER));
    message->data.reset(new std::vector<uint8_t>(binary_data));
    messages.push_back(std::move(message));

    std::vector<mojom::SessionMessagePtr> actual_msgs;
    {
      base::RunLoop run_loop;
      EXPECT_CALL(mock_client_, MessagesReceived())
          .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
      message_cb.Run(std::move(messages), pass_ownership);
      run_loop.Run();
    }
    ExpectSessionMessages(expected_msgs, mock_client_.messages_received_);
  }

  MockPresentationServiceDelegate mock_delegate_;

  scoped_ptr<PresentationServiceImpl> service_impl_;
  mojo::InterfacePtr<mojom::PresentationService> service_ptr_;

  MockPresentationServiceClient mock_client_;
  scoped_ptr<mojo::Binding<mojom::PresentationServiceClient>> client_binding_;

  base::Closure run_loop_quit_closure_;
  int default_session_started_count_;
};

TEST_F(PresentationServiceImplTest, ListenForScreenAvailability) {
  ListenForScreenAvailabilityAndWait(kPresentationUrl, true);

  SimulateScreenAvailabilityChangeAndWait(kPresentationUrl, true);
  SimulateScreenAvailabilityChangeAndWait(kPresentationUrl, false);
  SimulateScreenAvailabilityChangeAndWait(kPresentationUrl, true);
}

TEST_F(PresentationServiceImplTest, Reset) {
  ListenForScreenAvailabilityAndWait(kPresentationUrl, true);

  ExpectReset();
  service_impl_->Reset();
  ExpectCleanState();
}

TEST_F(PresentationServiceImplTest, DidNavigateThisFrame) {
  ListenForScreenAvailabilityAndWait(kPresentationUrl, true);

  ExpectReset();
  service_impl_->DidNavigateAnyFrame(
      contents()->GetMainFrame(),
      content::LoadCommittedDetails(),
      content::FrameNavigateParams());
  ExpectCleanState();
}

TEST_F(PresentationServiceImplTest, DidNavigateOtherFrame) {
  ListenForScreenAvailabilityAndWait(kPresentationUrl, true);

  // TODO(imcheng): How to get a different RenderFrameHost?
  service_impl_->DidNavigateAnyFrame(
      nullptr,
      content::LoadCommittedDetails(),
      content::FrameNavigateParams());

  // Availability is reported and callback is invoked since it was not
  // removed.
  SimulateScreenAvailabilityChangeAndWait(kPresentationUrl, true);
}

TEST_F(PresentationServiceImplTest, ThisRenderFrameDeleted) {
  ListenForScreenAvailabilityAndWait(kPresentationUrl, true);

  ExpectReset();

  // Since the frame matched the service, |service_impl_| will be deleted.
  PresentationServiceImpl* service = service_impl_.release();
  EXPECT_CALL(mock_delegate_, RemoveObserver(_, _)).Times(1);
  service->RenderFrameDeleted(contents()->GetMainFrame());
}

TEST_F(PresentationServiceImplTest, OtherRenderFrameDeleted) {
  ListenForScreenAvailabilityAndWait(kPresentationUrl, true);

  // TODO(imcheng): How to get a different RenderFrameHost?
  service_impl_->RenderFrameDeleted(nullptr);

  // Availability is reported and callback should be invoked since listener
  // has not been deleted.
  SimulateScreenAvailabilityChangeAndWait(kPresentationUrl, true);
}

TEST_F(PresentationServiceImplTest, DelegateFails) {
  ListenForScreenAvailabilityAndWait(kPresentationUrl, false);
  ASSERT_EQ(
      service_impl_->screen_availability_listeners_.find(kPresentationUrl),
      service_impl_->screen_availability_listeners_.end());
}

TEST_F(PresentationServiceImplTest, SetDefaultPresentationUrl) {
  std::string url1("http://fooUrl");
  EXPECT_CALL(mock_delegate_, SetDefaultPresentationUrl(_, _, Eq(url1), _))
      .Times(1);
  service_impl_->SetDefaultPresentationURL(url1);
  EXPECT_EQ(url1, service_impl_->default_presentation_url_);

  std::string url2("http://barUrl");
  // Sets different DPU.
  content::PresentationSessionStartedCallback callback;
  EXPECT_CALL(mock_delegate_, SetDefaultPresentationUrl(_, _, Eq(url2), _))
      .WillOnce(SaveArg<3>(&callback));
  service_impl_->SetDefaultPresentationURL(url2);
  EXPECT_EQ(url2, service_impl_->default_presentation_url_);

  mojom::PresentationSessionInfo session_info;
  session_info.url = url2;
  session_info.id = kPresentationId;
  base::RunLoop run_loop;
  EXPECT_CALL(mock_client_, OnDefaultSessionStarted(Equals(session_info)))
      .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
  callback.Run(content::PresentationSessionInfo(url2, kPresentationId));
  run_loop.Run();
}

TEST_F(PresentationServiceImplTest, ListenForConnectionStateChange) {
  content::PresentationSessionInfo connection(kPresentationUrl,
                                              kPresentationId);
  content::PresentationConnectionStateChangedCallback state_changed_cb;
  EXPECT_CALL(mock_delegate_, ListenForConnectionStateChange(_, _, _, _))
      .WillOnce(SaveArg<3>(&state_changed_cb));
  service_impl_->ListenForConnectionStateChange(connection);

  // Trigger state change. It should be propagated back up to |mock_client_|.
  mojom::PresentationSessionInfo presentation_connection;
  presentation_connection.url = kPresentationUrl;
  presentation_connection.id = kPresentationId;
  {
    base::RunLoop run_loop;
    EXPECT_CALL(mock_client_,
                OnConnectionStateChanged(
                    Equals(presentation_connection),
                    mojom::PresentationConnectionState::TERMINATED))
        .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
    state_changed_cb.Run(PresentationConnectionStateChangeInfo(
        PRESENTATION_CONNECTION_STATE_TERMINATED));
    run_loop.Run();
  }
}

TEST_F(PresentationServiceImplTest, ListenForConnectionClose) {
  content::PresentationSessionInfo connection(kPresentationUrl,
                                              kPresentationId);
  content::PresentationConnectionStateChangedCallback state_changed_cb;
  EXPECT_CALL(mock_delegate_, ListenForConnectionStateChange(_, _, _, _))
      .WillOnce(SaveArg<3>(&state_changed_cb));
  service_impl_->ListenForConnectionStateChange(connection);

  // Trigger connection close. It should be propagated back up to
  // |mock_client_|.
  mojom::PresentationSessionInfo presentation_connection;
  presentation_connection.url = kPresentationUrl;
  presentation_connection.id = kPresentationId;
  {
    base::RunLoop run_loop;
    PresentationConnectionStateChangeInfo closed_info(
        PRESENTATION_CONNECTION_STATE_CLOSED);
    closed_info.close_reason = PRESENTATION_CONNECTION_CLOSE_REASON_WENT_AWAY;
    closed_info.message = "Foo";

    EXPECT_CALL(
        mock_client_,
        OnConnectionClosed(Equals(presentation_connection),
                           mojom::PresentationConnectionCloseReason::WENT_AWAY,
                           mojo::String("Foo")))
        .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
    state_changed_cb.Run(closed_info);
    run_loop.Run();
  }
}

TEST_F(PresentationServiceImplTest, SetSameDefaultPresentationUrl) {
  EXPECT_CALL(mock_delegate_,
              SetDefaultPresentationUrl(_, _, Eq(kPresentationUrl), _))
      .Times(1);
  service_impl_->SetDefaultPresentationURL(kPresentationUrl);
  EXPECT_TRUE(Mock::VerifyAndClearExpectations(&mock_delegate_));
  EXPECT_EQ(kPresentationUrl, service_impl_->default_presentation_url_);

  // Same URL as before; no-ops.
  service_impl_->SetDefaultPresentationURL(kPresentationUrl);
  EXPECT_TRUE(Mock::VerifyAndClearExpectations(&mock_delegate_));
  EXPECT_EQ(kPresentationUrl, service_impl_->default_presentation_url_);
}

TEST_F(PresentationServiceImplTest, StartSessionSuccess) {
  service_ptr_->StartSession(
      kPresentationUrl,
      base::Bind(
          &PresentationServiceImplTest::ExpectNewSessionMojoCallbackSuccess,
          base::Unretained(this)));
  base::RunLoop run_loop;
  base::Callback<void(const PresentationSessionInfo&)> success_cb;
  EXPECT_CALL(mock_delegate_, StartSession(_, _, Eq(kPresentationUrl), _, _))
      .WillOnce(DoAll(
            InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit),
            SaveArg<3>(&success_cb)));
  run_loop.Run();

  EXPECT_CALL(mock_delegate_, ListenForConnectionStateChange(_, _, _, _))
      .Times(1);
  success_cb.Run(PresentationSessionInfo(kPresentationUrl, kPresentationId));
  SaveQuitClosureAndRunLoop();
}

TEST_F(PresentationServiceImplTest, StartSessionError) {
  service_ptr_->StartSession(
      kPresentationUrl,
      base::Bind(
          &PresentationServiceImplTest::ExpectNewSessionMojoCallbackError,
          base::Unretained(this)));
  base::RunLoop run_loop;
  base::Callback<void(const PresentationError&)> error_cb;
  EXPECT_CALL(mock_delegate_, StartSession(_, _, Eq(kPresentationUrl), _, _))
      .WillOnce(DoAll(
            InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit),
            SaveArg<4>(&error_cb)));
  run_loop.Run();
  error_cb.Run(PresentationError(PRESENTATION_ERROR_UNKNOWN, "Error message"));
  SaveQuitClosureAndRunLoop();
}

TEST_F(PresentationServiceImplTest, JoinSessionSuccess) {
  service_ptr_->JoinSession(
      kPresentationUrl,
      kPresentationId,
      base::Bind(
          &PresentationServiceImplTest::ExpectNewSessionMojoCallbackSuccess,
          base::Unretained(this)));
  base::RunLoop run_loop;
  base::Callback<void(const PresentationSessionInfo&)> success_cb;
  EXPECT_CALL(mock_delegate_, JoinSession(
      _, _, Eq(kPresentationUrl), Eq(kPresentationId), _, _))
      .WillOnce(DoAll(
            InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit),
            SaveArg<4>(&success_cb)));
  run_loop.Run();

  EXPECT_CALL(mock_delegate_, ListenForConnectionStateChange(_, _, _, _))
      .Times(1);
  success_cb.Run(PresentationSessionInfo(kPresentationUrl, kPresentationId));
  SaveQuitClosureAndRunLoop();
}

TEST_F(PresentationServiceImplTest, JoinSessionError) {
  service_ptr_->JoinSession(
      kPresentationUrl,
      kPresentationId,
      base::Bind(
          &PresentationServiceImplTest::ExpectNewSessionMojoCallbackError,
          base::Unretained(this)));
  base::RunLoop run_loop;
  base::Callback<void(const PresentationError&)> error_cb;
  EXPECT_CALL(mock_delegate_, JoinSession(
      _, _, Eq(kPresentationUrl), Eq(kPresentationId), _, _))
      .WillOnce(DoAll(
            InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit),
            SaveArg<5>(&error_cb)));
  run_loop.Run();
  error_cb.Run(PresentationError(PRESENTATION_ERROR_UNKNOWN, "Error message"));
  SaveQuitClosureAndRunLoop();
}

TEST_F(PresentationServiceImplTest, CloseConnection) {
  service_ptr_->CloseConnection(kPresentationUrl, kPresentationId);
  base::RunLoop run_loop;
  EXPECT_CALL(mock_delegate_, CloseConnection(_, _, Eq(kPresentationId)))
      .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
  run_loop.Run();
}

TEST_F(PresentationServiceImplTest, Terminate) {
  service_ptr_->Terminate(kPresentationUrl, kPresentationId);
  base::RunLoop run_loop;
  EXPECT_CALL(mock_delegate_, Terminate(_, _, Eq(kPresentationId)))
      .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
  run_loop.Run();
}

TEST_F(PresentationServiceImplTest, ListenForSessionMessagesPassed) {
  std::string text_msg("123");
  std::vector<uint8_t> binary_data(3, '\1');
  RunListenForSessionMessages(text_msg, binary_data, true);
}

TEST_F(PresentationServiceImplTest, ListenForSessionMessagesCopied) {
  std::string text_msg("123");
  std::vector<uint8_t> binary_data(3, '\1');
  RunListenForSessionMessages(text_msg, binary_data, false);
}

TEST_F(PresentationServiceImplTest, ListenForSessionMessagesWithEmptyMsg) {
  std::string text_msg("");
  std::vector<uint8_t> binary_data;
  RunListenForSessionMessages(text_msg, binary_data, false);
}

TEST_F(PresentationServiceImplTest, StartSessionInProgress) {
  std::string presentation_url1("http://fooUrl");
  std::string presentation_url2("http://barUrl");
  EXPECT_CALL(mock_delegate_, StartSession(_, _, Eq(presentation_url1), _, _))
      .Times(1);
  service_ptr_->StartSession(presentation_url1,
                             base::Bind(&DoNothing));

  // This request should fail immediately, since there is already a StartSession
  // in progress.
  service_ptr_->StartSession(
      presentation_url2,
      base::Bind(
          &PresentationServiceImplTest::ExpectNewSessionMojoCallbackError,
          base::Unretained(this)));
  SaveQuitClosureAndRunLoop();
}

TEST_F(PresentationServiceImplTest, SendStringMessage) {
  std::string message("Test presentation session message");

  mojom::PresentationSessionInfoPtr session(
      mojom::PresentationSessionInfo::New());
  session->url = kPresentationUrl;
  session->id = kPresentationId;
  mojom::SessionMessagePtr message_request(mojom::SessionMessage::New());
  message_request->type = mojom::PresentationMessageType::TEXT;
  message_request->message = message;
  service_ptr_->SendSessionMessage(
      std::move(session), std::move(message_request),
      base::Bind(&PresentationServiceImplTest::ExpectSendMessageMojoCallback,
                 base::Unretained(this)));

  base::RunLoop run_loop;
  base::Callback<void(bool)> send_message_cb;
  PresentationSessionMessage* test_message = nullptr;
  EXPECT_CALL(mock_delegate_, SendMessageRawPtr(_, _, _, _, _))
      .WillOnce(DoAll(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit),
                      SaveArg<3>(&test_message), SaveArg<4>(&send_message_cb)));
  run_loop.Run();

  // Make sure |test_message| gets deleted.
  scoped_ptr<PresentationSessionMessage> scoped_test_message(test_message);
  EXPECT_TRUE(test_message);
  EXPECT_FALSE(test_message->is_binary());
  EXPECT_LE(test_message->message.size(), kMaxPresentationSessionMessageSize);
  EXPECT_EQ(message, test_message->message);
  ASSERT_FALSE(test_message->data);
  send_message_cb.Run(true);
  SaveQuitClosureAndRunLoop();
}

TEST_F(PresentationServiceImplTest, SendArrayBuffer) {
  // Test Array buffer data.
  const uint8_t buffer[] = {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
  std::vector<uint8_t> data;
  data.assign(buffer, buffer + sizeof(buffer));

  mojom::PresentationSessionInfoPtr session(
      mojom::PresentationSessionInfo::New());
  session->url = kPresentationUrl;
  session->id = kPresentationId;
  mojom::SessionMessagePtr message_request(mojom::SessionMessage::New());
  message_request->type = mojom::PresentationMessageType::ARRAY_BUFFER;
  message_request->data = mojo::Array<uint8_t>::From(data);
  service_ptr_->SendSessionMessage(
      std::move(session), std::move(message_request),
      base::Bind(&PresentationServiceImplTest::ExpectSendMessageMojoCallback,
                 base::Unretained(this)));

  base::RunLoop run_loop;
  base::Callback<void(bool)> send_message_cb;
  PresentationSessionMessage* test_message = nullptr;
  EXPECT_CALL(mock_delegate_, SendMessageRawPtr(_, _, _, _, _))
      .WillOnce(DoAll(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit),
                      SaveArg<3>(&test_message), SaveArg<4>(&send_message_cb)));
  run_loop.Run();

  // Make sure |test_message| gets deleted.
  scoped_ptr<PresentationSessionMessage> scoped_test_message(test_message);
  EXPECT_TRUE(test_message);
  EXPECT_TRUE(test_message->is_binary());
  EXPECT_EQ(PresentationMessageType::ARRAY_BUFFER, test_message->type);
  EXPECT_TRUE(test_message->message.empty());
  ASSERT_TRUE(test_message->data);
  EXPECT_EQ(data.size(), test_message->data->size());
  EXPECT_LE(test_message->data->size(), kMaxPresentationSessionMessageSize);
  EXPECT_EQ(0, memcmp(buffer, &(*test_message->data)[0], sizeof(buffer)));
  send_message_cb.Run(true);
  SaveQuitClosureAndRunLoop();
}

TEST_F(PresentationServiceImplTest, SendArrayBufferWithExceedingLimit) {
  // Create buffer with size exceeding the limit.
  // Use same size as in content::kMaxPresentationSessionMessageSize.
  const size_t kMaxBufferSizeInBytes = 64 * 1024;  // 64 KB.
  uint8_t buffer[kMaxBufferSizeInBytes + 1];
  memset(buffer, 0, kMaxBufferSizeInBytes+1);
  std::vector<uint8_t> data;
  data.assign(buffer, buffer + sizeof(buffer));

  mojom::PresentationSessionInfoPtr session(
      mojom::PresentationSessionInfo::New());
  session->url = kPresentationUrl;
  session->id = kPresentationId;
  mojom::SessionMessagePtr message_request(mojom::SessionMessage::New());
  message_request->type = mojom::PresentationMessageType::ARRAY_BUFFER;
  message_request->data = mojo::Array<uint8_t>::From(data);
  service_ptr_->SendSessionMessage(
      std::move(session), std::move(message_request),
      base::Bind(&PresentationServiceImplTest::ExpectSendMessageMojoCallback,
                 base::Unretained(this)));

  base::RunLoop run_loop;
  base::Callback<void(bool)> send_message_cb;
  PresentationSessionMessage* test_message = nullptr;
  EXPECT_CALL(mock_delegate_, SendMessageRawPtr(_, _, _, _, _))
      .WillOnce(DoAll(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit),
                      SaveArg<3>(&test_message), SaveArg<4>(&send_message_cb)));
  run_loop.Run();

  EXPECT_FALSE(test_message);
  send_message_cb.Run(true);
  SaveQuitClosureAndRunLoop();
}

TEST_F(PresentationServiceImplTest, SendBlobData) {
  const uint8_t buffer[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
  std::vector<uint8_t> data;
  data.assign(buffer, buffer + sizeof(buffer));

  mojom::PresentationSessionInfoPtr session(
      mojom::PresentationSessionInfo::New());
  session->url = kPresentationUrl;
  session->id = kPresentationId;
  mojom::SessionMessagePtr message_request(mojom::SessionMessage::New());
  message_request->type = mojom::PresentationMessageType::BLOB;
  message_request->data = mojo::Array<uint8_t>::From(data);
  service_ptr_->SendSessionMessage(
      std::move(session), std::move(message_request),
      base::Bind(&PresentationServiceImplTest::ExpectSendMessageMojoCallback,
                 base::Unretained(this)));

  base::RunLoop run_loop;
  base::Callback<void(bool)> send_message_cb;
  PresentationSessionMessage* test_message = nullptr;
  EXPECT_CALL(mock_delegate_, SendMessageRawPtr(_, _, _, _, _))
      .WillOnce(DoAll(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit),
                      SaveArg<3>(&test_message), SaveArg<4>(&send_message_cb)));
  run_loop.Run();

  // Make sure |test_message| gets deleted.
  scoped_ptr<PresentationSessionMessage> scoped_test_message(test_message);
  EXPECT_TRUE(test_message);
  EXPECT_TRUE(test_message->is_binary());
  EXPECT_EQ(PresentationMessageType::BLOB, test_message->type);
  EXPECT_TRUE(test_message->message.empty());
  ASSERT_TRUE(test_message->data);
  EXPECT_EQ(data.size(), test_message->data->size());
  EXPECT_LE(test_message->data->size(), kMaxPresentationSessionMessageSize);
  EXPECT_EQ(0, memcmp(buffer, &(*test_message->data)[0], sizeof(buffer)));
  send_message_cb.Run(true);
  SaveQuitClosureAndRunLoop();
}

TEST_F(PresentationServiceImplTest, MaxPendingJoinSessionRequests) {
  const char* presentation_url = "http://fooUrl%d";
  const char* presentation_id = "presentationId%d";
  int num_requests = PresentationServiceImpl::kMaxNumQueuedSessionRequests;
  int i = 0;
  EXPECT_CALL(mock_delegate_, JoinSession(_, _, _, _, _, _))
      .Times(num_requests);
  for (; i < num_requests; ++i) {
    service_ptr_->JoinSession(
        base::StringPrintf(presentation_url, i),
        base::StringPrintf(presentation_id, i),
        base::Bind(&DoNothing));
  }

  // Exceeded maximum queue size, should invoke mojo callback with error.
  service_ptr_->JoinSession(
        base::StringPrintf(presentation_url, i),
        base::StringPrintf(presentation_id, i),
        base::Bind(
            &PresentationServiceImplTest::ExpectNewSessionMojoCallbackError,
            base::Unretained(this)));
  SaveQuitClosureAndRunLoop();
}

TEST_F(PresentationServiceImplTest, ScreenAvailabilityNotSupported) {
  mock_delegate_.set_screen_availability_listening_supported(false);
  base::RunLoop run_loop;
  EXPECT_CALL(mock_client_,
              OnScreenAvailabilityNotSupported(Eq(kPresentationUrl)))
      .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
  ListenForScreenAvailabilityAndWait(kPresentationUrl, false);
  run_loop.Run();
}

}  // namespace content