-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Vincent Biret <[email protected]>
- Loading branch information
Showing
7 changed files
with
571 additions
and
1 deletion.
There are no files selected for viewing
210 changes: 210 additions & 0 deletions
210
CodeSnippetsReflection.OpenAPI.Test/GoGeneratorTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
using System; | ||
using System.Net.Http; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using CodeSnippetsReflection.OpenAPI.LanguageGenerators; | ||
using Microsoft.OpenApi.Services; | ||
using Xunit; | ||
|
||
namespace CodeSnippetsReflection.OpenAPI.Test | ||
{ | ||
public class GoGeneratorTests { | ||
private const string ServiceRootUrl = "https://graph.microsoft.com/v1.0"; | ||
private static OpenApiUrlTreeNode _v1TreeNode; | ||
private static async Task<OpenApiUrlTreeNode> GetV1TreeNode() { | ||
if(_v1TreeNode == null) { | ||
_v1TreeNode = await SnippetModelTests.GetTreeNode("https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml"); | ||
} | ||
return _v1TreeNode; | ||
} | ||
private readonly GoGenerator _generator = new(); | ||
[Fact] | ||
public async Task GeneratesTheCorrectFluentAPIPath() { | ||
using var requestPayload = new HttpRequestMessage(HttpMethod.Get, $"{ServiceRootUrl}/me/messages"); | ||
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode()); | ||
var result = _generator.GenerateCodeSnippet(snippetModel); | ||
Assert.Contains(".Me().Messages()", result); | ||
} | ||
[Fact] | ||
public async Task GeneratesTheCorrectFluentAPIPathForIndexedCollections() { | ||
using var requestPayload = new HttpRequestMessage(HttpMethod.Get, $"{ServiceRootUrl}/me/messages/{{message-id}}"); | ||
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode()); | ||
var result = _generator.GenerateCodeSnippet(snippetModel); | ||
Assert.Contains("messageId := \"message-id\"", result); | ||
Assert.Contains(".Me().MessagesById(&messageId)", result); | ||
} | ||
[Fact] | ||
public async Task GeneratesTheSnippetHeader() { | ||
using var requestPayload = new HttpRequestMessage(HttpMethod.Get, $"{ServiceRootUrl}/me/messages"); | ||
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode()); | ||
var result = _generator.GenerateCodeSnippet(snippetModel); | ||
Assert.Contains("graphClient := msgraphsdk.NewGraphServiceClient(requestAdapter)", result); | ||
} | ||
[Fact] | ||
public async Task GeneratesTheGetMethodCall() { | ||
using var requestPayload = new HttpRequestMessage(HttpMethod.Get, $"{ServiceRootUrl}/me/messages"); | ||
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode()); | ||
var result = _generator.GenerateCodeSnippet(snippetModel); | ||
Assert.Contains("Get", result); | ||
} | ||
[Fact] | ||
public async Task GeneratesThePostMethodCall() { | ||
using var requestPayload = new HttpRequestMessage(HttpMethod.Post, $"{ServiceRootUrl}/me/messages"); | ||
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode()); | ||
var result = _generator.GenerateCodeSnippet(snippetModel); | ||
Assert.Contains("Post", result); | ||
} | ||
[Fact] | ||
public async Task GeneratesThePatchMethodCall() { | ||
using var requestPayload = new HttpRequestMessage(HttpMethod.Patch, $"{ServiceRootUrl}/me/messages/{{message-id}}"); | ||
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode()); | ||
var result = _generator.GenerateCodeSnippet(snippetModel); | ||
Assert.Contains("Patch", result); | ||
} | ||
[Fact] | ||
public async Task GeneratesThePutMethodCall() { | ||
using var requestPayload = new HttpRequestMessage(HttpMethod.Put, $"{ServiceRootUrl}/applications/{{application-id}}/logo"); | ||
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode()); | ||
var result = _generator.GenerateCodeSnippet(snippetModel); | ||
Assert.Contains("Put", result); | ||
} | ||
[Fact] | ||
public async Task GeneratesTheDeleteMethodCall() { | ||
using var requestPayload = new HttpRequestMessage(HttpMethod.Delete, $"{ServiceRootUrl}/me/messages/{{message-id}}"); | ||
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode()); | ||
var result = _generator.GenerateCodeSnippet(snippetModel); | ||
Assert.Contains("Delete", result); | ||
Assert.DoesNotContain("result, err :=", result); | ||
} | ||
[Fact] | ||
public async Task WritesTheRequestPayload() { | ||
const string userJsonObject = "{\r\n \"accountEnabled\": true,\r\n " + | ||
"\"displayName\": \"displayName-value\",\r\n " + | ||
"\"mailNickname\": \"mailNickname-value\",\r\n " + | ||
"\"userPrincipalName\": \"[email protected]\",\r\n " + | ||
" \"passwordProfile\" : {\r\n \"forceChangePasswordNextSignIn\": true,\r\n \"password\": \"password-value\"\r\n }\r\n}";//nested passwordProfile Object | ||
|
||
using var requestPayload = new HttpRequestMessage(HttpMethod.Post, $"{ServiceRootUrl}/users") | ||
{ | ||
Content = new StringContent(userJsonObject, Encoding.UTF8, "application/json") | ||
}; | ||
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode()); | ||
var result = _generator.GenerateCodeSnippet(snippetModel); | ||
Assert.Contains("msgraphsdk.NewUser", result); | ||
Assert.Contains("SetAccountEnabled(true)", result); | ||
Assert.Contains("SetPasswordProfile = new PasswordProfile", result); | ||
Assert.Contains("SetDisplayName(\"displayName-value\")", result); | ||
} | ||
[Fact] | ||
public async Task WritesALongAndFindsAnAction() { | ||
const string userJsonObject = "{\r\n \"chainId\": 10\r\n\r\n}"; | ||
|
||
using var requestPayload = new HttpRequestMessage(HttpMethod.Post, $"{ServiceRootUrl}/teams/{{team-id}}/sendActivityNotification") | ||
{ | ||
Content = new StringContent(userJsonObject, Encoding.UTF8, "application/json") | ||
}; | ||
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode()); | ||
var result = _generator.GenerateCodeSnippet(snippetModel); | ||
Assert.Contains("10L", result); | ||
Assert.DoesNotContain("microsoft.graph", result); | ||
} | ||
[Fact] | ||
public async Task WritesADouble() { | ||
const string userJsonObject = "{\r\n \"minimumAttendeePercentage\": 10\r\n\r\n}"; | ||
|
||
using var requestPayload = new HttpRequestMessage(HttpMethod.Post, $"{ServiceRootUrl}/me/findMeetingTimes") | ||
{ | ||
Content = new StringContent(userJsonObject, Encoding.UTF8, "application/json") | ||
}; | ||
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode()); | ||
var result = _generator.GenerateCodeSnippet(snippetModel); | ||
Assert.Contains("10d", result); | ||
} | ||
[Fact] | ||
public async Task GeneratesABinaryPayload() { | ||
using var requestPayload = new HttpRequestMessage(HttpMethod.Put, $"{ServiceRootUrl}/applications/{{application-id}}/logo") { | ||
Content = new ByteArrayContent(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10 }) | ||
}; | ||
requestPayload.Content.Headers.ContentType = new ("application/octect-stream"); | ||
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode()); | ||
var result = _generator.GenerateCodeSnippet(snippetModel); | ||
Assert.Contains("new MemoryStream", result); | ||
} | ||
[Fact] | ||
public async Task GeneratesABase64UrlPayload() { | ||
const string userJsonObject = "{\r\n \"contentBytes\": \"wiubviuwbegviwubiu\"\r\n\r\n}"; | ||
using var requestPayload = new HttpRequestMessage(HttpMethod.Post, $"{ServiceRootUrl}/chats/{{chat-id}}/messages/{{chatMessage-id}}/hostedContents") { | ||
Content = new StringContent(userJsonObject, Encoding.UTF8, "application/json") | ||
}; | ||
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode()); | ||
var result = _generator.GenerateCodeSnippet(snippetModel); | ||
Assert.Contains("Encoding.ASCII.GetBytes", result); | ||
} | ||
[Fact] | ||
public async Task GeneratesADateTimeOffsetPayload() { | ||
const string userJsonObject = "{\r\n \"receivedDateTime\": \"2021-08-30T20:00:00:00Z\"\r\n\r\n}"; | ||
using var requestPayload = new HttpRequestMessage(HttpMethod.Post, $"{ServiceRootUrl}/me/messages") { | ||
Content = new StringContent(userJsonObject, Encoding.UTF8, "application/json") | ||
}; | ||
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode()); | ||
var result = _generator.GenerateCodeSnippet(snippetModel); | ||
Assert.Contains("DateTimeOffset.Parse", result); | ||
} | ||
[Fact] | ||
public async Task GeneratesAnArrayPayloadInAdditionalData() { | ||
const string userJsonObject = "{\r\n \"[email protected]\": [\r\n \"https://graph.microsoft.com/v1.0/directoryObjects/{id}\",\r\n \"https://graph.microsoft.com/v1.0/directoryObjects/{id}\",\r\n \"https://graph.microsoft.com/v1.0/directoryObjects/{id}\"\r\n ]\r\n}"; | ||
using var requestPayload = new HttpRequestMessage(HttpMethod.Patch, $"{ServiceRootUrl}/groups/{{group-id}}") { | ||
Content = new StringContent(userJsonObject, Encoding.UTF8, "application/json") | ||
}; | ||
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode()); | ||
var result = _generator.GenerateCodeSnippet(snippetModel); | ||
Assert.Contains("new List", result); | ||
Assert.Contains("AdditionalData", result); | ||
Assert.Contains("members", result); // property name hasn't been changed | ||
} | ||
[Fact] | ||
public async Task GeneratesSelectQueryParameters() { | ||
using var requestPayload = new HttpRequestMessage(HttpMethod.Get, $"{ServiceRootUrl}/me?$select=displayName,id"); | ||
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode()); | ||
var result = _generator.GenerateCodeSnippet(snippetModel); | ||
Assert.Contains("displayName", result); | ||
Assert.Contains("(q) =>", result); | ||
} | ||
[Fact] | ||
public async Task GeneratesCountBooleanQueryParameters() { | ||
using var requestPayload = new HttpRequestMessage(HttpMethod.Get, $"{ServiceRootUrl}/users?$count=true&$select=displayName,id"); | ||
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode()); | ||
var result = _generator.GenerateCodeSnippet(snippetModel); | ||
Assert.Contains("displayName", result); | ||
Assert.DoesNotContain("\"true\"", result); | ||
Assert.Contains("true", result); | ||
} | ||
[Fact] | ||
public async Task GeneratesSkipQueryParameters() { | ||
using var requestPayload = new HttpRequestMessage(HttpMethod.Get, $"{ServiceRootUrl}/users?$skip=10"); | ||
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode()); | ||
var result = _generator.GenerateCodeSnippet(snippetModel); | ||
Assert.DoesNotContain("\"10\"", result); | ||
Assert.Contains("10", result); | ||
} | ||
[Fact] | ||
public async Task GeneratesSelectExpandQueryParameters() { | ||
using var requestPayload = new HttpRequestMessage(HttpMethod.Get, $"{ServiceRootUrl}/groups?$expand=members($select=id,displayName)"); | ||
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode()); | ||
var result = _generator.GenerateCodeSnippet(snippetModel); | ||
Assert.Contains("Expand", result); | ||
Assert.Contains("members($select=id,displayName)", result); | ||
Assert.DoesNotContain("Select", result); | ||
} | ||
[Fact] | ||
public async Task GeneratesRequestHeaders() { | ||
using var requestPayload = new HttpRequestMessage(HttpMethod.Get, $"{ServiceRootUrl}/groups"); | ||
requestPayload.Headers.Add("ConsistencyLevel", "eventual"); | ||
var snippetModel = new SnippetModel(requestPayload, ServiceRootUrl, await GetV1TreeNode()); | ||
var result = _generator.GenerateCodeSnippet(snippetModel); | ||
Assert.Contains("Add(\"ConsistencyLevel\", \"eventual\");", result); | ||
Assert.Contains("(h) =>", result); | ||
} | ||
//TODO test for DateTimeOffset | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
CodeSnippetsReflection.OpenAPI/KiotaOpenApiOperationExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// THIS CLASS IS COPIED FROM KIOTA TO GET THE SAME NAMING CONVENTIONS, WE SHOULD FIND A WAY TO MUTUALIZE THE CODE | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using Microsoft.OpenApi.Models; | ||
|
||
namespace CodeSnippetsReflection.OpenAPI { | ||
public static class OpenApiOperationExtensions { | ||
private static readonly HashSet<string> successCodes = new() {"200", "201", "202"}; //204 excluded as it won't have a schema | ||
public static OpenApiSchema GetResponseSchema(this OpenApiOperation operation) | ||
{ | ||
// Return Schema that represents all the possible success responses! | ||
// For the moment assume 200s and application/json | ||
var schemas = operation.Responses.Where(r => successCodes.Contains(r.Key)) | ||
.SelectMany(re => re.Value.Content) | ||
.Where(c => c.Key == "application/json") | ||
.Select(co => co.Value.Schema) | ||
.Where(s => s is not null); | ||
|
||
return schemas.FirstOrDefault(); | ||
} | ||
} | ||
|
||
} |
13 changes: 13 additions & 0 deletions
13
CodeSnippetsReflection.OpenAPI/KiotaOpenApiReferenceExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// THIS CLASS IS COPIED FROM KIOTA TO GET THE SAME NAMING CONVENTIONS, WE SHOULD FIND A WAY TO MUTUALIZE THE CODE | ||
using CodeSnippetsReflection.StringExtensions; | ||
using Microsoft.OpenApi.Models; | ||
|
||
namespace CodeSnippetsReflection.OpenAPI { | ||
public static class OpenApiReferenceExtensions { | ||
public static string GetClassName(this OpenApiReference reference) { | ||
var referenceId = reference?.Id; | ||
return referenceId?[((referenceId?.LastIndexOf('.') ?? 0) + 1)..] | ||
?.ToFirstCharacterUpperCase(); | ||
} | ||
} | ||
} |
59 changes: 59 additions & 0 deletions
59
CodeSnippetsReflection.OpenAPI/KiotaOpenApiUrlTreeNodeExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// ------------------------------------------------------------------------------------------------------------------------------------------------------ | ||
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. | ||
// ------------------------------------------------------------------------------------------------------------------------------------------------------ | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text.RegularExpressions; | ||
using CodeSnippetsReflection.StringExtensions; | ||
using Microsoft.OpenApi.Models; | ||
using Microsoft.OpenApi.Services; | ||
|
||
// THIS CLASS IS COPIED FROM KIOTA TO GET THE SAME NAMING CONVENTIONS, WE SHOULD FIND A WAY TO MUTUALIZE THE CODE | ||
namespace CodeSnippetsReflection.OpenAPI { | ||
|
||
public static class KiotaOpenApiUrlTreeNodeExtensions { | ||
private static readonly Regex PathParametersRegex = new(@"(?:\w+)?=?'?\{(?<paramName>\w+)\}'?,?", RegexOptions.Compiled); | ||
private static readonly char requestParametersChar = '{'; | ||
private static readonly char requestParametersEndChar = '}'; | ||
private static readonly char requestParametersSectionChar = '('; | ||
private static readonly char requestParametersSectionEndChar = ')'; | ||
private static readonly MatchEvaluator requestParametersMatchEvaluator = (match) => { | ||
return "With" + match.Groups["paramName"].Value.ToFirstCharacterUpperCase(); | ||
}; | ||
private static readonly Regex idClassNameCleanup = new(@"Id\d?$", RegexOptions.Compiled); | ||
///<summary> | ||
/// Returns the class name for the node with more or less precision depending on the provided arguments | ||
///</summary> | ||
public static string GetClassName(this OpenApiUrlTreeNode currentNode, string suffix = default, string prefix = default, OpenApiOperation operation = default) { | ||
var rawClassName = (operation?.GetResponseSchema()?.Reference?.GetClassName() ?? | ||
CleanupParametersFromPath(currentNode.Segment)?.ReplaceValueIdentifier()) | ||
.TrimEnd(requestParametersEndChar) | ||
.TrimStart(requestParametersChar) | ||
.TrimStart('$') //$ref from OData | ||
.Split('-') | ||
.First(); | ||
if((currentNode?.DoesNodeBelongToItemSubnamespace() ?? false) && idClassNameCleanup.IsMatch(rawClassName)) | ||
rawClassName = idClassNameCleanup.Replace(rawClassName, string.Empty); | ||
return prefix + rawClassName?.Split('.', StringSplitOptions.RemoveEmptyEntries)?.LastOrDefault() + suffix; | ||
} | ||
public static bool DoesNodeBelongToItemSubnamespace(this OpenApiUrlTreeNode currentNode) => currentNode.IsPathSegmentWithSingleSimpleParameter(); | ||
public static bool IsPathSegmentWithSingleSimpleParameter(this OpenApiUrlTreeNode currentNode) => | ||
currentNode?.Segment.IsPathSegmentWithSingleSimpleParameter() ?? false; | ||
private static bool IsPathSegmentWithSingleSimpleParameter(this string currentSegment) | ||
{ | ||
return (currentSegment?.StartsWith(requestParametersChar) ?? false) && | ||
currentSegment.EndsWith(requestParametersEndChar) && | ||
currentSegment.Count(x => x == requestParametersChar) == 1; | ||
} | ||
private static string CleanupParametersFromPath(string pathSegment) { | ||
if((pathSegment?.Contains(requestParametersChar) ?? false) || | ||
(pathSegment?.Contains(requestParametersSectionChar) ?? false)) | ||
return PathParametersRegex.Replace(pathSegment, requestParametersMatchEvaluator) | ||
.TrimEnd(requestParametersSectionEndChar) | ||
.Replace(requestParametersSectionChar.ToString(), string.Empty); | ||
return pathSegment; | ||
} | ||
} | ||
} |
Oops, something went wrong.