diff --git a/src/TALXIS.CLI.Platform.Dataverse.Data/EntityJsonConverter.cs b/src/TALXIS.CLI.Platform.Dataverse.Data/EntityJsonConverter.cs index 971a2fa5..d5870507 100644 --- a/src/TALXIS.CLI.Platform.Dataverse.Data/EntityJsonConverter.cs +++ b/src/TALXIS.CLI.Platform.Dataverse.Data/EntityJsonConverter.cs @@ -177,6 +177,20 @@ public static JsonElement EntityToJson(Entity entity, bool includeAnnotations = if (element.TryGetInt32(out var singleVal)) return new OptionSetValueCollection { new OptionSetValue(singleVal) }; } + if (attrMeta is IntegerAttributeMetadata) + { + if (element.TryGetInt32(out var intVal)) return intVal; + + + var column = attrMeta.LogicalName ?? "whole number column"; + + if (element.TryGetInt64(out var outOfRange)) + throw new ArgumentException( + $"Value {outOfRange} for '{column}' is outside the valid whole number range ({int.MinValue} to {int.MaxValue})."); + + throw new ArgumentException( + $"Value {element.GetRawText()} for '{column}' is not a valid whole number."); + } // Default: try integer types first, then floating-point. if (element.TryGetInt32(out var i32)) return i32; diff --git a/tests/TALXIS.CLI.Tests/Environment/Platforms/Dataverse/EntityJsonConverterTests.cs b/tests/TALXIS.CLI.Tests/Environment/Platforms/Dataverse/EntityJsonConverterTests.cs new file mode 100644 index 00000000..a452bb65 --- /dev/null +++ b/tests/TALXIS.CLI.Tests/Environment/Platforms/Dataverse/EntityJsonConverterTests.cs @@ -0,0 +1,95 @@ +using System; +using System.Text.Json; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Metadata; +using TALXIS.CLI.Platform.Dataverse.Data; +using Xunit; + +namespace TALXIS.CLI.Tests.Environment.Platforms.Dataverse; + +/// +/// Unit tests for value coercion, focused on +/// whole-number (Int32) range handling so out-of-range values produce a clear +/// message instead of a misleading SDK type-mismatch error. +/// +public class EntityJsonConverterTests +{ + private const string Entity = "txcv3_valv3rt1434"; + private const string Column = "txcv3_wholenumplain"; + + private static EntityMetadata MetadataWith(AttributeMetadata attribute) + { + var meta = new EntityMetadata { LogicalName = Entity }; + SetProp(meta, nameof(EntityMetadata.Attributes), new[] { attribute }); + return meta; + } + + private static AttributeMetadata IntegerColumn(string logicalName = Column) + { + var attr = new IntegerAttributeMetadata(); + SetProp(attr, nameof(AttributeMetadata.LogicalName), logicalName); + return attr; + } + + private static AttributeMetadata BigIntColumn(string logicalName) + { + var attr = new BigIntAttributeMetadata(); + SetProp(attr, nameof(AttributeMetadata.LogicalName), logicalName); + return attr; + } + + // Xrm SDK metadata properties expose non-public setters — reach them by reflection. + private static void SetProp(object target, string name, object? value) + => target.GetType().GetProperty(name)!.SetValue(target, value); + + private static Entity Convert(EntityMetadata metadata, string json) + => EntityJsonConverter.JsonToEntity(Entity, JsonDocument.Parse(json).RootElement, metadata); + + [Fact] + public void WholeNumber_WithinInt32Range_StoredAsInt() + { + var entity = Convert(MetadataWith(IntegerColumn()), $"{{\"{Column}\":2000000000}}"); + + Assert.Equal(2000000000, Assert.IsType(entity[Column])); + } + + [Fact] + public void WholeNumber_AboveInt32Max_ThrowsRangeErrorWithValueAndColumn() + { + var ex = Assert.Throws( + () => Convert(MetadataWith(IntegerColumn()), $"{{\"{Column}\":3000000000}}")); + + Assert.Contains("3000000000", ex.Message); + Assert.Contains(Column, ex.Message); + Assert.Contains("range", ex.Message, StringComparison.OrdinalIgnoreCase); + // The misleading SDK wording must not leak through. + Assert.DoesNotContain("Incorrect attribute value type", ex.Message); + } + + [Fact] + public void WholeNumber_BelowInt32Min_ThrowsRangeError() + { + var ex = Assert.Throws( + () => Convert(MetadataWith(IntegerColumn()), $"{{\"{Column}\":-3000000000}}")); + + Assert.Contains("-3000000000", ex.Message); + } + + [Fact] + public void WholeNumber_NonInteger_ThrowsFormatError() + { + var ex = Assert.Throws( + () => Convert(MetadataWith(IntegerColumn()), $"{{\"{Column}\":1.5}}")); + + Assert.Contains("whole number", ex.Message, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public void BigInt_AboveInt32Max_StoredAsLong() + { + const string column = "txcv3_bignum"; + var entity = Convert(MetadataWith(BigIntColumn(column)), $"{{\"{column}\":3000000000}}"); + + Assert.Equal(3000000000L, Assert.IsType(entity[column])); + } +}