code-reviews: 2026-06-25 re-review of Galaxy-adoption delta at 3cd7776

Re-review Server + Tests modules over 88915c3..3cd7776 (the
ZB.MOM.WW.GalaxyRepository 0.2.0 adoption + array-write fixes).
Security-critical browse-scope wiring verified sound. 3 new findings:

- Server-059 (Medium): dashboard Galaxy summary memoized only on
  cache Sequence -> status/timestamps freeze during a Galaxy SQL outage.
- Server-060 (Low): no DI test asserts IGalaxyBrowseScopeProvider
  resolves to GatewayBrowseScopeProvider (registration-order invariant).
- Tests-041 (Medium): memoization invalidation path untested.
This commit is contained in:
Joseph Doherty
2026-06-25 13:21:23 -04:00
parent 3cd7776fe8
commit b062cc0440
3 changed files with 166 additions and 9 deletions
+78 -3
View File
@@ -4,13 +4,38 @@
|---|---|
| Module | `src/ZB.MOM.WW.MxGateway.Tests` |
| Reviewer | Claude Code |
| Review date | 2026-06-18 |
| Commit reviewed | `88915c3` |
| Review date | 2026-06-25 |
| Commit reviewed | `3cd7776` |
| Status | Re-reviewed |
| Open findings | 0 |
| Open findings | 1 |
## Checklist coverage
### 2026-06-25 re-review (commit `3cd7776`)
Re-review of the `88915c3..3cd7776` diff. Primary change is the Galaxy library adoption
reconciliation: eight `Galaxy/*Tests.cs` files and `Grpc/GalaxyRepositoryGrpcServiceTests.cs`
deleted (code moved to `ZB.MOM.WW.GalaxyRepository` library and its own test suite).
Replacements: `Grpc/GalaxyRepositoryHostWiringTests.cs` (new), `Security/Authorization/GatewayBrowseScopeProviderTests.cs` (new),
`Dashboard/DashboardGalaxySummaryProjectorTests.cs` (new), `DashboardSnapshotServiceTests.cs`
(updated for projector-driven Galaxy summary). Secondary change: `GatewayArrayWriteWiringTests.cs`
(+438 lines: six missing write-expansion wiring tests from Tests-040 resolution, plus
`AddItem2`, `AddBufferedItem`, `AddItemBulk` normalization tests) and `ConstraintEnforcerTests.cs`
(+126 lines: three new authz-path tests for array-address denial).
| # | Category | Result |
|---|---|---|
| 1 | Correctness & logic bugs | No issues found. `GalaxyRepositoryHostWiringTests.BrowseChildren_BrowseSubtreesConstraintThroughHostWiring_FiltersChildren` correctly asserts 2 children unconstrained (phase 1) and 0 children under a non-matching scope (phase 2) — non-tautological. `DashboardGalaxySummaryProjectorTests` and `ConstraintEnforcerTests` new additions all assert concrete post-conditions; no swallowed exceptions or trivially-green assertions found. |
| 2 | mxaccessgw conventions | No issues found. New files use file-scoped namespaces, `sealed` classes, target-typed `new()`, and PascalCase `Method_Condition_Result` names. `DashboardSnapshotServiceTests` uses non-sealed `FakeApiKeyAdminStore` / `CountingApiKeyAdminStore` as intentional base classes for the inheritance chain (`SequencedApiKeyAdminStore`); acceptable in context. |
| 3 | Concurrency & thread safety | No issues found. `GatewayRepositoryHostWiringTests` and `GatewayBrowseScopeProviderTests` are synchronous or use immediate stubs; no new `Task.Delay` or wall-clock timing. `DashboardSnapshotServiceTests` new watch-snapshot tests use `CancellationTokenSource(TimeSpan.FromSeconds(2))` bounds, consistent with the existing suite. |
| 4 | Error handling & resilience | No issues found. |
| 5 | Security | No issues found. `GalaxyRepositoryHostWiringTests` proves the security-critical authz filter chain end-to-end: `GatewayRequestIdentityAccessor.Push``GatewayBrowseScopeProvider.ResolveBrowseSubtrees` → lib `GalaxyRepositoryGrpcService` → filtered `BrowseChildren`. `GatewayBrowseScopeProviderTests` covers happy path (non-empty `BrowseSubtrees`) and no-identity fallback (empty list = no scoping, as documented — safe behind the global auth interceptor). The deleted `GalaxyRepositoryGrpcServiceTests.BrowseChildren_BrowseSubtreesConstraint_FiltersChildren` (which passed `GatewayRequestIdentityAccessor` directly rather than through the `GatewayBrowseScopeProvider` wrapper) is now superseded by a stricter test that threads through the actual production seam. |
| 6 | Performance & resource management | No issues found. |
| 7 | Design-document adherence | No issues found. Galaxy test deletions match the `A2-galaxyrepository-adoption-handoff.md` adoption description. All eight deleted `Galaxy/*Tests.cs` files covered lib-owned types now in `scadaproj/ZB.MOM.WW.GalaxyRepository`; their test equivalents are confirmed present in the library's test suite (`GalaxyHierarchyCacheTests`, `GalaxyHierarchyRefreshServiceTests`, `GalaxyBrowseProjectorTests`, `GalaxyDeployNotifierTests`, `GalaxyHierarchyProjectorTests`, `GalaxyAlarmAttributeMappingTests`, `GalaxyHierarchySnapshotStoreTests`). `GalaxyProtoMapperTests` and `GalaxyHierarchyIndexTests` have no direct lib-side equivalent — the types are lib-owned; the index is exercised indirectly through `GalaxyBrowseProjectorTests` and `GalaxyHierarchyProjectorTests`; this is a lib-side gap, not a host-module finding. |
| 8 | Code organization & conventions | No issues found. |
| 9 | Testing coverage | Issue found: Tests-041 (`DashboardSnapshotServiceTests` does not assert the memoization-by-sequence invalidation path in `DashboardSnapshotService.ResolveGalaxySummary` — a new sequence bumps the cached summary; all tests use a fixed-sequence `StubGalaxyHierarchyCache` so a bug that never invalidates the memoized summary (always returning the first computed result) would pass). |
| 10 | Documentation & comments | No issues found. New test files carry accurate class-level `<summary>` docs; `GalaxyFilterInputSafetyTests.cs` class summary correctly updated to reference `ZB.MOM.WW.GalaxyRepository` lib types and notes that the `RegexCacheCapacity` counter is now lib-internal. |
### 2026-06-15 re-review (commit `410acc9`)
Re-review of the `42b0037..410acc9` diff (≈57 files), scoped to the alarm-provider
@@ -756,3 +781,53 @@ The cancellation tests for `WorkerClient` in `WorkerClientTests` *do* exercise t
**Recommendation:** Add one wiring test per uncovered variant (or a single `[Theory]` over the six command kinds), constructing the matching command type with a `SparseArrayValue` and asserting `worker.LastCommand!.Command.<Variant>.Value.KindCase == MxValue.KindOneofCase.ArrayValue` after `session.InvokeAsync`. The `SparseArrayExpanderTests` already pin the expander logic exhaustively; the wiring tests need only check that the choke point invokes expansion for each variant, not the expansion semantics themselves. The four secured variants (`WriteSecured`, `Write2`, `WriteSecured2`, `WriteSecured2Bulk`) can reuse the same `CapturingWorkerClient` stub.
**Resolution:** 2026-06-18 — root cause confirmed: the six arms (`WriteSecured`, `Write2`, `WriteSecured2`, `Write2Bulk`, `WriteSecuredBulk`, `WriteSecured2Bulk`) each had a `case` in `NormalizeOutboundCommand` calling `ExpandValue` but no wiring test. Server-057's additions (`AddItemBulk`, `AddBufferedItem`) covered address-normalization tests only, not the missing write-expansion variants. Added six tests to `GatewayArrayWriteWiringTests.cs` — one per uncovered arm — each constructing the matching command with a 4-element `SparseArrayValue` (Integer, single element set), driving it through `GatewaySession.InvokeAsync`, and asserting `worker.LastCommand.Command.<Variant>.Value.KindCase == ArrayValue` and the expected element positions. Tests: `WriteSecured_SparseArrayValue_ExpandedBeforeReachingWorker`, `Write2_SparseArrayValue_ExpandedBeforeReachingWorker`, `WriteSecured2_SparseArrayValue_ExpandedBeforeReachingWorker`, `Write2Bulk_SparseArrayEntryValue_ExpandedBeforeReachingWorker`, `WriteSecuredBulk_SparseArrayEntryValue_ExpandedBeforeReachingWorker`, `WriteSecured2Bulk_SparseArrayEntryValue_ExpandedBeforeReachingWorker`. All 13 `GatewayArrayWriteWiring` tests pass (7 pre-existing + 6 new).
### Tests-041
| Field | Value |
|---|---|
| Severity | Medium |
| Category | Testing coverage |
| Location | `src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardSnapshotServiceTests.cs`, `src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardSnapshotService.cs:109-128` |
| Status | Open |
**Description:** `DashboardSnapshotService.ResolveGalaxySummary` introduced a lock-free
memoization cache keyed on `GalaxyHierarchyCacheEntry.Sequence` (lines 109-128):
on a matching sequence the cached `DashboardGalaxySummary` is returned without
recomputing; on a new sequence it recomputes and stores the replacement. This
restores the pre-adoption O(1)-per-tick behavior (the library bumps `Sequence`
only on a heavy refresh).
`DashboardSnapshotServiceTests.GetSnapshot_ProjectsGalaxySummaryFromHierarchyCache`
verifies that a first `GetSnapshot()` returns the correct derived summary, but the
`StubGalaxyHierarchyCache` is immutable — it always returns the same entry with the
same `Sequence`. No test:
1. Calls `GetSnapshot()` twice with an unchanged entry and verifies the second call
returns the same summary (memoization hit); a test could observe this via a
counting stub that increments on each `Project` call.
2. Swaps the cache stub to a new entry with an incremented `Sequence`, then calls
`GetSnapshot()` and asserts the snapshot reflects the **new** entry's templates
and categories (invalidation). A bug that inverts the sequence check — e.g.
`cached is not null && cached.Sequence != sequence` — would always return the
first-computed summary, causing the dashboard Galaxy section to freeze after the
initial load regardless of subsequent cache refreshes. This regression passes all
existing tests because no test ever presents two entries with different sequences.
**Recommendation:** Add two tests:
1. `GetSnapshot_WhenGalaxyCacheSequenceUnchanged_ReusesProjectedSummary`: create a
`CountingProjectorCache` or a `SequenceChangingGalaxyHierarchyCache` that allows
controlled sequence changes. Call `GetSnapshot()` twice with the same entry
(same `Sequence`); assert the second snapshot's `Galaxy` object is reference-equal
to (or at minimum observably the same as) the first, and that the projector was
invoked only once (e.g. via a counting stub wrapping `DashboardGalaxyProjector`).
2. `GetSnapshot_WhenGalaxyCacheSequenceChanges_RecomputesGalaxySummary`: present
entry A (Sequence = 1, one template), call `GetSnapshot()`, then swap the cache
to entry B (Sequence = 2, different templates), call `GetSnapshot()` again, and
assert the second snapshot reflects entry B's template set, not entry A's. A
`MutableGalaxyHierarchyCache` with a settable `Current` property suffices; the
existing `StubGalaxyHierarchyCache` pattern is a single-line extension.
**Resolution:**