From e6cd0477b14ed34bf5393bc5ed891a794a88c004 Mon Sep 17 00:00:00 2001 From: adaines Date: Thu, 12 Dec 2024 12:11:47 -0500 Subject: [PATCH 1/2] Generator implementation for V2 smoke tests for C2J files. --- .../TestFiles/SmokeTestsV2.partial.cs | 153 ++++++++++++- .../Generators/TestFiles/SmokeTestsV2.tt | 214 ++++++++++++++++-- 2 files changed, 352 insertions(+), 15 deletions(-) diff --git a/generator/ServiceClientGeneratorLib/Generators/TestFiles/SmokeTestsV2.partial.cs b/generator/ServiceClientGeneratorLib/Generators/TestFiles/SmokeTestsV2.partial.cs index 2753fbf64471..e4cd6ba383b6 100644 --- a/generator/ServiceClientGeneratorLib/Generators/TestFiles/SmokeTestsV2.partial.cs +++ b/generator/ServiceClientGeneratorLib/Generators/TestFiles/SmokeTestsV2.partial.cs @@ -8,6 +8,7 @@ namespace ServiceClientGenerator.Generators.TestFiles { public partial class SmokeTestsV2 { + #region Core Configuration Properties public string GetRegion(JsonData testCase) { var config = testCase["config"]; @@ -21,13 +22,161 @@ public string GetRegion(JsonData testCase) return region.ToJson(); } + public string GetUri(JsonData testCase) + { + var config = testCase["config"]; + if (config == null) + return null; + + var uri = config["uri"]; + if (uri == null) + return null; + + return $"\"{uri.ToString()}\""; + } + + #endregion + + #region Endpoint Configuration Properties + public bool? GetUseFipsEndpoint(JsonData testCase) + { + var config = testCase["config"]; + if (config == null) + return null; + + var useFips = config["useFips"]; + if (useFips == null) + return null; + + return (bool)useFips; + } + + public bool? GetUseDualstackEndpoint(JsonData testCase) + { + var config = testCase["config"]; + if (config == null) + return null; + + var useDualstack = config["useDualstack"]; + if (useDualstack == null) + return null; + + return (bool)useDualstack; + } + #endregion + + #region S3-Specific Configuration Properties + public bool? GetUseAccelerateEndpoint(JsonData testCase) + { + var config = testCase["config"]; + if (config == null) + return null; + + var useAccelerate = config["useAccelerate"]; + if (useAccelerate == null) + return null; + + return (bool)useAccelerate; + } + + public bool? GetUseGlobalEndpoint(JsonData testCase) + { + var config = testCase["config"]; + if (config == null) + return null; + + var useGlobalEndpoint = config["useGlobalEndpoint"]; + if (useGlobalEndpoint == null) + return null; + + return (bool)useGlobalEndpoint; + } + + public bool? GetUseArnRegion(JsonData testCase) + { + var config = testCase["config"]; + if (config == null) + return null; + + var useArnRegion = config["useArnRegion"]; + if (useArnRegion == null) + return null; + + return (bool)useArnRegion; + } + + public bool? GetForcePathStyle(JsonData testCase) + { + var config = testCase["config"]; + if (config == null) + return null; + + var forcePathStyle = config["forcePathStyle"]; + if (forcePathStyle == null) + return null; + + return (bool)forcePathStyle; + } + #endregion + + #region Authentication Configuration Properties + public bool? GetUseAccountIdRouting(JsonData testCase) + { + var config = testCase["config"]; + if (config == null) + return null; + + var useAccountIdRouting = config["useAccountIdRouting"]; + if (useAccountIdRouting == null) + return null; + + return (bool)useAccountIdRouting; + } + + public string[] GetSigV4aRegionSet(JsonData testCase) + { + var config = testCase["config"]; + if (config == null) + return null; + + var sigv4aRegionSet = config["sigv4aRegionSet"]; + if (sigv4aRegionSet == null || !sigv4aRegionSet.IsArray) + return null; + + var regions = new List(); + foreach (JsonData region in sigv4aRegionSet) + { + regions.Add(region.ToString()); + } + return regions.ToArray(); + } + #endregion + + #region Test Case Properties + public JsonData GetInput(JsonData testCase) + { + return testCase["input"]; + } + + public bool IsSuccessExpected(JsonData testCase) + { + var expectation = testCase["expectation"]; + return expectation["success"] != null; + } + + public string GetExpectedErrorId(JsonData testCase) + { + var expectation = testCase["expectation"]; + var failure = expectation["failure"]; + return failure?["errorId"]?.ToString(); + } + #endregion + /// /// Finds the operation in the ServiceModel based on the operation name in the smoke2 json file. The /// name in that file does not take any customizations that might have been put in place. So we need to /// compare to the raw ShapeName instead of Name property which has customizations applied. /// - /// - /// private Operation FindOperation(JsonData testCase) { var operationShapeName = testCase["operationName"].ToString(); diff --git a/generator/ServiceClientGeneratorLib/Generators/TestFiles/SmokeTestsV2.tt b/generator/ServiceClientGeneratorLib/Generators/TestFiles/SmokeTestsV2.tt index d19757f11378..c1442c0b50a4 100644 --- a/generator/ServiceClientGeneratorLib/Generators/TestFiles/SmokeTestsV2.tt +++ b/generator/ServiceClientGeneratorLib/Generators/TestFiles/SmokeTestsV2.tt @@ -8,6 +8,7 @@ AddLicenseHeader(); #> using System; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -26,36 +27,223 @@ namespace AWSSDK_DotNet.IntegrationTests.SmokeTestsV2 var modeledOperation = FindOperation(testCase); if (modeledOperation == null) throw new ApplicationException($"Failed to find operation {testCase["operationName"]} for {testCase["id"]} while generating smoke tests for service {this.Config.ServiceModel.ServiceId}"); + + var testTags = testCase["tags"] as JsonData; #> [TestMethod] [TestCategory("SmokeTests")] [TestCategory("<#=this.Config.ServiceModel.ServiceId#>")] - public async Task <#=testCase["id"].ToString()#>() +<# + if (testTags != null && testTags.IsArray) + { + foreach (JsonData tag in testTags) + { + var tagValue = tag?.ToString(); + if (!string.IsNullOrEmpty(tagValue)) + { +#> + [TestCategory("<#=tagValue#>")] +<# + } + } + } +#> + public async Task <#=testCase["id"]?.ToString() ?? "Test"#>() { var serviceConfig = new Amazon<#=this.Config.ClassName#>Config(); - - // TODO: Apply any other config settings required for the service config. <# - if (this.GetRegion(testCase) != null) - { + var region = this.GetRegion(testCase); + if (!string.IsNullOrEmpty(region)) + { +#> + serviceConfig.RegionEndpoint = RegionEndpoint.GetBySystemName(<#=region#>); +<# + } + if (this.GetUseFipsEndpoint(testCase) != null && (bool)this.GetUseFipsEndpoint(testCase)) + { +#> + serviceConfig.UseFIPSEndpoint = true; +<# + } + if (this.GetUseDualstackEndpoint(testCase) != null && (bool)this.GetUseDualstackEndpoint(testCase)) + { +#> + serviceConfig.UseDualstackEndpoint = true; +<# + } + var uri = this.GetUri(testCase); + if (!string.IsNullOrEmpty(uri)) + { +#> + serviceConfig.ServiceURL = <#=uri#>; +<# + } + if (this.GetUseAccelerateEndpoint(testCase) != null) + { +#> + serviceConfig.UseAccelerateEndpoint = <#=this.GetUseAccelerateEndpoint(testCase).ToString().ToLower()#>; +<# + } + if (this.GetUseGlobalEndpoint(testCase) != null) + { +#> + serviceConfig.UseGlobalEndpoint = <#=this.GetUseGlobalEndpoint(testCase).ToString().ToLower()#>; +<# + } + if (this.GetUseArnRegion(testCase) != null) + { +#> + serviceConfig.UseArnRegion = <#=this.GetUseArnRegion(testCase).ToString().ToLower()#>; +<# + } + if (this.GetForcePathStyle(testCase) != null) + { #> - serviceConfig.RegionEndpoint = RegionEndpoint.GetBySystemName(<#=this.GetRegion(testCase)#>); + serviceConfig.ForcePathStyle = <#=this.GetForcePathStyle(testCase).ToString().ToLower()#>; <# - } + } + if (this.GetUseAccountIdRouting(testCase) != null) + { #> + serviceConfig.UseAccountIdRouting = <#=this.GetUseAccountIdRouting(testCase).ToString().ToLower()#>; +<# + } + var sigV4aRegionSet = this.GetSigV4aRegionSet(testCase); + if (sigV4aRegionSet != null && sigV4aRegionSet.Length > 0) + { +#> + serviceConfig.SignatureVersion = "4a"; + serviceConfig.SigningRegion = "<#=string.Join(",", sigV4aRegionSet)#>"; +<# + } +#> + var serviceClient = new Amazon<#=this.Config.ClassName#>Client(serviceConfig); - - // TODO: Add any input from the test case to the request object. var request = new <#=modeledOperation.Name#>Request(); - // TODO: Handle when test cases are testing a failure condition. That means catching the exception and verifing we got the right exception. - var response = await serviceClient.<#=modeledOperation.Name#>Async(request); +<# + var input = testCase["input"] as JsonData; + if (input != null && input.IsObject) + { + foreach (string key in input.PropertyNames) + { + var value = input[key]; + if (value != null) + { + if (value.IsString) + { +#> + request.<#=key#> = "<#=value.ToString()#>"; +<# + } + else if (value.IsInt) + { +#> + request.<#=key#> = (int)<#=value#>; +<# + } + else if (value.IsBoolean) + { +#> + request.<#=key#> = <#=value.ToString().ToLower()#>; +<# + } + else if (value.IsArray) + { + if (key == "Tags") + { +#> + request.Tags = new List(); +<# + foreach (JsonData tag in value) + { + if (tag != null && tag.IsObject && tag["Key"] != null && tag["Value"] != null) + { +#> + request.Tags.Add(new Tag { Key = "<#=tag["Key"]#>", Value = "<#=tag["Value"]#>" }); +<# + } + } + } + else + { + var arrayValues = new List(); + foreach (JsonData item in value) + { + if (item != null && item.IsString) + { + arrayValues.Add($"\"{item.ToString()}\""); + } + } + if (arrayValues.Count > 0) + { +#> + request.<#=key#> = new List { <#=string.Join(", ", arrayValues)#> }; +<# + } + } + } + else if (value.IsObject) + { + if (key == "Includes") + { +#> + request.<#=key#> = new Filters + { +<# + var keyTypes = value["keyTypes"] as JsonData; + if (keyTypes != null && keyTypes.IsArray) + { + var keyTypeValues = new List(); + foreach (JsonData keyType in keyTypes) + { + if (keyType != null && keyType.IsString) + { + keyTypeValues.Add($"\"{keyType.ToString()}\""); + } + } + if (keyTypeValues.Count > 0) + { +#> + KeyTypes = new List { <#=string.Join(", ", keyTypeValues)#> } +<# + } + } +#> + }; +<# + } + } + } + } + } - await Task.CompletedTask; + if (IsSuccessExpected(testCase)) + { +#> + var response = await serviceClient.<#=modeledOperation.Name#>Async(request); +<# + } + else + { + var errorId = GetExpectedErrorId(testCase); +#> + try + { + var response = await serviceClient.<#=modeledOperation.Name#>Async(request); + Assert.Fail("Expected <#=errorId ?? "an error response"#> exception, but the call succeeded."); + } + catch (Amazon<#=this.Config.ClassName#>Exception ex) + { + Assert.AreEqual("<#=errorId#>", ex.ErrorCode); + } +<# + } +#> } <# - } + } #> } } \ No newline at end of file From dcef01bdf41452ee37501e33f5624f9c2c4c93ea Mon Sep 17 00:00:00 2001 From: adaines Date: Tue, 17 Dec 2024 12:40:48 -0500 Subject: [PATCH 2/2] Bug fix for cases of null errorId, small changes to align with SEP. --- .../TestFiles/SmokeTestsV2.partial.cs | 18 ++++ .../Generators/TestFiles/SmokeTestsV2.tt | 88 ++++++++++++++++--- 2 files changed, 92 insertions(+), 14 deletions(-) diff --git a/generator/ServiceClientGeneratorLib/Generators/TestFiles/SmokeTestsV2.partial.cs b/generator/ServiceClientGeneratorLib/Generators/TestFiles/SmokeTestsV2.partial.cs index e4cd6ba383b6..0980c1702722 100644 --- a/generator/ServiceClientGeneratorLib/Generators/TestFiles/SmokeTestsV2.partial.cs +++ b/generator/ServiceClientGeneratorLib/Generators/TestFiles/SmokeTestsV2.partial.cs @@ -182,5 +182,23 @@ private Operation FindOperation(JsonData testCase) var operationShapeName = testCase["operationName"].ToString(); return this.Config.ServiceModel.Operations.FirstOrDefault(x => string.Equals(x.ShapeName, operationShapeName)); } + + /// + /// Finds the proper .NET property name for a given JSON key in the operation's input shape. + /// + private string FindPropertyName(Operation operation, string jsonKey) + { + // Get the input shape for the operation + var inputShape = operation.RequestStructure; + if (inputShape == null) + return jsonKey; + + // Look for a member in the input shape that matches the json key + var member = inputShape.Members.FirstOrDefault(m => + string.Equals(m.MarshallLocationName, jsonKey, StringComparison.OrdinalIgnoreCase)); + + // If found, return the .NET customized name, otherwise return original key + return member?.PropertyName ?? jsonKey; + } } } \ No newline at end of file diff --git a/generator/ServiceClientGeneratorLib/Generators/TestFiles/SmokeTestsV2.tt b/generator/ServiceClientGeneratorLib/Generators/TestFiles/SmokeTestsV2.tt index c1442c0b50a4..bc09eb016de3 100644 --- a/generator/ServiceClientGeneratorLib/Generators/TestFiles/SmokeTestsV2.tt +++ b/generator/ServiceClientGeneratorLib/Generators/TestFiles/SmokeTestsV2.tt @@ -49,7 +49,7 @@ namespace AWSSDK_DotNet.IntegrationTests.SmokeTestsV2 } } #> - public async Task <#=testCase["id"]?.ToString() ?? "Test"#>() + public async Task <#=testCase["id"]#>() { var serviceConfig = new Amazon<#=this.Config.ClassName#>Config(); <# @@ -126,32 +126,33 @@ namespace AWSSDK_DotNet.IntegrationTests.SmokeTestsV2 var input = testCase["input"] as JsonData; if (input != null && input.IsObject) { - foreach (string key in input.PropertyNames) + foreach (string jsonKey in input.PropertyNames) { - var value = input[key]; + var propertyName = FindPropertyName(modeledOperation, jsonKey); + var value = input[jsonKey]; if (value != null) { if (value.IsString) { #> - request.<#=key#> = "<#=value.ToString()#>"; + request.<#=propertyName#> = "<#=value.ToString()#>"; <# } else if (value.IsInt) { #> - request.<#=key#> = (int)<#=value#>; + request.<#=propertyName#> = (int)<#=value#>; <# } else if (value.IsBoolean) { #> - request.<#=key#> = <#=value.ToString().ToLower()#>; + request.<#=propertyName#> = <#=value.ToString().ToLower()#>; <# } else if (value.IsArray) { - if (key == "Tags") + if (propertyName == "Tags") { #> request.Tags = new List(); @@ -168,28 +169,80 @@ namespace AWSSDK_DotNet.IntegrationTests.SmokeTestsV2 } else { - var arrayValues = new List(); + // Check if all items are strings + bool allStrings = true; foreach (JsonData item in value) { - if (item != null && item.IsString) + if (item != null && !item.IsString) { - arrayValues.Add($"\"{item.ToString()}\""); + allStrings = false; + break; } } - if (arrayValues.Count > 0) + + if (allStrings) { #> - request.<#=key#> = new List { <#=string.Join(", ", arrayValues)#> }; + request.<#=propertyName#> = new List + { <# } + else + { +#> + request.<#=propertyName#> = new List + { +<# + } + var isFirst = true; + foreach (JsonData item in value) + { + if (item != null) + { + if (!isFirst) + { +#> + , +<# + } + if (item.IsString) + { +#> + "<#=item.ToString()#>" +<# + } + else if (item.IsInt) + { +#> + <#=item.ToString()#> +<# + } + else if (item.IsBoolean) + { +#> + <#=item.ToString().ToLower()#> +<# + } + else if (item.IsDouble) + { +#> + <#=item.ToString()#> +<# + } + isFirst = false; + } + } +#> + }; +<# } } else if (value.IsObject) { - if (key == "Includes") + if (propertyName == "Includes") { #> - request.<#=key#> = new Filters + request.<#=propertyName#> = new Filters { <# var keyTypes = value["keyTypes"] as JsonData; @@ -236,7 +289,14 @@ namespace AWSSDK_DotNet.IntegrationTests.SmokeTestsV2 } catch (Amazon<#=this.Config.ClassName#>Exception ex) { +<# + if (!string.IsNullOrEmpty(errorId)) + { +#> Assert.AreEqual("<#=errorId#>", ex.ErrorCode); +<# + } +#> } <# }