Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<AssemblyName>SemanticKernel.Connectors.OpenAI.UnitTests</AssemblyName>
Expand All @@ -7,7 +7,7 @@
<IsTestProject>true</IsTestProject>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<NoWarn>$(NoWarn);SKEXP0001;SKEXP0070;CS1591;IDE1006;RCS1261;CA1031;CA1308;CA1861;CA2007;CA2234;VSTHRD111</NoWarn>
<NoWarn>$(NoWarn);SKEXP0001;SKEXP0070;SKEXP0010;CS1591;IDE1006;RCS1261;CA1031;CA1308;CA1861;CA2007;CA2234;VSTHRD111</NoWarn>
</PropertyGroup>

<ItemGroup>
Expand All @@ -29,11 +29,21 @@

<ItemGroup>
<Compile Include="$(RepoRoot)/dotnet/src/InternalUtilities/test/AssertExtensions.cs" Link="%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
<Compile Include="$(RepoRoot)/dotnet/src/InternalUtilities/test/HttpMessageHandlerStub.cs" Link="%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\SemanticKernel.Core\SemanticKernel.Core.csproj" />
<ProjectReference Include="..\Connectors.OpenAIV2\Connectors.OpenAIV2.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="TestData\text-embeddings-multiple-response.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="TestData\text-embeddings-response.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.ClientModel;
using System.ClientModel.Primitives;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.Http;
using Moq;
using OpenAI;
using Xunit;

namespace SemanticKernel.Connectors.OpenAI.UnitTests.Core;
public partial class ClientCoreTests
{
[Fact]
public void ItCanBeInstantiatedAndPropertiesSetAsExpected()
{
// Act
var logger = new Mock<ILogger<ClientCoreTests>>().Object;
var openAIClient = new OpenAIClient(new ApiKeyCredential("key"));

var clientCoreModelConstructor = new ClientCore("model1", "apiKey");
var clientCoreOpenAIClientConstructor = new ClientCore("model1", openAIClient, logger: logger);

// Assert
Assert.NotNull(clientCoreModelConstructor);
Assert.NotNull(clientCoreOpenAIClientConstructor);

Assert.Equal("model1", clientCoreModelConstructor.ModelId);
Assert.Equal("model1", clientCoreOpenAIClientConstructor.ModelId);

Assert.NotNull(clientCoreModelConstructor.Client);
Assert.NotNull(clientCoreOpenAIClientConstructor.Client);
Assert.Equal(openAIClient, clientCoreOpenAIClientConstructor.Client);
Assert.Equal(NullLogger.Instance, clientCoreModelConstructor.Logger);
Assert.Equal(logger, clientCoreOpenAIClientConstructor.Logger);
}

[Theory]
[InlineData(null, null)]
[InlineData("http://localhost", null)]
[InlineData(null, "http://localhost")]
[InlineData("http://localhost-1", "http://localhost-2")]
public void ItUsesEndpointAsExpected(string? clientBaseAddress, string? providedEndpoint)
{
// Arrange
Uri? endpoint = null;
HttpClient? client = null;
if (providedEndpoint is not null)
{
endpoint = new Uri(providedEndpoint);
}

if (clientBaseAddress is not null)
{
client = new HttpClient { BaseAddress = new Uri(clientBaseAddress) };
}

// Act
var clientCore = new ClientCore("model", "apiKey", endpoint: endpoint, httpClient: client);

// Assert
Assert.Equal(endpoint ?? client?.BaseAddress ?? new Uri("https://api.openai.com/v1"), clientCore.Endpoint);

client?.Dispose();
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task ItAddOrganizationHeaderWhenProvidedAsync(bool organizationIdProvided)
{
using HttpMessageHandlerStub handler = new();
using HttpClient client = new(handler);
handler.ResponseToReturn = new HttpResponseMessage(System.Net.HttpStatusCode.OK);

// Act
var clientCore = new ClientCore(
modelId: "model",
apiKey: "test",
organizationId: (organizationIdProvided) ? "organization" : null,
httpClient: client);

var pipelineMessage = clientCore.Client.Pipeline.CreateMessage();
pipelineMessage.Request.Method = "POST";
pipelineMessage.Request.Uri = new Uri("http://localhost");
pipelineMessage.Request.Content = BinaryContent.Create(new BinaryData("test"));

// Assert
await clientCore.Client.Pipeline.SendAsync(pipelineMessage);

if (organizationIdProvided)
{
Assert.True(handler.RequestHeaders!.Contains("OpenAI-Organization"));
Assert.Equal("organization", handler.RequestHeaders.GetValues("OpenAI-Organization").FirstOrDefault());
}
else
{
Assert.False(handler.RequestHeaders!.Contains("OpenAI-Organization"));
}
}

[Fact]
public async Task ItAddSemanticKernelHeadersOnEachRequestAsync()
{
using HttpMessageHandlerStub handler = new();
using HttpClient client = new(handler);
handler.ResponseToReturn = new HttpResponseMessage(System.Net.HttpStatusCode.OK);

// Act
var clientCore = new ClientCore(modelId: "model", apiKey: "test", httpClient: client);

var pipelineMessage = clientCore.Client.Pipeline.CreateMessage();
pipelineMessage.Request.Method = "POST";
pipelineMessage.Request.Uri = new Uri("http://localhost");
pipelineMessage.Request.Content = BinaryContent.Create(new BinaryData("test"));

// Assert
await clientCore.Client.Pipeline.SendAsync(pipelineMessage);

Assert.True(handler.RequestHeaders!.Contains(HttpHeaderConstant.Names.SemanticKernelVersion));
Assert.Equal(HttpHeaderConstant.Values.GetAssemblyVersion(typeof(ClientCore)), handler.RequestHeaders.GetValues(HttpHeaderConstant.Names.SemanticKernelVersion).FirstOrDefault());

Assert.True(handler.RequestHeaders.Contains("User-Agent"));
Assert.Contains(HttpHeaderConstant.Values.UserAgent, handler.RequestHeaders.GetValues("User-Agent").FirstOrDefault());
}

[Fact]
public async Task ItDoNotAddSemanticKernelHeadersWhenOpenAIClientIsProvidedAsync()
{
using HttpMessageHandlerStub handler = new();
using HttpClient client = new(handler);
handler.ResponseToReturn = new HttpResponseMessage(System.Net.HttpStatusCode.OK);

// Act
var clientCore = new ClientCore(
modelId: "model",
openAIClient: new OpenAIClient(
new ApiKeyCredential("test"),
new OpenAIClientOptions()
{
Transport = new HttpClientPipelineTransport(client),
RetryPolicy = new ClientRetryPolicy(maxRetries: 0),
NetworkTimeout = Timeout.InfiniteTimeSpan
}));

var pipelineMessage = clientCore.Client.Pipeline.CreateMessage();
pipelineMessage.Request.Method = "POST";
pipelineMessage.Request.Uri = new Uri("http://localhost");
pipelineMessage.Request.Content = BinaryContent.Create(new BinaryData("test"));

// Assert
await clientCore.Client.Pipeline.SendAsync(pipelineMessage);

Assert.False(handler.RequestHeaders!.Contains(HttpHeaderConstant.Names.SemanticKernelVersion));
Assert.DoesNotContain(HttpHeaderConstant.Values.UserAgent, handler.RequestHeaders.GetValues("User-Agent").FirstOrDefault());
}

[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData("value")]
public void ItAddAttributesButDoesNothingIfNullOrEmpty(string? value)
{
// Arrange
var clientCore = new ClientCore("model", "apikey");
// Act

clientCore.AddAttribute("key", value);

// Assert
if (string.IsNullOrEmpty(value))
{
Assert.False(clientCore.Attributes.ContainsKey("key"));
}
else
{
Assert.True(clientCore.Attributes.ContainsKey("key"));
Assert.Equal(value, clientCore.Attributes["key"]);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) Microsoft. All rights reserved.

using System.ClientModel.Primitives;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Xunit;

namespace SemanticKernel.Connectors.OpenAI.UnitTests.Core.Models;

public class AddHeaderRequestPolicyTests
{
[Fact]
public void ItCanBeInstantiated()
{
// Arrange
var headerName = "headerName";
var headerValue = "headerValue";

// Act
var addHeaderRequestPolicy = new AddHeaderRequestPolicy(headerName, headerValue);

// Assert
Assert.NotNull(addHeaderRequestPolicy);
}

[Fact]
public void ItOnSendingRequestAddsHeaderToRequest()
{
// Arrange
var headerName = "headerName";
var headerValue = "headerValue";
var addHeaderRequestPolicy = new AddHeaderRequestPolicy(headerName, headerValue);
var pipeline = ClientPipeline.Create();
var message = pipeline.CreateMessage();

// Act
addHeaderRequestPolicy.OnSendingRequest(message);

// Assert
message.Request.Headers.TryGetValue(headerName, out var value);
Assert.NotNull(value);
Assert.Equal(headerValue, value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) Microsoft. All rights reserved.

using System.ClientModel.Primitives;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Xunit;

namespace SemanticKernel.Connectors.OpenAI.UnitTests.Core.Models;
public class PipelineSynchronousPolicyTests
{
[Fact]
public async Task ItProcessAsyncWhenSpecializationHasReceivedResponseOverrideShouldCallIt()
{
// Arrange
var first = new MyHttpPipelinePolicyWithoutOverride();
var last = new MyHttpPipelinePolicyWithOverride();

IReadOnlyList<PipelinePolicy> policies = [first, last];

// Act
await policies[0].ProcessAsync(ClientPipeline.Create().CreateMessage(), policies, 0);

// Assert
Assert.True(first.CalledProcess);
Assert.True(last.CalledProcess);
Assert.True(last.CalledOnReceivedResponse);
}

private class MyHttpPipelinePolicyWithoutOverride : PipelineSynchronousPolicy
{
public bool CalledProcess { get; private set; }

public override void Process(PipelineMessage message, IReadOnlyList<PipelinePolicy> pipeline, int currentIndex)
{
this.CalledProcess = true;
base.Process(message, pipeline, currentIndex);
}

public override ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList<PipelinePolicy> pipeline, int currentIndex)
{
this.CalledProcess = true;
return base.ProcessAsync(message, pipeline, currentIndex);
}
}

private sealed class MyHttpPipelinePolicyWithOverride : MyHttpPipelinePolicyWithoutOverride
{
public bool CalledOnReceivedResponse { get; private set; }

public override void OnReceivedResponse(PipelineMessage message)
{
this.CalledOnReceivedResponse = true;
}
}
}
Loading