// Copyright (c) 2011 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 "chrome/browser/chromeos/login/owner_manager.h"
#include "chrome/browser/chromeos/login/owner_manager_unittest.h"

#include <string>

#include "base/file_path.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/memory/scoped_temp_dir.h"
#include "crypto/nss_util.h"
#include "crypto/rsa_private_key.h"
#include "chrome/browser/chromeos/login/mock_owner_key_utils.h"
#include "content/browser/browser_thread.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::crypto::RSAPrivateKey;
using ::testing::DoAll;
using ::testing::Eq;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::SetArgumentPointee;
using ::testing::_;

namespace chromeos {

class OwnerManagerTest : public ::testing::Test {
 public:
  OwnerManagerTest()
      : message_loop_(MessageLoop::TYPE_UI),
        ui_thread_(BrowserThread::UI, &message_loop_),
        file_thread_(BrowserThread::FILE),
        mock_(new MockKeyUtils),
        injector_(mock_) /* injector_ takes ownership of mock_ */ {
  }
  virtual ~OwnerManagerTest() {}

  virtual void SetUp() {
    crypto::OpenPersistentNSSDB();  // TODO(cmasone): use test DB instead
    fake_private_key_.reset(RSAPrivateKey::Create(256));
    ASSERT_TRUE(fake_private_key_->ExportPublicKey(&fake_public_key_));

    // Mimic ownership.
    ASSERT_TRUE(tmpdir_.CreateUniqueTempDir());
    ASSERT_TRUE(file_util::CreateTemporaryFileInDir(tmpdir_.path(), &tmpfile_));

    file_thread_.Start();
    OwnerKeyUtils::set_factory(&injector_);
  }

  virtual void TearDown() {
    OwnerKeyUtils::set_factory(NULL);
  }

  void StartUnowned() {
    file_util::Delete(tmpfile_, false);
  }

  void InjectKeys(OwnerManager* manager) {
    manager->public_key_ = fake_public_key_;
    manager->private_key_.reset(fake_private_key_.release());
  }

  ScopedTempDir tmpdir_;
  FilePath tmpfile_;

  MessageLoop message_loop_;
  BrowserThread ui_thread_;
  BrowserThread file_thread_;

  std::vector<uint8> fake_public_key_;
  scoped_ptr<RSAPrivateKey> fake_private_key_;

  MockKeyUtils* mock_;
  MockInjector injector_;

};

TEST_F(OwnerManagerTest, UpdateOwnerKey) {
  scoped_refptr<OwnerManager> manager(new OwnerManager);

  MockKeyUpdateUser delegate;
  BrowserThread::PostTask(
      BrowserThread::FILE, FROM_HERE,
      NewRunnableMethod(manager.get(),
                        &OwnerManager::UpdateOwnerKey,
                        BrowserThread::UI,
                        std::vector<uint8>(),
                        &delegate));
  message_loop_.Run();
}

TEST_F(OwnerManagerTest, LoadOwnerKeyFail) {
  StartUnowned();
  MockKeyLoadObserver loader;
  scoped_refptr<OwnerManager> manager(new OwnerManager);

  EXPECT_CALL(*mock_, GetOwnerKeyFilePath())
      .WillRepeatedly(Return(tmpfile_));
  EXPECT_CALL(*mock_, ImportPublicKey(tmpfile_, _))
      .WillOnce(Return(false))
      .RetiresOnSaturation();

  BrowserThread::PostTask(
      BrowserThread::FILE, FROM_HERE,
      NewRunnableMethod(manager.get(),
                        &OwnerManager::LoadOwnerKey));
  message_loop_.Run();
}

TEST_F(OwnerManagerTest, AlreadyLoadedOwnerKey) {
  MockKeyLoadObserver loader;
  loader.ExpectKeyFetchSuccess(true);
  scoped_refptr<OwnerManager> manager(new OwnerManager);

  EXPECT_CALL(*mock_, GetOwnerKeyFilePath())
      .WillRepeatedly(Return(tmpfile_));

  InjectKeys(manager.get());

  BrowserThread::PostTask(
      BrowserThread::FILE, FROM_HERE,
      NewRunnableMethod(manager.get(),
                        &OwnerManager::LoadOwnerKey));

  message_loop_.Run();
}

TEST_F(OwnerManagerTest, LoadOwnerKey) {
  MockKeyLoadObserver loader;
  loader.ExpectKeyFetchSuccess(true);
  scoped_refptr<OwnerManager> manager(new OwnerManager);

  EXPECT_CALL(*mock_, GetOwnerKeyFilePath())
      .WillRepeatedly(Return(tmpfile_));
  EXPECT_CALL(*mock_, ImportPublicKey(tmpfile_, _))
      .WillOnce(DoAll(SetArgumentPointee<1>(fake_public_key_),
                      Return(true)))
      .RetiresOnSaturation();

  BrowserThread::PostTask(
      BrowserThread::FILE, FROM_HERE,
      NewRunnableMethod(manager.get(),
                        &OwnerManager::LoadOwnerKey));

  message_loop_.Run();
}

TEST_F(OwnerManagerTest, GetKeyFailDuringVerify) {
  StartUnowned();
  MockKeyLoadObserver loader;
  loader.ExpectKeyFetchSuccess(false);
  scoped_refptr<OwnerManager> manager(new OwnerManager);

  EXPECT_CALL(*mock_, GetOwnerKeyFilePath())
      .WillRepeatedly(Return(tmpfile_));
  EXPECT_CALL(*mock_, ImportPublicKey(tmpfile_, _))
      .WillOnce(Return(false))
      .RetiresOnSaturation();

  MockKeyUser delegate(OwnerManager::KEY_UNAVAILABLE);

  BrowserThread::PostTask(
      BrowserThread::FILE, FROM_HERE,
      NewRunnableMethod(manager.get(),
                        &OwnerManager::Verify,
                        BrowserThread::UI,
                        std::string(),
                        std::vector<uint8>(),
                        &delegate));
  message_loop_.Run();
}

TEST_F(OwnerManagerTest, AlreadyHaveKeysVerify) {
  scoped_refptr<OwnerManager> manager(new OwnerManager);

  std::string data;
  std::vector<uint8> sig(0, 2);

  EXPECT_CALL(*mock_, GetOwnerKeyFilePath())
      .WillRepeatedly(Return(tmpfile_));
  EXPECT_CALL(*mock_, Verify(Eq(data), Eq(sig), Eq(fake_public_key_)))
      .WillOnce(Return(true))
      .RetiresOnSaturation();

  InjectKeys(manager.get());
  MockKeyUser delegate(OwnerManager::SUCCESS);

  BrowserThread::PostTask(
      BrowserThread::FILE, FROM_HERE,
      NewRunnableMethod(manager.get(),
                        &OwnerManager::Verify,
                        BrowserThread::UI,
                        data,
                        sig,
                        &delegate));
  message_loop_.Run();
}

TEST_F(OwnerManagerTest, GetKeyAndVerify) {
  MockKeyLoadObserver loader;
  loader.ExpectKeyFetchSuccess(true);
  loader.SetQuitOnKeyFetch(false);
  scoped_refptr<OwnerManager> manager(new OwnerManager);

  std::string data;
  std::vector<uint8> sig(0, 2);

  EXPECT_CALL(*mock_, GetOwnerKeyFilePath())
      .WillRepeatedly(Return(tmpfile_));
  EXPECT_CALL(*mock_, ImportPublicKey(tmpfile_, _))
      .WillOnce(DoAll(SetArgumentPointee<1>(fake_public_key_),
                      Return(true)))
      .RetiresOnSaturation();
  EXPECT_CALL(*mock_, Verify(Eq(data), Eq(sig), Eq(fake_public_key_)))
      .WillOnce(Return(true))
      .RetiresOnSaturation();

  MockKeyUser delegate(OwnerManager::SUCCESS);

  BrowserThread::PostTask(
      BrowserThread::FILE, FROM_HERE,
      NewRunnableMethod(manager.get(),
                        &OwnerManager::Verify,
                        BrowserThread::UI,
                        data,
                        sig,
                        &delegate));
  message_loop_.Run();
}

TEST_F(OwnerManagerTest, AlreadyHaveKeysSign) {
  scoped_refptr<OwnerManager> manager(new OwnerManager);

  std::string data;
  std::vector<uint8> sig(0, 2);

  EXPECT_CALL(*mock_, GetOwnerKeyFilePath())
      .WillRepeatedly(Return(tmpfile_));
  EXPECT_CALL(*mock_, Sign(Eq(data), _, Eq(fake_private_key_.get())))
      .WillOnce(DoAll(SetArgumentPointee<1>(sig),
                      Return(true)))
      .RetiresOnSaturation();

  InjectKeys(manager.get());
  MockSigner delegate(OwnerManager::SUCCESS, sig);

  BrowserThread::PostTask(
      BrowserThread::FILE, FROM_HERE,
      NewRunnableMethod(manager.get(),
                        &OwnerManager::Sign,
                        BrowserThread::UI,
                        data,
                        &delegate));
  message_loop_.Run();
}

}  // namespace chromeos