Commit Graph

15 Commits

Author SHA1 Message Date
Jacob Magar
e87a33ef1a test(safety): add strict no-GraphQL-call and non-destructive regression tests
Two new test classes:

TestNoGraphQLCallsWhenUnconfirmed (parametrized over all 13 destructive actions):
- test_no_graphql_call_without_confirm: make_graphql_request must NOT be
  called when confirm is absent — verifies guard fires before any I/O
- test_no_graphql_call_with_confirm_false: same with explicit confirm=False

TestNonDestructiveActionsNeverRequireConfirm (5 representative non-destructive):
- Regression guard: non-destructive mutations must work without confirm=True;
  prevents accidental over-guarding from breaking normal operations

788 tests passing
2026-03-15 20:16:23 -04:00
Jacob Magar
389b88f560 feat(settings): add update_ssh action with confirm=True guard
Enables/disables SSH and sets port via updateSshSettings mutation
(UpdateSshInput: enabled: Boolean!, port: Int!). Changing SSH config
can lock users out of the server — requires confirm=True.

- Add update_ssh to MUTATIONS, DESTRUCTIVE_ACTIONS, SETTINGS_ACTIONS
- Add ssh_enabled/ssh_port parameters to unraid_settings
- Add TestSshSettings class (4 tests: require ssh_enabled, require ssh_port, success, disable+verify vars)
- Update safety test KNOWN_DESTRUCTIVE + _DESTRUCTIVE_TEST_CASES + positive confirm test
- Update schema completeness test

757 tests passing
2026-03-15 20:13:51 -04:00
Jacob Magar
94850333e8 fix(safety): add stop_array to DESTRUCTIVE_ACTIONS, add error propagation test
stop_array can cause data loss for running containers/VMs that depend on
array shares — requires confirm=True like other destructive mutations.

- Add stop_array to DESTRUCTIVE_ACTIONS and desc_map in array.py
- Update safety audit KNOWN_DESTRUCTIVE[array] to include stop_array
- Add stop_array negative/positive tests (test_array.py, safety tests)
- Add test_snapshot_wraps_bare_exception to test_live.py (bare Exception
  from subscribe_once is wrapped by tool_error_handler into ToolError)

748 tests passing
2026-03-15 20:02:33 -04:00
Jacob Magar
252ec520d1 fix(lint): remove __future__ annotations from new tools, fix 4 failing tests
- Remove `from __future__ import annotations` from array.py, live.py,
  oidc.py, plugins.py to match existing tool pattern and resolve TC002
  ruff errors (fastmcp imports only needed in annotations under PEP 563)
- Add `# noqa: ASYNC109` to live.py timeout parameter (asyncio.timeout
  already used internally)
- Fix test_network_sends_correct_query: query name is GetNetworkInfo
- Fix test_delete_requires_confirm: match "not confirmed" not "destructive"
- Fix test_destructive_set_matches_audit[settings]: add setup_remote_access
  and enable_dynamic_remote_access to KNOWN_DESTRUCTIVE
- Fix test_logs: update mock to dict format {lines: [{timestamp, message}]}

742 tests passing, ruff clean
2026-03-15 19:57:46 -04:00
Jacob Magar
2b4b1f0395 feat(plugins): add unraid_plugins tool with list, add, remove actions
Implements the unraid_plugins MCP tool (3 actions, 1 destructive) and adds
elicit_destructive_confirmation() to core/setup to support all tools that
gate dangerous mutations behind confirm=True with optional MCP elicitation.
2026-03-15 19:26:42 -04:00
Jacob Magar
76391b4d2b feat(keys): add add_role and remove_role actions for API key role management
Adds two new mutation actions to unraid_keys:
- add_role: calls apiKey.addRole with apiKeyId + role, requires key_id and roles
- remove_role: calls apiKey.removeRole with apiKeyId + role, requires key_id and roles

Updates safety audit to explicitly exempt remove_role from the delete/remove
heuristic (reversible action — role can be re-added). Updates schema coverage
test and adds schema validation tests for both new mutations.
2026-03-15 19:13:03 -04:00
Jacob Magar
3a72f6c6b9 feat(array): add parity_history, start/stop array, disk add/remove/mount/unmount/clear_stats
Expands unraid_array from 5 to 13 actions: adds parity_history query,
start_array/stop_array state mutations, and disk operations (add_disk,
remove_disk, mount_disk, unmount_disk, clear_disk_stats). Destructive
actions remove_disk and clear_disk_stats require confirm=True. Safety
audit tests updated to cover the new DESTRUCTIVE_ACTIONS registry entry.
2026-03-15 19:03:01 -04:00
Jacob Magar
482da4485d fix: flash_backup validation, smoke test assertions, docker/notification test coverage
- storage.py: validate initiateFlashBackup response before returning success=True
- test-tools.sh: remove set -e/inherit_errexit; add success assertion to smoke tests
- test_destructive_guards.py: add confirm-guard tests for new docker destructive actions
- test_docker.py: assert mutation variables in organizer tests; add items branch test
- test_query_validation.py: add 5 missing notification mutation schema test methods
- test_notifications.py: use lowercase importance to test uppercasing logic

Resolves review threads PRRT_kwDOO6Hdxs50FgPb PRRT_kwDOO6Hdxs50FgO4 PRRT_kwDOO6Hdxs50FgO8 PRRT_kwDOO6Hdxs50FgPI PRRT_kwDOO6Hdxs50FgPL PRRT_kwDOO6Hdxs50FgPm PRRT_kwDOO6Hdxs50E2iK PRRT_kwDOO6Hdxs50E2im
2026-03-13 10:41:43 -04:00
Jacob Magar
9aee3a2448 feat: add 28 GraphQL mutations across storage, info, docker, and new settings tool
- storage: flash_backup mutation (initiates rclone flash backup, destructive)
- info: update_server and update_ssh mutations
- docker: 11 organizer mutations (create_folder, set_folder_children,
  delete_entries, move_to_folder, move_to_position, rename_folder,
  create_folder_with_items, update_view_prefs, sync_templates,
  reset_template_mappings, refresh_digests); delete_entries and
  reset_template_mappings added to DESTRUCTIVE_ACTIONS
- settings: new unraid_settings tool with 9 mutations (update,
  update_temperature, update_time, configure_ups, update_api,
  connect_sign_in, connect_sign_out, setup_remote_access,
  enable_dynamic_remote_access); registered in server.py
- tests: 82 new tests (28 settings, 23 docker organizer, 7 info, 6 storage
  + 18 existing fixes for notification regex and safety audit list)
- bump version 0.3.0 → 0.4.0 (11 tools, ~104 actions)

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-13 03:03:37 -04:00
Jacob Magar
60defc35ca feat: add 5 notification mutations + comprehensive refactors from PR review
New notification actions (archive_many, create_unique, unarchive_many,
unarchive_all, recalculate) bring unraid_notifications to 14 actions.

Also includes continuation of CodeRabbit/PR review fixes:
- Remove redundant try-except in virtualization.py (silent failure fix)
- Add QueryCache protocol with get/put/invalidate_all to core/client.py
- Refactor subscriptions (manager, diagnostics, resources, utils)
- Update config (logging, settings) for improved structure
- Expand test coverage: http_layer, safety guards, schema validation
- Minor cleanups: array, docker, health, keys tools

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-13 01:54:55 -04:00
Jacob Magar
06f18f32fc chore: update gitignore, bump to 0.2.1, apply CodeRabbit fixes
- Add .windsurf/, *.bak*, .1code/, .emdash.json to .gitignore
- Sync standard gitignore entries per project conventions
- Apply final test/tool fixes from CodeRabbit review threads
- Update GraphQL schema to latest introspection snapshot
- Bump version 0.2.0 → 0.2.1

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-13 00:53:51 -04:00
Jacob Magar
2a5b19c42f test: address final 3 CodeRabbit review threads
- http_layer/test_request_construction.py: tighten JSON error match from
  "invalid response" to "invalid response.*not valid JSON" to prevent
  false positives
- safety/test_destructive_guards.py: add test_docker_update_all_with_confirm
  to TestConfirmAllowsExecution (was missing positive coverage for update_all)
- safety/test_destructive_guards.py: expand conftest import comment to explain
  why the direct conftest import is intentional and correct
2026-02-19 02:25:21 -05:00
Jacob Magar
348f4149a5 fix: address PR review threads - test assertions, ruff violations, format_kb consistency
Resolves review threads:
- PRRT_kwDOO6Hdxs5vNroH (Thread 36): tests now verify generic ToolError message
  instead of raw exception text (security: no sensitive data in user-facing errors)
- PRRT_kwDOO6Hdxs5vNuYg (Thread 14): format_kb KB branch now uses :.2f like all
  other branches (consistency fix)
- I001/F841/PERF401: fix ruff violations in http_layer, integration, safety tests

Changes:
- tests/test_array.py: match "Failed to execute array/parity_status" (not raw error)
- tests/test_keys.py: match "Failed to execute keys/list" (not raw error)
- tests/test_notifications.py: match "Failed to execute notifications/overview" (not raw error)
- tests/test_storage.py: update format_kb assertion to "512.00 KB" (:.2f format)
- tests/http_layer/test_request_construction.py: remove unused result var (F841)
  + fix import sort (I001)
- tests/safety/test_destructive_guards.py: use list.extend (PERF401) + fix import sort
- unraid_mcp/core/utils.py: format_kb returns f"{k:.2f} KB" for sub-MB values

Co-authored-by: @coderabbitai
Co-authored-by: @cubic-dev-ai
Co-authored-by: @copilot-pull-request-reviewer
2026-02-19 01:56:23 -05:00
Jacob Magar
f76e676fd4 test: close critical coverage gaps and harden PR review fixes
Critical bug fixes from PR review agents:
- client.py: eager asyncio.Lock init, Final[frozenset] for _SENSITIVE_KEYS,
  explicit 429 ToolError after retries exhausted, removed lazy _get_client_lock()
  and _RateLimiter._get_lock() patterns
- exceptions.py: use builtin TimeoutError (UP041), explicit handler before broad
  except so asyncio timeouts get descriptive messages
- docker.py: add update_all to DESTRUCTIVE_ACTIONS (was missing), remove dead
  _MUTATION_ACTIONS constant
- manager.py: _cap_log_content returns new dict (immutable), lock write to
  resource_data, clean dead task from active_subscriptions after loop exits
- diagnostics.py: fix inaccurate comment about semicolon injection guard
- health.py: narrow except ValueError in _safe_display_url, fix TODO comment

New test coverage (98 tests added, 529 → 598 passing):
- test_subscription_validation.py: 27 tests for _validate_subscription_query
  (security-critical allow-list, forbidden keyword guards, word-boundary test)
- test_subscription_manager.py: 12 tests for _cap_log_content
  (immutability, truncation, nesting, passthrough)
- test_client.py: +57 tests — _RateLimiter (token math, refill, sleep-on-empty),
  _QueryCache (TTL, invalidation, is_cacheable), 429 retry loop (1/2/3 failures)
- test_health.py: +10 tests for _safe_display_url (credential strip, port,
  path/query removal, malformed IPv6 → <unparseable>)
- test_notifications.py: +7 importance enum and field length validation tests
- test_rclone.py: +7 _validate_config_data security guard tests
- test_storage.py: +15 (tail_lines bounds, format_kb, safe_get)
- test_docker.py: update_all now requires confirm=True + new guard test
- test_destructive_guards.py: update audit to include update_all

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-18 01:28:40 -05:00
Jacob Magar
abb7915672 feat: harden API safety and expand command docs with full test coverage 2026-02-15 22:15:51 -05:00