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
@@ -0,0 +1,3 @@
changes:
- section: "Bugs Fixed"
description: "Fixed an issue where the Cosmos DB QueryItems operation silently parsed non-success (e.g. throttled or auth-failure) responses as data. The response is now validated before parsing so failures surface as errors."
Comment thread
vcolin7 marked this conversation as resolved.
2 changes: 2 additions & 0 deletions tools/Azure.Mcp.Tools.Cosmos/src/Services/CosmosService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,8 @@ public async Task<List<JsonElement>> QueryItems(
while (queryIterator.HasMoreResults)
{
using ResponseMessage response = await queryIterator.ReadNextAsync(cancellationToken);
response.EnsureSuccessStatusCode();

using var document = JsonDocument.Parse(response.Content);
items.Add(document.RootElement.Clone());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,31 @@ await _cacheService.DidNotReceive().GetAsync<CosmosClient>(
Arg.Any<CancellationToken>());
}

[Fact]
public async Task QueryItems_NonSuccessResponse_Throws()
{
// Arrange: route the CosmosClient's transport through the mocked IHttpClientFactory so it always returns 403.
var handler = new MockHttpHandler(HttpStatusCode.Forbidden);
_httpClientFactory.CreateClient(Arg.Any<string>()).Returns(new HttpClient(handler));
var clientOptions = new CosmosClientOptions { HttpClientFactory = () => _httpClientFactory.CreateClient() };

var credential = Substitute.For<TokenCredential>();
var token = new AccessToken("fake-token", DateTimeOffset.UtcNow.AddHours(1));
credential.GetTokenAsync(Arg.Any<TokenRequestContext>(), Arg.Any<CancellationToken>())
.Returns(new ValueTask<AccessToken>(token));
credential.GetToken(Arg.Any<TokenRequestContext>(), Arg.Any<CancellationToken>())
.Returns(token);

using var cosmosClient = new CosmosClient("https://myaccount.documents.azure.com", credential, clientOptions);

_cacheService.GetAsync<CosmosClient>(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<TimeSpan?>(), Arg.Any<CancellationToken>())
.Returns(cosmosClient);

// Act & Assert: a non-success response must throw rather than being returned as a data item
await Assert.ThrowsAnyAsync<CosmosException>(() =>
_service.QueryItems("myaccount", "mydb", "mycontainer", "SELECT * FROM c", "sub123", AuthMethod.Key, cancellationToken: TestContext.Current.CancellationToken));
}

private sealed class MockHttpHandler(HttpStatusCode statusCode) : HttpMessageHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
Expand Down