diff --git a/src/AzureOpenAIProxy.ApiApp/Endpoints/PlaygroundEndpointUrls.cs b/src/AzureOpenAIProxy.ApiApp/Endpoints/PlaygroundEndpointUrls.cs index b8ccbd2b..13faee0e 100644 --- a/src/AzureOpenAIProxy.ApiApp/Endpoints/PlaygroundEndpointUrls.cs +++ b/src/AzureOpenAIProxy.ApiApp/Endpoints/PlaygroundEndpointUrls.cs @@ -12,4 +12,12 @@ public static class PlaygroundEndpointUrls /// - GET method for listing all events /// public const string Events = "/events"; + + /// + /// Declares the deployment models list endpoint. + /// + /// + /// - GET method for listing all deployment models + /// + public const string DeploymentModels = "/events/{eventId}/deployment-models"; } diff --git a/src/AzureOpenAIProxy.ApiApp/Endpoints/PlaygroundEndpoints.cs b/src/AzureOpenAIProxy.ApiApp/Endpoints/PlaygroundEndpoints.cs index c21d6029..91c01b8f 100644 --- a/src/AzureOpenAIProxy.ApiApp/Endpoints/PlaygroundEndpoints.cs +++ b/src/AzureOpenAIProxy.ApiApp/Endpoints/PlaygroundEndpoints.cs @@ -1,3 +1,5 @@ +using Microsoft.AspNetCore.Mvc; + namespace AzureOpenAIProxy.ApiApp.Endpoints; /// @@ -32,4 +34,36 @@ public static RouteHandlerBuilder AddListEvents(this WebApplication app) return builder; } + + + /// + /// Adds the get deployment models + /// + /// instance. + /// Returns instance. + public static RouteHandlerBuilder AddListDeploymentModels(this WebApplication app) + { + // Todo: Issue #170 https://github.com/aliencube/azure-openai-sdk-proxy/issues/170 + var builder = app.MapGet(PlaygroundEndpointUrls.DeploymentModels, ( + [FromRoute] string eventId + ) => + { + return Results.Ok(); + }) + .Produces>(statusCode: StatusCodes.Status200OK, contentType: "application/json") + .Produces(statusCode: StatusCodes.Status401Unauthorized) + .Produces(statusCode: StatusCodes.Status404NotFound) + .Produces(statusCode: StatusCodes.Status500InternalServerError, contentType: "text/plain") + .WithTags("events") + .WithName("GetDeploymentModels") + .WithOpenApi(operation => + { + operation.Summary = "Gets all deployment models"; + operation.Description = "This endpoint gets all deployment models avaliable"; + + return operation; + }); + + return builder; + } } \ No newline at end of file diff --git a/src/AzureOpenAIProxy.ApiApp/Models/DeploymentModelDetails.cs b/src/AzureOpenAIProxy.ApiApp/Models/DeploymentModelDetails.cs new file mode 100644 index 00000000..ab2ef317 --- /dev/null +++ b/src/AzureOpenAIProxy.ApiApp/Models/DeploymentModelDetails.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; + +/// +/// This represent the event detail data for response by admin event endpoint. +/// +public class DeploymentModelDetails +{ + /// + /// Gets or sets the deployment model name. + /// + [JsonRequired] + public string Name { get; set; } = string.Empty; + +} \ No newline at end of file diff --git a/src/AzureOpenAIProxy.ApiApp/Program.cs b/src/AzureOpenAIProxy.ApiApp/Program.cs index 17d22e2e..f35b83ee 100644 --- a/src/AzureOpenAIProxy.ApiApp/Program.cs +++ b/src/AzureOpenAIProxy.ApiApp/Program.cs @@ -57,6 +57,7 @@ // Playground endpoints app.AddListEvents(); +app.AddListDeploymentModels(); // Admin endpoints app.AddNewAdminEvent(); diff --git a/test/AzureOpenAIProxy.AppHost.Tests/ApiApp/Endpoints/GetDeploymentModelsOpenApiTest.cs b/test/AzureOpenAIProxy.AppHost.Tests/ApiApp/Endpoints/GetDeploymentModelsOpenApiTest.cs new file mode 100644 index 00000000..51e72085 --- /dev/null +++ b/test/AzureOpenAIProxy.AppHost.Tests/ApiApp/Endpoints/GetDeploymentModelsOpenApiTest.cs @@ -0,0 +1,230 @@ +using System.Text.Json; + +using AzureOpenAIProxy.AppHost.Tests.Fixtures; + +using FluentAssertions; + +using IdentityModel.Client; + + +namespace AzureOpenAIProxy.AppHost.Tests.ApiApp.Endpoints; + +public class GetDeploymentModelsOpenApiTests(AspireAppHostFixture host) : IClassFixture +{ + [Fact] + public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Path() + { + // Arrange + using var httpClient = host.App!.CreateHttpClient("apiapp"); + + // Act + var json = await httpClient.GetStringAsync("/swagger/v1.0.0/swagger.json"); + var apiDocument = JsonSerializer.Deserialize(json); + + // Assert + var result = apiDocument!.RootElement.GetProperty("paths") + .TryGetProperty("/events/{eventId}/deployment-models", out var property) ? property : default; + result.ValueKind.Should().Be(JsonValueKind.Object); + } + + [Fact] + public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Verb() + { + // Arrange + using var httpClient = host.App!.CreateHttpClient("apiapp"); + + // Act + var json = await httpClient.GetStringAsync("/swagger/v1.0.0/swagger.json"); + var apiDocument = JsonSerializer.Deserialize(json); + + // Assert + var result = apiDocument!.RootElement.GetProperty("paths") + .GetProperty("/events/{eventId}/deployment-models") + .TryGetProperty("get", out var property) ? property : default; + result.ValueKind.Should().Be(JsonValueKind.Object); + } + + [Theory] + [InlineData("events")] + public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Tags(string tag) + { + // Arrange + using var httpClient = host.App!.CreateHttpClient("apiapp"); + + // Act + var json = await httpClient.GetStringAsync("/swagger/v1.0.0/swagger.json"); + var apiDocument = JsonSerializer.Deserialize(json); + + // Assert + var result = apiDocument!.RootElement.GetProperty("paths") + .GetProperty("/events/{eventId}/deployment-models") + .GetProperty("get") + .TryGetProperty("tags", out var property) ? property : default; + result.ValueKind.Should().Be(JsonValueKind.Array); + result.EnumerateArray().Select(p => p.GetString()).Should().Contain(tag); + } + + [Theory] + [InlineData("summary")] + [InlineData("description")] + [InlineData("operationId")] + public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Value(string attribute) + { + // Arrange + using var httpClient = host.App!.CreateHttpClient("apiapp"); + + // Act + var json = await httpClient.GetStringAsync("/swagger/v1.0.0/swagger.json"); + var apiDocument = JsonSerializer.Deserialize(json); + + // Assert + var result = apiDocument!.RootElement.GetProperty("paths") + .GetProperty("/events/{eventId}/deployment-models") + .GetProperty("get") + .TryGetProperty(attribute, out var property) ? property : default; + result.ValueKind.Should().Be(JsonValueKind.String); + } + + + [Theory] + [InlineData("eventId")] + public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Path_Parameter(string name) + { + // Arrange + using var httpClient = host.App!.CreateHttpClient("apiapp"); + + // Act + var json = await httpClient.GetStringAsync("/swagger/v1.0.0/swagger.json"); + var openapi = JsonSerializer.Deserialize(json); + + // Assert + var result = openapi!.RootElement.GetProperty("paths") + .GetProperty("/events/{eventId}/deployment-models") + .GetProperty("get") + .GetProperty("parameters") + .EnumerateArray() + .Where(p => p.GetProperty("in").GetString() == "path") + .Select(p => p.GetProperty("name").ToString()); + result.Should().Contain(name); + } + + [Theory] + [InlineData("responses")] + public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Object(string attribute) + { + // Arrange + using var httpClient = host.App!.CreateHttpClient("apiapp"); + + // Act + var json = await httpClient.GetStringAsync("/swagger/v1.0.0/swagger.json"); + var apiDocument = JsonSerializer.Deserialize(json); + + // Assert + var result = apiDocument!.RootElement.GetProperty("paths") + .GetProperty("/events/{eventId}/deployment-models") + .GetProperty("get") + .TryGetProperty(attribute, out var property) ? property : default; + result.ValueKind.Should().Be(JsonValueKind.Object); + } + + [Theory] + [InlineData("200")] + [InlineData("401")] + [InlineData("404")] + [InlineData("500")] + public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Response(string attribute) + { + // Arrange + using var httpClient = host.App!.CreateHttpClient("apiapp"); + + // Act + var json = await httpClient.GetStringAsync("/swagger/v1.0.0/swagger.json"); + var apiDocument = JsonSerializer.Deserialize(json); + + // Assert + var result = apiDocument!.RootElement.GetProperty("paths") + .GetProperty("/events/{eventId}/deployment-models") + .GetProperty("get") + .GetProperty("responses") + .TryGetProperty(attribute, out var property) ? property : default; + result.ValueKind.Should().Be(JsonValueKind.Object); + } + + + [Fact] + public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Model() + { + // Arrange + using var httpClient = host.App!.CreateHttpClient("apiapp"); + + // Act + var json = await httpClient.GetStringAsync("/swagger/v1.0.0/swagger.json"); + var openapi = JsonSerializer.Deserialize(json); + + // Assert + var result = openapi!.RootElement.GetProperty("components") + .GetProperty("schemas") + .TryGetProperty("DeploymentModelDetails", out var property) ? property : default; + result.ValueKind.Should().Be(JsonValueKind.Object); + } + + [Theory] + [InlineData("name", true)] + public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Required(string attribute, bool isRequired) + { + // Arrange + using var httpClient = host.App!.CreateHttpClient("apiapp"); + + // Act + var json = await httpClient.GetStringAsync("/swagger/v1.0.0/swagger.json"); + var openapi = JsonSerializer.Deserialize(json); + + // Assert + var result = openapi!.RootElement.GetProperty("components") + .GetProperty("schemas") + .GetProperty("DeploymentModelDetails") + .TryGetStringArray("required") + .ToList(); + result.Contains(attribute).Should().Be(isRequired); + } + + [Theory] + [InlineData("name")] + public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Property(string attribute) + { + // Arrange + using var httpClient = host.App!.CreateHttpClient("apiapp"); + + // Act + var json = await httpClient.GetStringAsync("/swagger/v1.0.0/swagger.json"); + var openapi = JsonSerializer.Deserialize(json); + + // Assert + var result = openapi!.RootElement.GetProperty("components") + .GetProperty("schemas") + .GetProperty("DeploymentModelDetails") + .GetProperty("properties") + .TryGetProperty(attribute, out var property) ? property : default; + result.ValueKind.Should().Be(JsonValueKind.Object); + } + + [Theory] + [InlineData("name", "string")] + public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Type(string attribute, string type) + { + // Arrange + using var httpClient = host.App!.CreateHttpClient("apiapp"); + + // Act + var json = await httpClient.GetStringAsync("/swagger/v1.0.0/swagger.json"); + var openapi = JsonSerializer.Deserialize(json); + + // Assert + var result = openapi!.RootElement.GetProperty("components") + .GetProperty("schemas") + .GetProperty("DeploymentModelDetails") + .GetProperty("properties") + .GetProperty(attribute); + result.TryGetString("type").Should().Be(type); + } +} \ No newline at end of file