Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
fd1f76a
feat(vdsm): install Download Station into the golden image
cmeans-claude-dev[bot] May 14, 2026
a89674f
feat(vdsm): configure Download Station after install
cmeans-claude-dev[bot] May 14, 2026
b27370d
test(vdsm): add Download Station integration tests
cmeans-claude-dev[bot] May 14, 2026
a3f0906
docs: add vdsm Download Station coverage notes
cmeans-claude-dev[bot] May 14, 2026
8b58270
docs(changelog): substitute real PR# 107 for placeholder
cmeans-claude-dev[bot] May 14, 2026
f4f1aaa
fix(vdsm): launch Package Center via SYNO.SDS.AppLaunch, not UI selec…
cmeans-claude-dev[bot] May 14, 2026
cba47a5
fix(vdsm): drop text= from install-complete poll selector
cmeans-claude-dev[bot] May 14, 2026
8c4db50
fix(vdsm): expand dialog walker labels, add screenshot artifact upload
cmeans-claude-dev[bot] May 14, 2026
97cbbfa
fix(vdsm): dismiss Package Center Terms of Service modal on first launch
cmeans-claude-dev[bot] May 14, 2026
9ac0844
fix(vdsm): ExtJS-aware ToS modal dismissal (checkbox label + _click_t…
cmeans-claude-dev[bot] May 14, 2026
98ac4b6
fix(vdsm): walk to ExtJS cb-wrap and verify checkbox state flipped
cmeans-claude-dev[bot] May 14, 2026
00c299f
fix(vdsm): install Download Station via synopkg over SSH, drop UI scrape
cmeans-claude-dev[bot] May 14, 2026
43a2526
fix(vdsm): start DS via package's start-stop-status, gate on API cache
cmeans-claude-dev[bot] May 14, 2026
e0ec3a8
fix(vdsm): gate DS readiness on ALL 4 APIs, not just Task
cmeans-claude-dev[bot] May 14, 2026
550f445
fix(vdsm): correct DS Info endpoint and method for setconfig step
cmeans-claude-dev[bot] May 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/vdsm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,12 @@ jobs:
/tmp/vdsm-*.log
if-no-files-found: ignore
retention-days: 7

- name: Upload Playwright screenshots on failure
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: vdsm-failure-screenshots
path: .vdsm/screenshots/
if-no-files-found: ignore
retention-days: 7
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Added

- **vdsm Download Station coverage** (#107) — closes #106. The vdsm golden image now installs Download Station at bake time via `synopkg install_from_server DownloadStation` over SSH (`tests/vdsm/setup_dsm.py:_install_download_station_via_ssh`), and configures it post-install via DSM API (`_configure_download_station_via_api`: sets `default_destination=writable`, grants the test_user permission with a graceful warning-and-continue fallback if `SYNO.Core.Package.Setting.User` is restricted on the vdsm DSM build). The SSH approach replaces an earlier Playwright/Package-Center UI path that proved unautomatable against Synology's heavily customised DSM 7 Package Center: the Terms of Service modal uses a `syno-ux` checkbox rendered as `<input type="button">` whose state is driven entirely by Ext 3.4.1's component model, and eight local iterations covering JS `.click()`, CDP `page.mouse.click(x, y)`, scoped Playwright locators, and direct Ext component access all failed to dismiss it. `synopkg install_from_server` completes in ~5s against a live container and is idempotent (skips the install if `synopkg list --name DownloadStation` already shows the package), so cached-golden-image re-bakes are safe. New `tests/vdsm/test_vdsm_downloadstation.py` exercises the five Phase 1 READ tools against the real DSM instance (`list_downloads`, `get_download_info`, `get_download_stats`, `get_download_config`, `get_schedule`), plus a create/read/delete lifecycle test that uses a fake magnet URI (no seeds, no leech behavior) so the task creates instantly and `get_download_info` has real data to render without network or wait-for-download flakiness. The `_ds_available` fixture skip-cleanly checks `SYNO.DownloadStation.Task` is in the API cache, so the file is safe to run against any DSM-compatible test target. Cold bake adds ~10s for the DS install; cached restores are unaffected. `golden_image.py` metadata now records `dsm_packages=["DownloadStation"]` so cached pre-DS images are detectable for invalidation.

- **Download Station module — Phase 2 (WRITE tools)** (#105). Second of three planned PRs from the DS module spec (`docs/superpowers/specs/2026-05-13-downloadstation-module-design.md`). Adds seven WRITE-tier tools: `create_download` (URI list or local `.torrent`/`.nzb` file via multipart POST), `delete_download` (explicit `delete_data: bool` required — `True` proceeds with the destructive default-DSM-behavior of removing both task and files, `False` is REFUSED with a clear error because DSM v1 `Task.delete` has no documented "keep files" mode), `pause_download` / `resume_download` (siblings sharing a private `_task_state_change` helper that walks the per-task error list), `edit_download` (currently `destination` only — DSM's `Task.edit` supported-field set varies by DSM version and is verified per-NAS), `set_download_config` (partial updates only — only supplied fields are written), `set_schedule` (168-character weekly plan, validated client-side via the same `SCHEDULE_PLAN_LENGTH` invariant `format_schedule_grid` uses on the read side). New `core/downloadstation_errors.py` carries DS-specific 400-series error codes (different semantics from FileStation's reuse of the same numeric range — DS 400 is "file upload failed", FS 400 is "invalid parameter"); `error_from_code()` dispatches to the DS map via a deferred local import when the api begins with `SYNO.DownloadStation`, with explicit regression tests guarding that codes 105 (permission denied) and 106 (session expired) still fall through to the common 100-series handlers for DS APIs — the CLAUDE.md "never re-auth on 105" invariant must hold for DS calls too. New `DsmClient.create_download_task_with_file()` mirrors the existing `upload_file()` multipart helper with the same re-auth-on-session-error retry semantics and `_sid`-masked debug logging. Bonus fix to `DsmClient.request()` debug logging: was calling `.keys()` unconditionally on the response `data` field, which crashed for endpoints (like `Task.delete`) that return a list instead of a dict — now guarded with `isinstance(data, dict)`. Re-auth retry tests added for `create_download_task_with_file` (multipart path) and the URI-path `create_download` documents the standard-GET re-auth surface — closes the operational coverage gap flagged at issue #99 for the DS module specifically. 62 new tests; every new module file at 100% line coverage; 727 repo tests pass at 96.26% overall.

- **Download Station module — Phase 1 (READ tools)** (#104). New module `mcp_synology.modules.downloadstation` adds five read-only tools — `list_downloads`, `get_download_info`, `get_download_stats`, `get_download_config`, `get_schedule` — that expose Synology's Download Station package via DSM API v1 (`SYNO.DownloadStation.Task`, `Info`, `Statistic`, `Schedule`). Module is opt-in via `instances.[name].modules.downloadstation.enabled: true`; the API preflight skips registration entirely when the DS package is not installed on the NAS, so users without DS pay zero LLM-context cost (no tools registered, no handler imports, no MODULE_INFO consumed beyond the preflight). Module shape mirrors File Station: domain-split files (`tasks.py`, `stats.py`, `config.py`, `helpers.py`), lazy handler imports inside `register()`, `respx`-mocked unit tests. Reuses the existing shared DSM session — DSM does not gate API access by session name, so no auth-manager changes were needed; ADR-0001's deferral of per-client sessions applies to client-isolation, not per-package isolation. `list_downloads` does client-side status filtering (DSM v1 `Task.list` lacks server-side filter support); `get_download_info` requests all five `additional` groups (`detail,transfer,file,tracker,peer`) and renders them as distinct sections; `get_schedule` parses DSM's 168-character `schedule_plan` into a 7×24 ASCII grid. Tool input validation uses the project's standard `error_response(ErrorCode.INVALID_PARAMETER, ...)` structured envelope. Phase 2 (task CRUD writes + global config writes) and Phase 3 (BT search + RSS) to follow as separate PRs. 60 new unit tests; every new module file (`__init__.py`, `tasks.py`, `stats.py`, `config.py`, `helpers.py`) at 100% line coverage. Full design at `docs/superpowers/specs/2026-05-13-downloadstation-module-design.md`.
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ uv run pytest --cov=mcp_synology # Tests with coverage
- Configure NAS connection + `test_paths` (existing_share, search_folder, search_keyword, writable_folder)
- Run: `uv run pytest -m integration -v --log-cli-level=INFO`
- Search tests can be flaky if the NAS search service is overloaded — allow recovery time between runs
- **vdsm Download Station coverage**: the vdsm golden image now installs Download Station at bake time via Playwright through Package Center (`tests/vdsm/setup_dsm.py:_install_download_station_via_ui`) and configures it post-install via DSM API (`_configure_download_station_via_api`: sets `default_destination=writable`, grants the test_user permission). Cached images skip re-install; cold bake adds ~3–5 min. If `tests/vdsm/test_vdsm_downloadstation.py` reports `pytest.skip("Download Station package not installed")`, the cached golden image is stale — delete `.vdsm/golden_images/*.tar.gz` to force a re-bake.

## Common Tasks

Expand Down
1 change: 1 addition & 0 deletions tests/vdsm/golden_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def save_golden_image(
"admin_user": DEFAULT_ADMIN_USER,
"test_user": DEFAULT_TEST_USER,
"test_password": DEFAULT_TEST_PASSWORD,
"dsm_packages": ["DownloadStation"],
"test_paths": {
"existing_share": "/testshare",
"search_folder": "/testshare/Documents",
Expand Down
Loading
Loading