fix(cli): resolve CLI-008..013 — format validation, exit-code semantics, debug-stream cancellation/disposal, test 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
|
||||
|
||||
@@ -318,7 +318,7 @@ env vars (see CLI-006).
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Code organization & conventions |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.CLI/Program.cs:10-11`, `src/ScadaLink.CLI/Commands/CommandHelpers.cs:60` |
|
||||
|
||||
**Description**
|
||||
@@ -334,7 +334,12 @@ Restrict the option to the accepted values, e.g. `formatOption.AcceptOnlyFromAmo
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit pending). Root cause confirmed — the `--format` option was
|
||||
constructed inline in `Program.cs` with no value constraint. Extracted option
|
||||
construction into a new `CliOptions.CreateFormatOption()` factory which applies
|
||||
`AcceptOnlyFromAmong("json", "table")`; `Program.cs` now uses it. Invalid values are
|
||||
rejected by `System.CommandLine` with a clear parse error. Regression tests in
|
||||
`FormatOptionValidationTests`.
|
||||
|
||||
### CLI-009 — Exit-code documentation does not match `HandleResponse` behaviour
|
||||
|
||||
@@ -342,7 +347,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Documentation & comments |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `docs/requirements/Component-CLI.md:238-249`, `src/ScadaLink.CLI/Commands/CommandHelpers.cs:75` |
|
||||
|
||||
**Description**
|
||||
@@ -365,7 +370,19 @@ with whichever is chosen.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit pending). Took the recommendation's second option — keying
|
||||
the authorization exit code off the response `code` field — since it makes the CLI honour
|
||||
the documented "authorization failure → exit 2" contract regardless of which channel the
|
||||
server uses, and it is the in-scope (code) fix. Replaced
|
||||
`return response.StatusCode == 403 ? 2 : 1;` with a `HandleResponse` →
|
||||
`IsAuthorizationFailure` helper that returns exit 2 for HTTP 403 **or** an error code of
|
||||
`UNAUTHORIZED`/`FORBIDDEN` (case-insensitive), and exit 1 for everything else.
|
||||
Authentication failure (HTTP 401 / bad credentials) deliberately remains exit 1.
|
||||
Regression tests in `ExitCodeTests`. Note: the doc-side clarification of the full
|
||||
client-side exit-code list (`NO_URL`, `NO_CREDENTIALS`, etc. → exit 1) was not made here —
|
||||
`docs/requirements/Component-CLI.md` is outside this module's editable surface; the
|
||||
remaining work is a non-blocking documentation-completeness edit owned by the docs
|
||||
surface, and the CLI's exit-code behaviour itself is now correct and pinned by tests.
|
||||
|
||||
### CLI-010 — `debug stream` reports Ctrl+C during connect as a connection failure
|
||||
|
||||
@@ -373,7 +390,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Error handling & resilience |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.CLI/Commands/DebugCommands.cs:181-189` |
|
||||
|
||||
**Description**
|
||||
@@ -394,7 +411,13 @@ lines 209-215 already treats cancellation as graceful.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit pending). Root cause confirmed — the `StartAsync` `catch`
|
||||
block reported every exception as `CONNECTION_FAILED`/exit 1. Extracted a pure
|
||||
`DebugStreamHelpers.ClassifyConnectFailure(ex, cancellationRequested)` classifier: an
|
||||
`OperationCanceledException` that coincides with a user-requested cancellation is treated
|
||||
as a graceful shutdown (exit 0, no error printed); anything else is a genuine connection
|
||||
failure (exit 1). The `StartAsync` catch block now uses it and disposes the connection on
|
||||
both paths. Regression tests in `DebugStreamTests` (`ClassifyConnectFailure_*`).
|
||||
|
||||
### CLI-011 — `CancellationTokenSource` in `debug stream` is never disposed
|
||||
|
||||
@@ -402,7 +425,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Performance & resource management |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.CLI/Commands/DebugCommands.cs:89` |
|
||||
|
||||
**Description**
|
||||
@@ -420,7 +443,12 @@ a `try/finally`).
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit pending). Root cause confirmed — `cts` was a plain local with
|
||||
no disposal on any exit path. Changed to `using var cts = new CancellationTokenSource();`
|
||||
in `StreamDebugAsync`, so it is disposed on every return path including the early
|
||||
connect/subscribe-failure returns. No dedicated regression test — undisposed-`IDisposable`
|
||||
is not observable from a unit test; the `using` declaration is verified by inspection and
|
||||
covered indirectly by the `DebugStreamTests` exit-path tests.
|
||||
|
||||
### CLI-012 — `debug stream` exit code is unreliable after stream termination
|
||||
|
||||
@@ -428,7 +456,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Concurrency & thread safety |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.CLI/Commands/DebugCommands.cs:208-227` |
|
||||
|
||||
**Description**
|
||||
@@ -452,7 +480,15 @@ Consider awaiting `exitTcs.Task` without the cancellation token after a brief gr
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit pending). Root cause confirmed — the final
|
||||
`exitTcs.Task.IsCompletedSuccessfully ? ... : 0` could miss an in-flight `TrySetResult`
|
||||
that races with the cancelled `WaitAsync`. Extracted
|
||||
`DebugStreamHelpers.ResolveStreamExitCodeAsync(exitTask)`: it returns an already-set
|
||||
result immediately, otherwise waits up to a 250 ms grace period (`Task.WhenAny` against
|
||||
`Task.Delay`) for a termination/closed result to land, and falls back to exit 0 only when
|
||||
no result is ever produced (pure Ctrl+C). `StreamDebugAsync` now resolves its exit code
|
||||
solely through this helper. Regression tests in `DebugStreamTests`
|
||||
(`ResolveStreamExitCodeAsync_*`, including the termination-racing-with-cancellation case).
|
||||
|
||||
### CLI-013 — HTTP client, `debug stream`, and JSON-argument parsing are untested
|
||||
|
||||
@@ -460,7 +496,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Testing coverage |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `tests/ScadaLink.CLI.Tests/` (vs. `src/ScadaLink.CLI/ManagementHttpClient.cs`, `src/ScadaLink.CLI/Commands/DebugCommands.cs`, `src/ScadaLink.CLI/Commands/InstanceCommands.cs:55-58`) |
|
||||
|
||||
**Description**
|
||||
@@ -487,4 +523,17 @@ resolves via `ManagementCommandRegistry`.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit pending). Coverage gaps confirmed and closed:
|
||||
- `ManagementHttpClientTests` — added an `internal` test-only `ManagementHttpClient`
|
||||
constructor accepting a pre-built `HttpClient`, and a stub `HttpMessageHandler`; tests
|
||||
cover the success, JSON error-body, non-JSON error-body fallback, connection-failure
|
||||
(status 0), and timeout (504) paths.
|
||||
- `CommandTreeTests` — builds all 14 command groups, recursively collects every leaf
|
||||
command and asserts each has an action (no dead wiring), and verifies representative
|
||||
command payload records round-trip through `ManagementCommandRegistry`.
|
||||
- `DebugStreamTests` — covers the `debug stream` decision logic via the new
|
||||
`DebugStreamHelpers` (see CLI-010, CLI-012).
|
||||
- JSON-argument parsing (`set-bindings`/`set-overrides`) was already extracted into
|
||||
`InstanceCommands.TryParseBindings`/`TryParseOverrides` and covered by
|
||||
`InstanceArgumentParsingTests` under CLI-005.
|
||||
The CLI test suite went from 42 to 77 passing tests.
|
||||
|
||||
Reference in New Issue
Block a user