// 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/child/service_worker/service_worker_dispatcher.h"
#include "content/child/service_worker/web_service_worker_impl.h"
#include "content/child/service_worker/web_service_worker_registration_impl.h"
#include "content/child/thread_safe_sender.h"
#include "content/common/service_worker/service_worker_messages.h"
#include "content/common/service_worker/service_worker_types.h"
#include "ipc/ipc_sync_message_filter.h"
#include "ipc/ipc_test_sink.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {

class ServiceWorkerTestSender : public ThreadSafeSender {
 public:
  explicit ServiceWorkerTestSender(IPC::TestSink* ipc_sink)
      : ThreadSafeSender(nullptr, nullptr),
        ipc_sink_(ipc_sink) {}

  bool Send(IPC::Message* message) override {
    return ipc_sink_->Send(message);
  }

 private:
  ~ServiceWorkerTestSender() override {}

  IPC::TestSink* ipc_sink_;

  DISALLOW_COPY_AND_ASSIGN(ServiceWorkerTestSender);
};

class ServiceWorkerDispatcherTest : public testing::Test {
 public:
  ServiceWorkerDispatcherTest() {}

  void SetUp() override {
    sender_ = new ServiceWorkerTestSender(&ipc_sink_);
    dispatcher_.reset(new ServiceWorkerDispatcher(sender_.get()));
  }

  void CreateObjectInfoAndVersionAttributes(
      ServiceWorkerRegistrationObjectInfo* info,
      ServiceWorkerVersionAttributes* attrs) {
    info->handle_id = 10;
    info->registration_id = 20;

    attrs->active.handle_id = 100;
    attrs->active.version_id = 200;
    attrs->waiting.handle_id = 101;
    attrs->waiting.version_id = 201;
    attrs->installing.handle_id = 102;
    attrs->installing.version_id = 202;
  }

  WebServiceWorkerRegistrationImpl* FindOrCreateRegistration(
      const ServiceWorkerRegistrationObjectInfo& info,
      const ServiceWorkerVersionAttributes& attrs) {
    return dispatcher_->FindOrCreateRegistration(info, attrs);
  }

  bool ContainsServiceWorker(int handle_id) {
    return ContainsKey(dispatcher_->service_workers_, handle_id);
  }

  bool ContainsRegistration(int registration_handle_id) {
    return ContainsKey(dispatcher_->registrations_, registration_handle_id);
  }

  ServiceWorkerDispatcher* dispatcher() { return dispatcher_.get(); }
  IPC::TestSink* ipc_sink() { return &ipc_sink_; }

 private:
  IPC::TestSink ipc_sink_;
  scoped_ptr<ServiceWorkerDispatcher> dispatcher_;
  scoped_refptr<ServiceWorkerTestSender> sender_;

  DISALLOW_COPY_AND_ASSIGN(ServiceWorkerDispatcherTest);
};

// TODO(nhiroki): Add tests for message handlers especially to receive reference
// counts like OnAssociateRegistration().
TEST_F(ServiceWorkerDispatcherTest, GetServiceWorker) {
  ServiceWorkerRegistrationObjectInfo info;
  ServiceWorkerVersionAttributes attrs;
  CreateObjectInfoAndVersionAttributes(&info, &attrs);

  // Should return a worker object newly created with incrementing refcount.
  bool adopt_handle = false;
  scoped_ptr<WebServiceWorkerImpl> worker(
      dispatcher()->GetServiceWorker(attrs.installing, adopt_handle));
  EXPECT_TRUE(worker);
  EXPECT_TRUE(ContainsServiceWorker(attrs.installing.handle_id));
  EXPECT_EQ(1UL, ipc_sink()->message_count());
  EXPECT_EQ(ServiceWorkerHostMsg_IncrementServiceWorkerRefCount::ID,
            ipc_sink()->GetMessageAt(0)->type());

  ipc_sink()->ClearMessages();

  // Should return the existing worker object.
  adopt_handle = false;
  WebServiceWorkerImpl* existing_worker =
      dispatcher()->GetServiceWorker(attrs.installing, adopt_handle);
  EXPECT_EQ(worker, existing_worker);
  EXPECT_EQ(0UL, ipc_sink()->message_count());

  // Should return the existing worker object with adopting refcount.
  adopt_handle = true;
  existing_worker =
      dispatcher()->GetServiceWorker(attrs.installing, adopt_handle);
  EXPECT_EQ(worker, existing_worker);
  ASSERT_EQ(1UL, ipc_sink()->message_count());
  EXPECT_EQ(ServiceWorkerHostMsg_DecrementServiceWorkerRefCount::ID,
            ipc_sink()->GetMessageAt(0)->type());

  ipc_sink()->ClearMessages();

  // Should return another worker object newly created with adopting refcount.
  adopt_handle = true;
  scoped_ptr<WebServiceWorkerImpl> another_worker(
      dispatcher()->GetServiceWorker(attrs.waiting, adopt_handle));
  EXPECT_NE(worker.get(), another_worker.get());
  EXPECT_TRUE(ContainsServiceWorker(attrs.waiting.handle_id));
  EXPECT_EQ(0UL, ipc_sink()->message_count());

  // Should return nullptr when a given object info is invalid.
  adopt_handle = false;
  WebServiceWorkerImpl* invalid_worker =
      dispatcher()->GetServiceWorker(ServiceWorkerObjectInfo(), adopt_handle);
  EXPECT_FALSE(invalid_worker);
  EXPECT_EQ(0UL, ipc_sink()->message_count());

  adopt_handle = true;
  invalid_worker =
      dispatcher()->GetServiceWorker(ServiceWorkerObjectInfo(), adopt_handle);
  EXPECT_FALSE(invalid_worker);
  EXPECT_EQ(0UL, ipc_sink()->message_count());
}

TEST_F(ServiceWorkerDispatcherTest, CreateServiceWorkerRegistration) {
  ServiceWorkerRegistrationObjectInfo info;
  ServiceWorkerVersionAttributes attrs;
  CreateObjectInfoAndVersionAttributes(&info, &attrs);

  // Should return a registration object newly created with incrementing
  // refcount.
  bool adopt_handle = false;
  scoped_ptr<WebServiceWorkerRegistrationImpl> registration(
      dispatcher()->CreateServiceWorkerRegistration(info, adopt_handle));
  EXPECT_TRUE(registration);
  EXPECT_TRUE(ContainsRegistration(info.handle_id));
  ASSERT_EQ(1UL, ipc_sink()->message_count());
  EXPECT_EQ(ServiceWorkerHostMsg_IncrementRegistrationRefCount::ID,
            ipc_sink()->GetMessageAt(0)->type());

  registration.reset();
  EXPECT_FALSE(ContainsRegistration(info.handle_id));
  ipc_sink()->ClearMessages();

  // Should return another registration object newly created with adopting
  // refcount.
  adopt_handle = true;
  scoped_ptr<WebServiceWorkerRegistrationImpl> another_registration(
      dispatcher()->CreateServiceWorkerRegistration(info, adopt_handle));
  EXPECT_TRUE(another_registration);
  EXPECT_TRUE(ContainsRegistration(info.handle_id));
  EXPECT_EQ(0UL, ipc_sink()->message_count());

  another_registration.reset();
  ipc_sink()->ClearMessages();

  // should return nullptr when a given object info is invalid.
  adopt_handle = false;
  scoped_ptr<WebServiceWorkerRegistrationImpl> invalid_registration(
      dispatcher()->CreateServiceWorkerRegistration(
          ServiceWorkerRegistrationObjectInfo(), adopt_handle));
  EXPECT_FALSE(invalid_registration);
  EXPECT_FALSE(ContainsRegistration(info.handle_id));
  EXPECT_EQ(0UL, ipc_sink()->message_count());

  adopt_handle = true;
  invalid_registration.reset(dispatcher()->CreateServiceWorkerRegistration(
      ServiceWorkerRegistrationObjectInfo(), adopt_handle));
  EXPECT_FALSE(invalid_registration);
  EXPECT_FALSE(ContainsRegistration(info.handle_id));
  EXPECT_EQ(0UL, ipc_sink()->message_count());
}

TEST_F(ServiceWorkerDispatcherTest, FindOrCreateRegistration) {
  ServiceWorkerRegistrationObjectInfo info;
  ServiceWorkerVersionAttributes attrs;
  CreateObjectInfoAndVersionAttributes(&info, &attrs);

  // Should return a registration object newly created with adopting refcounts.
  scoped_ptr<WebServiceWorkerRegistrationImpl> registration(
      FindOrCreateRegistration(info, attrs));
  EXPECT_TRUE(registration);
  EXPECT_EQ(info.registration_id, registration->registration_id());
  EXPECT_EQ(0UL, ipc_sink()->message_count());

  // Should return the existing registration object with adopting refcounts.
  WebServiceWorkerRegistrationImpl* existing_registration =
      FindOrCreateRegistration(info, attrs);
  EXPECT_EQ(registration, existing_registration);
  ASSERT_EQ(4UL, ipc_sink()->message_count());
  EXPECT_EQ(ServiceWorkerHostMsg_DecrementRegistrationRefCount::ID,
            ipc_sink()->GetMessageAt(0)->type());
  EXPECT_EQ(ServiceWorkerHostMsg_DecrementServiceWorkerRefCount::ID,
            ipc_sink()->GetMessageAt(1)->type());
  EXPECT_EQ(ServiceWorkerHostMsg_DecrementServiceWorkerRefCount::ID,
            ipc_sink()->GetMessageAt(2)->type());
  EXPECT_EQ(ServiceWorkerHostMsg_DecrementServiceWorkerRefCount::ID,
            ipc_sink()->GetMessageAt(3)->type());
}

}  // namespace content
