test: durable e2e SABnzbd compliance suite; fix priority-lookup bug - #84
Merged
Conversation
NZB360 (and real SABnzbd) add search results by issuing a plain GET request with mode=addurl, since there's no file body to upload — only the multipart POST path applied the cat/priority/password overrides for that mode, so GET requests silently fell through to the "Unknown mode" branch and any requested category was dropped. Extract the URL-fetch-and-enqueue logic into a shared handle_addurl() used by both the GET and POST entry points.
sab_priority_to_priority mapped 0/1/2/-100/3 to Low/Normal/High/Force, shifted by one from SABnzbd's real numeric codes (constants.py: Low=-1, Normal=0, High=1, Force=2, Default=-100, Repair=3) and contradicting sab_priority_matches, which already used the correct table for queue filtering. Any client setting priority via addfile/addurl/priority-change got the wrong priority applied.
Real SABnzbd has no top-level mode=priority or mode=rename -- those are sub-commands of mode=queue, dispatched via the name parameter (_api_queue_table: delete, rename, priority, purge, pause, resume, change_complete_action, ...). handle_queue only recognized delete/pause/resume, so a compliant client's real priority-change or rename request silently fell through to a plain queue listing. Adds routing for name=priority, name=rename, name=purge, and a change_complete_action no-op, alongside the existing (non-standard but harmless) top-level mode=priority/mode=rename aliases.
Real SABnzbd's get_cats calls list_cats(default=False), which leaves the default category's config-internal name "*" untouched -- the "*" -> "Default" substitution only happens for the config UI (default=True). RustNZB returned the display string "Default" instead, which a client that specifically recognizes "*" as the default-category sentinel wouldn't find. Adds sab_resolve_category() to translate "*" back to RustNZB's internal "Default" category name wherever a client-supplied cat value is applied (addfile, addurl, change_cat), so both directions of the boundary translation stay consistent.
get_scripts is a real top-level SABnzbd API mode (sabnzbd/api.py::_api_table["get_scripts"]) that fell through dispatch_mode's default arm as "Unknown mode". RustNZB doesn't support post-processing scripts, so ["None"] -- the same value real SABnzbd reports with no scripts configured -- is the correct permanent response. Clients that fetch categories and scripts together to populate an add-download dialog may abort populating the whole dialog (category picker included) if either call errors, so this is a plausible second contributor to #65 alongside #73.
Real SABnzbd's _api_queue_delete and _api_history_delete both accept a comma-separated list of nzo_ids in `value`, and _api_history_delete additionally accepts a `del_files` flag that removes the completed output directory from disk. RustNZB's handlers only matched a single ID (or the literal "all") and never freed disk space regardless of del_files. Also fixes a related gap: the POST handler's catch-all mode dispatch hardcoded `value`/`value2` to None instead of forwarding them from the query string, which would have silently broken these (and #72's priority/rename) sub-commands over POST.
Real SABnzbd's _api_change_cat parses `value` as a comma-separated list of nzo_ids via clean_comma_separated_list, applying the category change to all of them. handle_change_cat treated `value` as a single job ID, so a multi-ID request (e.g. bulk re-categorize) matched no job and silently failed for all of them.
…mpliance # Conflicts: # crates/nzb-web/src/sabnzbd_compat.rs
# Conflicts: # crates/nzb-web/src/sabnzbd_compat.rs
# Conflicts: # crates/nzb-web/src/sabnzbd_compat.rs
# Conflicts: # crates/nzb-web/src/sabnzbd_compat.rs
# Conflicts: # crates/nzb-web/src/sabnzbd_compat.rs
…nce suite (#77) set_job_priority requires an exact job-id match, but clients only ever send the truncated SABnzbd_nzo_<12 chars> form -- both the top-level handle_priority and the new mode=queue&name=priority route stripped the prefix and passed the truncated id straight through, so priority changes always silently failed against a real nzo_id. Resolve the full job id by prefix first, the same way pause/resume/rename/ change_cat already do. This was a pre-existing bug caught by the new end-to-end suite below, not introduced by the queue-routing fix. Adds apps/rustnzb/tests/sab_compliance_e2e.rs: a durable HTTP-level test suite (spins up the real router via axum::serve + reqwest, unlike the handler-function-level tests in sabnzbd_compat.rs) that exercises version, addfile, addurl (GET), get_cats, get_scripts, mode=queue&name=priority/rename/delete, and change_cat -- the exact surface area that drifted from the real SABnzbd protocol across issues #65 and #71-#76 without any test catching it.
# Conflicts: # crates/nzb-web/src/sabnzbd_compat.rs
…ance # Conflicts: # crates/nzb-web/src/sabnzbd_compat.rs
thedancingdeveloper
enabled auto-merge (squash)
August 11, 2026 10:03
This was referenced Aug 11, 2026
thedancingdeveloper
added a commit
that referenced
this pull request
Aug 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #77. Adds
apps/rustnzb/tests/sab_compliance_e2e.rs: a durable, HTTP-level test suite that spins up the real server (axum::serve+reqwest, same pattern asapi_contracts.rs) and exercises the exact SABnzbd API surface that drifted from the real protocol across #65 and #71-#76 without any test catching it —version,addfile,addurl(GET),get_cats,get_scripts,mode=queue&name=priority|rename|delete, andchange_cat. Each test cites the upstreamsabnzbd/sabnzbd@5.1.xsource it was verified against, matching the pattern used in the individual #71-#76 fixes.This is a stacked PR — its base includes #70 and #78-#83 (all of which it exercises end-to-end) — so its diff will shrink to just the new test file plus the fix below once those merge first.
Bug caught by the new suite
Writing the
mode=queue&name=priorityend-to-end test immediately failed against realnzo_ids. Root cause:queue_manager::set_job_priorityrequires an exact job-id match, but SABnzbd clients only ever know the truncatedSABnzbd_nzo_<12 chars>form — both the pre-existing top-levelhandle_priorityand the newmode=queue&name=priorityroute (#72) stripped theSABnzbd_nzo_prefix and passed the truncated id straight toset_job_priority, so priority changes silently failed against any real nzo_id. This is a pre-existing bug (present inhandle_prioritybefore any of #71-#76), not something introduced by this branch — the handler-level tests insabnzbd_compat.rsdidn't catch it because they used full job IDs directly rather than going through the real truncated-id round trip.Fixed by resolving the full job id via prefix lookup first, the same way
pause/resume/rename/change_catalready do.Test plan
cargo test -p nzb-web— full suite passes (30 tests insabnzbd_compat).cargo test -p rustnzb --test sab_compliance_e2e— all 9 new e2e tests pass.cargo test -p rustnzb— full workspace test suite passes, no regressions.cargo clippy -p nzb-web --all-targets/cargo clippy -p rustnzb --all-targets— clean.🤖 Generated with Claude Code