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 create event details #213 #281

Open
wants to merge 49 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
e93e518
add : Azure.Data.Tables 패키지 추가
tae0y Aug 31, 2024
b507d99
add : aspire.bicep 테이블 스토리지 추가
tae0y Aug 31, 2024
75d7042
add : 테이블 스토리지 연결, 데이터 생성 코드 준비
tae0y Aug 31, 2024
f4172b7
update : 병합 충돌 해소
tae0y Aug 31, 2024
b94bd2c
update : 테이블 스토리지 bicep 파일 분리, 별도 브랜치에서 작업
tae0y Sep 1, 2024
45b8e0c
update : events로 테이블명 변경
tae0y Sep 1, 2024
eaa6972
merge : 서비스, 레포지토리 추가 병합
tae0y Sep 1, 2024
290d971
update : 빌드오류 임시조치
tae0y Sep 1, 2024
9177a70
Merge branch 'aliencube:main' into feature/213-admin-event-create
tae0y Sep 3, 2024
9ad61bf
Merge branch 'aliencube:main' into feature/213-admin-event-create
tae0y Sep 6, 2024
b729ad0
Merge main into feature/213-admin-event-create
tae0y Sep 13, 2024
c158308
update : 작업내용 placeholder 추가
tae0y Sep 13, 2024
9b82301
update : ITableEntity 구현, TableClient 의존성 주입
tae0y Sep 14, 2024
954c43e
update : 객체 영속성 저장후 재조회
tae0y Sep 14, 2024
419a973
update : AdminEventDetails 역직렬화시 Etag 제외
tae0y Sep 14, 2024
30e911c
Merge branch 'main' into feature/213-admin-event-create
tae0y Sep 14, 2024
f17bd29
commit message 변경후 conflict 해소
tae0y Sep 14, 2024
719d973
update : null, 응답코드 처리 추가
tae0y Sep 14, 2024
a521674
add : 테스트 추가
tae0y Sep 14, 2024
ec396dd
update : repository/service 레이어별 로직 분리, 테스트 추가
tae0y Sep 15, 2024
b6f66f2
Merge branch 'main' into feature/213-admin-event-create
tae0y Sep 16, 2024
ada1f22
Merge branch 'main' into feature/213-admin-event-create
tae0y Sep 21, 2024
9385d2f
update : AdminEventDetails 불필요 ITableEntity 구현 제거
tae0y Sep 21, 2024
69f35d3
update : AdminEventService 필수값체크 및 PartitionKey 불필요 로직제거
tae0y Sep 21, 2024
17c9799
delete : 불필요 로컬 파일 제거
tae0y Sep 21, 2024
9548f5f
update : 테이블이름 하드코딩→동적조회
tae0y Sep 21, 2024
9b50e75
update : tableClient 이름변경
tae0y Sep 21, 2024
ca183e0
update : 저장후 재조회 로직 제거
tae0y Sep 21, 2024
1c6c2d6
update : 불필요 변경사항 롤백
tae0y Sep 21, 2024
9bef6ab
update : tableclient 조회 wrapper 메서드 추가
tae0y Sep 21, 2024
a836420
update : aspire.bicep 병합/변경충돌 정리
tae0y Sep 21, 2024
7a7d848
Merge branch 'main' into feature/213-admin-event-create
tae0y Sep 25, 2024
55db6d6
update : 병합후 repository 테이블 클라이언트, 파티션키 로직변경
tae0y Sep 25, 2024
473da19
update : 단위테스트 수정
tae0y Sep 25, 2024
2b466c7
update : 빌드오류 방지 임시조치
tae0y Sep 28, 2024
d77ca2c
update : OpenAIApiClient 빌드오류 임시조치 원복
tae0y Sep 28, 2024
3f4b1e1
Merge branch 'main' into feature/213-admin-event-create
tae0y Oct 2, 2024
74d9674
update : repository 불필요 각주 제거
tae0y Oct 4, 2024
9516436
update : unit test tableclient 목킹 추가
tae0y Oct 4, 2024
a083391
update : unit test AddEntityAsync 목킹 추가
tae0y Oct 4, 2024
6274ead
remote build 재시도
tae0y Oct 4, 2024
bbdbd68
update : 작업사항 목록화
tae0y Oct 12, 2024
0ff2663
Merge branch 'main' into feature/213-admin-event-create
tae0y Oct 12, 2024
f695a7c
update : Endpoint 유효성검사 필요여부 확인
tae0y Oct 12, 2024
4b5a1bd
update : RepositoryTests 예외발생 관점으로 테스트작성
tae0y Oct 12, 2024
78ffc92
update : ServiceTests 예외발생 관점으로 테스트작성, 필수값 없을 때는 payload 언마샬 실패로 테스트 불요
tae0y Oct 12, 2024
e503167
Merge branch 'main' into feature/213-admin-event-create
tae0y Oct 12, 2024
1988f7f
Merge branch 'main' into feature/213-admin-event-create
tae0y Oct 17, 2024
2587bd6
update : repo/service 반환값 검사하는 테스트로 수정
tae0y Oct 17, 2024
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
24 changes: 11 additions & 13 deletions src/AzureOpenAIProxy.ApiApp/Endpoints/AdminEventEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,20 @@ public static RouteHandlerBuilder AddNewAdminEvent(this WebApplication app)
return Results.BadRequest("Payload is null");
}

justinyoo marked this conversation as resolved.
Show resolved Hide resolved
//try
//{
// var result = await service.CreateEvent(payload);

// logger.LogInformation("Created a new event");
try
{
var result = await service.CreateEvent(payload);

// return Results.Ok(result);
//}
//catch (Exception ex)
//{
// logger.LogError(ex, "Failed to create a new event");
logger.LogInformation("Created a new event");

// return Results.Problem(ex.Message, statusCode: StatusCodes.Status500InternalServerError);
//}
return Results.Ok(result);
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to create a new event");

return await Task.FromResult(Results.Ok());
return Results.Problem(ex.Message, statusCode: StatusCodes.Status500InternalServerError);
}
})
.Accepts<AdminEventDetails>(contentType: "application/json")
.Produces<AdminEventDetails>(statusCode: StatusCodes.Status200OK, contentType: "application/json")
Expand Down
3 changes: 3 additions & 0 deletions src/AzureOpenAIProxy.ApiApp/Models/AdminEventDetails.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System.Text.Json.Serialization;

using Azure;
using Azure.Data.Tables;

namespace AzureOpenAIProxy.ApiApp.Models;

/// <summary>
Expand Down
13 changes: 12 additions & 1 deletion src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,18 @@
/// <inheritdoc />
public async Task<AdminEventDetails> CreateEvent(AdminEventDetails eventDetails)
{
throw new NotImplementedException();
// 테이블 클라이언트
TableClient tableClient = await GetTableClientAsync();

// 데이터 저장
var createResponse = await tableClient.AddEntityAsync(eventDetails).ConfigureAwait(false);

// 저장한 엔티티 반환
return eventDetails;
}

/// <inheritdoc />
public async Task<List<AdminEventDetails>> GetEvents()

Check warning on line 65 in src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs

View workflow job for this annotation

GitHub Actions / build-test

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 65 in src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs

View workflow job for this annotation

GitHub Actions / build-test

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
throw new NotImplementedException();
}
Expand All @@ -74,11 +81,15 @@
}

/// <inheritdoc />
public async Task<AdminEventDetails> UpdateEvent(Guid eventId, AdminEventDetails eventDetails)

Check warning on line 84 in src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs

View workflow job for this annotation

GitHub Actions / build-test

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 84 in src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs

View workflow job for this annotation

GitHub Actions / build-test

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
throw new NotImplementedException();
}

/// <summary>
/// Gets the <see cref="TableClient"/> instance.
/// </summary>
/// <returns>TableClient</returns>
justinyoo marked this conversation as resolved.
Show resolved Hide resolved
private async Task<TableClient> GetTableClientAsync()
{
TableClient tableClient = _tableServiceClient.GetTableClient(_storageAccountSettings.TableStorage.TableName);
Expand Down
10 changes: 7 additions & 3 deletions src/AzureOpenAIProxy.ApiApp/Services/AdminEventService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,13 @@ public class AdminEventService(IAdminEventRepository repository) : IAdminEventSe
/// <inheritdoc />
public async Task<AdminEventDetails> CreateEvent(AdminEventDetails eventDetails)
{
var result = await this._repository.CreateEvent(eventDetails).ConfigureAwait(false);

return result;
// Validate
eventDetails.PartitionKey = PartitionKeys.EventDetails;
eventDetails.RowKey = eventDetails.EventId.ToString();

// Save
var response = await this._repository.CreateEvent(eventDetails).ConfigureAwait(false);
return response;
}

/// <inheritdoc />
Expand Down
57 changes: 30 additions & 27 deletions src/AzureOpenAIProxy.ApiApp/appsettings.Development.sample.json
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},

"Azure": {
"OpenAI": {
"Instances": [
{
"Endpoint": "https://{{location}}.api.cognitive.microsoft.com/",
"ApiKey": "{{api-key}}",
"DeploymentNames": [
"deployment-name-1",
"deployment-name-2"
]
}
]
},
"KeyVault": {
"VaultUri": "https://{{key-vault-name}}.vault.azure.net/",
"SecretName": "azure-openai-instances"
}
}
}
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},

"Azure": {
"OpenAI": {
"Instances": [
{
"Endpoint": "https://{{location}}.api.cognitive.microsoft.com/",
"ApiKey": "{{api-key}}",
"DeploymentNames": [
"deployment-name-1",
"deployment-name-2"
]
}
]
},
"KeyVault": {
"VaultUri": "https://{{key-vault-name}}.vault.azure.net/",
"SecretNames": {
"OpenAI": "azure-openai-instances",
"Storage": "storage-connection-string"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
var tableServiceClient = default(TableServiceClient);

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

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

View workflow job for this annotation

GitHub Actions / build-test

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

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

View workflow job for this annotation

GitHub Actions / build-test

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

// Assert
action.Should().Throw<ArgumentNullException>();
Expand All @@ -51,14 +51,14 @@
var tableServiceClient = Substitute.For<TableServiceClient>();

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

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

View workflow job for this annotation

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_Same_Instance()
{
// Arrange
var settings = Substitute.For<StorageAccountSettings>();
Expand All @@ -67,10 +67,33 @@
var repository = new AdminEventRepository(tableServiceClient, settings);

// Act
Func<Task> func = async () => await repository.CreateEvent(eventDetails);
var result = await repository.CreateEvent(eventDetails);
Copy link
Contributor

@justinyoo justinyoo Oct 3, 2024

Choose a reason for hiding this comment

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

이게 동작하나요????

https://learn.microsoft.com/en-us/dotnet/azure/sdk/unit-testing-mocking?tabs=nsubstitute

만약에 동작한다면, 그건 그것대로 문제 같은데... 왜냐면 목킹이 제대로 안 된 것 같아서;;;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

오.....! ㅠㅠㅠ 보내주신 문서보고 다시 해보겠습니다

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(수정내용)
단위 테스트 자체에 이해도가 낮아서, 아직 테스트 방법을 갈피를 잡지 못한 것 같습니다ㅠㅠ
우선 tableServiceClient가 tableClient를 반환하는 동작,
tableClient에서 호출되는 AddEntityAsync의 동작도 목킹을 추가했습니다.

(테스트목표)
이 테스트 메서드는 파라미터로 전달한 객체가 오류없이 잘 반환되는지,
그래서 파라미터로 전달한 것과 반환된 것이 동일한지 확인해보고 싶었어요.

(궁금한점)
음 저희 단위 테스트 코드를 보면 대체로 동작을 비슷한 방법으로 목킹하고 있는데
만약 어떠어떠한 부분이 잘 동작한다면, 이란 가정하에 나머지 부분이 어떻게 동작하는지를 확인하는 걸까요?

Copy link
Contributor

@justinyoo justinyoo Oct 4, 2024

Choose a reason for hiding this comment

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

(궁금한점)
음 저희 단위 테스트 코드를 보면 대체로 동작을 비슷한 방법으로 목킹하고 있는데 만약 어떠어떠한 부분이 잘 동작한다면, 이란 가정하에 나머지 부분이 어떻게 동작하는지를 확인하는 걸까요?

단위테스트는 내가 테스트하고자 하는 메소드가 제대로의도한대로 작동하는 것만을 확인합니다. 따라서, 그 안에서 어떤 일이 벌어지는지는 테스트하지 않아요.

예를 들어서 지금과 같이 CreateEvent라는 메소드를 보면, 그 안에서 TableClient를 만들잖아요? 근데, 이건 우리 메소드가 하는 코어 로직하고는 다르죠. 사실 어떻게 보면 이건 외부에서 인젝션해 주는 거나 마찬가지라서, 이 부분은 목킹으로 넘어가면 됩니다.

또한가지는 만약에 TableClient에서 우리가 AddEntity 메소드를 호출한다고 가정하면, 실제 상황에서는 이 메소드 호출시 에러가 나는 경우도 있겠죠? 그러면 AddEntity 메소드를 에러가 나는 상황으로 목킹해서 CreateEvent 메소드가 어떤식으로 에러 처리를 하는지 확인해야 할 겁니다. 또한 AddEntity 메소드를 에러가 나지 않는 상황으로 목킹해서 그 결과값을 테스트하면 됩니다.

정리하자면 우리가 테스트하려는 대상은 CreateEvent이고, 그 안에 들어가는 의존성 개체라든가 데이터는 가짜 데이터를 심어서 그 가짜 데이터를 바탕으로 우리 CreateEvent 메소드가 예상하는 대로 동작하는지를 확인하는 게 단위테스트의 목적입니다.


// Assert
func.Should().ThrowAsync<NotImplementedException>();
result.Should().BeSameAs(eventDetails);
}

[Fact]
public async Task Given_Failure_In_Add_Entity_When_CreateEvent_Invoked_Then_It_Should_Throw_Exception()
{
// Arrange
var settings = Substitute.For<StorageAccountSettings>();
var tableServiceClient = Substitute.For<TableServiceClient>();
var repository = new AdminEventRepository(tableServiceClient, settings);
var eventDetails = new AdminEventDetails();

var exception = new InvalidOperationException("Invalid Operation Error : check duplicate, null or empty values");

var tableClient = Substitute.For<TableClient>();
tableServiceClient.GetTableClient(Arg.Any<string>()).Returns(tableClient);
tableClient.AddEntityAsync<AdminEventDetails>(Arg.Any<AdminEventDetails>())
.ThrowsAsync(exception);

// Act
Func<Task> func = () => repository.CreateEvent(eventDetails);

// Assert
await func.Should().ThrowAsync<InvalidOperationException>();
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

using FluentAssertions;

using Microsoft.AspNetCore.Authentication;

using Microsoft.Extensions.DependencyInjection;

using NSubstitute;
Expand All @@ -29,18 +31,42 @@ public void Given_ServiceCollection_When_AddAdminEventService_Invoked_Then_It_Sh
}

[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_Same_Instance()
{
// Arrange
var eventDetails = new AdminEventDetails();
justinyoo marked this conversation as resolved.
Show resolved Hide resolved
var repository = Substitute.For<IAdminEventRepository>();
var service = new AdminEventService(repository);

repository.CreateEvent(Arg.Any<AdminEventDetails>()).Returns(eventDetails);

// Act
Func<Task> func = async () => await service.CreateEvent(eventDetails);
var result = await service.CreateEvent(eventDetails);

// Assert
func.Should().ThrowAsync<NotImplementedException>();
result.Should().BeEquivalentTo(
eventDetails,
options => options.Excluding(x => x.PartitionKey)
.Excluding(x => x.RowKey)
);
}

[Fact]
public async Task Given_Failure_In_Add_Entity_When_CreateEvent_Invoked_Then_It_Should_Throw_Exception()
{
// Arrange
var eventDetails = new AdminEventDetails();
var repository = Substitute.For<IAdminEventRepository>();
var service = new AdminEventService(repository);

var exception = new InvalidOperationException("Invalid Operation Error : check duplicate, null or empty values");
repository.CreateEvent(Arg.Any<AdminEventDetails>()).ThrowsAsync(exception);

// Act
Func<Task> func = () => service.CreateEvent(eventDetails);

// Assert
await func.Should().ThrowAsync<InvalidOperationException>();
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ namespace AzureOpenAIProxy.AppHost.Tests.ApiApp.Endpoints;

public class AdminGetEventsOpenApiTests(AspireAppHostFixture host) : IClassFixture<AspireAppHostFixture>
{
// TODO: [tae0y] 테스트코드 작성하기
[Fact]
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Path()
{
Expand Down
Loading