From 61a17ad6ffa806138a717bc0b6eb7d4378fcbcce Mon Sep 17 00:00:00 2001 From: Justin Yoo Date: Thu, 15 Aug 2024 15:59:00 +0900 Subject: [PATCH] Add extension method for OpenAISettingsBuilder --- .../OpenAISettingsBuilderExtensions.cs | 29 +++ .../Extensions/ServiceCollectionExtensions.cs | 17 +- .../OpenAISettingsBuilderExtensionsTests.cs | 220 ++++++++++++++++++ 3 files changed, 255 insertions(+), 11 deletions(-) create mode 100644 src/AzureOpenAIProxy.ApiApp/Extensions/OpenAISettingsBuilderExtensions.cs create mode 100644 test/AzureOpenAIProxy.ApiApp.Tests/Extensions/OpenAISettingsBuilderExtensionsTests.cs diff --git a/src/AzureOpenAIProxy.ApiApp/Extensions/OpenAISettingsBuilderExtensions.cs b/src/AzureOpenAIProxy.ApiApp/Extensions/OpenAISettingsBuilderExtensions.cs new file mode 100644 index 00000000..e0549e29 --- /dev/null +++ b/src/AzureOpenAIProxy.ApiApp/Extensions/OpenAISettingsBuilderExtensions.cs @@ -0,0 +1,29 @@ +using AzureOpenAIProxy.ApiApp.Builders; +using AzureOpenAIProxy.ApiApp.Configurations; + +namespace AzureOpenAIProxy.ApiApp.Extensions; + +/// +/// This represents the extensions entity for the OpenAI settings builder. +/// +public static class OpenAISettingsBuilderExtensions +{ + /// + /// Sets the list of instances from the app settings. + /// + /// instance. + /// instance. + /// Returns instance. + public static IOpenAISettingsBuilder WithAppSettings(this IOpenAISettingsBuilder builder, IServiceProvider sp) + { + var configuration = sp.GetService() + ?? throw new InvalidOperationException($"{nameof(IConfiguration)} service is not registered."); + + var settings = configuration.GetSection(AzureSettings.Name).GetSection(OpenAISettings.Name).Get() + ?? throw new InvalidOperationException($"{nameof(OpenAISettings)} could not be retrieved from the configuration."); + + builder.SetOpenAIInstances(settings.Instances); + + return builder; + } +} diff --git a/src/AzureOpenAIProxy.ApiApp/Extensions/ServiceCollectionExtensions.cs b/src/AzureOpenAIProxy.ApiApp/Extensions/ServiceCollectionExtensions.cs index de22edeb..d39a1712 100644 --- a/src/AzureOpenAIProxy.ApiApp/Extensions/ServiceCollectionExtensions.cs +++ b/src/AzureOpenAIProxy.ApiApp/Extensions/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ -using AzureOpenAIProxy.ApiApp.Configurations; +using AzureOpenAIProxy.ApiApp.Builders; +using AzureOpenAIProxy.ApiApp.Configurations; using AzureOpenAIProxy.ApiApp.Filters; using AzureOpenAIProxy.ApiApp.Services; @@ -20,16 +21,10 @@ public static IServiceCollection AddOpenAISettings(this IServiceCollection servi { services.AddSingleton(sp => { - //var settings = new OpenAISettingsBuilder() - // .WithAppSettings(sp) - // .WithKeyVault(sp) - // .Build(); - - var configuration = sp.GetService() - ?? throw new InvalidOperationException($"{nameof(IConfiguration)} service is not registered."); - - var settings = configuration.GetSection(AzureSettings.Name).GetSection(OpenAISettings.Name).Get() - ?? throw new InvalidOperationException($"{nameof(OpenAISettings)} could not be retrieved from the configuration."); + var settings = new OpenAISettingsBuilder() + .WithAppSettings(sp) + //.WithKeyVault(sp) + .Build(); return settings; }); diff --git a/test/AzureOpenAIProxy.ApiApp.Tests/Extensions/OpenAISettingsBuilderExtensionsTests.cs b/test/AzureOpenAIProxy.ApiApp.Tests/Extensions/OpenAISettingsBuilderExtensionsTests.cs new file mode 100644 index 00000000..8ab757cd --- /dev/null +++ b/test/AzureOpenAIProxy.ApiApp.Tests/Extensions/OpenAISettingsBuilderExtensionsTests.cs @@ -0,0 +1,220 @@ +using AzureOpenAIProxy.ApiApp.Builders; +using AzureOpenAIProxy.ApiApp.Extensions; + +using FluentAssertions; + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +using NSubstitute; + +namespace AzureOpenAIProxy.ApiApp.Tests.Extensions; + +public class OpenAISettingsBuilderExtensionsTests +{ + [Fact] + public void Given_Null_AppSettings_When_Invoked_WithAppSettings_Then_It_Should_Throw_Exception() + { + // Arrange + var config = default(IConfiguration); + + var sp = Substitute.For(); + ServiceProviderServiceExtensions.GetService(sp).Returns(config); + + var builder = new OpenAISettingsBuilder(); + + // Act + Action action = () => builder.WithAppSettings(sp) + .Build(); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void Given_Empty_AzureSettings_When_Invoked_WithAppSettings_Then_It_Should_Throw_Exception() + { + // Arrange + var dict = new Dictionary() + { + { "Azure", string.Empty } + }; +#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(); +#pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. + + var sp = Substitute.For(); + ServiceProviderServiceExtensions.GetService(sp).Returns(config); + + var builder = new OpenAISettingsBuilder(); + + // Act + Action action = () => builder.WithAppSettings(sp) + .Build(); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void Given_Empty_OpenAISettings_When_Invoked_WithAppSettings_Then_It_Should_Throw_Exception() + { + // Arrange + var dict = new Dictionary() + { + { "Azure:OpenAI", string.Empty } + }; +#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(); +#pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. + + var sp = Substitute.For(); + ServiceProviderServiceExtensions.GetService(sp).Returns(config); + + var builder = new OpenAISettingsBuilder(); + + // Act + Action action = () => builder.WithAppSettings(sp) + .Build(); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void Given_Null_OpenAIInstances_When_Invoked_WithAppSettings_Then_It_Should_Return_Empty_Instance() + { + // Arrange + var dict = new Dictionary() + { + { "Azure:OpenAI:Instances", string.Empty } + }; +#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(); +#pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. + + var sp = Substitute.For(); + ServiceProviderServiceExtensions.GetService(sp).Returns(config); + + var builder = new OpenAISettingsBuilder(); + + // Act + var result = builder.WithAppSettings(sp) + .Build(); + + // Assert + result.Instances.Should().NotBeNull() + .And.BeEmpty(); + } + + [Fact] + public void Given_AppSettings_When_Invoked_WithAppSettings_Then_It_Should_Return_Instance() + { + // Arrange + var dict = new Dictionary() + { + { "Azure:OpenAI:Instances:0", string.Empty }, + }; +#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(); +#pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. + + var sp = Substitute.For(); + ServiceProviderServiceExtensions.GetService(sp).Returns(config); + + var builder = new OpenAISettingsBuilder(); + + // Act + var result = builder.WithAppSettings(sp) + .Build(); + + // Assert + result.Instances.Should().NotBeNull() + .And.HaveCount(1); + } + + [Theory] + [InlineData("http://localhost")] + public void Given_AppSettings_When_Invoked_WithAppSettings_Then_It_Should_Return_Endpoint(string endpoint) + { + // Arrange + var dict = new Dictionary() + { + { "Azure:OpenAI:Instances:0:Endpoint", endpoint }, + }; +#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(); +#pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. + + var sp = Substitute.For(); + ServiceProviderServiceExtensions.GetService(sp).Returns(config); + + var builder = new OpenAISettingsBuilder(); + + // Act + var result = builder.WithAppSettings(sp) + .Build(); + + // Assert + result.Instances.First().Endpoint.Should().Be(endpoint); + } + + [Theory] + [InlineData("api-key")] + public void Given_AppSettings_When_Invoked_WithAppSettings_Then_It_Should_Return_ApiKey(string apiKey) + { + // Arrange + var dict = new Dictionary() + { + { "Azure:OpenAI:Instances:0:ApiKey", apiKey }, + }; +#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(); +#pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. + + var sp = Substitute.For(); + ServiceProviderServiceExtensions.GetService(sp).Returns(config); + + var builder = new OpenAISettingsBuilder(); + + // Act + var result = builder.WithAppSettings(sp) + .Build(); + + // Assert + result.Instances.First().ApiKey.Should().Be(apiKey); + } + + [Theory] + [InlineData("deployment-name-1")] + [InlineData("deployment-name-1", "deployment-name-2")] + [InlineData("deployment-name-1", "deployment-name-2", "deployment-name-3")] + public void Given_AppSettings_When_Invoked_WithAppSettings_Then_It_Should_Return_Instances(params string[] deploymentNames) + { + // Arrange + var dict = new Dictionary(); + for (var i = 0; i < deploymentNames.Length; i++) + { + dict.Add($"Azure:OpenAI:Instances:0:DeploymentNames:{i}", deploymentNames[i]); + } +#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(); +#pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. + + var sp = Substitute.For(); + ServiceProviderServiceExtensions.GetService(sp).Returns(config); + + var builder = new OpenAISettingsBuilder(); + + // Act + var result = builder.WithAppSettings(sp) + .Build(); + + // Assert + result.Instances.First().DeploymentNames.Should().HaveCount(deploymentNames.Length); + foreach (var dn in deploymentNames) + { + result.Instances.First().DeploymentNames.Should().Contain(dn); + } + } +}