Skip to content

fix: harden response parsing and CLI/MCP input handling - #2

Merged
protego-maxima-fianto-duri merged 1 commit into
synology-apm:mainfrom
davidleitw:fix/harden-parsing
Jul 28, 2026
Merged

fix: harden response parsing and CLI/MCP input handling#2
protego-maxima-fianto-duri merged 1 commit into
synology-apm:mainfrom
davidleitw:fix/harden-parsing

Conversation

@davidleitw

Copy link
Copy Markdown
Contributor

This hardens five parse/input paths that either crashed with a raw Python exception or silently produced wrong data. Each change is behavior-preserving for well-formed input — the only behavior change is on previously-crashing or previously-buggy malformed paths. All five are covered by targeted tests.

Note: the present-but-null parser guards from the earlier null-guard work (#1) are already upstream; this PR is the remaining set that sweep did not cover.

1. BackupServer.to_dict() omits storage_usage_pct

BackupServer exposes three derived @property metrics — storage_usage_pct, backup_data_reduction_bytes, backup_data_reduction_ratio — but to_dict() only put the latter two in its extra= dict. Since auto_to_dict serializes dataclass fields only, storage_usage_pct was dropped from the JSON output entirely, even though the CLI table already displays it. Fix: add storage_usage_pct to the serialized output alongside the two reduction metrics.

2. Empty-string progress/count crashes a running-workload parse

In _parse_workload, a RUNNING workload reads cache["progress"] (PC/PS/VM) or cache["processedSuccessCount"] (FS). Both were guarded with is not None, so an empty string "" — which the field can carry before the first counter tick — passed the guard and then raised ValueError from int(float("")) / int(""), failing the whole workload list. Fix: treat an empty/absent value as "not reported" (progress → 0, count → None), while preserving a legitimate integer 0 count as 0.

3. MCP backup-window / tasks_json input validation

  • _parse_backup_window: a non-numeric hour token (e.g. mon:a-b) raised a raw int() ValueError instead of a helpful message. Fix: report Backup window hours must be integers 0-23, got ....
  • _parse_tasks_json: a string custom_volumes (e.g. "C:") was passed straight to tuple(...), char-splitting it into ("C", ":") — two bogus single-character volumes. Fix: require a JSON array and reject a string with a clear ValueError.

4. Malformed relative time filter throws a traceback

parse_time_filter wrapped only the ISO-8601 branch in error handling; the relative-time branches (30m/1h/7d) called float(value[:-1]) unguarded, so a value like --since 1h30m produced an unhandled ValueError traceback. Fix: route the relative branch through the same typer.BadParameter message the ISO branch uses.

5. Failed export download deletes an existing destination file

On OSError, the CLI export download ran unlink(dest_path). But the SDK stages the download into a dest_path + ".part" file and only os.replaces it into place on success — an existing file at dest_path is never touched by a failed download. So the CLI cleanup deleted the user's previous good file while the failed download had written nothing to dest_path. Fix: remove the CLI unlink; .part cleanup is already the SDK's responsibility.

Testing

make test green — 3113 unit tests, coverage 98.76%, plus ruff, mypy, MCP coverage, and version-consistency checks. 12 new tests, each red-green verified against the pre-fix code.

Guard several parse and input paths that previously crashed with a raw
Python exception or silently produced wrong data:

- BackupServer.to_dict() now includes the storage_usage_pct derived
  metric alongside the two data-reduction metrics, so JSON output matches
  what the CLI table already shows.
- machine workload parsing treats an empty-string progress /
  processedSuccessCount during a running backup as "not reported"
  (progress -> 0, count -> None) instead of raising ValueError; a
  legitimate integer 0 count is still preserved as 0.
- the MCP backup-window parser reports a non-numeric hour token with a
  clear message instead of a raw int() error, and tasks_json rejects a
  string custom_volumes instead of silently splitting it into
  single-character volumes.
- the CLI relative time filter (--since/--until) reports a malformed
  value such as "1h30m" as a friendly parameter error instead of an
  unhandled traceback.
- the CLI export download no longer deletes an existing file at the
  destination path when a download fails; the SDK stages into a .part
  file and leaves the destination untouched on failure.
@protego-maxima-fianto-duri
protego-maxima-fianto-duri merged commit 7d4d432 into synology-apm:main Jul 28, 2026
7 checks passed
@davidleitw
davidleitw deleted the fix/harden-parsing branch July 28, 2026 12:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants