1134 lines
38 KiB
Go
1134 lines
38 KiB
Go
package component
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/riverqueue/river"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
|
|
metadataPb "homelab.lan/music-agregator/gen/metadata/v1"
|
|
pb "homelab.lan/music-agregator/gen/music_agregator/v1"
|
|
"homelab.lan/music-agregator/internal/database"
|
|
"homelab.lan/music-agregator/internal/indexer"
|
|
"homelab.lan/music-agregator/internal/torrent"
|
|
"homelab.lan/music-agregator/internal/workers"
|
|
)
|
|
|
|
func TestMonitorAlbum_HappyPath(t *testing.T) {
|
|
suite := setupSuite(t)
|
|
cleanTables(t, suite.pool)
|
|
|
|
suite.mocks.metadata.GetAlbumFunc = func(ctx context.Context, in *metadataPb.GetAlbumRequest, opts ...grpc.CallOption) (*metadataPb.GetAlbumResponse, error) {
|
|
return &metadataPb.GetAlbumResponse{
|
|
Album: newMetadataAlbum("test-album-ext-id", "Test Album", "artist-ext-id", "Test Artist"),
|
|
}, nil
|
|
}
|
|
|
|
suite.mocks.indexer.SearchFunc = func(query string, limit int32, idx string) (*indexer.SearchResponse, error) {
|
|
return newSearchResponse(
|
|
newSearchItem("Test Artist - Test Album [FLAC]", 50, "magnet:?xt=urn:btih:abc123&dn=test"),
|
|
newSearchItem("Test Artist - Test Album [MP3 320]", 10, "magnet:?xt=urn:btih:def456&dn=test"),
|
|
), nil
|
|
}
|
|
|
|
suite.mocks.magnet.ResolveFunc = func(magnetURI string) ([]byte, error) {
|
|
return newTorrentData(), nil
|
|
}
|
|
|
|
suite.mocks.torrent.FindFunc = func(opts torrent.FindOptions) ([]torrent.TorrentInfo, error) {
|
|
return []torrent.TorrentInfo{}, nil
|
|
}
|
|
|
|
suite.mocks.torrent.AddMagnetFunc = func(magnetURI string, savePath string) error {
|
|
return nil
|
|
}
|
|
|
|
ctx := context.Background()
|
|
resp, err := suite.client.MonitorAlbum(ctx, &pb.MonitorAlbumRequest{
|
|
AlbumId: "test-album-ext-id",
|
|
Quality: pb.QualityType_QUALITY_LOSSLESS,
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp.Album)
|
|
assert.NotEmpty(t, resp.Album.Id)
|
|
assert.Equal(t, "Test Album", resp.Album.Title)
|
|
assert.Equal(t, pb.MonitorState_MONITOR_STATE_MONITORED, resp.Album.MonitorState)
|
|
require.NotNil(t, resp.Artist)
|
|
assert.NotEmpty(t, resp.Artist.Id)
|
|
assert.Equal(t, "Test Artist", resp.Artist.Name)
|
|
assert.Equal(t, pb.MonitorState_MONITOR_STATE_MONITORED, resp.Artist.MonitorState)
|
|
require.NotNil(t, resp.Release)
|
|
assert.NotEmpty(t, resp.Release.InfoHash)
|
|
assert.Equal(t, int32(50), resp.Release.Seeders)
|
|
|
|
var artistMonitorState string
|
|
err = suite.pool.QueryRow(ctx, "SELECT monitor_state FROM artists WHERE external_id = $1", "artist-ext-id").Scan(&artistMonitorState)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "monitored", artistMonitorState)
|
|
|
|
var albumMonitorState string
|
|
err = suite.pool.QueryRow(ctx, "SELECT monitor_state FROM albums WHERE external_id = $1", "test-album-ext-id").Scan(&albumMonitorState)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "monitored", albumMonitorState)
|
|
|
|
var torrentCount int
|
|
err = suite.pool.QueryRow(ctx, "SELECT COUNT(*) FROM torrents").Scan(&torrentCount)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, torrentCount)
|
|
|
|
var downloadState string
|
|
err = suite.pool.QueryRow(ctx, "SELECT state FROM downloads").Scan(&downloadState)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "downloading", downloadState)
|
|
}
|
|
|
|
func TestMonitorAlbum_MetadataUnavailable(t *testing.T) {
|
|
suite := setupSuite(t)
|
|
cleanTables(t, suite.pool)
|
|
|
|
suite.mocks.metadata.GetAlbumFunc = func(ctx context.Context, in *metadataPb.GetAlbumRequest, opts ...grpc.CallOption) (*metadataPb.GetAlbumResponse, error) {
|
|
return nil, status.Error(codes.Unavailable, "connection refused")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
_, err := suite.client.MonitorAlbum(ctx, &pb.MonitorAlbumRequest{
|
|
AlbumId: "test-album-ext-id",
|
|
Quality: pb.QualityType_QUALITY_LOSSLESS,
|
|
})
|
|
|
|
require.Error(t, err)
|
|
|
|
var count int
|
|
err = suite.pool.QueryRow(ctx, "SELECT COUNT(*) FROM artists").Scan(&count)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, count)
|
|
|
|
err = suite.pool.QueryRow(ctx, "SELECT COUNT(*) FROM albums").Scan(&count)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, count)
|
|
|
|
err = suite.pool.QueryRow(ctx, "SELECT COUNT(*) FROM torrents").Scan(&count)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, count)
|
|
|
|
err = suite.pool.QueryRow(ctx, "SELECT COUNT(*) FROM downloads").Scan(&count)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, count)
|
|
}
|
|
|
|
func TestMonitorAlbum_MetadataNotFound(t *testing.T) {
|
|
suite := setupSuite(t)
|
|
cleanTables(t, suite.pool)
|
|
|
|
suite.mocks.metadata.GetAlbumFunc = func(ctx context.Context, in *metadataPb.GetAlbumRequest, opts ...grpc.CallOption) (*metadataPb.GetAlbumResponse, error) {
|
|
return nil, status.Error(codes.NotFound, "album not found")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
_, err := suite.client.MonitorAlbum(ctx, &pb.MonitorAlbumRequest{
|
|
AlbumId: "nonexistent-album",
|
|
Quality: pb.QualityType_QUALITY_LOSSLESS,
|
|
})
|
|
|
|
require.Error(t, err)
|
|
|
|
var count int
|
|
err = suite.pool.QueryRow(ctx, "SELECT COUNT(*) FROM artists").Scan(&count)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, count)
|
|
|
|
err = suite.pool.QueryRow(ctx, "SELECT COUNT(*) FROM albums").Scan(&count)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, count)
|
|
}
|
|
|
|
func TestMonitorAlbum_ArtistPersistFails(t *testing.T) {
|
|
suite := setupSuite(t)
|
|
cleanTables(t, suite.pool)
|
|
|
|
suite.mocks.metadata.GetAlbumFunc = func(ctx context.Context, in *metadataPb.GetAlbumRequest, opts ...grpc.CallOption) (*metadataPb.GetAlbumResponse, error) {
|
|
return &metadataPb.GetAlbumResponse{
|
|
Album: &metadataPb.Album{
|
|
Id: "orphan-album-ext-id",
|
|
Title: "Orphan Album",
|
|
AlbumType: "album",
|
|
ReleaseDate: "2024-01-15",
|
|
TotalTracks: 10,
|
|
Artists: []*metadataPb.ArtistCredit{},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
suite.mocks.indexer.SearchFunc = func(query string, limit int32, idx string) (*indexer.SearchResponse, error) {
|
|
return newSearchResponse(
|
|
newSearchItem("Orphan Album [FLAC]", 50, "magnet:?xt=urn:btih:abc123&dn=test"),
|
|
), nil
|
|
}
|
|
|
|
suite.mocks.magnet.ResolveFunc = func(magnetURI string) ([]byte, error) {
|
|
return newTorrentData(), nil
|
|
}
|
|
|
|
suite.mocks.torrent.FindFunc = func(opts torrent.FindOptions) ([]torrent.TorrentInfo, error) {
|
|
return []torrent.TorrentInfo{}, nil
|
|
}
|
|
|
|
suite.mocks.torrent.AddMagnetFunc = func(magnetURI string, savePath string) error {
|
|
return nil
|
|
}
|
|
|
|
ctx := context.Background()
|
|
resp, err := suite.client.MonitorAlbum(ctx, &pb.MonitorAlbumRequest{
|
|
AlbumId: "orphan-album-ext-id",
|
|
Quality: pb.QualityType_QUALITY_LOSSLESS,
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp.Release)
|
|
assert.Nil(t, resp.Album)
|
|
|
|
var count int
|
|
err = suite.pool.QueryRow(ctx, "SELECT COUNT(*) FROM downloads").Scan(&count)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, count)
|
|
|
|
err = suite.pool.QueryRow(ctx, "SELECT COUNT(*) FROM torrents").Scan(&count)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, count)
|
|
}
|
|
|
|
func TestMonitorAlbum_AlreadyOwned(t *testing.T) {
|
|
suite := setupSuite(t)
|
|
cleanTables(t, suite.pool)
|
|
|
|
ctx := context.Background()
|
|
|
|
var artistID string
|
|
err := suite.pool.QueryRow(ctx, `
|
|
INSERT INTO artists (external_id, name, artist_type, country, genres, image_url, monitor_state)
|
|
VALUES ('artist-ext-id', 'Test Artist', 'person', 'US', ARRAY['Rock'], 'http://img.com', 'monitored')
|
|
RETURNING id
|
|
`).Scan(&artistID)
|
|
require.NoError(t, err)
|
|
|
|
var albumID string
|
|
err = suite.pool.QueryRow(ctx, `
|
|
INSERT INTO albums (external_id, artist_id, title, album_type, total_tracks, total_discs, label, genres, cover_url, monitor_state)
|
|
VALUES ('owned-album-ext-id', $1, 'Owned Album', 'album', 10, 1, 'Test Label', ARRAY['Rock'], 'http://cover.com', 'monitored')
|
|
RETURNING id
|
|
`, artistID).Scan(&albumID)
|
|
require.NoError(t, err)
|
|
|
|
var torrentID string
|
|
err = suite.pool.QueryRow(ctx, `
|
|
INSERT INTO torrents (album_id, info_hash, tracker, title, format, quality, source, seeders, peers, size)
|
|
VALUES ($1, 'existing-hash', 'test-tracker', 'Owned Album FLAC', 'FLAC', '16-44', 'CD', 100, 50, 500000000)
|
|
RETURNING id
|
|
`, albumID).Scan(&torrentID)
|
|
require.NoError(t, err)
|
|
|
|
_, err = suite.pool.Exec(ctx, `
|
|
INSERT INTO downloads (torrent_id, album_id, format, quality, state, qbit_hash, save_path)
|
|
VALUES ($1, $2, 'QUALITY_LOSSLESS', '16-44', 'completed', 'existing-hash', '/music/owned')
|
|
`, torrentID, albumID)
|
|
require.NoError(t, err)
|
|
|
|
suite.mocks.metadata.GetAlbumFunc = func(ctx context.Context, in *metadataPb.GetAlbumRequest, opts ...grpc.CallOption) (*metadataPb.GetAlbumResponse, error) {
|
|
return &metadataPb.GetAlbumResponse{
|
|
Album: newMetadataAlbum("owned-album-ext-id", "Owned Album", "artist-ext-id", "Test Artist"),
|
|
}, nil
|
|
}
|
|
|
|
var indexerCalled bool
|
|
suite.mocks.indexer.SearchFunc = func(query string, limit int32, idx string) (*indexer.SearchResponse, error) {
|
|
indexerCalled = true
|
|
return newSearchResponse(), nil
|
|
}
|
|
|
|
resp, err := suite.client.MonitorAlbum(ctx, &pb.MonitorAlbumRequest{
|
|
AlbumId: "owned-album-ext-id",
|
|
Quality: pb.QualityType_QUALITY_LOSSLESS,
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp.Album)
|
|
assert.Equal(t, pb.MonitorState_MONITOR_STATE_MONITORED, resp.Album.MonitorState)
|
|
require.NotNil(t, resp.Album.Download)
|
|
assert.Equal(t, "completed", resp.Album.Download.State)
|
|
assert.Nil(t, resp.Release)
|
|
assert.False(t, indexerCalled)
|
|
|
|
var downloadCount int
|
|
err = suite.pool.QueryRow(ctx, "SELECT COUNT(*) FROM downloads").Scan(&downloadCount)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, downloadCount)
|
|
}
|
|
|
|
func TestMonitorAlbum_IndexerDown(t *testing.T) {
|
|
suite := setupSuite(t)
|
|
cleanTables(t, suite.pool)
|
|
|
|
suite.mocks.metadata.GetAlbumFunc = func(ctx context.Context, in *metadataPb.GetAlbumRequest, opts ...grpc.CallOption) (*metadataPb.GetAlbumResponse, error) {
|
|
return &metadataPb.GetAlbumResponse{
|
|
Album: newMetadataAlbum("test-album-ext-id", "Test Album", "artist-ext-id", "Test Artist"),
|
|
}, nil
|
|
}
|
|
|
|
suite.mocks.indexer.SearchFunc = func(query string, limit int32, idx string) (*indexer.SearchResponse, error) {
|
|
return nil, assert.AnError
|
|
}
|
|
|
|
ctx := context.Background()
|
|
_, err := suite.client.MonitorAlbum(ctx, &pb.MonitorAlbumRequest{
|
|
AlbumId: "test-album-ext-id",
|
|
Quality: pb.QualityType_QUALITY_LOSSLESS,
|
|
})
|
|
|
|
require.Error(t, err)
|
|
|
|
var artistMonitorState string
|
|
err = suite.pool.QueryRow(ctx, "SELECT monitor_state FROM artists WHERE external_id = $1", "artist-ext-id").Scan(&artistMonitorState)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "monitored", artistMonitorState)
|
|
|
|
var albumMonitorState string
|
|
err = suite.pool.QueryRow(ctx, "SELECT monitor_state FROM albums WHERE external_id = $1", "test-album-ext-id").Scan(&albumMonitorState)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "monitored", albumMonitorState)
|
|
}
|
|
|
|
func TestMonitorAlbum_IndexerNoResults(t *testing.T) {
|
|
suite := setupSuite(t)
|
|
cleanTables(t, suite.pool)
|
|
|
|
suite.mocks.metadata.GetAlbumFunc = func(ctx context.Context, in *metadataPb.GetAlbumRequest, opts ...grpc.CallOption) (*metadataPb.GetAlbumResponse, error) {
|
|
return &metadataPb.GetAlbumResponse{
|
|
Album: newMetadataAlbum("test-album-ext-id", "Test Album", "artist-ext-id", "Test Artist"),
|
|
}, nil
|
|
}
|
|
|
|
suite.mocks.indexer.SearchFunc = func(query string, limit int32, idx string) (*indexer.SearchResponse, error) {
|
|
return newSearchResponse(), nil
|
|
}
|
|
|
|
ctx := context.Background()
|
|
resp, err := suite.client.MonitorAlbum(ctx, &pb.MonitorAlbumRequest{
|
|
AlbumId: "test-album-ext-id",
|
|
Quality: pb.QualityType_QUALITY_LOSSLESS,
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp.Album)
|
|
require.NotNil(t, resp.Artist)
|
|
assert.Nil(t, resp.Release)
|
|
|
|
var count int
|
|
err = suite.pool.QueryRow(ctx, "SELECT COUNT(*) FROM torrents").Scan(&count)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, count)
|
|
|
|
err = suite.pool.QueryRow(ctx, "SELECT COUNT(*) FROM downloads").Scan(&count)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, count)
|
|
}
|
|
|
|
func TestMonitorAlbum_AllSeedersZero(t *testing.T) {
|
|
suite := setupSuite(t)
|
|
cleanTables(t, suite.pool)
|
|
|
|
suite.mocks.metadata.GetAlbumFunc = func(ctx context.Context, in *metadataPb.GetAlbumRequest, opts ...grpc.CallOption) (*metadataPb.GetAlbumResponse, error) {
|
|
return &metadataPb.GetAlbumResponse{
|
|
Album: newMetadataAlbum("test-album-ext-id", "Test Album", "artist-ext-id", "Test Artist"),
|
|
}, nil
|
|
}
|
|
|
|
suite.mocks.indexer.SearchFunc = func(query string, limit int32, idx string) (*indexer.SearchResponse, error) {
|
|
return newSearchResponse(
|
|
newSearchItem("Test Album FLAC 1", 0, "magnet:?xt=urn:btih:abc1"),
|
|
newSearchItem("Test Album FLAC 2", 0, "magnet:?xt=urn:btih:abc2"),
|
|
newSearchItem("Test Album FLAC 3", 0, "magnet:?xt=urn:btih:abc3"),
|
|
), nil
|
|
}
|
|
|
|
ctx := context.Background()
|
|
resp, err := suite.client.MonitorAlbum(ctx, &pb.MonitorAlbumRequest{
|
|
AlbumId: "test-album-ext-id",
|
|
Quality: pb.QualityType_QUALITY_LOSSLESS,
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp.Album)
|
|
require.NotNil(t, resp.Artist)
|
|
assert.Nil(t, resp.Release)
|
|
}
|
|
|
|
func TestMonitorAlbum_AllMagnetsFail(t *testing.T) {
|
|
suite := setupSuite(t)
|
|
cleanTables(t, suite.pool)
|
|
|
|
suite.mocks.metadata.GetAlbumFunc = func(ctx context.Context, in *metadataPb.GetAlbumRequest, opts ...grpc.CallOption) (*metadataPb.GetAlbumResponse, error) {
|
|
return &metadataPb.GetAlbumResponse{
|
|
Album: newMetadataAlbum("test-album-ext-id", "Test Album", "artist-ext-id", "Test Artist"),
|
|
}, nil
|
|
}
|
|
|
|
suite.mocks.indexer.SearchFunc = func(query string, limit int32, idx string) (*indexer.SearchResponse, error) {
|
|
return newSearchResponse(
|
|
newSearchItem("Test Album 1", 50, "magnet:?xt=urn:btih:abc1"),
|
|
newSearchItem("Test Album 2", 30, "magnet:?xt=urn:btih:abc2"),
|
|
), nil
|
|
}
|
|
|
|
suite.mocks.magnet.ResolveFunc = func(magnetURI string) ([]byte, error) {
|
|
return nil, assert.AnError
|
|
}
|
|
|
|
ctx := context.Background()
|
|
resp, err := suite.client.MonitorAlbum(ctx, &pb.MonitorAlbumRequest{
|
|
AlbumId: "test-album-ext-id",
|
|
Quality: pb.QualityType_QUALITY_LOSSLESS,
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp.Album)
|
|
require.NotNil(t, resp.Artist)
|
|
assert.Nil(t, resp.Release)
|
|
}
|
|
|
|
func TestMonitorAlbum_NoQualityMatch(t *testing.T) {
|
|
suite := setupSuite(t)
|
|
cleanTables(t, suite.pool)
|
|
|
|
suite.mocks.metadata.GetAlbumFunc = func(ctx context.Context, in *metadataPb.GetAlbumRequest, opts ...grpc.CallOption) (*metadataPb.GetAlbumResponse, error) {
|
|
return &metadataPb.GetAlbumResponse{
|
|
Album: newMetadataAlbum("test-album-ext-id", "Test Album", "artist-ext-id", "Test Artist"),
|
|
}, nil
|
|
}
|
|
|
|
suite.mocks.indexer.SearchFunc = func(query string, limit int32, idx string) (*indexer.SearchResponse, error) {
|
|
return newSearchResponse(
|
|
newSearchItem("Test Album MP3 320", 50, "magnet:?xt=urn:btih:abc1"),
|
|
newSearchItem("Test Album MP3 V0", 30, "magnet:?xt=urn:btih:abc2"),
|
|
), nil
|
|
}
|
|
|
|
suite.mocks.magnet.ResolveFunc = func(magnetURI string) ([]byte, error) {
|
|
return newTorrentDataMP3(), nil
|
|
}
|
|
|
|
ctx := context.Background()
|
|
resp, err := suite.client.MonitorAlbum(ctx, &pb.MonitorAlbumRequest{
|
|
AlbumId: "test-album-ext-id",
|
|
Quality: pb.QualityType_QUALITY_LOSSLESS,
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp.Album)
|
|
require.NotNil(t, resp.Artist)
|
|
assert.Nil(t, resp.Release)
|
|
}
|
|
|
|
func TestMonitorAlbum_QBitDown(t *testing.T) {
|
|
suite := setupSuite(t)
|
|
cleanTables(t, suite.pool)
|
|
|
|
suite.mocks.metadata.GetAlbumFunc = func(ctx context.Context, in *metadataPb.GetAlbumRequest, opts ...grpc.CallOption) (*metadataPb.GetAlbumResponse, error) {
|
|
return &metadataPb.GetAlbumResponse{
|
|
Album: newMetadataAlbum("test-album-ext-id", "Test Album", "artist-ext-id", "Test Artist"),
|
|
}, nil
|
|
}
|
|
|
|
suite.mocks.indexer.SearchFunc = func(query string, limit int32, idx string) (*indexer.SearchResponse, error) {
|
|
return newSearchResponse(
|
|
newSearchItem("Test Album [FLAC]", 50, "magnet:?xt=urn:btih:abc123"),
|
|
), nil
|
|
}
|
|
|
|
suite.mocks.magnet.ResolveFunc = func(magnetURI string) ([]byte, error) {
|
|
return newTorrentData(), nil
|
|
}
|
|
|
|
suite.mocks.torrent.FindFunc = func(opts torrent.FindOptions) ([]torrent.TorrentInfo, error) {
|
|
return nil, assert.AnError
|
|
}
|
|
|
|
suite.mocks.torrent.AddMagnetFunc = func(magnetURI string, savePath string) error {
|
|
return assert.AnError
|
|
}
|
|
|
|
ctx := context.Background()
|
|
_, err := suite.client.MonitorAlbum(ctx, &pb.MonitorAlbumRequest{
|
|
AlbumId: "test-album-ext-id",
|
|
Quality: pb.QualityType_QUALITY_LOSSLESS,
|
|
})
|
|
|
|
require.Error(t, err)
|
|
|
|
var artistCount int
|
|
err = suite.pool.QueryRow(ctx, "SELECT COUNT(*) FROM artists").Scan(&artistCount)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, artistCount)
|
|
|
|
var albumCount int
|
|
err = suite.pool.QueryRow(ctx, "SELECT COUNT(*) FROM albums").Scan(&albumCount)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, albumCount)
|
|
|
|
var torrentCount int
|
|
err = suite.pool.QueryRow(ctx, "SELECT COUNT(*) FROM torrents").Scan(&torrentCount)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, torrentCount)
|
|
|
|
var downloadCount int
|
|
err = suite.pool.QueryRow(ctx, "SELECT COUNT(*) FROM downloads").Scan(&downloadCount)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, downloadCount)
|
|
}
|
|
|
|
func TestMonitorAlbum_TorrentAlreadyExists(t *testing.T) {
|
|
suite := setupSuite(t)
|
|
cleanTables(t, suite.pool)
|
|
|
|
ctx := context.Background()
|
|
|
|
var artistID string
|
|
err := suite.pool.QueryRow(ctx, `
|
|
INSERT INTO artists (external_id, name, artist_type, country, genres, image_url, monitor_state)
|
|
VALUES ('artist-ext-id', 'Test Artist', 'person', 'US', ARRAY['Rock'], 'http://img.com', 'monitored')
|
|
RETURNING id
|
|
`).Scan(&artistID)
|
|
require.NoError(t, err)
|
|
|
|
var albumID string
|
|
err = suite.pool.QueryRow(ctx, `
|
|
INSERT INTO albums (external_id, artist_id, title, album_type, total_tracks, total_discs, label, genres, cover_url, monitor_state)
|
|
VALUES ('test-album-ext-id', $1, 'Test Album', 'album', 10, 1, 'Test Label', ARRAY['Rock'], 'http://cover.com', 'unmonitored')
|
|
RETURNING id
|
|
`, artistID).Scan(&albumID)
|
|
require.NoError(t, err)
|
|
|
|
torrentHash := "6ff7af15d0745a3e29d1b9620191cfe01ad3cc70"
|
|
|
|
var torrentID string
|
|
err = suite.pool.QueryRow(ctx, `
|
|
INSERT INTO torrents (album_id, info_hash, tracker, title, format, quality, source, seeders, peers, size)
|
|
VALUES ($1, $2, 'test-tracker', 'Test Album FLAC', 'FLAC', '16-44', 'CD', 100, 50, 500000000)
|
|
RETURNING id
|
|
`, albumID, torrentHash).Scan(&torrentID)
|
|
require.NoError(t, err)
|
|
|
|
_, err = suite.pool.Exec(ctx, `
|
|
INSERT INTO downloads (torrent_id, album_id, format, quality, state, qbit_hash)
|
|
VALUES ($1, $2, 'FLAC', '16-44', 'completed', $3)
|
|
`, torrentID, albumID, torrentHash)
|
|
require.NoError(t, err)
|
|
|
|
suite.mocks.metadata.GetAlbumFunc = func(ctx context.Context, in *metadataPb.GetAlbumRequest, opts ...grpc.CallOption) (*metadataPb.GetAlbumResponse, error) {
|
|
return &metadataPb.GetAlbumResponse{
|
|
Album: newMetadataAlbum("test-album-ext-id", "Test Album", "artist-ext-id", "Test Artist"),
|
|
}, nil
|
|
}
|
|
|
|
suite.mocks.indexer.SearchFunc = func(query string, limit int32, idx string) (*indexer.SearchResponse, error) {
|
|
return newSearchResponse(
|
|
newSearchItem("Test Album FLAC", 50, "magnet:?xt=urn:btih:"+torrentHash),
|
|
), nil
|
|
}
|
|
|
|
suite.mocks.magnet.ResolveFunc = func(magnetURI string) ([]byte, error) {
|
|
return newTorrentData(), nil
|
|
}
|
|
|
|
suite.mocks.torrent.FindFunc = func(opts torrent.FindOptions) ([]torrent.TorrentInfo, error) {
|
|
return []torrent.TorrentInfo{{State: "stalledUP"}}, nil
|
|
}
|
|
|
|
resp, err := suite.client.MonitorAlbum(ctx, &pb.MonitorAlbumRequest{
|
|
AlbumId: "test-album-ext-id",
|
|
Quality: pb.QualityType_QUALITY_LOSSLESS,
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp.Release)
|
|
|
|
var downloadCount int
|
|
err = suite.pool.QueryRow(ctx, "SELECT COUNT(*) FROM downloads").Scan(&downloadCount)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, downloadCount)
|
|
}
|
|
|
|
func TestMonitorAlbum_AddMagnetFails(t *testing.T) {
|
|
suite := setupSuite(t)
|
|
cleanTables(t, suite.pool)
|
|
|
|
suite.mocks.metadata.GetAlbumFunc = func(ctx context.Context, in *metadataPb.GetAlbumRequest, opts ...grpc.CallOption) (*metadataPb.GetAlbumResponse, error) {
|
|
return &metadataPb.GetAlbumResponse{
|
|
Album: newMetadataAlbum("test-album-ext-id", "Test Album", "artist-ext-id", "Test Artist"),
|
|
}, nil
|
|
}
|
|
|
|
suite.mocks.indexer.SearchFunc = func(query string, limit int32, idx string) (*indexer.SearchResponse, error) {
|
|
return newSearchResponse(
|
|
newSearchItem("Test Album FLAC", 50, "magnet:?xt=urn:btih:abc123"),
|
|
), nil
|
|
}
|
|
|
|
suite.mocks.magnet.ResolveFunc = func(magnetURI string) ([]byte, error) {
|
|
return newTorrentData(), nil
|
|
}
|
|
|
|
suite.mocks.torrent.FindFunc = func(opts torrent.FindOptions) ([]torrent.TorrentInfo, error) {
|
|
return []torrent.TorrentInfo{}, nil
|
|
}
|
|
|
|
suite.mocks.torrent.AddMagnetFunc = func(magnetURI string, savePath string) error {
|
|
return assert.AnError
|
|
}
|
|
|
|
ctx := context.Background()
|
|
_, err := suite.client.MonitorAlbum(ctx, &pb.MonitorAlbumRequest{
|
|
AlbumId: "test-album-ext-id",
|
|
Quality: pb.QualityType_QUALITY_LOSSLESS,
|
|
})
|
|
|
|
require.Error(t, err)
|
|
|
|
var albumCount int
|
|
err = suite.pool.QueryRow(ctx, "SELECT COUNT(*) FROM albums").Scan(&albumCount)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, albumCount)
|
|
|
|
var downloadCount int
|
|
err = suite.pool.QueryRow(ctx, "SELECT COUNT(*) FROM downloads").Scan(&downloadCount)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, downloadCount)
|
|
}
|
|
|
|
func TestMonitorAlbum_DuplicateDownloadSkipped(t *testing.T) {
|
|
suite := setupSuite(t)
|
|
cleanTables(t, suite.pool)
|
|
|
|
ctx := context.Background()
|
|
|
|
var artistID string
|
|
err := suite.pool.QueryRow(ctx, `
|
|
INSERT INTO artists (external_id, name, artist_type, country, genres, image_url, monitor_state)
|
|
VALUES ('artist-ext-id', 'Test Artist', 'person', 'US', ARRAY['Rock'], 'http://img.com', 'monitored')
|
|
RETURNING id
|
|
`).Scan(&artistID)
|
|
require.NoError(t, err)
|
|
|
|
var albumID string
|
|
err = suite.pool.QueryRow(ctx, `
|
|
INSERT INTO albums (external_id, artist_id, title, album_type, total_tracks, total_discs, label, genres, cover_url, monitor_state)
|
|
VALUES ('test-album-ext-id', $1, 'Test Album', 'album', 10, 1, 'Test Label', ARRAY['Rock'], 'http://cover.com', 'unmonitored')
|
|
RETURNING id
|
|
`, artistID).Scan(&albumID)
|
|
require.NoError(t, err)
|
|
|
|
torrentHash := "6ff7af15d0745a3e29d1b9620191cfe01ad3cc70"
|
|
|
|
var torrentID string
|
|
err = suite.pool.QueryRow(ctx, `
|
|
INSERT INTO torrents (album_id, info_hash, tracker, title, format, quality, source, seeders, peers, size)
|
|
VALUES ($1, $2, 'test-tracker', 'Test Album FLAC', 'FLAC', '16-44', 'CD', 100, 50, 500000000)
|
|
RETURNING id
|
|
`, albumID, torrentHash).Scan(&torrentID)
|
|
require.NoError(t, err)
|
|
|
|
_, err = suite.pool.Exec(ctx, `
|
|
INSERT INTO downloads (torrent_id, album_id, format, quality, state, qbit_hash)
|
|
VALUES ($1, $2, 'FLAC', '16-44', 'downloading', $3)
|
|
`, torrentID, albumID, torrentHash)
|
|
require.NoError(t, err)
|
|
|
|
suite.mocks.metadata.GetAlbumFunc = func(ctx context.Context, in *metadataPb.GetAlbumRequest, opts ...grpc.CallOption) (*metadataPb.GetAlbumResponse, error) {
|
|
return &metadataPb.GetAlbumResponse{
|
|
Album: newMetadataAlbum("test-album-ext-id", "Test Album", "artist-ext-id", "Test Artist"),
|
|
}, nil
|
|
}
|
|
|
|
suite.mocks.indexer.SearchFunc = func(query string, limit int32, idx string) (*indexer.SearchResponse, error) {
|
|
return newSearchResponse(
|
|
newSearchItem("Test Album FLAC", 50, "magnet:?xt=urn:btih:"+torrentHash),
|
|
), nil
|
|
}
|
|
|
|
suite.mocks.magnet.ResolveFunc = func(magnetURI string) ([]byte, error) {
|
|
return newTorrentData(), nil
|
|
}
|
|
|
|
suite.mocks.torrent.FindFunc = func(opts torrent.FindOptions) ([]torrent.TorrentInfo, error) {
|
|
return []torrent.TorrentInfo{{State: "downloading"}}, nil
|
|
}
|
|
|
|
resp, err := suite.client.MonitorAlbum(ctx, &pb.MonitorAlbumRequest{
|
|
AlbumId: "test-album-ext-id",
|
|
Quality: pb.QualityType_QUALITY_LOSSLESS,
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp.Release)
|
|
|
|
var downloadCount int
|
|
err = suite.pool.QueryRow(ctx, "SELECT COUNT(*) FROM downloads").Scan(&downloadCount)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, downloadCount)
|
|
}
|
|
|
|
func TestMonitorAlbum_SearchQueryFormat(t *testing.T) {
|
|
suite := setupSuite(t)
|
|
cleanTables(t, suite.pool)
|
|
|
|
suite.mocks.metadata.GetAlbumFunc = func(ctx context.Context, in *metadataPb.GetAlbumRequest, opts ...grpc.CallOption) (*metadataPb.GetAlbumResponse, error) {
|
|
return &metadataPb.GetAlbumResponse{
|
|
Album: newMetadataAlbum("test-album-ext-id", "Test Album", "artist-ext-id", "Test Artist"),
|
|
}, nil
|
|
}
|
|
|
|
var capturedQuery string
|
|
suite.mocks.indexer.SearchFunc = func(query string, limit int32, idx string) (*indexer.SearchResponse, error) {
|
|
capturedQuery = query
|
|
return newSearchResponse(), nil
|
|
}
|
|
|
|
ctx := context.Background()
|
|
_, err := suite.client.MonitorAlbum(ctx, &pb.MonitorAlbumRequest{
|
|
AlbumId: "test-album-ext-id",
|
|
Quality: pb.QualityType_QUALITY_LOSSLESS,
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "Test Artist Test Album", capturedQuery)
|
|
}
|
|
|
|
func TestPollWorker_QBitUnreachable(t *testing.T) {
|
|
suite := setupSuite(t)
|
|
cleanTables(t, suite.pool)
|
|
|
|
ctx := context.Background()
|
|
|
|
var artistID string
|
|
err := suite.pool.QueryRow(ctx, `
|
|
INSERT INTO artists (external_id, name, artist_type, country, genres, image_url, monitor_state)
|
|
VALUES ('artist-ext-id', 'Test Artist', 'person', 'US', ARRAY['Rock'], 'http://img.com', 'monitored')
|
|
RETURNING id
|
|
`).Scan(&artistID)
|
|
require.NoError(t, err)
|
|
|
|
var albumID string
|
|
err = suite.pool.QueryRow(ctx, `
|
|
INSERT INTO albums (external_id, artist_id, title, album_type, total_tracks, total_discs, label, genres, cover_url, monitor_state)
|
|
VALUES ('test-album-ext-id', $1, 'Test Album', 'album', 10, 1, 'Test Label', ARRAY['Rock'], 'http://cover.com', 'monitored')
|
|
RETURNING id
|
|
`, artistID).Scan(&albumID)
|
|
require.NoError(t, err)
|
|
|
|
var torrentID string
|
|
err = suite.pool.QueryRow(ctx, `
|
|
INSERT INTO torrents (album_id, info_hash, tracker, title, format, quality, source, seeders, peers, size)
|
|
VALUES ($1, 'poll-hash-123', 'test-tracker', 'Test Album FLAC', 'FLAC', '16-44', 'CD', 100, 50, 500000000)
|
|
RETURNING id
|
|
`, albumID).Scan(&torrentID)
|
|
require.NoError(t, err)
|
|
|
|
var downloadID string
|
|
err = suite.pool.QueryRow(ctx, `
|
|
INSERT INTO downloads (torrent_id, album_id, format, quality, state, qbit_hash)
|
|
VALUES ($1, $2, 'FLAC', '16-44', 'downloading', 'poll-hash-123')
|
|
RETURNING id
|
|
`, torrentID, albumID).Scan(&downloadID)
|
|
require.NoError(t, err)
|
|
|
|
mockTorrent := &mockTorrentClient{
|
|
FindFunc: func(opts torrent.FindOptions) ([]torrent.TorrentInfo, error) {
|
|
return nil, assert.AnError
|
|
},
|
|
}
|
|
|
|
worker := &workers.PollDownloadWorker{
|
|
TorrentClient: mockTorrent,
|
|
Downloads: database.NewDownloadRepository(suite.pool),
|
|
DownloadFiles: database.NewDownloadFileRepository(suite.pool),
|
|
RiverClient: nil,
|
|
}
|
|
|
|
job := &river.Job[workers.PollDownloadArgs]{
|
|
Args: workers.PollDownloadArgs{
|
|
DownloadID: downloadID,
|
|
TorrentHash: "poll-hash-123",
|
|
},
|
|
}
|
|
|
|
err = worker.Work(ctx, job)
|
|
require.NoError(t, err)
|
|
|
|
var state string
|
|
err = suite.pool.QueryRow(ctx, "SELECT state FROM downloads WHERE id = $1", downloadID).Scan(&state)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "downloading", state)
|
|
}
|
|
|
|
func TestPollWorker_TorrentDisappeared(t *testing.T) {
|
|
suite := setupSuite(t)
|
|
cleanTables(t, suite.pool)
|
|
|
|
ctx := context.Background()
|
|
|
|
var artistID string
|
|
err := suite.pool.QueryRow(ctx, `
|
|
INSERT INTO artists (external_id, name, artist_type, country, genres, image_url, monitor_state)
|
|
VALUES ('artist-ext-id', 'Test Artist', 'person', 'US', ARRAY['Rock'], 'http://img.com', 'monitored')
|
|
RETURNING id
|
|
`).Scan(&artistID)
|
|
require.NoError(t, err)
|
|
|
|
var albumID string
|
|
err = suite.pool.QueryRow(ctx, `
|
|
INSERT INTO albums (external_id, artist_id, title, album_type, total_tracks, total_discs, label, genres, cover_url, monitor_state)
|
|
VALUES ('test-album-ext-id', $1, 'Test Album', 'album', 10, 1, 'Test Label', ARRAY['Rock'], 'http://cover.com', 'monitored')
|
|
RETURNING id
|
|
`, artistID).Scan(&albumID)
|
|
require.NoError(t, err)
|
|
|
|
var torrentID string
|
|
err = suite.pool.QueryRow(ctx, `
|
|
INSERT INTO torrents (album_id, info_hash, tracker, title, format, quality, source, seeders, peers, size)
|
|
VALUES ($1, 'disappeared-hash', 'test-tracker', 'Test Album FLAC', 'FLAC', '16-44', 'CD', 100, 50, 500000000)
|
|
RETURNING id
|
|
`, albumID).Scan(&torrentID)
|
|
require.NoError(t, err)
|
|
|
|
var downloadID string
|
|
err = suite.pool.QueryRow(ctx, `
|
|
INSERT INTO downloads (torrent_id, album_id, format, quality, state, qbit_hash)
|
|
VALUES ($1, $2, 'FLAC', '16-44', 'downloading', 'disappeared-hash')
|
|
RETURNING id
|
|
`, torrentID, albumID).Scan(&downloadID)
|
|
require.NoError(t, err)
|
|
|
|
mockTorrent := &mockTorrentClient{
|
|
FindFunc: func(opts torrent.FindOptions) ([]torrent.TorrentInfo, error) {
|
|
return []torrent.TorrentInfo{}, nil
|
|
},
|
|
}
|
|
|
|
worker := &workers.PollDownloadWorker{
|
|
TorrentClient: mockTorrent,
|
|
Downloads: database.NewDownloadRepository(suite.pool),
|
|
DownloadFiles: database.NewDownloadFileRepository(suite.pool),
|
|
RiverClient: nil,
|
|
}
|
|
|
|
job := &river.Job[workers.PollDownloadArgs]{
|
|
Args: workers.PollDownloadArgs{
|
|
DownloadID: downloadID,
|
|
TorrentHash: "disappeared-hash",
|
|
},
|
|
}
|
|
|
|
err = worker.Work(ctx, job)
|
|
require.NoError(t, err)
|
|
|
|
var state, errorMsg string
|
|
err = suite.pool.QueryRow(ctx, "SELECT state, error_message FROM downloads WHERE id = $1", downloadID).Scan(&state, &errorMsg)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "failed", state)
|
|
assert.Equal(t, "torrent not found in client", errorMsg)
|
|
}
|
|
|
|
func TestPollWorker_TorrentError(t *testing.T) {
|
|
suite := setupSuite(t)
|
|
cleanTables(t, suite.pool)
|
|
|
|
ctx := context.Background()
|
|
|
|
var artistID string
|
|
err := suite.pool.QueryRow(ctx, `
|
|
INSERT INTO artists (external_id, name, artist_type, country, genres, image_url, monitor_state)
|
|
VALUES ('artist-ext-id', 'Test Artist', 'person', 'US', ARRAY['Rock'], 'http://img.com', 'monitored')
|
|
RETURNING id
|
|
`).Scan(&artistID)
|
|
require.NoError(t, err)
|
|
|
|
var albumID string
|
|
err = suite.pool.QueryRow(ctx, `
|
|
INSERT INTO albums (external_id, artist_id, title, album_type, total_tracks, total_discs, label, genres, cover_url, monitor_state)
|
|
VALUES ('test-album-ext-id', $1, 'Test Album', 'album', 10, 1, 'Test Label', ARRAY['Rock'], 'http://cover.com', 'monitored')
|
|
RETURNING id
|
|
`, artistID).Scan(&albumID)
|
|
require.NoError(t, err)
|
|
|
|
var torrentID string
|
|
err = suite.pool.QueryRow(ctx, `
|
|
INSERT INTO torrents (album_id, info_hash, tracker, title, format, quality, source, seeders, peers, size)
|
|
VALUES ($1, 'error-hash', 'test-tracker', 'Test Album FLAC', 'FLAC', '16-44', 'CD', 100, 50, 500000000)
|
|
RETURNING id
|
|
`, albumID).Scan(&torrentID)
|
|
require.NoError(t, err)
|
|
|
|
var downloadID string
|
|
err = suite.pool.QueryRow(ctx, `
|
|
INSERT INTO downloads (torrent_id, album_id, format, quality, state, qbit_hash)
|
|
VALUES ($1, $2, 'FLAC', '16-44', 'downloading', 'error-hash')
|
|
RETURNING id
|
|
`, torrentID, albumID).Scan(&downloadID)
|
|
require.NoError(t, err)
|
|
|
|
mockTorrent := &mockTorrentClient{
|
|
FindFunc: func(opts torrent.FindOptions) ([]torrent.TorrentInfo, error) {
|
|
return []torrent.TorrentInfo{{State: "error"}}, nil
|
|
},
|
|
}
|
|
|
|
worker := &workers.PollDownloadWorker{
|
|
TorrentClient: mockTorrent,
|
|
Downloads: database.NewDownloadRepository(suite.pool),
|
|
DownloadFiles: database.NewDownloadFileRepository(suite.pool),
|
|
RiverClient: nil,
|
|
}
|
|
|
|
job := &river.Job[workers.PollDownloadArgs]{
|
|
Args: workers.PollDownloadArgs{
|
|
DownloadID: downloadID,
|
|
TorrentHash: "error-hash",
|
|
},
|
|
}
|
|
|
|
err = worker.Work(ctx, job)
|
|
require.NoError(t, err)
|
|
|
|
var state, errorMsg string
|
|
err = suite.pool.QueryRow(ctx, "SELECT state, error_message FROM downloads WHERE id = $1", downloadID).Scan(&state, &errorMsg)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "failed", state)
|
|
assert.Equal(t, "torrent error state", errorMsg)
|
|
}
|
|
|
|
func TestPollWorker_CompletedSuccess(t *testing.T) {
|
|
suite := setupSuite(t)
|
|
cleanTables(t, suite.pool)
|
|
|
|
ctx := context.Background()
|
|
|
|
tempDir := t.TempDir()
|
|
flacFile1 := filepath.Join(tempDir, "01-track.flac")
|
|
flacFile2 := filepath.Join(tempDir, "02-track.flac")
|
|
require.NoError(t, os.WriteFile(flacFile1, []byte("fake flac data 1"), 0644))
|
|
require.NoError(t, os.WriteFile(flacFile2, []byte("fake flac data 2"), 0644))
|
|
|
|
var artistID string
|
|
err := suite.pool.QueryRow(ctx, `
|
|
INSERT INTO artists (external_id, name, artist_type, country, genres, image_url, monitor_state)
|
|
VALUES ('artist-ext-id', 'Test Artist', 'person', 'US', ARRAY['Rock'], 'http://img.com', 'monitored')
|
|
RETURNING id
|
|
`).Scan(&artistID)
|
|
require.NoError(t, err)
|
|
|
|
var albumID string
|
|
err = suite.pool.QueryRow(ctx, `
|
|
INSERT INTO albums (external_id, artist_id, title, album_type, total_tracks, total_discs, label, genres, cover_url, monitor_state)
|
|
VALUES ('test-album-ext-id', $1, 'Test Album', 'album', 10, 1, 'Test Label', ARRAY['Rock'], 'http://cover.com', 'monitored')
|
|
RETURNING id
|
|
`, artistID).Scan(&albumID)
|
|
require.NoError(t, err)
|
|
|
|
var torrentID string
|
|
err = suite.pool.QueryRow(ctx, `
|
|
INSERT INTO torrents (album_id, info_hash, tracker, title, format, quality, source, seeders, peers, size)
|
|
VALUES ($1, 'completed-hash', 'test-tracker', 'Test Album FLAC', 'FLAC', '16-44', 'CD', 100, 50, 500000000)
|
|
RETURNING id
|
|
`, albumID).Scan(&torrentID)
|
|
require.NoError(t, err)
|
|
|
|
var downloadID string
|
|
err = suite.pool.QueryRow(ctx, `
|
|
INSERT INTO downloads (torrent_id, album_id, format, quality, state, qbit_hash)
|
|
VALUES ($1, $2, 'FLAC', '16-44', 'downloading', 'completed-hash')
|
|
RETURNING id
|
|
`, torrentID, albumID).Scan(&downloadID)
|
|
require.NoError(t, err)
|
|
|
|
mockTorrent := &mockTorrentClient{
|
|
FindFunc: func(opts torrent.FindOptions) ([]torrent.TorrentInfo, error) {
|
|
return []torrent.TorrentInfo{{
|
|
Progress: 1.0,
|
|
SavePath: "/downloads",
|
|
ContentPath: tempDir,
|
|
}}, nil
|
|
},
|
|
}
|
|
|
|
worker := &workers.PollDownloadWorker{
|
|
TorrentClient: mockTorrent,
|
|
Downloads: database.NewDownloadRepository(suite.pool),
|
|
DownloadFiles: database.NewDownloadFileRepository(suite.pool),
|
|
RiverClient: nil,
|
|
}
|
|
|
|
job := &river.Job[workers.PollDownloadArgs]{
|
|
Args: workers.PollDownloadArgs{
|
|
DownloadID: downloadID,
|
|
TorrentHash: "completed-hash",
|
|
},
|
|
}
|
|
|
|
err = worker.Work(ctx, job)
|
|
require.NoError(t, err)
|
|
|
|
var state string
|
|
var savePath *string
|
|
err = suite.pool.QueryRow(ctx, "SELECT state, save_path FROM downloads WHERE id = $1", downloadID).Scan(&state, &savePath)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "completed", state)
|
|
require.NotNil(t, savePath)
|
|
assert.Equal(t, "/downloads", *savePath)
|
|
|
|
var fileCount int
|
|
err = suite.pool.QueryRow(ctx, "SELECT COUNT(*) FROM download_files WHERE download_id = $1", downloadID).Scan(&fileCount)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 2, fileCount)
|
|
|
|
var fileTypes []string
|
|
rows, err := suite.pool.Query(ctx, "SELECT file_type FROM download_files WHERE download_id = $1", downloadID)
|
|
require.NoError(t, err)
|
|
defer rows.Close()
|
|
for rows.Next() {
|
|
var ft string
|
|
require.NoError(t, rows.Scan(&ft))
|
|
fileTypes = append(fileTypes, ft)
|
|
}
|
|
assert.Contains(t, fileTypes, "flac")
|
|
}
|
|
|
|
func TestPollWorker_FileScanFails(t *testing.T) {
|
|
suite := setupSuite(t)
|
|
cleanTables(t, suite.pool)
|
|
|
|
ctx := context.Background()
|
|
|
|
var artistID string
|
|
err := suite.pool.QueryRow(ctx, `
|
|
INSERT INTO artists (external_id, name, artist_type, country, genres, image_url, monitor_state)
|
|
VALUES ('artist-ext-id', 'Test Artist', 'person', 'US', ARRAY['Rock'], 'http://img.com', 'monitored')
|
|
RETURNING id
|
|
`).Scan(&artistID)
|
|
require.NoError(t, err)
|
|
|
|
var albumID string
|
|
err = suite.pool.QueryRow(ctx, `
|
|
INSERT INTO albums (external_id, artist_id, title, album_type, total_tracks, total_discs, label, genres, cover_url, monitor_state)
|
|
VALUES ('test-album-ext-id', $1, 'Test Album', 'album', 10, 1, 'Test Label', ARRAY['Rock'], 'http://cover.com', 'monitored')
|
|
RETURNING id
|
|
`, artistID).Scan(&albumID)
|
|
require.NoError(t, err)
|
|
|
|
var torrentID string
|
|
err = suite.pool.QueryRow(ctx, `
|
|
INSERT INTO torrents (album_id, info_hash, tracker, title, format, quality, source, seeders, peers, size)
|
|
VALUES ($1, 'scan-fail-hash', 'test-tracker', 'Test Album FLAC', 'FLAC', '16-44', 'CD', 100, 50, 500000000)
|
|
RETURNING id
|
|
`, albumID).Scan(&torrentID)
|
|
require.NoError(t, err)
|
|
|
|
var downloadID string
|
|
err = suite.pool.QueryRow(ctx, `
|
|
INSERT INTO downloads (torrent_id, album_id, format, quality, state, qbit_hash)
|
|
VALUES ($1, $2, 'FLAC', '16-44', 'downloading', 'scan-fail-hash')
|
|
RETURNING id
|
|
`, torrentID, albumID).Scan(&downloadID)
|
|
require.NoError(t, err)
|
|
|
|
mockTorrent := &mockTorrentClient{
|
|
FindFunc: func(opts torrent.FindOptions) ([]torrent.TorrentInfo, error) {
|
|
return []torrent.TorrentInfo{{
|
|
Progress: 1.0,
|
|
SavePath: "/downloads",
|
|
ContentPath: "/nonexistent/path/that/does/not/exist",
|
|
}}, nil
|
|
},
|
|
}
|
|
|
|
worker := &workers.PollDownloadWorker{
|
|
TorrentClient: mockTorrent,
|
|
Downloads: database.NewDownloadRepository(suite.pool),
|
|
DownloadFiles: database.NewDownloadFileRepository(suite.pool),
|
|
RiverClient: nil,
|
|
}
|
|
|
|
job := &river.Job[workers.PollDownloadArgs]{
|
|
Args: workers.PollDownloadArgs{
|
|
DownloadID: downloadID,
|
|
TorrentHash: "scan-fail-hash",
|
|
},
|
|
}
|
|
|
|
err = worker.Work(ctx, job)
|
|
require.NoError(t, err)
|
|
|
|
var state string
|
|
err = suite.pool.QueryRow(ctx, "SELECT state FROM downloads WHERE id = $1", downloadID).Scan(&state)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "completed", state)
|
|
|
|
var fileCount int
|
|
err = suite.pool.QueryRow(ctx, "SELECT COUNT(*) FROM download_files WHERE download_id = $1", downloadID).Scan(&fileCount)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, fileCount)
|
|
}
|
|
|
|
func TestRecoverOrphaned_FindsActiveDownloads(t *testing.T) {
|
|
suite := setupSuite(t)
|
|
cleanTables(t, suite.pool)
|
|
|
|
ctx := context.Background()
|
|
|
|
var artistID string
|
|
err := suite.pool.QueryRow(ctx, `
|
|
INSERT INTO artists (external_id, name, artist_type, country, genres, image_url, monitor_state)
|
|
VALUES ('artist-ext-id', 'Test Artist', 'person', 'US', ARRAY['Rock'], 'http://img.com', 'monitored')
|
|
RETURNING id
|
|
`).Scan(&artistID)
|
|
require.NoError(t, err)
|
|
|
|
var albumID string
|
|
err = suite.pool.QueryRow(ctx, `
|
|
INSERT INTO albums (external_id, artist_id, title, album_type, total_tracks, total_discs, label, genres, cover_url, monitor_state)
|
|
VALUES ('test-album-ext-id', $1, 'Test Album', 'album', 10, 1, 'Test Label', ARRAY['Rock'], 'http://cover.com', 'monitored')
|
|
RETURNING id
|
|
`, artistID).Scan(&albumID)
|
|
require.NoError(t, err)
|
|
|
|
var torrentID string
|
|
err = suite.pool.QueryRow(ctx, `
|
|
INSERT INTO torrents (album_id, info_hash, tracker, title, format, quality, source, seeders, peers, size)
|
|
VALUES ($1, 'orphan-hash', 'test-tracker', 'Test Album FLAC', 'FLAC', '16-44', 'CD', 100, 50, 500000000)
|
|
RETURNING id
|
|
`, albumID).Scan(&torrentID)
|
|
require.NoError(t, err)
|
|
|
|
var downloadID string
|
|
err = suite.pool.QueryRow(ctx, `
|
|
INSERT INTO downloads (torrent_id, album_id, format, quality, state, qbit_hash)
|
|
VALUES ($1, $2, 'FLAC', '16-44', 'downloading', 'orphan-hash')
|
|
RETURNING id
|
|
`, torrentID, albumID).Scan(&downloadID)
|
|
require.NoError(t, err)
|
|
|
|
downloads := database.NewDownloadRepository(suite.pool)
|
|
active, err := downloads.GetActive(ctx)
|
|
require.NoError(t, err)
|
|
require.Len(t, active, 1)
|
|
assert.Equal(t, downloadID, active[0].ID)
|
|
assert.Equal(t, "orphan-hash", active[0].QbitHash)
|
|
assert.Equal(t, "downloading", active[0].State)
|
|
}
|