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
2 changes: 2 additions & 0 deletions .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ fileignoreconfig:
checksum: b63897181a8cb5993d1305248cfc3e711c4039b5677b6c1e4e2a639e4ecb391b
- filename: Contentstack.Core.Tests/RegionHandlerTest.cs
checksum: 69899138754908e156aa477d775d12fd6b3fefc1a6c2afec22cb409bd6e6446c
- filename: CHANGELOG.md
checksum: bc17fd4cf564e524c686a8271033f8e6e7f5f69de8137007d1c72d5f563fe92a
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
### Version: 2.25.2
#### Date: Nov-13-2025


##### Fix:
- Error Handling
- Fixed error message extraction from Contentstack API responses across all model classes
- HTTP request errors now properly extract and display actual API error messages instead of generic exception messages
- Improved error handling in Query, Entry, Asset, GlobalField, ContentType, AssetLibrary, GlobalFieldQuery, and Taxonomy classes
- Users will now see meaningful error messages (e.g., "Invalid API key", "Entry not found") instead of generic "Exception of type 'ContentstackException' was thrown" messages
- ErrorCode, StatusCode, and Errors dictionary are now properly populated from API responses

### Version: 2.25.1
#### Date: Nov-10-2025
Expand Down
34 changes: 34 additions & 0 deletions Contentstack.Core.Unit.Tests/AssetLibraryUnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,40 @@ public void GetContentstackError_WithGenericException_ReturnsContentstackExcepti
Assert.Equal("Test error", result.Message);
}

[Fact]
public void GetContentstackError_WithWebException_HandlesExceptionCorrectly()
{
// Arrange
var method = typeof(AssetLibrary).GetMethod("GetContentstackError",
BindingFlags.NonPublic | BindingFlags.Static);
var webEx = new System.Net.WebException("Test error");

// Act
var result = method?.Invoke(null, new object[] { webEx }) as ContentstackException;

// Assert
Assert.NotNull(result);
// When WebException has no response, it should fall back to ex.Message
Assert.NotNull(result.Message);
}

[Fact]
public void ErrorHandling_WithWebException_ExtractsErrorMessage()
{
// Arrange
var method = typeof(AssetLibrary).GetMethod("GetContentstackError",
BindingFlags.NonPublic | BindingFlags.Static);
var errorMessage = "Asset library error";
var ex = new Exception(errorMessage);

// Act
var result = method?.Invoke(null, new object[] { ex }) as ContentstackException;

// Assert
Assert.NotNull(result);
Assert.Equal(errorMessage, result.Message);
}

[Fact]
public void GetHeader_WithNullLocalHeader_ReturnsStackHeaders()
{
Expand Down
34 changes: 34 additions & 0 deletions Contentstack.Core.Unit.Tests/AssetUnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,40 @@ public void GetContentstackError_WithGenericException_ReturnsContentstackExcepti
Assert.Equal("Test error", result.Message);
}

[Fact]
public void GetContentstackError_WithWebException_HandlesExceptionCorrectly()
{
// Arrange
var method = typeof(Asset).GetMethod("GetContentstackError",
BindingFlags.NonPublic | BindingFlags.Static);
var webEx = new System.Net.WebException("Test error");

// Act
var result = method?.Invoke(null, new object[] { webEx }) as ContentstackException;

// Assert
Assert.NotNull(result);
// When WebException has no response, it should fall back to ex.Message
Assert.NotNull(result.Message);
}

[Fact]
public void ErrorHandling_WithWebException_ExtractsErrorMessage()
{
// Arrange
var method = typeof(Asset).GetMethod("GetContentstackError",
BindingFlags.NonPublic | BindingFlags.Static);
var errorMessage = "Asset processing failed";
var ex = new Exception(errorMessage);

// Act
var result = method?.Invoke(null, new object[] { ex }) as ContentstackException;

// Assert
Assert.NotNull(result);
Assert.Equal(errorMessage, result.Message);
}

[Fact]
public void GetHeader_WithNullLocalHeader_ReturnsStackHeaders()
{
Expand Down
30 changes: 30 additions & 0 deletions Contentstack.Core.Unit.Tests/ContentTypeUnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,36 @@ public void GetContentstackError_WithGenericException_ReturnsContentstackExcepti
Assert.IsType<ContentstackException>(result);
}

[Fact]
public void GetContentstackError_WithGenericException_ReturnsExceptionWithCorrectMessage()
{
// Arrange
var errorMessage = "Content type error";
var exception = new Exception(errorMessage);

// Act
var result = ContentType.GetContentstackError(exception);

// Assert
Assert.NotNull(result);
Assert.Equal(errorMessage, result.Message);
}

[Fact]
public void GetContentstackError_WithWebException_HandlesExceptionCorrectly()
{
// Arrange
var webEx = new System.Net.WebException("Test error");

// Act
var result = ContentType.GetContentstackError(webEx);

// Assert
Assert.NotNull(result);
// When WebException has no response, it should fall back to ex.Message
Assert.NotNull(result.Message);
}

[Fact]
public void Fetch_WithIncludeBranch_VerifiesQueryParameters()
{
Expand Down
36 changes: 36 additions & 0 deletions Contentstack.Core.Unit.Tests/EntryUnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1704,6 +1704,42 @@ public void GetContentstackError_WithGenericException_ReturnsContentstackExcepti
Assert.Equal("Test error", result.Message);
}

[Fact]
public void GetContentstackError_WithWebException_HandlesExceptionCorrectly()
{
// Arrange
var method = typeof(Entry).GetMethod("GetContentstackError",
BindingFlags.NonPublic | BindingFlags.Static);
var webEx = new System.Net.WebException("Test error");

// Act
var result = method?.Invoke(null, new object[] { webEx }) as ContentstackException;

// Assert
Assert.NotNull(result);
// When WebException has no response, it should fall back to ex.Message
Assert.NotNull(result.Message);
}

[Fact]
public void ErrorHandling_WithWebException_ExtractsErrorMessage()
{
// Arrange
// This test verifies that the error handling logic properly checks for WebException
// and calls GetContentstackError to extract error messages
var method = typeof(Entry).GetMethod("GetContentstackError",
BindingFlags.NonPublic | BindingFlags.Static);
var errorMessage = "Custom error message";
var ex = new Exception(errorMessage);

// Act
var result = method?.Invoke(null, new object[] { ex }) as ContentstackException;

// Assert
Assert.NotNull(result);
Assert.Equal(errorMessage, result.Message);
}

[Fact]
public void GetHeader_WithNullLocalHeader_ReturnsFormHeaders()
{
Expand Down
30 changes: 30 additions & 0 deletions Contentstack.Core.Unit.Tests/GlobalFieldQueryUnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,36 @@ public void GetContentstackError_WithGenericException_ReturnsContentstackExcepti
Assert.IsType<ContentstackException>(result);
}

[Fact]
public void GetContentstackError_WithGenericException_ReturnsExceptionWithCorrectMessage()
{
// Arrange
var errorMessage = "Global field query error";
var exception = new Exception(errorMessage);

// Act
var result = GlobalFieldQuery.GetContentstackError(exception);

// Assert
Assert.NotNull(result);
Assert.Equal(errorMessage, result.Message);
}

[Fact]
public void GetContentstackError_WithWebException_HandlesExceptionCorrectly()
{
// Arrange
var webEx = new System.Net.WebException("Test error");

// Act
var result = GlobalFieldQuery.GetContentstackError(webEx);

// Assert
Assert.NotNull(result);
// When WebException has no response, it should fall back to ex.Message
Assert.NotNull(result.Message);
}

[Fact]
public void Find_WithMultipleParameters_VerifiesAllQueryParameters()
{
Expand Down
69 changes: 69 additions & 0 deletions Contentstack.Core.Unit.Tests/GlobalFieldUnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,75 @@ public void RemoveHeader_RemovesHeader()
}

#endregion

#region GetContentstackError Tests

[Fact]
public void GetContentstackError_WithWebException_ReturnsContentstackException()
{
// Arrange
var method = typeof(GlobalField).GetMethod("GetContentstackError",
BindingFlags.NonPublic | BindingFlags.Static);
var webEx = new System.Net.WebException("Test error");

// Act
var result = method?.Invoke(null, new object[] { webEx }) as Contentstack.Core.Internals.ContentstackException;

// Assert
Assert.NotNull(result);
}

[Fact]
public void GetContentstackError_WithGenericException_ReturnsContentstackException()
{
// Arrange
var method = typeof(GlobalField).GetMethod("GetContentstackError",
BindingFlags.NonPublic | BindingFlags.Static);
var ex = new Exception("Test error");

// Act
var result = method?.Invoke(null, new object[] { ex }) as Contentstack.Core.Internals.ContentstackException;

// Assert
Assert.NotNull(result);
Assert.Equal("Test error", result.Message);
}

[Fact]
public void GetContentstackError_WithWebException_HandlesExceptionCorrectly()
{
// Arrange
var method = typeof(GlobalField).GetMethod("GetContentstackError",
BindingFlags.NonPublic | BindingFlags.Static);
var webEx = new System.Net.WebException("Test error");

// Act
var result = method?.Invoke(null, new object[] { webEx }) as Contentstack.Core.Internals.ContentstackException;

// Assert
Assert.NotNull(result);
// When WebException has no response, it should fall back to ex.Message
Assert.NotNull(result.Message);
}

[Fact]
public void ErrorHandling_WithWebException_ExtractsErrorMessage()
{
// Arrange
var method = typeof(GlobalField).GetMethod("GetContentstackError",
BindingFlags.NonPublic | BindingFlags.Static);
var errorMessage = "Global field error";
var ex = new Exception(errorMessage);

// Act
var result = method?.Invoke(null, new object[] { ex }) as Contentstack.Core.Internals.ContentstackException;

// Assert
Assert.NotNull(result);
Assert.Equal(errorMessage, result.Message);
}

#endregion
}
}

36 changes: 36 additions & 0 deletions Contentstack.Core.Unit.Tests/QueryUnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1447,6 +1447,42 @@ public void GetContentstackError_WithGenericException_ReturnsContentstackExcepti
Assert.IsType<ContentstackException>(result);
}

[Fact]
public void GetContentstackError_WithGenericException_ReturnsExceptionWithCorrectMessage()
{
// Arrange
var method = typeof(Query).GetMethod("GetContentstackError",
BindingFlags.NonPublic | BindingFlags.Static);
var errorMessage = "Test error message";
var ex = new Exception(errorMessage);

// Act
var result = method?.Invoke(null, new object[] { ex }) as ContentstackException;

// Assert
Assert.NotNull(result);
Assert.Equal(errorMessage, result.Message);
}

[Fact]
public void ErrorHandling_WithWebException_CallsGetContentstackError()
{
// Arrange
// This test verifies that when a WebException is caught, GetContentstackError is called
// We can't easily mock a WebException with a response, but we can verify the logic path
var method = typeof(Query).GetMethod("GetContentstackError",
BindingFlags.NonPublic | BindingFlags.Static);
var webEx = new System.Net.WebException("Test error");

// Act
var result = method?.Invoke(null, new object[] { webEx }) as ContentstackException;

// Assert
Assert.NotNull(result);
// When WebException has no response, it should fall back to ex.Message
Assert.NotNull(result.Message);
}

[Fact]
public void And_WithExistingAndKey_ReplacesAndValue()
{
Expand Down
10 changes: 10 additions & 0 deletions Contentstack.Core/Models/Asset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,16 @@ public async Task<Asset> Fetch()
}
catch (Exception ex)
{
if (ex is System.Net.WebException)
{
var contentstackError = GetContentstackError(ex);
throw new AssetException(contentstackError.Message, ex)
{
ErrorCode = contentstackError.ErrorCode,
StatusCode = contentstackError.StatusCode,
Errors = contentstackError.Errors
};
}
throw AssetException.CreateForProcessingError(ex);
}
}
Expand Down
10 changes: 10 additions & 0 deletions Contentstack.Core/Models/AssetLibrary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,16 @@ private async Task<JObject> Exec()
}
catch (Exception ex)
{
if (ex is System.Net.WebException)
{
var contentstackError = GetContentstackError(ex);
throw new AssetException(contentstackError.Message, ex)
{
ErrorCode = contentstackError.ErrorCode,
StatusCode = contentstackError.StatusCode,
Errors = contentstackError.Errors
};
}
throw AssetException.CreateForProcessingError(ex);
}
}
Expand Down
10 changes: 10 additions & 0 deletions Contentstack.Core/Models/ContentType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,16 @@ public async Task<JObject> Fetch(Dictionary<string, object> param = null)
}
catch (Exception ex)
{
if (ex is System.Net.WebException)
{
var contentstackError = GetContentstackError(ex);
throw new ContentTypeException(contentstackError.Message, ex)
{
ErrorCode = contentstackError.ErrorCode,
StatusCode = contentstackError.StatusCode,
Errors = contentstackError.Errors
};
}
throw ContentTypeException.CreateForProcessingError(ex);
}
}
Expand Down
Loading
Loading