Skip to content
Merged
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
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
### 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

##### Enh:
- Improved Error messages
##### Fix:
- Taxonomy
- Fixed NullReferenceExceptions
- Fixed InvalidCastException in `GetContentstackError` when exception is not a WebException
- Fixed JsonReaderException in `GetContentstackError` when response is not valid JSON
- All exceptions now properly throw TaxonomyException (extends ContentstackException) with descriptive error messages

### Version: 2.25.0
#### Date: Jan-07-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
Loading
Loading