Skip to content

Commit

Permalink
Merge branch 'main' into bugfix/output-folder-on-generate
Browse files Browse the repository at this point in the history
  • Loading branch information
samwelkanda authored Oct 7, 2024
2 parents 47b99df + 83dbaae commit fca08a7
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 8 deletions.
28 changes: 26 additions & 2 deletions src/Kiota.Builder/Plugins/AuthComparer.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.Plugins.Manifest;

namespace Kiota.Builder.Plugins;
Expand All @@ -12,15 +13,38 @@ internal class AuthComparer(StringComparer? stringComparer = null) : IEqualityCo
public bool Equals(Auth? x, Auth? y)
{
if (x is null || y is null) return object.Equals(x, y);
// TODO: Should we compare the reference id as well?
return _stringComparer.Equals(x.Type, y.Type);
return x switch
{
AnonymousAuth when y is AnonymousAuth => true,
ApiKeyPluginVault x0 when y is ApiKeyPluginVault y0 => _stringComparer.Equals(x0.ReferenceId,
y0.ReferenceId),
OAuthPluginVault x1 when y is OAuthPluginVault y1 => _stringComparer.Equals(x1.ReferenceId, y1.ReferenceId),
EntraOnBehalfOf x2 when y is EntraOnBehalfOf y2 => (x2.Scopes ?? []).SequenceEqual(y2.Scopes ?? []),
_ => false
};
}
/// <inheritdoc/>
public int GetHashCode([DisallowNull] Auth obj)
{
var hash = new HashCode();
if (obj == null) return hash.ToHashCode();
hash.Add(obj.Type, _stringComparer);
switch (obj)
{
case ApiKeyPluginVault o0:
hash.Add(o0.ReferenceId, _stringComparer);
break;
case OAuthPluginVault o1:
hash.Add(o1.ReferenceId, _stringComparer);
break;
case EntraOnBehalfOf o2:
foreach (var scope in o2.Scopes ?? [])
{
hash.Add(scope, _stringComparer);
}
break;
}

return hash.ToHashCode();
}
}
13 changes: 9 additions & 4 deletions src/Kiota.Builder/Plugins/PluginsGenerationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ private static (OpenApiRuntime[], Function[], ConversationStarter[]) GetRuntimes
{
// Configuration overrides document information
Auth = configAuth ?? GetAuth(operation.Security ?? document.SecurityRequirements),
Spec = new OpenApiRuntimeSpec { Url = openApiDocumentPath, },
Spec = new OpenApiRuntimeSpec { Url = openApiDocumentPath },
RunForFunctions = [operation.OperationId]
});

Expand Down Expand Up @@ -387,9 +387,14 @@ private static (OpenApiRuntime[], Function[], ConversationStarter[]) GetRuntimes

private static Auth GetAuth(IList<OpenApiSecurityRequirement> securityRequirements)
{
// Only one security object is allowed
var security = securityRequirements.SingleOrDefault();
var opSecurity = security?.Keys.SingleOrDefault();
// Only one security requirement object is allowed
const string tooManySchemesError = "Multiple security requirements are not supported. Operations can only list one security requirement.";
if (securityRequirements.Count > 1 || securityRequirements.FirstOrDefault()?.Keys.Count > 1)
{
throw new InvalidOperationException(tooManySchemesError);
}
var security = securityRequirements.FirstOrDefault();
var opSecurity = security?.Keys.FirstOrDefault();
return (opSecurity is null || opSecurity.UnresolvedReference) ? new AnonymousAuth() : GetAuthFromSecurityScheme(opSecurity);
}

Expand Down
110 changes: 108 additions & 2 deletions tests/Kiota.Builder.Tests/Plugins/PluginsGenerationServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -276,12 +276,14 @@ public async Task GeneratesManifestAndCleansUpInputDescriptionAsync()
Assert.Single(resultDocument.Paths["/test/{id}"].Operations[OperationType.Get].Extensions); // 1 supported extension still present in operation
}

#region Security

public static TheoryData<string, string, string, PluginAuthConfiguration, Action<DocumentValidationResults<PluginManifestDocument>>>
SecurityInformationSuccess()
{
return new TheoryData<string, string, string, PluginAuthConfiguration, Action<DocumentValidationResults<PluginManifestDocument>>>
{
// security scheme in operation object
// security requirement in operation object
{
"{securitySchemes: {apiKey0: {type: apiKey, name: x-api-key, in: header }}}",
string.Empty, "security: [apiKey0: []]", null, resultingManifest =>
Expand All @@ -295,7 +297,21 @@ public static TheoryData<string, string, string, PluginAuthConfiguration, Action
Assert.Equal("{apiKey0_REGISTRATION_ID}", ((ApiKeyPluginVault)auth0!).ReferenceId);
}
},
// security scheme in root object
// multiple security schemes
{
"{securitySchemes: {apiKey0: {type: apiKey, name: x-api-key0, in: header }, apiKey1: {type: apiKey, name: x-api-key1, in: header }}}",
string.Empty, "security: [apiKey0: []]", null, resultingManifest =>
{
Assert.NotNull(resultingManifest.Document);
Assert.Empty(resultingManifest.Problems);
Assert.NotEmpty(resultingManifest.Document.Runtimes);
var auth0 = resultingManifest.Document.Runtimes[0].Auth;
Assert.IsType<ApiKeyPluginVault>(auth0);
Assert.Equal(AuthType.ApiKeyPluginVault, auth0?.Type);
Assert.Equal("{apiKey0_REGISTRATION_ID}", ((ApiKeyPluginVault)auth0!).ReferenceId);
}
},
// security requirement in root object
// TODO: Revisit when https://github.com/microsoft/OpenAPI.NET/issues/1797 is fixed
// {
// "{securitySchemes: {apiKey0: {type: apiKey, name: x-api-key, in: header }}}",
Expand Down Expand Up @@ -556,6 +572,96 @@ await assertions(async () =>
}
}

[Fact]
public async Task GeneratesManifestWithMultipleSecuritySchemesAsync()
{
var apiDescription = """
openapi: 3.0.0
info:
title: test
version: "1.0"
servers:
- url: https://localhost:8080
paths:
/test:
get:
description: description for test path
responses:
"200":
description: test
security: [{apiKey0: []}]
patch:
description: description for test path
responses:
"200":
description: test
security: [{apiKey1: []}]
components:
{
securitySchemes: {
apiKey0: { type: apiKey, name: x-api-key0, in: header },
apiKey1: { type: apiKey, name: x-api-key1, in: header },
},
}
""";
var workingDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
var simpleDescriptionPath = Path.Combine(workingDirectory) + "description.yaml";
await File.WriteAllTextAsync(simpleDescriptionPath, apiDescription);
var mockLogger = new Mock<ILogger<PluginsGenerationService>>();
var openApiDocumentDs = new OpenApiDocumentDownloadService(_httpClient, mockLogger.Object);
var outputDirectory = Path.Combine(workingDirectory, "output");
var generationConfiguration = new GenerationConfiguration
{
OutputPath = outputDirectory,
OpenAPIFilePath = "openapiPath",
PluginTypes = [PluginType.APIPlugin],
ClientClassName = "client",
ApiRootUrl = "http://localhost/", //Kiota builder would set this for us
};
var (openApiDocumentStream, _) =
await openApiDocumentDs.LoadStreamAsync(simpleDescriptionPath, generationConfiguration, null, false);
var openApiDocument =
await openApiDocumentDs.GetDocumentFromStreamAsync(openApiDocumentStream, generationConfiguration);
Assert.NotNull(openApiDocument);
KiotaBuilder.CleanupOperationIdForPlugins(openApiDocument);
var urlTreeNode = OpenApiUrlTreeNode.Create(openApiDocument, Constants.DefaultOpenApiLabel);

var pluginsGenerationService =
new PluginsGenerationService(openApiDocument, urlTreeNode, generationConfiguration, workingDirectory);
await pluginsGenerationService.GenerateManifestAsync();

Assert.True(File.Exists(Path.Combine(outputDirectory, ManifestFileName)));
Assert.True(File.Exists(Path.Combine(outputDirectory, OpenApiFileName)));

// Validate the v2 plugin
var manifestContent = await File.ReadAllTextAsync(Path.Combine(outputDirectory, ManifestFileName));
using var jsonDocument = JsonDocument.Parse(manifestContent);
var resultingManifest = PluginManifestDocument.Load(jsonDocument.RootElement);

Assert.NotNull(resultingManifest.Document);
Assert.Empty(resultingManifest.Problems);
Assert.NotEmpty(resultingManifest.Document.Runtimes);
var auth0 = resultingManifest.Document.Runtimes[0].Auth;
Assert.IsType<ApiKeyPluginVault>(auth0);
Assert.Equal(AuthType.ApiKeyPluginVault, auth0.Type);
Assert.Equal("{apiKey0_REGISTRATION_ID}", ((ApiKeyPluginVault)auth0!).ReferenceId);
var auth1 = resultingManifest.Document.Runtimes[1].Auth;
Assert.IsType<ApiKeyPluginVault>(auth1);
Assert.Equal(AuthType.ApiKeyPluginVault, auth1.Type);
Assert.Equal("{apiKey1_REGISTRATION_ID}", ((ApiKeyPluginVault)auth1!).ReferenceId);
// Cleanup
try
{
Directory.Delete(outputDirectory);
}
catch (Exception)
{
// ignored
}
}

#endregion

#region Validation

public static TheoryData<string, Action<OpenApiDocument, OpenApiDiagnostic>>
Expand Down

0 comments on commit fca08a7

Please sign in to comment.