From 424ca33c9b3cfe0d5a2fdaa390b3f28a272e6352 Mon Sep 17 00:00:00 2001 From: Justin Yoo Date: Fri, 20 Sep 2024 17:40:25 +0900 Subject: [PATCH 1/2] Implement ITableEntity --- .../Models/AdminResourceDetails.cs | 21 ++++++++++++++++++- .../Models/EventDetails.cs | 21 ++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/AzureOpenAIProxy.ApiApp/Models/AdminResourceDetails.cs b/src/AzureOpenAIProxy.ApiApp/Models/AdminResourceDetails.cs index b610f077..7cb8e1c5 100644 --- a/src/AzureOpenAIProxy.ApiApp/Models/AdminResourceDetails.cs +++ b/src/AzureOpenAIProxy.ApiApp/Models/AdminResourceDetails.cs @@ -1,6 +1,9 @@ using System.Runtime.Serialization; using System.Text.Json.Serialization; +using Azure; +using Azure.Data.Tables; + using AzureOpenAIProxy.ApiApp.Converters; namespace AzureOpenAIProxy.ApiApp.Models; @@ -8,7 +11,7 @@ namespace AzureOpenAIProxy.ApiApp.Models; /// /// This represent the entity for the resource details for admin. /// -public class AdminResourceDetails +public class AdminResourceDetails : ITableEntity { /// /// Gets or sets the event id. @@ -57,6 +60,22 @@ public class AdminResourceDetails /// [JsonRequired] public bool IsActive { get; set; } + + /// + [JsonIgnore] + public string PartitionKey { get; set; } = string.Empty; + + /// + [JsonIgnore] + public string RowKey { get; set; } = string.Empty; + + /// + [JsonIgnore] + public DateTimeOffset? Timestamp { get; set; } + + /// + [JsonIgnore] + public ETag ETag { get; set; } } /// diff --git a/src/AzureOpenAIProxy.ApiApp/Models/EventDetails.cs b/src/AzureOpenAIProxy.ApiApp/Models/EventDetails.cs index 459ff655..d70c3952 100644 --- a/src/AzureOpenAIProxy.ApiApp/Models/EventDetails.cs +++ b/src/AzureOpenAIProxy.ApiApp/Models/EventDetails.cs @@ -1,9 +1,12 @@ using System.Text.Json.Serialization; +using Azure; +using Azure.Data.Tables; + /// /// This represent the entity for the event details for users. /// -public class EventDetails +public class EventDetails : ITableEntity { /// /// Gets or sets the event id. @@ -34,4 +37,20 @@ public class EventDetails /// [JsonRequired] public int DailyRequestCap { get; set; } + + /// + [JsonIgnore] + public string PartitionKey { get; set; } = string.Empty; + + /// + [JsonIgnore] + public string RowKey { get; set; } = string.Empty; + + /// + [JsonIgnore] + public DateTimeOffset? Timestamp { get; set; } + + /// + [JsonIgnore] + public ETag ETag { get; set; } } \ No newline at end of file From d03655f1a9030af71593827bac85fbe8df8732ac Mon Sep 17 00:00:00 2001 From: Justin Yoo Date: Fri, 20 Sep 2024 18:23:05 +0900 Subject: [PATCH 2/2] Add tests --- .../Models/AdminEventDetailsTests.cs | 87 +++++++++++ .../Models/EventDetailsTests.cs | 58 ++++++++ .../AdminCreateEventsOpenApiTests.cs | 104 +++++++++++++ .../AdminGetEventDetailsOpenApiTests.cs | 140 +----------------- 4 files changed, 250 insertions(+), 139 deletions(-) create mode 100644 test/AzureOpenAIProxy.ApiApp.Tests/Models/AdminEventDetailsTests.cs create mode 100644 test/AzureOpenAIProxy.ApiApp.Tests/Models/EventDetailsTests.cs diff --git a/test/AzureOpenAIProxy.ApiApp.Tests/Models/AdminEventDetailsTests.cs b/test/AzureOpenAIProxy.ApiApp.Tests/Models/AdminEventDetailsTests.cs new file mode 100644 index 00000000..3ab3ad7f --- /dev/null +++ b/test/AzureOpenAIProxy.ApiApp.Tests/Models/AdminEventDetailsTests.cs @@ -0,0 +1,87 @@ +using System.Text.Json; + +using AzureOpenAIProxy.ApiApp.Models; + +using FluentAssertions; + +namespace AzureOpenAIProxy.ApiApp.Tests.Models; + +public class AdminEventDetailsTests +{ + private static readonly AdminEventDetails examplePayload = new() + { + EventId = Guid.Parse("67f410a3-c5e4-4326-a3ad-5812b9adfc06"), + Title = "Test Title", + Summary = "Test Summary", + Description = "Test Description", + DateStart = DateTimeOffset.Parse("2024-12-01T12:34:56+00:00"), + DateEnd = DateTimeOffset.Parse("2024-12-02T12:34:56+00:00"), + TimeZone = "Asia/Seoul", + IsActive = true, + OrganizerName = "Test Organizer", + OrganizerEmail = "organiser@testemail.com", + CoorganizerName = "Test Coorganizer", + CoorganizerEmail = "coorganiser@testemail.com", + MaxTokenCap = 1000, + DailyRequestCap = 4000, + }; + + private static readonly string exampleJson = """ + { + "eventId": "67f410a3-c5e4-4326-a3ad-5812b9adfc06", + "title": "Test Title", + "summary": "Test Summary", + "description": "Test Description", + "dateStart": "2024-12-01T12:34:56+00:00", + "dateEnd": "2024-12-02T12:34:56+00:00", + "timeZone": "Asia/Seoul", + "isActive": true, + "organizerName": "Test Organizer", + "organizerEmail": "organiser@testemail.com", + "coorganizerName": "Test Coorganizer", + "coorganizerEmail": "coorganiser@testemail.com", + "maxTokenCap": 1000, + "dailyRequestCap": 4000 + } + """; + + private static readonly JsonSerializerOptions options = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = true + }; + + [Fact] + public void Given_ExamplePayload_When_Serialized_Then_It_Should_Match_Json() + { + // Act + var serialised = JsonSerializer.Serialize(examplePayload, options); + + // Assert + serialised.Should().ContainAll( + "\"eventId\":", "\"67f410a3-c5e4-4326-a3ad-5812b9adfc06\"", + "\"title\":", "\"Test Title\"", + "\"summary\":", "\"Test Summary\"", + "\"description\":", "\"Test Description\"", + "\"dateStart\":", "\"2024-12-01T12:34:56+00:00\"", + "\"dateEnd\":", "\"2024-12-02T12:34:56+00:00\"", + "\"timeZone\":", "\"Asia/Seoul\"", + "\"isActive\":", "true", + "\"organizerName\":", "\"Test Organizer\"", + "\"organizerEmail\":", "\"organiser@testemail.com\"", + "\"coorganizerName\":", "\"Test Coorganizer\"", + "\"coorganizerEmail\":", "\"coorganiser@testemail.com\"", + "\"maxTokenCap\":", "1000", + "\"dailyRequestCap\":", "4000"); + } + + [Fact] + public void Given_ExampleJson_When_Deserialized_Then_It_Should_Match_Object() + { + // Arrange & Act + var deserialised = JsonSerializer.Deserialize(exampleJson, options); + + // Assert + deserialised.Should().BeEquivalentTo(examplePayload); + } +} \ No newline at end of file diff --git a/test/AzureOpenAIProxy.ApiApp.Tests/Models/EventDetailsTests.cs b/test/AzureOpenAIProxy.ApiApp.Tests/Models/EventDetailsTests.cs new file mode 100644 index 00000000..f5e987d0 --- /dev/null +++ b/test/AzureOpenAIProxy.ApiApp.Tests/Models/EventDetailsTests.cs @@ -0,0 +1,58 @@ +using System.Text.Json; + +using FluentAssertions; + +namespace AzureOpenAIProxy.ApiApp.Tests.Models; + +public class EventDetailsTests +{ + private static readonly EventDetails examplePayload = new() + { + EventId = Guid.Parse("67f410a3-c5e4-4326-a3ad-5812b9adfc06"), + Title = "Test Title", + Summary = "Test Summary", + MaxTokenCap = 1000, + DailyRequestCap = 4000, + }; + + private static readonly string exampleJson = """ + { + "eventId": "67f410a3-c5e4-4326-a3ad-5812b9adfc06", + "title": "Test Title", + "summary": "Test Summary", + "maxTokenCap": 1000, + "dailyRequestCap": 4000 + } + """; + + private static readonly JsonSerializerOptions options = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = true + }; + + [Fact] + public void Given_ExamplePayload_When_Serialized_Then_It_Should_Match_Json() + { + // Act + var serialised = JsonSerializer.Serialize(examplePayload, options); + + // Assert + serialised.Should().ContainAll( + "\"eventId\":", "\"67f410a3-c5e4-4326-a3ad-5812b9adfc06\"", + "\"title\":", "\"Test Title\"", + "\"summary\":", "\"Test Summary\"", + "\"maxTokenCap\":", "1000", + "\"dailyRequestCap\":", "4000"); + } + + [Fact] + public void Given_ExampleJson_When_Deserialized_Then_It_Should_Match_Object() + { + // Arrange & Act + var deserialised = JsonSerializer.Deserialize(exampleJson, options); + + // Assert + deserialised.Should().BeEquivalentTo(examplePayload); + } +} \ No newline at end of file diff --git a/test/AzureOpenAIProxy.AppHost.Tests/ApiApp/Endpoints/AdminCreateEventsOpenApiTests.cs b/test/AzureOpenAIProxy.AppHost.Tests/ApiApp/Endpoints/AdminCreateEventsOpenApiTests.cs index 79e566f4..466600c8 100644 --- a/test/AzureOpenAIProxy.AppHost.Tests/ApiApp/Endpoints/AdminCreateEventsOpenApiTests.cs +++ b/test/AzureOpenAIProxy.AppHost.Tests/ApiApp/Endpoints/AdminCreateEventsOpenApiTests.cs @@ -4,6 +4,8 @@ using FluentAssertions; +using IdentityModel.Client; + namespace AzureOpenAIProxy.AppHost.Tests.ApiApp.Endpoints; public class AdminCreateEventsOpenApiTests(AspireAppHostFixture host) : IClassFixture @@ -165,4 +167,106 @@ public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Mod .TryGetProperty("AdminEventDetails", out var property) ? property : default; result.ValueKind.Should().Be(JsonValueKind.Object); } + + [Theory] + [InlineData("eventId", true)] + [InlineData("title", true)] + [InlineData("summary", true)] + [InlineData("description", false)] + [InlineData("dateStart", true)] + [InlineData("dateEnd", true)] + [InlineData("timeZone", true)] + [InlineData("isActive", true)] + [InlineData("organizerName", true)] + [InlineData("organizerEmail", true)] + [InlineData("coorganizerName", false)] + [InlineData("coorganizerEmail", false)] + [InlineData("maxTokenCap", true)] + [InlineData("dailyRequestCap", 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"); + await host.ResourceNotificationService.WaitForResourceAsync("apiapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30)); + + // 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("AdminEventDetails") + .TryGetStringArray("required") + .ToList(); + result.Contains(attribute).Should().Be(isRequired); + } + + [Theory] + [InlineData("eventId")] + [InlineData("title")] + [InlineData("summary")] + [InlineData("description")] + [InlineData("dateStart")] + [InlineData("dateEnd")] + [InlineData("timeZone")] + [InlineData("isActive")] + [InlineData("organizerName")] + [InlineData("organizerEmail")] + [InlineData("coorganizerName")] + [InlineData("coorganizerEmail")] + [InlineData("maxTokenCap")] + [InlineData("dailyRequestCap")] + public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Property(string attribute) + { + // Arrange + using var httpClient = host.App!.CreateHttpClient("apiapp"); + await host.ResourceNotificationService.WaitForResourceAsync("apiapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30)); + + // 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("AdminEventDetails") + .GetProperty("properties") + .TryGetProperty(attribute, out var property) ? property : default; + result.ValueKind.Should().Be(JsonValueKind.Object); + } + + [Theory] + [InlineData("eventId", "string")] + [InlineData("title", "string")] + [InlineData("summary", "string")] + [InlineData("description", "string")] + [InlineData("dateStart", "string")] + [InlineData("dateEnd", "string")] + [InlineData("timeZone", "string")] + [InlineData("isActive", "boolean")] + [InlineData("organizerName", "string")] + [InlineData("organizerEmail", "string")] + [InlineData("coorganizerName", "string")] + [InlineData("coorganizerEmail", "string")] + [InlineData("maxTokenCap", "integer")] + [InlineData("dailyRequestCap", "integer")] + 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"); + await host.ResourceNotificationService.WaitForResourceAsync("apiapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30)); + + // 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("AdminEventDetails") + .GetProperty("properties") + .GetProperty(attribute); + result.TryGetString("type").Should().Be(type); + } } diff --git a/test/AzureOpenAIProxy.AppHost.Tests/ApiApp/Endpoints/AdminGetEventDetailsOpenApiTests.cs b/test/AzureOpenAIProxy.AppHost.Tests/ApiApp/Endpoints/AdminGetEventDetailsOpenApiTests.cs index dbb0e846..c2d84bfb 100644 --- a/test/AzureOpenAIProxy.AppHost.Tests/ApiApp/Endpoints/AdminGetEventDetailsOpenApiTests.cs +++ b/test/AzureOpenAIProxy.AppHost.Tests/ApiApp/Endpoints/AdminGetEventDetailsOpenApiTests.cs @@ -4,8 +4,6 @@ using FluentAssertions; -using IdentityModel.Client; - namespace AzureOpenAIProxy.AppHost.Tests.ApiApp.Endpoints; public class AdminGetEventDetailsOpenApiTests(AspireAppHostFixture host) : IClassFixture @@ -154,6 +152,7 @@ public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Obj [Theory] [InlineData("200")] [InlineData("401")] + [InlineData("404")] [InlineData("500")] public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Response(string attribute) { @@ -173,141 +172,4 @@ public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Res .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_Schemas() - { - // Arrange - using var httpClient = host.App!.CreateHttpClient("apiapp"); - await host.ResourceNotificationService.WaitForResourceAsync("apiapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30)); - - // Act - var json = await httpClient.GetStringAsync("/swagger/v1.0.0/swagger.json"); - var openapi = JsonSerializer.Deserialize(json); - - // Assert - var result = openapi!.RootElement.GetProperty("components") - .TryGetProperty("schemas", 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"); - await host.ResourceNotificationService.WaitForResourceAsync("apiapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30)); - - // 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("AdminEventDetails", out var property) ? property : default; - result.ValueKind.Should().Be(JsonValueKind.Object); - } - - [Theory] - [InlineData("eventId", true)] - [InlineData("title", true)] - [InlineData("summary", true)] - [InlineData("description", false)] - [InlineData("dateStart", true)] - [InlineData("dateEnd", true)] - [InlineData("timeZone", true)] - [InlineData("isActive", true)] - [InlineData("organizerName", true)] - [InlineData("organizerEmail", true)] - [InlineData("coorganizerName", false)] - [InlineData("coorganizerEmail", false)] - [InlineData("maxTokenCap", true)] - [InlineData("dailyRequestCap", 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"); - await host.ResourceNotificationService.WaitForResourceAsync("apiapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30)); - - // 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("AdminEventDetails") - .TryGetStringArray("required") - .ToList(); - result.Contains(attribute).Should().Be(isRequired); - } - - [Theory] - [InlineData("eventId")] - [InlineData("title")] - [InlineData("summary")] - [InlineData("description")] - [InlineData("dateStart")] - [InlineData("dateEnd")] - [InlineData("timeZone")] - [InlineData("isActive")] - [InlineData("organizerName")] - [InlineData("organizerEmail")] - [InlineData("coorganizerName")] - [InlineData("coorganizerEmail")] - [InlineData("maxTokenCap")] - [InlineData("dailyRequestCap")] - public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Property(string attribute) - { - // Arrange - using var httpClient = host.App!.CreateHttpClient("apiapp"); - await host.ResourceNotificationService.WaitForResourceAsync("apiapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30)); - - // 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("AdminEventDetails") - .GetProperty("properties") - .TryGetProperty(attribute, out var property) ? property : default; - result.ValueKind.Should().Be(JsonValueKind.Object); - } - - [Theory] - [InlineData("eventId", "string")] - [InlineData("title", "string")] - [InlineData("summary", "string")] - [InlineData("description", "string")] - [InlineData("dateStart", "string")] - [InlineData("dateEnd", "string")] - [InlineData("timeZone", "string")] - [InlineData("isActive", "boolean")] - [InlineData("organizerName", "string")] - [InlineData("organizerEmail", "string")] - [InlineData("coorganizerName", "string")] - [InlineData("coorganizerEmail", "string")] - [InlineData("maxTokenCap", "integer")] - [InlineData("dailyRequestCap", "integer")] - 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"); - await host.ResourceNotificationService.WaitForResourceAsync("apiapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30)); - - // 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("AdminEventDetails") - .GetProperty("properties") - .GetProperty(attribute); - result.TryGetString("type").Should().Be(type); - } }