fix(tagconfig): a numerically-spelled enum names no member — reject it fleet-wide (Driver.Sql.Contracts-002)
TagConfigJson.ClassifyEnumText classified via Enum.TryParse(text, ignoreCase: true), which accepts a numeric STRING for any enum: "1" returns the member at that ordinal and an out-of-range "99" parses just as happily. Every driver contract's strict enum read inherits that, so a Sql tag authored "type":"99" was ACCEPTED, carried (DriverDataType)99, and SqlPollReader.TryCoerce fell through its switch to the String arm — a stringified cell published Good under a type nothing can map. Refuse a digit- or sign-leading spelling before the parse (judged on the TRIMMED text, because Enum.TryParse trims), and require Enum.IsDefined — which covers what the leading-character check cannot, since TryParse also ORs a comma-separated list for a non-[Flags] enum. Mirrors RawBrowseCommitMapper.ParseNativeType and MTConnectTagDefinitionFactory.TryParseDataType, which already guard their own parses. Three AdminUI restatements of the same rule now ask ClassifyEnumText instead of Enum.TryParse (TagConfigJson.GetEnum, SqlTagConfigModel.ReadEnumStrict, MqttTagConfigModel.DescribeInvalidEnum/GetEnumNullable) — leaving them would have opened the #549 direction the moment the runtime tightened: a blob the modal accepts and the driver skips. RED first: 10 failing in Core.Abstractions.Tests, 4 in Driver.Sql.Tests, and 10 of the 11 new fleet-wide parity cases (re-confirmed by reverting the Core fix).
This commit is contained in:
@@ -196,4 +196,31 @@ public class SqlEquipmentTagParserTests
|
||||
=> SqlEquipmentTagParser.TryParse(
|
||||
"""{"driver":"Sql","model":"KeyValue","table":"T","keyColumn":"k","keyValue":"v","valueColumn":"c","type":"Nonsense"}""",
|
||||
"raw", out _).ShouldBeFalse();
|
||||
|
||||
/// <summary>
|
||||
/// Driver.Sql.Contracts-002. A numerically-spelled enum is not a member name, and the strict read
|
||||
/// must refuse it exactly as it refuses a typo. It used to parse — <c>"99"</c> gave
|
||||
/// <c>(DriverDataType)99</c>, a declared type nothing can map, and <c>SqlPollReader.TryCoerce</c>
|
||||
/// then fell through its switch to the <c>String</c> arm and published a stringified cell under it.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData("""{"driver":"Sql","model":"KeyValue","table":"T","keyColumn":"k","keyValue":"v","valueColumn":"c","type":"99"}""")]
|
||||
[InlineData("""{"driver":"Sql","model":"KeyValue","table":"T","keyColumn":"k","keyValue":"v","valueColumn":"c","type":"8"}""")]
|
||||
// "1" is SqlTagModel.WideRow's ordinal — an otherwise-complete WideRow blob, so the rejection can only
|
||||
// come from the enum read and not from a missing field.
|
||||
[InlineData("""{"driver":"Sql","model":"1","table":"T","columnName":"c","rowSelector":{"topByTimestamp":"ts"}}""")]
|
||||
public void TryParse_aNumericallySpelledEnum_rejectsTag(string json)
|
||||
=> SqlEquipmentTagParser.TryParse(json, "raw", out _).ShouldBeFalse();
|
||||
|
||||
/// <summary>
|
||||
/// …and the deploy gate explains it in the same words every other invalid enum draws, rather than
|
||||
/// falling through to the backstop warning that names no field at all.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Inspect_aNumericallySpelledType_namesTheFieldAndTheValidMembers()
|
||||
{
|
||||
var warnings = SqlTagDefinitionFactory.Inspect(
|
||||
"""{"driver":"Sql","model":"KeyValue","table":"T","keyColumn":"k","keyValue":"v","valueColumn":"c","type":"99"}""");
|
||||
warnings.ShouldContain(w => w.Contains("'99'") && w.Contains("type") && w.Contains(nameof(DriverDataType.Float64)));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user