fix(commons): reject comma-joined enum tokens in strict audit query parsers (arch-review 2026-08-19 P6-3, register row 99 defect)
This commit is contained in:
@@ -145,6 +145,66 @@ public class AuditQueryParamParsersTests
|
||||
new[] { "", " ", null }, "status"));
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Comma-joined single token (arch-review 2026-08-19 P6-3, register row 99
|
||||
// closure defect): a bare Enum.TryParse splits and bitwise-ORs a comma list
|
||||
// inside ONE token for ANY enum, not just [Flags] ones. AuditStatus is dense
|
||||
// and 0-based, so the OR'd composite almost always collides with a REAL, but
|
||||
// WRONG, member. Values arriving as separate tokens (repeated query param /
|
||||
// already-split list) must be unaffected — only a comma INSIDE one token is
|
||||
// rejected. Proof this closes the defect: reverting the fix (`git stash`)
|
||||
// makes every one of these throw-expecting tests fail, because the call
|
||||
// instead RETURNS the wrong composite value below.
|
||||
// ========================================================================
|
||||
|
||||
[Fact]
|
||||
public void ParseEnumListStrict_CommaJoinedTwoNames_IsRejected_NotSilentlyOrdedIntoWrongMember()
|
||||
{
|
||||
// Forwarded(1) | Attempted(2) = 3 = Delivered — the exact silent-wrong-data
|
||||
// collision the finding calls out. Pre-fix this returned [Delivered].
|
||||
var ex = Assert.Throws<AuditFilterParseException>(
|
||||
() => AuditQueryParamParsers.ParseEnumListStrict<AuditStatus>(
|
||||
new[] { "Forwarded,Attempted" }, "status"));
|
||||
Assert.Contains("Forwarded,Attempted", ex.Message);
|
||||
Assert.Contains("status", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseEnumListStrict_CommaJoinedNameAndOrdinal_IsRejected_NotSmuggledPastNumericGuard()
|
||||
{
|
||||
// Failed(4) | 7 = 7 = Skipped — a comma smuggles an ordinal past the
|
||||
// LooksNumeric guard because the guard only inspects the WHOLE token, and
|
||||
// a comma-joined token starts with a letter. Pre-fix this returned [Skipped].
|
||||
var ex = Assert.Throws<AuditFilterParseException>(
|
||||
() => AuditQueryParamParsers.ParseEnumListStrict<AuditStatus>(
|
||||
new[] { "Failed,7" }, "status"));
|
||||
Assert.Contains("Failed,7", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseEnumListStrict_CommaJoinedNamesDropAMember_IsRejected()
|
||||
{
|
||||
// Submitted(0) | Parked(5) = 5 = Parked — Submitted silently vanishes from
|
||||
// the composite. Pre-fix this returned [Parked], dropping Submitted entirely.
|
||||
var ex = Assert.Throws<AuditFilterParseException>(
|
||||
() => AuditQueryParamParsers.ParseEnumListStrict<AuditStatus>(
|
||||
new[] { "Submitted,Parked" }, "status"));
|
||||
Assert.Contains("Submitted,Parked", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseEnumListStrict_RepeatedParamTokensOfValidNames_StillParseIndependently()
|
||||
{
|
||||
// The fix targets a comma INSIDE one token — values that legitimately
|
||||
// arrive as separate tokens (a repeated query param, or an already-split
|
||||
// list) must keep working exactly as before.
|
||||
var result = AuditQueryParamParsers.ParseEnumListStrict<AuditStatus>(
|
||||
new[] { "Forwarded", "Attempted", "Submitted" }, "status");
|
||||
Assert.Equal(
|
||||
new[] { AuditStatus.Forwarded, AuditStatus.Attempted, AuditStatus.Submitted },
|
||||
result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseStringList_NullInput_ReturnsNull()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user