// Copyright 2010-2021, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "dictionary/user_dictionary_session_handler.h"

#include <cstdint>
#include <memory>
#include <string>
#include <vector>

#include "base/file_stream.h"
#include "base/file_util.h"
#include "base/protobuf/protobuf.h"
#include "base/protobuf/repeated_field.h"
#include "base/system_util.h"
#include "protocol/user_dictionary_storage.pb.h"
#include "testing/base/public/googletest.h"
#include "testing/base/public/gunit.h"
#include "testing/base/public/testing_util.h"
#include "absl/flags/flag.h"
#include "absl/memory/memory.h"

namespace mozc {
namespace {

using ::mozc::protobuf::RepeatedPtrField;
using ::mozc::user_dictionary::UserDictionary;
using ::mozc::user_dictionary::UserDictionaryCommand;
using ::mozc::user_dictionary::UserDictionaryCommandStatus;
using ::mozc::user_dictionary::UserDictionarySessionHandler;

const char kDictionaryData[] =
    "きょうと\t京都\t名詞\n"
    "おおさか\t大阪\t地名\n"
    "とうきょう\t東京\t地名\tコメント\n"
    "すずき\t鈴木\t人名\n";

// 0 means invalid dictionary id.
// c.f., UserDictionaryUtil::CreateNewDictionaryId()
const uint64_t kInvalidDictionaryId = 0;

class UserDictionarySessionHandlerTest : public ::testing::Test {
 protected:
  void SetUp() override {
    original_user_profile_directory_ = SystemUtil::GetUserProfileDirectory();
    SystemUtil::SetUserProfileDirectory(absl::GetFlag(FLAGS_test_tmpdir));
    FileUtil::Unlink(GetUserDictionaryFile());

    handler_ = absl::make_unique<UserDictionarySessionHandler>();
    command_ = absl::make_unique<UserDictionaryCommand>();
    status_ = absl::make_unique<UserDictionaryCommandStatus>();

    handler_->set_dictionary_path(GetUserDictionaryFile());
  }

  void TearDown() override {
    FileUtil::Unlink(GetUserDictionaryFile());
    SystemUtil::SetUserProfileDirectory(original_user_profile_directory_);
  }

  void Clear() {
    command_->Clear();
    status_->Clear();
  }

  static std::string GetUserDictionaryFile() {
    return FileUtil::JoinPath(absl::GetFlag(FLAGS_test_tmpdir), "test.db");
  }

  uint64_t CreateSession() {
    Clear();
    command_->set_type(UserDictionaryCommand::CREATE_SESSION);
    EXPECT_TRUE(handler_->Evaluate(*command_, status_.get()));
    EXPECT_EQ(UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS,
              status_->status());
    EXPECT_TRUE(status_->has_session_id());
    EXPECT_NE(0, status_->session_id());
    return status_->session_id();
  }

  void DeleteSession(uint64_t session_id) {
    Clear();
    command_->set_type(UserDictionaryCommand::DELETE_SESSION);
    command_->set_session_id(session_id);
    EXPECT_TRUE(handler_->Evaluate(*command_, status_.get()));
    EXPECT_EQ(UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS,
              status_->status());
  }

  uint64_t CreateUserDictionary(uint64_t session_id, const std::string &name) {
    Clear();
    command_->set_type(UserDictionaryCommand::CREATE_DICTIONARY);
    command_->set_session_id(session_id);
    command_->set_dictionary_name(name);
    EXPECT_TRUE(handler_->Evaluate(*command_, status_.get()));
    EXPECT_EQ(UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS,
              status_->status());
    EXPECT_TRUE(status_->has_dictionary_id());
    return status_->dictionary_id();
  }

  void AddUserDictionaryEntry(uint64_t session_id, uint64_t dictionary_id,
                              const std::string &key, const std::string &value,
                              UserDictionary::PosType pos,
                              const std::string &comment) {
    Clear();
    command_->set_type(UserDictionaryCommand::ADD_ENTRY);
    command_->set_session_id(session_id);
    command_->set_dictionary_id(dictionary_id);
    UserDictionary::Entry *entry = command_->mutable_entry();
    entry->set_key(key);
    entry->set_value(value);
    entry->set_pos(pos);
    entry->set_comment(comment);
    EXPECT_TRUE(handler_->Evaluate(*command_, status_.get()));
    EXPECT_EQ(UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS,
              status_->status());
  }

  RepeatedPtrField<UserDictionary::Entry> GetAllUserDictionaryEntries(
      uint64_t session_id, uint64_t dictionary_id) {
    std::vector<int> indices;
    const uint32_t entries_size =
        GetUserDictionaryEntrySize(session_id, dictionary_id);
    for (uint32_t i = 0; i < entries_size; ++i) {
      indices.push_back(i);
    }
    return GetUserDictionaryEntries(session_id, dictionary_id, indices);
  }

  RepeatedPtrField<UserDictionary::Entry> GetUserDictionaryEntries(
      uint64_t session_id, uint64_t dictionary_id,
      const std::vector<int> &indices) {
    Clear();
    command_->set_type(UserDictionaryCommand::GET_ENTRIES);
    command_->set_session_id(session_id);
    command_->set_dictionary_id(dictionary_id);
    for (size_t i = 0; i < indices.size(); ++i) {
      command_->add_entry_index(indices[i]);
    }
    EXPECT_TRUE(handler_->Evaluate(*command_, status_.get()));
    EXPECT_EQ(UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS,
              status_->status());
    EXPECT_EQ(indices.size(), status_->entries_size());
    EXPECT_TRUE(handler_->Evaluate(*command_, status_.get()));
    EXPECT_EQ(UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS,
              status_->status());
    return status_->entries();
  }

  uint32_t GetUserDictionaryEntrySize(uint64_t session_id,
                                      uint64_t dictionary_id) {
    Clear();
    command_->set_type(UserDictionaryCommand::GET_ENTRY_SIZE);
    command_->set_session_id(session_id);
    command_->set_dictionary_id(dictionary_id);
    EXPECT_TRUE(handler_->Evaluate(*command_, status_.get()));
    EXPECT_EQ(UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS,
              status_->status());
    EXPECT_TRUE(status_->has_entry_size());
    return status_->entry_size();
  }

  std::unique_ptr<UserDictionarySessionHandler> handler_;
  std::unique_ptr<UserDictionaryCommand> command_;
  std::unique_ptr<UserDictionaryCommandStatus> status_;

 private:
  std::string original_user_profile_directory_;
};

TEST_F(UserDictionarySessionHandlerTest, InvalidCommand) {
  ASSERT_FALSE(handler_->Evaluate(*command_, status_.get()));

  // We cannot test setting invalid id, because it'll just fail to cast
  // (i.e. assertion error) in debug build.
}

TEST_F(UserDictionarySessionHandlerTest, NoOperation) {
  const uint64_t session_id = CreateSession();

  Clear();
  command_->set_type(UserDictionaryCommand::NO_OPERATION);
  command_->set_session_id(session_id);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: USER_DICTIONARY_COMMAND_SUCCESS", *status_);

  Clear();
  command_->set_type(UserDictionaryCommand::NO_OPERATION);
  command_->set_session_id(0);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: UNKNOWN_SESSION_ID", *status_);

  Clear();
  command_->set_type(UserDictionaryCommand::NO_OPERATION);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: INVALID_ARGUMENT", *status_);

  // Delete the session.
  DeleteSession(session_id);
}

TEST_F(UserDictionarySessionHandlerTest, ClearStorage) {
  // Set up a user dictionary.
  {
    Clear();
    const uint64_t session_id = CreateSession();
    const uint64_t dictionary_id =
        CreateUserDictionary(session_id, "dictionary");
    AddUserDictionaryEntry(session_id, dictionary_id, "reading", "word",
                           UserDictionary::NOUN, "");
    AddUserDictionaryEntry(session_id, dictionary_id, "reading", "word2",
                           UserDictionary::NOUN, "");
    ASSERT_EQ(2, GetUserDictionaryEntrySize(session_id, dictionary_id));
    DeleteSession(session_id);
  }
  // Test CLEAR_STORAGE command.
  {
    Clear();
    command_->set_type(UserDictionaryCommand::CLEAR_STORAGE);
    EXPECT_TRUE(handler_->Evaluate(*command_, status_.get()));
    EXPECT_EQ(UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS,
              status_->status());
  }
  // After the command invocation, storage becomes empty.
  {
    Clear();
    const uint64_t session_id = CreateSession();
    command_->set_type(UserDictionaryCommand::GET_STORAGE);
    command_->set_session_id(session_id);
    ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
    EXPECT_PROTO_PEQ(
        "status: USER_DICTIONARY_COMMAND_SUCCESS\n"
        "storage <\n"
        ">\n",
        *status_);
  }
}

TEST_F(UserDictionarySessionHandlerTest, CreateDeleteSession) {
  const uint64_t session_id = CreateSession();

  // Without session_id, the command should fail.
  Clear();
  command_->set_type(UserDictionaryCommand::DELETE_SESSION);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_EQ(UserDictionaryCommandStatus::INVALID_ARGUMENT, status_->status());

  // Test for invalid session id.
  Clear();
  command_->set_type(UserDictionaryCommand::DELETE_SESSION);
  command_->set_session_id(0);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_EQ(UserDictionaryCommandStatus::UNKNOWN_SESSION_ID, status_->status());

  // Test for valid session.
  DeleteSession(session_id);

  // Test deletion twice should fail.
  Clear();
  command_->set_type(UserDictionaryCommand::DELETE_SESSION);
  command_->set_session_id(session_id);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_EQ(UserDictionaryCommandStatus::UNKNOWN_SESSION_ID, status_->status());
}

TEST_F(UserDictionarySessionHandlerTest, CreateTwice) {
  const uint64_t session_id1 = CreateSession();
  const uint64_t session_id2 = CreateSession();
  ASSERT_NE(session_id1, session_id2);

  // Here, the first session is lost, so trying to delete it should fail
  // with unknown id error, and deletion of the second session should
  // success.
  Clear();
  command_->set_type(UserDictionaryCommand::DELETE_SESSION);
  command_->set_session_id(session_id1);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_EQ(UserDictionaryCommandStatus::UNKNOWN_SESSION_ID, status_->status());

  DeleteSession(session_id2);
}

TEST_F(UserDictionarySessionHandlerTest, LoadAndSave) {
  const uint64_t session_id = CreateSession();

  // First of all, create a dictionary named "dictionary".
  CreateUserDictionary(session_id, "dictionary");

  // Save the current storage.
  Clear();
  command_->set_type(UserDictionaryCommand::SAVE);
  command_->set_session_id(session_id);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: USER_DICTIONARY_COMMAND_SUCCESS", *status_);

  // Create another dictionary.
  CreateUserDictionary(session_id, "dictionary2");

  Clear();
  command_->set_type(UserDictionaryCommand::GET_USER_DICTIONARY_NAME_LIST);
  command_->set_session_id(session_id);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_PEQ(
      "status: USER_DICTIONARY_COMMAND_SUCCESS\n"
      "storage: <\n"
      "  dictionaries: < name: \"dictionary\" >\n"
      "  dictionaries: < name: \"dictionary2\" >\n"
      ">",
      *status_);

  // Load the data to the storage. So the storage content should be reverted
  // to the saved one.
  Clear();
  command_->set_type(UserDictionaryCommand::LOAD);
  command_->set_session_id(session_id);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: USER_DICTIONARY_COMMAND_SUCCESS", *status_);

  Clear();
  command_->set_type(UserDictionaryCommand::GET_USER_DICTIONARY_NAME_LIST);
  command_->set_session_id(session_id);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_PEQ(
      "status: USER_DICTIONARY_COMMAND_SUCCESS\n"
      "storage: <\n"
      "  dictionaries: < name: \"dictionary\" >\n"
      ">",
      *status_);

  DeleteSession(session_id);
}

TEST_F(UserDictionarySessionHandlerTest, LoadWithEnsuringNonEmptyStorage) {
  const uint64_t session_id = CreateSession();

  Clear();
  command_->set_type(UserDictionaryCommand::SET_DEFAULT_DICTIONARY_NAME);
  command_->set_session_id(session_id);
  command_->set_dictionary_name("abcde");
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: USER_DICTIONARY_COMMAND_SUCCESS", *status_);

  // Load the data to the storage. It should be failed as there should be no
  // file yet. Regardless of the failure, a new dictionary should be
  // created.
  Clear();
  command_->set_type(UserDictionaryCommand::LOAD);
  command_->set_session_id(session_id);
  command_->set_ensure_non_empty_storage(true);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: FILE_NOT_FOUND", *status_);

  Clear();
  command_->set_type(UserDictionaryCommand::GET_USER_DICTIONARY_NAME_LIST);
  command_->set_session_id(session_id);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_PEQ(
      "status: USER_DICTIONARY_COMMAND_SUCCESS\n"
      "storage: <\n"
      "  dictionaries: < name: \"abcde\" >\n"
      ">",
      *status_);

  DeleteSession(session_id);
}

TEST_F(UserDictionarySessionHandlerTest, Undo) {
  const uint64_t session_id = CreateSession();

  // At first, the session shouldn't be undoable.
  Clear();
  command_->set_type(UserDictionaryCommand::CHECK_UNDOABILITY);
  command_->set_session_id(session_id);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: NO_UNDO_HISTORY", *status_);

  // The first undo without any preceding operation should fail.
  Clear();
  command_->set_type(UserDictionaryCommand::UNDO);
  command_->set_session_id(session_id);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: NO_UNDO_HISTORY", *status_);

  // Create a dictionary.
  CreateUserDictionary(session_id, "dictionary");

  // Now the session should be undoable.
  Clear();
  command_->set_type(UserDictionaryCommand::CHECK_UNDOABILITY);
  command_->set_session_id(session_id);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: USER_DICTIONARY_COMMAND_SUCCESS", *status_);

  // And then undo. This time, the command should succeed.
  Clear();
  command_->set_type(UserDictionaryCommand::UNDO);
  command_->set_session_id(session_id);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: USER_DICTIONARY_COMMAND_SUCCESS", *status_);

  DeleteSession(session_id);
}

TEST_F(UserDictionarySessionHandlerTest, GetEntries) {
  const uint64_t session_id = CreateSession();
  const uint64_t dictionary_id = CreateUserDictionary(session_id, "dictionary");

  AddUserDictionaryEntry(session_id, dictionary_id, "key1", "value1",
                         UserDictionary::NOUN, "comment1");
  AddUserDictionaryEntry(session_id, dictionary_id, "key2", "value2",
                         UserDictionary::NOUN, "comment2");
  AddUserDictionaryEntry(session_id, dictionary_id, "key3", "value3",
                         UserDictionary::SYMBOL, "comment3");
  ASSERT_EQ(3, GetUserDictionaryEntrySize(session_id, dictionary_id));

  std::vector<int> indices;
  indices.push_back(0);
  indices.push_back(2);
  GetUserDictionaryEntries(session_id, dictionary_id, indices);
  EXPECT_PROTO_PEQ(
      "entries: <\n"
      "  key: \"key1\"\n"
      "  value: \"value1\"\n"
      "  pos: NOUN\n"
      "  comment: \"comment1\"\n"
      ">"
      "entries: <\n"
      "  key: \"key3\"\n"
      "  value: \"value3\"\n"
      "  pos: SYMBOL\n"
      "  comment: \"comment3\"\n"
      ">",
      *status_);

  // Invalid dictionary ID
  Clear();
  command_->set_type(UserDictionaryCommand::GET_ENTRIES);
  command_->set_session_id(session_id);
  command_->set_dictionary_id(kInvalidDictionaryId);
  command_->add_entry_index(0);
  EXPECT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_EQ(UserDictionaryCommandStatus::UNKNOWN_DICTIONARY_ID,
            status_->status());

  // Invalid entry index
  Clear();
  command_->set_type(UserDictionaryCommand::GET_ENTRIES);
  command_->set_session_id(session_id);
  command_->set_dictionary_id(dictionary_id);
  command_->add_entry_index(0);
  command_->add_entry_index(3);
  EXPECT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_EQ(UserDictionaryCommandStatus::ENTRY_INDEX_OUT_OF_RANGE,
            status_->status());

  // Invalid entry index
  Clear();
  command_->set_type(UserDictionaryCommand::GET_ENTRIES);
  command_->set_session_id(session_id);
  command_->set_dictionary_id(dictionary_id);
  command_->add_entry_index(0);
  command_->add_entry_index(-1);
  EXPECT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_EQ(UserDictionaryCommandStatus::ENTRY_INDEX_OUT_OF_RANGE,
            status_->status());

  DeleteSession(session_id);
}

TEST_F(UserDictionarySessionHandlerTest, DictionaryEdit) {
  const uint64_t session_id = CreateSession();

  // Create a dictionary named "dictionary".
  CreateUserDictionary(session_id, "dictionary");

  Clear();
  command_->set_type(UserDictionaryCommand::GET_USER_DICTIONARY_NAME_LIST);
  command_->set_session_id(session_id);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_PEQ(
      "status: USER_DICTIONARY_COMMAND_SUCCESS\n"
      "storage: <\n"
      "  dictionaries: < name: \"dictionary\" >\n"
      ">",
      *status_);

  // Create another dictionary named "dictionary2".
  CreateUserDictionary(session_id, "dictionary2");

  Clear();
  command_->set_type(UserDictionaryCommand::GET_USER_DICTIONARY_NAME_LIST);
  command_->set_session_id(session_id);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_PEQ(
      "status: USER_DICTIONARY_COMMAND_SUCCESS\n"
      "storage: <\n"
      "  dictionaries: < name: \"dictionary\" >\n"
      "  dictionaries: < name: \"dictionary2\" >\n"
      ">",
      *status_);
  const uint64_t dictionary_id1 = status_->storage().dictionaries(0).id();
  const uint64_t dictionary_id2 = status_->storage().dictionaries(1).id();

  // Dictionary creation without name should be failed.
  Clear();
  command_->set_type(UserDictionaryCommand::CREATE_DICTIONARY);
  command_->set_session_id(session_id);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: INVALID_ARGUMENT", *status_);

  // Then rename the second dictionary to "dictionary3".
  Clear();
  command_->set_type(UserDictionaryCommand::RENAME_DICTIONARY);
  command_->set_session_id(session_id);
  command_->set_dictionary_id(dictionary_id2);
  command_->set_dictionary_name("dictionary3");
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: USER_DICTIONARY_COMMAND_SUCCESS", *status_);

  Clear();
  command_->set_type(UserDictionaryCommand::GET_USER_DICTIONARY_NAME_LIST);
  command_->set_session_id(session_id);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_PEQ(
      "status: USER_DICTIONARY_COMMAND_SUCCESS\n"
      "storage: <\n"
      "  dictionaries: < name: \"dictionary\" >\n"
      "  dictionaries: < name: \"dictionary3\" >\n"
      ">",
      *status_);
  EXPECT_EQ(dictionary_id1, status_->storage().dictionaries(0).id());
  EXPECT_EQ(dictionary_id2, status_->storage().dictionaries(1).id());

  // Dictionary renaming without dictionary_id or new name should be failed.
  Clear();
  command_->set_type(UserDictionaryCommand::RENAME_DICTIONARY);
  command_->set_session_id(session_id);
  command_->set_dictionary_id(dictionary_id2);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: INVALID_ARGUMENT", *status_);

  Clear();
  command_->set_type(UserDictionaryCommand::RENAME_DICTIONARY);
  command_->set_session_id(session_id);
  command_->set_dictionary_name("new dictionary name");
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: INVALID_ARGUMENT", *status_);

  // Then delete the first dictionary.
  Clear();
  command_->set_type(UserDictionaryCommand::DELETE_DICTIONARY);
  command_->set_session_id(session_id);
  command_->set_dictionary_id(dictionary_id1);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: USER_DICTIONARY_COMMAND_SUCCESS", *status_);

  Clear();
  command_->set_type(UserDictionaryCommand::GET_USER_DICTIONARY_NAME_LIST);
  command_->set_session_id(session_id);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_PEQ(
      "status: USER_DICTIONARY_COMMAND_SUCCESS\n"
      "storage: <\n"
      "  dictionaries: < name: \"dictionary3\" >\n"
      ">",
      *status_);
  EXPECT_EQ(dictionary_id2, status_->storage().dictionaries(0).id());

  // Dictionary deletion without dictionary id should be failed.
  Clear();
  command_->set_type(UserDictionaryCommand::DELETE_DICTIONARY);
  command_->set_session_id(session_id);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: INVALID_ARGUMENT", *status_);

  // Then delete the dictionary again with the ensure_non_empty_dictionary.
  Clear();
  command_->set_type(UserDictionaryCommand::SET_DEFAULT_DICTIONARY_NAME);
  command_->set_session_id(session_id);
  command_->set_dictionary_name("abcde");
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: USER_DICTIONARY_COMMAND_SUCCESS", *status_);

  Clear();
  command_->set_type(UserDictionaryCommand::DELETE_DICTIONARY);
  command_->set_session_id(session_id);
  command_->set_dictionary_id(dictionary_id2);
  command_->set_ensure_non_empty_storage(true);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: USER_DICTIONARY_COMMAND_SUCCESS", *status_);

  Clear();
  command_->set_type(UserDictionaryCommand::GET_USER_DICTIONARY_NAME_LIST);
  command_->set_session_id(session_id);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_PEQ(
      "status: USER_DICTIONARY_COMMAND_SUCCESS\n"
      "storage: <\n"
      "  dictionaries: < name: \"abcde\" >\n"
      ">",
      *status_);
  EXPECT_NE(dictionary_id2, status_->storage().dictionaries(0).id());

  DeleteSession(session_id);
}

TEST_F(UserDictionarySessionHandlerTest, AddEntry) {
  const uint64_t session_id = CreateSession();
  const uint64_t dictionary_id = CreateUserDictionary(session_id, "dictionary");
  ASSERT_EQ(0, GetUserDictionaryEntrySize(session_id, dictionary_id));

  // Add an entry.
  AddUserDictionaryEntry(session_id, dictionary_id, "reading", "word",
                         UserDictionary::NOUN, "");
  ASSERT_EQ(1, GetUserDictionaryEntrySize(session_id, dictionary_id));
  GetAllUserDictionaryEntries(session_id, dictionary_id);
  EXPECT_PROTO_PEQ(
      "entries: <\n"
      "  key: \"reading\"\n"
      "  value: \"word\"\n"
      "  pos: NOUN\n"
      ">\n",
      *status_);

  // AddEntry without dictionary_id or entry should be failed.
  Clear();
  command_->set_type(UserDictionaryCommand::ADD_ENTRY);
  command_->set_session_id(session_id);
  command_->set_dictionary_id(dictionary_id);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: INVALID_ARGUMENT", *status_);

  Clear();
  command_->set_type(UserDictionaryCommand::ADD_ENTRY);
  command_->set_session_id(session_id);
  {
    UserDictionary::Entry *entry = command_->mutable_entry();
    entry->set_key("reading");
    entry->set_value("word");
    entry->set_pos(UserDictionary::NOUN);
  }
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: INVALID_ARGUMENT", *status_);

  DeleteSession(session_id);
}

TEST_F(UserDictionarySessionHandlerTest, EditEntry) {
  const uint64_t session_id = CreateSession();
  const uint64_t dictionary_id = CreateUserDictionary(session_id, "dictionary");
  ASSERT_EQ(0, GetUserDictionaryEntrySize(session_id, dictionary_id));

  // Add an entry.
  AddUserDictionaryEntry(session_id, dictionary_id, "reading", "word",
                         UserDictionary::NOUN, "");
  ASSERT_EQ(1, GetUserDictionaryEntrySize(session_id, dictionary_id));

  // Add another entry.
  AddUserDictionaryEntry(session_id, dictionary_id, "reading2", "word2",
                         UserDictionary::NOUN, "");
  ASSERT_EQ(2, GetUserDictionaryEntrySize(session_id, dictionary_id));
  GetAllUserDictionaryEntries(session_id, dictionary_id);
  EXPECT_PROTO_PEQ(
      "entries: <\n"
      "  key: \"reading\"\n"
      "  value: \"word\"\n"
      "  pos: NOUN\n"
      ">\n"
      "entries: <\n"
      "  key: \"reading2\"\n"
      "  value: \"word2\"\n"
      "  pos: NOUN\n"
      ">",
      *status_);

  Clear();
  command_->set_type(UserDictionaryCommand::EDIT_ENTRY);
  command_->set_session_id(session_id);
  command_->set_dictionary_id(dictionary_id);
  command_->add_entry_index(1);
  {
    UserDictionary::Entry *entry = command_->mutable_entry();
    entry->set_key("reading3");
    entry->set_value("word3");
    entry->set_pos(UserDictionary::PREFIX);
  }
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: USER_DICTIONARY_COMMAND_SUCCESS", *status_);

  ASSERT_EQ(2, GetUserDictionaryEntrySize(session_id, dictionary_id));
  GetAllUserDictionaryEntries(session_id, dictionary_id);
  EXPECT_PROTO_PEQ(
      "entries: <\n"
      "  key: \"reading\"\n"
      "  value: \"word\"\n"
      "  pos: NOUN\n"
      ">"
      "entries: <\n"
      "  key: \"reading3\"\n"
      "  value: \"word3\"\n"
      "  pos: PREFIX\n"
      ">",
      *status_);

  // EditEntry without dictionary_id or entry should be failed.
  // Also, the number of entry_index should exactly equals to '1'.
  Clear();
  command_->set_type(UserDictionaryCommand::EDIT_ENTRY);
  command_->set_session_id(session_id);
  command_->set_dictionary_id(dictionary_id);
  command_->add_entry_index(1);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: INVALID_ARGUMENT", *status_);

  Clear();
  command_->set_type(UserDictionaryCommand::EDIT_ENTRY);
  command_->set_session_id(session_id);
  command_->add_entry_index(1);
  {
    UserDictionary::Entry *entry = command_->mutable_entry();
    entry->set_key("reading3");
    entry->set_value("word3");
    entry->set_pos(UserDictionary::PREFIX);
  }
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: INVALID_ARGUMENT", *status_);

  Clear();
  command_->set_type(UserDictionaryCommand::EDIT_ENTRY);
  command_->set_session_id(session_id);
  command_->set_dictionary_id(dictionary_id);
  {
    UserDictionary::Entry *entry = command_->mutable_entry();
    entry->set_key("reading3");
    entry->set_value("word3");
    entry->set_pos(UserDictionary::PREFIX);
  }
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: INVALID_ARGUMENT", *status_);

  Clear();
  command_->set_type(UserDictionaryCommand::EDIT_ENTRY);
  command_->set_session_id(session_id);
  command_->set_dictionary_id(dictionary_id);
  command_->add_entry_index(0);
  command_->add_entry_index(1);
  {
    UserDictionary::Entry *entry = command_->mutable_entry();
    entry->set_key("reading3");
    entry->set_value("word3");
    entry->set_pos(UserDictionary::PREFIX);
  }
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: INVALID_ARGUMENT", *status_);

  DeleteSession(session_id);
}

TEST_F(UserDictionarySessionHandlerTest, DeleteEntry) {
  const uint64_t session_id = CreateSession();
  const uint64_t dictionary_id = CreateUserDictionary(session_id, "dictionary");
  ASSERT_EQ(0, GetUserDictionaryEntrySize(session_id, dictionary_id));

  // Add entries.
  AddUserDictionaryEntry(session_id, dictionary_id, "reading", "word",
                         UserDictionary::NOUN, "");
  AddUserDictionaryEntry(session_id, dictionary_id, "reading2", "word2",
                         UserDictionary::NOUN, "");
  AddUserDictionaryEntry(session_id, dictionary_id, "reading3", "word3",
                         UserDictionary::NOUN, "");
  AddUserDictionaryEntry(session_id, dictionary_id, "reading4", "word4",
                         UserDictionary::NOUN, "");
  AddUserDictionaryEntry(session_id, dictionary_id, "reading5", "word5",
                         UserDictionary::NOUN, "");
  ASSERT_EQ(5, GetUserDictionaryEntrySize(session_id, dictionary_id));

  // Delete the second and fourth entries.
  Clear();
  command_->set_type(UserDictionaryCommand::DELETE_ENTRY);
  command_->set_session_id(session_id);
  command_->set_dictionary_id(dictionary_id);
  command_->add_entry_index(1);
  command_->add_entry_index(3);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_EQ(UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS,
            status_->status());
  ASSERT_EQ(3, GetUserDictionaryEntrySize(session_id, dictionary_id));
  GetAllUserDictionaryEntries(session_id, dictionary_id);
  EXPECT_PROTO_PEQ(
      "entries: <\n"
      "  key: \"reading\"\n"
      "  value: \"word\"\n"
      "  pos: NOUN\n"
      ">"
      "entries: <\n"
      "  key: \"reading3\"\n"
      "  value: \"word3\"\n"
      "  pos: NOUN\n"
      ">"
      "entries: <\n"
      "  key: \"reading5\"\n"
      "  value: \"word5\"\n"
      "  pos: NOUN\n"
      ">",
      *status_);

  // Entry deletion without dictionary_id or entry_index should be failed.
  Clear();
  command_->set_type(UserDictionaryCommand::GET_ENTRIES);
  command_->set_session_id(session_id);
  command_->add_entry_index(0);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: INVALID_ARGUMENT", *status_);
  ASSERT_EQ(3, GetUserDictionaryEntrySize(session_id, dictionary_id));

  Clear();
  command_->set_type(UserDictionaryCommand::GET_ENTRIES);
  command_->set_session_id(session_id);
  command_->set_dictionary_id(dictionary_id);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: INVALID_ARGUMENT", *status_);
  ASSERT_EQ(3, GetUserDictionaryEntrySize(session_id, dictionary_id));

  DeleteSession(session_id);
}

TEST_F(UserDictionarySessionHandlerTest, ImportData1) {
  const uint64_t session_id = CreateSession();

  // First of all, create a dictionary named "dictionary".
  const uint64_t dictionary_id = CreateUserDictionary(session_id, "dictionary");

  // Import data to the dictionary.
  Clear();
  command_->set_type(UserDictionaryCommand::IMPORT_DATA);
  command_->set_session_id(session_id);
  command_->set_dictionary_id(dictionary_id);
  command_->set_data(kDictionaryData);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_PEQ("status: USER_DICTIONARY_COMMAND_SUCCESS", *status_);
  ASSERT_TRUE(status_->has_dictionary_id());
  EXPECT_EQ(dictionary_id, status_->dictionary_id());

  // Make sure the size of the data.
  ASSERT_EQ(4, GetUserDictionaryEntrySize(session_id, dictionary_id));

  DeleteSession(session_id);
}

TEST_F(UserDictionarySessionHandlerTest, ImportData2) {
  const uint64_t session_id = CreateSession();

  // Import data to a new dictionary.
  Clear();
  command_->set_type(UserDictionaryCommand::IMPORT_DATA);
  command_->set_session_id(session_id);
  command_->set_dictionary_name("user dictionary");
  command_->set_data(kDictionaryData);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_PEQ("status: USER_DICTIONARY_COMMAND_SUCCESS", *status_);
  ASSERT_TRUE(status_->has_dictionary_id());
  const uint64_t dictionary_id = status_->dictionary_id();

  // Make sure the size of the data.
  ASSERT_EQ(4, GetUserDictionaryEntrySize(session_id, dictionary_id));

  DeleteSession(session_id);
}

TEST_F(UserDictionarySessionHandlerTest, ImportDataFailure) {
  const uint64_t session_id = CreateSession();
  const uint64_t dictionary_id = CreateUserDictionary(session_id, "dictionary");

  // Fail if the data is missing.
  Clear();
  command_->set_type(UserDictionaryCommand::IMPORT_DATA);
  command_->set_session_id(session_id);
  command_->set_dictionary_name("user dictionary");
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: INVALID_ARGUMENT", *status_);

  Clear();
  command_->set_type(UserDictionaryCommand::IMPORT_DATA);
  command_->set_session_id(session_id);
  command_->set_dictionary_id(dictionary_id);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: INVALID_ARGUMENT", *status_);

  // Fail if neither dictionary_name nor dictionary_id is set.
  Clear();
  command_->set_type(UserDictionaryCommand::IMPORT_DATA);
  command_->set_session_id(session_id);
  command_->set_data(kDictionaryData);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_EQ("status: INVALID_ARGUMENT", *status_);

  DeleteSession(session_id);
}

TEST_F(UserDictionarySessionHandlerTest, ImportDataIgnoringInvalidEntries) {
  const uint64_t session_id = CreateSession();

  std::string data = kDictionaryData;
  data.append("☻\tEMOTICON\t名詞\n");  // Invalid symbol reading.
  data.append("読み\tYOMI\t名詞\n");   // Invalid Kanji reading.

  // Import data to a new dictionary.
  Clear();
  command_->set_type(UserDictionaryCommand::IMPORT_DATA);
  command_->set_session_id(session_id);
  command_->set_dictionary_name("user dictionary");
  command_->set_data(data);
  command_->set_ignore_invalid_entries(true);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_PEQ("status: USER_DICTIONARY_COMMAND_SUCCESS", *status_);
  ASSERT_TRUE(status_->has_dictionary_id());
  const uint64_t dictionary_id = status_->dictionary_id();

  // Make sure the size of the data.
  ASSERT_EQ(4, GetUserDictionaryEntrySize(session_id, dictionary_id));

  DeleteSession(session_id);
}

TEST_F(UserDictionarySessionHandlerTest, GetStorage) {
  const uint64_t session_id = CreateSession();
  const uint64_t dictionary_id1 =
      CreateUserDictionary(session_id, "dictionary1");

  AddUserDictionaryEntry(session_id, dictionary_id1, "reading1_1", "word1_1",
                         UserDictionary::NOUN, "");
  AddUserDictionaryEntry(session_id, dictionary_id1, "reading1_2", "word1_2",
                         UserDictionary::NOUN, "");

  // Create a dictionary named "dictionary2".
  const uint64_t dictionary_id2 =
      CreateUserDictionary(session_id, "dictionary2");

  AddUserDictionaryEntry(session_id, dictionary_id2, "reading2_1", "word2_1",
                         UserDictionary::NOUN, "");

  Clear();
  command_->set_type(UserDictionaryCommand::GET_STORAGE);
  command_->set_session_id(session_id);
  ASSERT_TRUE(handler_->Evaluate(*command_, status_.get()));
  EXPECT_PROTO_PEQ(
      "status: USER_DICTIONARY_COMMAND_SUCCESS\n"
      "storage <\n"
      "  dictionaries <\n"
      "    name: \"dictionary1\"\n"
      "    entries <\n"
      "      key: \"reading1_1\"\n"
      "      value: \"word1_1\"\n"
      "      comment: \"\"\n"
      "      pos: NOUN\n"
      "    >\n"
      "    entries <\n"
      "      key: \"reading1_2\"\n"
      "      value: \"word1_2\"\n"
      "      comment: \"\"\n"
      "      pos: NOUN\n"
      "    >\n"
      "  >\n"
      "  dictionaries <\n"
      "    name: \"dictionary2\"\n"
      "    entries <\n"
      "      key: \"reading2_1\"\n"
      "      value: \"word2_1\"\n"
      "      comment: \"\"\n"
      "      pos: NOUN\n"
      "    >\n"
      "  >\n"
      ">\n",
      *status_);

  DeleteSession(session_id);
}

}  // namespace
}  // namespace mozc
