soulsync/tests/imports
Broque Thomas f4cff78f13 Quarantine management — list, approve, delete, recover
Closes #584. Quarantined files used to sit in ss_quarantine/ with a
thin sidecar — no UI, no recovery, no way to see what got dropped.
This adds the management surface the user needs without going to the
filesystem.

UI: new "Quarantine" button on the downloads page header opens a
modal with every quarantined file (filename, expected track/artist,
reason, when, size). Three actions per row:

- Approve (one-click): restores the file, re-runs the post-process
  pipeline with ONLY the failing check skipped, lands in the library
  with full tags + lyrics + scan
- Recover (legacy fallback): moves to Staging for thin-sidecar
  entries that lack the embedded context Approve needs
- Delete: permanent removal of file + sidecar

Per-check bypass: context['_skip_quarantine_check'] = 'integrity' /
'acoustid' / 'bit_depth'. Skips ONLY the named check — other quality
gates stay live. No blanket bypass-all flag.

Sidecar expansion: move_to_quarantine now persists the full
json-serializable context via serialize_quarantine_context (drops
non-JSON-safe values, walks nested dicts/lists/sets, str-coerces
unknown objects) plus the trigger name. Existing thin sidecars are
detected and routed to Recover instead of Approve.

Pure helpers in core/imports/quarantine.py: list_quarantine_entries
/ delete_quarantine_entry / approve_quarantine_entry /
recover_to_staging / serialize_quarantine_context. 27 tests pin
every shape: orphan files / orphan sidecars / corrupt sidecars /
collision-safe filename restoration / full-context vs thin-sidecar
dispatch / json round-trip safety.

Four new endpoints in web_server.py — thin glue around the helpers:
GET /api/quarantine/list, DELETE /api/quarantine/<id>,
POST /api/quarantine/<id>/approve, POST /api/quarantine/<id>/recover.

Download modal status differentiates "🛡️ Quarantined" from
" Failed" so recoverable files are visible at a glance — checked
against the error_message text, no schema change needed.

Pipeline changes are three minimal per-check conditionals at the
existing quarantine sites in core/imports/pipeline.py. Each
move_to_quarantine call now passes its trigger name so the sidecar
records which check fired.

Full suite: 2992 passed.
2026-05-14 08:06:19 -07:00
..
test_album_matching_exact_id.py Auto-import matching: fix Deezer source classification + bump tolerance 2026-05-09 15:53:17 -07:00
test_album_matching_helper.py Auto-import polish: real-file tag reader test, source-aware duration, pin consolation 2026-05-09 11:08:09 -07:00
test_album_search_scoring.py Auto-import: fall through to other metadata sources when primary returns no match 2026-05-12 12:32:18 -07:00
test_auto_import_clear_completed_endpoint.py Import history: Clear History button now sweeps stuck 'processing' rows 2026-05-12 12:53:37 -07:00
test_auto_import_context_shape.py Auto-import: fall through to other metadata sources when primary returns no match 2026-05-12 12:32:18 -07:00
test_auto_import_executor.py Auto-import: bounded ThreadPoolExecutor + per-candidate UI state isolation 2026-05-09 17:45:42 -07:00
test_auto_import_live_progress.py Auto-import: live per-track progress + in-progress history row 2026-05-02 22:34:09 -07:00
test_auto_import_multi_disc_matching.py Auto-import: respect disc_number in dedup + match scoring 2026-05-08 22:36:51 -07:00
test_auto_import_multi_source_fallback.py Auto-import: fall through to other metadata sources when primary returns no match 2026-05-12 12:32:18 -07:00
test_auto_import_scanner_grouping.py Auto-import: dedup on folder_hash, not path — fixes silent-skip bug 2026-05-09 14:09:19 -07:00
test_auto_import_tag_reader_real_files.py Auto-import polish: real-file tag reader test, source-aware duration, pin consolation 2026-05-09 11:08:09 -07:00
test_build_single_import_context_typed_path.py Migrate album-info builders to typed Album path 2026-05-03 22:53:12 -07:00
test_dedup_orphan_cleanup.py Prune slskd dedup orphans after import 2026-05-01 09:35:08 -07:00
test_duration_tolerance_resolution.py Configurable duration tolerance for downloaded-file integrity check 2026-05-14 06:53:36 -07:00
test_file_integrity.py Reject broken downloads before tagging via universal integrity check 2026-05-03 08:21:01 -07:00
test_import_album.py Rehome import helpers into core/imports 2026-04-27 19:54:44 +03:00
test_import_context.py Fix Spotify source ID fallback 2026-05-02 22:02:01 +03:00
test_import_file_ops.py Broaden import and metadata test coverage 2026-04-27 19:55:07 +03:00
test_import_filename.py Rehome import helpers into core/imports 2026-04-27 19:54:44 +03:00
test_import_guards.py Rehome import helpers into core/imports 2026-04-27 19:54:44 +03:00
test_import_paths.py fix: substitute \$cdnum in download paths and skip auto disc folder when template uses it 2026-04-28 21:32:24 -07:00
test_import_pipeline.py Reject broken downloads before tagging via universal integrity check 2026-05-03 08:21:01 -07:00
test_import_resolution_single_track_context.py Move metadata helpers into package modules 2026-04-29 11:28:42 +03:00
test_import_routes.py Extract manual import route handlers 2026-05-13 21:27:01 -07:00
test_import_side_effects.py Auto-import: album duration = album total + conservative re-import UPDATE path 2026-05-09 21:19:35 -07:00
test_import_singles_route_through_album_path.py Route imported singles/EPs through album_path template 2026-04-30 21:33:09 -07:00
test_import_staging.py Rehome import helpers into core/imports 2026-04-27 19:54:44 +03:00
test_lossy_copy_delete_original.py Honor lossy_copy.delete_original after successful conversion 2026-05-02 14:26:46 -07:00
test_quarantine_management.py Quarantine management — list, approve, delete, recover 2026-05-14 08:06:19 -07:00
test_search_match_endpoints.py Deezer search: drop advanced-syntax at endpoint, free-text + rerank wins 2026-05-10 09:36:48 -07:00