// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/os_crypt/async/browser/keychain_key_provider.h"

#include <vector>

#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "components/os_crypt/async/common/encryptor.h"
#include "crypto/apple/fake_keychain_v2.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace os_crypt_async {

class KeychainKeyProviderTest : public ::testing::Test {
 protected:
  base::test::TaskEnvironment task_environment_{
      base::test::TaskEnvironment::MainThreadType::UI,
      base::test::TaskEnvironment::ThreadPoolExecutionMode::ASYNC};
};

TEST_F(KeychainKeyProviderTest, GetKey_Success) {
  crypto::apple::FakeKeychainV2 mock_keychain("test-access-group");
  mock_keychain.set_find_generic_result(noErr);

  KeychainKeyProvider provider(&mock_keychain);
  base::test::TestFuture<const std::string&,
                         base::expected<Encryptor::Key, KeyProvider::KeyError>>
      future;
  provider.GetKey(future.GetCallback());

  auto& [tag, key_result] = future.Get();
  EXPECT_EQ(tag, "v10");
  ASSERT_TRUE(key_result.has_value());
  EXPECT_FALSE(mock_keychain.called_add_generic());

  // Known answer test for key derivation.
  // This value is from PBKDF2-HMAC-SHA1("mock_password", "saltysalt", 1003).
  const std::vector<uint8_t> expected_key = {0xAF, 0x0F, 0x76, 0x2A, 0xAF, 0x6D,
                                             0x7D, 0x11, 0x58, 0x1B, 0x7A, 0xA8,
                                             0xCE, 0x72, 0x18, 0xDE};
  EXPECT_EQ(key_result.value().key_, expected_key);
}

TEST_F(KeychainKeyProviderTest, GetKey_NotFound) {
  crypto::apple::FakeKeychainV2 mock_keychain("test-access-group");
  mock_keychain.set_find_generic_result(errSecItemNotFound);

  KeychainKeyProvider provider(&mock_keychain);
  base::test::TestFuture<const std::string&,
                         base::expected<Encryptor::Key, KeyProvider::KeyError>>
      future;
  provider.GetKey(future.GetCallback());

  auto& [tag, key_result] = future.Get();
  EXPECT_EQ(tag, "v10");
  ASSERT_TRUE(key_result.has_value());
  EXPECT_TRUE(mock_keychain.called_add_generic());
}

TEST_F(KeychainKeyProviderTest, GetKey_Failure_AuthFailed) {
  crypto::apple::FakeKeychainV2 mock_keychain("test-access-group");
  mock_keychain.set_find_generic_result(errSecAuthFailed);

  KeychainKeyProvider provider(&mock_keychain);
  base::test::TestFuture<const std::string&,
                         base::expected<Encryptor::Key, KeyProvider::KeyError>>
      future;
  provider.GetKey(future.GetCallback());

  auto& [tag, key_result] = future.Get();
  EXPECT_EQ(tag, "v10");
  ASSERT_FALSE(key_result.has_value());
  EXPECT_EQ(key_result.error(), KeyProvider::KeyError::kTemporarilyUnavailable);
  EXPECT_FALSE(mock_keychain.called_add_generic());
}

TEST_F(KeychainKeyProviderTest, GetKey_Failure_OtherError) {
  crypto::apple::FakeKeychainV2 mock_keychain("test-access-group");
  mock_keychain.set_find_generic_result(errSecNotAvailable);

  KeychainKeyProvider provider(&mock_keychain);
  base::test::TestFuture<const std::string&,
                         base::expected<Encryptor::Key, KeyProvider::KeyError>>
      future;
  provider.GetKey(future.GetCallback());

  auto& [tag, key_result] = future.Get();
  EXPECT_EQ(tag, "v10");
  ASSERT_FALSE(key_result.has_value());
  EXPECT_EQ(key_result.error(), KeyProvider::KeyError::kTemporarilyUnavailable);
  EXPECT_FALSE(mock_keychain.called_add_generic());
}

}  // namespace os_crypt_async
