feat: implement listing a page of files

This commit is contained in:
Stevan Freeborn
2025-07-14 23:30:02 -05:00
parent c212ebffcd
commit 9d5c620167
5 changed files with 144 additions and 0 deletions
@@ -388,6 +388,15 @@ public class AnthropicApiClient : IAnthropicApiClient
return await CreateResultAsync<AnthropicFile>(response);
}
/// <inheritdoc/>
public async Task<AnthropicResult<Page<AnthropicFile>>> ListFilesAsync(PagingRequest? request = null, CancellationToken cancellationToken = default)
{
var pagingRequest = request ?? new PagingRequest();
var endpoint = $"{FilesEndpoint}?{pagingRequest.ToQueryParameters()}";
var response = await SendRequestAsync(endpoint, cancellationToken: cancellationToken);
return await CreateResultAsync<Page<AnthropicFile>>(response);
}
private async IAsyncEnumerable<AnthropicResult<Page<T>>> GetAllPagesAsync<T>(string endpoint, int limit = 20, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var pagingRequest = new PagingRequest(limit: limit);
@@ -119,4 +119,12 @@ public interface IAnthropicApiClient
/// <param name="cancellationToken">A token to cancel the asynchronous operation.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the response as an <see cref="AnthropicResult{T}"/> where T is <see cref="AnthropicFile"/>.</returns>
Task<AnthropicResult<AnthropicFile>> CreateFileAsync(CreateFileRequest request, CancellationToken cancellationToken = default);
/// <summary>
/// Lists files asynchronously, returning a single page of results.
/// </summary>
/// <param name="request">The paging request to use for listing the files.</param>
/// <param name="cancellationToken">A token to cancel the asynchronous operation.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the response as an <see cref="AnthropicResult{T}"/> where T is <see cref="Page{T}"/> where T is <see cref="AnthropicFile"/>.</returns>
Task<AnthropicResult<Page<AnthropicFile>>> ListFilesAsync(PagingRequest? request = null, CancellationToken cancellationToken = default);
}
@@ -708,4 +708,22 @@ public class AnthropicApiClientTests(ConfigurationFixture configFixture) : EndTo
result.Value.Name.Should().Be(fileName);
result.Value.MimeType.Should().Be(fileType);
}
[Fact]
public async Task ListFilesAsync_WhenCalled_ItShouldReturnPageOfFiles()
{
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("anthropic-beta", "files-api-2025-04-14");
var client = CreateClient(httpClient);
var fileBytes = await File.ReadAllBytesAsync(TestFileHelper.GetTestFilePath("story.txt"));
var createFileRequest = new CreateFileRequest(fileBytes, "story.txt", "text/plain");
var createdFile = await client.CreateFileAsync(createFileRequest);
var result = await client.ListFilesAsync();
result.IsSuccess.Should().BeTrue();
result.Value.Should().BeOfType<Page<AnthropicFile>>();
result.Value.Data.Should().ContainSingle(f => f.Id == createdFile.Value.Id);
}
}
@@ -1965,4 +1965,107 @@ public class AnthropicApiClientTests : IntegrationTest
Type = "file"
});
}
[Fact]
public async Task ListFilesAsync_WhenCalled_ItShouldReturnPageOfFiles()
{
_mockHttpMessageHandler
.WhenListFilesRequest()
.Respond(
HttpStatusCode.OK,
"application/json",
@"{
""data"": [
{
""created_at"": ""2023-11-07T05:31:56Z"",
""downloadable"": false,
""filename"": ""example.txt"",
""id"": ""file_013Zva2CMHLNnXjNJJKqJ2EF"",
""mime_type"": ""text/plain"",
""size_bytes"": 1234,
""type"": ""file""
}
],
""has_more"": true,
""first_id"": ""1"",
""last_id"": ""1""
}"
);
var result = await Client.ListFilesAsync();
result.IsSuccess.Should().BeTrue();
result.Value.Should().BeOfType<Page<AnthropicFile>>();
result.Value.HasMore.Should().BeTrue();
result.Value.FirstId.Should().Be("1");
result.Value.LastId.Should().Be("1");
result.Value.Data.Should().BeEquivalentTo(new AnthropicFile[]
{
new()
{
CreatedAt = DateTimeOffset.Parse("2023-11-07T05:31:56Z"),
Downloadable = false,
Name = "example.txt",
Id = "file_013Zva2CMHLNnXjNJJKqJ2EF",
MimeType = "text/plain",
Size = 1234,
Type = "file"
}
});
}
[Fact]
public async Task ListFilesAsync_WhenCalledWithPagingRequest_ItShouldReturnPageOfFiles()
{
var pagingRequest = new PagingRequest(afterId: "next_id", limit: 10);
_mockHttpMessageHandler
.WhenListFilesRequest()
.WithQueryString(new Dictionary<string, string>
{
{ "after_id", pagingRequest.AfterId },
{ "limit", pagingRequest.Limit.ToString() },
})
.Respond(
HttpStatusCode.OK,
"application/json",
@"{
""data"": [
{
""created_at"": ""2023-11-07T05:31:56Z"",
""downloadable"": false,
""filename"": ""example.txt"",
""id"": ""file_013Zva2CMHLNnXjNJJKqJ2EF"",
""mime_type"": ""text/plain"",
""size_bytes"": 1234,
""type"": ""file""
}
],
""has_more"": true,
""first_id"": ""1"",
""last_id"": ""1""
}"
);
var result = await Client.ListFilesAsync(pagingRequest);
result.IsSuccess.Should().BeTrue();
result.Value.Should().BeOfType<Page<AnthropicFile>>();
result.Value.HasMore.Should().BeTrue();
result.Value.FirstId.Should().Be("1");
result.Value.LastId.Should().Be("1");
result.Value.Data.Should().BeEquivalentTo(new AnthropicFile[]
{
new()
{
CreatedAt = DateTimeOffset.Parse("2023-11-07T05:31:56Z"),
Downloadable = false,
Name = "example.txt",
Id = "file_013Zva2CMHLNnXjNJJKqJ2EF",
MimeType = "text/plain",
Size = 1234,
Type = "file"
}
});
}
}
@@ -110,4 +110,10 @@ public static class MockHttpMessageHandlerExtensions
return mockHttpMessageHandler
.SetupBaseRequest(HttpMethod.Post, FilesEndpoint);
}
public static MockedRequest WhenListFilesRequest(this MockHttpMessageHandler mockHttpMessageHandler)
{
return mockHttpMessageHandler
.SetupBaseRequest(HttpMethod.Get, FilesEndpoint);
}
}