using System.Text.Json; using FluentAssertions; using Scripting.Application.Services; using Xunit; namespace BuildFlow.Scripting.Tests; /// /// ScriptExecutionService.ParseVariables 类型映射逻辑测试(纯函数,无需 DI/数据库)。 /// 验证 JSON 输入被正确转为原生 .NET 类型,让脚本能直接 (decimal)Variables["x"] 而非操作 JsonElement。 /// public class ScriptExecutionServiceParseVariablesTests { // ParseVariables 是 internal static,通过 InternalsVisibleTo 暴露给本测试程序集 private static IReadOnlyDictionary Parse(string json) => ScriptExecutionService.ParseVariables(json); [Fact] public void Empty_Or_Whitespace_Input_Returns_Empty_Dictionary() { Parse("").Should().BeEmpty(); Parse(" ").Should().BeEmpty(); } [Fact] public void Integer_Number_Maps_To_Long() { var vars = Parse("{\"count\": 42, \"big\": 9007199254740993}"); vars["count"].Should().BeOfType().Which.Should().Be(42L); // 9007199254740993 超出 double 精度但 long 可表示,应保持 long vars["big"].Should().BeOfType().Which.Should().Be(9007199254740993L); } [Fact] public void Decimal_Number_Maps_To_Decimal_For_Money_Friendliness() { var vars = Parse("{\"price\": 19.99}"); vars["price"].Should().BeOfType().Which.Should().Be(19.99m); } [Fact] public void Bool_Maps_To_Boolean() { var vars = Parse("{\"active\": true, \"deleted\": false}"); vars["active"].Should().BeOfType().Which.Should().BeTrue(); vars["deleted"].Should().BeOfType().Which.Should().BeFalse(); } [Fact] public void Null_Maps_To_Null() { var vars = Parse("{\"name\": null}"); vars["name"].Should().BeNull(); } [Fact] public void Plain_String_Maps_To_String() { var vars = Parse("{\"title\": \"hello world\"}"); vars["title"].Should().BeOfType().Which.Should().Be("hello world"); } [Fact] public void Iso8601_String_Maps_To_DateTime() { var vars = Parse("{\"createdAt\": \"2026-07-06T10:30:00Z\"}"); vars["createdAt"].Should().BeOfType(); var dt = (DateTime)vars["createdAt"]!; dt.Year.Should().Be(2026); dt.Month.Should().Be(7); dt.Day.Should().Be(6); dt.Hour.Should().Be(10); dt.Minute.Should().Be(30); } [Fact] public void Guid_String_Maps_To_Guid() { var guidStr = "f47ac10b-58cc-4372-a567-0e02b2c3d479"; var vars = Parse("{\"id\": \"" + guidStr + "\"}"); vars["id"].Should().BeOfType(); ((Guid)vars["id"]!).Should().Be(Guid.Parse(guidStr)); } /// ISO 8601 日期优先于 Guid(日期串不会误判为 Guid)。 [Fact] public void Date_Parsed_Before_Guid_Check() { var vars = Parse("{\"ts\": \"2026-01-15\"}"); vars["ts"].Should().BeOfType(); } [Fact] public void Object_Maps_To_JsonElement() { var vars = Parse("{\"nested\": {\"a\": 1, \"b\": 2}}"); vars["nested"].Should().BeOfType(); var el = (JsonElement)vars["nested"]!; el.ValueKind.Should().Be(JsonValueKind.Object); } [Fact] public void Array_Maps_To_JsonElement() { var vars = Parse("{\"items\": [1, 2, 3]}"); vars["items"].Should().BeOfType(); var el = (JsonElement)vars["items"]!; el.ValueKind.Should().Be(JsonValueKind.Array); el.GetArrayLength().Should().Be(3); } [Fact] public void Mixed_Types_All_Resolve_Correctly() { var json = "{\"count\": 100, \"price\": 49.95, \"active\": true, \"name\": \"widget\", " + "\"id\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"date\": \"2026-07-06T00:00:00\", " + "\"meta\": {\"key\": \"val\"}, \"nullable\": null}"; var vars = Parse(json); vars["count"].Should().BeOfType(); vars["price"].Should().BeOfType(); vars["active"].Should().BeOfType(); vars["name"].Should().BeOfType(); vars["id"].Should().BeOfType(); vars["date"].Should().BeOfType(); vars["meta"].Should().BeOfType(); vars["nullable"].Should().BeNull(); } /// 脚本端典型用法:原生类型可直接强转,无需 GetInt64() 等中间步骤。 [Fact] public void Script_Idiom_Direct_Cast_Works() { var vars = Parse("{\"qty\": 5, \"rate\": 12.5}"); // 模拟脚本里写 (long)Variables["qty"] * (decimal)Variables["rate"] var result = (long)vars["qty"]! * (decimal)vars["rate"]!; result.Should().Be(62.5m); } /// Ordinal 键比较器:键名大小写敏感。 [Fact] public void Keys_Are_Case_Sensitive_Ordinal() { var vars = Parse("{\"Name\": \"a\", \"name\": \"b\"}"); vars.Should().HaveCount(2); vars["Name"].Should().Be("a"); vars["name"].Should().Be("b"); } }