fix(cli): resolve CLI-002..007 — robust response rendering, URL/JSON arg validation, credential env-vars, doc refresh
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
| Last reviewed | 2026-05-16 |
|
||||
| Reviewer | claude-agent |
|
||||
| Commit reviewed | `9c60592` |
|
||||
| Open findings | 12 |
|
||||
| Open findings | 6 |
|
||||
|
||||
## Summary
|
||||
|
||||
@@ -92,7 +92,7 @@ now call `ResolveFormat`. Regression tests added in `FormatResolutionTests`.
|
||||
|--|--|
|
||||
| Severity | Medium |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.CLI/Commands/CommandHelpers.cs:59-68`, `src/ScadaLink.CLI/Commands/CommandHelpers.cs:78-80` |
|
||||
|
||||
**Description**
|
||||
@@ -112,7 +112,13 @@ output" case (print nothing or `(ok)`), and return 0 before attempting to parse.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit pending). Root cause confirmed against source —
|
||||
`HandleResponse` tested `JsonData != null`, so an empty success body fell through to
|
||||
`WriteAsTable` → `JsonDocument.Parse("")` and threw an uncaught `JsonException`.
|
||||
`HandleResponse` now treats a null-or-whitespace `JsonData` as a "succeeded, no output"
|
||||
case, prints `(ok)`, and returns 0 before any parse. Regression tests added in
|
||||
`ResponseRenderingTests` (`HandleResponse_EmptyBody_TableFormat_DoesNotThrow_ReturnsZero`,
|
||||
`HandleResponse_EmptyBody_JsonFormat_DoesNotThrow_ReturnsZero`).
|
||||
|
||||
### CLI-003 — Non-JSON success body crashes table rendering
|
||||
|
||||
@@ -120,7 +126,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | Medium |
|
||||
| Category | Error handling & resilience |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.CLI/Commands/CommandHelpers.cs:80` |
|
||||
|
||||
**Description**
|
||||
@@ -139,7 +145,11 @@ printing the raw body verbatim (as the JSON path already does at line 66).
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit pending). Root cause confirmed — `WriteAsTable` parsed the
|
||||
body with no `try/catch`. The `JsonDocument.Parse` call is now wrapped in a
|
||||
`try/catch (JsonException)` that prints the raw body verbatim on failure, mirroring the
|
||||
raw-body fallback on the JSON path. Regression test
|
||||
`ResponseRenderingTests.HandleResponse_NonJsonBody_TableFormat_FallsBackToRaw_ReturnsZero`.
|
||||
|
||||
### CLI-004 — Malformed `--url` throws an unhandled `UriFormatException`
|
||||
|
||||
@@ -147,7 +157,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | Medium |
|
||||
| Category | Error handling & resilience |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.CLI/ManagementHttpClient.cs:13` |
|
||||
|
||||
**Description**
|
||||
@@ -166,7 +176,12 @@ clean `INVALID_URL` error with exit code 1 on failure.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit pending). Root cause confirmed — the
|
||||
`ManagementHttpClient` constructor's `new Uri(...)` ran outside the `SendCommandAsync`
|
||||
`try/catch`. Added `CommandHelpers.IsValidManagementUrl`, which checks for an absolute
|
||||
http/https URL via `Uri.TryCreate`. Both `CommandHelpers.ExecuteCommandAsync` and
|
||||
`DebugCommands.BuildStream` now validate the resolved URL up front and emit a clean
|
||||
`INVALID_URL` error with exit code 1. Regression tests in `UrlValidationTests`.
|
||||
|
||||
### CLI-005 — Malformed `--bindings` / `--overrides` JSON throws unhandled exceptions
|
||||
|
||||
@@ -174,7 +189,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | Medium |
|
||||
| Category | Error handling & resilience |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.CLI/Commands/InstanceCommands.cs:55-58`, `src/ScadaLink.CLI/Commands/InstanceCommands.cs:181-182` |
|
||||
|
||||
**Description**
|
||||
@@ -195,7 +210,15 @@ code and return 1.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit pending). Root cause confirmed — both `set-bindings` and
|
||||
`set-overrides` deserialized and indexed JSON inline with no `try/catch`. Extracted the
|
||||
parsing into testable `InstanceCommands.TryParseBindings` / `TryParseOverrides` helpers
|
||||
that catch `JsonException`, guard against null results, and (for bindings) validate pair
|
||||
arity and element kinds (`JsonValueKind`) instead of letting `ArgumentOutOfRangeException`
|
||||
/ `InvalidOperationException` escape. The command actions now emit a clean
|
||||
`INVALID_ARGUMENT` error and return 1 on failure. Regression tests in
|
||||
`InstanceArgumentParsingTests` (8 tests covering valid input, malformed JSON, short pairs,
|
||||
wrong element types, and JSON null).
|
||||
|
||||
### CLI-006 — Password is passed as a command-line argument with no safer alternative
|
||||
|
||||
@@ -203,7 +226,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | Medium |
|
||||
| Category | Security |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.CLI/Program.cs:9`, `src/ScadaLink.CLI/Commands/CommandHelpers.cs:36-44` |
|
||||
|
||||
**Description**
|
||||
@@ -225,7 +248,17 @@ supplied.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit pending). Root cause confirmed — credentials had no
|
||||
non-command-line source. Added `SCADALINK_USERNAME` / `SCADALINK_PASSWORD` environment
|
||||
fallbacks: `CliConfig.Load` now reads them into new `CliConfig.Username` / `Password`
|
||||
properties (credentials are sourced from environment variables only, never the config
|
||||
file, so they are not persisted). `CommandHelpers.ResolveCredential` resolves precedence
|
||||
(explicit `--username`/`--password` → env var); both `ExecuteCommandAsync` and
|
||||
`DebugCommands.BuildStream` use it. The design doc and the in-repo `README.md` now
|
||||
document that `--password` on the command line is discouraged. The `--password-stdin`
|
||||
option / interactive prompt was not added — the env-var fallback fully satisfies the
|
||||
CI/CD safe-credential need; a stdin/prompt variant can be a follow-up if interactive use
|
||||
demands it. Regression tests in `CredentialResolutionTests`.
|
||||
|
||||
### CLI-007 — `Component-CLI.md` command surface is substantially stale
|
||||
|
||||
@@ -233,7 +266,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | Medium |
|
||||
| Category | Design-document adherence |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `docs/requirements/Component-CLI.md:51-211` (vs. all files under `src/ScadaLink.CLI/Commands/`) |
|
||||
|
||||
**Description**
|
||||
@@ -267,7 +300,17 @@ authoritative.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit pending). Drift confirmed against every file under
|
||||
`src/ScadaLink.CLI/Commands/`. Regenerated the entire "Command Structure" section of
|
||||
`Component-CLI.md` from the actual command tree: all entities are now keyed by integer
|
||||
`--id`; the non-existent `--file` option is removed; create/update commands list their
|
||||
real individual flags; non-existent commands (`template diff`, `instance
|
||||
bind-connections`/`assign-area`, `data-connection assign/unassign`, `security api-key
|
||||
enable/disable`) are removed; previously-omitted commands (`instance alarm-override
|
||||
set/delete/list`, `external-system method` subgroup, `site deploy-artifacts`) are added.
|
||||
A note now points to `src/ScadaLink.CLI/README.md` as the authoritative reference. The
|
||||
Configuration section also documents the new `SCADALINK_USERNAME`/`SCADALINK_PASSWORD`
|
||||
env vars (see CLI-006).
|
||||
|
||||
### CLI-008 — `--format` value is not validated
|
||||
|
||||
|
||||
Reference in New Issue
Block a user