mirror of
https://github.com/Comfy-Org/ComfyUI-Manager.git
synced 2026-05-09 00:22:51 +08:00
Defense-in-depth over GET→POST alone: reject the three CORS-safelisted simple-form Content-Types (x-www-form-urlencoded, multipart/form-data, text/plain) on 16 no-body POST handlers (glob + legacy) to block <form method=POST> CSRF that bypasses method-only gating. Move comfyui_switch_version to a JSON body so the preflight requirement applies. Split db_mode/policy/update/channel_url_list into GET(read) + POST(write). Tighten do_fix (high → high+) and gate three previously-ungated config setters at middle. Resynchronize openapi.yaml (27 paths, 30 operations, ComfyUISwitchVersionParams as a shared $ref component). Add E2E harness variants, Playwright config, CSRF/secgate suites, 39-endpoint coverage, and a CHANGELOG. Breaking: legacy per-op POST routes (install/uninstall/fix/disable/update/ reinstall/abort_current) are removed; callers already use queue/batch. Legacy /manager/notice (v1) is removed; /v2/manager/notice is retained. Reported-by: XlabAI Team of Tencent Xuanwu Lab CVSS: 8.1 (AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H)
515 lines
22 KiB
Markdown
515 lines
22 KiB
Markdown
# Scenario × Functional Effect Mapping
|
||
|
||
**Generated**: 2026-04-18
|
||
**Definition of "effect"**: The actual **functional purpose** of the feature — not just any side effect. A scenario is verified only when the intended outcome is observably achieved.
|
||
|
||
| Pattern | Effect definition |
|
||
|---|---|
|
||
| Success scenario | The feature's PURPOSE is fulfilled and observable |
|
||
| Validation/security error | The purpose is NOT fulfilled + correct rejection signal |
|
||
| State edge case | The purpose is correctly short-circuited or no-op |
|
||
|
||
Unless specified, status code alone is NOT sufficient evidence of effect.
|
||
|
||
---
|
||
|
||
# Section 1 — Glob v2 Endpoints
|
||
|
||
## 1.1 Queue Management (Install/Uninstall/Update/Fix/Disable/Enable/Model)
|
||
|
||
### POST /v2/manager/queue/task (kind=install)
|
||
|
||
Purpose: **install a custom node pack so it becomes loadable by ComfyUI**.
|
||
|
||
| Scenario | Functional effect to verify |
|
||
|---|---|
|
||
| Success (CNR pack) | (a) pack directory exists under `custom_nodes/`, (b) `.tracking` file present (CNR marker), (c) pack appears in GET `customnode/installed` with correct cnr_id + version, (d) worker `task_worker_lock` released after completion |
|
||
| Success (nightly/URL) | (a) pack directory exists, (b) `.git` subdir present (git clone), (c) repo remote matches requested URL, (d) appears in installed list |
|
||
| Success (skip_post_install + already disabled) | Pack moved from `.disabled/` back to active (enable shortcut), NOT a fresh install |
|
||
| Validation error (bad `kind` value) | Task NOT queued (queue/status unchanged), queue/history does not contain this ui_id, pack NOT installed |
|
||
| Validation error (missing ui_id/client_id) | Same: no queued task, no installation side-effect |
|
||
| Worker auto-start | After task queued, `queue/status.is_processing=true` and eventually `done_count` increments |
|
||
|
||
### POST /v2/manager/queue/task (kind=uninstall)
|
||
|
||
Purpose: **remove an installed pack so it is no longer loaded**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | Pack directory no longer exists under `custom_nodes/`, pack absent from `customnode/installed`, no import error on next ComfyUI reload |
|
||
| Target not installed | No-op or error — purpose already satisfied; no state change |
|
||
| Unknown pack | No filesystem change |
|
||
|
||
### POST /v2/manager/queue/task (kind=update)
|
||
|
||
Purpose: **update an installed pack to a newer version**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | (a) pack directory still exists, (b) version actually changed (check `.tracking` content or pyproject version), (c) dependencies refreshed, (d) still loadable by ComfyUI |
|
||
| Already up-to-date | No-op or confirmatory response; no downgrade |
|
||
| Unknown pack / Update fails | No partial state (pack not removed nor corrupted) |
|
||
|
||
### POST /v2/manager/queue/task (kind=fix)
|
||
|
||
Purpose: **re-install dependencies of an existing pack without changing source**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | (a) pack directory unchanged (same HEAD/version), (b) dependencies present in venv after fix, (c) pack import succeeds on reload |
|
||
| Missing dependencies pre-fix | After fix, imports succeed |
|
||
|
||
### POST /v2/manager/queue/task (kind=disable)
|
||
|
||
Purpose: **stop loading a pack without removing it, reversibly**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | (a) pack moved from `custom_nodes/<name>/` to `custom_nodes/.disabled/<name>/`, (b) on next ComfyUI reload, pack nodes NOT registered, (c) pack absent from `customnode/installed` (active) |
|
||
| Already disabled | No-op; still in `.disabled/` |
|
||
|
||
### POST /v2/manager/queue/task (kind=enable)
|
||
|
||
Purpose: **restore a disabled pack to active, loadable state**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | (a) pack restored from `.disabled/` to active `custom_nodes/` (may be case-normalized CNR name), (b) on reload, nodes registered again, (c) appears in `customnode/installed` |
|
||
| Not disabled (already active) | No-op; no regression |
|
||
|
||
### POST /v2/manager/queue/install_model
|
||
|
||
Purpose: **download a model file to the appropriate models/ subdirectory**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | (a) task queued (queue/status reflects), (b) eventually file downloaded to `models/<type>/<filename>`, (c) file size > 0, (d) visible via `externalmodel/getlist` with `installed=True` (legacy) |
|
||
| Missing client_id/ui_id | Task NOT queued; no download attempted |
|
||
| Invalid metadata | Task NOT queued |
|
||
| Not in whitelist (legacy check) | Download rejected; no file written |
|
||
| Non-safetensors + security<high+ | Rejected; no file written |
|
||
|
||
### POST /v2/manager/queue/update_all
|
||
|
||
Purpose: **queue update tasks for ALL currently active packs**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | queue/status.pending_count == N where N = (active_nodes + unknown_active_nodes - manager-skip). Each queued task has correct `kind=update` + correct `node_name` |
|
||
| Security denied (<middle+) | 403; NO tasks queued; queue/status unchanged |
|
||
| Missing params | 400; NO tasks queued |
|
||
| mode=local | No remote fetch; uses local channel data |
|
||
| Desktop build | `comfyui-manager` pack NOT in queued tasks |
|
||
|
||
### POST /v2/manager/queue/update_comfyui
|
||
|
||
Purpose: **queue a self-update task for ComfyUI core**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | (a) queue/status.total_count increased by 1, (b) the queued task has `kind=update-comfyui` with `params.is_stable` matching request/config |
|
||
| Missing params | 400; no task queued |
|
||
| stable=true overrides config | Task params.is_stable==True regardless of config policy |
|
||
|
||
### POST /v2/manager/queue/reset
|
||
|
||
Purpose: **clear all queued/running/history tasks**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | queue/status: total_count=0, done_count=0, pending_count=0, in_progress_count=0, is_processing=false |
|
||
| Already empty | Same; idempotent |
|
||
|
||
### POST /v2/manager/queue/start
|
||
|
||
Purpose: **start the worker thread to process queued tasks**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Worker not running | queue/status.is_processing becomes true (may be momentary if queue empty); tasks transition pending → running → done |
|
||
| Already running | 201; is_processing remains true; no duplicate worker spawned |
|
||
| Empty queue | Worker starts and idles; no errors |
|
||
|
||
### GET /v2/manager/queue/status
|
||
|
||
Purpose: **accurately reflect the current queue state**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| No filter | Counts match actual internal queue state (cross-check via known queued tasks) |
|
||
| With client_id filter | client_id echo + filtered counts correspond to only that client's tasks |
|
||
| Fields shape | total/done/in_progress/pending/is_processing all present + correct types |
|
||
|
||
### GET /v2/manager/queue/history
|
||
|
||
Purpose: **retrieve completed task records for introspection**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| id=<batch_id> query | Returns JSON content of that batch file (not another's) |
|
||
| Path traversal | file read DOES NOT occur; returns 400 |
|
||
| ui_id filter | Returns the matching single task record |
|
||
| client_id filter | Returns only that client's history |
|
||
| Pagination | Result size ≤ max_items |
|
||
| Serialization limitation | If 400 returned, server didn't crash; no corrupted state |
|
||
|
||
### GET /v2/manager/queue/history_list
|
||
|
||
Purpose: **list available batch history file IDs**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | Returned `ids` ⊆ files in `manager_batch_history_path` (mtime-desc sorted) |
|
||
| Empty | ids=[] reflects empty dir |
|
||
|
||
## 1.2 Custom Node Info
|
||
|
||
### GET /v2/customnode/getmappings
|
||
|
||
Purpose: **provide node→pack mapping for the UI to resolve missing nodes**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success mode=local/cache/remote | Returned dict: values are `[node_list, metadata]`, all currently-loaded `NODE_CLASS_MAPPINGS` either present in a node_list OR matched by `nodename_pattern` regex |
|
||
| mode=nickname | Nicknames filter applied (each entry has nickname field) |
|
||
| Missing mode query | 500/KeyError; no partial data returned |
|
||
|
||
### GET /v2/customnode/fetch_updates (deprecated)
|
||
|
||
Purpose: **(deprecated) was previously used to fetch git updates**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Always | 410 + `{deprecated: true}`. No git fetch performed (no disk I/O on .git dirs) |
|
||
|
||
### GET /v2/customnode/installed
|
||
|
||
Purpose: **list currently-installed packs with metadata for the UI**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| mode=default | Dict reflects real filesystem scan of `custom_nodes/`: every dir with proper marker appears |
|
||
| mode=imported | Returns snapshot frozen at startup (unchanged after runtime installs) — proves stability |
|
||
| Newly installed pack | After install, default mode reflects it; imported mode does NOT |
|
||
|
||
### POST /v2/customnode/import_fail_info
|
||
|
||
Purpose: **return detailed traceback/message for a pack that failed to import at startup**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Known failed pack via cnr_id | 200 + body has `msg` + `traceback` matching `cm_global.error_dict[module]` |
|
||
| Known failed via url | Same |
|
||
| Unknown pack | 400 (no info); `error_dict` NOT mutated |
|
||
| Missing fields / non-dict | 400 with appropriate text |
|
||
|
||
### POST /v2/customnode/import_fail_info_bulk
|
||
|
||
Purpose: **same as above but for multiple packs in one call**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| cnr_ids list | Each key maps to either {error, traceback} (if failed) or null (if no failure). Unknown cnr_ids → null |
|
||
| urls list | Same semantics |
|
||
| Empty lists | 400 |
|
||
| Mixed types inside list | 400 or skip with per-item error |
|
||
|
||
## 1.3 Snapshots
|
||
|
||
### GET /v2/snapshot/get_current
|
||
|
||
Purpose: **capture and return the current system state (not persist it)**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | Returned dict contains `comfyui` (hash/tag), `git_custom_nodes` (list), `cnr_custom_nodes` (list), `pips`. Consistent with actual installed state |
|
||
|
||
### POST /v2/snapshot/save
|
||
|
||
Purpose: **persist current system state so it can be restored later**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | (a) new file created in `manager_snapshot_path` with timestamped name, (b) file content == get_current() at save time, (c) appears in `snapshot/getlist.items` |
|
||
|
||
### GET /v2/snapshot/getlist
|
||
|
||
Purpose: **list saved snapshots for UI selection**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | items list matches .json files in snapshot dir (without extension), sorted desc |
|
||
| After save | New snapshot name appears at top |
|
||
| After remove | Removed name absent |
|
||
|
||
### POST /v2/snapshot/remove
|
||
|
||
Purpose: **delete a saved snapshot permanently**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | File removed from disk; absent from getlist |
|
||
| Nonexistent target | No change; 200 (no-op) |
|
||
| Path traversal | File NOT removed; 400; any other files untouched |
|
||
| Security denied | File NOT removed; 403 |
|
||
|
||
### POST /v2/snapshot/restore
|
||
|
||
Purpose: **schedule a snapshot to be applied on next server restart** (the actual restore happens at startup).
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | `restore-snapshot.json` copied to `manager_startup_script_path`. Next reboot → actual state reverts to snapshot (verifiable by reboot + get_current comparison) |
|
||
| Nonexistent target | No marker file created; 400 |
|
||
| Path traversal | No file operations; 400 |
|
||
| Security denied | No marker file; 403 |
|
||
|
||
## 1.4 Configuration
|
||
|
||
### GET /v2/manager/db_mode
|
||
|
||
Purpose: **return current DB source mode config**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | Returned text == `core.get_config()["db_mode"]` value in `config.ini` |
|
||
|
||
### POST /v2/manager/db_mode
|
||
|
||
Purpose: **persist new DB mode to config.ini**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Valid value | (a) config.ini written to disk with new value, (b) GET returns new value, (c) survives process restart |
|
||
| Malformed JSON / missing value | 400; config.ini UNCHANGED |
|
||
|
||
### GET/POST /v2/manager/policy/update
|
||
|
||
Purpose: **read/persist update policy (stable vs nightly)**.
|
||
|
||
Same verification pattern as db_mode but for `update_policy` key.
|
||
|
||
### GET /v2/manager/channel_url_list
|
||
|
||
Purpose: **return available channels + currently selected**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | `selected` matches channel whose URL == config.channel_url (else "custom"); `list` is all known channels as "name::url" |
|
||
|
||
### POST /v2/manager/channel_url_list
|
||
|
||
Purpose: **switch active channel by name**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Known name | config.channel_url written with new URL; GET.selected matches new name |
|
||
| Unknown name | Silent no-op; 200; channel_url UNCHANGED (verify) |
|
||
| Malformed | 400; channel_url UNCHANGED |
|
||
|
||
## 1.5 System
|
||
|
||
### GET /v2/manager/is_legacy_manager_ui
|
||
|
||
Purpose: **let UI know which Manager UI (legacy vs current) to load**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | `is_legacy_manager_ui` matches the CLI flag `--enable-manager-legacy-ui` that was passed |
|
||
|
||
### GET /v2/manager/version
|
||
|
||
Purpose: **report the Manager package version**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | Text == core.version_str (non-empty, semver-ish) |
|
||
| Idempotent | Consecutive calls return identical value |
|
||
|
||
### POST /v2/manager/reboot
|
||
|
||
Purpose: **restart the server process**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | (a) server process actually exits, (b) new process binds same port, (c) new process serves requests, (d) pre-reboot state preserved (version, config) |
|
||
| CLI session mode | `.reboot` marker file created before exit(0); process-manager restarts |
|
||
| Security denied | 403; process continues (no restart) |
|
||
|
||
### GET /v2/comfyui_manager/comfyui_versions
|
||
|
||
Purpose: **enumerate available ComfyUI versions + current**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | `current` is a git tag/hash present in `.git` log; `versions` array non-empty; current ∈ versions |
|
||
| Git failure | 400; no partial response |
|
||
|
||
### POST /v2/comfyui_manager/comfyui_switch_version
|
||
|
||
Purpose: **queue a task to switch ComfyUI to a target version (actual switch happens via worker)**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | (a) task queued with `params.target_version=<ver>`, (b) queue/status reflects, (c) eventually `.git` HEAD points at target commit/tag after worker runs |
|
||
| Missing params | 400; no task queued |
|
||
| Security denied (<high+) | 403; no task queued |
|
||
|
||
---
|
||
|
||
# Section 2 — Legacy-only Endpoints (UI → effect)
|
||
|
||
For these, the functional purpose is triggered by UI interaction. The effect MUST be observable both through the UI (state transitions, renders) AND/OR through the backend (filesystem, queue state).
|
||
|
||
### POST /v2/manager/queue/batch (legacy)
|
||
|
||
Purpose: **accept one aggregated request to enqueue multiple operations, then start the worker**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| install item(s) | Each pack installed (filesystem effect); `failed` list only contains actually-failed ids |
|
||
| uninstall item(s) | Each pack removed |
|
||
| update item(s) | Packs updated (version change verifiable) |
|
||
| reinstall item(s) | Pack removed then re-installed (dir exists, .tracking present) |
|
||
| disable | Pack in `.disabled/` |
|
||
| install_model | Model file downloaded |
|
||
| fix | Dependencies re-resolved |
|
||
| update_comfyui | ComfyUI update task queued |
|
||
| update_all | All active pack updates queued |
|
||
| Mixed kinds | Each kind's effect achieved; `failed` contains only real failures |
|
||
|
||
### GET /v2/customnode/getlist (legacy)
|
||
|
||
Purpose: **feed the Custom Nodes Manager dialog with the list of available + installed packs**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | Response has `channel` + `node_packs`; each pack includes install state (installed/disabled), stars (github-stats), update availability (if skip_update=false) |
|
||
| skip_update=true | No git fetch performed (check timing / no remote calls) |
|
||
| Channel resolution | Maps URL back to name (default/custom/etc.) |
|
||
|
||
### GET /customnode/alternatives (legacy)
|
||
|
||
Purpose: **show alternative pack recommendations for a given pack**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | Response dict keyed by unified pack id; values from `alter-list.json` with markdown processed |
|
||
|
||
### GET /v2/externalmodel/getlist (legacy)
|
||
|
||
Purpose: **list available external models with install state for Model Manager dialog**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | Each model entry has `installed` ∈ {'True','False'}; True ⟺ file actually exists under appropriate models subdir |
|
||
| HuggingFace sentinel | Filename resolved from URL basename; installed flag correct |
|
||
| Custom save_path | Path resolved correctly |
|
||
|
||
### GET /v2/customnode/versions/{node_name} (legacy)
|
||
|
||
Purpose: **list all versions of a CNR pack for the user to pick**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Known CNR pack | Response array lists all available versions (latest first, typically) matches CNR registry |
|
||
| Unknown pack | 400; no partial data |
|
||
|
||
### GET /v2/customnode/disabled_versions/{node_name} (legacy)
|
||
|
||
Purpose: **list versions of a pack currently in the disabled state for possible re-enable**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Has disabled versions | Response array matches actual `cnr_inactive_nodes[node]` keys + "nightly" if in `nightly_inactive_nodes` |
|
||
| None disabled | 400 |
|
||
|
||
### POST /v2/customnode/install/git_url (legacy)
|
||
|
||
Purpose: **clone a pack from arbitrary git URL (dangerous; requires high+)**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | Repo cloned into `custom_nodes/`, `.git` dir present, repo remote matches URL |
|
||
| Already installed | 200 skip; no duplicate; no overwrite |
|
||
| Clone failure | 400; no partial dir left behind |
|
||
| Security denied (<high+) | 403; no filesystem change |
|
||
|
||
### POST /v2/customnode/install/pip (legacy)
|
||
|
||
Purpose: **run `pip install <packages>` in the venv**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| Success | Packages are importable from the venv Python afterwards (or `pip list` shows them) |
|
||
| Security denied (<high+) | 403; no pip invocation |
|
||
|
||
### GET /v2/manager/notice (legacy)
|
||
|
||
Purpose: **fetch the News wiki content and augment with version footer**.
|
||
|
||
| Scenario | Effect to verify |
|
||
|---|---|
|
||
| GitHub reachable | HTML returned; contains markdown-body content + ComfyUI/Manager version footer appended |
|
||
| GitHub unreachable | "Unable to retrieve Notice"; no crash |
|
||
| Non-git ComfyUI | Response starts with "Your ComfyUI isn't git repo" warning |
|
||
| Outdated ComfyUI | Response starts with "too OUTDATED!!!" warning |
|
||
| Desktop variant | Footer uses `__COMFYUI_DESKTOP_VERSION__` instead of commit hash |
|
||
|
||
---
|
||
|
||
# Section 3 — UI→effect Mapping (Legacy)
|
||
|
||
For Playwright tests, the "UI→effect" contract requires:
|
||
|
||
| UI action | Target endpoint | Effect to verify |
|
||
|---|---|---|
|
||
| Click Manager menu button | (none — UI only) | `#cm-manager-dialog` visible |
|
||
| Click "Custom Nodes Manager" menu item | GET customnode/getlist + getmappings | `#cn-manager-dialog` + grid populated (rows > 0) |
|
||
| Click "Model Manager" menu item | GET externalmodel/getlist | `#cmm-manager-dialog` + grid populated |
|
||
| Click "Snapshot Manager" menu item | GET snapshot/getlist | `#snapshot-manager-dialog` + list populated |
|
||
| Click "Install" on a pack row | GET customnode/versions/{id} → POST queue/batch (install) → WebSocket cm-queue-status | Pack dir exists on disk + row shows "Installed" state in UI + WebSocket `all-done` received |
|
||
| Click "Uninstall" on installed row | POST queue/batch (uninstall) | Pack dir removed + row state updates to "Not Installed" |
|
||
| Click "Disable" on row | POST queue/batch (disable) | Pack in `.disabled/` + row state "Disabled" |
|
||
| Click "Update" on outdated row | POST queue/batch (update) | Pack version changes + row state update |
|
||
| Click "Fix" on row | POST queue/batch (fix) | Dependencies restored |
|
||
| Click "Try alternatives" | GET /customnode/alternatives | Alternatives list rendered |
|
||
| Open "Versions" dropdown on row | GET customnode/versions/{id} | Version list rendered in UI |
|
||
| Open "Disabled Versions" on row | GET customnode/disabled_versions/{id} | Disabled versions rendered |
|
||
| Click "Install via Git URL" button + enter URL + confirm | POST customnode/install/git_url | Pack cloned; dir visible in UI |
|
||
| Click "Install via pip" | POST customnode/install/pip | Package installed; no UI crash |
|
||
| Click "Install" on Model Manager row | POST queue/install_model | Model file downloaded; row state "Installed" |
|
||
| Change DB mode dropdown | POST db_mode | Config persisted; dropdown value persists after dialog reopen |
|
||
| Change Update Policy dropdown | POST policy/update | Same |
|
||
| Change Channel dropdown | POST channel_url_list | Same |
|
||
| Click "Update All" button | POST queue/update_all | Multiple tasks queued; progress indicator shows count |
|
||
| Click "Update ComfyUI" button | POST queue/update_comfyui | Task queued; status indicator |
|
||
| Click "Save Snapshot" in Snapshot Manager | POST snapshot/save | New row in dialog list with timestamp |
|
||
| Click "Remove" on snapshot row | POST snapshot/remove?target=X | Row disappears from list |
|
||
| Click "Restore" on snapshot row | POST snapshot/restore?target=X | Marker file created; next reboot applies |
|
||
| Click "Restart" button | POST manager/reboot | Server restarts; UI reconnects |
|
||
| Open Manager menu with pending News | GET manager/notice | News panel visible with HTML content |
|
||
| Filter/search in grid | (client-side) | Row count ≤ initial count |
|
||
| Close dialog (X button / Esc) | (none) | Dialog hidden; no leaked DOM |
|
||
|
||
---
|
||
|
||
# Section 4 — Effects Not Easily Observable
|
||
|
||
Some purposes can only be proven via side-channel observation:
|
||
|
||
| Endpoint | Purpose | Why hard to verify |
|
||
|---|---|---|
|
||
| POST snapshot/restore | Apply snapshot at next reboot | Must actually reboot + compare post-state; destructive |
|
||
| POST switch_version (positive) | Change ComfyUI version | Destructive; needs rollback |
|
||
| POST manager/reboot | Restart process | Hard to assert "new process" vs "same process" cleanly; proxy: pid change or connection drop+rebind |
|
||
| POST queue/start → worker runs | Tasks execute | Timing-dependent; must poll done_count |
|
||
| GET manager/notice | Content from GitHub | External dependency; flaky |
|
||
| POST install (network) | Actually installs | Depends on CNR/GitHub availability |
|
||
| POST install_model (download) | File downloaded | Slow; large files; fake whitelist URL returns quick 404 |
|
||
|
||
For these, tests either (a) accept destructive as out-of-scope, (b) use timing/polling, or (c) mock at minimum granularity.
|
||
|
||
---
|
||
*End of Scenario × Effect Mapping*
|