Skip to content

Commit

Permalink
Merge pull request #29 from microsoft/po/refactorTests
Browse files Browse the repository at this point in the history
Refactor tests
  • Loading branch information
peombwa authored Oct 10, 2023
2 parents fab39e2 + ff2841e commit 79fd328
Show file tree
Hide file tree
Showing 34 changed files with 1,218 additions and 436 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/sonarcloud.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ jobs:
CoverletOutputFormat: "opencover" # https://github.com/microsoft/vstest/issues/4014#issuecomment-1307913682
shell: pwsh
run: |
./.sonar/scanner/dotnet-sonarscanner begin /k:"microsoft_OpenApi.ApiManifest" /o:"microsoft" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="src/tests/**/coverage.opencover.xml"
./.sonar/scanner/dotnet-sonarscanner begin /k:"microsoft_OpenApi.ApiManifest" /o:"microsoft" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="tests/**/coverage.opencover.xml"
dotnet build
dotnet test apimanifest.sln --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover
./.sonar/scanner/dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}"
29 changes: 17 additions & 12 deletions apimanifest.sln
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,22 @@ VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{39495B7F-9E1F-4DBE-AAA1-C9C9620675AA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tests", "src\tests\tests.csproj", "{02EFB22C-FF50-4D4C-8F83-A394597E11E6}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "apimanifest", "src\lib\apimanifest.csproj", "{3B4ACF87-6364-48A2-94B8-0EB3201D922E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "apimanifest", "src\lib\apimanifest.csproj", "{3B4ACF87-6364-48A2-94B8-0EB3201D922E}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "tool", "src\tool\tool.csproj", "{DCFFC5B9-253A-4BFE-9CBE-0DAAE822E3EB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tool", "src\tool\tool.csproj", "{DCFFC5B9-253A-4BFE-9CBE-0DAAE822E3EB}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "benchmark", "src\benchmark\benchmark.csproj", "{24B7722C-4FE9-4B52-9AA3-5D2FBCDA2DFA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "benchmark", "src\benchmark\benchmark.csproj", "{24B7722C-4FE9-4B52-9AA3-5D2FBCDA2DFA}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{13E6B8EB-7EA6-4CAD-A9A2-3473307EB30F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiManifest.Tests", "tests\ApiManifest.Tests\ApiManifest.Tests.csproj", "{10411C2B-C1AC-44FC-AF46-E0264438E797}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{02EFB22C-FF50-4D4C-8F83-A394597E11E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{02EFB22C-FF50-4D4C-8F83-A394597E11E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{02EFB22C-FF50-4D4C-8F83-A394597E11E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{02EFB22C-FF50-4D4C-8F83-A394597E11E6}.Release|Any CPU.Build.0 = Release|Any CPU
{3B4ACF87-6364-48A2-94B8-0EB3201D922E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B4ACF87-6364-48A2-94B8-0EB3201D922E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B4ACF87-6364-48A2-94B8-0EB3201D922E}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand All @@ -38,11 +33,21 @@ Global
{24B7722C-4FE9-4B52-9AA3-5D2FBCDA2DFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{24B7722C-4FE9-4B52-9AA3-5D2FBCDA2DFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{24B7722C-4FE9-4B52-9AA3-5D2FBCDA2DFA}.Release|Any CPU.Build.0 = Release|Any CPU
{10411C2B-C1AC-44FC-AF46-E0264438E797}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{10411C2B-C1AC-44FC-AF46-E0264438E797}.Debug|Any CPU.Build.0 = Debug|Any CPU
{10411C2B-C1AC-44FC-AF46-E0264438E797}.Release|Any CPU.ActiveCfg = Release|Any CPU
{10411C2B-C1AC-44FC-AF46-E0264438E797}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{02EFB22C-FF50-4D4C-8F83-A394597E11E6} = {39495B7F-9E1F-4DBE-AAA1-C9C9620675AA}
{3B4ACF87-6364-48A2-94B8-0EB3201D922E} = {39495B7F-9E1F-4DBE-AAA1-C9C9620675AA}
{DCFFC5B9-253A-4BFE-9CBE-0DAAE822E3EB} = {39495B7F-9E1F-4DBE-AAA1-C9C9620675AA}
{24B7722C-4FE9-4B52-9AA3-5D2FBCDA2DFA} = {39495B7F-9E1F-4DBE-AAA1-C9C9620675AA}
{10411C2B-C1AC-44FC-AF46-E0264438E797} = {13E6B8EB-7EA6-4CAD-A9A2-3473307EB30F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6A91121B-DC65-413C-8635-B32B30C30A6F}
EndGlobalSection
EndGlobal
3 changes: 1 addition & 2 deletions src/lib/AccessRequest.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
using Microsoft.OpenApi.ApiManifest.Helpers;
using System.Text.Json;
using System.Text.Json.Nodes;

namespace Microsoft.OpenApi.ApiManifest;

public class AccessRequest
{
// TODO: Add validation. Type is required and is unique for the described API according to RAR - https://www.rfc-editor.org/rfc/rfc9396.
private const string TypeProperty = "type";
// TODO: Rename to 'actions' to match RAR spec.
private const string ContentProperty = "content";

public string? Type { get; set; }
Expand Down
15 changes: 4 additions & 11 deletions src/lib/ApiDependency.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Microsoft.OpenApi.ApiManifest.Helpers;
using System.Text.Json;

namespace Microsoft.OpenApi.ApiManifest;
Expand All @@ -6,11 +7,12 @@ public class ApiDependency
public string? ApiDescriptionUrl { get; set; }
public string? ApiDescriptionVersion { get; set; }
private string? _apiDeploymentBaseUrl;
public string? ApiDeploymentBaseUrl {
public string? ApiDeploymentBaseUrl
{
get { return _apiDeploymentBaseUrl; }
set
{
ValidateApiDeploymentBaseUrl(value);
ValidationHelpers.ValidateBaseUrl(nameof(ApiDeploymentBaseUrl), value);
_apiDeploymentBaseUrl = value;
}
}
Expand Down Expand Up @@ -69,15 +71,6 @@ internal static ApiDependency Load(JsonElement value)
return apiDependency;
}

private static void ValidateApiDeploymentBaseUrl(string? apiDeploymentBaseUrl)
{
// Check if the apiDeploymentBaseUrl is a valid URL and ends in a slash.
if (apiDeploymentBaseUrl == null || !apiDeploymentBaseUrl.EndsWith("/", StringComparison.Ordinal) || !Uri.TryCreate(apiDeploymentBaseUrl, UriKind.Absolute, out _))
{
throw new ArgumentException($"The {nameof(apiDeploymentBaseUrl)} must be a valid URL and end in a slash.");
}
}

// Fixed fieldmap for ApiDependency
private static readonly FixedFieldMap<ApiDependency> handlers = new()
{
Expand Down
6 changes: 3 additions & 3 deletions src/lib/ApiManifestDocument.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json;
using Microsoft.OpenApi.ApiManifest.Helpers;
using System.Text.Json;

namespace Microsoft.OpenApi.ApiManifest;

Expand Down Expand Up @@ -72,8 +73,7 @@ public static ApiManifestDocument Load(JsonElement value)

private static void Validate(string? applicationName)
{
if (string.IsNullOrWhiteSpace(applicationName))
throw new ArgumentNullException(applicationName, String.Format(ErrorMessage.FieldIsRequired, "applicationName", "ApiManifest"));
ValidationHelpers.ValidateNullOrWhitespace(nameof(applicationName), applicationName, nameof(ApiManifestDocument));
}

// Create fixed field map for ApiManifest
Expand Down
2 changes: 1 addition & 1 deletion src/lib/AuthorizationRequirements.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using Microsoft.OpenApi.ApiManifest.Helpers;
using System.Text.Json;

namespace Microsoft.OpenApi.ApiManifest;

public class AuthorizationRequirements
{
public string? ClientIdentifier { get; set; }
// TODO: Confirm the need for AccessReference property. It is not present in the spec.
public List<string>? AccessReference { get; set; }
public List<AccessRequest>? Access { get; set; }

Expand Down
11 changes: 8 additions & 3 deletions src/lib/Constants.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
namespace Microsoft.OpenApi.ApiManifest
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

namespace Microsoft.OpenApi.ApiManifest
{
internal static class Constants
{
Expand All @@ -7,7 +10,9 @@ internal static class Constants

internal static class ErrorMessage
{
public static string FieldIsRequired = "'{0}' is a required property of '{1}'.";
public static string FieldIsNotValid = "'{0}' is not valid.";
public static readonly string FieldIsRequired = "'{0}' is a required property of '{1}'.";
public static readonly string FieldIsNotValid = "'{0}' is not valid.";
public static readonly string FieldLengthExceeded = "'{0}' length exceeded. Maximum length allowed is '{1}'.";
public static readonly string BaseUrlIsNotValid = "The {0} must be a valid URL and end in a slash.";
}
}
5 changes: 4 additions & 1 deletion src/lib/Extensions.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System.Text.Json;
using System.Text.Json.Nodes;

Expand All @@ -14,7 +17,7 @@ public static Extensions Load(JsonElement value)
{
if (property.Value.ValueKind != JsonValueKind.Null)
{
var extensionValue = JsonSerializer.Deserialize<JsonObject>(property.Value.GetRawText());
var extensionValue = JsonSerializer.Deserialize<JsonNode>(property.Value.GetRawText());
extensions.Add(property.Name, extensionValue);
}
}
Expand Down
37 changes: 26 additions & 11 deletions src/lib/ParsingHelpers.cs → src/lib/Helpers/ParsingHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System.Diagnostics;
using System.Text.Json;

namespace Microsoft.OpenApi.ApiManifest;
namespace Microsoft.OpenApi.ApiManifest.Helpers;

internal class ParsingHelpers
internal static class ParsingHelpers
{
public static void ParseMap<T>(JsonElement node, T permissionsDocument, FixedFieldMap<T> handlers)
internal static void ParseMap<T>(JsonElement node, T permissionsDocument, FixedFieldMap<T> handlers)
{
foreach (var element in node.EnumerateObject())
{
if (handlers.TryGetValue(element.Name, out var handler))
{
handler(permissionsDocument, element.Value);
}
//TODO we should log the unknown property or use an additional properties model
};
else
{
// Logs the unknown property. We can switch to additional properties model in the future if need be.
Debug.WriteLine($"Skipped {element.Name}. The property is unknown.");
}
}
}

internal static List<T> GetList<T>(JsonElement v, Func<JsonElement, T> load)
Expand Down Expand Up @@ -46,6 +54,17 @@ internal static SortedDictionary<string, T> GetOrderedMap<T>(JsonElement v, Func
return map;
}

internal static Dictionary<string, string> GetMapOfString(JsonElement v)
{
var map = new Dictionary<string, string>();
foreach (var item in v.EnumerateObject())
{
var value = item.Value.GetString();
map.Add(item.Name, string.IsNullOrWhiteSpace(value) ? string.Empty : value);
}
return map;
}

internal static List<string> GetListOfString(JsonElement v)
{
var list = new List<string>();
Expand Down Expand Up @@ -107,23 +126,19 @@ internal static IEnumerable<KeyValuePair<string, string>> ParseKey(string key)
foreach (var pair in key.Split(';'))
{
if (string.IsNullOrEmpty(pair))
{
continue;
}

var index = pair.IndexOf('=');
if (index == -1)
{
throw new InvalidOperationException($"Unable to parse: {key}. Format is name1=value1;name2=value2;...");
}

var keyValue = new KeyValuePair<string, string>(pair.Substring(0, index), pair.Substring(index + 1));
var keyValue = new KeyValuePair<string, string>(pair[..index], pair[(index + 1)..]);
yield return keyValue;
}
}
}

public class FixedFieldMap<T> : Dictionary<string, Action<T, JsonElement>>
internal class FixedFieldMap<T> : Dictionary<string, Action<T, JsonElement>>
{
public FixedFieldMap() : base(StringComparer.OrdinalIgnoreCase)
{
Expand Down
41 changes: 41 additions & 0 deletions src/lib/Helpers/ValidationHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Text.RegularExpressions;

namespace Microsoft.OpenApi.ApiManifest.Helpers
{
internal static class ValidationHelpers
{
internal static void ValidateNullOrWhitespace(string parameterName, string? value, string parentName)
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentNullException(parameterName, string.Format(ErrorMessage.FieldIsRequired, parameterName, parentName));
}

internal static void ValidateLength(string parameterName, string? value, int maxLength)
{
if (value?.Length > maxLength)
throw new ArgumentOutOfRangeException(parameterName, string.Format(ErrorMessage.FieldLengthExceeded, parameterName, maxLength));
}

internal static void ValidateEmail(string parameterName, string? value, string parentName)
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentNullException(parameterName, string.Format(ErrorMessage.FieldIsRequired, parameterName, parentName));
else
ValidateEmail(parameterName, value);
}

internal static void ValidateBaseUrl(string parameterName, string? baseUrl)
{
// Check if the baseUrl is a valid URL and ends in a slash.
if (string.IsNullOrWhiteSpace(baseUrl) || !baseUrl.EndsWith("/", StringComparison.Ordinal) || !Uri.TryCreate(baseUrl, UriKind.Absolute, out _))
throw new ArgumentException(string.Format(ErrorMessage.BaseUrlIsNotValid, nameof(baseUrl)), parameterName);
}

private static readonly Regex s_emailRegex = new(@"^[^@\s]+@[^@\s]+$", RegexOptions.Compiled, Constants.DefaultRegexTimeout);
private static void ValidateEmail(string parameterName, string value)
{
if (!s_emailRegex.IsMatch(value))
throw new ArgumentException(string.Format(ErrorMessage.FieldIsNotValid, parameterName), parameterName);
}
}
}
41 changes: 34 additions & 7 deletions src/lib/OpenAI/Api.cs
Original file line number Diff line number Diff line change
@@ -1,37 +1,64 @@

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using Microsoft.OpenApi.ApiManifest.Helpers;
using System.Text.Json;

namespace Microsoft.OpenApi.ApiManifest.OpenAI;

public class Api
{
private const string TypeProperty = "type";
private const string UrlProperty = "url";
private const string IsUserAuthenticatedProperty = "is_user_authenticated";
public string? Type { get; set; }
public string? Url { get; set; }
public bool? IsUserAuthenticated { get; set; }

public Api(string type, string url)
{
Type = type;
Url = url;
Validate(this);
}

internal Api(JsonElement value)
{
ParsingHelpers.ParseMap(value, this, handlers);
Validate(this);
}

public static Api Load(JsonElement value)
{
var api = new Api();
var api = new Api(value);
ParsingHelpers.ParseMap(value, api, handlers);
return api;
}

// Create handlers FixedFieldMap for Api
private static readonly FixedFieldMap<Api> handlers = new()
{
{ "type", (o,v) => {o.Type = v.GetString(); } },
{ "url", (o,v) => {o.Url = v.GetString(); } },
{ "is_user_authenticated", (o,v) => {o.IsUserAuthenticated = v.GetBoolean(); }},
{ TypeProperty, (o,v) => {o.Type = v.GetString(); } },
{ UrlProperty, (o,v) => {o.Url = v.GetString(); } },
{ IsUserAuthenticatedProperty, (o,v) => {o.IsUserAuthenticated = v.GetBoolean(); }},
};

public void Write(Utf8JsonWriter writer)
{
Validate(this);
writer.WriteStartObject();
writer.WriteString("type", Type);
writer.WriteString("url", Url);
writer.WriteBoolean("is_user_authenticated", IsUserAuthenticated ?? false);
writer.WriteString(TypeProperty, Type);
writer.WriteString(UrlProperty, Url);
writer.WriteBoolean(IsUserAuthenticatedProperty, IsUserAuthenticated ?? false);
writer.WriteEndObject();
}

private void Validate(Api api)
{
ValidationHelpers.ValidateNullOrWhitespace(nameof(Type), api.Type, nameof(Api));
ValidationHelpers.ValidateNullOrWhitespace(nameof(Url), api.Url, nameof(Api));
}
}


Loading

0 comments on commit 79fd328

Please sign in to comment.