diff --git a/.github/workflows/azure-dev-build-only.yml b/.github/workflows/azure-dev-build-only.yml index 15926842..58979745 100644 --- a/.github/workflows/azure-dev-build-only.yml +++ b/.github/workflows/azure-dev-build-only.yml @@ -25,8 +25,8 @@ jobs: - name: Install local certs shell: pwsh run: | - dotnet dev-certs https --clean - dotnet dev-certs https --trust + dotnet tool update -g linux-dev-certs + dotnet linux-dev-certs install - name: Install Aspire workload shell: pwsh diff --git a/.github/workflows/azure-dev.yml b/.github/workflows/azure-dev.yml index 1bbdc574..0f6924aa 100644 --- a/.github/workflows/azure-dev.yml +++ b/.github/workflows/azure-dev.yml @@ -34,8 +34,8 @@ jobs: - name: Install local certs shell: pwsh run: | - dotnet dev-certs https --clean - dotnet dev-certs https --trust + dotnet tool update -g linux-dev-certs + dotnet linux-dev-certs install - name: Install Aspire workload shell: pwsh diff --git a/AzureOpenAIProxy.sln b/AzureOpenAIProxy.sln index ea1ee86f..bd88d76b 100644 --- a/AzureOpenAIProxy.sln +++ b/AzureOpenAIProxy.sln @@ -17,6 +17,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{A69C5782-F EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureOpenAIProxy.AppHost.Tests", "test\AzureOpenAIProxy.AppHost.Tests\AzureOpenAIProxy.AppHost.Tests.csproj", "{E8994388-24FA-4221-8E46-4CD1DA96C585}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureOpenAIProxy.PlaygroundApp.Tests", "test\AzureOpenAIProxy.PlaygroundApp.Tests\AzureOpenAIProxy.PlaygroundApp.Tests.csproj", "{0AE9860A-0F8D-49EF-B375-BF95374A1EDA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureOpenAIProxy.ApiApp.Tests", "test\AzureOpenAIProxy.ApiApp.Tests\AzureOpenAIProxy.ApiApp.Tests.csproj", "{0E772300-9263-49CA-8E26-D0B9CC584202}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -43,6 +47,14 @@ Global {E8994388-24FA-4221-8E46-4CD1DA96C585}.Debug|Any CPU.Build.0 = Debug|Any CPU {E8994388-24FA-4221-8E46-4CD1DA96C585}.Release|Any CPU.ActiveCfg = Release|Any CPU {E8994388-24FA-4221-8E46-4CD1DA96C585}.Release|Any CPU.Build.0 = Release|Any CPU + {0AE9860A-0F8D-49EF-B375-BF95374A1EDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0AE9860A-0F8D-49EF-B375-BF95374A1EDA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0AE9860A-0F8D-49EF-B375-BF95374A1EDA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0AE9860A-0F8D-49EF-B375-BF95374A1EDA}.Release|Any CPU.Build.0 = Release|Any CPU + {0E772300-9263-49CA-8E26-D0B9CC584202}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E772300-9263-49CA-8E26-D0B9CC584202}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E772300-9263-49CA-8E26-D0B9CC584202}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E772300-9263-49CA-8E26-D0B9CC584202}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -53,6 +65,8 @@ Global {A8BBA7EA-5615-470C-8D2E-77CCCAE3F51E} = {004CE987-A209-4933-884A-2F59506CDC9E} {1E3B6609-56EC-4FEC-A689-34F9E5DCCF22} = {004CE987-A209-4933-884A-2F59506CDC9E} {E8994388-24FA-4221-8E46-4CD1DA96C585} = {A69C5782-F0B3-46B9-8089-3E7216AAAB25} + {0AE9860A-0F8D-49EF-B375-BF95374A1EDA} = {A69C5782-F0B3-46B9-8089-3E7216AAAB25} + {0E772300-9263-49CA-8E26-D0B9CC584202} = {A69C5782-F0B3-46B9-8089-3E7216AAAB25} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1D2D444D-0384-4C78-B968-156C7C848DE8} diff --git a/Directory.Build.props b/Directory.Build.props index 664fe504..7236a166 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -17,7 +17,10 @@ 6.* 6.* + 6.* 17.* + 5.* + 8.* 2.* diff --git a/src/AzureOpenAIProxy.ApiApp/Builders/OpenAIServiceRequestBuilder.cs b/src/AzureOpenAIProxy.ApiApp/Builders/OpenAIServiceRequestBuilder.cs index b46b451e..50283df2 100644 --- a/src/AzureOpenAIProxy.ApiApp/Builders/OpenAIServiceRequestBuilder.cs +++ b/src/AzureOpenAIProxy.ApiApp/Builders/OpenAIServiceRequestBuilder.cs @@ -64,10 +64,10 @@ public IOpenAIServiceRequestBuilder SetOpenAIInstance(OpenAISettings openaiSetti var instances = (openaiSettings ?? throw new ArgumentNullException(nameof(openaiSettings))) .Instances .Where(p => p.DeploymentNames!.Contains(deploymentName) == true); - var openai = instances.Skip(openaiSettings.Random.Next(instances.Count())).First(); + var openai = instances.Skip(openaiSettings.Random.Next(instances.Count())).FirstOrDefault(); - this._endpoint = openai.Endpoint; - this._apiKey = openai.ApiKey; + this._endpoint = openai?.Endpoint; + this._apiKey = openai?.ApiKey; return this; } diff --git a/test/AzureOpenAIProxy.ApiApp.Tests/AzureOpenAIProxy.ApiApp.Tests.csproj b/test/AzureOpenAIProxy.ApiApp.Tests/AzureOpenAIProxy.ApiApp.Tests.csproj new file mode 100644 index 00000000..e5441763 --- /dev/null +++ b/test/AzureOpenAIProxy.ApiApp.Tests/AzureOpenAIProxy.ApiApp.Tests.csproj @@ -0,0 +1,34 @@ + + + + false + true + + AzureOpenAIProxy.ApiApp.Tests + AzureOpenAIProxy.ApiApp.Tests + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/test/AzureOpenAIProxy.ApiApp.Tests/Builders/OpenAIServiceRequestBuilderTests.cs b/test/AzureOpenAIProxy.ApiApp.Tests/Builders/OpenAIServiceRequestBuilderTests.cs new file mode 100644 index 00000000..fa737b5c --- /dev/null +++ b/test/AzureOpenAIProxy.ApiApp.Tests/Builders/OpenAIServiceRequestBuilderTests.cs @@ -0,0 +1,67 @@ +using System.Reflection; + +using AzureOpenAIProxy.ApiApp.Builders; +using AzureOpenAIProxy.ApiApp.Configurations; + +using FluentAssertions; + +namespace AzureOpenAIProxy.ApiApp.Tests.Builders; + +public class OpenAIServiceRequestBuilderTests +{ + [Theory] + [InlineData("https://localhost", "my-api-key", "deployment-name-1")] + public void Given_OpenAISettings_When_Invoked_SetOpenAIInstance_Then_It_Should_Store_Value(string endpoint, string apiKey, string deploymentName) + { + // Arrange + var instance = new OpenAIInstanceSettings + { + Endpoint = endpoint, + ApiKey = apiKey, + DeploymentNames = new List() { deploymentName }, + }; + var settings = new OpenAISettings() + { + Instances = { instance } + }; + + // Act + var builder = new OpenAIServiceRequestBuilder(); + builder.SetOpenAIInstance(settings, deploymentName); + + // Assert + var _endpoint = builder.GetType().GetField("_endpoint", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(builder) as string; + var _apiKey = builder.GetType().GetField("_apiKey", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(builder) as string; + + _endpoint.Should().Be(endpoint); + _apiKey.Should().Be(apiKey); + } + + [Theory] + [InlineData("https://localhost", "my-api-key", "deployment-name-1", "deployment-name-2")] + public void Given_OpenAISettings_When_Invoked_SetOpenAIInstance_Then_It_Should_Return_Null(string endpoint, string apiKey, string deploymentName, string nonDeploymentName) + { + // Arrange + var instance = new OpenAIInstanceSettings + { + Endpoint = endpoint, + ApiKey = apiKey, + DeploymentNames = new List() { deploymentName }, + }; + var settings = new OpenAISettings() + { + Instances = { instance } + }; + + // Act + var builder = new OpenAIServiceRequestBuilder(); + builder.SetOpenAIInstance(settings, nonDeploymentName); + + // Assert + var _endpoint = builder.GetType().GetField("_endpoint", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(builder) as string; + var _apiKey = builder.GetType().GetField("_apiKey", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(builder) as string; + + _endpoint.Should().BeNull(); + _apiKey.Should().BeNull(); + } +} \ No newline at end of file diff --git a/test/AzureOpenAIProxy.ApiApp.Tests/UnitTest1.cs b/test/AzureOpenAIProxy.ApiApp.Tests/UnitTest1.cs new file mode 100644 index 00000000..9dec26e7 --- /dev/null +++ b/test/AzureOpenAIProxy.ApiApp.Tests/UnitTest1.cs @@ -0,0 +1,9 @@ +namespace AzureOpenAIProxy.ApiApp.Tests; + +public class UnitTest1 +{ + [Fact] + public void Test1() + { + } +} \ No newline at end of file diff --git a/test/AzureOpenAIProxy.AppHost.Tests/AppHostProgramTests.cs b/test/AzureOpenAIProxy.AppHost.Tests/AppHostProgramTests.cs new file mode 100644 index 00000000..880bf4d7 --- /dev/null +++ b/test/AzureOpenAIProxy.AppHost.Tests/AppHostProgramTests.cs @@ -0,0 +1,26 @@ +using System.Net; + +using FluentAssertions; + +namespace AzureOpenAIProxy.Tests; + +public class AppHostProgramTests +{ + [Theory] + [InlineData("apiapp", "/health", HttpStatusCode.OK)] + [InlineData("playgroundapp", "/health", HttpStatusCode.OK)] + public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Healthy(string resourceName, string endpoint, HttpStatusCode statusCode) + { + // Arrange + var appHost = await DistributedApplicationTestingBuilder.CreateAsync(); + await using var app = await appHost.BuildAsync(); + await app.StartAsync(); + + // Act + var httpClient = app.CreateHttpClient(resourceName); + var response = await httpClient.GetAsync(endpoint); + + // Assert + response.StatusCode.Should().Be(statusCode); + } +} diff --git a/test/AzureOpenAIProxy.AppHost.Tests/AzureOpenAIProxy.AppHost.Tests.csproj b/test/AzureOpenAIProxy.AppHost.Tests/AzureOpenAIProxy.AppHost.Tests.csproj index 2002218a..f7cc56ea 100644 --- a/test/AzureOpenAIProxy.AppHost.Tests/AzureOpenAIProxy.AppHost.Tests.csproj +++ b/test/AzureOpenAIProxy.AppHost.Tests/AzureOpenAIProxy.AppHost.Tests.csproj @@ -14,7 +14,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + all diff --git a/test/AzureOpenAIProxy.AppHost.Tests/WebTests.cs b/test/AzureOpenAIProxy.AppHost.Tests/WebTests.cs deleted file mode 100644 index 6c83ad0d..00000000 --- a/test/AzureOpenAIProxy.AppHost.Tests/WebTests.cs +++ /dev/null @@ -1,22 +0,0 @@ -// using System.Net; - -// namespace AzureOpenAIProxy.Tests; - -// public class WebTests -// { -// [Fact] -// public async Task GetWebResourceRootReturnsOkStatusCode() -// { -// // Arrange -// var appHost = await DistributedApplicationTestingBuilder.CreateAsync(); -// await using var app = await appHost.BuildAsync(); -// await app.StartAsync(); - -// // Act -// var httpClient = app.CreateHttpClient("playgroundapp"); -// var response = await httpClient.GetAsync("/"); - -// // Assert -// Assert.Equal(HttpStatusCode.OK, response.StatusCode); -// } -// } diff --git a/test/AzureOpenAIProxy.PlaygroundApp.Tests/AzureOpenAIProxy.PlaygroundApp.Tests.csproj b/test/AzureOpenAIProxy.PlaygroundApp.Tests/AzureOpenAIProxy.PlaygroundApp.Tests.csproj new file mode 100644 index 00000000..1a9f53fe --- /dev/null +++ b/test/AzureOpenAIProxy.PlaygroundApp.Tests/AzureOpenAIProxy.PlaygroundApp.Tests.csproj @@ -0,0 +1,33 @@ + + + + false + true + + AzureOpenAIProxy.PlaygroundApp.Tests + AzureOpenAIProxy.PlaygroundApp.Tests + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/test/AzureOpenAIProxy.PlaygroundApp.Tests/UnitTest1.cs b/test/AzureOpenAIProxy.PlaygroundApp.Tests/UnitTest1.cs new file mode 100644 index 00000000..e41ada39 --- /dev/null +++ b/test/AzureOpenAIProxy.PlaygroundApp.Tests/UnitTest1.cs @@ -0,0 +1,9 @@ +namespace AzureOpenAIProxy.PlaygroundApp.Tests; + +public class UnitTest1 +{ + [Fact] + public void Test1() + { + } +} \ No newline at end of file