diff --git a/generator/.DevConfigs/433a9a6d-b8ea-4676-b763-70711e8288e4.json b/generator/.DevConfigs/433a9a6d-b8ea-4676-b763-70711e8288e4.json new file mode 100644 index 000000000000..166d9469d903 --- /dev/null +++ b/generator/.DevConfigs/433a9a6d-b8ea-4676-b763-70711e8288e4.json @@ -0,0 +1,11 @@ +{ + "services": [ + { + "serviceName": "S3", + "type": "patch", + "changeLogMessages": [ + "Added PutObjectResponse to TransferUtilityUploadResponse mapping" + ] + } + ] +} diff --git a/generator/ServiceClientGeneratorLib/Generators/SourceFiles/AssemblyInfo.cs b/generator/ServiceClientGeneratorLib/Generators/SourceFiles/AssemblyInfo.cs index ecf1a83dd482..156c2b897efe 100644 --- a/generator/ServiceClientGeneratorLib/Generators/SourceFiles/AssemblyInfo.cs +++ b/generator/ServiceClientGeneratorLib/Generators/SourceFiles/AssemblyInfo.cs @@ -15,7 +15,7 @@ namespace ServiceClientGenerator.Generators.SourceFiles /// Class to produce the template output /// - #line 1 "C:\codebase\v4\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\SourceFiles\AssemblyInfo.tt" + #line 1 "C:\dev\repos\aws-sdk-net\generator\ServiceClientGeneratorLib\Generators\SourceFiles\AssemblyInfo.tt" [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] public partial class AssemblyInfo : BaseGenerator { @@ -36,35 +36,35 @@ public override string TransformText() // associated with an assembly. [assembly: AssemblyTitle("""); - #line 12 "C:\codebase\v4\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\SourceFiles\AssemblyInfo.tt" + #line 12 "C:\dev\repos\aws-sdk-net\generator\ServiceClientGeneratorLib\Generators\SourceFiles\AssemblyInfo.tt" this.Write(this.ToStringHelper.ToStringWithCulture(this.Config.AssemblyTitle)); #line default #line hidden this.Write("\")]\r\n#if BCL\r\n[assembly: AssemblyDescription(\""); - #line 14 "C:\codebase\v4\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\SourceFiles\AssemblyInfo.tt" + #line 14 "C:\dev\repos\aws-sdk-net\generator\ServiceClientGeneratorLib\Generators\SourceFiles\AssemblyInfo.tt" this.Write(this.ToStringHelper.ToStringWithCulture(this.Config.AssemblyDescription(versionIdentifier: "4.7.2"))); #line default #line hidden this.Write("\")]\r\n#elif NETSTANDARD20\r\n[assembly: AssemblyDescription(\""); - #line 16 "C:\codebase\v4\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\SourceFiles\AssemblyInfo.tt" + #line 16 "C:\dev\repos\aws-sdk-net\generator\ServiceClientGeneratorLib\Generators\SourceFiles\AssemblyInfo.tt" this.Write(this.ToStringHelper.ToStringWithCulture(this.Config.AssemblyDescription(versionIdentifier: "NetStandard 2.0"))); #line default #line hidden this.Write("\")]\r\n#elif NETCOREAPP3_1\r\n[assembly: AssemblyDescription(\""); - #line 18 "C:\codebase\v4\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\SourceFiles\AssemblyInfo.tt" + #line 18 "C:\dev\repos\aws-sdk-net\generator\ServiceClientGeneratorLib\Generators\SourceFiles\AssemblyInfo.tt" this.Write(this.ToStringHelper.ToStringWithCulture(this.Config.AssemblyDescription(versionIdentifier: ".NET Core 3.1"))); #line default #line hidden this.Write("\")]\r\n#elif NET8_0\r\n[assembly: AssemblyDescription(\""); - #line 20 "C:\codebase\v4\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\SourceFiles\AssemblyInfo.tt" + #line 20 "C:\dev\repos\aws-sdk-net\generator\ServiceClientGeneratorLib\Generators\SourceFiles\AssemblyInfo.tt" this.Write(this.ToStringHelper.ToStringWithCulture(this.Config.AssemblyDescription(versionIdentifier: ".NET 8.0"))); #line default @@ -72,7 +72,7 @@ public override string TransformText() this.Write("\")]\r\n#else\r\n#error Unknown platform constant - unable to set correct AssemblyDesc" + "ription\r\n#endif\r\n\r\n"); - #line 25 "C:\codebase\v4\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\SourceFiles\AssemblyInfo.tt" + #line 25 "C:\dev\repos\aws-sdk-net\generator\ServiceClientGeneratorLib\Generators\SourceFiles\AssemblyInfo.tt" if (this.Config.AssemblyTitle=="AWSSDK.DynamoDBv2") { #line default @@ -81,7 +81,22 @@ public override string TransformText() [assembly: InternalsVisibleTo(""AWSSDK.UnitTests.NetFramework, PublicKey=0024000004800000940000000602000000240000525341310004000001000100db5f59f098d27276c7833875a6263a3cc74ab17ba9a9df0b52aedbe7252745db7274d5271fd79c1f08f668ecfa8eaab5626fa76adc811d3c8fc55859b0d09d3bc0a84eecd0ba891f2b8a2fc55141cdcc37c2053d53491e650a479967c3622762977900eddbf1252ed08a2413f00a28f3a0752a81203f03ccb7f684db373518b4"")] "); - #line 28 "C:\codebase\v4\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\SourceFiles\AssemblyInfo.tt" + #line 28 "C:\dev\repos\aws-sdk-net\generator\ServiceClientGeneratorLib\Generators\SourceFiles\AssemblyInfo.tt" + } + + #line default + #line hidden + + #line 29 "C:\dev\repos\aws-sdk-net\generator\ServiceClientGeneratorLib\Generators\SourceFiles\AssemblyInfo.tt" + if (this.Config.AssemblyTitle=="AWSSDK.S3") { + + #line default + #line hidden + this.Write(@"[assembly: InternalsVisibleTo(""AWSSDK.UnitTests.S3.NetFramework, PublicKey=0024000004800000940000000602000000240000525341310004000001000100db5f59f098d27276c7833875a6263a3cc74ab17ba9a9df0b52aedbe7252745db7274d5271fd79c1f08f668ecfa8eaab5626fa76adc811d3c8fc55859b0d09d3bc0a84eecd0ba891f2b8a2fc55141cdcc37c2053d53491e650a479967c3622762977900eddbf1252ed08a2413f00a28f3a0752a81203f03ccb7f684db373518b4"")] +[assembly: InternalsVisibleTo(""AWSSDK.UnitTests.NetFramework, PublicKey=0024000004800000940000000602000000240000525341310004000001000100db5f59f098d27276c7833875a6263a3cc74ab17ba9a9df0b52aedbe7252745db7274d5271fd79c1f08f668ecfa8eaab5626fa76adc811d3c8fc55859b0d09d3bc0a84eecd0ba891f2b8a2fc55141cdcc37c2053d53491e650a479967c3622762977900eddbf1252ed08a2413f00a28f3a0752a81203f03ccb7f684db373518b4"")] +"); + + #line 32 "C:\dev\repos\aws-sdk-net\generator\ServiceClientGeneratorLib\Generators\SourceFiles\AssemblyInfo.tt" } #line default @@ -110,14 +125,14 @@ public override string TransformText() // [assembly: AssemblyVersion(""1.0.*"")] [assembly: AssemblyVersion("""); - #line 51 "C:\codebase\v4\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\SourceFiles\AssemblyInfo.tt" + #line 55 "C:\dev\repos\aws-sdk-net\generator\ServiceClientGeneratorLib\Generators\SourceFiles\AssemblyInfo.tt" this.Write(this.ToStringHelper.ToStringWithCulture(this.Config.ServiceVersion)); #line default #line hidden this.Write("\")]\r\n[assembly: AssemblyFileVersion(\""); - #line 52 "C:\codebase\v4\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\SourceFiles\AssemblyInfo.tt" + #line 56 "C:\dev\repos\aws-sdk-net\generator\ServiceClientGeneratorLib\Generators\SourceFiles\AssemblyInfo.tt" this.Write(this.ToStringHelper.ToStringWithCulture(this.Config.ServiceFileVersion)); #line default diff --git a/generator/ServiceClientGeneratorLib/Generators/SourceFiles/AssemblyInfo.tt b/generator/ServiceClientGeneratorLib/Generators/SourceFiles/AssemblyInfo.tt index 4a8b9fad751a..ab2cf5d21a23 100644 --- a/generator/ServiceClientGeneratorLib/Generators/SourceFiles/AssemblyInfo.tt +++ b/generator/ServiceClientGeneratorLib/Generators/SourceFiles/AssemblyInfo.tt @@ -26,6 +26,10 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("AWSSDK.UnitTests.DynamoDBv2.NetFramework, PublicKey=0024000004800000940000000602000000240000525341310004000001000100db5f59f098d27276c7833875a6263a3cc74ab17ba9a9df0b52aedbe7252745db7274d5271fd79c1f08f668ecfa8eaab5626fa76adc811d3c8fc55859b0d09d3bc0a84eecd0ba891f2b8a2fc55141cdcc37c2053d53491e650a479967c3622762977900eddbf1252ed08a2413f00a28f3a0752a81203f03ccb7f684db373518b4")] [assembly: InternalsVisibleTo("AWSSDK.UnitTests.NetFramework, PublicKey=0024000004800000940000000602000000240000525341310004000001000100db5f59f098d27276c7833875a6263a3cc74ab17ba9a9df0b52aedbe7252745db7274d5271fd79c1f08f668ecfa8eaab5626fa76adc811d3c8fc55859b0d09d3bc0a84eecd0ba891f2b8a2fc55141cdcc37c2053d53491e650a479967c3622762977900eddbf1252ed08a2413f00a28f3a0752a81203f03ccb7f684db373518b4")] <# } #> +<# if (this.Config.AssemblyTitle=="AWSSDK.S3") { #> +[assembly: InternalsVisibleTo("AWSSDK.UnitTests.S3.NetFramework, PublicKey=0024000004800000940000000602000000240000525341310004000001000100db5f59f098d27276c7833875a6263a3cc74ab17ba9a9df0b52aedbe7252745db7274d5271fd79c1f08f668ecfa8eaab5626fa76adc811d3c8fc55859b0d09d3bc0a84eecd0ba891f2b8a2fc55141cdcc37c2053d53491e650a479967c3622762977900eddbf1252ed08a2413f00a28f3a0752a81203f03ccb7f684db373518b4")] +[assembly: InternalsVisibleTo("AWSSDK.UnitTests.NetFramework, PublicKey=0024000004800000940000000602000000240000525341310004000001000100db5f59f098d27276c7833875a6263a3cc74ab17ba9a9df0b52aedbe7252745db7274d5271fd79c1f08f668ecfa8eaab5626fa76adc811d3c8fc55859b0d09d3bc0a84eecd0ba891f2b8a2fc55141cdcc37c2053d53491e650a479967c3622762977900eddbf1252ed08a2413f00a28f3a0752a81203f03ccb7f684db373518b4")] +<# } #> [assembly: AssemblyConfiguration("")] [assembly: AssemblyProduct("Amazon Web Services SDK for .NET")] [assembly: AssemblyCompany("Amazon.com, Inc")] diff --git a/generator/ServiceModels/_manifest.json b/generator/ServiceModels/_manifest.json index 24252ee5bfd6..5d712b04c8c1 100644 --- a/generator/ServiceModels/_manifest.json +++ b/generator/ServiceModels/_manifest.json @@ -60,7 +60,8 @@ "Custom\\Runtime\\TestResponses\\*.txt", "Custom\\Runtime\\EventStreams\\test_vectors\\*", "Custom\\Runtime\\TestEndpoints\\*.json", - "Custom\\TestTools\\ComparerTest.json" + "Custom\\TestTools\\ComparerTest.json", + "..\\Services\\S3\\UnitTests\\Custom\\EmbeddedResource\\*" ], "packageReferences": [ { diff --git a/sdk/src/Services/S3/Custom/Transfer/Internal/ResponseMapper.cs b/sdk/src/Services/S3/Custom/Transfer/Internal/ResponseMapper.cs new file mode 100644 index 000000000000..d130aee20bff --- /dev/null +++ b/sdk/src/Services/S3/Custom/Transfer/Internal/ResponseMapper.cs @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use + * this file except in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. + * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * ***************************************************************************** + * __ _ _ ___ + * ( )( \/\/ )/ __) + * /__\ \ / \__ \ + * (_)(_) \/\/ (___/ + * + * AWS SDK for .NET + * API Version: 2006-03-01 + * + */ + +using Amazon.S3.Model; + +namespace Amazon.S3.Transfer.Internal +{ + /// + /// Utility class for mapping S3 response objects to TransferUtilityUploadResponse. + /// Maps fields based on the mapping.json configuration used by the Transfer Utility. + /// + internal static class ResponseMapper + { + /// + /// Maps a PutObjectResponse to TransferUtilityUploadResponse. + /// Uses the field mappings defined in mapping.json "Conversion" -> "PutObjectResponse" -> "UploadResponse". + /// + /// The PutObjectResponse to map from + /// A new TransferUtilityUploadResponse with mapped fields + internal static TransferUtilityUploadResponse MapPutObjectResponse(PutObjectResponse source) + { + if (source == null) + return null; + + var response = new TransferUtilityUploadResponse(); + + // Map all fields as defined in mapping.json "Conversion" -> "PutObjectResponse" -> "UploadResponse" + if (source.IsSetBucketKeyEnabled()) + response.BucketKeyEnabled = source.BucketKeyEnabled.GetValueOrDefault(); + + if (source.IsSetChecksumCRC32()) + response.ChecksumCRC32 = source.ChecksumCRC32; + + if (source.IsSetChecksumCRC32C()) + response.ChecksumCRC32C = source.ChecksumCRC32C; + + if (source.IsSetChecksumCRC64NVME()) + response.ChecksumCRC64NVME = source.ChecksumCRC64NVME; + + if (source.IsSetChecksumSHA1()) + response.ChecksumSHA1 = source.ChecksumSHA1; + + if (source.IsSetChecksumSHA256()) + response.ChecksumSHA256 = source.ChecksumSHA256; + + if (source.IsSetChecksumType()) + response.ChecksumType = source.ChecksumType; + + if (source.IsSetETag()) + response.ETag = source.ETag; + + if (source.Expiration != null) + response.Expiration = source.Expiration; + + if (source.IsSetRequestCharged()) + response.RequestCharged = source.RequestCharged; + + if (source.ServerSideEncryptionCustomerMethod != null) + response.ServerSideEncryptionCustomerMethod = source.ServerSideEncryptionCustomerMethod; + + if (source.ServerSideEncryptionCustomerProvidedKeyMD5 != null) + response.ServerSideEncryptionCustomerProvidedKeyMD5 = source.ServerSideEncryptionCustomerProvidedKeyMD5; + + if (source.ServerSideEncryptionKeyManagementServiceEncryptionContext != null) + response.ServerSideEncryptionKeyManagementServiceEncryptionContext = source.ServerSideEncryptionKeyManagementServiceEncryptionContext; + + if (source.IsSetServerSideEncryptionKeyManagementServiceKeyId()) + response.ServerSideEncryptionKeyManagementServiceKeyId = source.ServerSideEncryptionKeyManagementServiceKeyId; + + if (source.ServerSideEncryptionMethod != null) + response.ServerSideEncryptionMethod = source.ServerSideEncryptionMethod; + + if (source.IsSetVersionId()) + response.VersionId = source.VersionId; + + // Copy response metadata + response.ResponseMetadata = source.ResponseMetadata; + response.ContentLength = source.ContentLength; + response.HttpStatusCode = source.HttpStatusCode; + + return response; + } + + } +} diff --git a/sdk/src/Services/S3/Custom/Transfer/TransferUtilityUploadResponse.cs b/sdk/src/Services/S3/Custom/Transfer/TransferUtilityUploadResponse.cs new file mode 100644 index 000000000000..3fcc20294a0a --- /dev/null +++ b/sdk/src/Services/S3/Custom/Transfer/TransferUtilityUploadResponse.cs @@ -0,0 +1,470 @@ +/******************************************************************************* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use + * this file except in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. + * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * ***************************************************************************** + * __ _ _ ___ + * ( )( \/\/ )/ __) + * /__\ \ / \__ \ + * (_)(_) \/\/ (___/ + * + * AWS SDK for .NET + * API Version: 2006-03-01 + * + */ + +using System; +using Amazon.Runtime; +using Amazon.S3.Model; +using Amazon.Runtime.Internal; + +namespace Amazon.S3.Transfer +{ + /// + /// Response object for Transfer Utility upload operations. + /// Contains unified response fields from both simple uploads (PutObjectResponse) + /// and multipart uploads (CompleteMultipartUploadResponse). + /// + public class TransferUtilityUploadResponse : AmazonWebServiceResponse + { + private bool? _bucketKeyEnabled; + private string _checksumCRC32; + private string _checksumCRC32C; + private string _checksumCRC64NVME; + private string _checksumSHA1; + private string _checksumSHA256; + private ChecksumType _checksumType; + private string _etag; + private Expiration _expiration; + private RequestCharged _requestCharged; + private ServerSideEncryptionCustomerMethod _serverSideEncryptionCustomerMethod; + private string _sseCustomerKeyMD5; + private string _sseKmsEncryptionContext; + private string _sseKmsKeyId; + private ServerSideEncryptionMethod _serverSideEncryption; + private string _versionId; + + /// + /// Gets and sets the property BucketKeyEnabled. + /// + /// Indicates whether the uploaded object uses an S3 Bucket Key for server-side encryption + /// with Key Management Service (KMS) keys (SSE-KMS). + /// + /// + public bool? BucketKeyEnabled + { + get { return this._bucketKeyEnabled; } + set { this._bucketKeyEnabled = value; } + } + + /// + /// Checks if BucketKeyEnabled property is set. + /// + /// true if BucketKeyEnabled property is set. + internal bool IsSetBucketKeyEnabled() + { + return this._bucketKeyEnabled.HasValue; + } + + /// + /// Gets and sets the property ChecksumCRC32. + /// + /// The Base64 encoded, 32-bit CRC-32 checksum of the object. This checksum is only present + /// if the checksum was uploaded with the object. When you use an API operation on an object that + /// was uploaded using multipart uploads, this value may not be a direct checksum value + /// of the full object. Instead, it's a calculation based on the checksum values of each + /// individual part. For more information about how checksums are calculated with multipart + /// uploads, see + /// Checking object integrity in the Amazon S3 User Guide. + /// + /// + public string ChecksumCRC32 + { + get { return this._checksumCRC32; } + set { this._checksumCRC32 = value; } + } + + /// + /// Checks if ChecksumCRC32 property is set. + /// + /// true if ChecksumCRC32 property is set. + internal bool IsSetChecksumCRC32() + { + return !string.IsNullOrEmpty(this._checksumCRC32); + } + + /// + /// Gets and sets the property ChecksumCRC32C. + /// + /// The Base64 encoded, 32-bit CRC-32C checksum of the object. This checksum is only present + /// if the checksum was uploaded with the object. When you use an API operation on an object that + /// was uploaded using multipart uploads, this value may not be a direct checksum value + /// of the full object. Instead, it's a calculation based on the checksum values of each + /// individual part. For more information about how checksums are calculated with multipart + /// uploads, see + /// Checking object integrity in the Amazon S3 User Guide. + /// + /// + public string ChecksumCRC32C + { + get { return this._checksumCRC32C; } + set { this._checksumCRC32C = value; } + } + + /// + /// Checks if ChecksumCRC32C property is set. + /// + /// true if ChecksumCRC32C property is set. + internal bool IsSetChecksumCRC32C() + { + return !string.IsNullOrEmpty(this._checksumCRC32C); + } + + /// + /// Gets and sets the property ChecksumCRC64NVME. + /// + /// The Base64 encoded, 64-bit CRC-64NVME checksum of the object. This header is present + /// if it was uploaded with the CRC-64NVME checksum algorithm, or if it was uploaded + /// without a checksum (and Amazon S3 added the default checksum, CRC-64NVME, to the uploaded object). + /// For more information about how checksums are calculated with multipart + /// uploads, see + /// Checking object integrity in the Amazon S3 User Guide. + /// + /// + public string ChecksumCRC64NVME + { + get { return this._checksumCRC64NVME; } + set { this._checksumCRC64NVME = value; } + } + + /// + /// Checks if ChecksumCRC64NVME property is set. + /// + /// true if ChecksumCRC64NVME property is set. + internal bool IsSetChecksumCRC64NVME() + { + return !string.IsNullOrEmpty(this._checksumCRC64NVME); + } + + /// + /// Gets and sets the property ChecksumSHA1. + /// + /// The Base64 encoded, 160-bit SHA-1 digest of the object. This will only be present + /// if it was uploaded with the object. When you use the API operation on an object that + /// was uploaded using multipart uploads, this value may not be a direct checksum value + /// of the full object. Instead, it's a calculation based on the checksum values of each + /// individual part. For more information about how checksums are calculated with multipart + /// uploads, see + /// Checking object integrity in the Amazon S3 User Guide. + /// + /// + public string ChecksumSHA1 + { + get { return this._checksumSHA1; } + set { this._checksumSHA1 = value; } + } + + /// + /// Checks if ChecksumSHA1 property is set. + /// + /// true if ChecksumSHA1 property is set. + internal bool IsSetChecksumSHA1() + { + return !string.IsNullOrEmpty(this._checksumSHA1); + } + + /// + /// Gets and sets the property ChecksumSHA256. + /// + /// The Base64 encoded, 256-bit SHA-256 digest of the object. This will only be present + /// if it was uploaded with the object. When you use an API operation on an object that + /// was uploaded using multipart uploads, this value may not be a direct checksum value + /// of the full object. Instead, it's a calculation based on the checksum values of each + /// individual part. For more information about how checksums are calculated with multipart + /// uploads, see + /// Checking object integrity in the Amazon S3 User Guide. + /// + /// + public string ChecksumSHA256 + { + get { return this._checksumSHA256; } + set { this._checksumSHA256 = value; } + } + + /// + /// Checks if ChecksumSHA256 property is set. + /// + /// true if ChecksumSHA256 property is set. + internal bool IsSetChecksumSHA256() + { + return !string.IsNullOrEmpty(this._checksumSHA256); + } + + /// + /// Gets and sets the property ChecksumType. + /// + /// This header specifies the checksum type of the object, which determines how part-level + /// checksums are combined to create an object-level checksum for multipart objects. For + /// PutObject uploads, the checksum type is always FULL_OBJECT. You can use + /// this header as a data integrity check to verify that the checksum type that is received + /// is the same checksum that was specified. For more information, + /// see + /// Checking object integrity in the Amazon S3 User Guide. + /// + /// + public ChecksumType ChecksumType + { + get { return this._checksumType; } + set { this._checksumType = value; } + } + + /// + /// Checks if ChecksumType property is set. + /// + /// true if ChecksumType property is set. + internal bool IsSetChecksumType() + { + return this._checksumType != null; + } + + /// + /// Gets and sets the property ETag. + /// + /// Entity tag for the uploaded object. + /// + /// + /// + /// General purpose buckets - To ensure that data is not corrupted traversing + /// the network, for objects where the ETag is the MD5 digest of the object, you can calculate + /// the MD5 while putting an object to Amazon S3 and compare the returned ETag to the + /// calculated MD5 value. + /// + /// + /// + /// Directory buckets - The ETag for the object in a directory bucket isn't the + /// MD5 digest of the object. + /// + /// + public string ETag + { + get { return this._etag; } + set { this._etag = value; } + } + + /// + /// Checks if ETag property is set. + /// + /// true if ETag property is set. + internal bool IsSetETag() + { + return !string.IsNullOrEmpty(this._etag); + } + + /// + /// Gets and sets the property Expiration. + /// + /// If the object expiration is configured, this will contain the expiration date (expiry-date) + /// and rule ID (rule-id). The value of rule-id is URL encoded. + /// + /// + /// Object expiration information is not returned for directory buckets (for those, the + /// response header will contain the value "NotImplemented"). + /// + /// + public Expiration Expiration + { + get { return this._expiration; } + set { this._expiration = value; } + } + + /// + /// Checks if Expiration property is set. + /// + /// true if Expiration property is set. + internal bool IsSetExpiration() + { + return this._expiration != null; + } + + /// + /// Gets and sets the property RequestCharged. + /// + /// If present, indicates that the requester was successfully charged for the request. + /// + /// + public RequestCharged RequestCharged + { + get { return this._requestCharged; } + set { this._requestCharged = value; } + } + + /// + /// Checks if RequestCharged property is set. + /// + /// true if RequestCharged property is set. + internal bool IsSetRequestCharged() + { + return this._requestCharged != null; + } + + /// + /// The Server-side encryption algorithm to be used with the customer provided key. + /// + /// + /// This functionality is not supported for directory buckets. + /// + /// + /// + public ServerSideEncryptionCustomerMethod ServerSideEncryptionCustomerMethod + { + get { return this._serverSideEncryptionCustomerMethod; } + set { this._serverSideEncryptionCustomerMethod = value; } + } + + /// + /// Checks if ServerSideEncryptionCustomerMethod property is set. + /// + /// true if ServerSideEncryptionCustomerMethod property is set. + internal bool IsSetServerSideEncryptionCustomerMethod() + { + return this._serverSideEncryptionCustomerMethod != null; + } + + /// + /// The MD5 of the customer encryption key specified in the ServerSideEncryptionCustomerProvidedKey property. The MD5 is + /// base 64 encoded. This field is optional, the SDK will calculate the MD5 if this is not set. + /// + /// + /// This functionality is not supported for directory buckets. + /// + /// + /// + public string ServerSideEncryptionCustomerProvidedKeyMD5 + { + get { return this._sseCustomerKeyMD5; } + set { this._sseCustomerKeyMD5 = value; } + } + + /// + /// Checks if ServerSideEncryptionCustomerProvidedKeyMD5 property is set. + /// + /// true if ServerSideEncryptionCustomerProvidedKeyMD5 property is set. + internal bool IsSetServerSideEncryptionCustomerProvidedKeyMD5() + { + return !string.IsNullOrEmpty(this._sseCustomerKeyMD5); + } + + /// + /// + /// If present, indicates the Amazon Web Services KMS Encryption Context to use for object encryption. + /// The value of this header is a Base64 encoded string of a UTF-8 encoded JSON, which contains the encryption context as key-value pairs. + /// This value is stored as object metadata and automatically gets passed on to Amazon Web Services KMS for future GetObject operations on this object. + /// + /// + [AWSProperty(Sensitive=true)] + public string ServerSideEncryptionKeyManagementServiceEncryptionContext + { + get { return this._sseKmsEncryptionContext; } + set { this._sseKmsEncryptionContext = value; } + } + + /// + /// Checks if ServerSideEncryptionKeyManagementServiceEncryptionContext property is set. + /// + /// true if ServerSideEncryptionKeyManagementServiceEncryptionContext property is set. + internal bool IsSetServerSideEncryptionKeyManagementServiceEncryptionContext() + { + return !string.IsNullOrEmpty(this._sseKmsEncryptionContext); + } + + /// + /// + /// If present, indicates the ID of the KMS key that was used for object encryption. + /// + /// + [AWSProperty(Sensitive=true)] + public string ServerSideEncryptionKeyManagementServiceKeyId + { + get { return this._sseKmsKeyId; } + set { this._sseKmsKeyId = value; } + } + + /// + /// Checks if ServerSideEncryptionKeyManagementServiceKeyId property is set. + /// + /// true if ServerSideEncryptionKeyManagementServiceKeyId property is set. + internal bool IsSetServerSideEncryptionKeyManagementServiceKeyId() + { + return !string.IsNullOrEmpty(this._sseKmsKeyId); + } + + /// + /// + /// The server-side encryption algorithm used when you store this object in Amazon S3 or Amazon FSx. + /// + /// + /// + /// When accessing data stored in Amazon FSx file systems using S3 access points, the only valid server side encryption option is aws:fsx. + /// + /// + /// + public ServerSideEncryptionMethod ServerSideEncryptionMethod + { + get { return this._serverSideEncryption; } + set { this._serverSideEncryption = value; } + } + + /// + /// Checks if ServerSideEncryptionMethod property is set. + /// + /// true if ServerSideEncryptionMethod property is set. + internal bool IsSetServerSideEncryptionMethod() + { + return this._serverSideEncryption != null; + } + + /// + /// Gets and sets the property VersionId. + /// + /// Version ID of the object. + /// + /// + /// + /// If you enable versioning for a bucket, Amazon S3 automatically generates a unique + /// version ID for the object being stored. Amazon S3 returns this ID in the response. + /// When you enable versioning for a bucket, if Amazon S3 receives multiple write requests + /// for the same object simultaneously, it stores all of the objects. For more information + /// about versioning, see Adding + /// Objects to Versioning-Enabled Buckets in the Amazon S3 User Guide. For + /// information about returning the versioning state of a bucket, see GetBucketVersioning. + /// + /// + /// + /// + /// This functionality is not supported for directory buckets. + /// + /// + /// + public string VersionId + { + get { return this._versionId; } + set { this._versionId = value; } + } + + /// + /// Checks if VersionId property is set. + /// + /// true if VersionId property is set. + internal bool IsSetVersionId() + { + return !string.IsNullOrEmpty(this._versionId); + } + } +} diff --git a/sdk/src/Services/S3/Properties/AssemblyInfo.cs b/sdk/src/Services/S3/Properties/AssemblyInfo.cs index 90f36fe7e34e..b8f59159c3e1 100644 --- a/sdk/src/Services/S3/Properties/AssemblyInfo.cs +++ b/sdk/src/Services/S3/Properties/AssemblyInfo.cs @@ -19,6 +19,8 @@ #error Unknown platform constant - unable to set correct AssemblyDescription #endif +[assembly: InternalsVisibleTo("AWSSDK.UnitTests.S3.NetFramework, PublicKey=0024000004800000940000000602000000240000525341310004000001000100db5f59f098d27276c7833875a6263a3cc74ab17ba9a9df0b52aedbe7252745db7274d5271fd79c1f08f668ecfa8eaab5626fa76adc811d3c8fc55859b0d09d3bc0a84eecd0ba891f2b8a2fc55141cdcc37c2053d53491e650a479967c3622762977900eddbf1252ed08a2413f00a28f3a0752a81203f03ccb7f684db373518b4")] +[assembly: InternalsVisibleTo("AWSSDK.UnitTests.NetFramework, PublicKey=0024000004800000940000000602000000240000525341310004000001000100db5f59f098d27276c7833875a6263a3cc74ab17ba9a9df0b52aedbe7252745db7274d5271fd79c1f08f668ecfa8eaab5626fa76adc811d3c8fc55859b0d09d3bc0a84eecd0ba891f2b8a2fc55141cdcc37c2053d53491e650a479967c3622762977900eddbf1252ed08a2413f00a28f3a0752a81203f03ccb7f684db373518b4")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyProduct("Amazon Web Services SDK for .NET")] [assembly: AssemblyCompany("Amazon.com, Inc")] diff --git a/sdk/test/Services/S3/UnitTests/AWSSDK.UnitTests.S3.NetFramework.csproj b/sdk/test/Services/S3/UnitTests/AWSSDK.UnitTests.S3.NetFramework.csproj index 3b58730ba499..9a461a902882 100644 --- a/sdk/test/Services/S3/UnitTests/AWSSDK.UnitTests.S3.NetFramework.csproj +++ b/sdk/test/Services/S3/UnitTests/AWSSDK.UnitTests.S3.NetFramework.csproj @@ -75,5 +75,8 @@ + + + \ No newline at end of file diff --git a/sdk/test/Services/S3/UnitTests/Custom/EmbeddedResource/mapping.json b/sdk/test/Services/S3/UnitTests/Custom/EmbeddedResource/mapping.json new file mode 100644 index 000000000000..224a0a35dfdb --- /dev/null +++ b/sdk/test/Services/S3/UnitTests/Custom/EmbeddedResource/mapping.json @@ -0,0 +1,291 @@ +{ + "Definition": { + "UploadRequest": { + "PutObjectRequest": [ + "ACL", + "Bucket", + "BucketKeyEnabled", + "CacheControl", + "ChecksumAlgorithm", + "ChecksumCRC32", + "ChecksumCRC32C", + "ChecksumCRC64NVME", + "ChecksumSHA1", + "ChecksumSHA256", + "ContentDisposition", + "ContentEncoding", + "ContentLanguage", + "ContentType", + "ExpectedBucketOwner", + "Expires", + "GrantFullControl", + "GrantRead", + "GrantReadACP", + "GrantWriteACP", + "IfMatch", + "IfNoneMatch", + "Key", + "Metadata", + "ObjectLockLegalHoldStatus", + "ObjectLockMode", + "ObjectLockRetainUntilDate", + "RequestPayer", + "SSECustomerAlgorithm", + "SSECustomerKey", + "SSECustomerKeyMD5", + "SSEKMSEncryptionContext", + "SSEKMSKeyId", + "ServerSideEncryption", + "StorageClass", + "Tagging", + "WebsiteRedirectLocation" + ] + }, + "UploadResponse": { + "PutObjectResponse": [ + "BucketKeyEnabled", + "ChecksumCRC32", + "ChecksumCRC32C", + "ChecksumCRC64NVME", + "ChecksumSHA1", + "ChecksumSHA256", + "ChecksumType", + "ETag", + "Expiration", + "RequestCharged", + "SSECustomerAlgorithm", + "SSECustomerKeyMD5", + "SSEKMSEncryptionContext", + "SSEKMSKeyId", + "ServerSideEncryption", + "VersionId" + ] + }, + "DownloadRequest": { + "GetObjectRequest": [ + "Bucket", + "ChecksumMode", + "ExpectedBucketOwner", + "IfMatch", + "IfModifiedSince", + "IfNoneMatch", + "IfUnmodifiedSince", + "Key", + "RequestPayer", + "ResponseCacheControl", + "ResponseContentDisposition", + "ResponseContentEncoding", + "ResponseContentLanguage", + "ResponseContentType", + "ResponseExpires", + "SSECustomerAlgorithm", + "SSECustomerKey", + "SSECustomerKeyMD5", + "VersionId" + ] + }, + "DownloadResponse": { + "GetObjectResponse": [ + "AcceptRanges", + "BucketKeyEnabled", + "CacheControl", + "ChecksumCRC32", + "ChecksumCRC32C", + "ChecksumCRC64NVME", + "ChecksumSHA1", + "ChecksumSHA256", + "ChecksumType", + "ContentDisposition", + "ContentEncoding", + "ContentLanguage", + "ContentLength", + "ContentRange", + "ContentType", + "DeleteMarker", + "ETag", + "Expiration", + "Expires", + "LastModified", + "Metadata", + "MissingMeta", + "ObjectLockLegalHoldStatus", + "ObjectLockMode", + "ObjectLockRetainUntilDate", + "PartsCount", + "ReplicationStatus", + "RequestCharged", + "Restore", + "SSECustomerAlgorithm", + "SSECustomerKeyMD5", + "SSEKMSKeyId", + "ServerSideEncryption", + "StorageClass", + "TagCount", + "VersionId", + "WebsiteRedirectLocation" + ] + } + }, + "Conversion": { + "UploadRequest": { + "PutObjectRequest": [ + "Bucket", + "ChecksumAlgorithm", + "ChecksumCRC32", + "ChecksumCRC32C", + "ChecksumCRC64NVME", + "ChecksumSHA1", + "ChecksumSHA256", + "ExpectedBucketOwner", + "Key", + "RequestPayer", + "SSECustomerAlgorithm", + "SSECustomerKey", + "SSECustomerKeyMD5" + ], + "CreateMultipartRequest": [ + "ACL", + "Bucket", + "BucketKeyEnabled", + "CacheControl", + "ChecksumAlgorithm", + "ContentDisposition", + "ContentEncoding", + "ContentLanguage", + "ContentType", + "ExpectedBucketOwner", + "Expires", + "GrantFullControl", + "GrantRead", + "GrantReadACP", + "GrantWriteACP", + "Key", + "Metadata", + "ObjectLockLegalHoldStatus", + "ObjectLockMode", + "ObjectLockRetainUntilDate", + "RequestPayer", + "SSECustomerAlgorithm", + "SSECustomerKey", + "SSECustomerKeyMD5", + "SSEKMSEncryptionContext", + "SSEKMSKeyId", + "ServerSideEncryption", + "StorageClass", + "Tagging", + "WebsiteRedirectLocation" + ], + "UploadPartRequest": [ + "Bucket", + "ChecksumAlgorithm", + "ExpectedBucketOwner", + "Key", + "RequestPayer", + "SSECustomerAlgorithm", + "SSECustomerKey", + "SSECustomerKeyMD5" + ], + "CompleteMultipartRequest": [ + "Bucket", + "ChecksumCRC32", + "ChecksumCRC32C", + "ChecksumCRC64NVME", + "ChecksumSHA1", + "ChecksumSHA256", + "ExpectedBucketOwner", + "IfMatch", + "IfNoneMatch", + "Key", + "RequestPayer", + "SSECustomerAlgorithm", + "SSECustomerKey", + "SSECustomerKeyMD5" + ], + "AbortMultipartRequest": [ + "Bucket", + "ExpectedBucketOwner", + "Key", + "RequestPayer" + ] + }, + "CompleteMultipartResponse": { + "UploadResponse": [ + "BucketKeyEnabled", + "ChecksumCRC32", + "ChecksumCRC32C", + "ChecksumCRC64NVME", + "ChecksumSHA1", + "ChecksumSHA256", + "ChecksumType", + "ETag", + "Expiration", + "RequestCharged", + "SSEKMSKeyId", + "ServerSideEncryption", + "VersionId" + ] + }, + "PutObjectResponse": { + "UploadResponse": [ + "BucketKeyEnabled", + "ChecksumCRC32", + "ChecksumCRC32C", + "ChecksumCRC64NVME", + "ChecksumSHA1", + "ChecksumSHA256", + "ChecksumType", + "ETag", + "Expiration", + "RequestCharged", + "SSECustomerAlgorithm", + "SSECustomerKeyMD5", + "SSEKMSEncryptionContext", + "SSEKMSKeyId", + "ServerSideEncryption", + "VersionId" + ] + }, + "GetObjectResponse": { + "DownloadResponse": [ + "AcceptRanges", + "BucketKeyEnabled", + "CacheControl", + "ChecksumCRC32", + "ChecksumCRC32C", + "ChecksumCRC64NVME", + "ChecksumSHA1", + "ChecksumSHA256", + "ChecksumType", + "ContentDisposition", + "ContentEncoding", + "ContentLanguage", + "ContentLength", + "ContentRange", + "ContentType", + "DeleteMarker", + "ETag", + "Expiration", + "Expires", + "ExpiresString", + "LastModified", + "Metadata", + "MissingMeta", + "ObjectLockLegalHoldStatus", + "ObjectLockMode", + "ObjectLockRetainUntilDate", + "PartsCount", + "ReplicationStatus", + "RequestCharged", + "Restore", + "SSECustomerAlgorithm", + "SSECustomerKeyMD5", + "SSEKMSKeyId", + "ServerSideEncryption", + "StorageClass", + "TagCount", + "VersionId", + "WebsiteRedirectLocation" + ] + } + } +} \ No newline at end of file diff --git a/sdk/test/Services/S3/UnitTests/Custom/EmbeddedResource/property-aliases.json b/sdk/test/Services/S3/UnitTests/Custom/EmbeddedResource/property-aliases.json new file mode 100644 index 000000000000..245790b5fdbe --- /dev/null +++ b/sdk/test/Services/S3/UnitTests/Custom/EmbeddedResource/property-aliases.json @@ -0,0 +1,10 @@ +{ + "PropertyAliases": { + "SSECustomerAlgorithm": "ServerSideEncryptionCustomerMethod", + "SSECustomerKeyMD5": "ServerSideEncryptionCustomerProvidedKeyMD5", + "SSEKMSKeyId": "ServerSideEncryptionKeyManagementServiceKeyId", + "ServerSideEncryption": "ServerSideEncryptionMethod", + "SSEKMSEncryptionContext": "ServerSideEncryptionKeyManagementServiceEncryptionContext", + "Restore": "RestoreExpiration" + } +} diff --git a/sdk/test/Services/S3/UnitTests/Custom/ResponseMapperTests.cs b/sdk/test/Services/S3/UnitTests/Custom/ResponseMapperTests.cs new file mode 100644 index 000000000000..ce4960e85985 --- /dev/null +++ b/sdk/test/Services/S3/UnitTests/Custom/ResponseMapperTests.cs @@ -0,0 +1,458 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using Amazon.Runtime; +using Amazon.S3; +using Amazon.S3.Model; +using Amazon.S3.Transfer; +using Amazon.S3.Transfer.Internal; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Text.Json; + +namespace AWSSDK.UnitTests +{ + [TestClass] + public class ResponseMapperTests + { + private static JsonDocument _mappingJson; + private static JsonDocument _propertyAliasesJson; + private static Dictionary _propertyAliases; + + [ClassInitialize] + public static void ClassInitialize(TestContext context) + { + // Read mapping.json using robust resource loading (same pattern as Utils.cs) + using (var stream = GetResourceStream("mapping.json")) + { + if (stream == null) + { + throw new FileNotFoundException("Could not find embedded resource: mapping.json"); + } + + using (var reader = new StreamReader(stream)) + { + var jsonContent = reader.ReadToEnd(); + _mappingJson = JsonDocument.Parse(jsonContent); + } + } + + // Read property-aliases.json using robust resource loading + using (var stream = GetResourceStream("property-aliases.json")) + { + if (stream != null) + { + using (var reader = new StreamReader(stream)) + { + var aliasContent = reader.ReadToEnd(); + _propertyAliasesJson = JsonDocument.Parse(aliasContent); + + // Convert to dictionary for fast lookup + _propertyAliases = new Dictionary(); + var aliasesElement = _propertyAliasesJson.RootElement.GetProperty("PropertyAliases"); + foreach (var alias in aliasesElement.EnumerateObject()) + { + _propertyAliases[alias.Name] = alias.Value.GetString(); + } + } + } + else + { + _propertyAliases = new Dictionary(); + } + } + } + + /// + /// Gets embedded resource stream using partial name matching (same pattern as Utils.cs) + /// + private static Stream GetResourceStream(string resourceName) + { + Assembly assembly = Assembly.GetExecutingAssembly(); + var resource = FindResourceName(assembly, resourceName); + if(resource == null) + { + assembly = Assembly.GetCallingAssembly(); + resource = FindResourceName(assembly, resourceName); + } + + return resource != null ? assembly.GetManifestResourceStream(resource) : null; + } + + /// + /// Finds resource name using case-insensitive partial matching (same pattern as Utils.cs) + /// + private static string FindResourceName(Assembly assembly, string partialName) + { + var resources = FindResourceName(assembly, s => s.IndexOf(partialName, StringComparison.OrdinalIgnoreCase) >= 0); + return resources.FirstOrDefault(); + } + + /// + /// Finds resource names matching predicate (same pattern as Utils.cs) + /// + private static IEnumerable FindResourceName(Assembly assembly, Predicate match) + { + var allResources = assembly.GetManifestResourceNames(); + foreach (var resource in allResources) + { + if (match(resource)) + yield return resource; + } + } + + [ClassCleanup] + public static void ClassCleanup() + { + _mappingJson?.Dispose(); + _propertyAliasesJson?.Dispose(); + } + + [TestMethod] + [TestCategory("S3")] + public void MapPutObjectResponse_AllMappedProperties_WorkCorrectly() + { + // Get the expected mappings from JSON + var putObjectMappings = _mappingJson.RootElement + .GetProperty("Conversion") + .GetProperty("PutObjectResponse") + .GetProperty("UploadResponse") + .EnumerateArray() + .Select(prop => prop.GetString()) + .ToList(); + + // Create source object with dynamically generated test data + var sourceResponse = new PutObjectResponse(); + var sourceType = typeof(PutObjectResponse); + var testDataValues = new Dictionary(); + + // Generate test data for each mapped property + foreach (var propertyName in putObjectMappings) + { + // Resolve alias to actual property name + var resolvedPropertyName = ResolvePropertyName(propertyName); + var sourceProperty = sourceType.GetProperty(resolvedPropertyName); + if (sourceProperty?.CanWrite == true) + { + var testValue = GenerateTestValue(sourceProperty.PropertyType, propertyName); + sourceProperty.SetValue(sourceResponse, testValue); + testDataValues[propertyName] = testValue; + } + } + + // Add inherited properties for comprehensive testing + sourceResponse.HttpStatusCode = HttpStatusCode.OK; + sourceResponse.ContentLength = 1024; + + // Map the response + var mappedResponse = ResponseMapper.MapPutObjectResponse(sourceResponse); + Assert.IsNotNull(mappedResponse, "Mapped response should not be null"); + + // Verify all mapped properties using reflection + var targetType = typeof(TransferUtilityUploadResponse); + var failedAssertions = new List(); + + foreach (var propertyName in putObjectMappings) + { + // Resolve alias to actual property name for reflection lookups + var resolvedPropertyName = ResolvePropertyName(propertyName); + var sourceProperty = sourceType.GetProperty(resolvedPropertyName); + var targetProperty = targetType.GetProperty(resolvedPropertyName); + + if (sourceProperty == null) + { + failedAssertions.Add($"Source property '{propertyName}' (resolved to: {resolvedPropertyName}) not found in PutObjectResponse"); + continue; + } + + if (targetProperty == null) + { + failedAssertions.Add($"Target property '{propertyName}' (resolved to: {resolvedPropertyName}) not found in TransferUtilityUploadResponse"); + continue; + } + + var sourceValue = sourceProperty.GetValue(sourceResponse); + var targetValue = targetProperty.GetValue(mappedResponse); + + // Special handling for complex object comparisons + if (!AreValuesEqual(sourceValue, targetValue)) + { + failedAssertions.Add($"{propertyName}: Expected '{sourceValue ?? "null"}', got '{targetValue ?? "null"}'"); + } + } + + // Test inherited properties + Assert.AreEqual(sourceResponse.HttpStatusCode, mappedResponse.HttpStatusCode, "HttpStatusCode should match"); + Assert.AreEqual(sourceResponse.ContentLength, mappedResponse.ContentLength, "ContentLength should match"); + + // Report any failures + if (failedAssertions.Any()) + { + Assert.Fail($"Property mapping failures:\n{string.Join("\n", failedAssertions)}"); + } + } + + [TestMethod] + [TestCategory("S3")] + public void MapPutObjectResponse_NullValues_HandledCorrectly() + { + // Test null handling scenarios + var testCases = new[] + { + // Test null Expiration + new PutObjectResponse { Expiration = null }, + + // Test null enum conversions + new PutObjectResponse { ChecksumType = null, RequestCharged = null, ServerSideEncryptionMethod = null } + }; + + foreach (var testCase in testCases) + { + var mapped = ResponseMapper.MapPutObjectResponse(testCase); + Assert.IsNotNull(mapped, "Response should always be mappable"); + + // Test null handling + if (testCase.Expiration == null) + { + Assert.IsNull(mapped.Expiration, "Null Expiration should map to null"); + } + } + } + + [TestMethod] + [TestCategory("S3")] + public void ValidateTransferUtilityUploadResponseDefinitionCompleteness() + { + ValidateResponseDefinitionCompleteness( + new[] { "Definition", "UploadResponse", "PutObjectResponse" }, + "TransferUtilityUploadResponse"); + } + + [TestMethod] + [TestCategory("S3")] + public void ValidateCompleteMultipartUploadResponseConversionCompleteness() + { + ValidateResponseDefinitionCompleteness( + new[] { "Conversion", "CompleteMultipartResponse", "UploadResponse" }, + "TransferUtilityUploadResponse"); + } + + // Uncomment for DOTNET-8277 + + // [TestMethod] + // [TestCategory("S3")] + // public void ValidatePutObjectRequestDefinitionCompleteness() + // { + // ValidateResponseDefinitionCompleteness( + // new[] { "Definition", "UploadRequest", "PutObjectRequest" }, + // "PutObjectRequest"); + // } + + // [TestMethod] + // [TestCategory("S3")] + // public void ValidateGetObjectRequestDefinitionCompleteness() + // { + // ValidateResponseDefinitionCompleteness( + // new[] { "Definition", "DownloadRequest", "GetObjectRequest" }, + // "GetObjectRequest"); + // } + + // [TestMethod] + // [TestCategory("S3")] + // public void ValidateGetObjectRequestDefinitionCompleteness() + // { + // ValidateResponseDefinitionCompleteness( + // new[] { "Definition", "DownloadRequest", "GetObjectRequest" }, + // "TransferUtilityDownloadRequest"); + // } + + /// + /// Generates appropriate test data for a given property type + /// + private static object GenerateTestValue(Type propertyType, string propertyName) + { + // Handle nullable types + if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + var underlyingType = Nullable.GetUnderlyingType(propertyType); + return GenerateTestValue(underlyingType, propertyName); + } + + // String properties + if (propertyType == typeof(string)) + { + return $"test-{propertyName.ToLower()}"; + } + + // Boolean properties + if (propertyType == typeof(bool)) + { + return true; + } + + // Enum properties + if (propertyType.IsEnum) + { + // For all enums, use the first available value + var enumValues = Enum.GetValues(propertyType); + return enumValues.Length > 0 ? enumValues.GetValue(0) : + throw new InvalidOperationException($"Enum {propertyType.Name} has no values"); + } + + // AWS SDK ConstantClass properties (like ChecksumType, RequestCharged, etc.) + if (typeof(ConstantClass).IsAssignableFrom(propertyType)) + { + // Use reflection to get static readonly fields that are of the same type + var constantFields = propertyType.GetFields(BindingFlags.Public | BindingFlags.Static) + .Where(f => f.IsStatic && f.IsInitOnly && f.FieldType == propertyType); + + var firstConstant = constantFields.FirstOrDefault(); + return firstConstant?.GetValue(null) ?? + throw new InvalidOperationException($"ConstantClass {propertyType.Name} has no static constants"); + } + + // Special object types + if (propertyType == typeof(Expiration)) + { + return new Expiration + { + ExpiryDate = DateTime.UtcNow.AddDays(30), + RuleId = "test-expiration-rule" + }; + } + + // Integer types + if (propertyType == typeof(int) || propertyType == typeof(long)) + { + return 1024; + } + + // For unknown types, throw an exception instead of returning null + // If we've reached this point it means there is an unhandled scenario/missing mapping in our test code that we need to handle. + throw new NotSupportedException( + $"GenerateTestValue does not support type '{propertyType.FullName}' for property '{propertyName}'. " + + $"Please add support for this type to ensure comprehensive test coverage."); + } + + /// + /// Compares two values for equality with special handling for complex objects + /// + private static bool AreValuesEqual(object sourceValue, object targetValue) + { + // Both null + if (sourceValue == null && targetValue == null) + return true; + + // One null, other not + if (sourceValue == null || targetValue == null) + return false; + + // Special handling for Expiration objects + if (sourceValue is Expiration sourceExpiration && targetValue is Expiration targetExpiration) + { + return sourceExpiration.ExpiryDate == targetExpiration.ExpiryDate && + sourceExpiration.RuleId == targetExpiration.RuleId; + } + + // For most cases, use default equality + return sourceValue.Equals(targetValue); + } + + /// + /// Resolves a property name to its actual class property name, checking aliases if needed + /// + private static string ResolvePropertyName(string propertyName) + { + // Check if there's an alias for this property name + if (_propertyAliases.TryGetValue(propertyName, out var aliasedName)) + { + return aliasedName; + } + + // Return the original name if no alias exists + return propertyName; + } + + /// + /// Generic helper method to validate response definition completeness. + /// This method ensures that all properties defined in mapping.json actually exist + /// in the corresponding AWS SDK response classes, supporting property name aliases + /// for backwards compatibility and maintainability. + /// + private static void ValidateResponseDefinitionCompleteness( + string[] jsonPath, + string responseTypeName, + Func> getAdditionalProperties = null) + { + // Get direct properties from response class + var directProperties = typeof(TResponse) + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(p => p.CanRead) + .Select(p => p.Name) + .ToList(); + + // Get additional properties if provided (e.g., HeadersCollection properties) + var additionalProperties = getAdditionalProperties?.Invoke()?.ToList() ?? new List(); + + // Combine direct and additional properties + var actualProperties = directProperties.Union(additionalProperties) + .OrderBy(name => name) + .ToList(); + + // Navigate to the JSON definition using the provided path + var jsonElement = _mappingJson.RootElement; + foreach (var pathSegment in jsonPath) + { + jsonElement = jsonElement.GetProperty(pathSegment); + } + + var definitionProperties = jsonElement + .EnumerateArray() + .Select(prop => prop.GetString()) + .OrderBy(name => name) + .ToList(); + + // Check each definition property, resolving aliases as needed + var extraInDefinition = new List(); + + foreach (var definitionProperty in definitionProperties) + { + var resolvedPropertyName = ResolvePropertyName(definitionProperty); + + // Check if the resolved property name exists in the actual class + if (!actualProperties.Contains(resolvedPropertyName)) + { + extraInDefinition.Add($"{definitionProperty} (resolved to: {resolvedPropertyName})"); + } + } + + // Assert no extra properties + if (extraInDefinition.Any()) + { + var additionalContext = additionalProperties.Any() + ? $" or additional properties" + : ""; + + Assert.Fail($"Definition section contains {extraInDefinition.Count} extra properties that don't exist in the actual {responseTypeName} class{additionalContext}: {string.Join(", ", extraInDefinition)}. " + + $"Please verify they exist in the response class{additionalContext}."); + } + } + } +} diff --git a/sdk/test/UnitTests/AWSSDK.UnitTests.NetFramework.csproj b/sdk/test/UnitTests/AWSSDK.UnitTests.NetFramework.csproj index 5d1c7e6f8ba0..b6f3c88dfff9 100644 --- a/sdk/test/UnitTests/AWSSDK.UnitTests.NetFramework.csproj +++ b/sdk/test/UnitTests/AWSSDK.UnitTests.NetFramework.csproj @@ -84,6 +84,7 @@ + \ No newline at end of file