Add MonitorAlbum component tests: 21 cases covering all flow diagrams (bufconn + testcontainers + hand-rolled mocks)
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
@startuml MonitorAlbum - Async Poll Worker Failures
|
||||
skinparam sequenceMessageAlign center
|
||||
skinparam responseMessageBelowArrow true
|
||||
title MonitorAlbum: Async Download Poll Worker Failures
|
||||
|
||||
participant "River Queue" as River
|
||||
participant "PollDownloadWorker" as Worker
|
||||
participant "TorrentClient\n(qBittorrent)" as QBit
|
||||
database "PostgreSQL" as DB
|
||||
|
||||
note over Worker: These occur asynchronously\nafter MonitorAlbum returns.\nClient already received response.
|
||||
|
||||
== Case 1: qBittorrent unreachable during poll ==
|
||||
|
||||
River -> Worker: Work(PollDownloadArgs)
|
||||
Worker -> QBit: Find(hash)
|
||||
QBit --> Worker: error (connection refused)
|
||||
note right #orange: Logged as error.\nJob rescheduled.
|
||||
|
||||
Worker -> River: Insert(PollDownloadArgs)
|
||||
note right: Reschedule after check_interval (30s).\nRetries indefinitely until\nqBit becomes available.
|
||||
|
||||
== Case 2: Torrent disappeared from qBittorrent ==
|
||||
|
||||
River -> Worker: Work(PollDownloadArgs)
|
||||
Worker -> QBit: Find(hash)
|
||||
QBit --> Worker: empty results
|
||||
note right #salmon: Torrent was removed\nfrom qBit externally.
|
||||
|
||||
Worker -> DB: downloads.SetFailed(id, "torrent not found in client")
|
||||
note right: Download marked as failed.\nNo further polls scheduled.\nNo retry.
|
||||
|
||||
== Case 3: Torrent in error state ==
|
||||
|
||||
River -> Worker: Work(PollDownloadArgs)
|
||||
Worker -> QBit: Find(hash)
|
||||
QBit --> Worker: TorrentInfo{state: "error"}
|
||||
note right #salmon: qBit reports torrent error.\n(e.g. tracker unreachable,\ncorrupt data, disk full)
|
||||
|
||||
Worker -> DB: downloads.SetFailed(id, "torrent error state")
|
||||
note right: Download marked as failed.\nNo further polls.\nTorrent remains in qBit.
|
||||
|
||||
== Case 4: Download completes, but SetCompleted fails ==
|
||||
|
||||
River -> Worker: Work(PollDownloadArgs)
|
||||
Worker -> QBit: Find(hash)
|
||||
QBit --> Worker: TorrentInfo{progress: 1.0, savePath: "/downloads"}
|
||||
|
||||
Worker -> DB: downloads.SetCompleted(id, "/downloads")
|
||||
DB --> Worker: error (DB connection lost)
|
||||
note right #salmon: Worker returns error.\nRiver will retry the job\n(built-in retry policy).\nDownload stays in\n"downloading" state.
|
||||
|
||||
== Case 5: File scan fails after completion ==
|
||||
|
||||
River -> Worker: Work(PollDownloadArgs)
|
||||
Worker -> QBit: Find(hash)
|
||||
QBit --> Worker: TorrentInfo{progress: 1.0, path: "/downloads/Album"}
|
||||
|
||||
Worker -> DB: downloads.SetCompleted(id, "/downloads")
|
||||
DB --> Worker: OK
|
||||
note right #lightgreen: Download marked completed.
|
||||
|
||||
Worker -> Worker: scanAndHashFiles("/downloads/Album")
|
||||
Worker --> Worker: error (permission denied / path not found)
|
||||
note right #orange: Logged as error.\nDownload IS completed.\nBut download_files NOT populated.\nGetAlbum won't show file info\nfor individual tracks.
|
||||
|
||||
== Case 6: File persist fails ==
|
||||
|
||||
River -> Worker: Work(PollDownloadArgs)
|
||||
Worker -> QBit: Find(hash)
|
||||
QBit --> Worker: TorrentInfo{progress: 1.0}
|
||||
|
||||
Worker -> DB: downloads.SetCompleted(id, savePath)
|
||||
Worker -> Worker: scanAndHashFiles → 12 files
|
||||
Worker -> DB: download_files.CreateBatch(files)
|
||||
DB --> Worker: error (duplicate / constraint)
|
||||
note right #orange: Download is completed.\nFiles not persisted.\nNon-fatal: returns nil.\nNo retry.
|
||||
|
||||
== Case 7: App crash during download (startup recovery) ==
|
||||
|
||||
note over River, Worker: Application restarts.\nRiver picks up persisted jobs.
|
||||
|
||||
River -> Worker: RecoverOrphanedDownloads()
|
||||
Worker -> DB: downloads.GetActive()
|
||||
DB --> Worker: [Download{state: downloading, hash: ...}]
|
||||
|
||||
Worker -> River: Insert(PollDownloadArgs, UniqueOpts{ByArgs})
|
||||
note right #lightgreen: Deduplicated insert.\nIf River job already exists\n→ no duplicate.\nIf job was lost → recovered.\nPolling resumes.
|
||||
|
||||
@enduml
|
||||
Reference in New Issue
Block a user