// Copyright (c) Microsoft. All rights reserved.
using System;
using System.ClientModel.Primitives;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Azure.AI.Projects;
using OpenAI.Files;
#pragma warning disable OPENAI001, CS0618
namespace Microsoft.Agents.AI.Foundry.UnitTests;
///
/// Unit tests for the file and vector-store forwarder extensions on
/// declared in . The forwarders are thin shims over the
/// inner , so coverage focuses on (a) request shape (the agent
/// path reaches the same wire as a direct chat-client call), (b) null/missing-FoundryChatClient
/// handling, and (c) returns the same payload the chat client would.
///
public sealed class FoundryAgentExtensionsTests
{
private static readonly Uri s_testProjectEndpoint = new("https://test.openai.azure.com/");
[Fact]
public async Task UploadFileAsync_Forwards_ToInnerFoundryChatClient_Async()
{
// Arrange — agent built via the Responses Agent (Mode 1) projectEndpoint+model+instructions
// ctor wires a FoundryChatClient inside that the extension can resolve via GetService.
var sawPostToFiles = false;
using var handler = new HttpHandlerAssert(req =>
{
if (req.Method == HttpMethod.Post && req.RequestUri!.AbsolutePath.Contains("/files", StringComparison.Ordinal))
{
sawPostToFiles = true;
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(FakeFileJson("file_via_agent"), Encoding.UTF8, "application/json"),
};
}
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("{}", Encoding.UTF8, "application/json") };
});
#pragma warning disable CA5399
using var httpClient = new HttpClient(handler);
#pragma warning restore CA5399
var agent = new FoundryAgent(
projectEndpoint: s_testProjectEndpoint,
credential: new FakeAuthenticationTokenProvider(),
model: "gpt-4o-mini",
instructions: "Be helpful.",
clientOptions: new AIProjectClientOptions { Transport = new HttpClientPipelineTransport(httpClient) });
var path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), $"fae-{Guid.NewGuid():N}.txt");
System.IO.File.WriteAllText(path, "hello");
try
{
// Act — call the forwarder on the agent.
var result = await agent.UploadFileAsync(path, FileUploadPurpose.Assistants);
// Assert
Assert.True(sawPostToFiles, "POST to /files must reach the wire through the agent forwarder.");
Assert.Equal("file_via_agent", result.Id);
}
finally
{
System.IO.File.Delete(path);
}
}
[Fact]
public async Task DeleteFileAsync_Forwards_ToInnerFoundryChatClient_Async()
{
var sawDelete = false;
using var handler = new HttpHandlerAssert(req =>
{
if (req.Method == HttpMethod.Delete && req.RequestUri!.AbsolutePath.Contains("/files/", StringComparison.Ordinal))
{
sawDelete = true;
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent("{\"id\":\"file_abc\",\"object\":\"file\",\"deleted\":true}", Encoding.UTF8, "application/json"),
};
}
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("{}", Encoding.UTF8, "application/json") };
});
#pragma warning disable CA5399
using var httpClient = new HttpClient(handler);
#pragma warning restore CA5399
var agent = new FoundryAgent(
projectEndpoint: s_testProjectEndpoint,
credential: new FakeAuthenticationTokenProvider(),
model: "gpt-4o-mini",
instructions: "Be helpful.",
clientOptions: new AIProjectClientOptions { Transport = new HttpClientPipelineTransport(httpClient) });
var result = await agent.DeleteFileAsync("file_abc");
Assert.True(sawDelete);
Assert.NotNull(result);
}
[Fact]
public async Task CreateVectorStoreAsync_Forwards_ToInnerFoundryChatClient_Async()
{
var sawVectorStorePost = false;
using var handler = new HttpHandlerAssert(req =>
{
if (req.Method == HttpMethod.Post && req.RequestUri!.AbsolutePath.Contains("/vector_stores", StringComparison.Ordinal))
{
sawVectorStorePost = true;
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(FakeVectorStoreJson("vs_via_agent", "kb"), Encoding.UTF8, "application/json"),
};
}
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("{}", Encoding.UTF8, "application/json") };
});
#pragma warning disable CA5399
using var httpClient = new HttpClient(handler);
#pragma warning restore CA5399
var agent = new FoundryAgent(
projectEndpoint: s_testProjectEndpoint,
credential: new FakeAuthenticationTokenProvider(),
model: "gpt-4o-mini",
instructions: "Be helpful.",
clientOptions: new AIProjectClientOptions { Transport = new HttpClientPipelineTransport(httpClient) });
var store = await agent.CreateVectorStoreAsync("kb", Array.Empty());
Assert.True(sawVectorStorePost);
Assert.Equal("vs_via_agent", store.Id);
}
[Fact]
public async Task DeleteVectorStoreAsync_Forwards_ToInnerFoundryChatClient_Async()
{
var sawDelete = false;
using var handler = new HttpHandlerAssert(req =>
{
if (req.Method == HttpMethod.Delete && req.RequestUri!.AbsolutePath.Contains("/vector_stores/", StringComparison.Ordinal))
{
sawDelete = true;
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent("{\"id\":\"vs_abc\",\"object\":\"vector_store.deleted\",\"deleted\":true}", Encoding.UTF8, "application/json"),
};
}
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("{}", Encoding.UTF8, "application/json") };
});
#pragma warning disable CA5399
using var httpClient = new HttpClient(handler);
#pragma warning restore CA5399
var agent = new FoundryAgent(
projectEndpoint: s_testProjectEndpoint,
credential: new FakeAuthenticationTokenProvider(),
model: "gpt-4o-mini",
instructions: "Be helpful.",
clientOptions: new AIProjectClientOptions { Transport = new HttpClientPipelineTransport(httpClient) });
await agent.DeleteVectorStoreAsync("vs_abc");
Assert.True(sawDelete);
}
[Fact]
public async Task UploadFileAsync_NullAgent_ThrowsArgumentNullExceptionAsync()
=> await Assert.ThrowsAsync(() =>
FoundryAgentExtensions.UploadFileAsync(null!, "x", FileUploadPurpose.Assistants));
[Fact]
public async Task DeleteFileAsync_NullAgent_ThrowsArgumentNullExceptionAsync()
=> await Assert.ThrowsAsync(() =>
FoundryAgentExtensions.DeleteFileAsync(null!, "file_abc"));
[Fact]
public async Task CreateVectorStoreAsync_NullAgent_ThrowsArgumentNullExceptionAsync()
=> await Assert.ThrowsAsync(() =>
FoundryAgentExtensions.CreateVectorStoreAsync(null!, "kb", Array.Empty()));
[Fact]
public async Task DeleteVectorStoreAsync_NullAgent_ThrowsArgumentNullExceptionAsync()
=> await Assert.ThrowsAsync(() =>
FoundryAgentExtensions.DeleteVectorStoreAsync(null!, "vs_abc"));
// ----- Helpers -----
private static string FakeFileJson(string id)
=> $"{{\"id\":\"{id}\",\"object\":\"file\",\"bytes\":11,\"created_at\":1700000000,\"filename\":\"x.txt\",\"purpose\":\"assistants\",\"status\":\"processed\"}}";
private static string FakeVectorStoreJson(string id, string name)
=> $"{{\"id\":\"{id}\",\"object\":\"vector_store\",\"created_at\":1700000000,\"name\":\"{name}\",\"usage_bytes\":0,\"file_counts\":{{\"in_progress\":0,\"completed\":0,\"failed\":0,\"cancelled\":0,\"total\":0}},\"status\":\"completed\",\"last_active_at\":1700000000}}";
}
#pragma warning restore CS0618