// Copyright (c) 2017 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 "components/password_manager/core/browser/password_reuse_detection_manager.h"

#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_task_environment.h"
#include "base/test/simple_test_clock.h"
#include "components/password_manager/core/browser/mock_password_store.h"
#include "components/password_manager/core/browser/stub_password_manager_client.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "url/gurl.h"

using base::ASCIIToUTF16;
using testing::AnyNumber;
using testing::_;

namespace password_manager {

namespace {

constexpr size_t kMaxNumberOfCharactersToStore = 30;

class MockPasswordManagerClient : public StubPasswordManagerClient {
 public:
  MockPasswordManagerClient() = default;
  ~MockPasswordManagerClient() override = default;

  MOCK_CONST_METHOD0(GetPasswordStore, PasswordStore*());

 private:
  DISALLOW_COPY_AND_ASSIGN(MockPasswordManagerClient);
};

class PasswordReuseDetectionManagerTest : public ::testing::Test {
 public:
  PasswordReuseDetectionManagerTest() {}
  void SetUp() override {
    store_ = new testing::StrictMock<MockPasswordStore>;
    CHECK(store_->Init(syncer::SyncableService::StartSyncFlare(), nullptr));
  }
  void TearDown() override {
    store_->ShutdownOnUIThread();
    store_ = nullptr;
  }

 protected:
  // It's needed for an initialisation of thread runners that are used in
  // MockPasswordStore.
  base::test::ScopedTaskEnvironment scoped_task_environment_;
  MockPasswordManagerClient client_;
  scoped_refptr<MockPasswordStore> store_;

  DISALLOW_COPY_AND_ASSIGN(PasswordReuseDetectionManagerTest);
};

// Verify that CheckReuse is called on each key pressed event with an argument
// equal to the last 30 keystrokes typed after the last main frame navigation.
TEST_F(PasswordReuseDetectionManagerTest, CheckReuseCalled) {
  const GURL gurls[] = {GURL("https://www.example.com"),
                        GURL("https://www.otherexample.com")};
  const base::string16 input[] = {
      base::ASCIIToUTF16(
          "1234567890abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVXYZ"),
      base::ASCIIToUTF16("?<>:'{}ABCDEF")};

  EXPECT_CALL(client_, GetPasswordStore())
      .WillRepeatedly(testing::Return(store_.get()));
  PasswordReuseDetectionManager manager(&client_);

  for (size_t test = 0; test < arraysize(gurls); ++test) {
    manager.DidNavigateMainFrame(gurls[test]);
    for (size_t i = 0; i < input[test].size(); ++i) {
      base::string16 expected_input = input[test].substr(0, i + 1);
      if (expected_input.size() > kMaxNumberOfCharactersToStore)
        expected_input = expected_input.substr(expected_input.size() -
                                               kMaxNumberOfCharactersToStore);
      EXPECT_CALL(
          *store_,
          CheckReuse(expected_input, gurls[test].GetOrigin().spec(), &manager));
      manager.OnKeyPressed(input[test].substr(i, 1));
      testing::Mock::VerifyAndClearExpectations(store_.get());
    }
  }
}

// Verify that the keystroke buffer is cleared after 10 seconds of user
// inactivity.
TEST_F(PasswordReuseDetectionManagerTest,
       CheckThatBufferClearedAfterInactivity) {
  EXPECT_CALL(client_, GetPasswordStore())
      .WillRepeatedly(testing::Return(store_.get()));
  PasswordReuseDetectionManager manager(&client_);

  base::SimpleTestClock clock;
  base::Time now = base::Time::Now();
  clock.SetNow(now);
  manager.SetClockForTesting(&clock);

  EXPECT_CALL(*store_, CheckReuse(base::ASCIIToUTF16("1"), _, _));
  manager.OnKeyPressed(base::ASCIIToUTF16("1"));

  // Simulate 10 seconds of inactivity.
  clock.SetNow(now + base::TimeDelta::FromSeconds(10));
  // Expect that a keystroke typed before inactivity is cleared.
  EXPECT_CALL(*store_, CheckReuse(base::ASCIIToUTF16("2"), _, _));
  manager.OnKeyPressed(base::ASCIIToUTF16("2"));
}

// Verify that the keystroke buffer is cleared after user presses enter.
TEST_F(PasswordReuseDetectionManagerTest, CheckThatBufferClearedAfterEnter) {
  EXPECT_CALL(client_, GetPasswordStore())
      .WillRepeatedly(testing::Return(store_.get()));
  PasswordReuseDetectionManager manager(&client_);

  EXPECT_CALL(*store_, CheckReuse(base::ASCIIToUTF16("1"), _, _));
  manager.OnKeyPressed(base::ASCIIToUTF16("1"));

  base::string16 enter_text(1, ui::VKEY_RETURN);
  EXPECT_CALL(*store_, CheckReuse(_, _, _)).Times(0);
  manager.OnKeyPressed(enter_text);

  // Expect only a keystroke typed after enter.
  EXPECT_CALL(*store_, CheckReuse(base::ASCIIToUTF16("2"), _, _));
  manager.OnKeyPressed(base::ASCIIToUTF16("2"));
}

// Verify that after reuse found, no reuse checking happens till next main frame
// navigation.
TEST_F(PasswordReuseDetectionManagerTest, NoReuseCheckingAfterReuseFound) {
  EXPECT_CALL(client_, GetPasswordStore())
      .WillRepeatedly(testing::Return(store_.get()));
  PasswordReuseDetectionManager manager(&client_);

  // Simulate that reuse found.
  manager.OnReuseFound(0ul, base::nullopt, {"https://example.com"}, 0);

  // Expect no checking of reuse.
  EXPECT_CALL(*store_, CheckReuse(_, _, _)).Times(0);
  manager.OnKeyPressed(base::ASCIIToUTF16("1"));

  // Expect that after main frame navigation checking is restored.
  manager.DidNavigateMainFrame(GURL("https://www.example.com"));
  EXPECT_CALL(*store_, CheckReuse(base::ASCIIToUTF16("1"), _, _));
  manager.OnKeyPressed(base::ASCIIToUTF16("1"));
}

// Verify that keystroke buffer is cleared only on cross host navigation.
TEST_F(PasswordReuseDetectionManagerTest, DidNavigateMainFrame) {
  EXPECT_CALL(client_, GetPasswordStore())
      .WillRepeatedly(testing::Return(store_.get()));
  PasswordReuseDetectionManager manager(&client_);

  manager.DidNavigateMainFrame(GURL("https://www.example1.com/123"));
  EXPECT_CALL(*store_, CheckReuse(base::ASCIIToUTF16("1"), _, _));
  manager.OnKeyPressed(base::ASCIIToUTF16("1"));

  // Check that the buffer is not cleared on the same host navigation.
  manager.DidNavigateMainFrame(GURL("https://www.example1.com/456"));
  EXPECT_CALL(*store_, CheckReuse(base::ASCIIToUTF16("12"), _, _));
  manager.OnKeyPressed(base::ASCIIToUTF16("2"));

  // Check that the buffer is cleared on the cross host navigation.
  manager.DidNavigateMainFrame(GURL("https://www.example2.com/123"));
  EXPECT_CALL(*store_, CheckReuse(base::ASCIIToUTF16("3"), _, _));
  manager.OnKeyPressed(base::ASCIIToUTF16("3"));
}

}  // namespace

}  // namespace password_manager
