d2d2e5f68f
Restores the `code-reviews/` tree (was unwritten on this working copy)
and re-reviews every module per `REVIEW-PROCESS.md` against HEAD
`d692232`. The diff in scope is the five commits since the last sweep:
`dc9c0c9` (ZB.MOM.WW gateway-side rename + slnx migrate),
`397d3c5` (client SDK rename + the missing alarm-RPC proto types and
the .NET DiscoverHierarchyOptions POCO), `27ed651` (role-based LDAP
auth + HubToken bearer, drop PathBase), `6594359` (sidebar layout +
three SignalR push hubs), and `d692232` (EventsHub publisher + doc
refresh).
Module status
| Module | Open | Total | Delta this pass |
|---|---|---|---|
| Server | 8 | 43 | +6 |
| Contracts | 2 | 17 | +2 |
| Tests | 2 | 26 | +2 |
| IntegrationTests | 3 | 24 | +3 |
| Client.Java | 5 | 31 | +5 |
| Client.Rust | 1 | 21 | +1 |
| Worker | 0 | 25 | 0 (rename-only diff, clean) |
| Worker.Tests | 0 | 30 | 0 (rename-only diff, clean) |
| Client.Dotnet | 0 | 17 | 0 (rename + alarm-fix diff, clean) |
| Client.Python | 0 | 21 | 0 (rename + alarm-fix diff, clean) |
| Client.Go | 0 | 21 | 0 (rename + alarm-fix diff, clean) |
Total new findings: 19. Severity breakdown: 1 Medium-security
(Server-038), 4 Medium-documentation/coverage, 14 Low.
New findings
* Server-038 (Medium / Security) — EventsHub.SubscribeSession accepts
any session id from any Viewer; no per-session ACL guards the
EventsHub group fan-out.
* Server-039 (Low / Error handling) — HubTokenService.Validate
accepts a payload with null Name/NameIdentifier.
* Server-040 (Low / Conventions) — MapGroupsToRoles undocumented
full-vs-RDN lookup precedence.
* Server-041 (Low / Design adherence) — EventStreamService calls
IDashboardEventBroadcaster.Publish without a try/catch — fragile
seam relying on the never-throw contract.
* Server-042 (Low / Performance) — DashboardSnapshotPublisher tight
retry loop with no backoff (vs AlarmsHubPublisher 5s delay).
* Server-043 (Low / Documentation) — HubTokenService singleton
sharing across login + hub-token validation undocumented.
* Contracts-016 (Low / Conventions) — QueryActiveAlarmsRequest.session_id
reserved-for-future-use ambiguity.
* Contracts-017 (Low / Documentation) — rpc QueryActiveAlarms doc
omits the alarm_filter_prefix filter description.
* Tests-025 (Low / Conventions) — duplicate NullDashboardEventBroadcaster
fakes in EventStreamServiceTests and GatewayEndToEndFakeWorkerSmokeTests.
* Tests-026 (Medium / Testing coverage) — no test proves
EventStreamService actually calls IDashboardEventBroadcaster.Publish.
* IntegrationTests-022 (Low / Conventions) — ResolveRepositoryRoot
silent fallback to Directory.GetCurrentDirectory().
* IntegrationTests-023 (Low / Testing coverage) — DashboardLdapLiveTests
success-path asserts ldap_group but not the Role claim.
* IntegrationTests-024 (Low / Conventions) — inline
NullDashboardEventBroadcaster fake duplicates Tests-side copies.
* Client.Java-027 (Medium / Documentation) — README + JavaClientDesign
Gradle task names still use the old short project names.
* Client.Java-028 (Medium / Design adherence) — JavaClientDesign
build-layout shows the old `com/dohertylan/mxgateway/` package paths.
* Client.Java-029 (Low / Documentation) — README installDist path
cites the wrong directory.
* Client.Java-030 (Low / Testing coverage) — no Java test exercises
the regenerated QueryActiveAlarmsRequest RPC.
* Client.Java-031 (Low / Conventions) — README prose uses old short
project names instead of canonical prefixed ones.
* Client.Rust-021 (Low / Design adherence) — RustClientDesign.md
"Crate layout" shows an aspirational nested `crates/zb-mom-ww-mxgateway-client/`
that does not exist; actual layout is the flat top-level crate.
Two pre-existing pending findings (Server-031 lock-contention,
Server-032 bounded event channel) remain unchanged — neither was
touched by this wave of commits.
Process notes
- The `code-reviews/` tree was not in this working copy's git
history (the local extract pre-dates the divergent branch that
carried the reviews). Restored from `dd7ca16` via
`git checkout dd7ca16 -- code-reviews/` before the re-review.
- Some "Resolved" entries in the restored findings.md reference
fixes that landed on the divergent branch (the same one that
carried the reviews) and are not present on the current main
lineage. The re-review treats those statuses as historical;
the new pass only files findings against HEAD's actual state.
- `python code-reviews/regen-readme.py --check` is green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
159 lines
4.4 KiB
Python
159 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Tests for regen-readme.py.
|
|
|
|
Dependency-free: run with `python code-reviews/test_regen_readme.py`.
|
|
Exits 0 if all tests pass, 1 otherwise.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import tempfile
|
|
import traceback
|
|
from pathlib import Path
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
|
|
# regen-readme.py is not an importable module name (hyphen), so load it by path.
|
|
_spec = importlib.util.spec_from_file_location("regen_readme", HERE / "regen-readme.py")
|
|
regen = importlib.util.module_from_spec(_spec)
|
|
_spec.loader.exec_module(regen)
|
|
|
|
FIXTURE = """# Code Review — Demo
|
|
|
|
| Field | Value |
|
|
|---|---|
|
|
| Module | `src/Demo` |
|
|
| Reviewer | Tester |
|
|
| Review date | 2026-05-18 |
|
|
| Commit reviewed | `abc1234` |
|
|
| Status | Reviewed |
|
|
| Open findings | 1 |
|
|
|
|
## Findings
|
|
|
|
### Demo-001
|
|
|
|
| Field | Value |
|
|
|---|---|
|
|
| Severity | High |
|
|
| Category | Security |
|
|
| Location | `src/Demo/File.cs:10` |
|
|
| Status | Open |
|
|
|
|
**Description:** A first problem that matters.
|
|
|
|
**Recommendation:** Fix it.
|
|
|
|
**Resolution:** _(open)_
|
|
|
|
### Demo-002
|
|
|
|
| Field | Value |
|
|
|---|---|
|
|
| Severity | Low |
|
|
| Category | Documentation & comments |
|
|
| Location | `src/Demo/File.cs:20` |
|
|
| Status | Resolved |
|
|
|
|
**Description:** A second, minor problem.
|
|
|
|
**Recommendation:** Tidy it.
|
|
|
|
**Resolution:** Fixed in def5678 on 2026-05-18.
|
|
"""
|
|
|
|
|
|
def _parse_fixture() -> dict:
|
|
"""Write FIXTURE to a temp Demo/findings.md and parse it."""
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "Demo" / "findings.md"
|
|
path.parent.mkdir()
|
|
path.write_text(FIXTURE, encoding="utf-8")
|
|
return regen.parse_module(path)
|
|
|
|
|
|
def test_first_table_skips_separator_and_field_header():
|
|
table = regen.first_table("| Field | Value |\n|---|---|\n| Severity | High |\n")
|
|
assert table == {"Severity": "High"}, table
|
|
|
|
|
|
def test_parse_module_header():
|
|
m = _parse_fixture()
|
|
assert m["module"] == "Demo", m["module"]
|
|
assert m["header"]["Reviewer"] == "Tester"
|
|
assert m["header"]["Status"] == "Reviewed"
|
|
assert m["header"]["Open findings"] == "1"
|
|
|
|
|
|
def test_parse_module_findings():
|
|
m = _parse_fixture()
|
|
assert len(m["findings"]) == 2, len(m["findings"])
|
|
first = m["findings"][0]
|
|
assert first["id"] == "Demo-001"
|
|
assert first["severity"] == "High"
|
|
assert first["category"] == "Security"
|
|
assert first["location"] == "`src/Demo/File.cs:10`"
|
|
assert first["status"] == "Open"
|
|
assert first["description"] == "A first problem that matters."
|
|
assert m["findings"][1]["status"] == "Resolved"
|
|
|
|
|
|
def test_build_readme_splits_pending_and_closed():
|
|
readme = regen.build_readme([_parse_fixture()])
|
|
assert "## Pending findings" in readme
|
|
assert "## Closed findings" in readme
|
|
pending, closed = readme.split("## Closed findings", 1)
|
|
assert "Demo-001" in pending # Open -> pending
|
|
assert "Demo-001" not in closed
|
|
assert "Demo-002" in closed # Resolved -> closed
|
|
assert "_No pending findings._" not in pending
|
|
|
|
|
|
def test_find_inconsistencies_clean_fixture():
|
|
assert regen.find_inconsistencies([_parse_fixture()]) == []
|
|
|
|
|
|
def test_find_inconsistencies_detects_wrong_open_count():
|
|
m = _parse_fixture()
|
|
m["header"]["Open findings"] = "7"
|
|
issues = regen.find_inconsistencies([m])
|
|
assert len(issues) == 1 and "Open findings" in issues[0], issues
|
|
|
|
|
|
def test_find_inconsistencies_detects_unknown_status():
|
|
m = _parse_fixture()
|
|
m["findings"][0]["status"] = "Bogus"
|
|
issues = regen.find_inconsistencies([m])
|
|
# Wrong status also shifts the open count, so expect the status issue present.
|
|
assert any("unrecognised Status" in i for i in issues), issues
|
|
|
|
|
|
def test_summarize_truncates_long_text():
|
|
long = "x" * 500
|
|
out = regen.summarize(long)
|
|
assert len(out) <= 240 and out.endswith("…"), len(out)
|
|
assert regen.summarize("short") == "short"
|
|
|
|
|
|
def main() -> int:
|
|
tests = sorted(
|
|
(name, fn)
|
|
for name, fn in globals().items()
|
|
if name.startswith("test_") and callable(fn)
|
|
)
|
|
failed = 0
|
|
for name, fn in tests:
|
|
try:
|
|
fn()
|
|
print(f"PASS {name}")
|
|
except Exception: # noqa: BLE001 - test runner reports all failures
|
|
failed += 1
|
|
print(f"FAIL {name}")
|
|
traceback.print_exc()
|
|
print(f"\n{len(tests) - failed}/{len(tests)} passed.")
|
|
return 1 if failed else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|