Stop the GL account-code check from being able to abort startup

The catch filter only matched DomainException/OperationCanceledException,
so anything else escaped a hosted service and took the host down. One such
path was mine: `"items": null` deserializes over the property initialiser,
so `data.Items.Select(...)` would NullReferenceException straight past the
filter.

Fixed at both ends - the reader tolerates a null or absent items array, and
the catch is now a deliberate catch-all that only lets a genuine host
shutdown through. This is a diagnostic; a diagnostic that can core-dump the
thing it diagnoses is worse than no diagnostic at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 11:53:31 +05:30
parent e0601d5a8d
commit 3c8837e21e
3 changed files with 33 additions and 7 deletions
@@ -91,6 +91,30 @@ public class GlAccountCodeValidatorTests
await validator.StartAsync(CancellationToken.None);
}
/// <summary>
/// The earlier catch filter only matched DomainException/OperationCanceledException, so anything
/// else — a parse failure, or a null `items` NRE in the reader — escaped and aborted the host.
/// A diagnostic must not be able to crash the thing it is diagnosing.
/// </summary>
[Theory]
[MemberData(nameof(UnexpectedFailures))]
public async Task StartAsync_DoesNotThrow_OnAnyUnexpectedFailure(Exception failure)
{
var validator = CreateValidator(
new() { ["Grn:GlInventoryAccountCode"] = "1100" },
new StubGl(failure));
await validator.StartAsync(CancellationToken.None);
}
public static TheoryData<Exception> UnexpectedFailures() =>
[
new NullReferenceException("items was null"),
new global::System.Text.Json.JsonException("unexpected shape"),
new InvalidOperationException("something else entirely"),
new HttpRequestException("connection reset"),
];
[Fact]
public async Task StartAsync_DoesNotThrow_WhenTheChartOfAccountsReadIsCancelled()
{