From bcdc02783390f2b4f47a77f4de54958c6e90250a Mon Sep 17 00:00:00 2001 From: praivesi <praivesi@gmail.com> Date: Wed, 18 Sep 2024 17:27:31 +0900 Subject: [PATCH 1/3] =?UTF-8?q?chore:=20=EC=98=A4=ED=83=88=EC=9E=90=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Extensions/ServiceCollectionExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AzureOpenAIProxy.ApiApp/Extensions/ServiceCollectionExtensions.cs b/src/AzureOpenAIProxy.ApiApp/Extensions/ServiceCollectionExtensions.cs index 00783177..965f119d 100644 --- a/src/AzureOpenAIProxy.ApiApp/Extensions/ServiceCollectionExtensions.cs +++ b/src/AzureOpenAIProxy.ApiApp/Extensions/ServiceCollectionExtensions.cs @@ -145,7 +145,7 @@ public static IServiceCollection AddTableStorageService(this IServiceCollection { services.AddSingleton<TableServiceClient>(sp => { var configuration = sp.GetService<IConfiguration>() - ?? throw new InvalidOperationException($"{nameof(IConfiguration)} service is not registerd."); + ?? throw new InvalidOperationException($"{nameof(IConfiguration)} service is not registered."); var settings = configuration.GetSection(AzureSettings.Name).GetSection(KeyVaultSettings.Name).Get<KeyVaultSettings>() ?? throw new InvalidOperationException($"{nameof(KeyVaultSettings)} could not be retrieved from the configuration."); From 42c6d00058d323b268fe939c9e87126d5e9d9dad Mon Sep 17 00:00:00 2001 From: praivesi <praivesi@gmail.com> Date: Fri, 27 Sep 2024 08:37:45 +0900 Subject: [PATCH 2/3] feat: add AdminEventRepository CRUD tests & implement CRUD --- .../Repositories/AdminEventRepository.cs | 30 +++++++- .../Repositories/AdminEventRepositoryTests.cs | 74 ++++++++++--------- 2 files changed, 65 insertions(+), 39 deletions(-) diff --git a/src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs b/src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs index 1ef0dfcb..6fc42bc0 100644 --- a/src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs +++ b/src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs @@ -1,4 +1,7 @@ -using Azure.Data.Tables; +using System.Collections.Specialized; +using System.Text.Json; + +using Azure.Data.Tables; using AzureOpenAIProxy.ApiApp.Configurations; using AzureOpenAIProxy.ApiApp.Extensions; @@ -51,13 +54,26 @@ public class AdminEventRepository(TableServiceClient tableServiceClient, Storage /// <inheritdoc /> public async Task<AdminEventDetails> CreateEvent(AdminEventDetails eventDetails) { - throw new NotImplementedException(); + var tableClient = _tableServiceClient.GetTableClient(_storageAccountSettings.TableStorage.TableName); + + await tableClient.AddEntityAsync<AdminEventDetails>(eventDetails); + + return eventDetails; } /// <inheritdoc /> public async Task<List<AdminEventDetails>> GetEvents() { - throw new NotImplementedException(); + var tableClient = await GetTableClientAsync(); + + var eventDetailsList = new List<AdminEventDetails>(); + + await foreach (var entity in tableClient.QueryAsync<AdminEventDetails>()) + { + eventDetailsList.Add(entity); + } + + return eventDetailsList; } /// <inheritdoc /> @@ -76,7 +92,13 @@ public async Task<AdminEventDetails> GetEvent(Guid eventId) /// <inheritdoc /> public async Task<AdminEventDetails> UpdateEvent(Guid eventId, AdminEventDetails eventDetails) { - throw new NotImplementedException(); + var tableClient = await GetTableClientAsync(); + + eventDetails.EventId = eventId; + + await tableClient.UpdateEntityAsync<AdminEventDetails>(eventDetails, eventDetails.ETag, TableUpdateMode.Replace); + + return eventDetails; } private async Task<TableClient> GetTableClientAsync() diff --git a/test/AzureOpenAIProxy.ApiApp.Tests/Repositories/AdminEventRepositoryTests.cs b/test/AzureOpenAIProxy.ApiApp.Tests/Repositories/AdminEventRepositoryTests.cs index ddc0facd..f60e733c 100644 --- a/test/AzureOpenAIProxy.ApiApp.Tests/Repositories/AdminEventRepositoryTests.cs +++ b/test/AzureOpenAIProxy.ApiApp.Tests/Repositories/AdminEventRepositoryTests.cs @@ -1,4 +1,6 @@ -using Azure; +using System.Configuration; + +using Azure; using Azure.Data.Tables; using AzureOpenAIProxy.ApiApp.Configurations; @@ -12,10 +14,25 @@ using NSubstitute; using NSubstitute.ExceptionExtensions; +using Xunit.Sdk; + namespace AzureOpenAIProxy.ApiApp.Tests.Repositories; public class AdminEventRepositoryTests { + private StorageAccountSettings mockSettings; + private TableServiceClient mockTableServiceClient; + private TableClient mockTableClient; + + public AdminEventRepositoryTests() + { + mockSettings = Substitute.For<StorageAccountSettings>(); + mockTableServiceClient = Substitute.For<TableServiceClient>(); + mockTableClient = Substitute.For<TableClient>(); + + mockTableServiceClient.GetTableClient(Arg.Any<string>()).Returns(mockTableClient); + } + [Fact] public void Given_ServiceCollection_When_AddAdminEventRepository_Invoked_Then_It_Should_Contain_AdminEventRepository() { @@ -33,11 +50,10 @@ public void Given_ServiceCollection_When_AddAdminEventRepository_Invoked_Then_It public void Given_Null_TableServiceClient_When_Creating_AdminEventRepository_Then_It_Should_Throw_Exception() { // Arrange - var settings = Substitute.For<StorageAccountSettings>(); var tableServiceClient = default(TableServiceClient); // Act - Action action = () => new AdminEventRepository(tableServiceClient, settings); + Action action = () => new AdminEventRepository(tableServiceClient, mockSettings); // Assert action.Should().Throw<ArgumentNullException>(); @@ -48,44 +64,39 @@ public void Given_Null_StorageAccountSettings_When_Creating_AdminEventRepository { // Arrange var settings = default(StorageAccountSettings); - var tableServiceClient = Substitute.For<TableServiceClient>(); // Act - Action action = () => new AdminEventRepository(tableServiceClient, settings); + Action action = () => new AdminEventRepository(mockTableServiceClient, settings); // Assert action.Should().Throw<ArgumentNullException>(); } [Fact] - public void Given_Instance_When_CreateEvent_Invoked_Then_It_Should_Throw_Exception() + public async Task Given_Instance_When_CreateEvent_Invoked_Then_It_Should_Return_Created_Event() { // Arrange - var settings = Substitute.For<StorageAccountSettings>(); - var tableServiceClient = Substitute.For<TableServiceClient>(); var eventDetails = new AdminEventDetails(); - var repository = new AdminEventRepository(tableServiceClient, settings); + var repository = new AdminEventRepository(mockTableServiceClient, mockSettings); // Act - Func<Task> func = async () => await repository.CreateEvent(eventDetails); + await repository.CreateEvent(eventDetails); // Assert - func.Should().ThrowAsync<NotImplementedException>(); + await mockTableClient.Received(1).AddEntityAsync(eventDetails, default); } [Fact] - public void Given_Instance_When_GetEvents_Invoked_Then_It_Should_Throw_Exception() + public async Task Given_Instance_When_GetEvents_Invoked_Then_It_Should_Invoke_QueryAsync_Method() { // Arrange - var settings = Substitute.For<StorageAccountSettings>(); - var tableServiceClient = Substitute.For<TableServiceClient>(); - var repository = new AdminEventRepository(tableServiceClient, settings); + var repository = new AdminEventRepository(mockTableServiceClient, mockSettings); // Act - Func<Task> func = async () => await repository.GetEvents(); + await repository.GetEvents(); // Assert - func.Should().ThrowAsync<NotImplementedException>(); + mockTableClient.Received(1).QueryAsync<AdminEventDetails>(); } [Theory] @@ -94,16 +105,12 @@ public void Given_Instance_When_GetEvents_Invoked_Then_It_Should_Throw_Exception public async Task Given_Failure_In_Get_Entity_When_GetEvent_Invoked_Then_It_Should_Throw_Exception(int statusCode) { // Arrange - var settings = Substitute.For<StorageAccountSettings>(); - var tableServiceClient = Substitute.For<TableServiceClient>(); var eventId = Guid.NewGuid(); - var repository = new AdminEventRepository(tableServiceClient, settings); + var repository = new AdminEventRepository(mockTableServiceClient, mockSettings); var exception = new RequestFailedException(statusCode, "Request Error", default, default); - var tableClient = Substitute.For<TableClient>(); - tableServiceClient.GetTableClient(Arg.Any<string>()).Returns(tableClient); - tableClient.GetEntityAsync<AdminEventDetails>(Arg.Any<string>(), Arg.Any<string>()) + mockTableClient.GetEntityAsync<AdminEventDetails>(Arg.Any<string>(), Arg.Any<string>()) .ThrowsAsync(exception); // Act @@ -119,9 +126,7 @@ public async Task Given_Failure_In_Get_Entity_When_GetEvent_Invoked_Then_It_Shou public async Task Given_Exist_EventId_When_GetEvent_Invoked_Then_It_Should_Return_AdminEventDetails(string eventId, string partitionKey) { // Arrange - var settings = Substitute.For<StorageAccountSettings>(); - var tableServiceClient = Substitute.For<TableServiceClient>(); - var repository = new AdminEventRepository(tableServiceClient, settings); + var repository = new AdminEventRepository(mockTableServiceClient, mockSettings); var eventDetails = new AdminEventDetails { @@ -131,9 +136,7 @@ public async Task Given_Exist_EventId_When_GetEvent_Invoked_Then_It_Should_Retur var response = Response.FromValue(eventDetails, Substitute.For<Response>()); - var tableClient = Substitute.For<TableClient>(); - tableServiceClient.GetTableClient(Arg.Any<string>()).Returns(tableClient); - tableClient.GetEntityAsync<AdminEventDetails>(partitionKey, eventId) + mockTableClient.GetEntityAsync<AdminEventDetails>(partitionKey, eventId) .Returns(Task.FromResult(response)); // Act @@ -144,19 +147,20 @@ public async Task Given_Exist_EventId_When_GetEvent_Invoked_Then_It_Should_Retur } [Fact] - public void Given_Instance_When_UpdateEvent_Invoked_Then_It_Should_Throw_Exception() + public async Task Given_Instance_When_UpdateEvent_Invoked_Then_It_Should_Invoke_UpdateEntityAsync_Method() { // Arrange - var settings = Substitute.For<StorageAccountSettings>(); - var tableServiceClient = Substitute.For<TableServiceClient>(); var eventId = Guid.NewGuid(); var eventDetails = new AdminEventDetails(); - var repository = new AdminEventRepository(tableServiceClient, settings); + var repository = new AdminEventRepository(mockTableServiceClient, mockSettings); // Act - Func<Task> func = async () => await repository.UpdateEvent(eventId, eventDetails); + await repository.UpdateEvent(eventId, eventDetails); // Assert - func.Should().ThrowAsync<NotImplementedException>(); + await mockTableClient.Received(1) + .UpdateEntityAsync<AdminEventDetails>(Arg.Any<AdminEventDetails>(), + Arg.Any<Azure.ETag>(), + TableUpdateMode.Replace); } } From 65be6a9fb3d612919124c69d29b29d108945f821 Mon Sep 17 00:00:00 2001 From: praivesi <praivesi@gmail.com> Date: Sat, 28 Sep 2024 13:53:06 +0900 Subject: [PATCH 3/3] feat: add AdminEventService Tests --- .../Repositories/AdminEventRepository.cs | 17 +++++- .../Services/AdminEventService.cs | 15 ++++++ .../Repositories/AdminEventRepositoryTests.cs | 31 ++++++++--- .../Services/AdminEventServiceTests.cs | 52 +++++++++++++------ 4 files changed, 91 insertions(+), 24 deletions(-) diff --git a/src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs b/src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs index 6fc42bc0..f72ec2db 100644 --- a/src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs +++ b/src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs @@ -41,6 +41,13 @@ public interface IAdminEventRepository /// <param name="eventDetails">Event details instance.</param> /// <returns>Returns the updated record of the event details.</returns> Task<AdminEventDetails> UpdateEvent(Guid eventId, AdminEventDetails eventDetails); + + /// <summary> + /// Deletes the event details. + /// </summary> + /// <param name="eventDetails">Event details instance.</param> + /// <returns>Removed EventID of event details instance.</returns> + Task<Guid> DeleteEvent(AdminEventDetails eventDetails); } /// <summary> @@ -65,7 +72,6 @@ public async Task<AdminEventDetails> CreateEvent(AdminEventDetails eventDetails) public async Task<List<AdminEventDetails>> GetEvents() { var tableClient = await GetTableClientAsync(); - var eventDetailsList = new List<AdminEventDetails>(); await foreach (var entity in tableClient.QueryAsync<AdminEventDetails>()) @@ -101,6 +107,15 @@ public async Task<AdminEventDetails> UpdateEvent(Guid eventId, AdminEventDetails return eventDetails; } + public async Task<Guid> DeleteEvent(AdminEventDetails eventDetails) + { + var tableClient = await GetTableClientAsync(); + + await tableClient.DeleteEntityAsync(eventDetails.PartitionKey, eventDetails.RowKey); + + return eventDetails.EventId; + } + private async Task<TableClient> GetTableClientAsync() { TableClient tableClient = _tableServiceClient.GetTableClient(_storageAccountSettings.TableStorage.TableName); diff --git a/src/AzureOpenAIProxy.ApiApp/Services/AdminEventService.cs b/src/AzureOpenAIProxy.ApiApp/Services/AdminEventService.cs index 09f21797..d4e0746c 100644 --- a/src/AzureOpenAIProxy.ApiApp/Services/AdminEventService.cs +++ b/src/AzureOpenAIProxy.ApiApp/Services/AdminEventService.cs @@ -35,6 +35,13 @@ public interface IAdminEventService /// <param name="eventDetails">Event details to update.</param> /// <returns>Returns the updated event details.</returns> Task<AdminEventDetails> UpdateEvent(Guid eventId, AdminEventDetails eventDetails); + + /// <summary> + /// Deletes the event details. + /// </summary> + /// <param name="eventDetails">Event details to update.</param> + /// <returns>Removed EventID of event details instance.</returns> + Task<Guid> DeleteEvent(AdminEventDetails eventDetails); } /// <summary> @@ -75,6 +82,14 @@ public async Task<AdminEventDetails> UpdateEvent(Guid eventId, AdminEventDetails return result; } + + /// <inheritdoc /> + public async Task<Guid> DeleteEvent(AdminEventDetails eventDetails) + { + var result = await this._repository.DeleteEvent(eventDetails).ConfigureAwait(false); + + return result; + } } /// <summary> diff --git a/test/AzureOpenAIProxy.ApiApp.Tests/Repositories/AdminEventRepositoryTests.cs b/test/AzureOpenAIProxy.ApiApp.Tests/Repositories/AdminEventRepositoryTests.cs index f60e733c..bba20443 100644 --- a/test/AzureOpenAIProxy.ApiApp.Tests/Repositories/AdminEventRepositoryTests.cs +++ b/test/AzureOpenAIProxy.ApiApp.Tests/Repositories/AdminEventRepositoryTests.cs @@ -1,6 +1,4 @@ -using System.Configuration; - -using Azure; +using Azure; using Azure.Data.Tables; using AzureOpenAIProxy.ApiApp.Configurations; @@ -14,15 +12,13 @@ using NSubstitute; using NSubstitute.ExceptionExtensions; -using Xunit.Sdk; - namespace AzureOpenAIProxy.ApiApp.Tests.Repositories; public class AdminEventRepositoryTests { - private StorageAccountSettings mockSettings; - private TableServiceClient mockTableServiceClient; - private TableClient mockTableClient; + private readonly StorageAccountSettings mockSettings; + private readonly TableServiceClient mockTableServiceClient; + private readonly TableClient mockTableClient; public AdminEventRepositoryTests() { @@ -163,4 +159,23 @@ await mockTableClient.Received(1) Arg.Any<Azure.ETag>(), TableUpdateMode.Replace); } + + [Fact] + public async Task Given_Instance_When_DeleteEvent_Invoked_Then_It_Should_Invoke_DeleteEntityAsync_Method() + { + // Arrange + var eventDetails = new AdminEventDetails(); + var repository = new AdminEventRepository(mockTableServiceClient, mockSettings); + var eventId = Guid.NewGuid(); + + eventDetails.EventId = eventId; + + // Act + Guid deletedEventId = await repository.DeleteEvent(eventDetails); + + // Assert + deletedEventId.Should().Be(eventId); + await mockTableClient.Received(1) + .DeleteEntityAsync(Arg.Any<string>(), Arg.Any<string>()); + } } diff --git a/test/AzureOpenAIProxy.ApiApp.Tests/Services/AdminEventServiceTests.cs b/test/AzureOpenAIProxy.ApiApp.Tests/Services/AdminEventServiceTests.cs index f0442c76..a1f4bd23 100644 --- a/test/AzureOpenAIProxy.ApiApp.Tests/Services/AdminEventServiceTests.cs +++ b/test/AzureOpenAIProxy.ApiApp.Tests/Services/AdminEventServiceTests.cs @@ -15,6 +15,13 @@ namespace AzureOpenAIProxy.ApiApp.Tests.Services; public class AdminEventServiceTests { + private readonly IAdminEventRepository mockRepository; + + public AdminEventServiceTests() + { + mockRepository = Substitute.For<IAdminEventRepository>(); + } + [Fact] public void Given_ServiceCollection_When_AddAdminEventService_Invoked_Then_It_Should_Contain_AdminEventService() { @@ -33,8 +40,7 @@ public void Given_Instance_When_CreateEvent_Invoked_Then_It_Should_Throw_Excepti { // Arrange var eventDetails = new AdminEventDetails(); - var repository = Substitute.For<IAdminEventRepository>(); - var service = new AdminEventService(repository); + var service = new AdminEventService(mockRepository); // Act Func<Task> func = async () => await service.CreateEvent(eventDetails); @@ -47,8 +53,7 @@ public void Given_Instance_When_CreateEvent_Invoked_Then_It_Should_Throw_Excepti public void Given_Instance_When_GetEvents_Invoked_Then_It_Should_Throw_Exception() { // Arrange - var repository = Substitute.For<IAdminEventRepository>(); - var service = new AdminEventService(repository); + var service = new AdminEventService(mockRepository); // Act Func<Task> func = async () => await service.GetEvents(); @@ -65,12 +70,11 @@ public async Task Given_Failure_In_Get_Entity_When_GetEvent_Invoked_Then_It_Shou { // Arrange var eventId = Guid.NewGuid(); - var repository = Substitute.For<IAdminEventRepository>(); - var service = new AdminEventService(repository); + var service = new AdminEventService(mockRepository); var exception = new RequestFailedException(statusCode, "Request Failed", default, default); - repository.GetEvent(Arg.Any<Guid>()).ThrowsAsync(exception); + mockRepository.GetEvent(Arg.Any<Guid>()).ThrowsAsync(exception); // Act Func<Task> func = () => service.GetEvent(eventId); @@ -85,8 +89,7 @@ public async Task Given_Failure_In_Get_Entity_When_GetEvent_Invoked_Then_It_Shou public async Task Given_Exist_EventId_When_GetEvent_Invoked_Then_It_Should_Return_AdminEventDetails(string eventId) { // Arrange - var repository = Substitute.For<IAdminEventRepository>(); - var service = new AdminEventService(repository); + var service = new AdminEventService(mockRepository); var eventDetails = new AdminEventDetails { @@ -95,7 +98,7 @@ public async Task Given_Exist_EventId_When_GetEvent_Invoked_Then_It_Should_Retur var guid = Guid.Parse(eventId); - repository.GetEvent(guid).Returns(Task.FromResult(eventDetails)); + mockRepository.GetEvent(guid).Returns(Task.FromResult(eventDetails)); // Act var result = await service.GetEvent(guid); @@ -105,18 +108,37 @@ public async Task Given_Exist_EventId_When_GetEvent_Invoked_Then_It_Should_Retur } [Fact] - public void Given_Instance_When_UpdateEvent_Invoked_Then_It_Should_Throw_Exception() + public async Task Given_Instance_When_UpdateEvent_Invoked_Then_It_Should_Return_Updated_Event_Details() { // Arrange var eventId = Guid.NewGuid(); var eventDetails = new AdminEventDetails(); - var repository = Substitute.For<IAdminEventRepository>(); - var service = new AdminEventService(repository); + var service = new AdminEventService(mockRepository); + + mockRepository.UpdateEvent(eventId, eventDetails).Returns(eventDetails); // Act - Func<Task> func = async () => await service.UpdateEvent(eventId, eventDetails); + var updatedEventDetails = await service.UpdateEvent(eventId, eventDetails); // Assert - func.Should().ThrowAsync<NotImplementedException>(); + updatedEventDetails.Should().BeEquivalentTo(eventDetails); + } + + [Fact] + public async Task Given_Instance_When_DeleteEvent_Invoked_Then_It_Should_Return_Deleted_Event_Id() + { + // Arrange + var eventId = Guid.NewGuid(); + var eventDetails = new AdminEventDetails(); + var service = new AdminEventService(mockRepository); + + eventDetails.EventId = eventId; + mockRepository.DeleteEvent(eventDetails).Returns(eventDetails.EventId); + + // Act + var deletedEventId = await service.DeleteEvent(eventDetails); + + // Assert + deletedEventId.Should().Be(eventId); } }