diff --git a/src/AzureOpenAIProxy.ApiApp/Endpoints/AdminEventEndpoints.cs b/src/AzureOpenAIProxy.ApiApp/Endpoints/AdminEventEndpoints.cs index 2687693f..b877754b 100644 --- a/src/AzureOpenAIProxy.ApiApp/Endpoints/AdminEventEndpoints.cs +++ b/src/AzureOpenAIProxy.ApiApp/Endpoints/AdminEventEndpoints.cs @@ -1,3 +1,5 @@ +using Azure; + using AzureOpenAIProxy.ApiApp.Models; using AzureOpenAIProxy.ApiApp.Services; @@ -107,12 +109,35 @@ public static RouteHandlerBuilder AddGetAdminEvent(this WebApplication app) { // Todo: Issue #19 https://github.com/aliencube/azure-openai-sdk-proxy/issues/19 // Need authorization by admin - var builder = app.MapGet(AdminEndpointUrls.AdminEventDetails, ( - [FromRoute] string eventId) => + var builder = app.MapGet(AdminEndpointUrls.AdminEventDetails, async ( + [FromRoute] Guid eventId, + IAdminEventService service, + ILoggerFactory loggerFactory) => { - // Todo: Issue #208 https://github.com/aliencube/azure-openai-sdk-proxy/issues/208 - return Results.Ok(); - // Todo: Issue #208 + var logger = loggerFactory.CreateLogger(nameof(AdminEventEndpoints)); + logger.LogInformation($"Received request to fetch details for event with ID: {eventId}"); + + try + { + var details = await service.GetEvent(eventId); + return Results.Ok(details); + } + catch(RequestFailedException ex) + { + if(ex.Status == 404) + { + logger.LogError($"Failed to get event details of {eventId}"); + return Results.NotFound(); + } + + logger.LogError(ex, $"Error occurred while fetching event details of {eventId} with status {ex.Status}"); + return Results.Problem(ex.Message, statusCode: StatusCodes.Status500InternalServerError); + } + catch(Exception ex) + { + logger.LogError(ex, $"Error occurred while fetching event details of {eventId}"); + return Results.Problem(ex.Message, statusCode: StatusCodes.Status500InternalServerError); + } }) .Produces(statusCode: StatusCodes.Status200OK, contentType: "application/json") .Produces(statusCode: StatusCodes.Status401Unauthorized) diff --git a/src/AzureOpenAIProxy.ApiApp/PartitionKeys.cs b/src/AzureOpenAIProxy.ApiApp/PartitionKeys.cs new file mode 100644 index 00000000..1949fd7e --- /dev/null +++ b/src/AzureOpenAIProxy.ApiApp/PartitionKeys.cs @@ -0,0 +1,17 @@ +namespace AzureOpenAIProxy.ApiApp; + +/// +/// This represents the partition keys for azure table storage +/// +public class PartitionKeys +{ + /// + /// Partition key for event details + /// + public const string EventDetails = "event-details"; + + /// + /// Partition key for resource details + /// + public const string ResourceDetails = "resource-details"; +} \ No newline at end of file diff --git a/src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs b/src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs index 4881da1c..1ef0dfcb 100644 --- a/src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs +++ b/src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs @@ -63,7 +63,14 @@ public async Task> GetEvents() /// public async Task GetEvent(Guid eventId) { - throw new NotImplementedException(); + TableClient tableClient = await GetTableClientAsync(); + + var eventDetail = await tableClient.GetEntityAsync( + rowKey: eventId.ToString(), + partitionKey: PartitionKeys.EventDetails + ).ConfigureAwait(false); + + return eventDetail.Value; } /// @@ -71,6 +78,15 @@ public async Task UpdateEvent(Guid eventId, AdminEventDetails { throw new NotImplementedException(); } + + private async Task GetTableClientAsync() + { + TableClient tableClient = _tableServiceClient.GetTableClient(_storageAccountSettings.TableStorage.TableName); + + await tableClient.CreateIfNotExistsAsync().ConfigureAwait(false); + + return tableClient; + } } /// diff --git a/test/AzureOpenAIProxy.ApiApp.Tests/Repositories/AdminEventRepositoryTests.cs b/test/AzureOpenAIProxy.ApiApp.Tests/Repositories/AdminEventRepositoryTests.cs index 33755e47..ddc0facd 100644 --- a/test/AzureOpenAIProxy.ApiApp.Tests/Repositories/AdminEventRepositoryTests.cs +++ b/test/AzureOpenAIProxy.ApiApp.Tests/Repositories/AdminEventRepositoryTests.cs @@ -1,16 +1,16 @@ -using Azure.Data.Tables; +using Azure; +using Azure.Data.Tables; using AzureOpenAIProxy.ApiApp.Configurations; using AzureOpenAIProxy.ApiApp.Models; using AzureOpenAIProxy.ApiApp.Repositories; -using Castle.Core.Configuration; - using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using NSubstitute; +using NSubstitute.ExceptionExtensions; namespace AzureOpenAIProxy.ApiApp.Tests.Repositories; @@ -88,8 +88,10 @@ public void Given_Instance_When_GetEvents_Invoked_Then_It_Should_Throw_Exception func.Should().ThrowAsync(); } - [Fact] - public void Given_Instance_When_GetEvent_Invoked_Then_It_Should_Throw_Exception() + [Theory] + [InlineData(404)] + [InlineData(500)] + public async Task Given_Failure_In_Get_Entity_When_GetEvent_Invoked_Then_It_Should_Throw_Exception(int statusCode) { // Arrange var settings = Substitute.For(); @@ -97,11 +99,48 @@ public void Given_Instance_When_GetEvent_Invoked_Then_It_Should_Throw_Exception( var eventId = Guid.NewGuid(); var repository = new AdminEventRepository(tableServiceClient, settings); + var exception = new RequestFailedException(statusCode, "Request Error", default, default); + + var tableClient = Substitute.For(); + tableServiceClient.GetTableClient(Arg.Any()).Returns(tableClient); + tableClient.GetEntityAsync(Arg.Any(), Arg.Any()) + .ThrowsAsync(exception); + // Act - Func func = async () => await repository.GetEvent(eventId); + Func func = () => repository.GetEvent(eventId); // Assert - func.Should().ThrowAsync(); + var assertion = await func.Should().ThrowAsync(); + assertion.Which.Status.Should().Be(statusCode); + } + + [Theory] + [InlineData("c355cc28-d847-4637-aad9-2f03d39aa51f", "event-details")] + public async Task Given_Exist_EventId_When_GetEvent_Invoked_Then_It_Should_Return_AdminEventDetails(string eventId, string partitionKey) + { + // Arrange + var settings = Substitute.For(); + var tableServiceClient = Substitute.For(); + var repository = new AdminEventRepository(tableServiceClient, settings); + + var eventDetails = new AdminEventDetails + { + RowKey = eventId, + PartitionKey = partitionKey + }; + + var response = Response.FromValue(eventDetails, Substitute.For()); + + var tableClient = Substitute.For(); + tableServiceClient.GetTableClient(Arg.Any()).Returns(tableClient); + tableClient.GetEntityAsync(partitionKey, eventId) + .Returns(Task.FromResult(response)); + + // Act + var result = await repository.GetEvent(Guid.Parse(eventId)); + + // Assert + result.Should().BeEquivalentTo(eventDetails); } [Fact] diff --git a/test/AzureOpenAIProxy.ApiApp.Tests/Services/AdminEventServiceTests.cs b/test/AzureOpenAIProxy.ApiApp.Tests/Services/AdminEventServiceTests.cs index cf56f8e2..f0442c76 100644 --- a/test/AzureOpenAIProxy.ApiApp.Tests/Services/AdminEventServiceTests.cs +++ b/test/AzureOpenAIProxy.ApiApp.Tests/Services/AdminEventServiceTests.cs @@ -1,4 +1,6 @@ -using AzureOpenAIProxy.ApiApp.Models; +using Azure; + +using AzureOpenAIProxy.ApiApp.Models; using AzureOpenAIProxy.ApiApp.Repositories; using AzureOpenAIProxy.ApiApp.Services; @@ -7,6 +9,7 @@ using Microsoft.Extensions.DependencyInjection; using NSubstitute; +using NSubstitute.ExceptionExtensions; namespace AzureOpenAIProxy.ApiApp.Tests.Services; @@ -54,19 +57,51 @@ public void Given_Instance_When_GetEvents_Invoked_Then_It_Should_Throw_Exception func.Should().ThrowAsync(); } - [Fact] - public void Given_Instance_When_GetEvent_Invoked_Then_It_Should_Throw_Exception() + + [Theory] + [InlineData(404)] + [InlineData(500)] + public async Task Given_Failure_In_Get_Entity_When_GetEvent_Invoked_Then_It_Should_Throw_Exception(int statusCode) { // Arrange var eventId = Guid.NewGuid(); var repository = Substitute.For(); var service = new AdminEventService(repository); + var exception = new RequestFailedException(statusCode, "Request Failed", default, default); + + repository.GetEvent(Arg.Any()).ThrowsAsync(exception); + // Act - Func func = async () => await service.GetEvent(eventId); + Func func = () => service.GetEvent(eventId); // Assert - func.Should().ThrowAsync(); + var assertion = await func.Should().ThrowAsync(); + assertion.Which.Status.Should().Be(statusCode); + } + + [Theory] + [InlineData("c355cc28-d847-4637-aad9-2f03d39aa51f")] + public async Task Given_Exist_EventId_When_GetEvent_Invoked_Then_It_Should_Return_AdminEventDetails(string eventId) + { + // Arrange + var repository = Substitute.For(); + var service = new AdminEventService(repository); + + var eventDetails = new AdminEventDetails + { + RowKey = eventId + }; + + var guid = Guid.Parse(eventId); + + repository.GetEvent(guid).Returns(Task.FromResult(eventDetails)); + + // Act + var result = await service.GetEvent(guid); + + // Assert + result.Should().BeEquivalentTo(eventDetails); } [Fact]