text: // Copyright (c) Microsoft Corporation. All rights reserved.
  // SPDX-License-Identifier: MIT
  
  #if defined(_MSC_VER)
  #define _CRT_SECURE_NO_WARNINGS
  #endif
  
  #include "gtest/gtest.h"
  
  #include "key_client_base_test.hpp"
  
  #include <azure/keyvault/key_vault_keys.hpp>
  #include <private/key_constants.hpp>
  
  #include <string>
  
  namespace {
  std::string GetNotFoundErrorMsg(std::string const& keyName)
  {
    return "A key with (name/id) " + keyName
        + " was not found in this key vault. If you recently deleted this key you may be able "
          "to recover it using the correct recovery command. For help resolving this issue, "
          "please see https://go.microsoft.com/fwlink/?linkid=2125182";
  }
  
  std::string GetConflictErrorMsg(std::string const& keyName)
  {
    return "Key " + keyName
        + " is currently in a deleted but recoverable state, and its name cannot be reused; in this "
          "state, the key can only be recovered or purged.";
  }
  
  std::string GetConflictDeletingErrorMsg(std::string const& keyName)
  {
    return "Key " + keyName + " is currently being deleted and cannot be re-created; retry later.";
  }
  } // namespace
  
  using namespace Azure::Security::KeyVault::Keys::Test;
  
  // Test Key Delete.
  // The test works for either soft-delete or not, but for non soft-delete, the LRO is completed as
  // soon as the Operation returns.
  TEST_F(KeyVaultClientTest, DeleteKey)
  {
    Azure::Security::KeyVault::Keys::KeyClient keyClient(m_keyVaultUrl, m_credential);
    auto keyName = GetUniqueName();
  
    {
      auto keyResponse
          = keyClient.CreateKey(keyName, Azure::Security::KeyVault::Keys::KeyVaultKeyType::Ec);
      CheckValidResponse(keyResponse);
      auto keyVaultKey = keyResponse.Value;
      EXPECT_EQ(keyVaultKey.Name(), keyName);
    }
    {
      // Setting a timeout context to avoid this test to run up to ctest default timeout
      // The polling operation would usually complete in ~20 seconds.
      // Setting 3 min as timeout just because I like number 3. We just want to prevent test running
      // for so long if something happens and no exception is thrown (paranoid scenario)
      auto duration = std::chrono::system_clock::now() + std::chrono::minutes(3);
      auto cancelToken = Azure::Core::Context::ApplicationContext.WithDeadline(duration);
  
      auto keyResponseLRO = keyClient.StartDeleteKey(keyName);
      auto expectedStatusToken = keyName;
      EXPECT_EQ(keyResponseLRO.GetResumeToken(), expectedStatusToken);
      // poll each second until key is soft-deleted
      // Will throw and fail test if test takes more than 3 minutes (token cancelled)
      auto keyResponse = keyResponseLRO.PollUntilDone(std::chrono::milliseconds(1000), cancelToken);
    }
    {
      // recover
      auto recoverOperation = keyClient.StartRecoverDeletedKey(keyName);
      auto keyResponse = recoverOperation.PollUntilDone(std::chrono::milliseconds(500));
      auto key = keyResponse.Value;
      // Delete again for purging
      auto deleteOp = keyClient.StartDeleteKey(key.Name());
      deleteOp.PollUntilDone(std::chrono::milliseconds(200));
    }
    {
      // Purge
      auto response = keyClient.PurgeDeletedKey(keyName);
      CheckValidResponse(response, Azure::Core::Http::HttpStatusCode::NoContent);
    }
  }
  
  TEST_F(KeyVaultClientTest, DeleteKeyOperationPoll)
  {
    Azure::Security::KeyVault::Keys::KeyClient keyClient(m_keyVaultUrl, m_credential);
    auto keyName = GetUniqueName();
  
    {
      auto keyResponse
          = keyClient.CreateKey(keyName, Azure::Security::KeyVault::Keys::KeyVaultKeyType::Ec);
      CheckValidResponse(keyResponse);
      auto keyVaultKey = keyResponse.Value;
      EXPECT_EQ(keyVaultKey.Name(), keyName);
    }
    {
      auto keyResponseLRO = keyClient.StartDeleteKey(keyName);
      auto pollResponse = keyResponseLRO.Poll();
      // Expected not completed operation
      EXPECT_EQ(
          static_cast<typename std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
              pollResponse.GetStatusCode()),
          404);
    }
  }
  
  // Delete key which doesn't exists
  TEST_F(KeyVaultClientTest, DeleteInvalidKey)
  {
    Azure::Security::KeyVault::Keys::KeyClient keyClient(m_keyVaultUrl, m_credential);
    auto keyName = GetUniqueName();
  
    auto wasThrown = false;
    try
    {
      auto keyResponseLRO = keyClient.StartDeleteKey(keyName);
    }
    catch (Azure::Core::RequestFailedException const& error)
    {
      EXPECT_EQ(
          static_cast<typename std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
              error.StatusCode),
          404);
      EXPECT_EQ(error.Message, GetNotFoundErrorMsg(keyName));
      EXPECT_EQ(error.ErrorCode, "KeyNotFound");
      wasThrown = true;
    }
    catch (std::exception const&)
    {
      throw;
    }
    EXPECT_TRUE(wasThrown);
  }
  
  TEST_F(KeyVaultClientTest, DoubleDelete)
  {
    Azure::Security::KeyVault::Keys::KeyClient keyClient(m_keyVaultUrl, m_credential);
    auto keyName = GetUniqueName();
  
    {
      auto keyResponse
          = keyClient.CreateKey(keyName, Azure::Security::KeyVault::Keys::KeyVaultKeyType::Ec);
    }
    {
      auto duration = std::chrono::system_clock::now() + std::chrono::minutes(3);
      auto cancelToken = Azure::Core::Context::ApplicationContext.WithDeadline(duration);
      auto keyResponseLRO = keyClient.StartDeleteKey(keyName);
      auto keyResponse = keyResponseLRO.PollUntilDone(std::chrono::milliseconds(1000), cancelToken);
    }
    // delete same key again
    auto wasThrown = false;
    try
    {
      auto keyResponseLRO = keyClient.StartDeleteKey(keyName);
    }
    catch (Azure::Core::RequestFailedException const& error)
    {
      EXPECT_EQ(
          static_cast<typename std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
              error.StatusCode),
          404);
      EXPECT_EQ(error.Message, GetNotFoundErrorMsg(keyName));
      EXPECT_EQ(error.ErrorCode, "KeyNotFound");
      wasThrown = true;
    }
    catch (std::exception const&)
    {
      throw;
    }
    EXPECT_TRUE(wasThrown);
  }
  
  TEST_F(KeyVaultClientTest, DoubleDeleteBeforePollComplete)
  {
    Azure::Security::KeyVault::Keys::KeyClient keyClient(m_keyVaultUrl, m_credential);
    auto keyName = GetUniqueName();
  
    {
      auto keyResponse
          = keyClient.CreateKey(keyName, Azure::Security::KeyVault::Keys::KeyVaultKeyType::Ec);
    }
    {
      auto keyResponseLRO = keyClient.StartDeleteKey(keyName);
    }
    // delete same key again before waitting for poll complete
    auto wasThrown = false;
    try
    {
      auto keyResponseLRO = keyClient.StartDeleteKey(keyName);
    }
    catch (Azure::Core::RequestFailedException const& error)
    {
      EXPECT_EQ(
          static_cast<typename std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
              error.StatusCode),
          404);
  
      EXPECT_EQ(error.Message, GetNotFoundErrorMsg(keyName));
      EXPECT_EQ(error.ErrorCode, "KeyNotFound");
      wasThrown = true;
    }
    catch (std::exception const&)
    {
      throw;
    }
    EXPECT_TRUE(wasThrown);
  }
  
  TEST_F(KeyVaultClientTest, CreateDeletedKey)
  {
    Azure::Security::KeyVault::Keys::KeyClient keyClient(m_keyVaultUrl, m_credential);
    auto keyName = GetUniqueName();
  
    {
      auto keyResponse
          = keyClient.CreateKey(keyName, Azure::Security::KeyVault::Keys::KeyVaultKeyType::Ec);
    }
    {
      auto duration = std::chrono::system_clock::now() + std::chrono::minutes(3);
      auto cancelToken = Azure::Core::Context::ApplicationContext.WithDeadline(duration);
      auto keyResponseLRO = keyClient.StartDeleteKey(keyName);
      auto keyResponse = keyResponseLRO.PollUntilDone(std::chrono::milliseconds(1000), cancelToken);
    }
    // Create a key with same name
    auto wasThrown = false;
    try
    {
      auto keyResponse
          = keyClient.CreateKey(keyName, Azure::Security::KeyVault::Keys::KeyVaultKeyType::Ec);
    }
    catch (Azure::Core::RequestFailedException const& error)
    {
      EXPECT_EQ(
          static_cast<typename std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
              error.StatusCode),
          409);
      EXPECT_EQ(error.Message, GetConflictErrorMsg(keyName));
      EXPECT_EQ(error.ErrorCode, "Conflict");
  
      wasThrown = true;
    }
    catch (std::exception const&)
    {
      throw;
    }
    EXPECT_TRUE(wasThrown);
  }
  
  TEST_F(KeyVaultClientTest, CreateDeletedKeyBeforePollComplete)
  {
    Azure::Security::KeyVault::Keys::KeyClient keyClient(m_keyVaultUrl, m_credential);
    auto keyName = GetUniqueName();
  
    {
      auto keyResponse
          = keyClient.CreateKey(keyName, Azure::Security::KeyVault::Keys::KeyVaultKeyType::Ec);
    }
    {
      auto keyResponseLRO = keyClient.StartDeleteKey(keyName);
    }
    // Create a key with same name
    auto wasThrown = false;
    try
    {
      auto keyResponse
          = keyClient.CreateKey(keyName, Azure::Security::KeyVault::Keys::KeyVaultKeyType::Ec);
    }
    catch (Azure::Core::RequestFailedException const& error)
    {
      EXPECT_EQ(
          static_cast<typename std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
              error.StatusCode),
          409);
      EXPECT_EQ(error.Message, GetConflictDeletingErrorMsg(keyName));
      EXPECT_EQ(error.ErrorCode, "Conflict");
      wasThrown = true;
    }
    catch (std::exception const&)
    {
      throw;
    }
    EXPECT_TRUE(wasThrown);
  }
  
  // Get Delete Key
  TEST_F(KeyVaultClientTest, GetDeletedKey)
  {
    Azure::Security::KeyVault::Keys::KeyClient keyClient(m_keyVaultUrl, m_credential);
    auto keyName = GetUniqueName();
  
    {
      auto keyResponse
          = keyClient.CreateKey(keyName, Azure::Security::KeyVault::Keys::KeyVaultKeyType::Ec);
      CheckValidResponse(keyResponse);
      auto keyVaultKey = keyResponse.Value;
      EXPECT_EQ(keyVaultKey.Name(), keyName);
    }
    {
      // Wait until key is deleted
      auto duration = std::chrono::system_clock::now() + std::chrono::minutes(3);
      auto cancelToken = Azure::Core::Context::ApplicationContext.WithDeadline(duration);
  
      auto keyResponseLRO = keyClient.StartDeleteKey(keyName);
      auto expectedStatusToken = m_keyVaultUrl
          + std::string(Azure::Security::KeyVault::Keys::_detail::DeletedKeysPath) + "/" + keyName;
      auto keyResponse = keyResponseLRO.PollUntilDone(std::chrono::milliseconds(1000), cancelToken);
    }
    {
      // Get the deleted key
      auto deletedKey = keyClient.GetDeletedKey(keyName).Value;
      EXPECT_FALSE(deletedKey.RecoveryId.empty());
      EXPECT_EQ(deletedKey.Name(), keyName);
      auto expectedType = Azure::Security::KeyVault::Keys::KeyVaultKeyType::Ec;
      EXPECT_EQ(expectedType, deletedKey.Key.KeyType);
    }
  }
  
  TEST_F(KeyVaultClientTest, DeleteOperationResumeToken)
  {
    Azure::Security::KeyVault::Keys::KeyClient keyClient(m_keyVaultUrl, m_credential);
    auto keyName = GetUniqueName();
  
    {
      auto keyResponse
          = keyClient.CreateKey(keyName, Azure::Security::KeyVault::Keys::KeyVaultKeyType::Ec);
      CheckValidResponse(keyResponse);
      auto keyVaultKey = keyResponse.Value;
      EXPECT_EQ(keyVaultKey.Name(), keyName);
    }
    std::string resumeToken;
    {
      auto keyResponseLRO = keyClient.StartDeleteKey(keyName);
      resumeToken = keyResponseLRO.GetResumeToken();
    }
    // Resume operation from token
    {
      auto resumeOperation
          = Azure::Security::KeyVault::Keys::DeleteKeyOperation::CreateFromResumeToken(
              resumeToken, keyClient);
      resumeOperation.PollUntilDone(std::chrono::milliseconds(500));
    }
    {
      // recover
      auto recoverOperation = keyClient.StartRecoverDeletedKey(keyName);
      auto keyResponse = recoverOperation.PollUntilDone(std::chrono::milliseconds(500));
      auto key = keyResponse.Value;
      // Delete again for purging
      auto deleteOp = keyClient.StartDeleteKey(key.Name());
      deleteOp.PollUntilDone(std::chrono::milliseconds(200));
    }
    {
      // Purge
      auto response = keyClient.PurgeDeletedKey(keyName);
      CheckValidResponse(response, Azure::Core::Http::HttpStatusCode::NoContent);
    }
  }
  
  TEST_F(KeyVaultClientTest, RecoverOperationResumeToken)
  {
    Azure::Security::KeyVault::Keys::KeyClient keyClient(m_keyVaultUrl, m_credential);
    auto keyName = GetUniqueName();
  
    {
      auto keyResponse
          = keyClient.CreateKey(keyName, Azure::Security::KeyVault::Keys::KeyVaultKeyType::Ec);
      CheckValidResponse(keyResponse);
      auto keyVaultKey = keyResponse.Value;
      EXPECT_EQ(keyVaultKey.Name(), keyName);
    }
    std::string resumeToken;
    {
      auto keyResponseLRO = keyClient.StartDeleteKey(keyName);
      resumeToken = keyResponseLRO.GetResumeToken();
    }
    // Resume operation from token
    {
      auto resumeOperation
          = Azure::Security::KeyVault::Keys::DeleteKeyOperation::CreateFromResumeToken(
              resumeToken, keyClient);
      resumeOperation.PollUntilDone(std::chrono::milliseconds(500));
    }
    {
      // recover
      auto recoverOperation = keyClient.StartRecoverDeletedKey(keyName);
      resumeToken = recoverOperation.GetResumeToken();
    }
    {
      // resume from token
      auto resumeRecoveryOp
          = Azure::Security::KeyVault::Keys::RecoverDeletedKeyOperation::CreateFromResumeToken(
              resumeToken, keyClient);
      auto keyResponse = resumeRecoveryOp.PollUntilDone(std::chrono::milliseconds(500));
      auto key = keyResponse.Value;
      // Delete again for purging
      auto deleteOp = keyClient.StartDeleteKey(key.Name());
      deleteOp.PollUntilDone(std::chrono::milliseconds(200));
    }
    {
      // Purge
      auto response = keyClient.PurgeDeletedKey(keyName);
      CheckValidResponse(response, Azure::Core::Http::HttpStatusCode::NoContent);
    }
  }
  