Skip to content
This repository was archived by the owner on Feb 23, 2024. It is now read-only.

Commit 5282cbd

Browse files
f-alizadaFarhad Alizada
andauthored
Sanitize apiOpretaion representation example values for arm template (#789)
Co-authored-by: Farhad Alizada <falizada@microsoft.com>
1 parent f4d5f3a commit 5282cbd

File tree

8 files changed

+337
-2
lines changed

8 files changed

+337
-2
lines changed

src/ArmTemplates/Common/API/Clients/ApiOperations/ApiOperationClient.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,20 @@
1010
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Constants;
1111
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.ApiOperations;
1212
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Extractor.Models;
13+
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Extractor.Utilities.DataProcessors.Absctraction;
1314

1415
namespace Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.API.Clients.ApiOperations
1516
{
1617
public class ApiOperationClient : ApiClientBase, IApiOperationClient
1718
{
1819
const string GetOperationsLinkedToApiRequest = "{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.ApiManagement/service/{3}/apis/{4}/operations?api-version={5}";
1920

20-
public ApiOperationClient(IHttpClientFactory httpClientFactory) : base(httpClientFactory)
21+
readonly IApiOperationDataProcessor apiOperationDataProcessor;
22+
public ApiOperationClient(
23+
IHttpClientFactory httpClientFactory,
24+
IApiOperationDataProcessor apiOperationDataProcessor) : base(httpClientFactory)
2125
{
26+
this.apiOperationDataProcessor = apiOperationDataProcessor;
2227
}
2328

2429
public async Task<List<ApiOperationTemplateResource>> GetOperationsLinkedToApiAsync(string apiName, ExtractorParameters extractorParameters)
@@ -28,7 +33,9 @@ public async Task<List<ApiOperationTemplateResource>> GetOperationsLinkedToApiAs
2833
string requestUrl = string.Format(GetOperationsLinkedToApiRequest,
2934
this.BaseUrl, azSubId, extractorParameters.ResourceGroup, extractorParameters.SourceApimName, apiName, GlobalConstants.ApiVersion);
3035

31-
return await this.GetPagedResponseAsync<ApiOperationTemplateResource>(azToken, requestUrl);
36+
var apiOperations = await this.GetPagedResponseAsync<ApiOperationTemplateResource>(azToken, requestUrl);
37+
this.apiOperationDataProcessor.ProcessData(apiOperations, extractorParameters);
38+
return apiOperations;
3239
}
3340
}
3441
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// --------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
// Licensed under the MIT License.
4+
// --------------------------------------------------------------------------
5+
6+
using System.Collections.Generic;
7+
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.ApiOperations;
8+
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Extractor.Models;
9+
10+
namespace Microsoft.Azure.Management.ApiManagement.ArmTemplates.Extractor.Utilities.DataProcessors.Absctraction
11+
{
12+
public interface IApiOperationDataProcessor
13+
{
14+
void ProcessData(List<ApiOperationTemplateResource> templates, ExtractorParameters extractorParameters);
15+
}
16+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// --------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
// Licensed under the MIT License.
4+
// --------------------------------------------------------------------------
5+
6+
using System.Collections.Generic;
7+
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Extensions;
8+
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.ApiOperations;
9+
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Extractor.Models;
10+
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Extractor.Utilities.DataProcessors.Absctraction;
11+
12+
namespace Microsoft.Azure.Management.ApiManagement.ArmTemplates.Extractor.Utilities.DataProcessors
13+
{
14+
public class ApiOperationDataProcessor : IApiOperationDataProcessor
15+
{
16+
public IDictionary<string, string> OverrideRules { get; }
17+
18+
public void ProcessData(List<ApiOperationTemplateResource> apiOperationTemplateResources, ExtractorParameters extractorParameters)
19+
{
20+
if (apiOperationTemplateResources.IsNullOrEmpty())
21+
{
22+
return;
23+
}
24+
25+
foreach (var apiOperationTemplate in apiOperationTemplateResources)
26+
{
27+
apiOperationTemplate.OriginalName = apiOperationTemplate.Name;
28+
this.SanitazeApiOperationRepresantations(apiOperationTemplate);
29+
}
30+
}
31+
32+
void SanitizeExampleValues(ApiOperationRepresentation representation)
33+
{
34+
if (representation?.Examples.IsNullOrEmpty() == false)
35+
{
36+
foreach (var exampleValue in representation.Examples.Values)
37+
{
38+
// Documentation on escaping characters https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-expressions#escape-characters
39+
if (exampleValue.Value.GetType() == typeof(string) && exampleValue.Value.ToString().StartsWith("[") && exampleValue.Value.ToString().EndsWith("]"))
40+
{
41+
exampleValue.Value = $"[{exampleValue.Value}";
42+
}
43+
}
44+
}
45+
}
46+
47+
void SanitazeApiOperationRepresantations(ApiOperationTemplateResource apiOperation)
48+
{
49+
if (apiOperation.Properties?.Request?.Representations?.IsNullOrEmpty() == false)
50+
{
51+
foreach (var requestRepresentation in apiOperation.Properties.Request.Representations)
52+
{
53+
this.SanitizeExampleValues(requestRepresentation);
54+
}
55+
}
56+
57+
58+
if (apiOperation.Properties?.Responses?.IsNullOrEmpty() == false)
59+
{
60+
foreach (var operationResponse in apiOperation.Properties?.Responses)
61+
{
62+
if (operationResponse?.Representations?.IsNullOrEmpty() == false)
63+
{
64+
foreach (var responseRepresentation in operationResponse.Representations)
65+
{
66+
this.SanitizeExampleValues(responseRepresentation);
67+
}
68+
}
69+
}
70+
}
71+
}
72+
}
73+
}

src/ArmTemplates/ServiceExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ static void SetupDataProcessors(IServiceCollection services)
7878
services.AddScoped<IOpenIdConnectProviderProcessor, OpenIdConnectProviderProcessor>();
7979
services.AddScoped<IPolicyFragmentDataProcessor, PolicyFragmentDataProcessor>();
8080
services.AddScoped<IApiDataProcessor, ApiDataProcessor>();
81+
services.AddScoped<IApiOperationDataProcessor, ApiOperationDataProcessor>();
8182
}
8283

8384
static void SetupCommands(IServiceCollection services)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// --------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
// Licensed under the MIT License.
4+
// --------------------------------------------------------------------------
5+
6+
using System.IO;
7+
using System.Linq;
8+
using System.Threading.Tasks;
9+
using FluentAssertions;
10+
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Extractor.EntityExtractors;
11+
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Extractor.Models;
12+
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Tests.Extractor.Abstractions;
13+
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Tests.Moqs.ApiClients;
14+
using Xunit;
15+
16+
namespace Microsoft.Azure.Management.ApiManagement.ArmTemplates.Tests.Extractor.Scenarios
17+
{
18+
[Trait("Category", "Api operations Extraction")]
19+
public class ApiOperationExtractorTests : ExtractorMockerWithOutputTestsBase
20+
{
21+
public ApiOperationExtractorTests() : base("api-operations-tests")
22+
{
23+
}
24+
25+
[Fact]
26+
public async Task GenerateApiOperationsResourcesAsync_ProperlyParsesResponse()
27+
{
28+
// arrange
29+
var extractorConfig = this.GetDefaultExtractorConsoleAppConfiguration(
30+
apiName: "apiName");
31+
var extractorParameters = new ExtractorParameters(extractorConfig);
32+
var fileLocation = Path.Combine(MockClientUtils.ApiClientJsonResponsesPath, "ApiManagementListApiOperations_success_response.json");
33+
var mockedClient = await MockApiOperationClient.GetMockedHttpApiOperationClient(fileLocation);
34+
var apiOperationExtractor = new ApiOperationExtractor(this.GetTestLogger<ApiOperationExtractor>(), mockedClient);
35+
36+
// act
37+
var apiOperations = await apiOperationExtractor.GenerateApiOperationsResourcesAsync("apiName", extractorParameters);
38+
39+
// assert
40+
apiOperations.Count.Should().Be(5);
41+
42+
var submitOrderOperation = apiOperations.First(x => x.Properties.DisplayName.Equals("submitOrder"));
43+
44+
var exampleDefault = submitOrderOperation.Properties.Request.Representations[0].Examples["default"];
45+
exampleDefault.Value.Should().Be("[[{\"key\":\"value\"}]");
46+
}
47+
}
48+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// --------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
// Licensed under the MIT License.
4+
// --------------------------------------------------------------------------
5+
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using FluentAssertions;
9+
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Constants;
10+
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.ApiOperations;
11+
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Extractor.Utilities.DataProcessors;
12+
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Tests.Extractor.Abstractions;
13+
using Xunit;
14+
15+
namespace Microsoft.Azure.Management.ApiManagement.ArmTemplates.Tests.Extractor.Utilities
16+
{
17+
public class ApiOperationDataProcessorTest : ExtractorMockerTestsBase
18+
{
19+
public List<ApiOperationTemplateResource> GetMockedApiOperationTemplates() {
20+
return new List<ApiOperationTemplateResource>
21+
{
22+
new ApiOperationTemplateResource
23+
{
24+
Name = "api-operation-1",
25+
Type = ResourceTypeConstants.APIOperation,
26+
Properties = new ApiOperationProperties
27+
{
28+
DisplayName = "operation-1",
29+
Description = "operation 1 description",
30+
Request = new ApiOperationRequest
31+
{
32+
Representations = new ApiOperationRepresentation[]
33+
{
34+
new ApiOperationRepresentation
35+
{
36+
Examples = new Dictionary<string, ParameterExampleContract>
37+
{
38+
{ "default", new ParameterExampleContract {
39+
Value = "[{\"example-key\": \"example-value\"}]"
40+
} },
41+
{ "non-default", new ParameterExampleContract {
42+
Value = "[{\"example-key\": \"example-value\"}]"
43+
} }
44+
}
45+
}
46+
}
47+
},
48+
Responses = new ApiOperationResponse[]
49+
{
50+
new ApiOperationResponse{
51+
Representations = new ApiOperationRepresentation[]
52+
{
53+
new ApiOperationRepresentation
54+
{
55+
Examples = new Dictionary<string, ParameterExampleContract>
56+
{
57+
{ "default", new ParameterExampleContract {
58+
Value = "[{\"example-key\": \"example-value\"}]"
59+
} },
60+
{ "non-default", new ParameterExampleContract {
61+
Value = "[{\"example-key\": \"example-value\"}] test"
62+
} }
63+
}
64+
}
65+
}
66+
}
67+
}
68+
}
69+
}
70+
};
71+
}
72+
73+
[Fact]
74+
public void TestApiOperationDataProcess()
75+
{
76+
var apiOperationTemplates = this.GetMockedApiOperationTemplates();
77+
var apiOperationDataProcessor = new ApiOperationDataProcessor();
78+
79+
apiOperationDataProcessor.ProcessData(apiOperationTemplates, default);
80+
81+
apiOperationTemplates.Count.Should().Be(1);
82+
apiOperationTemplates[0].Properties.Responses.Count().Should().Be(1);
83+
84+
foreach (var response in apiOperationTemplates[0].Properties.Responses)
85+
{
86+
this.ValidateSanitizedExampleValue(response.Representations);
87+
}
88+
this.ValidateSanitizedExampleValue(apiOperationTemplates[0].Properties.Request.Representations);
89+
}
90+
91+
void ValidateSanitizedExampleValue(ApiOperationRepresentation[] representations)
92+
{
93+
foreach (var representation in representations)
94+
{
95+
foreach (var exampleValue in representation.Examples.Values)
96+
{
97+
if (exampleValue.Value.GetType() == typeof(string))
98+
{
99+
var defaultRepresentationValuess = (string)exampleValue.Value;
100+
if (defaultRepresentationValuess.EndsWith("]"))
101+
{
102+
defaultRepresentationValuess.StartsWith("[[").Should().BeTrue();
103+
}
104+
}
105+
}
106+
}
107+
}
108+
}
109+
}

tests/ArmTemplates.Tests/Moqs/ApiClients/MockApiOperationClient.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44
// --------------------------------------------------------------------------
55

66
using System.Collections.Generic;
7+
using System.Threading.Tasks;
78
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.API.Clients.Abstractions;
9+
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.API.Clients.ApiOperations;
810
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Constants;
911
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.ApiOperations;
1012
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Extractor.Models;
13+
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Extractor.Utilities.DataProcessors;
1114
using Moq;
1215

1316
namespace Microsoft.Azure.Management.ApiManagement.ArmTemplates.Tests.Moqs.ApiClients
@@ -104,5 +107,14 @@ public static IApiOperationClient GetMockedApiClientWithDefaultValues()
104107

105108
return mockApiOperationClient.Object;
106109
}
110+
111+
public static async Task<IApiOperationClient> GetMockedHttpApiOperationClient(string responseFileLocation)
112+
{
113+
var dataProcessor = new ApiOperationDataProcessor();
114+
var mockedClient = new Mock<ApiOperationClient>(MockBehavior.Strict, await MockClientUtils.GenerateMockedIHttpClientFactoryWithResponse(responseFileLocation), dataProcessor);
115+
MockClientUtils.MockAuthOfApiClient(mockedClient);
116+
117+
return mockedClient.Object;
118+
}
107119
}
108120
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
{
2+
"value": [
3+
{
4+
"id": "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.ApiManagement/service/apimService1/apis/57d2ef278aa04f0888cba3f3/operations/57d2ef278aa04f0ad01d6cdc",
5+
"type": "Microsoft.ApiManagement/service/apis/operations",
6+
"name": "57d2ef278aa04f0ad01d6cdc",
7+
"properties": {
8+
"displayName": "CancelOrder",
9+
"method": "POST",
10+
"urlTemplate": "/?soapAction=http://tempuri.org/IFazioService/CancelOrder"
11+
}
12+
},
13+
{
14+
"id": "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.ApiManagement/service/apimService1/apis/57d2ef278aa04f0888cba3f3/operations/57d2ef278aa04f0ad01d6cda",
15+
"type": "Microsoft.ApiManagement/service/apis/operations",
16+
"name": "57d2ef278aa04f0ad01d6cda",
17+
"properties": {
18+
"displayName": "GetMostRecentOrder",
19+
"method": "POST",
20+
"urlTemplate": "/?soapAction=http://tempuri.org/IFazioService/GetMostRecentOrder"
21+
}
22+
},
23+
{
24+
"id": "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.ApiManagement/service/apimService1/apis/57d2ef278aa04f0888cba3f3/operations/57d2ef278aa04f0ad01d6cd9",
25+
"type": "Microsoft.ApiManagement/service/apis/operations",
26+
"name": "57d2ef278aa04f0ad01d6cd9",
27+
"properties": {
28+
"displayName": "GetOpenOrders",
29+
"method": "POST",
30+
"urlTemplate": "/?soapAction=http://tempuri.org/IFazioService/GetOpenOrders"
31+
}
32+
},
33+
{
34+
"id": "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.ApiManagement/service/apimService1/apis/57d2ef278aa04f0888cba3f3/operations/57d2ef278aa04f0ad01d6cdb",
35+
"type": "Microsoft.ApiManagement/service/apis/operations",
36+
"name": "57d2ef278aa04f0ad01d6cdb",
37+
"properties": {
38+
"displayName": "GetOrder",
39+
"method": "POST",
40+
"urlTemplate": "/?soapAction=http://tempuri.org/IFazioService/GetOrder"
41+
}
42+
},
43+
{
44+
"id": "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.ApiManagement/service/apimService1/apis/57d2ef278aa04f0888cba3f3/operations/57d2ef278aa04f0ad01d6cd8",
45+
"type": "Microsoft.ApiManagement/service/apis/operations",
46+
"name": "57d2ef278aa04f0ad01d6cd8",
47+
"properties": {
48+
"displayName": "submitOrder",
49+
"method": "POST",
50+
"urlTemplate": "/?soapAction=http://tempuri.org/IFazioService/submitOrder",
51+
"request": {
52+
"description": "description",
53+
"representations": [
54+
{
55+
"contentType": "application/json",
56+
"examples": {
57+
"default": {
58+
"description": "exampleDescription",
59+
"value": "[{\"key\":\"value\"}]"
60+
}
61+
}
62+
}
63+
]
64+
}
65+
}
66+
}
67+
],
68+
"count": 5
69+
}

0 commit comments

Comments
 (0)