Skip to content

Commit 78eaf86

Browse files
committed
Merge latest development: error handling improvements and version bump
2 parents edc3906 + 20a2b75 commit 78eaf86

14 files changed

Lines changed: 689 additions & 7 deletions

.talismanrc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ fileignoreconfig:
120120
checksum: 01517f2224fbb2956d79292e6d3d23d1cc970dbfc190623496bcac1335bcd683
121121
- filename: Contentstack.Core.Tests/generate_html_report.py
122122
checksum: b4bec9ef853703e989b3d8077edc5c3ec6ea13a23826699d8beca5e87323e128
123+
- filename: Scripts/generate_html_report.py
124+
checksum: 343a6c4a3608e4506cd7c9de04f9246da304ff95d256a3215c2f0a2d37d4e4da
123125
- filename: Scripts/generate_enhanced_html_report.py
124-
checksum: e31c1372ea6e1cd1381c9533ba89b95edf746b36e4a1098d4aa51fba296ca928
126+
checksum: 69de208724714fcb474e41e17c5e67a1f875b96e2cc479c71f03c38b7a8c3be9
125127
version: "1.0"

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
### Version: 2.26.0
2+
#### Date: Feb-10-2026
3+
4+
##### Feat:
5+
- CDA / – AssetFields support
6+
- Added `AssetFields(params string[] fields)` to request specific asset-related metadata via the CDA `asset_fields[]` query parameter
7+
- Implemented on: Entry (single entry fetch), Query (entries find), Asset (single asset fetch), AssetLibrary (assets find)
8+
- Valid parameters: `user_defined_fields`, `embedded_metadata`, `ai_generated_metadata`, `visual_markups`
9+
- Method is chainable; when called with no arguments, the query parameter is not set
10+
- CDA / – Asset localisation support
11+
- Added `SetLocale(string locale)` on Asset for single-asset fetch by locale (e.g. `stack.Asset(uid).SetLocale("en-us").Fetch()`)
12+
- Added `Title` property on Asset for localised title in API response
13+
- AssetLibrary `SetLocale` continues to support listing assets by locale
14+
115
### Version: 2.25.2
216
#### Date: Nov-13-2025
317

Contentstack.Core.Tests/Contentstack.Core.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
<PackageReference Include="AutoFixture" Version="4.18.1" />
2929
<PackageReference Include="AutoFixture.AutoMoq" Version="4.18.1" />
3030
<PackageReference Include="Moq" Version="4.20.72" />
31+
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
3132
</ItemGroup>
3233

3334
<ItemGroup>

Contentstack.Core.Unit.Tests/AssetLibraryUnitTests.cs

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,119 @@ public void IncludeMetadata_AddsQueryParameter()
171171

172172
#endregion
173173

174+
#region AssetFields Tests
175+
176+
[Fact]
177+
public void AssetFields_WithSingleField_AddsQueryParameter()
178+
{
179+
// Arrange
180+
var assetLibrary = CreateAssetLibrary();
181+
var field = "user_defined_fields";
182+
183+
// Act
184+
AssetLibrary result = assetLibrary.AssetFields(field);
185+
186+
// Assert
187+
Assert.NotNull(result);
188+
Assert.Equal(assetLibrary, result);
189+
190+
var urlQueriesField = typeof(AssetLibrary).GetField("UrlQueries",
191+
BindingFlags.NonPublic | BindingFlags.Instance);
192+
var urlQueries = (Dictionary<string, object>)urlQueriesField?.GetValue(assetLibrary);
193+
194+
Assert.True(urlQueries?.ContainsKey("asset_fields[]") ?? false);
195+
var fields = urlQueries?["asset_fields[]"] as string[];
196+
Assert.NotNull(fields);
197+
Assert.Single(fields);
198+
Assert.Equal("user_defined_fields", fields[0]);
199+
}
200+
201+
[Fact]
202+
public void AssetFields_WithMultipleFields_AddsQueryParameter()
203+
{
204+
// Arrange
205+
var assetLibrary = CreateAssetLibrary();
206+
var fields = new[] { "user_defined_fields", "embedded_metadata", "ai_generated_metadata" };
207+
208+
// Act
209+
AssetLibrary result = assetLibrary.AssetFields(fields);
210+
211+
// Assert
212+
Assert.NotNull(result);
213+
Assert.Equal(assetLibrary, result);
214+
215+
var urlQueriesField = typeof(AssetLibrary).GetField("UrlQueries",
216+
BindingFlags.NonPublic | BindingFlags.Instance);
217+
var urlQueries = (Dictionary<string, object>)urlQueriesField?.GetValue(assetLibrary);
218+
219+
Assert.True(urlQueries?.ContainsKey("asset_fields[]") ?? false);
220+
Assert.Equal(fields, urlQueries?["asset_fields[]"]);
221+
}
222+
223+
[Fact]
224+
public void AssetFields_ReturnsSameInstance_ForChaining()
225+
{
226+
// Arrange
227+
var assetLibrary = CreateAssetLibrary();
228+
229+
// Act
230+
AssetLibrary result = assetLibrary.AssetFields("embedded_metadata");
231+
232+
// Assert
233+
Assert.NotNull(result);
234+
Assert.Same(assetLibrary, result);
235+
}
236+
237+
[Fact]
238+
public void AssetFields_WithNoArguments_DoesNotAddParameter()
239+
{
240+
// Arrange
241+
var assetLibrary = CreateAssetLibrary();
242+
243+
// Act
244+
AssetLibrary result = assetLibrary.AssetFields();
245+
246+
// Assert
247+
var urlQueriesField = typeof(AssetLibrary).GetField("UrlQueries",
248+
BindingFlags.NonPublic | BindingFlags.Instance);
249+
var urlQueries = (Dictionary<string, object>)urlQueriesField?.GetValue(assetLibrary);
250+
Assert.False(urlQueries?.ContainsKey("asset_fields[]") ?? false);
251+
}
252+
253+
[Fact]
254+
public void AssetFields_WithNull_DoesNotAddParameter()
255+
{
256+
// Arrange
257+
var assetLibrary = CreateAssetLibrary();
258+
259+
// Act
260+
AssetLibrary result = assetLibrary.AssetFields(null);
261+
262+
// Assert
263+
var urlQueriesField = typeof(AssetLibrary).GetField("UrlQueries",
264+
BindingFlags.NonPublic | BindingFlags.Instance);
265+
var urlQueries = (Dictionary<string, object>)urlQueriesField?.GetValue(assetLibrary);
266+
Assert.False(urlQueries?.ContainsKey("asset_fields[]") ?? false);
267+
}
268+
269+
[Fact]
270+
public void AssetFields_WithEmptyArray_DoesNotAddParameter()
271+
{
272+
// Arrange
273+
var assetLibrary = CreateAssetLibrary();
274+
275+
// Act
276+
AssetLibrary result = assetLibrary.AssetFields(new string[0]);
277+
278+
// Assert
279+
var urlQueriesField = typeof(AssetLibrary).GetField("UrlQueries",
280+
BindingFlags.NonPublic | BindingFlags.Instance);
281+
var urlQueries = (Dictionary<string, object>)urlQueriesField?.GetValue(assetLibrary);
282+
Assert.False(urlQueries?.ContainsKey("asset_fields[]") ?? false);
283+
}
284+
285+
#endregion
286+
174287
#region IncludeBranch Tests
175288

176289
[Fact]
@@ -220,6 +333,43 @@ public void SetLocale_AddsQueryParameter()
220333
Assert.Equal(locale, urlQueries?["locale"]?.ToString());
221334
}
222335

336+
/// <summary>
337+
/// Asset localisation: SetLocale with locale "ar" adds locale query param for API.
338+
/// </summary>
339+
[Fact]
340+
public void SetLocale_ForAssetLocalisation_AddsLocaleQueryParameter()
341+
{
342+
var assetLibrary = CreateAssetLibrary();
343+
var locale = "ar";
344+
345+
AssetLibrary result = assetLibrary.SetLocale(locale);
346+
347+
Assert.NotNull(result);
348+
Assert.Same(assetLibrary, result);
349+
var urlQueriesField = typeof(AssetLibrary).GetField("UrlQueries",
350+
BindingFlags.NonPublic | BindingFlags.Instance);
351+
var urlQueries = (Dictionary<string, object>)urlQueriesField?.GetValue(assetLibrary);
352+
Assert.True(urlQueries?.ContainsKey("locale") ?? false);
353+
Assert.Equal("ar", urlQueries?["locale"]?.ToString());
354+
}
355+
356+
/// <summary>
357+
/// SetLocale when called again updates the locale query param (overwrite).
358+
/// </summary>
359+
[Fact]
360+
public void SetLocale_UpdatesLocaleWhenCalledAgain()
361+
{
362+
var assetLibrary = CreateAssetLibrary();
363+
assetLibrary.SetLocale("en-us");
364+
assetLibrary.SetLocale("ar");
365+
366+
var urlQueriesField = typeof(AssetLibrary).GetField("UrlQueries",
367+
BindingFlags.NonPublic | BindingFlags.Instance);
368+
var urlQueries = (Dictionary<string, object>)urlQueriesField?.GetValue(assetLibrary);
369+
Assert.True(urlQueries?.ContainsKey("locale") ?? false);
370+
Assert.Equal("ar", urlQueries?["locale"]?.ToString());
371+
}
372+
223373
#endregion
224374

225375
#region AddParam Tests

Contentstack.Core.Unit.Tests/AssetUnitTests.cs

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,119 @@ public void IncludeMetadata_AddsQueryParameter()
368368

369369
#endregion
370370

371+
#region AssetFields Tests
372+
373+
[Fact]
374+
public void AssetFields_WithSingleField_AddsQueryParameter()
375+
{
376+
// Arrange
377+
var asset = CreateAsset();
378+
var field = "user_defined_fields";
379+
380+
// Act
381+
Asset result = asset.AssetFields(field);
382+
383+
// Assert
384+
Assert.NotNull(result);
385+
Assert.Equal(asset, result);
386+
387+
var urlQueriesField = typeof(Asset).GetField("UrlQueries",
388+
BindingFlags.NonPublic | BindingFlags.Instance);
389+
var urlQueries = (Dictionary<string, object>)urlQueriesField?.GetValue(asset);
390+
391+
Assert.True(urlQueries?.ContainsKey("asset_fields[]") ?? false);
392+
var fields = urlQueries?["asset_fields[]"] as string[];
393+
Assert.NotNull(fields);
394+
Assert.Single(fields);
395+
Assert.Equal("user_defined_fields", fields[0]);
396+
}
397+
398+
[Fact]
399+
public void AssetFields_WithMultipleFields_AddsQueryParameter()
400+
{
401+
// Arrange
402+
var asset = CreateAsset();
403+
var fields = new[] { "embedded_metadata", "visual_markups" };
404+
405+
// Act
406+
Asset result = asset.AssetFields(fields);
407+
408+
// Assert
409+
Assert.NotNull(result);
410+
Assert.Equal(asset, result);
411+
412+
var urlQueriesField = typeof(Asset).GetField("UrlQueries",
413+
BindingFlags.NonPublic | BindingFlags.Instance);
414+
var urlQueries = (Dictionary<string, object>)urlQueriesField?.GetValue(asset);
415+
416+
Assert.True(urlQueries?.ContainsKey("asset_fields[]") ?? false);
417+
Assert.Equal(fields, urlQueries?["asset_fields[]"]);
418+
}
419+
420+
[Fact]
421+
public void AssetFields_ReturnsSameInstance_ForChaining()
422+
{
423+
// Arrange
424+
var asset = CreateAsset();
425+
426+
// Act
427+
Asset result = asset.AssetFields("ai_generated_metadata");
428+
429+
// Assert
430+
Assert.NotNull(result);
431+
Assert.Same(asset, result);
432+
}
433+
434+
[Fact]
435+
public void AssetFields_WithNoArguments_DoesNotAddParameter()
436+
{
437+
// Arrange
438+
var asset = CreateAsset();
439+
440+
// Act
441+
Asset result = asset.AssetFields();
442+
443+
// Assert
444+
var urlQueriesField = typeof(Asset).GetField("UrlQueries",
445+
BindingFlags.NonPublic | BindingFlags.Instance);
446+
var urlQueries = (Dictionary<string, object>)urlQueriesField?.GetValue(asset);
447+
Assert.False(urlQueries?.ContainsKey("asset_fields[]") ?? false);
448+
}
449+
450+
[Fact]
451+
public void AssetFields_WithNull_DoesNotAddParameter()
452+
{
453+
// Arrange
454+
var asset = CreateAsset();
455+
456+
// Act
457+
Asset result = asset.AssetFields(null);
458+
459+
// Assert
460+
var urlQueriesField = typeof(Asset).GetField("UrlQueries",
461+
BindingFlags.NonPublic | BindingFlags.Instance);
462+
var urlQueries = (Dictionary<string, object>)urlQueriesField?.GetValue(asset);
463+
Assert.False(urlQueries?.ContainsKey("asset_fields[]") ?? false);
464+
}
465+
466+
[Fact]
467+
public void AssetFields_WithEmptyArray_DoesNotAddParameter()
468+
{
469+
// Arrange
470+
var asset = CreateAsset();
471+
472+
// Act
473+
Asset result = asset.AssetFields(new string[0]);
474+
475+
// Assert
476+
var urlQueriesField = typeof(Asset).GetField("UrlQueries",
477+
BindingFlags.NonPublic | BindingFlags.Instance);
478+
var urlQueries = (Dictionary<string, object>)urlQueriesField?.GetValue(asset);
479+
Assert.False(urlQueries?.ContainsKey("asset_fields[]") ?? false);
480+
}
481+
482+
#endregion
483+
371484
#region IncludeBranch Tests
372485

373486
[Fact]
@@ -420,6 +533,68 @@ public void AddParam_AddsQueryParameter()
420533

421534
#endregion
422535

536+
#region Asset Locale Tests (single-asset fetch with locale)
537+
538+
/// <summary>
539+
/// Asset.SetLocale adds locale query param for single-asset fetch.
540+
/// </summary>
541+
[Fact]
542+
public void SetLocale_AddsQueryParameter()
543+
{
544+
var asset = CreateAsset();
545+
var locale = "en-us";
546+
547+
Asset result = asset.SetLocale(locale);
548+
549+
Assert.NotNull(result);
550+
Assert.Same(asset, result);
551+
var urlQueriesField = typeof(Asset).GetField("UrlQueries",
552+
BindingFlags.NonPublic | BindingFlags.Instance);
553+
var urlQueries = (Dictionary<string, object>)urlQueriesField?.GetValue(asset);
554+
Assert.True(urlQueries?.ContainsKey("locale") ?? false);
555+
Assert.Equal("en-us", urlQueries?["locale"]?.ToString());
556+
}
557+
/// <summary>
558+
/// Asset localisation: AddParam("locale", "ar") adds locale query for single-asset fetch.
559+
/// </summary>
560+
[Fact]
561+
public void AddParam_WithLocale_AddsLocaleQueryParameter()
562+
{
563+
var asset = CreateAsset();
564+
var locale = "ar";
565+
566+
Asset result = asset.AddParam("locale", locale);
567+
568+
Assert.NotNull(result);
569+
Assert.Same(asset, result);
570+
var urlQueriesField = typeof(Asset).GetField("UrlQueries",
571+
BindingFlags.NonPublic | BindingFlags.Instance);
572+
var urlQueries = (Dictionary<string, object>)urlQueriesField?.GetValue(asset);
573+
Assert.True(urlQueries?.ContainsKey("locale") ?? false);
574+
Assert.Equal("ar", urlQueries?["locale"]?.ToString());
575+
}
576+
577+
/// <summary>
578+
/// Single-asset fetch: locale can be combined with include_fallback.
579+
/// </summary>
580+
[Fact]
581+
public void AddParam_LocaleWithIncludeFallback_AddsBothQueryParameters()
582+
{
583+
var asset = CreateAsset();
584+
585+
asset.AddParam("locale", "en-us").IncludeFallback();
586+
587+
var urlQueriesField = typeof(Asset).GetField("UrlQueries",
588+
BindingFlags.NonPublic | BindingFlags.Instance);
589+
var urlQueries = (Dictionary<string, object>)urlQueriesField?.GetValue(asset);
590+
Assert.True(urlQueries?.ContainsKey("locale") ?? false);
591+
Assert.Equal("en-us", urlQueries?["locale"]?.ToString());
592+
Assert.True(urlQueries?.ContainsKey("include_fallback") ?? false);
593+
Assert.Equal("true", urlQueries?["include_fallback"]?.ToString());
594+
}
595+
596+
#endregion
597+
423598
#region SetHeader and RemoveHeader Tests
424599

425600
[Fact]

Contentstack.Core.Unit.Tests/Contentstack.Core.Unit.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<PackageReference Include="Moq" Version="4.20.72" />
2222
<PackageReference Include="System.Configuration.ConfigurationManager" Version="9.0.0" />
2323
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.0" />
24+
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
2425
</ItemGroup>
2526

2627
<ItemGroup>

0 commit comments

Comments
 (0)