Handle empty featured_image from Columbia feed

Products with no featured image return featured_image: [] (empty array) rather
than an object or null, which failed to bind to a single ColumbiaImage and
aborted the whole sync. Adds ColumbiaImageJsonConverter that reads the object
when present and yields null for any non-object form ([], false, ""), and drops
the unused GalleryImages property (we only use featured_image) to remove the
same risk. Regression tests cover both the empty-array and object cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 11:47:28 -04:00
parent 2286b5431d
commit eed61a298b
4 changed files with 81 additions and 1 deletions
@@ -230,6 +230,37 @@ public class ColumbiaCatalogMapperTests
// ── End-to-end mapping invariants ─────────────────────────────────────
// ── Tolerant image deserialization (WordPress returns [] / false when empty) ──
private static readonly JsonSerializerOptions ClientJsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
PropertyNameCaseInsensitive = true,
Converters = { new ColumbiaImageJsonConverter() },
};
[Fact]
public void Deserialize_FeaturedImageEmptyArray_YieldsNullImage()
{
// WordPress returns featured_image: [] for products with no image.
var json = """{ "sku": "X1", "name": "No Image", "featured_image": [] }""";
var product = JsonSerializer.Deserialize<ColumbiaProduct>(json, ClientJsonOptions);
Assert.NotNull(product);
Assert.Null(product!.FeaturedImage);
Assert.Null(ColumbiaCatalogMapper.Map(product).ImageUrl);
}
[Fact]
public void Deserialize_FeaturedImageObject_ParsesSrc()
{
var json = """{ "sku": "X1", "name": "Has Image", "featured_image": { "id": 5, "src": "https://x/img.png", "name": "i", "alt": "" } }""";
var product = JsonSerializer.Deserialize<ColumbiaProduct>(json, ClientJsonOptions);
Assert.Equal("https://x/img.png", product!.FeaturedImage!.Src);
Assert.Equal("https://x/img.png", ColumbiaCatalogMapper.Map(product).ImageUrl);
}
[Fact]
public void Map_AlwaysStampsSource_AndLeavesEnrichmentFieldsNull()
{