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

[Backend API] Implement endpoint for list event details #331

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.");
45 changes: 41 additions & 4 deletions src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs
Original file line number Diff line number Diff line change
@@ -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;
@@ -38,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);
}
Comment on lines +45 to +50
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이벤트를 삭제할 일이 없습니다.


/// <summary>
@@ -51,13 +61,25 @@ 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;
}
Comment on lines +64 to +68
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 태스크는 이벤트 리스트 가져오는 거라 이 메소드는 굳이 구현하실 필요가 없습니다.


/// <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);
}

Comment on lines +77 to +80
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테이블에 이벤트 디테일만 들어있는 게 아니라서, 이렇게 하면 에러 날 거예요. 적어도 파티션 키를 넣어줘야 할 겁니다.

return eventDetailsList;
}

/// <inheritdoc />
@@ -76,7 +98,22 @@ 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;
}
Comment on lines +101 to +107
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

마찬가지로 이 태스크의 스코프를 벗어난 구현입니다.


public async Task<Guid> DeleteEvent(AdminEventDetails eventDetails)
{
var tableClient = await GetTableClientAsync();

await tableClient.DeleteEntityAsync(eventDetails.PartitionKey, eventDetails.RowKey);

return eventDetails.EventId;
}
Comment on lines +110 to +116
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

필요 없습니다.


private async Task<TableClient> GetTableClientAsync()
15 changes: 15 additions & 0 deletions src/AzureOpenAIProxy.ApiApp/Services/AdminEventService.cs
Original file line number Diff line number Diff line change
@@ -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);
Comment on lines +38 to +44
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

필요 없습니다.

}

/// <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;
}
Comment on lines +86 to +92
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

필요 없습니다

}

/// <summary>
Original file line number Diff line number Diff line change
@@ -16,6 +16,19 @@

public class AdminEventRepositoryTests
{
private readonly StorageAccountSettings mockSettings;
private readonly TableServiceClient mockTableServiceClient;
private readonly TableClient mockTableClient;

public AdminEventRepositoryTests()
{
mockSettings = Substitute.For<StorageAccountSettings>();
mockTableServiceClient = Substitute.For<TableServiceClient>();
mockTableClient = Substitute.For<TableClient>();

mockTableServiceClient.GetTableClient(Arg.Any<string>()).Returns(mockTableClient);
}
Comment on lines +19 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트 코드는 이렇게 작성하면 안될 거예요.


[Fact]
public void Given_ServiceCollection_When_AddAdminEventRepository_Invoked_Then_It_Should_Contain_AdminEventRepository()
{
@@ -33,11 +46,10 @@
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);

Check warning on line 52 in test/AzureOpenAIProxy.ApiApp.Tests/Repositories/AdminEventRepositoryTests.cs

GitHub Actions / build-test

Possible null reference argument for parameter 'tableServiceClient' in 'AdminEventRepository.AdminEventRepository(TableServiceClient tableServiceClient, StorageAccountSettings storageAccountSettings)'.

Check warning on line 52 in test/AzureOpenAIProxy.ApiApp.Tests/Repositories/AdminEventRepositoryTests.cs

GitHub Actions / build-test

Possible null reference argument for parameter 'tableServiceClient' in 'AdminEventRepository.AdminEventRepository(TableServiceClient tableServiceClient, StorageAccountSettings storageAccountSettings)'.

// Assert
action.Should().Throw<ArgumentNullException>();
@@ -48,44 +60,39 @@
{
// Arrange
var settings = default(StorageAccountSettings);
var tableServiceClient = Substitute.For<TableServiceClient>();

// Act
Action action = () => new AdminEventRepository(tableServiceClient, settings);
Action action = () => new AdminEventRepository(mockTableServiceClient, settings);

Check warning on line 65 in test/AzureOpenAIProxy.ApiApp.Tests/Repositories/AdminEventRepositoryTests.cs

GitHub Actions / build-test

Possible null reference argument for parameter 'storageAccountSettings' in 'AdminEventRepository.AdminEventRepository(TableServiceClient tableServiceClient, StorageAccountSettings storageAccountSettings)'.

Check warning on line 65 in test/AzureOpenAIProxy.ApiApp.Tests/Repositories/AdminEventRepositoryTests.cs

GitHub Actions / build-test

Possible null reference argument for parameter 'storageAccountSettings' in 'AdminEventRepository.AdminEventRepository(TableServiceClient tableServiceClient, StorageAccountSettings storageAccountSettings)'.

// 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 +101,12 @@
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 +122,7 @@
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 +132,7 @@

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 +143,39 @@
}

[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
await repository.UpdateEvent(eventId, eventDetails);

// Assert
await mockTableClient.Received(1)
.UpdateEntityAsync<AdminEventDetails>(Arg.Any<AdminEventDetails>(),
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
Func<Task> func = async () => await repository.UpdateEvent(eventId, eventDetails);
Guid deletedEventId = await repository.DeleteEvent(eventDetails);

// Assert
func.Should().ThrowAsync<NotImplementedException>();
deletedEventId.Should().Be(eventId);
await mockTableClient.Received(1)
.DeleteEntityAsync(Arg.Any<string>(), Arg.Any<string>());
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}

Unchanged files with check annotations Beta

var services = new ServiceCollection();
var dict = new Dictionary<string, string>()
{
{ "Azure:KeyVault:SecretNames:Storage", secretName },

Check warning on line 317 in test/AzureOpenAIProxy.ApiApp.Tests/Extensions/ServiceCollectionExtensionsTests.cs

GitHub Actions / build-test

Possible null reference argument for parameter 'value' in 'void Dictionary<string, string>.Add(string key, string value)'.

Check warning on line 317 in test/AzureOpenAIProxy.ApiApp.Tests/Extensions/ServiceCollectionExtensionsTests.cs

GitHub Actions / build-test

Possible null reference argument for parameter 'value' in 'void Dictionary<string, string>.Add(string key, string value)'.
};
#pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types.
var config = new ConfigurationBuilder().AddInMemoryCollection(dict).Build();
// Arrange
var dict = new Dictionary<string, string>()
{
{ "Azure:StorageAccount:TableStorage:TableName", tableName }

Check warning on line 448 in test/AzureOpenAIProxy.ApiApp.Tests/Extensions/ServiceCollectionExtensionsTests.cs

GitHub Actions / build-test

Possible null reference argument for parameter 'value' in 'void Dictionary<string, string>.Add(string key, string value)'.

Check warning on line 448 in test/AzureOpenAIProxy.ApiApp.Tests/Extensions/ServiceCollectionExtensionsTests.cs

GitHub Actions / build-test

Possible null reference argument for parameter 'value' in 'void Dictionary<string, string>.Add(string key, string value)'.
};
#pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types.
var config = new ConfigurationBuilder().AddInMemoryCollection(dict).Build();
{
var endpoint = new Uri($"{clientOptions.Endpoint!.TrimEnd('/')}/api");
var credential = new AzureKeyCredential(clientOptions.ApiKey!);
var openai = new AzureOpenAIClient(endpoint, credential);

Check failure on line 31 in src/AzureOpenAIProxy.PlaygroundApp/Clients/OpenAIApiClient.cs

GitHub Actions / build-test

Argument 2: cannot convert from 'Azure.AzureKeyCredential' to 'System.ClientModel.ApiKeyCredential'

Check failure on line 31 in src/AzureOpenAIProxy.PlaygroundApp/Clients/OpenAIApiClient.cs

GitHub Actions / build-test

Argument 2: cannot convert from 'Azure.AzureKeyCredential' to 'System.ClientModel.ApiKeyCredential'
var chat = openai.GetChatClient(clientOptions.DeploymentName);
var messages = new List<ChatMessage>()
};
var options = new ChatCompletionOptions
{
MaxTokens = clientOptions.MaxTokens,

Check failure on line 41 in src/AzureOpenAIProxy.PlaygroundApp/Clients/OpenAIApiClient.cs

GitHub Actions / build-test

'ChatCompletionOptions' does not contain a definition for 'MaxTokens'

Check failure on line 41 in src/AzureOpenAIProxy.PlaygroundApp/Clients/OpenAIApiClient.cs

GitHub Actions / build-test

'ChatCompletionOptions' does not contain a definition for 'MaxTokens'
Temperature = clientOptions.Temperature,
};