soulsync/tests/discovery
Broque Thomas 6fe85f2f37 Server playlist sync: append mode (preserve user-added tracks)
Discord report (CJFC, 2026-04-26): syncing a Spotify playlist to the
server overwrote anything manually added to the server-side playlist.
The fix adds a per-sync mode picker next to the Sync button on the
playlist details modal — Replace (default, current delete-recreate
behavior) or Append only (preserves existing tracks, only adds new
ones). Useful when the source platform caps playlist size and the
user is manually building beyond it on the server.

Implementation:

* New `append_to_playlist(name, tracks)` method on Plex / Jellyfin /
  Navidrome clients. Each uses the server's NATIVE append API:
    - Plex: `existing_playlist.addItems(new_tracks)`
    - Jellyfin: `POST /Playlists/<id>/Items?Ids=...&UserId=...`
    - Navidrome: Subsonic `updatePlaylist?songIdToAdd=...`
  Falls back to `create_playlist` when the playlist doesn't exist
  yet (first sync). No delete-recreate, no backup playlist created
  (preserves playlist creation date + metadata + non-soulsync-managed
  tracks).
* Dedup-by-server-native-id (ratingKey for Plex, GUID for Jellyfin,
  song-id for Navidrome) — never re-adds a track already on the
  playlist. Server-native identity, not fuzzy title+artist match,
  so it can't false-collide.
* `sync_service.sync_playlist` accepts `sync_mode='replace'|'append'`
  kwarg. Single if/else branch dispatches to `append_to_playlist` or
  `update_playlist`. Threaded through `core/discovery/sync.run_sync_task`
  and the `/api/sync/start` HTTP handler. Validation on the API rejects
  unknown mode strings (defaults to 'replace').
* Frontend: per-playlist `<select id="sync-mode-${id}">` rendered next
  to the Sync button in both modal renderers (sync-spotify.js for
  Spotify playlists, sync-services.js for Deezer ARL playlists).
  `startPlaylistSync` reads the select at click time; missing select
  (other callers like discover.js) defaults to 'replace' so backward
  compat preserved without per-call-site updates.
* SoulSync standalone has no playlist methods at all and the modal
  hides the Sync button entirely on it via `_isSoulsyncStandalone` —
  dispatch never reaches that path, no defensive fallback needed.

15 new tests pin per-server append behavior:
  - missing playlist → create_playlist delegation
  - dedup filtering (existing IDs skipped, only new tracks added)
  - empty new-track set short-circuits without API call
  - failure paths return False without raising
  - contract listing (KNOWN_PER_SERVER_METHODS includes
    'append_to_playlist'; Plex / Jellyfin / Navidrome all implement)

Plus tests/discovery/test_discovery_sync.py fake `sync_playlist`
fixture got `sync_mode='replace'` default to match the new signature
(was breaking after the kwarg add; now passing).

WHATS_NEW entry under new '2.6.0' block (hidden by
`_getLatestWhatsNewVersion` until next release bump).

Closes CJFC discord request.
2026-05-10 22:52:11 -07:00
..
__init__.py PR5a: lift _run_sync_task to core/discovery/sync.py 2026-04-28 11:20:47 -07:00
test_discovery_beatport.py PR5f: lift _run_beatport_discovery_worker to core/discovery/beatport.py 2026-04-28 15:36:15 -07:00
test_discovery_deezer.py PR5d: lift _run_deezer_discovery_worker to core/discovery/deezer.py 2026-04-28 12:45:18 -07:00
test_discovery_listenbrainz.py PR5g: lift _run_listenbrainz_discovery_worker to core/discovery/listenbrainz.py 2026-04-28 16:09:02 -07:00
test_discovery_playlist.py PR5c: lift _run_playlist_discovery_worker to core/discovery/playlist.py 2026-04-28 12:20:48 -07:00
test_discovery_quality_scanner.py Preserve artwork in quality scanner wishlist handoff 2026-04-30 21:42:16 +03:00
test_discovery_spotify_public.py PR5e: lift _run_spotify_public_discovery_worker to core/discovery/spotify_public.py 2026-04-28 13:13:41 -07:00
test_discovery_sync.py Server playlist sync: append mode (preserve user-added tracks) 2026-05-10 22:52:11 -07:00
test_discovery_tidal.py Lift _run_tidal_discovery_worker to core/discovery/tidal.py 2026-04-28 23:02:24 -07:00
test_discovery_youtube.py fix: cast duration_ms to int before :02d format in discovery workers 2026-04-28 12:07:54 -07:00
test_quality_scanner_typed_album.py Migrate discography + quality scanner to typed Album path 2026-05-04 08:12:40 -07:00