Add MonitorAlbum component tests: 21 cases covering all flow diagrams (bufconn + testcontainers + hand-rolled mocks)
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
@@ -0,0 +1,39 @@
|
|||||||
|
@startuml MonitorAlbum - Already Owned (Early Return)
|
||||||
|
skinparam sequenceMessageAlign center
|
||||||
|
skinparam responseMessageBelowArrow true
|
||||||
|
title MonitorAlbum: Album Already Owned in Requested Quality
|
||||||
|
|
||||||
|
actor Client
|
||||||
|
participant "MusicAgregatorService" as Service
|
||||||
|
participant "MetadataService" as Metadata
|
||||||
|
database "metadata-agregator\n(gRPC)" as MetaGRPC
|
||||||
|
database "PostgreSQL" as DB
|
||||||
|
|
||||||
|
Client -> Service: MonitorAlbum(album_id, quality=LOSSLESS)
|
||||||
|
Service -> Metadata: GetAlbum(album_id)
|
||||||
|
Metadata -> MetaGRPC: GetAlbum(id)
|
||||||
|
MetaGRPC --> Metadata: Album
|
||||||
|
Metadata -> DB: albums.GetByExternalID()
|
||||||
|
DB --> Metadata: found (already persisted)
|
||||||
|
note right: Album exists from\nprevious MonitorAlbum\nor GetArtists discovery
|
||||||
|
Metadata --> Service: Album
|
||||||
|
|
||||||
|
Service -> DB: albums.GetByExternalID()
|
||||||
|
DB --> Service: dbAlbum
|
||||||
|
Service -> DB: albums.SetMonitorState(id, monitored)
|
||||||
|
note right #lightgreen: Always set monitored\nregardless of outcome
|
||||||
|
|
||||||
|
Service -> DB: downloads.HasAlbumInQuality(id, LOSSLESS, "16-44")
|
||||||
|
DB --> Service: true
|
||||||
|
note right #lightgreen: Found existing download\nwith state IN\n('completed', 'seeding')
|
||||||
|
|
||||||
|
Service -> Service: buildMonitorAlbumResponse()
|
||||||
|
Service -> DB: downloads.GetByAlbumID()
|
||||||
|
DB --> Service: download (completed, FLAC, /downloads)
|
||||||
|
Service -> DB: artists.GetByExternalID()
|
||||||
|
DB --> Service: artist
|
||||||
|
|
||||||
|
Service --> Client: MonitorAlbumResponse
|
||||||
|
note right #lightgreen: album: monitored, download info\nartist: monitored\nrelease: nil (no new search)\n\nNo indexer search.\nNo torrent client interaction.\nFast response.
|
||||||
|
|
||||||
|
@enduml
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 125 KiB |
@@ -0,0 +1,74 @@
|
|||||||
|
@startuml MonitorAlbum - Indexer & Parse Failures
|
||||||
|
skinparam sequenceMessageAlign center
|
||||||
|
skinparam responseMessageBelowArrow true
|
||||||
|
title MonitorAlbum Error: Indexer Search & Parse Failures
|
||||||
|
|
||||||
|
actor Client
|
||||||
|
participant "MusicAgregatorService" as Service
|
||||||
|
participant "IndexerService\n(Jackett)" as Indexer
|
||||||
|
participant "MagnetResolver" as Magnet
|
||||||
|
|
||||||
|
== Case 1: Indexer search fails (Jackett down / timeout) ==
|
||||||
|
|
||||||
|
Client -> Service: MonitorAlbum(album_id, quality)
|
||||||
|
note right: metadata fetch + persist succeeded
|
||||||
|
|
||||||
|
Service -> Indexer: Search("Artist Album", tracker)
|
||||||
|
Indexer --> Service: error (connection refused / timeout)
|
||||||
|
Service --> Client: error
|
||||||
|
note right #salmon: Full stop after metadata.\nAlbum is persisted & monitored\nbut no torrent found.
|
||||||
|
|
||||||
|
== Case 2: Indexer returns zero results ==
|
||||||
|
|
||||||
|
Client -> Service: MonitorAlbum(album_id, quality)
|
||||||
|
Service -> Indexer: Search("Artist Album", tracker)
|
||||||
|
Indexer --> Service: SearchResponse (0 items)
|
||||||
|
Service -> Service: parseSearchResults → empty
|
||||||
|
Service -> Service: filterByQuality → empty
|
||||||
|
Service --> Client: MonitorAlbumResponse
|
||||||
|
note right #orange: Partial response.\nalbum + artist returned.\nrelease: nil.\nNo torrent added.
|
||||||
|
|
||||||
|
== Case 3: All results have no seeders ==
|
||||||
|
|
||||||
|
Client -> Service: MonitorAlbum(album_id, quality)
|
||||||
|
Service -> Indexer: Search(...)
|
||||||
|
Indexer --> Service: SearchResponse (5 items, all seeders=0)
|
||||||
|
|
||||||
|
loop for each item
|
||||||
|
Service -> Service: item.Seeders == 0 → skip
|
||||||
|
note right: Logged as warning per item
|
||||||
|
end
|
||||||
|
|
||||||
|
Service -> Service: parsed = empty
|
||||||
|
Service -> Service: filterByQuality → empty
|
||||||
|
Service --> Client: MonitorAlbumResponse (no release)
|
||||||
|
|
||||||
|
== Case 4: All magnet resolves fail ==
|
||||||
|
|
||||||
|
Client -> Service: MonitorAlbum(album_id, quality)
|
||||||
|
Service -> Indexer: Search(...)
|
||||||
|
Indexer --> Service: SearchResponse (3 items)
|
||||||
|
|
||||||
|
loop for each item
|
||||||
|
Service -> Magnet: Resolve(magnet_uri)
|
||||||
|
Magnet --> Service: error (timeout / no active peers)
|
||||||
|
Service -> Service: fallback to title parse
|
||||||
|
note right: Release parsed from title only.\nFormat may be "unknown".\nNo torrent data (nil).
|
||||||
|
end
|
||||||
|
|
||||||
|
Service -> Service: filterByQuality(parsed, LOSSLESS)
|
||||||
|
note right #orange: Title-parsed releases may have\nformat=unknown → not lossless\n→ filtered out
|
||||||
|
Service --> Client: MonitorAlbumResponse (no release)
|
||||||
|
|
||||||
|
== Case 5: No results match quality filter ==
|
||||||
|
|
||||||
|
Client -> Service: MonitorAlbum(album_id, quality=LOSSLESS)
|
||||||
|
Service -> Indexer: Search(...)
|
||||||
|
Indexer --> Service: SearchResponse (3 items)
|
||||||
|
Service -> Service: parseSearchResults → 3 items (all MP3)
|
||||||
|
Service -> Service: filterByQuality(LOSSLESS) → empty
|
||||||
|
note right: All releases are lossy.\nQuality filter rejects all.
|
||||||
|
Service --> Client: MonitorAlbumResponse
|
||||||
|
note right #orange: album + artist returned.\nrelease: nil.\n"no releases match quality filter"
|
||||||
|
|
||||||
|
@enduml
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
@@ -0,0 +1,58 @@
|
|||||||
|
@startuml MonitorAlbum - Metadata Fetch Failures
|
||||||
|
skinparam sequenceMessageAlign center
|
||||||
|
skinparam responseMessageBelowArrow true
|
||||||
|
title MonitorAlbum Error: Metadata Fetch Failures
|
||||||
|
|
||||||
|
actor Client
|
||||||
|
participant "MusicAgregatorService" as Service
|
||||||
|
participant "MetadataService" as Metadata
|
||||||
|
database "metadata-agregator\n(gRPC)" as MetaGRPC
|
||||||
|
database "PostgreSQL" as DB
|
||||||
|
|
||||||
|
== Case 1: metadata-agregator unreachable ==
|
||||||
|
|
||||||
|
Client -> Service: MonitorAlbum(album_id, quality)
|
||||||
|
Service -> Metadata: GetAlbum(album_id)
|
||||||
|
Metadata -> MetaGRPC: GetAlbum(id)
|
||||||
|
MetaGRPC --> Metadata: gRPC error (Unavailable)
|
||||||
|
Metadata --> Service: error: "fetching album: ..."
|
||||||
|
Service --> Client: error (Unavailable)
|
||||||
|
note right: Full stop.\nNo DB writes occur.\nNo indexer search.
|
||||||
|
|
||||||
|
== Case 2: Album not found in metadata ==
|
||||||
|
|
||||||
|
Client -> Service: MonitorAlbum(album_id, quality)
|
||||||
|
Service -> Metadata: GetAlbum(album_id)
|
||||||
|
Metadata -> MetaGRPC: GetAlbum(id)
|
||||||
|
MetaGRPC --> Metadata: gRPC error (NotFound)
|
||||||
|
Metadata --> Service: error: "fetching album: ..."
|
||||||
|
Service --> Client: error (NotFound)
|
||||||
|
note right: Full stop.\nInvalid album_id.\nNo side effects.
|
||||||
|
|
||||||
|
== Case 3: Album found, but artist persist fails ==
|
||||||
|
|
||||||
|
Client -> Service: MonitorAlbum(album_id, quality)
|
||||||
|
Service -> Metadata: GetAlbum(album_id)
|
||||||
|
Metadata -> MetaGRPC: GetAlbum(id)
|
||||||
|
MetaGRPC --> Metadata: Album
|
||||||
|
|
||||||
|
Metadata -> DB: albums.GetByExternalID()
|
||||||
|
DB --> Metadata: not found
|
||||||
|
|
||||||
|
Metadata -> DB: artists.Create()
|
||||||
|
DB --> Metadata: error (e.g. connection lost)
|
||||||
|
note right #salmon: Artist persist fails.\nLogged as warning.\nFlow continues.
|
||||||
|
|
||||||
|
Metadata -> DB: albums.Create()
|
||||||
|
note right #salmon: artistID is empty\n→ album persist skipped\n(no artist reference)
|
||||||
|
|
||||||
|
Metadata --> Service: Album (metadata only)
|
||||||
|
|
||||||
|
Service -> DB: albums.GetByExternalID()
|
||||||
|
DB --> Service: not found (album never persisted)
|
||||||
|
note right #salmon: dbAlbum is nil.\nMonitor state not set.\nOwnership check skipped.
|
||||||
|
|
||||||
|
Service -> Service: continues to indexer search...
|
||||||
|
note right: Flow proceeds but\ndownload persistence\nwill be skipped later\n(dbAlbum == nil)
|
||||||
|
|
||||||
|
@enduml
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 180 KiB |
@@ -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
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 120 KiB |
@@ -0,0 +1,71 @@
|
|||||||
|
@startuml MonitorAlbum - Torrent Client Failures
|
||||||
|
skinparam sequenceMessageAlign center
|
||||||
|
skinparam responseMessageBelowArrow true
|
||||||
|
title MonitorAlbum Error: Torrent Client Failures
|
||||||
|
|
||||||
|
actor Client
|
||||||
|
participant "MusicAgregatorService" as Service
|
||||||
|
participant "TorrentClient\n(qBittorrent)" as QBit
|
||||||
|
database "PostgreSQL" as DB
|
||||||
|
|
||||||
|
note over Service: Metadata fetched, indexer searched,\nbest release selected.
|
||||||
|
|
||||||
|
== Case 1: qBittorrent unreachable ==
|
||||||
|
|
||||||
|
Service -> QBit: Find(hash)
|
||||||
|
QBit --> Service: error (connection refused)
|
||||||
|
note right: Find() fails → skip existence check
|
||||||
|
|
||||||
|
Service -> QBit: AddMagnet(uri)
|
||||||
|
QBit --> Service: error (connection refused)
|
||||||
|
Service --> Client: error
|
||||||
|
note right #salmon: Album is persisted & monitored.\nNo torrent added.\nNo download record created.
|
||||||
|
|
||||||
|
== Case 2: Torrent already exists in qBit ==
|
||||||
|
|
||||||
|
Service -> QBit: Find(hash)
|
||||||
|
QBit --> Service: [TorrentInfo{state: stalledUP}]
|
||||||
|
note right #lightgreen: Torrent found.\nSkip AddMagnet/AddTorrent.
|
||||||
|
|
||||||
|
Service -> DB: torrents.Create (upsert)
|
||||||
|
Service -> DB: torrents.GetByInfoHash
|
||||||
|
DB --> Service: savedTorrent
|
||||||
|
|
||||||
|
Service -> DB: downloads.GetActiveByTorrentID(torrent_id)
|
||||||
|
DB --> Service: Download{state: completed}
|
||||||
|
note right #lightgreen: Active download exists.\nSkip duplicate insert.
|
||||||
|
|
||||||
|
Service --> Client: MonitorAlbumResponse
|
||||||
|
note right: Returns album + artist + release.\nNo duplicate download created.\nNo error.
|
||||||
|
|
||||||
|
== Case 3: AddTorrent fails (no torrent data) ==
|
||||||
|
|
||||||
|
Service -> QBit: Find(hash)
|
||||||
|
QBit --> Service: not found
|
||||||
|
|
||||||
|
note right: best.torrentData is nil\n(magnet resolve failed,\nfell back to title parse)
|
||||||
|
|
||||||
|
Service -> Service: len(best.torrentData) == 0
|
||||||
|
Service --> Client: error "no torrent data available"
|
||||||
|
note right #salmon: Magnet link but no\ntorrent data resolved.\nCannot add to client.
|
||||||
|
|
||||||
|
== Case 4: Torrent persists, but download insert fails ==
|
||||||
|
|
||||||
|
Service -> QBit: AddMagnet(uri)
|
||||||
|
QBit --> Service: OK
|
||||||
|
|
||||||
|
Service -> DB: torrents.Create (upsert)
|
||||||
|
Service -> DB: torrents.GetByInfoHash
|
||||||
|
DB --> Service: savedTorrent
|
||||||
|
|
||||||
|
Service -> DB: downloads.GetActiveByTorrentID
|
||||||
|
DB --> Service: not found
|
||||||
|
|
||||||
|
Service -> DB: downloads.Create(download)
|
||||||
|
DB --> Service: error (constraint violation)
|
||||||
|
note right #salmon: Logged as error.\nTorrent is in qBit but\nno download record.\nResponse still returned\n(non-fatal within saveTorrentAndDownload).\nNo poll job scheduled.
|
||||||
|
|
||||||
|
Service --> Client: MonitorAlbumResponse
|
||||||
|
note right #orange: Response includes release info.\nDownload info may be missing.\nTorrent is downloading in qBit\nbut untracked in DB.
|
||||||
|
|
||||||
|
@enduml
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 163 KiB |
@@ -0,0 +1,138 @@
|
|||||||
|
@startuml MonitorAlbum Happy Path
|
||||||
|
skinparam sequenceMessageAlign center
|
||||||
|
skinparam responseMessageBelowArrow true
|
||||||
|
|
||||||
|
actor Client
|
||||||
|
participant "gRPC Server" as Server
|
||||||
|
participant "MusicAgregatorService" as Service
|
||||||
|
participant "MetadataService" as Metadata
|
||||||
|
database "metadata-agregator\n(gRPC)" as MetaGRPC
|
||||||
|
database "PostgreSQL" as DB
|
||||||
|
participant "IndexerService\n(Jackett)" as Indexer
|
||||||
|
participant "MagnetResolver" as Magnet
|
||||||
|
participant "TorrentClient\n(qBittorrent)" as QBit
|
||||||
|
participant "River Queue" as River
|
||||||
|
participant "PollDownloadWorker" as PollWorker
|
||||||
|
|
||||||
|
== 1. Fetch Album Metadata ==
|
||||||
|
|
||||||
|
Client -> Server: MonitorAlbum(album_id, quality, tracker)
|
||||||
|
Server -> Service: MonitorAlbum(ctx, req)
|
||||||
|
|
||||||
|
Service -> Metadata: GetAlbum(album_id)
|
||||||
|
Metadata -> MetaGRPC: GetAlbum(id)
|
||||||
|
MetaGRPC --> Metadata: Album (title, artists, genres, ...)
|
||||||
|
|
||||||
|
Metadata -> DB: albums.GetByExternalID(external_id)
|
||||||
|
note right: Check if album already persisted
|
||||||
|
DB --> Metadata: not found
|
||||||
|
|
||||||
|
Metadata -> DB: artists.Create(artist, state=monitored)
|
||||||
|
note right: Upsert artist\nnever downgrades\nmonitored/excluded
|
||||||
|
Metadata -> DB: albums.Create(album, state=monitored)
|
||||||
|
note right: Upsert album\nnever downgrades\nmonitored/excluded
|
||||||
|
Metadata --> Service: Album
|
||||||
|
|
||||||
|
== 2. Set Monitor State ==
|
||||||
|
|
||||||
|
Service -> DB: albums.GetByExternalID(external_id)
|
||||||
|
DB --> Service: dbAlbum
|
||||||
|
Service -> DB: albums.SetMonitorState(id, monitored)
|
||||||
|
note right: Explicitly mark\nalbum as monitored
|
||||||
|
|
||||||
|
== 3. Check If Already Owned ==
|
||||||
|
|
||||||
|
Service -> DB: downloads.HasAlbumInQuality(album_id, format, quality)
|
||||||
|
DB --> Service: false (not owned)
|
||||||
|
|
||||||
|
== 4. Search Indexers ==
|
||||||
|
|
||||||
|
Service -> Indexer: Search(artist + album title, tracker)
|
||||||
|
Indexer -> Indexer: Jackett API\n/api/v2.0/indexers/all/results
|
||||||
|
Indexer --> Service: SearchResponse (N items)
|
||||||
|
|
||||||
|
== 5. Parse & Resolve Releases ==
|
||||||
|
|
||||||
|
loop for each search result (with download link & seeders > 0)
|
||||||
|
alt magnet link
|
||||||
|
Service -> Magnet: Resolve(magnet_uri)
|
||||||
|
note right: DHT lookup, 30s timeout\n15s early exit if peers\nbut none active
|
||||||
|
Magnet --> Service: torrent metadata (files, hash, size)
|
||||||
|
Service -> Service: ParseTorrent(torrentData, album)
|
||||||
|
else HTTP torrent link
|
||||||
|
Service -> Service: downloadTorrentData(url)
|
||||||
|
Service -> Service: ParseTorrent(torrentData, album)
|
||||||
|
end
|
||||||
|
note right: Extract: format, bitDepth, sampleRate,\nsource, trackCount, coverArt, cueSheet, ripLog
|
||||||
|
end
|
||||||
|
|
||||||
|
== 6. Filter & Select Best ==
|
||||||
|
|
||||||
|
Service -> Service: filterByQuality(parsed, quality)
|
||||||
|
note right: Match LOSSLESS/LOSSY/UNSPECIFIED\nagainst release format
|
||||||
|
Service -> Service: selectBestRelease(filtered)
|
||||||
|
note right: Highest seeder count wins
|
||||||
|
|
||||||
|
== 7. Add to Torrent Client ==
|
||||||
|
|
||||||
|
Service -> QBit: Find(hash)
|
||||||
|
QBit --> Service: not found
|
||||||
|
|
||||||
|
alt magnet link
|
||||||
|
Service -> QBit: AddMagnet(magnet_uri)
|
||||||
|
else torrent file
|
||||||
|
Service -> QBit: AddTorrent(file)
|
||||||
|
end
|
||||||
|
QBit --> Service: OK
|
||||||
|
|
||||||
|
== 8. Persist Torrent & Download ==
|
||||||
|
|
||||||
|
Service -> DB: torrents.Create(torrent)
|
||||||
|
note right: Upsert on info_hash\nupdates seeders/peers
|
||||||
|
Service -> DB: torrents.GetByInfoHash(hash)
|
||||||
|
DB --> Service: savedTorrent (with DB id)
|
||||||
|
|
||||||
|
Service -> DB: downloads.GetActiveByTorrentID(torrent_id)
|
||||||
|
DB --> Service: not found (no active download)
|
||||||
|
|
||||||
|
Service -> DB: downloads.Create(download)
|
||||||
|
note right: state = "downloading"\nformat, quality, qbit_hash
|
||||||
|
DB --> Service: download (with DB id)
|
||||||
|
|
||||||
|
== 9. Schedule Download Poll ==
|
||||||
|
|
||||||
|
Service -> River: Insert(PollDownloadArgs)
|
||||||
|
note right: download_id, torrent_hash\ncheck_interval = 30s\nscheduled_at = now + 30s
|
||||||
|
River --> Service: job scheduled
|
||||||
|
|
||||||
|
== 10. Build & Return Response ==
|
||||||
|
|
||||||
|
Service -> DB: albums.GetByExternalID(external_id)
|
||||||
|
DB --> Service: dbAlbum (refreshed)
|
||||||
|
Service -> DB: downloads.GetByAlbumID(album_id)
|
||||||
|
DB --> Service: downloads (with state)
|
||||||
|
Service -> DB: artists.GetByExternalID(artist_external_id)
|
||||||
|
DB --> Service: dbArtist
|
||||||
|
|
||||||
|
Service --> Server: MonitorAlbumResponse
|
||||||
|
note right: album: id, title, monitor_state=monitored,\n download: state, format, quality\nartist: id, name, monitor_state\nrelease: hash, format, seeders, tracks
|
||||||
|
Server --> Client: MonitorAlbumResponse
|
||||||
|
|
||||||
|
== 11. Async: Download Polling (River Worker) ==
|
||||||
|
|
||||||
|
River -> PollWorker: Work(PollDownloadArgs)
|
||||||
|
PollWorker -> QBit: Find(hash)
|
||||||
|
QBit --> PollWorker: TorrentInfo (progress, state, path)
|
||||||
|
|
||||||
|
alt progress < 100%
|
||||||
|
PollWorker -> River: Insert(PollDownloadArgs)
|
||||||
|
note right: Reschedule after check_interval
|
||||||
|
else progress == 100%
|
||||||
|
PollWorker -> DB: downloads.SetCompleted(id, save_path)
|
||||||
|
PollWorker -> PollWorker: scanAndHashFiles(content_path)
|
||||||
|
note right: Walk directory, identify audio files\n(.flac, .mp3, .aac, ...)\nSHA-256 hash each file
|
||||||
|
PollWorker -> DB: download_files.CreateBatch(files)
|
||||||
|
note right: file_path, file_size, file_type,\nsha256_hash, verified_at
|
||||||
|
end
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -5,6 +5,9 @@ go 1.26.2
|
|||||||
require github.com/rs/zerolog v1.35.1
|
require github.com/rs/zerolog v1.35.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
dario.cat/mergo v1.0.2 // indirect
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/RoaringBitmap/roaring v1.2.3 // indirect
|
github.com/RoaringBitmap/roaring v1.2.3 // indirect
|
||||||
github.com/alecthomas/atomic v0.1.0-alpha2 // indirect
|
github.com/alecthomas/atomic v0.1.0-alpha2 // indirect
|
||||||
github.com/anacrolix/btree v0.0.0-20251201064447-d86c3fa41bd8 // indirect
|
github.com/anacrolix/btree v0.0.0-20251201064447-d86c3fa41bd8 // indirect
|
||||||
@@ -29,15 +32,27 @@ require (
|
|||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bits-and-blooms/bitset v1.2.2 // indirect
|
github.com/bits-and-blooms/bitset v1.2.2 // indirect
|
||||||
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
|
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/cespare/xxhash v1.1.0 // indirect
|
github.com/cespare/xxhash v1.1.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/containerd/errdefs v1.0.0 // indirect
|
||||||
|
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||||
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
|
github.com/containerd/platforms v0.2.1 // indirect
|
||||||
|
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/distribution/reference v0.6.0 // indirect
|
||||||
|
github.com/docker/go-connections v0.6.0 // indirect
|
||||||
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||||
|
github.com/ebitengine/purego v0.10.0 // indirect
|
||||||
github.com/edsrzf/mmap-go v1.1.0 // indirect
|
github.com/edsrzf/mmap-go v1.1.0 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/go-llsqlite/adapter v0.0.0-20230927005056-7f5ce7f0c916 // indirect
|
github.com/go-llsqlite/adapter v0.0.0-20230927005056-7f5ce7f0c916 // indirect
|
||||||
github.com/go-llsqlite/crawshaw v0.5.6-0.20250312230104-194977a03421 // indirect
|
github.com/go-llsqlite/crawshaw v0.5.6-0.20250312230104-194977a03421 // indirect
|
||||||
github.com/go-logr/logr v1.4.3 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
github.com/google/btree v1.1.2 // indirect
|
github.com/google/btree v1.1.2 // indirect
|
||||||
github.com/google/go-cmp v0.7.0 // indirect
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
@@ -49,15 +64,29 @@ require (
|
|||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/pgx/v5 v5.9.2 // indirect
|
github.com/jackc/pgx/v5 v5.9.2 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
|
github.com/klauspost/compress v1.18.5 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.3 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.3 // indirect
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
|
||||||
|
github.com/magiconair/properties v1.8.10 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/minio/sha256-simd v1.0.0 // indirect
|
github.com/minio/sha256-simd v1.0.0 // indirect
|
||||||
|
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||||
|
github.com/moby/go-archive v0.2.0 // indirect
|
||||||
|
github.com/moby/moby/api v1.54.1 // indirect
|
||||||
|
github.com/moby/moby/client v0.4.0 // indirect
|
||||||
|
github.com/moby/patternmatcher v0.6.1 // indirect
|
||||||
|
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||||
|
github.com/moby/sys/user v0.4.0 // indirect
|
||||||
|
github.com/moby/sys/userns v0.1.0 // indirect
|
||||||
|
github.com/moby/term v0.5.2 // indirect
|
||||||
github.com/mr-tron/base58 v1.2.0 // indirect
|
github.com/mr-tron/base58 v1.2.0 // indirect
|
||||||
github.com/mschoch/smat v0.2.0 // indirect
|
github.com/mschoch/smat v0.2.0 // indirect
|
||||||
github.com/multiformats/go-multihash v0.2.3 // indirect
|
github.com/multiformats/go-multihash v0.2.3 // indirect
|
||||||
github.com/multiformats/go-varint v0.0.6 // indirect
|
github.com/multiformats/go-varint v0.0.6 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
|
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||||
github.com/pion/datachannel v1.5.9 // indirect
|
github.com/pion/datachannel v1.5.9 // indirect
|
||||||
github.com/pion/dtls/v3 v3.0.3 // indirect
|
github.com/pion/dtls/v3 v3.0.3 // indirect
|
||||||
github.com/pion/ice/v4 v4.0.2 // indirect
|
github.com/pion/ice/v4 v4.0.2 // indirect
|
||||||
@@ -76,6 +105,7 @@ require (
|
|||||||
github.com/pion/webrtc/v4 v4.0.0 // indirect
|
github.com/pion/webrtc/v4 v4.0.0 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||||
github.com/prometheus/client_model v0.6.2 // indirect
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
github.com/prometheus/common v0.66.1 // indirect
|
github.com/prometheus/common v0.66.1 // indirect
|
||||||
@@ -89,16 +119,24 @@ require (
|
|||||||
github.com/riverqueue/river/rivertype v0.35.1 // indirect
|
github.com/riverqueue/river/rivertype v0.35.1 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 // indirect
|
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 // indirect
|
||||||
|
github.com/shirou/gopsutil/v4 v4.26.3 // indirect
|
||||||
|
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
github.com/stretchr/testify v1.11.1 // indirect
|
github.com/stretchr/testify v1.11.1 // indirect
|
||||||
|
github.com/testcontainers/testcontainers-go v0.42.0 // indirect
|
||||||
|
github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0 // indirect
|
||||||
github.com/tidwall/btree v1.8.1 // indirect
|
github.com/tidwall/btree v1.8.1 // indirect
|
||||||
github.com/tidwall/gjson v1.18.0 // indirect
|
github.com/tidwall/gjson v1.18.0 // indirect
|
||||||
github.com/tidwall/match v1.2.0 // indirect
|
github.com/tidwall/match v1.2.0 // indirect
|
||||||
github.com/tidwall/pretty v1.2.1 // indirect
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
github.com/tidwall/sjson v1.2.5 // indirect
|
github.com/tidwall/sjson v1.2.5 // indirect
|
||||||
|
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||||
|
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||||
github.com/wlynxg/anet v0.0.3 // indirect
|
github.com/wlynxg/anet v0.0.3 // indirect
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
go.etcd.io/bbolt v1.3.6 // indirect
|
go.etcd.io/bbolt v1.3.6 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
go.opentelemetry.io/otel v1.43.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||||
|
|||||||
@@ -2,7 +2,13 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
|||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk=
|
crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk=
|
||||||
crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4=
|
crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4=
|
||||||
|
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||||
|
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w=
|
github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w=
|
||||||
github.com/RoaringBitmap/roaring v0.4.17/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrXIr8KVNvRsDi1NI=
|
github.com/RoaringBitmap/roaring v0.4.17/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrXIr8KVNvRsDi1NI=
|
||||||
@@ -89,16 +95,34 @@ github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2w
|
|||||||
github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
||||||
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8=
|
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8=
|
||||||
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og=
|
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||||
|
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||||
|
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||||
|
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||||
|
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||||
|
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||||
|
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
|
||||||
|
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
|
||||||
|
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
|
||||||
|
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
|
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||||
|
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||||
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||||
github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||||
@@ -106,8 +130,12 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn
|
|||||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||||
|
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
|
||||||
|
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ=
|
github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ=
|
||||||
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
|
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/frankban/quicktest v1.9.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
|
github.com/frankban/quicktest v1.9.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
|
||||||
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
@@ -130,6 +158,9 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
|||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
@@ -197,6 +228,8 @@ github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVY
|
|||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||||
|
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU=
|
github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU=
|
||||||
@@ -213,6 +246,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||||
|
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||||
|
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
@@ -220,6 +257,24 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
|||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
|
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
|
||||||
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
|
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
|
github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8=
|
||||||
|
github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU=
|
||||||
|
github.com/moby/moby/api v1.54.1 h1:TqVzuJkOLsgLDDwNLmYqACUuTehOHRGKiPhvH8V3Nn4=
|
||||||
|
github.com/moby/moby/api v1.54.1/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
|
||||||
|
github.com/moby/moby/client v0.4.0 h1:S+2XegzHQrrvTCvF6s5HFzcrywWQmuVnhOXe2kiWjIw=
|
||||||
|
github.com/moby/moby/client v0.4.0/go.mod h1:QWPbvWchQbxBNdaLSpoKpCdf5E+WxFAgNHogCWDoa7g=
|
||||||
|
github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U=
|
||||||
|
github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||||
|
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||||
|
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||||
|
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
||||||
|
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||||
|
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||||
|
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||||
|
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||||
|
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
@@ -239,6 +294,10 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
|
|||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
|
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||||
|
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||||
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
@@ -281,6 +340,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
@@ -333,8 +394,12 @@ github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417/go.mod h1:qe5TWALJ8/a1
|
|||||||
github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI=
|
github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI=
|
||||||
github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
||||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
|
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
|
||||||
|
github.com/shirou/gopsutil/v4 v4.26.3 h1:2ESdQt90yU3oXF/CdOlRCJxrP+Am1aBYubTMTfxJ1qc=
|
||||||
|
github.com/shirou/gopsutil/v4 v4.26.3/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
|
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||||
|
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||||
@@ -359,6 +424,10 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
|
|||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/testcontainers/testcontainers-go v0.42.0 h1:He3IhTzTZOygSXLJPMX7n44XtK+qhjat1nI9cneBbUY=
|
||||||
|
github.com/testcontainers/testcontainers-go v0.42.0/go.mod h1:vZjdY1YmUA1qEForxOIOazfsrdyORJAbhi0bp8plN30=
|
||||||
|
github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0 h1:GCbb1ndrF7OTDiIvxXyItaDab4qkzTFJ48LKFdM7EIo=
|
||||||
|
github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0/go.mod h1:IRPBaI8jXdrNfD0e4Zm7Fbcgaz5shKxOQv4axiL09xs=
|
||||||
github.com/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA=
|
github.com/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA=
|
||||||
github.com/tidwall/btree v1.8.1/go.mod h1:jBbTdUWhSZClZWoDg54VnvV7/54modSOzDN7VXftj1A=
|
github.com/tidwall/btree v1.8.1/go.mod h1:jBbTdUWhSZClZWoDg54VnvV7/54modSOzDN7VXftj1A=
|
||||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
@@ -375,12 +444,18 @@ github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6
|
|||||||
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
|
||||||
|
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
|
||||||
|
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
|
||||||
github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||||
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||||
github.com/wlynxg/anet v0.0.3 h1:PvR53psxFXstc12jelG6f1Lv4MWqE0tI76/hHGjh9rg=
|
github.com/wlynxg/anet v0.0.3 h1:PvR53psxFXstc12jelG6f1Lv4MWqE0tI76/hHGjh9rg=
|
||||||
github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
||||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||||
@@ -388,6 +463,8 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
|||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
|
||||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||||
@@ -462,17 +539,21 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ import (
|
|||||||
"homelab.lan/music-agregator/internal/config"
|
"homelab.lan/music-agregator/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Searcher interface {
|
||||||
|
Search(query string, limit int32, indexer string) (*SearchResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
type IndexerService struct {
|
type IndexerService struct {
|
||||||
indexer Indexer
|
indexer Indexer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ func NewMusicAgregatorServer(cfg config.Config, riverClient *river.Client[pgx.Tx
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewMusicAgregatorServerWithService(service *MusicAgregatorService) *MusicAgregatorServer {
|
||||||
|
return &MusicAgregatorServer{service: service}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *MusicAgregatorServer) GetArtists(ctx context.Context, req *pb.GetArtistsRequest) (*pb.GetArtistsResponse, error) {
|
func (s *MusicAgregatorServer) GetArtists(ctx context.Context, req *pb.GetArtistsRequest) (*pb.GetArtistsResponse, error) {
|
||||||
return s.service.GetArtists(ctx, req)
|
return s.service.GetArtists(ctx, req)
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-4
@@ -34,9 +34,9 @@ type parsedItem struct {
|
|||||||
type MusicAgregatorService struct {
|
type MusicAgregatorService struct {
|
||||||
config config.Config
|
config config.Config
|
||||||
metadata *metadata.MetadataService
|
metadata *metadata.MetadataService
|
||||||
indexer *indexer.IndexerService
|
indexer indexer.Searcher
|
||||||
torrentClient torrent.TorrentClient
|
torrentClient torrent.TorrentClient
|
||||||
magnetResolver *torrentParser.MagnetResolver
|
magnetResolver torrentParser.Resolver
|
||||||
riverClient *river.Client[pgx.Tx]
|
riverClient *river.Client[pgx.Tx]
|
||||||
torrents *database.TorrentRepository
|
torrents *database.TorrentRepository
|
||||||
downloads *database.DownloadRepository
|
downloads *database.DownloadRepository
|
||||||
@@ -83,9 +83,30 @@ func NewMusicAgregatorService(cfg config.Config, riverClient *river.Client[pgx.T
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewMusicAgregatorServiceWithDeps(
|
||||||
|
metadata *metadata.MetadataService,
|
||||||
|
searcher indexer.Searcher,
|
||||||
|
torrentClient torrent.TorrentClient,
|
||||||
|
magnetResolver torrentParser.Resolver,
|
||||||
|
riverClient *river.Client[pgx.Tx],
|
||||||
|
db *database.DB,
|
||||||
|
) *MusicAgregatorService {
|
||||||
|
return &MusicAgregatorService{
|
||||||
|
metadata: metadata,
|
||||||
|
indexer: searcher,
|
||||||
|
torrentClient: torrentClient,
|
||||||
|
magnetResolver: magnetResolver,
|
||||||
|
riverClient: riverClient,
|
||||||
|
torrents: database.NewTorrentRepository(db.Pool),
|
||||||
|
downloads: database.NewDownloadRepository(db.Pool),
|
||||||
|
artists: database.NewArtistRepository(db.Pool),
|
||||||
|
downloadFiles: database.NewDownloadFileRepository(db.Pool),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *MusicAgregatorService) Close() {
|
func (s *MusicAgregatorService) Close() {
|
||||||
if s.magnetResolver != nil {
|
if closer, ok := s.magnetResolver.(interface{ Close() }); ok {
|
||||||
s.magnetResolver.Close()
|
closer.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Resolver interface {
|
||||||
|
Resolve(magnetURI string) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
type MagnetResolver struct {
|
type MagnetResolver struct {
|
||||||
client *torrent.Client
|
client *torrent.Client
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
|
|||||||
@@ -106,6 +106,10 @@ func (w *PollDownloadWorker) onCompleted(ctx context.Context, args PollDownloadA
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *PollDownloadWorker) reschedule(ctx context.Context, args PollDownloadArgs) error {
|
func (w *PollDownloadWorker) reschedule(ctx context.Context, args PollDownloadArgs) error {
|
||||||
|
if w.RiverClient == nil {
|
||||||
|
log.Warn().Str("download_id", args.DownloadID).Msg("no river client, cannot reschedule poll_download")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
_, err := w.RiverClient.Insert(ctx, args, &river.InsertOpts{
|
_, err := w.RiverClient.Insert(ctx, args, &river.InsertOpts{
|
||||||
ScheduledAt: time.Now().Add(args.CheckInterval),
|
ScheduledAt: time.Now().Add(args.CheckInterval),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package component
|
||||||
|
|
||||||
|
import (
|
||||||
|
metadataPb "homelab.lan/music-agregator/gen/metadata/v1"
|
||||||
|
"homelab.lan/music-agregator/internal/indexer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newMetadataAlbum(id, title, artistID, artistName string) *metadataPb.Album {
|
||||||
|
return &metadataPb.Album{
|
||||||
|
Id: id,
|
||||||
|
Title: title,
|
||||||
|
AlbumType: "album",
|
||||||
|
ReleaseDate: "2024-01-15",
|
||||||
|
TotalTracks: 10,
|
||||||
|
TotalDiscs: 1,
|
||||||
|
CoverUrl: "https://example.com/cover.jpg",
|
||||||
|
Artists: []*metadataPb.ArtistCredit{
|
||||||
|
{
|
||||||
|
Artist: &metadataPb.Artist{
|
||||||
|
Id: artistID,
|
||||||
|
Name: artistName,
|
||||||
|
},
|
||||||
|
Role: "primary",
|
||||||
|
Position: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Label: &metadataPb.Label{
|
||||||
|
Id: "label-1",
|
||||||
|
Name: "Test Label",
|
||||||
|
},
|
||||||
|
Genres: []*metadataPb.Genre{
|
||||||
|
{Id: "genre-1", Name: "Rock"},
|
||||||
|
{Id: "genre-2", Name: "Alternative"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMetadataTrack(id, title string, trackNum int32) *metadataPb.Track {
|
||||||
|
return &metadataPb.Track{
|
||||||
|
Id: id,
|
||||||
|
Title: title,
|
||||||
|
DurationMs: 240000,
|
||||||
|
Isrc: "US-XYZ-24-00001",
|
||||||
|
Explicit: false,
|
||||||
|
DiscNumber: 1,
|
||||||
|
TrackNumber: trackNum,
|
||||||
|
Artists: []*metadataPb.ArtistCredit{
|
||||||
|
{
|
||||||
|
Artist: &metadataPb.Artist{
|
||||||
|
Id: "artist-1",
|
||||||
|
Name: "Test Artist",
|
||||||
|
},
|
||||||
|
Role: "primary",
|
||||||
|
Position: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSearchResponse(items ...*indexer.SearchItemResult) *indexer.SearchResponse {
|
||||||
|
return &indexer.SearchResponse{
|
||||||
|
Items: items,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSearchItem(title string, seeders int, downloadLink string) *indexer.SearchItemResult {
|
||||||
|
return &indexer.SearchItemResult{
|
||||||
|
Title: title,
|
||||||
|
DownloadLink: downloadLink,
|
||||||
|
Size: 500 * 1024 * 1024,
|
||||||
|
Tracker: "test-tracker",
|
||||||
|
Seeders: seeders,
|
||||||
|
Peers: seeders / 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTorrentData() []byte {
|
||||||
|
return []byte("d8:announce35:http://tracker.example.com/announce4:infod6:lengthi1024e4:name9:test.flac12:piece lengthi16384e6:pieces20:01234567890123456789ee")
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTorrentDataMP3() []byte {
|
||||||
|
return []byte("d8:announce35:http://tracker.example.com/announce4:infod6:lengthi1024e4:name8:test.mp312:piece lengthi16384e6:pieces20:01234567890123456789ee")
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
package component
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
metadataPb "homelab.lan/music-agregator/gen/metadata/v1"
|
||||||
|
"homelab.lan/music-agregator/internal/indexer"
|
||||||
|
"homelab.lan/music-agregator/internal/torrent"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockMetadataClient struct {
|
||||||
|
GetAlbumFunc func(ctx context.Context, in *metadataPb.GetAlbumRequest, opts ...grpc.CallOption) (*metadataPb.GetAlbumResponse, error)
|
||||||
|
GetArtistAlbumsFunc func(ctx context.Context, in *metadataPb.GetArtistAlbumsRequest, opts ...grpc.CallOption) (*metadataPb.GetArtistAlbumsResponse, error)
|
||||||
|
GetAlbumTracksFunc func(ctx context.Context, in *metadataPb.GetAlbumTracksRequest, opts ...grpc.CallOption) (*metadataPb.GetAlbumTracksResponse, error)
|
||||||
|
GetArtistFunc func(ctx context.Context, in *metadataPb.GetArtistRequest, opts ...grpc.CallOption) (*metadataPb.GetArtistResponse, error)
|
||||||
|
SearchArtistsFunc func(ctx context.Context, in *metadataPb.SearchArtistsRequest, opts ...grpc.CallOption) (*metadataPb.SearchArtistsResponse, error)
|
||||||
|
GetTrackFunc func(ctx context.Context, in *metadataPb.GetTrackRequest, opts ...grpc.CallOption) (*metadataPb.GetTrackResponse, error)
|
||||||
|
SearchAlbumsFunc func(ctx context.Context, in *metadataPb.SearchAlbumsRequest, opts ...grpc.CallOption) (*metadataPb.SearchAlbumsResponse, error)
|
||||||
|
SyncArtistFunc func(ctx context.Context, in *metadataPb.SyncArtistRequest, opts ...grpc.CallOption) (*metadataPb.SyncArtistResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockMetadataClient) GetAlbum(ctx context.Context, in *metadataPb.GetAlbumRequest, opts ...grpc.CallOption) (*metadataPb.GetAlbumResponse, error) {
|
||||||
|
if m.GetAlbumFunc != nil {
|
||||||
|
return m.GetAlbumFunc(ctx, in, opts...)
|
||||||
|
}
|
||||||
|
return nil, status.Error(codes.Unimplemented, "not mocked")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockMetadataClient) GetArtistAlbums(ctx context.Context, in *metadataPb.GetArtistAlbumsRequest, opts ...grpc.CallOption) (*metadataPb.GetArtistAlbumsResponse, error) {
|
||||||
|
if m.GetArtistAlbumsFunc != nil {
|
||||||
|
return m.GetArtistAlbumsFunc(ctx, in, opts...)
|
||||||
|
}
|
||||||
|
return nil, status.Error(codes.Unimplemented, "not mocked")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockMetadataClient) GetAlbumTracks(ctx context.Context, in *metadataPb.GetAlbumTracksRequest, opts ...grpc.CallOption) (*metadataPb.GetAlbumTracksResponse, error) {
|
||||||
|
if m.GetAlbumTracksFunc != nil {
|
||||||
|
return m.GetAlbumTracksFunc(ctx, in, opts...)
|
||||||
|
}
|
||||||
|
return nil, status.Error(codes.Unimplemented, "not mocked")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockMetadataClient) GetArtist(ctx context.Context, in *metadataPb.GetArtistRequest, opts ...grpc.CallOption) (*metadataPb.GetArtistResponse, error) {
|
||||||
|
if m.GetArtistFunc != nil {
|
||||||
|
return m.GetArtistFunc(ctx, in, opts...)
|
||||||
|
}
|
||||||
|
return nil, status.Error(codes.Unimplemented, "not mocked")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockMetadataClient) SearchArtists(ctx context.Context, in *metadataPb.SearchArtistsRequest, opts ...grpc.CallOption) (*metadataPb.SearchArtistsResponse, error) {
|
||||||
|
if m.SearchArtistsFunc != nil {
|
||||||
|
return m.SearchArtistsFunc(ctx, in, opts...)
|
||||||
|
}
|
||||||
|
return nil, status.Error(codes.Unimplemented, "not mocked")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockMetadataClient) GetTrack(ctx context.Context, in *metadataPb.GetTrackRequest, opts ...grpc.CallOption) (*metadataPb.GetTrackResponse, error) {
|
||||||
|
if m.GetTrackFunc != nil {
|
||||||
|
return m.GetTrackFunc(ctx, in, opts...)
|
||||||
|
}
|
||||||
|
return nil, status.Error(codes.Unimplemented, "not mocked")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockMetadataClient) SearchAlbums(ctx context.Context, in *metadataPb.SearchAlbumsRequest, opts ...grpc.CallOption) (*metadataPb.SearchAlbumsResponse, error) {
|
||||||
|
if m.SearchAlbumsFunc != nil {
|
||||||
|
return m.SearchAlbumsFunc(ctx, in, opts...)
|
||||||
|
}
|
||||||
|
return nil, status.Error(codes.Unimplemented, "not mocked")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockMetadataClient) SyncArtist(ctx context.Context, in *metadataPb.SyncArtistRequest, opts ...grpc.CallOption) (*metadataPb.SyncArtistResponse, error) {
|
||||||
|
if m.SyncArtistFunc != nil {
|
||||||
|
return m.SyncArtistFunc(ctx, in, opts...)
|
||||||
|
}
|
||||||
|
return nil, status.Error(codes.Unimplemented, "not mocked")
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockTorrentClient struct {
|
||||||
|
LoginFunc func(username, password string) (string, error)
|
||||||
|
ListFunc func() ([]torrent.TorrentInfo, error)
|
||||||
|
FindFunc func(opts torrent.FindOptions) ([]torrent.TorrentInfo, error)
|
||||||
|
AddTorrentFunc func(file torrent.TorrentFile) error
|
||||||
|
AddMagnetFunc func(magnetURI string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockTorrentClient) Login(username, password string) (string, error) {
|
||||||
|
if m.LoginFunc != nil {
|
||||||
|
return m.LoginFunc(username, password)
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("not mocked")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockTorrentClient) List() ([]torrent.TorrentInfo, error) {
|
||||||
|
if m.ListFunc != nil {
|
||||||
|
return m.ListFunc()
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("not mocked")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockTorrentClient) Find(opts torrent.FindOptions) ([]torrent.TorrentInfo, error) {
|
||||||
|
if m.FindFunc != nil {
|
||||||
|
return m.FindFunc(opts)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("not mocked")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockTorrentClient) AddTorrent(file torrent.TorrentFile) error {
|
||||||
|
if m.AddTorrentFunc != nil {
|
||||||
|
return m.AddTorrentFunc(file)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("not mocked")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockTorrentClient) AddMagnet(magnetURI string) error {
|
||||||
|
if m.AddMagnetFunc != nil {
|
||||||
|
return m.AddMagnetFunc(magnetURI)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("not mocked")
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockSearcher struct {
|
||||||
|
SearchFunc func(query string, limit int32, indexer string) (*indexer.SearchResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockSearcher) Search(query string, limit int32, idx string) (*indexer.SearchResponse, error) {
|
||||||
|
if m.SearchFunc != nil {
|
||||||
|
return m.SearchFunc(query, limit, idx)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("not mocked")
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockResolver struct {
|
||||||
|
ResolveFunc func(magnetURI string) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockResolver) Resolve(magnetURI string) ([]byte, error) {
|
||||||
|
if m.ResolveFunc != nil {
|
||||||
|
return m.ResolveFunc(magnetURI)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("not mocked")
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,174 @@
|
|||||||
|
package component
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/testcontainers/testcontainers-go"
|
||||||
|
"github.com/testcontainers/testcontainers-go/modules/postgres"
|
||||||
|
"github.com/testcontainers/testcontainers-go/wait"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
"google.golang.org/grpc/test/bufconn"
|
||||||
|
|
||||||
|
pb "homelab.lan/music-agregator/gen/music_agregator/v1"
|
||||||
|
"homelab.lan/music-agregator/internal"
|
||||||
|
"homelab.lan/music-agregator/internal/database"
|
||||||
|
"homelab.lan/music-agregator/internal/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
const bufSize = 1024 * 1024
|
||||||
|
|
||||||
|
type testMocks struct {
|
||||||
|
metadata *mockMetadataClient
|
||||||
|
torrent *mockTorrentClient
|
||||||
|
indexer *mockSearcher
|
||||||
|
magnet *mockResolver
|
||||||
|
}
|
||||||
|
|
||||||
|
type testSuite struct {
|
||||||
|
db *database.DB
|
||||||
|
grpcConn *grpc.ClientConn
|
||||||
|
client pb.MusicAgregatorServiceClient
|
||||||
|
pool *pgxpool.Pool
|
||||||
|
mocks *testMocks
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupSuite(t *testing.T) *testSuite {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
schemaPath := getSchemaPath(t)
|
||||||
|
schemaSQL, err := os.ReadFile(schemaPath)
|
||||||
|
require.NoError(t, err, "failed to read schema file")
|
||||||
|
|
||||||
|
pgContainer, err := postgres.Run(ctx,
|
||||||
|
"postgres:16-alpine",
|
||||||
|
postgres.WithDatabase("music_agregator_test"),
|
||||||
|
postgres.WithUsername("test"),
|
||||||
|
postgres.WithPassword("test"),
|
||||||
|
postgres.WithInitScripts(),
|
||||||
|
testcontainers.WithWaitStrategy(
|
||||||
|
wait.ForLog("database system is ready to accept connections").
|
||||||
|
WithOccurrence(2).
|
||||||
|
WithStartupTimeout(30*time.Second),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
require.NoError(t, err, "failed to start postgres container")
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := pgContainer.Terminate(ctx); err != nil {
|
||||||
|
t.Logf("failed to terminate postgres container: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
connStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable")
|
||||||
|
require.NoError(t, err, "failed to get connection string")
|
||||||
|
|
||||||
|
pool, err := pgxpool.New(ctx, connStr)
|
||||||
|
require.NoError(t, err, "failed to create pgxpool")
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
pool.Close()
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err = pool.Exec(ctx, string(schemaSQL))
|
||||||
|
require.NoError(t, err, "failed to apply schema")
|
||||||
|
|
||||||
|
db := &database.DB{Pool: pool}
|
||||||
|
|
||||||
|
mocks := &testMocks{
|
||||||
|
metadata: &mockMetadataClient{},
|
||||||
|
torrent: &mockTorrentClient{},
|
||||||
|
indexer: &mockSearcher{},
|
||||||
|
magnet: &mockResolver{},
|
||||||
|
}
|
||||||
|
|
||||||
|
metadataSvc := metadata.NewMetadataService(mocks.metadata, db)
|
||||||
|
|
||||||
|
service := internal.NewMusicAgregatorServiceWithDeps(
|
||||||
|
metadataSvc,
|
||||||
|
mocks.indexer,
|
||||||
|
mocks.torrent,
|
||||||
|
mocks.magnet,
|
||||||
|
nil,
|
||||||
|
db,
|
||||||
|
)
|
||||||
|
|
||||||
|
server := internal.NewMusicAgregatorServerWithService(service)
|
||||||
|
|
||||||
|
lis := bufconn.Listen(bufSize)
|
||||||
|
grpcServer := grpc.NewServer()
|
||||||
|
server.Register(grpcServer)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := grpcServer.Serve(lis); err != nil {
|
||||||
|
t.Logf("grpc server error: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
grpcServer.GracefulStop()
|
||||||
|
})
|
||||||
|
|
||||||
|
conn, err := grpc.NewClient(
|
||||||
|
"passthrough://bufnet",
|
||||||
|
grpc.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) {
|
||||||
|
return lis.DialContext(ctx)
|
||||||
|
}),
|
||||||
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||||
|
)
|
||||||
|
require.NoError(t, err, "failed to create grpc client connection")
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
conn.Close()
|
||||||
|
})
|
||||||
|
|
||||||
|
client := pb.NewMusicAgregatorServiceClient(conn)
|
||||||
|
|
||||||
|
return &testSuite{
|
||||||
|
db: db,
|
||||||
|
grpcConn: conn,
|
||||||
|
client: client,
|
||||||
|
pool: pool,
|
||||||
|
mocks: mocks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSchemaPath(t *testing.T) string {
|
||||||
|
_, currentFile, _, ok := runtime.Caller(0)
|
||||||
|
require.True(t, ok, "failed to get current file path")
|
||||||
|
|
||||||
|
testDir := filepath.Dir(currentFile)
|
||||||
|
schemaPath := filepath.Join(testDir, "..", "..", "..", "containers", "database", "music-agregator", "002_schema.sql")
|
||||||
|
|
||||||
|
if _, err := os.Stat(schemaPath); os.IsNotExist(err) {
|
||||||
|
schemaPath = filepath.Join(testDir, "..", "..", "containers", "database", "music-agregator", "002_schema.sql")
|
||||||
|
}
|
||||||
|
|
||||||
|
return schemaPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanTables(t *testing.T, pool *pgxpool.Pool) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
tables := []string{
|
||||||
|
"download_files",
|
||||||
|
"downloads",
|
||||||
|
"torrents",
|
||||||
|
"tracks",
|
||||||
|
"albums",
|
||||||
|
"artists",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, table := range tables {
|
||||||
|
_, err := pool.Exec(ctx, "TRUNCATE TABLE "+table+" CASCADE")
|
||||||
|
require.NoError(t, err, "failed to truncate table %s", table)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user