Skip to content

Commit 2699d1c

Browse files
committed
sync fork to upstream
2 parents cd0258a + af47323 commit 2699d1c

File tree

6 files changed

+412
-34
lines changed

6 files changed

+412
-34
lines changed

src/AzureOpenAIProxy.ApiApp/Endpoints/AdminEndpointUrls.cs

+8
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,12 @@ public static class AdminEndpointUrls
2222
/// - POST method for new event creation
2323
/// </remarks>
2424
public const string AdminEvents = "/admin/events";
25+
26+
/// <summary>
27+
/// Declares the admin resource details endpoint.
28+
/// </summary>
29+
/// <remarks>
30+
/// - POST method for new resource creation
31+
/// </remarks>
32+
public const string AdminResources = "/admin/resources";
2533
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using AzureOpenAIProxy.ApiApp.Models;
2+
using AzureOpenAIProxy.ApiApp.Services;
3+
4+
using Microsoft.AspNetCore.Mvc;
5+
6+
namespace AzureOpenAIProxy.ApiApp.Endpoints;
7+
8+
/// <summary>
9+
/// This represents the endpoint entity for resource details by admin
10+
/// </summary>
11+
public static class AdminResourceEndpoints
12+
{
13+
/// <summary>
14+
/// Adds the admin resource endpoint
15+
/// </summary>
16+
/// <param name="app"><see cref="WebApplication"/> instance.</param>
17+
/// <returns>Returns <see cref="RouteHandlerBuilder"/> instance.</returns>
18+
public static RouteHandlerBuilder AddNewAdminResource(this WebApplication app)
19+
{
20+
var builder = app.MapPost(AdminEndpointUrls.AdminResources, async (
21+
[FromBody] AdminResourceDetails payload,
22+
IAdminEventService service,
23+
ILoggerFactory loggerFactory) =>
24+
{
25+
var logger = loggerFactory.CreateLogger(nameof(AdminResourceEndpoints));
26+
logger.LogInformation("Received a new resource request");
27+
28+
if (payload is null)
29+
{
30+
logger.LogError("No payload found");
31+
32+
return Results.BadRequest("Payload is null");
33+
}
34+
35+
return await Task.FromResult(Results.Ok());
36+
})
37+
.Accepts<AdminResourceDetails>(contentType: "application/json")
38+
.Produces<AdminResourceDetails>(statusCode: StatusCodes.Status200OK, contentType: "application/json")
39+
.Produces(statusCode: StatusCodes.Status400BadRequest)
40+
.Produces(statusCode: StatusCodes.Status401Unauthorized)
41+
.Produces<string>(statusCode: StatusCodes.Status500InternalServerError, contentType: "text/plain")
42+
.WithTags("admin")
43+
.WithName("CreateAdminResource")
44+
.WithOpenApi(operation =>
45+
{
46+
operation.Summary = "Create admin resource";
47+
operation.Description = "Create admin resource";
48+
49+
return operation;
50+
});
51+
52+
return builder;
53+
}
54+
}

src/AzureOpenAIProxy.ApiApp/Filters/OpenApiTagFilter.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
1616
[
1717
new OpenApiTag { Name = "weather", Description = "Weather forecast operations" },
1818
new OpenApiTag { Name = "openai", Description = "Azure OpenAI operations" },
19-
new OpenApiTag { Name = "admin", Description = "Admin for organizing events" },
19+
new OpenApiTag { Name = "admin", Description = "Admin operations for managing events and resources" },
2020
new OpenApiTag { Name = "events", Description = "User events" }
2121
];
2222
}

src/AzureOpenAIProxy.ApiApp/Program.cs

+2
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,6 @@
6464
app.AddGetAdminEvent();
6565
app.AddUpdateAdminEvent();
6666

67+
app.AddNewAdminResource();
68+
6769
await app.RunAsync();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
using System.Text.Json;
2+
3+
using AzureOpenAIProxy.AppHost.Tests.Fixtures;
4+
5+
using FluentAssertions;
6+
7+
using IdentityModel.Client;
8+
9+
namespace AzureOpenAIProxy.AppHost.Tests.ApiApp.Endpoints;
10+
11+
public class AdminCreateResourcesOpenApiTests(AspireAppHostFixture host) : IClassFixture<AspireAppHostFixture>
12+
{
13+
[Fact]
14+
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Path()
15+
{
16+
// Arrange
17+
using var httpClient = host.App!.CreateHttpClient("apiapp");
18+
await host.ResourceNotificationService.WaitForResourceAsync("apiapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30));
19+
20+
// Act
21+
var json = await httpClient.GetStringAsync("/swagger/v1.0.0/swagger.json");
22+
var openapi = JsonSerializer.Deserialize<JsonDocument>(json);
23+
24+
// Assert
25+
var result = openapi!.RootElement.GetProperty("paths")
26+
.GetProperty("/admin/resources");
27+
result.ValueKind.Should().Be(JsonValueKind.Object);
28+
}
29+
30+
[Fact]
31+
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Verb()
32+
{
33+
// Arrange
34+
using var httpClient = host.App!.CreateHttpClient("apiapp");
35+
await host.ResourceNotificationService.WaitForResourceAsync("apiapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30));
36+
37+
// Act
38+
var json = await httpClient.GetStringAsync("/swagger/v1.0.0/swagger.json");
39+
var openapi = JsonSerializer.Deserialize<JsonDocument>(json);
40+
41+
// Assert
42+
var result = openapi!.RootElement.GetProperty("paths")
43+
.GetProperty("/admin/resources")
44+
.GetProperty("post");
45+
result.ValueKind.Should().Be(JsonValueKind.Object);
46+
}
47+
48+
[Theory]
49+
[InlineData("admin")]
50+
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Tags(string tag)
51+
{
52+
// Arrange
53+
using var httpClient = host.App!.CreateHttpClient("apiapp");
54+
await host.ResourceNotificationService.WaitForResourceAsync("apiapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30));
55+
56+
// Act
57+
var json = await httpClient.GetStringAsync("/swagger/v1.0.0/swagger.json");
58+
var openapi = JsonSerializer.Deserialize<JsonDocument>(json);
59+
60+
// Assert
61+
var result = openapi!.RootElement.GetProperty("paths")
62+
.GetProperty("/admin/resources")
63+
.GetProperty("post")
64+
.GetProperty("tags");
65+
result.ValueKind.Should().Be(JsonValueKind.Array);
66+
result.EnumerateArray().Select(p => p.GetString()).Should().Contain(tag);
67+
}
68+
69+
[Theory]
70+
[InlineData("summary")]
71+
[InlineData("description")]
72+
[InlineData("operationId")]
73+
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Value(string attribute)
74+
{
75+
// Arrange
76+
using var httpClient = host.App!.CreateHttpClient("apiapp");
77+
await host.ResourceNotificationService.WaitForResourceAsync("apiapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30));
78+
79+
// Act
80+
var json = await httpClient.GetStringAsync("/swagger/v1.0.0/swagger.json");
81+
var openapi = JsonSerializer.Deserialize<JsonDocument>(json);
82+
83+
// Assert
84+
var result = openapi!.RootElement.GetProperty("paths")
85+
.GetProperty("/admin/resources")
86+
.GetProperty("post")
87+
.GetProperty(attribute);
88+
result.ValueKind.Should().Be(JsonValueKind.String);
89+
}
90+
91+
[Theory]
92+
[InlineData("requestBody")]
93+
[InlineData("responses")]
94+
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Object(string attribute)
95+
{
96+
// Arrange
97+
using var httpClient = host.App!.CreateHttpClient("apiapp");
98+
await host.ResourceNotificationService.WaitForResourceAsync("apiapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30));
99+
100+
// Act
101+
var json = await httpClient.GetStringAsync("/swagger/v1.0.0/swagger.json");
102+
var openapi = JsonSerializer.Deserialize<JsonDocument>(json);
103+
104+
// Assert
105+
var result = openapi!.RootElement.GetProperty("paths")
106+
.GetProperty("/admin/resources")
107+
.GetProperty("post")
108+
.GetProperty(attribute);
109+
result.ValueKind.Should().Be(JsonValueKind.Object);
110+
}
111+
112+
[Theory]
113+
[InlineData("200")]
114+
[InlineData("400")]
115+
[InlineData("401")]
116+
[InlineData("500")]
117+
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Response(string attribute)
118+
{
119+
// Arrange
120+
using var httpClient = host.App!.CreateHttpClient("apiapp");
121+
await host.ResourceNotificationService.WaitForResourceAsync("apiapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30));
122+
123+
// Act
124+
var json = await httpClient.GetStringAsync("/swagger/v1.0.0/swagger.json");
125+
var openapi = JsonSerializer.Deserialize<JsonDocument>(json);
126+
127+
// Assert
128+
var result = openapi!.RootElement.GetProperty("paths")
129+
.GetProperty("/admin/resources")
130+
.GetProperty("post")
131+
.GetProperty("responses")
132+
.GetProperty(attribute);
133+
result.ValueKind.Should().Be(JsonValueKind.Object);
134+
}
135+
136+
[Fact]
137+
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Schemas()
138+
{
139+
// Arrange
140+
using var httpClient = host.App!.CreateHttpClient("apiapp");
141+
await host.ResourceNotificationService.WaitForResourceAsync("apiapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30));
142+
143+
// Act
144+
var json = await httpClient.GetStringAsync("/swagger/v1.0.0/swagger.json");
145+
var openapi = JsonSerializer.Deserialize<JsonDocument>(json);
146+
147+
// Assert
148+
var result = openapi!.RootElement.GetProperty("components")
149+
.GetProperty("schemas");
150+
result.ValueKind.Should().Be(JsonValueKind.Object);
151+
}
152+
153+
[Fact]
154+
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Model()
155+
{
156+
// Arrange
157+
using var httpClient = host.App!.CreateHttpClient("apiapp");
158+
await host.ResourceNotificationService.WaitForResourceAsync("apiapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30));
159+
160+
// Act
161+
var json = await httpClient.GetStringAsync("/swagger/v1.0.0/swagger.json");
162+
var openapi = JsonSerializer.Deserialize<JsonDocument>(json);
163+
164+
// Assert
165+
var result = openapi!.RootElement.GetProperty("components")
166+
.GetProperty("schemas")
167+
.GetProperty("AdminResourceDetails");
168+
result.ValueKind.Should().Be(JsonValueKind.Object);
169+
}
170+
171+
[Theory]
172+
[InlineData("resourceId", true)]
173+
[InlineData("friendlyName", true)]
174+
[InlineData("deploymentName", true)]
175+
[InlineData("resourceType", true)]
176+
[InlineData("endpoint", true)]
177+
[InlineData("apiKey", true)]
178+
[InlineData("region", true)]
179+
[InlineData("isActive", true)]
180+
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Required(string attribute, bool isRequired)
181+
{
182+
// Arrange
183+
using var httpClient = host.App!.CreateHttpClient("apiapp");
184+
await host.ResourceNotificationService.WaitForResourceAsync("apiapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30));
185+
186+
// Act
187+
var json = await httpClient.GetStringAsync("/swagger/v1.0.0/swagger.json");
188+
var openapi = JsonSerializer.Deserialize<JsonDocument>(json);
189+
190+
// Assert
191+
var result = openapi!.RootElement.GetProperty("components")
192+
.GetProperty("schemas")
193+
.GetProperty("AdminResourceDetails")
194+
.TryGetStringArray("required")
195+
.ToList();
196+
result.Contains(attribute).Should().Be(isRequired);
197+
}
198+
199+
[Theory]
200+
[InlineData("resourceId")]
201+
[InlineData("friendlyName")]
202+
[InlineData("deploymentName")]
203+
[InlineData("resourceType")]
204+
[InlineData("endpoint")]
205+
[InlineData("apiKey")]
206+
[InlineData("region")]
207+
[InlineData("isActive")]
208+
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Property(string attribute)
209+
{
210+
// Arrange
211+
using var httpClient = host.App!.CreateHttpClient("apiapp");
212+
await host.ResourceNotificationService.WaitForResourceAsync("apiapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30));
213+
214+
// Act
215+
var json = await httpClient.GetStringAsync("/swagger/v1.0.0/swagger.json");
216+
var openapi = JsonSerializer.Deserialize<JsonDocument>(json);
217+
218+
// Assert
219+
var result = openapi!.RootElement.GetProperty("components")
220+
.GetProperty("schemas")
221+
.GetProperty("AdminResourceDetails")
222+
.GetProperty("properties")
223+
.GetProperty(attribute);
224+
result.ValueKind.Should().Be(JsonValueKind.Object);
225+
}
226+
227+
[Theory]
228+
[InlineData("resourceId", "string")]
229+
[InlineData("friendlyName", "string")]
230+
[InlineData("deploymentName", "string")]
231+
[InlineData("resourceType", "string")]
232+
[InlineData("endpoint", "string")]
233+
[InlineData("apiKey", "string")]
234+
[InlineData("region", "string")]
235+
[InlineData("isActive", "boolean")]
236+
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Type(string attribute, string type)
237+
{
238+
// Arrange
239+
using var httpClient = host.App!.CreateHttpClient("apiapp");
240+
await host.ResourceNotificationService.WaitForResourceAsync("apiapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30));
241+
242+
// Act
243+
var json = await httpClient.GetStringAsync("/swagger/v1.0.0/swagger.json");
244+
var openapi = JsonSerializer.Deserialize<JsonDocument>(json);
245+
246+
// Assert
247+
var result = openapi!.RootElement.GetProperty("components")
248+
.GetProperty("schemas")
249+
.GetProperty("AdminResourceDetails")
250+
.GetProperty("properties")
251+
.GetProperty(attribute);
252+
253+
if (!result.TryGetProperty("type", out var typeProperty))
254+
{
255+
var refPath = result.TryGetString("$ref").TrimStart('#', '/').Split('/');
256+
var refSchema = openapi.RootElement;
257+
258+
foreach (var part in refPath)
259+
{
260+
refSchema = refSchema.GetProperty(part);
261+
}
262+
263+
typeProperty = refSchema.GetProperty("type");
264+
}
265+
266+
typeProperty.GetString().Should().Be(type);
267+
}
268+
269+
[Fact]
270+
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Validate_ResourceType_As_Enum()
271+
{
272+
// Arrange
273+
using var httpClient = host.App!.CreateHttpClient("apiapp");
274+
await host.ResourceNotificationService.WaitForResourceAsync("apiapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30));
275+
276+
// Act
277+
var json = await httpClient.GetStringAsync("/swagger/v1.0.0/swagger.json");
278+
var openapi = JsonSerializer.Deserialize<JsonDocument>(json);
279+
280+
// Assert
281+
var result = openapi!.RootElement.GetProperty("components")
282+
.GetProperty("schemas")
283+
.GetProperty("AdminResourceDetails")
284+
.GetProperty("properties")
285+
.GetProperty("resourceType");
286+
287+
var refPath = result.TryGetString("$ref").TrimStart('#', '/').Split('/');
288+
var refSchema = openapi.RootElement;
289+
290+
foreach (var part in refPath)
291+
{
292+
refSchema = refSchema.GetProperty(part);
293+
}
294+
295+
var enumValues = refSchema.GetProperty("enum")
296+
.EnumerateArray()
297+
.Select(p => p.GetString())
298+
.ToList();
299+
300+
enumValues.Should().BeEquivalentTo(["none", "chat", "image"]);
301+
}
302+
}

0 commit comments

Comments
 (0)