feat: add proper error handling with full API error details
- Add ApiResult<T> type to carry success/failure with error details - Add UpdateDocumentWithResultAsync and CreateTagWithResultAsync methods - Update DocumentTools.Update and TagTools.Create to return actual HTTP status codes and response bodies in error responses - Add comprehensive tests for error handling (18 new tests) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -433,6 +433,47 @@ public class DocumentToolsTests : IDisposable
|
||||
json.RootElement.GetProperty("error").GetProperty("code").GetString().Should().Be("NOT_FOUND");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Update_WhenBadRequest_ReturnsErrorWithDetails()
|
||||
{
|
||||
// Arrange
|
||||
var errorBody = """{"title": ["This field may not be blank."]}""";
|
||||
_factory.SetupPatchWithError("api/documents/1/", HttpStatusCode.BadRequest, errorBody);
|
||||
|
||||
// Act
|
||||
var result = await DocumentTools.Update(_factory.Client, 1, title: "");
|
||||
|
||||
// Assert
|
||||
var json = JsonDocument.Parse(result);
|
||||
json.RootElement.GetProperty("ok").GetBoolean().Should().BeFalse();
|
||||
json.RootElement.GetProperty("error").GetProperty("code").GetString().Should().Be("UPSTREAM_ERROR");
|
||||
|
||||
// Verify error details include status code and response body
|
||||
var details = json.RootElement.GetProperty("error").GetProperty("details");
|
||||
details.GetProperty("status_code").GetInt32().Should().Be(400);
|
||||
details.GetProperty("response_body").GetString().Should().Contain("This field may not be blank");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Update_WhenForbidden_ReturnsErrorWithDetails()
|
||||
{
|
||||
// Arrange
|
||||
var errorBody = """{"detail": "You do not have permission to perform this action."}""";
|
||||
_factory.SetupPatchWithError("api/documents/1/", HttpStatusCode.Forbidden, errorBody);
|
||||
|
||||
// Act
|
||||
var result = await DocumentTools.Update(_factory.Client, 1, title: "Updated");
|
||||
|
||||
// Assert
|
||||
var json = JsonDocument.Parse(result);
|
||||
json.RootElement.GetProperty("ok").GetBoolean().Should().BeFalse();
|
||||
json.RootElement.GetProperty("error").GetProperty("code").GetString().Should().Be("UPSTREAM_ERROR");
|
||||
|
||||
var details = json.RootElement.GetProperty("error").GetProperty("details");
|
||||
details.GetProperty("status_code").GetInt32().Should().Be(403);
|
||||
details.GetProperty("response_body").GetString().Should().Contain("permission");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Delete Tests
|
||||
|
||||
@@ -119,6 +119,47 @@ public class TagToolsTests : IDisposable
|
||||
json.RootElement.GetProperty("error").GetProperty("code").GetString().Should().Be("UPSTREAM_ERROR");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Create_WhenDuplicate_ReturnsErrorWithDetails()
|
||||
{
|
||||
// Arrange
|
||||
var errorBody = """{"name": ["tag with this name already exists."]}""";
|
||||
_factory.SetupPostWithError("api/tags/", HttpStatusCode.BadRequest, errorBody);
|
||||
|
||||
// Act
|
||||
var result = await TagTools.Create(_factory.Client, "Existing Tag");
|
||||
|
||||
// Assert
|
||||
var json = JsonDocument.Parse(result);
|
||||
json.RootElement.GetProperty("ok").GetBoolean().Should().BeFalse();
|
||||
json.RootElement.GetProperty("error").GetProperty("code").GetString().Should().Be("UPSTREAM_ERROR");
|
||||
|
||||
// Verify error details include status code and response body
|
||||
var details = json.RootElement.GetProperty("error").GetProperty("details");
|
||||
details.GetProperty("status_code").GetInt32().Should().Be(400);
|
||||
details.GetProperty("response_body").GetString().Should().Contain("already exists");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Create_WhenUnauthorized_ReturnsErrorWithDetails()
|
||||
{
|
||||
// Arrange
|
||||
var errorBody = """{"detail": "Authentication credentials were not provided."}""";
|
||||
_factory.SetupPostWithError("api/tags/", HttpStatusCode.Unauthorized, errorBody);
|
||||
|
||||
// Act
|
||||
var result = await TagTools.Create(_factory.Client, "New Tag");
|
||||
|
||||
// Assert
|
||||
var json = JsonDocument.Parse(result);
|
||||
json.RootElement.GetProperty("ok").GetBoolean().Should().BeFalse();
|
||||
json.RootElement.GetProperty("error").GetProperty("code").GetString().Should().Be("UPSTREAM_ERROR");
|
||||
|
||||
var details = json.RootElement.GetProperty("error").GetProperty("details");
|
||||
details.GetProperty("status_code").GetInt32().Should().Be(401);
|
||||
details.GetProperty("response_body").GetString().Should().Contain("Authentication credentials");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Update_WhenSuccessful_ReturnsUpdatedTag()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user