/**
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */


#include <gtest/gtest.h>
#include <aws/testing/AwsTestHelpers.h>
#include <aws/core/client/ClientConfiguration.h>
#include <aws/core/client/DefaultRetryStrategy.h>
#include <aws/core/auth/AWSCredentialsProviderChain.h>
#include <aws/core/http/HttpClient.h>
#include <aws/core/http/HttpClientFactory.h>
#include <aws/core/platform/Environment.h>
#include <aws/core/utils/Outcome.h>
#include <aws/core/utils/UUID.h>
#include <aws/testing/platform/PlatformTesting.h>
#include <aws/testing/TestingEnvironment.h>
#include <aws/testing/mocks/monitoring/TestingMonitoring.h>
#include <aws/s3/S3Client.h>
#include <aws/s3/model/CreateBucketRequest.h>
#include <aws/s3/model/HeadBucketRequest.h>
#include <aws/s3/model/ListObjectsRequest.h>
#include <aws/s3/model/DeleteBucketRequest.h>
#include <aws/s3/model/PutObjectRequest.h>
#include <aws/s3/model/HeadObjectRequest.h>
#include <aws/s3/model/GetObjectRequest.h>
#include <aws/s3/model/DeleteObjectRequest.h>
#include <aws/s3/model/PutBucketTaggingRequest.h>
#include <aws/s3control/S3ControlClient.h>
#include <aws/s3control/model/PutPublicAccessBlockRequest.h>
#include <aws/s3control/model/CreateBucketRequest.h>
#include <aws/s3control/model/GetBucketRequest.h>
#include <aws/s3control/model/DeleteBucketRequest.h>
#include <aws/s3control/model/DescribeMultiRegionAccessPointOperationRequest.h>
#include <aws/s3control/model/CreateAccessPointRequest.h>
#include <aws/s3control/model/GetAccessPointRequest.h>
#include <aws/s3control/model/ListAccessPointsRequest.h>
#include <aws/s3control/model/DeleteAccessPointRequest.h>
#include <aws/s3control/model/CreateMultiRegionAccessPointRequest.h>
#include <aws/s3control/model/DeleteMultiRegionAccessPointRequest.h>
#include <aws/s3control/model/GetMultiRegionAccessPointRequest.h>
#include <aws/access-management/AccessManagementClient.h>
#include <aws/iam/IAMClient.h>
#include <aws/cognito-identity/CognitoIdentityClient.h>
#include <aws/sts/STSClient.h>
#include <aws/sts/model/AssumeRoleRequest.h>
#include <thread>

using namespace Aws;
using namespace Aws::Http;
using namespace Aws::Client;
using namespace Aws::S3Control;
using namespace Aws::S3Control::Model;

namespace
{
    static const char ALLOCATION_TAG[] = "S3ControlTest";
    static const char BASE_BUCKET_NAME[] = "accesspointbucket";
    static const char BASE_MRAP_BUCKET_NAME[] = "mrapbucket";
    static const char BASE_ACCESS_POINT[] = "accesspoint";
    static const char BASE_MRAP[] = "mrap";
    static const char BASE_OUTPOST_ID[] = "op-0123456789abcdef0";
    static const char PRESIGNED_URLS_BUCKET_NAME[] = "presignedaccesspointbucket";
    static const char PRESIGNED_URLS_ACCESS_POINT[] = "presignedaccesspoint";
    static const char TEST_OBJECT_KEY[] = "TestObjectKey";
    static const char TEST_BUCKET_TAG[] = "IntegrationTestResource";
    static const int TIMEOUT_MAX = 20;

    class S3ControlTest : public ::testing::Test
    {
    public:
        S3ControlClient m_client;
        S3::S3Client m_s3Client;
        Aws::String m_accountId;
        std::shared_ptr<HttpClient> m_httpClient;

        Aws::Vector<std::pair<const char*, Aws::String>> m_environments;

        S3ControlTest()
        {
            // Create a client
            ClientConfiguration config;
            config.region = Aws::Region::US_WEST_2;
            config.scheme = Scheme::HTTPS;
            config.connectTimeoutMs = 30000;
            config.requestTimeoutMs = 30000;
            m_client = S3ControlClient(config);
            m_s3Client = createS3Client(config);
            m_httpClient = Aws::Http::CreateHttpClient(config);

            // IAM client has to use us-east-1 in its signer.
            auto accountId = Aws::Environment::GetEnv("CATAPULT_TEST_ACCOUNT");
            if(accountId.empty()) {
                config.region = Aws::Region::US_EAST_1;
                auto iamClient = Aws::MakeShared<Aws::IAM::IAMClient>(ALLOCATION_TAG, config);
                auto cognitoClient = Aws::MakeShared<Aws::CognitoIdentity::CognitoIdentityClient>(ALLOCATION_TAG, config);
                Aws::AccessManagement::AccessManagementClient accessManagementClient(iamClient, cognitoClient);
                accountId = accessManagementClient.GetAccountId();
            }
            m_accountId = accountId;
        }

    protected:

        static void SetUpTestCase()
        {
            TestingMonitoringManager::InitTestingMonitoring();
        }

        static void TearDownTestCase()
        {
            TestingMonitoringManager::CleanupTestingMonitoring();
        }

        Aws::String BuildResourceName(const char* baseAccessPoint)
        {
            // In case the length exceeds the limits.
            return Aws::Testing::GetAwsResourcePrefix() + baseAccessPoint + Aws::Utils::StringUtils::ToLower(GetRandomUUID().substr(0, 8).c_str());
        }

        static Aws::String GetRandomUUID()
        {
            static const Aws::Utils::UUID resourceUUID = Aws::Utils::UUID::RandomUUID();
            return resourceUUID;
        }

        static S3::S3Client createS3Client(const ClientConfiguration &configuration) {
            Aws::String testAccount(Aws::Environment::GetEnv("CATAPULT_TEST_ACCOUNT"));
            if (!testAccount.empty()) {
                Aws::String testRoleArn("arn:aws:iam::" + testAccount + ":role/IntegrationTest");
                STS::STSClient stsClient(configuration);
                STS::Model::AssumeRoleRequest assumeRoleRequest;
                assumeRoleRequest.SetRoleArn(testRoleArn);
                assumeRoleRequest.SetRoleSessionName("s3-control-cpp-integ-test");
                STS::Model::AssumeRoleOutcome outcome = stsClient.AssumeRole(assumeRoleRequest);
                STS::Model::Credentials creds = outcome.GetResult().GetCredentials();
                Auth::AWSCredentials awsCredentials(creds.GetAccessKeyId(),
                                                    creds.GetSecretAccessKey(),
                                                    creds.GetSessionToken(),
                                                    creds.GetExpiration());
                return {awsCredentials, Aws::MakeShared<S3::Endpoint::S3EndpointProvider>(ALLOCATION_TAG), configuration};
            }
            return {configuration};
        }

        static void TagTestBucket(const Aws::String& bucketName, const S3::S3Client& client) {
            ASSERT_TRUE(!bucketName.empty());

            Aws::S3::Model::PutBucketTaggingRequest taggingRequest;
            taggingRequest.SetBucket(bucketName);
            Aws::S3::Model::Tag tag;
            tag.SetKey(TEST_BUCKET_TAG);
            tag.SetValue(TEST_BUCKET_TAG);
            Aws::S3::Model::Tagging tagging;
            tagging.AddTagSet(tag);
            taggingRequest.SetTagging(tagging);

            auto taggingOutcome = CallOperationWithUnconditionalRetry(&client, &Aws::S3::S3Client::PutBucketTagging, taggingRequest);
            AWS_ASSERT_SUCCESS(taggingOutcome);
        }

        static bool WaitForBucketToPropagate(const Aws::String& bucketName, const S3::S3Client& client)
        {
            unsigned timeoutCount = 0;
            while (timeoutCount++ < TIMEOUT_MAX)
            {
                S3::Model::ListObjectsRequest ListObjectsRequest;
                ListObjectsRequest.SetBucket(bucketName);
                auto listObjectsOutcome = client.ListObjects(ListObjectsRequest);
                if (listObjectsOutcome.IsSuccess())
                {
                    return true;
                }

                std::this_thread::sleep_for(std::chrono::seconds(10));
            }

            return false;
        }

        static bool WaitForObjectToPropagate(const Aws::String& accessPointArn, const char* objectKey, const S3::S3Client& client)
        {
            unsigned timeoutCount = 0;
            while (timeoutCount++ < TIMEOUT_MAX)
            {
                S3::Model::GetObjectRequest getObjectRequest;
                getObjectRequest.SetBucket(accessPointArn);
                getObjectRequest.SetKey(objectKey);
                auto getObjectOutcome = client.GetObject(getObjectRequest);
                if (getObjectOutcome.IsSuccess())
                {
                    return true;
                }

                std::this_thread::sleep_for(std::chrono::seconds(5));
            }

            return false;
        }

        /**
         * Wait for S3-MRAP server-side asynchronous Operation completion
         *
         * @param requestToken
         * @param accountId
         * @param client
         * @return
         */
        static bool WaitForMultiRegionAccessPointOperationCompletion(const Aws::String& requestToken,
                                                                     const Aws::String& accountId,
                                                                     const S3Control::S3ControlClient& client)
        {
            /*
             * These (CreateMultiRegionAccessPoint,DeleteMultiRegionAccessPoint, etc.) requests are asynchronous,
             * meaning that you might receive a response before the command has completed.
             */
            unsigned timeoutCount = 0;
            while (timeoutCount++ < TIMEOUT_MAX)
            {
                S3Control::Model::DescribeMultiRegionAccessPointOperationRequest request;
                request.SetRequestTokenARN(requestToken);
                request.SetAccountId(accountId);
                auto outcome =
                        CallOperationWithUnconditionalRetry(&client, &Aws::S3Control::S3ControlClient::DescribeMultiRegionAccessPointOperation, request);
                AWS_EXPECT_SUCCESS(outcome);
                if(!outcome.IsSuccess())
                {
                    return false;
                }

                const Aws::String& operation = S3Control::Model::AsyncOperationNameMapper::GetNameForAsyncOperationName(
                        outcome.GetResult().GetAsyncOperation().GetOperation());
                const auto& status = outcome.GetResult().GetAsyncOperation().GetRequestStatus();
                std::cout << "S3Control::DescribeMultiRegionAccessPointOperation request status for " << operation << " is " <<
                             status << " after poll #" << timeoutCount << "\n";
                EXPECT_NE(status, "FAILED") << outcome.GetResult().GetAsyncOperation().GetResponseDetails().GetErrorDetails().GetMessage();
                if(status == "FAILED")
                {
                    for(const auto& item : outcome.GetResult().GetAsyncOperation().GetResponseDetails().GetMultiRegionAccessPointDetails().GetRegions()) {
                        std::cout << "MRAP Detail: " << item.GetName() << " " << item.GetRequestStatus() << "\n";
                    }
                    return false;
                }
                if(status == "SUCCEEDED")
                {
                    return true;
                }
                std::this_thread::sleep_for(std::chrono::seconds(20));
            }

            return false;
        }

        void PrepareAccessPointTest(const Aws::String& bucketName, const Aws::String& accessPointName)
        {
            m_environments.emplace_back("AWS_S3_USE_ARN_REGION", Aws::Environment::GetEnv("AWS_S3_USE_ARN_REGION"));
            Aws::Environment::UnSetEnv("AWS_S3_USE_ARN_REGION");

            S3::Model::CreateBucketRequest createBucketRequest;
            S3::Model::CreateBucketConfiguration bucketConfiguration;
            bucketConfiguration.SetLocationConstraint(S3::Model::BucketLocationConstraint::us_west_2);
            createBucketRequest.SetBucket(bucketName);
            createBucketRequest.SetCreateBucketConfiguration(bucketConfiguration);
            auto createBucketOutcome = m_s3Client.CreateBucket(createBucketRequest);
            AWS_ASSERT_SUCCESS(createBucketOutcome);
            ASSERT_TRUE(WaitForBucketToPropagate(bucketName, m_s3Client));
            TagTestBucket(bucketName, m_s3Client);

            CreateAccessPointRequest createAccessPointRequest;
            createAccessPointRequest.SetName(accessPointName);
            createAccessPointRequest.SetAccountId(m_accountId);
            createAccessPointRequest.SetBucket(bucketName);
            auto createAccessPointOutcome = m_client.CreateAccessPoint(createAccessPointRequest);
            AWS_ASSERT_SUCCESS(createAccessPointOutcome);
        }

        void CleanUpAccessPointTest(const Aws::String& bucketName, const Aws::String& accessPointName)
        {
            DeleteAccessPointRequest deleteAccessPointRequest;
            deleteAccessPointRequest.SetName(accessPointName);
            deleteAccessPointRequest.SetAccountId(m_accountId);
            auto deleteAccessPointOutcome = m_client.DeleteAccessPoint(deleteAccessPointRequest);
            AWS_ASSERT_SUCCESS(deleteAccessPointOutcome);

            S3::Model::DeleteBucketRequest deleteBucketRequest;
            deleteBucketRequest.SetBucket(bucketName);
            auto deleteBucketOutcome = m_s3Client.DeleteBucket(deleteBucketRequest);
            AWS_ASSERT_SUCCESS(deleteBucketOutcome);

            for(const auto& iter : m_environments)
            {
                if(iter.second.empty())
                {
                    Aws::Environment::UnSetEnv(iter.first);
                }
                else
                {
                    Aws::Environment::SetEnv(iter.first, iter.second.c_str(), 1);
                }
            }
        }

        void PrepareMRAPTest(const Aws::String& bucketName, const Aws::Vector<Aws::String>& multiRegions, Aws::String& accessPointName)
        {
            using namespace S3::Model::BucketLocationConstraintMapper;

            Aws::Vector<Aws::S3Control::Model::Region> bucketsInMultiRegions;
            for (const Aws::String& region : multiRegions)
            {
                Aws::String regionalBucketName = bucketName + "-" + region;

                Aws::Client::ClientConfiguration config;
                config.region = region;
                Aws::S3::S3Client s3Client = createS3Client(config);

                S3::Model::CreateBucketRequest createBucketRequest;
                S3::Model::CreateBucketConfiguration bucketConfiguration;
                bucketConfiguration.SetLocationConstraint(GetBucketLocationConstraintForName(region));
                createBucketRequest.SetBucket(regionalBucketName);
                createBucketRequest.SetCreateBucketConfiguration(bucketConfiguration);
                auto createBucketOutcome = s3Client.CreateBucket(createBucketRequest);
                AWS_ASSERT_SUCCESS(createBucketOutcome);
                ASSERT_TRUE(WaitForBucketToPropagate(regionalBucketName, s3Client));
                TagTestBucket(regionalBucketName, s3Client);

                S3Control::Model::Region regionalBucket;
                regionalBucket.SetBucket(regionalBucketName);
                bucketsInMultiRegions.push_back(regionalBucket);
            }

            CreateMultiRegionAccessPointRequest createMRAPRequest;
            CreateMultiRegionAccessPointInput createMRAPInput;
            createMRAPInput.SetName(accessPointName);
            createMRAPInput.SetRegions(bucketsInMultiRegions);
            createMRAPRequest.SetAccountId(m_accountId);
            createMRAPRequest.SetDetails(createMRAPInput);

            auto createMRAPOutcome = m_client.CreateMultiRegionAccessPoint(createMRAPRequest);
            AWS_ASSERT_SUCCESS(createMRAPOutcome);
            ASSERT_TRUE(WaitForMultiRegionAccessPointOperationCompletion(createMRAPOutcome.GetResult().GetRequestTokenARN(), m_accountId, m_client));

            GetMultiRegionAccessPointRequest getMRAPRequest;
            getMRAPRequest.SetAccountId(m_accountId);
            getMRAPRequest.SetName(accessPointName);
            auto getMRAPOutcome = m_client.GetMultiRegionAccessPoint(getMRAPRequest);
            AWS_ASSERT_SUCCESS(createMRAPOutcome);
            ASSERT_EQ(getMRAPOutcome.GetResult().GetAccessPoint().GetStatus(), MultiRegionAccessPointStatus::READY);
        }

        void CleanUpMRAPTest(const Aws::String& bucketName, const Aws::Vector<Aws::String>& multiRegions, const Aws::String& accessPointName)
        {
            AWS_UNREFERENCED_PARAM(accessPointName);
            DeleteMultiRegionAccessPointRequest deleteMRAPRequest;
            DeleteMultiRegionAccessPointInput deleteMRAPInput;
            deleteMRAPInput.SetName(accessPointName);
            deleteMRAPRequest.SetDetails(deleteMRAPInput);
            deleteMRAPRequest.SetAccountId(m_accountId);
            auto deleteMRAPOutcome = m_client.DeleteMultiRegionAccessPoint(deleteMRAPRequest);
            AWS_ASSERT_SUCCESS(deleteMRAPOutcome);
            ASSERT_TRUE(WaitForMultiRegionAccessPointOperationCompletion(deleteMRAPOutcome.GetResult().GetRequestTokenARN(), m_accountId, m_client));

            GetMultiRegionAccessPointRequest getMRAPRequest;
            getMRAPRequest.SetAccountId(m_accountId);
            getMRAPRequest.SetName(accessPointName);
            auto getMRAPOutcome = m_client.GetMultiRegionAccessPoint(getMRAPRequest);
            ASSERT_FALSE(getMRAPOutcome.IsSuccess());

            for (const Aws::String& region : multiRegions)
            {
                Aws::Client::ClientConfiguration config;
                config.region = region;
                Aws::S3::S3Client s3Client = createS3Client(config);

                Aws::String regionalBucket = bucketName + "-" + region;
                S3::Model::DeleteBucketRequest deleteBucketRequest;
                deleteBucketRequest.SetBucket(regionalBucket);
                auto deleteBucketOutcome = s3Client.DeleteBucket(deleteBucketRequest);
                AWS_ASSERT_SUCCESS(deleteBucketOutcome);
            }
        }

        void DoPresignedUrlTest(const Aws::String& accessPointArn, std::shared_ptr<HttpRequest>& putRequest)
        {
            std::shared_ptr<Aws::IOStream> objectStream = Aws::MakeShared<Aws::StringStream>("BucketAndObjectOperationTest");
            *objectStream << "Test Object";
            objectStream->flush();

            putRequest->AddContentBody(objectStream);
            Aws::StringStream intConverter;
            intConverter << objectStream->tellp();
            putRequest->SetContentLength(intConverter.str());
            putRequest->SetContentType("text/plain");
            std::shared_ptr<HttpResponse> putResponse = m_httpClient->MakeRequest(putRequest);

            ASSERT_EQ(HttpResponseCode::OK, putResponse->GetResponseCode());

            ASSERT_TRUE(WaitForObjectToPropagate(accessPointArn, TEST_OBJECT_KEY, m_s3Client));

            // GetObject with presigned url
            Aws::String presignedUrlGet = m_s3Client.GeneratePresignedUrl(accessPointArn, TEST_OBJECT_KEY, HttpMethod::HTTP_GET);
            std::shared_ptr<HttpRequest> getRequest = CreateHttpRequest(presignedUrlGet, HttpMethod::HTTP_GET, Aws::Utils::Stream::DefaultResponseStreamFactoryMethod);
            std::shared_ptr<HttpResponse> getResponse = m_httpClient->MakeRequest(getRequest);

            ASSERT_EQ(HttpResponseCode::OK, getResponse->GetResponseCode());
            Aws::StringStream ss;
            ss << getResponse->GetResponseBody().rdbuf();
            ASSERT_STREQ("Test Object", ss.str().c_str());

            // DeleteObject with presigned url
            Aws::String presignedUrlDelete = m_s3Client.GeneratePresignedUrl(accessPointArn, TEST_OBJECT_KEY, HttpMethod::HTTP_DELETE);
            std::shared_ptr<HttpRequest> deleteRequest = CreateHttpRequest(presignedUrlDelete, HttpMethod::HTTP_DELETE, Aws::Utils::Stream::DefaultResponseStreamFactoryMethod);
            std::shared_ptr<HttpResponse> deleteResponse = m_httpClient->MakeRequest(deleteRequest);
            ASSERT_EQ(HttpResponseCode::NO_CONTENT, deleteResponse->GetResponseCode());
        }
    };

    TEST_F(S3ControlTest, TestInvalidAccountId)
    {
        PutPublicAccessBlockRequest putPublicAccessBlockRequest;
        PublicAccessBlockConfiguration publicAccessBlockConfiguration;
        publicAccessBlockConfiguration.SetBlockPublicPolicy(true);
        putPublicAccessBlockRequest.SetPublicAccessBlockConfiguration(publicAccessBlockConfiguration);
        putPublicAccessBlockRequest.SetAccountId("fakeaccountid");

        auto putPublicAccessBlockOutcome = m_client.PutPublicAccessBlock(putPublicAccessBlockRequest);
        ASSERT_FALSE(putPublicAccessBlockOutcome.IsSuccess());

        // The account id should be a valid DNS label. Otherwise we will not make the request.
        putPublicAccessBlockRequest.SetAccountId("invalid_account_id");

        putPublicAccessBlockOutcome = m_client.PutPublicAccessBlock(putPublicAccessBlockRequest);
        ASSERT_FALSE(putPublicAccessBlockOutcome.IsSuccess());
        ASSERT_EQ(CoreErrors::INVALID_PARAMETER_VALUE, static_cast<CoreErrors>(putPublicAccessBlockOutcome.GetError().GetErrorType()));
    }

    TEST_F(S3ControlTest, TestS3AccessPointEndpoint)
    {
        Aws::String bucketName = BuildResourceName(BASE_BUCKET_NAME);
        Aws::String accessPointName = BuildResourceName(BASE_ACCESS_POINT);

        PrepareAccessPointTest(bucketName, accessPointName);

        S3::Model::HeadBucketRequest headBucketRequest;
        Aws::StringStream ss;
        ss << "arn:aws:s3:" << Aws::Region::US_WEST_2 << ":" << m_accountId << ":accesspoint:" << accessPointName;
        Aws::String accessPointArn = ss.str();
        headBucketRequest.SetBucket(accessPointArn);
        auto headBucketOutcome = m_s3Client.HeadBucket(headBucketRequest);
        AWS_ASSERT_SUCCESS(headBucketOutcome);

        GetAccessPointRequest getAccessPointRequest;
        getAccessPointRequest.SetAccountId(m_accountId);
        getAccessPointRequest.SetName(accessPointName);
        auto getAccessPointOutcome = m_client.GetAccessPoint(getAccessPointRequest);
        AWS_ASSERT_SUCCESS(getAccessPointOutcome);
        ASSERT_EQ(accessPointName, getAccessPointOutcome.GetResult().GetName());

        // Invalid service name
        ss.str("");
        ss << "arn:aws:sqs:" << Aws::Region::US_WEST_2 << ":" << m_accountId << ":accesspoint:" << accessPointName;
        headBucketRequest.SetBucket(ss.str());
        headBucketOutcome = m_s3Client.HeadBucket(headBucketRequest);
        ASSERT_FALSE(headBucketOutcome.IsSuccess());
        ASSERT_EQ(CoreErrors::ENDPOINT_RESOLUTION_FAILURE, (CoreErrors) headBucketOutcome.GetError().GetErrorType());

        // Invalid resource type
        ss.str("");
        ss << "arn:aws:s3:" << Aws::Region::US_WEST_2 << ":" << m_accountId << ":bucket_name:" << accessPointName;
        headBucketRequest.SetBucket(ss.str());
        headBucketOutcome = m_s3Client.HeadBucket(headBucketRequest);
        ASSERT_FALSE(headBucketOutcome.IsSuccess());
        ASSERT_EQ(CoreErrors::ENDPOINT_RESOLUTION_FAILURE, (CoreErrors) headBucketOutcome.GetError().GetErrorType());

        // Empty account ID
        ss.str("");
        ss << "arn:aws:s3:" << Aws::Region::US_WEST_2 << ":" << "" << ":accesspoint:" << accessPointName;
        headBucketRequest.SetBucket(ss.str());
        headBucketOutcome = m_s3Client.HeadBucket(headBucketRequest);
        ASSERT_FALSE(headBucketOutcome.IsSuccess());
        EXPECT_EQ(CoreErrors::ENDPOINT_RESOLUTION_FAILURE, (CoreErrors) headBucketOutcome.GetError().GetErrorType());

        // Invalid account ID
        ss.str("");
        ss << "arn:aws:s3:" << Aws::Region::US_WEST_2 << ":." << m_accountId << ".:accesspoint:" << accessPointName;
        headBucketRequest.SetBucket(ss.str());
        headBucketOutcome = m_s3Client.HeadBucket(headBucketRequest);
        ASSERT_FALSE(headBucketOutcome.IsSuccess());
        EXPECT_EQ(CoreErrors::ENDPOINT_RESOLUTION_FAILURE, (CoreErrors) headBucketOutcome.GetError().GetErrorType());

        // Missing Access Point name
        ss.str("");
        ss << "arn:aws:s3:" << Aws::Region::US_WEST_2 << ":" << m_accountId << ":accesspoint:";
        headBucketRequest.SetBucket(ss.str());
        headBucketOutcome = m_s3Client.HeadBucket(headBucketRequest);
        ASSERT_FALSE(headBucketOutcome.IsSuccess());
        EXPECT_EQ(CoreErrors::ENDPOINT_RESOLUTION_FAILURE, (CoreErrors) headBucketOutcome.GetError().GetErrorType());

        // Missing Access Point name
        ss.str("");
        ss << "arn:aws:s3:" << Aws::Region::US_WEST_2 << ":" << m_accountId << ":accesspoint";
        headBucketRequest.SetBucket(ss.str());
        headBucketOutcome = m_s3Client.HeadBucket(headBucketRequest);
        ASSERT_FALSE(headBucketOutcome.IsSuccess());
        EXPECT_EQ(CoreErrors::ENDPOINT_RESOLUTION_FAILURE, (CoreErrors) headBucketOutcome.GetError().GetErrorType());

        // Invalid Access Point Name
        ss.str("");
        ss << "arn:aws:s3:" << Aws::Region::US_WEST_2 << ":" << m_accountId << ":accesspoint:*";
        headBucketRequest.SetBucket(ss.str());
        headBucketOutcome = m_s3Client.HeadBucket(headBucketRequest);
        ASSERT_FALSE(headBucketOutcome.IsSuccess());
        EXPECT_EQ(CoreErrors::ENDPOINT_RESOLUTION_FAILURE, (CoreErrors) headBucketOutcome.GetError().GetErrorType());

        // Invalid Access Point Name
        ss.str("");
        ss << "arn:aws:s3:" << Aws::Region::US_WEST_2 << ":" << m_accountId << ":accesspoint:my.bucket";
        headBucketRequest.SetBucket(ss.str());
        headBucketOutcome = m_s3Client.HeadBucket(headBucketRequest);
        ASSERT_FALSE(headBucketOutcome.IsSuccess());
        EXPECT_EQ(CoreErrors::ENDPOINT_RESOLUTION_FAILURE, (CoreErrors) headBucketOutcome.GetError().GetErrorType());

        // Invalid Access Point Name, containing sub resources
        ss.str("");
        ss << "arn:aws:s3:" << Aws::Region::US_WEST_2 << ":" << m_accountId << ":accesspoint:my-bucket:object:foo";
        headBucketRequest.SetBucket(ss.str());
        headBucketOutcome = m_s3Client.HeadBucket(headBucketRequest);
        ASSERT_FALSE(headBucketOutcome.IsSuccess());
        EXPECT_EQ(CoreErrors::ENDPOINT_RESOLUTION_FAILURE, (CoreErrors) headBucketOutcome.GetError().GetErrorType());

        // Using dualstack
        ClientConfiguration config;
        config.region = Aws::Region::US_WEST_2;
        config.useDualStack = true;
        S3::S3Client s3ClientInUsWest2UsingDualStack(config);
        S3ControlClient clientInUsWest2UsingDualStack(config);
        headBucketRequest.SetBucket(accessPointArn);
        headBucketOutcome = s3ClientInUsWest2UsingDualStack.HeadBucket(headBucketRequest);
        AWS_ASSERT_SUCCESS(headBucketOutcome);

        getAccessPointOutcome = clientInUsWest2UsingDualStack.GetAccessPoint(getAccessPointRequest);
        AWS_ASSERT_SUCCESS(getAccessPointOutcome);
        ASSERT_EQ(accessPointName, getAccessPointOutcome.GetResult().GetName());

        config.region = Aws::Region::US_EAST_1;
        config.useDualStack = false;
        S3::S3Client s3ClientInUsEast1(config);
        headBucketOutcome = s3ClientInUsEast1.HeadBucket(headBucketRequest);
        ASSERT_FALSE(headBucketOutcome.IsSuccess());
        EXPECT_EQ(CoreErrors::ENDPOINT_RESOLUTION_FAILURE, (CoreErrors) headBucketOutcome.GetError().GetErrorType());

        // The following tests are using region in ARN when making requests.
        Aws::Environment::SetEnv("AWS_S3_USE_ARN_REGION", "true", 1);

        config.region = Aws::Region::US_EAST_1;
        S3::S3Client s3ClientInUsEast1usingArnRegion(config);
        headBucketOutcome = s3ClientInUsEast1usingArnRegion.HeadBucket(headBucketRequest);
        AWS_ASSERT_SUCCESS(headBucketOutcome);

        // Using custom endpoint
        config.region = Aws::Region::US_WEST_2;
        config.endpointOverride = "s3-accesspoint.us-west-2.amazonaws.com";
        S3::S3Client s3ClientInUsWest2UsingCustomEndpoint(config);
        headBucketOutcome = s3ClientInUsWest2UsingCustomEndpoint.HeadBucket(headBucketRequest);
        AWS_ASSERT_SUCCESS(headBucketOutcome);

        // Using path style addressing
        config.endpointOverride = "";
        S3::S3Client s3ClientInUsWest2UsingPathStyleAddressing(config, AWSAuthV4Signer::PayloadSigningPolicy::Never /*signPayloads*/, false /*useVirtualAddressing*/);
        headBucketOutcome = s3ClientInUsWest2UsingPathStyleAddressing.HeadBucket(headBucketRequest);
        ASSERT_FALSE(headBucketOutcome.IsSuccess());
        EXPECT_EQ(CoreErrors::ENDPOINT_RESOLUTION_FAILURE, (CoreErrors) headBucketOutcome.GetError().GetErrorType());

        CleanUpAccessPointTest(bucketName, accessPointName);
    }

    TEST_F(S3ControlTest, TestS3OutpostsOperations)
    {
        Aws::String bucketName = BuildResourceName(BASE_BUCKET_NAME);
        Aws::String outpostId = BuildResourceName(BASE_OUTPOST_ID);
        Aws::String accessPointName = BuildResourceName(BASE_ACCESS_POINT);

        Aws::StringStream ss;
        GetAccessPointRequest getAccessPointRequest;
        getAccessPointRequest.SetAccountId(m_accountId);
        // Invalid service name
        ss.str("");
        ss << "arn:aws:sqs:" << Aws::Region::US_WEST_2 << ":" << m_accountId << ":outpost:" << outpostId << ":accesspoint:" << accessPointName;
        getAccessPointRequest.SetName(ss.str());
        auto getAccessPointOutcome = m_client.GetAccessPoint(getAccessPointRequest);
        ASSERT_FALSE(getAccessPointOutcome.IsSuccess());
        EXPECT_EQ(CoreErrors::ENDPOINT_RESOLUTION_FAILURE, (CoreErrors) getAccessPointOutcome.GetError().GetErrorType());

        // Empty region
        ss.str("");
        ss << "arn:aws:s3-outposts:" << "" << ":" << m_accountId << ":outpost:" << outpostId << ":accesspoint:" << accessPointName;
        getAccessPointRequest.SetName(ss.str());
        getAccessPointOutcome = m_client.GetAccessPoint(getAccessPointRequest);
        ASSERT_FALSE(getAccessPointOutcome.IsSuccess());
        EXPECT_EQ(CoreErrors::ENDPOINT_RESOLUTION_FAILURE, (CoreErrors) getAccessPointOutcome.GetError().GetErrorType());

        // Empty account ID
        ss.str("");
        ss << "arn:aws:s3-outposts:" << Aws::Region::US_WEST_2 << ":" << "" << ":outpost:" << outpostId << ":accesspoint:" << accessPointName;
        getAccessPointRequest.SetName(ss.str());
        getAccessPointOutcome = m_client.GetAccessPoint(getAccessPointRequest);
        ASSERT_FALSE(getAccessPointOutcome.IsSuccess());
        EXPECT_EQ(CoreErrors::ENDPOINT_RESOLUTION_FAILURE, (CoreErrors) getAccessPointOutcome.GetError().GetErrorType());

        // Invalid account ID
        ss.str("");
        ss << "arn:aws:s3-outposts:" << Aws::Region::US_WEST_2 << ":." << m_accountId << ":outpost:" << outpostId << ":accesspoint:" << accessPointName;
        getAccessPointRequest.SetName(ss.str());
        getAccessPointOutcome = m_client.GetAccessPoint(getAccessPointRequest);
        ASSERT_FALSE(getAccessPointOutcome.IsSuccess());
        EXPECT_EQ(CoreErrors::ENDPOINT_RESOLUTION_FAILURE, (CoreErrors) getAccessPointOutcome.GetError().GetErrorType());

        // Account ID mismatch
        ss.str("");
        ss << "arn:aws:s3-outposts:" << Aws::Region::US_WEST_2 << ":" << m_accountId << ":outpost:" << outpostId << ":accesspoint:" << accessPointName;
        getAccessPointRequest.SetName(ss.str());
        getAccessPointRequest.SetAccountId("123456789012");
        getAccessPointOutcome = m_client.GetAccessPoint(getAccessPointRequest);
        ASSERT_FALSE(getAccessPointOutcome.IsSuccess());
        EXPECT_EQ(CoreErrors::ENDPOINT_RESOLUTION_FAILURE, (CoreErrors) getAccessPointOutcome.GetError().GetErrorType());

        // Missing account ID
        getAccessPointRequest.SetName(accessPointName);
        getAccessPointRequest.SetAccountId("");
        getAccessPointOutcome = m_client.GetAccessPoint(getAccessPointRequest);
        ASSERT_FALSE(getAccessPointOutcome.IsSuccess());
        EXPECT_EQ(S3ControlErrors::INVALID_PARAMETER_VALUE, getAccessPointOutcome.GetError().GetErrorType());
    }

    TEST_F(S3ControlTest, TestS3AccessPointWithPresignedUrls)
    {
        Aws::String bucketName = BuildResourceName(PRESIGNED_URLS_BUCKET_NAME);
        Aws::String accessPoint = BuildResourceName(PRESIGNED_URLS_ACCESS_POINT);

        PrepareAccessPointTest(bucketName, accessPoint);

        Aws::StringStream ss;
        ss << "arn:aws:s3:" << Aws::Region::US_WEST_2 << ":" << m_accountId << ":accesspoint:" << accessPoint;
        Aws::String accessPointArn = ss.str();

        Aws::String presignedUrlPut = m_s3Client.GeneratePresignedUrl(accessPointArn, TEST_OBJECT_KEY, HttpMethod::HTTP_PUT);
        std::shared_ptr<HttpRequest> putRequest = CreateHttpRequest(presignedUrlPut, HttpMethod::HTTP_PUT, Aws::Utils::Stream::DefaultResponseStreamFactoryMethod);
        DoPresignedUrlTest(accessPointArn, putRequest);

        ClientConfiguration config;
        config.region = Aws::Region::US_EAST_1;
        S3::S3Client s3ClientInUsEast1(config);
        presignedUrlPut = s3ClientInUsEast1.GeneratePresignedUrl(accessPointArn, TEST_OBJECT_KEY, HttpMethod::HTTP_PUT);
        ASSERT_STREQ("", presignedUrlPut.c_str());

        // The following tests are using region in ARN when making requests.
        Aws::Environment::SetEnv("AWS_S3_USE_ARN_REGION", "true", 1);

        S3::S3Client s3ClientInUsEast1UsingArnRegion(config);
        presignedUrlPut = s3ClientInUsEast1UsingArnRegion.GeneratePresignedUrl(accessPointArn, TEST_OBJECT_KEY, HttpMethod::HTTP_PUT);
        putRequest = CreateHttpRequest(presignedUrlPut, HttpMethod::HTTP_PUT, Aws::Utils::Stream::DefaultResponseStreamFactoryMethod);
        DoPresignedUrlTest(accessPointArn, putRequest);

        CleanUpAccessPointTest(bucketName, accessPoint);
    }

    TEST_F(S3ControlTest, TestMultiRegionAccessPoint)
    {
        Aws::String bucketName = BuildResourceName(BASE_MRAP_BUCKET_NAME);
        Aws::String accessPointName = BuildResourceName(BASE_MRAP) + "integrationtest";

        Aws::Vector<Aws::String> multiRegions;
        multiRegions.push_back(Aws::Region::US_EAST_2);
        multiRegions.push_back(Aws::Region::US_WEST_2);
        PrepareMRAPTest(bucketName, multiRegions, accessPointName);

        GetMultiRegionAccessPointRequest getMRAPRequest;
        getMRAPRequest.SetAccountId(m_accountId);
        getMRAPRequest.SetName(accessPointName);
        auto getMRAPOutcome = m_client.GetMultiRegionAccessPoint(getMRAPRequest);
        AWS_ASSERT_SUCCESS(getMRAPOutcome);
        Aws::Vector<S3Control::Model::RegionReport> returnedRegions = getMRAPOutcome.GetResult().GetAccessPoint().GetRegions();
        ASSERT_EQ(2u, returnedRegions.size());

        Aws::Client::ClientConfiguration config;
        config.region = Aws::Region::US_WEST_2;
        Aws::S3::S3Client s3Client = createS3Client(config);

        Aws::Vector<Aws::String> objectKeys;
        objectKeys.push_back(TEST_OBJECT_KEY);
        objectKeys.push_back(Aws::String(TEST_OBJECT_KEY) + "With!and?\\+/plus:In=Key&Name");
        for (const auto& objectKey : objectKeys)
        {
            S3::Model::PutObjectRequest putObjectRequest;
            Aws::StringStream arn;
            arn << "arn:aws:s3::" << m_accountId << ":accesspoint:" << getMRAPOutcome.GetResult().GetAccessPoint().GetAlias();
            putObjectRequest.SetBucket(arn.str());
            putObjectRequest.SetKey(objectKey);
            std::shared_ptr<Aws::IOStream> objectStream = Aws::MakeShared<Aws::StringStream>(ALLOCATION_TAG);
            *objectStream << "Test Multi Region Access Point";
            putObjectRequest.SetBody(objectStream);
            auto putObjectOutcome = s3Client.PutObject(putObjectRequest);
            AWS_ASSERT_SUCCESS(putObjectOutcome);

            S3::Model::HeadObjectRequest headObjectRequest;
            headObjectRequest.SetBucket(arn.str());
            headObjectRequest.SetKey(objectKey);
            auto headObjectOutcome = s3Client.HeadObject(headObjectRequest);
            AWS_ASSERT_SUCCESS(headObjectOutcome);

            S3::Model::GetObjectRequest getObjectRequest;
            getObjectRequest.SetBucket(arn.str());
            getObjectRequest.SetKey(objectKey);
            auto getObjectOutcome = s3Client.GetObject(getObjectRequest);
            AWS_ASSERT_SUCCESS(getObjectOutcome);
            Aws::StringStream ss;
            ss << getObjectOutcome.GetResult().GetBody().rdbuf();
            ASSERT_STREQ("Test Multi Region Access Point", ss.str().c_str());
            S3::Model::DeleteObjectRequest deleteObjectRequest;
            deleteObjectRequest.SetBucket(arn.str());
            deleteObjectRequest.SetKey(objectKey);
            auto deleteObjectOutcome = s3Client.DeleteObject(deleteObjectRequest);
            AWS_ASSERT_SUCCESS(deleteObjectOutcome);
        }

        CleanUpMRAPTest(bucketName, multiRegions, accessPointName);
    }
}