fix(configuration-database): resolve ConfigurationDatabase-005,006,008,009,010,011 — bounded gRPC columns, split queries, CSV-parse logging, null guards, coverage
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
| Last reviewed | 2026-05-16 |
|
||||
| Reviewer | claude-agent |
|
||||
| Commit reviewed | `9c60592` |
|
||||
| Open findings | 6 |
|
||||
| Open findings | 0 |
|
||||
|
||||
## Summary
|
||||
|
||||
@@ -261,7 +261,7 @@ follow-up. The code fix in this module is complete.
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Design-document adherence |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Configurations/AuditConfiguration.cs:11` (entity `src/ScadaLink.Commons/Entities/Audit/AuditLogEntry.cs`) |
|
||||
|
||||
**Description**
|
||||
@@ -282,7 +282,20 @@ Resolve the discrepancy in one direction.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit pending). Root cause confirmed against source: the
|
||||
`AuditLogEntry` entity declares `int Id`, while the design doc's Audit Entry Schema
|
||||
table said `Long / GUID`. The entity lives in `ScadaLink.Commons`
|
||||
(`src/ScadaLink.Commons/Entities/Audit/AuditLogEntry.cs`), which is outside this
|
||||
module's editable scope, so the discrepancy was resolved by aligning the design doc to
|
||||
the code — the recommendation's second option. The schema table now records `Id` as
|
||||
`int (identity)` with an explicit justification: a 32-bit identity matches the key type
|
||||
of every other entity in the schema (uniform repository/query code), and at a sustained
|
||||
100 rows/second the `int` range is not exhausted for roughly 680 years, so the
|
||||
indefinite-retention policy poses no realistic overflow risk; if a future deployment
|
||||
ever approaches the limit the column can be widened to `bigint` via a migration without
|
||||
a schema redesign. No regression test is meaningful for a documentation alignment; the
|
||||
existing `AuditConfiguration` (`HasKey(a => a.Id)`) and the audit repository tests
|
||||
already exercise the `int` key end to end.
|
||||
|
||||
### ConfigurationDatabase-006 — `Site.GrpcNodeAAddress` / `GrpcNodeBAddress` columns are unbounded
|
||||
|
||||
@@ -290,7 +303,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Code organization & conventions |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Configurations/SiteConfiguration.cs:24-25` |
|
||||
|
||||
**Description**
|
||||
@@ -310,7 +323,19 @@ generate a migration to alter the column types.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit pending). Root cause confirmed against source:
|
||||
`SiteConfiguration` configured `NodeAAddress`/`NodeBAddress` with `HasMaxLength(500)` but
|
||||
left `GrpcNodeAAddress`/`GrpcNodeBAddress` unconfigured, so EF mapped them to
|
||||
`nvarchar(max)` — inconsistent with the sibling columns and non-indexable. Applied the
|
||||
recommendation: added `builder.Property(s => s.GrpcNodeAAddress).HasMaxLength(500)` and
|
||||
the same for `GrpcNodeBAddress`. Generated migration
|
||||
`20260517020720_BoundGrpcNodeAddressLength` altering both columns from `nvarchar(max)`
|
||||
to `nvarchar(500)` (the model snapshot was updated to match). Regression tests added in
|
||||
`SchemaConfigurationTests.cs`:
|
||||
`GrpcNodeAddressColumns_AreLengthBoundedTo500` (theory over both columns, asserting the
|
||||
EF model metadata reports `MaxLength == 500`) and
|
||||
`GrpcNodeAddressColumns_MatchSiblingNodeAddressBounds` (asserting the gRPC columns share
|
||||
the bound of the `NodeAAddress`/`NodeBAddress` siblings).
|
||||
|
||||
### ConfigurationDatabase-007 — `AuditService` does not handle JSON-serialization failure of arbitrary `afterState`
|
||||
|
||||
@@ -367,7 +392,7 @@ added in `AuditServiceTests.cs`:
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Repositories/InboundApiRepository.cs:46-58` |
|
||||
|
||||
**Description**
|
||||
@@ -390,7 +415,25 @@ gives referential integrity and correct cascade behaviour when an API key is del
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit pending). Root cause confirmed against source:
|
||||
`GetApprovedKeysForMethodAsync` mapped each CSV token with
|
||||
`int.TryParse(...) ? id : -1` then filtered `id > 0`, so any unparseable (or
|
||||
non-positive) token was discarded with no signal — a corrupt `ApprovedApiKeyIds` value
|
||||
silently approves fewer keys than intended, an authorization-relevant outcome.
|
||||
|
||||
Applied the recommendation's short-term fix: the parse loop was rewritten to log a
|
||||
warning for every token that fails to parse to a positive integer, naming the method id
|
||||
and the offending token, so corruption is observable in logs. Valid ids still resolve
|
||||
normally. `InboundApiRepository` gained an optional `ILogger<InboundApiRepository>`
|
||||
constructor parameter (defaulting to `NullLogger`, matching the `MigrationHelper`
|
||||
pattern) and the project now references `Microsoft.Extensions.Logging.Abstractions`. The
|
||||
longer-term join-table redesign would change the `ApiMethod` entity / schema and the
|
||||
`IInboundApiRepository` contract (Commons, out of this module's scope) and is left as a
|
||||
future schema-design item. Regression tests added in `InboundApiRepositoryTests.cs`:
|
||||
`GetApprovedKeysForMethod_WithMalformedCsvToken_LogsWarningAndDropsToken`,
|
||||
`GetApprovedKeysForMethod_WithValidCsv_ReturnsAllKeys`, and
|
||||
`GetApprovedKeysForMethod_WithNullOrEmptyCsv_ReturnsEmptyWithoutWarning` (using a
|
||||
capturing `ILogger` to assert the warning is emitted only on malformed input).
|
||||
|
||||
### ConfigurationDatabase-009 — Multi-collection eager loads issue cartesian-product queries
|
||||
|
||||
@@ -398,7 +441,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Performance & resource management |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Repositories/TemplateEngineRepository.cs:43-51,53-61`, `src/ScadaLink.ConfigurationDatabase/Repositories/CentralUiRepository.cs:45-55` |
|
||||
|
||||
**Description**
|
||||
@@ -421,7 +464,24 @@ cartesian explosion is avoided.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit pending). Root cause confirmed against source:
|
||||
`GetAllTemplatesAsync` and `GetTemplatesComposingAsync` (`TemplateEngineRepository`) and
|
||||
`GetTemplateTreeAsync` (`CentralUiRepository`) each `Include` three-to-four sibling
|
||||
collections in a single query, producing a cartesian-product join. The same shape was
|
||||
also present in `GetTemplateByIdAsync`, `GetInstanceByIdAsync`, `GetAllInstancesAsync`,
|
||||
`GetInstancesBySiteIdAsync`, and `GetInstanceByUniqueNameAsync`.
|
||||
|
||||
Applied the recommendation's per-query option: `.AsSplitQuery()` was added to every
|
||||
multi-collection-include query in `TemplateEngineRepository` (eight call sites) and to
|
||||
`GetTemplateTreeAsync` in `CentralUiRepository`, so each collection loads with its own
|
||||
query and the cartesian explosion is avoided. Per-query `AsSplitQuery()` was preferred
|
||||
over a global `UseQuerySplittingBehavior` so single-collection queries elsewhere keep
|
||||
the cheaper single-query plan. Split queries change query *shape* only, not results;
|
||||
regression tests added in `SchemaConfigurationTests.cs` pin that behaviour:
|
||||
`GetAllTemplatesAsync_WithMultipleMembersPerCollection_LoadsAllWithoutDuplication`
|
||||
(a template with 3 attributes, 2 alarms, 4 scripts must return exactly those counts —
|
||||
not a 24-row cartesian product) and
|
||||
`GetTemplateByIdAsync_WithMultipleMembers_LoadsAllCollections`.
|
||||
|
||||
### ConfigurationDatabase-010 — Several repositories and `InstanceLocator` lack direct test coverage
|
||||
|
||||
@@ -429,7 +489,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Testing coverage |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Repositories/TemplateEngineRepository.cs`, `Repositories/DeploymentManagerRepository.cs`, `Repositories/ExternalSystemRepository.cs`, `Repositories/InboundApiRepository.cs`, `Repositories/NotificationRepository.cs`, `Repositories/SiteRepository.cs`, `Services/InstanceLocator.cs` |
|
||||
|
||||
**Description**
|
||||
@@ -453,7 +513,24 @@ and `InstanceLocator.GetSiteIdForInstanceAsync` for found/not-found cases.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit pending). Direct repository/service tests were added using
|
||||
the existing `SqliteTestHelper` pattern. `InboundApiRepositoryTests.cs` covers
|
||||
`InboundApiRepository` (API-key/method CRUD round-trips and the
|
||||
`GetApprovedKeysForMethodAsync` valid/malformed/empty-CSV cases — see CD-008).
|
||||
`RepositoryCoverageTests.cs` adds `ExternalSystemRepositoryTests` (definition/method CRUD,
|
||||
parent-filtered method query, database-connection delete), `NotificationRepositoryTests`
|
||||
(notification-list-with-recipients and SMTP-configuration round-trips, list delete),
|
||||
`SiteRepositoryTests` (site/identifier round-trip plus the stub-attach delete fallback
|
||||
exercised for both `DeleteSiteAsync` and `DeleteDataConnectionAsync` by clearing the
|
||||
ChangeTracker, and the site-filtered instance query), `DeploymentManagerRepositoryTests`
|
||||
(deployment-record CRUD and `GetCurrentDeploymentStatusAsync` ordering, the stub-attach
|
||||
`DeleteDeploymentRecordAsync` fallback, and `DeleteInstanceAsync`'s explicit
|
||||
Restrict-FK deployment-record cleanup), and `InstanceLocatorTests`
|
||||
(`GetSiteIdForInstanceAsync` for the found and not-found cases). `TemplateEngineRepository`
|
||||
gained the CD-001 and CD-009 regression tests
|
||||
(`TemplateEngineRepositoryTests.cs`, `SchemaConfigurationTests.cs`). A constructor
|
||||
null-guard test was added for each of the five repositories/services covered, doubling
|
||||
as the CD-011 regression guard. The full module suite is green.
|
||||
|
||||
### ConfigurationDatabase-011 — Inconsistent constructor null-guarding across repositories/services
|
||||
|
||||
@@ -461,7 +538,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Code organization & conventions |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Repositories/ExternalSystemRepository.cs:11-14`, `Repositories/InboundApiRepository.cs:11-14`, `Repositories/NotificationRepository.cs:11-14`, `Services/InstanceLocator.cs:13-16` |
|
||||
|
||||
**Description**
|
||||
@@ -482,4 +559,14 @@ inconsistent constructors so all data-access types behave uniformly.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit pending). Root cause confirmed against source:
|
||||
`ExternalSystemRepository`, `InboundApiRepository`, `NotificationRepository`, and
|
||||
`InstanceLocator` assigned the injected `ScadaLinkDbContext` directly with no null
|
||||
guard, diverging from `SecurityRepository`/`CentralUiRepository`/`TemplateEngineRepository`/
|
||||
`DeploymentManagerRepository`/`SiteRepository`/`AuditService`. Applied the recommendation:
|
||||
all four constructors now use `context ?? throw new ArgumentNullException(nameof(context))`
|
||||
(`InboundApiRepository`'s guard was added as part of its CD-008 constructor change), so
|
||||
every data-access type behaves uniformly and a hand-constructed instance fails with an
|
||||
informative exception at construction rather than a later `NullReferenceException`.
|
||||
Regression: `Constructor_NullContext_Throws` tests were added for all four affected types
|
||||
(`InboundApiRepositoryTests.cs`, `RepositoryCoverageTests.cs`).
|
||||
|
||||
Reference in New Issue
Block a user