// Copyright 2016 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 <cstdarg>  // Needed to mock ellipsis
#include <string>

#include "base/macros.h"
#include "base/test/test_simple_task_runner.h"
#include "components/os_crypt/key_storage_keyring.h"
#include "components/os_crypt/keyring_util_linux.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

#if defined(GOOGLE_CHROME_BUILD)
const char kApplicationName[] = "chrome";
#else
const char kApplicationName[] = "chromium";
#endif

// Replaces some of GnomeKeyringLoader's methods with mocked ones.
class MockGnomeKeyringLoader : public GnomeKeyringLoader {
 public:
  static void ResetForOSCrypt() {
    GnomeKeyringLoader::gnome_keyring_find_password_sync_ptr =
        &mock_gnome_keyring_find_password_sync;
    GnomeKeyringLoader::gnome_keyring_store_password_sync_ptr =
        &mock_gnome_keyring_store_password_sync;
    GnomeKeyringLoader::gnome_keyring_free_password_ptr =
        &mock_gnome_keyring_free_password;

    delete s_password_ptr_;
    s_password_ptr_ = nullptr;

    // GnomeKeyringLoader does not (re)load keyring is this is true.
    GnomeKeyringLoader::keyring_loaded = true;
  }

  static void SetOSCryptPassword(const char* password) {
    delete s_password_ptr_;
    s_password_ptr_ = new std::string(password);
  }

  static void TearDown() {
    delete s_password_ptr_;
    s_password_ptr_ = nullptr;
    // Function pointers will be reset the next time loading is requested.
    GnomeKeyringLoader::keyring_loaded = false;
  }

 private:
  // These methods are used to redirect calls through GnomeKeyringLoader.
  static GnomeKeyringResult mock_gnome_keyring_find_password_sync(
      const GnomeKeyringPasswordSchema* schema,
      gchar** password,
      ...);

  static GnomeKeyringResult mock_gnome_keyring_store_password_sync(
      const GnomeKeyringPasswordSchema* schema,
      const gchar* keyring,
      const gchar* display_name,
      const gchar* password,
      ...);

  static void mock_gnome_keyring_free_password(gchar* password);

  static std::string* s_password_ptr_;
};

std::string* MockGnomeKeyringLoader::s_password_ptr_ = nullptr;

// static
GnomeKeyringResult
MockGnomeKeyringLoader::mock_gnome_keyring_find_password_sync(
    const GnomeKeyringPasswordSchema* schema,
    gchar** password,
    ...) {
  va_list attrs;
  va_start(attrs, password);
  EXPECT_STREQ("application", va_arg(attrs, const char*));
  EXPECT_STREQ(kApplicationName, va_arg(attrs, const char*));
  EXPECT_EQ(nullptr, va_arg(attrs, const char*));
  va_end(attrs);

  if (!s_password_ptr_)
    return GNOME_KEYRING_RESULT_NO_MATCH;
  *password = strdup(s_password_ptr_->c_str());
  return GNOME_KEYRING_RESULT_OK;
}

// static
GnomeKeyringResult
MockGnomeKeyringLoader::mock_gnome_keyring_store_password_sync(
    const GnomeKeyringPasswordSchema* schema,
    const gchar* keyring,
    const gchar* display_name,
    const gchar* password,
    ...) {
  va_list attrs;
  va_start(attrs, password);
  EXPECT_STREQ("application", va_arg(attrs, const char*));
  EXPECT_STREQ(kApplicationName, va_arg(attrs, const char*));
  EXPECT_EQ(nullptr, va_arg(attrs, const char*));
  va_end(attrs);

  delete s_password_ptr_;
  s_password_ptr_ = new std::string(password);
  return GNOME_KEYRING_RESULT_OK;
}

// static
void MockGnomeKeyringLoader::mock_gnome_keyring_free_password(gchar* password) {
  free(password);  // We are mocking a C function.
}

class GnomeKeyringTest : public testing::Test {
 public:
  GnomeKeyringTest();
  ~GnomeKeyringTest() override;

 protected:
  scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
  KeyStorageKeyring keyring_;

 private:
  DISALLOW_COPY_AND_ASSIGN(GnomeKeyringTest);
};

GnomeKeyringTest::GnomeKeyringTest()
    : task_runner_(new base::TestSimpleTaskRunner()), keyring_(task_runner_) {
  MockGnomeKeyringLoader::ResetForOSCrypt();
}

GnomeKeyringTest::~GnomeKeyringTest() {
  MockGnomeKeyringLoader::TearDown();
}

TEST_F(GnomeKeyringTest, KeyringRepeats) {
  std::string password = keyring_.GetKey();
  EXPECT_FALSE(password.empty());
  std::string password_repeat = keyring_.GetKey();
  EXPECT_EQ(password, password_repeat);
}

TEST_F(GnomeKeyringTest, KeyringCreatesRandomised) {
  std::string password = keyring_.GetKey();
  MockGnomeKeyringLoader::ResetForOSCrypt();
  std::string password_new = keyring_.GetKey();
  EXPECT_NE(password, password_new);
}

}  // namespace
