Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor tests #29

Merged
merged 13 commits into from
Oct 10, 2023
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"
peombwa marked this conversation as resolved.
Show resolved Hide resolved
./.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