Add streaming, subscribe, cancel cleanup, and recovery component tests
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/claude-agent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -699,7 +699,7 @@ func TestMonitorAlbumStream_AutomaticQBitDown(t *testing.T) {
|
||||
var downloadCount int
|
||||
err := suite.pool.QueryRow(ctx, "SELECT COUNT(*) FROM downloads").Scan(&downloadCount)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, downloadCount)
|
||||
assert.Equal(t, 1, downloadCount, "download record should exist even when qBit fails (DB save happens before qBit)")
|
||||
}
|
||||
|
||||
func TestMonitorAlbumStream_AutomaticTorrentExists(t *testing.T) {
|
||||
@@ -1525,3 +1525,562 @@ func TestMonitorAlbumStream_InvalidPromptId(t *testing.T) {
|
||||
}
|
||||
assert.True(t, hasError, "expected error for invalid prompt ID")
|
||||
}
|
||||
|
||||
func TestMonitorAlbumStream_ManualCancelBeforeQBit(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
|
||||
}
|
||||
|
||||
indexerCalled := make(chan struct{})
|
||||
suite.mocks.indexer.SearchFunc = func(query string, limit int32, idx string) (*indexer.SearchResponse, error) {
|
||||
close(indexerCalled)
|
||||
time.Sleep(2 * time.Second)
|
||||
return newSearchResponse(
|
||||
newSearchItem("Test Artist - Test Album [FLAC]", 50, "magnet:?xt=urn:btih:abc123"),
|
||||
), nil
|
||||
}
|
||||
|
||||
var addMagnetCalled bool
|
||||
suite.mocks.torrent.AddMagnetFunc = func(magnetURI string, savePath string) error {
|
||||
addMagnetCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
var deleteTorrentCalled bool
|
||||
suite.mocks.torrent.DeleteTorrentFunc = func(hash string) error {
|
||||
deleteTorrentCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
stream, err := suite.client.MonitorAlbumStream(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = stream.Send(&pb.MonitorAlbumStreamRequest{
|
||||
Message: &pb.MonitorAlbumStreamRequest_Start{
|
||||
Start: &pb.StartMonitorRequest{
|
||||
AlbumId: "test-album-ext-id",
|
||||
Quality: pb.QualityType_QUALITY_LOSSLESS,
|
||||
Mode: pb.InteractionMode_INTERACTION_MODE_MANUAL,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
<-indexerCalled
|
||||
|
||||
cancel()
|
||||
|
||||
for {
|
||||
_, err = stream.Recv()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
assert.False(t, addMagnetCalled, "AddMagnet should not be called when cancelled before qbit")
|
||||
assert.False(t, deleteTorrentCalled, "DeleteTorrent should not be called when no torrent was added")
|
||||
|
||||
bgCtx := context.Background()
|
||||
var downloadCount int
|
||||
err = suite.pool.QueryRow(bgCtx, "SELECT COUNT(*) FROM downloads").Scan(&downloadCount)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, downloadCount)
|
||||
}
|
||||
|
||||
func TestMonitorAlbumStream_ManualCancelAfterQBit(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"),
|
||||
), 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
|
||||
}
|
||||
|
||||
torrentAddedCh := make(chan struct{})
|
||||
suite.mocks.torrent.AddMagnetFunc = func(magnetURI string, savePath string) error {
|
||||
close(torrentAddedCh)
|
||||
return nil
|
||||
}
|
||||
|
||||
var deletedHash string
|
||||
suite.mocks.torrent.DeleteTorrentFunc = func(hash string) error {
|
||||
deletedHash = hash
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
stream, err := suite.client.MonitorAlbumStream(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = stream.Send(&pb.MonitorAlbumStreamRequest{
|
||||
Message: &pb.MonitorAlbumStreamRequest_Start{
|
||||
Start: &pb.StartMonitorRequest{
|
||||
AlbumId: "test-album-ext-id",
|
||||
Quality: pb.QualityType_QUALITY_LOSSLESS,
|
||||
Mode: pb.InteractionMode_INTERACTION_MODE_MANUAL,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, prompt, err := collectUntilPrompt(t, stream, 0)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, prompt)
|
||||
assert.Equal(t, pb.PromptType_PROMPT_TYPE_CONFIRM, prompt.Type)
|
||||
|
||||
sendDecision(t, stream, prompt.PromptId, &pb.UserDecision{
|
||||
Decision: &pb.UserDecision_Confirm{Confirm: true},
|
||||
})
|
||||
|
||||
select {
|
||||
case <-torrentAddedCh:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("timeout waiting for torrent to be added")
|
||||
}
|
||||
|
||||
cancel()
|
||||
|
||||
for {
|
||||
_, err = stream.Recv()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
assert.NotEmpty(t, deletedHash, "DeleteTorrent should be called with the torrent hash")
|
||||
assert.Equal(t, "6ff7af15d0745a3e29d1b9620191cfe01ad3cc70", deletedHash)
|
||||
|
||||
bgCtx := context.Background()
|
||||
var downloadState string
|
||||
err = suite.pool.QueryRow(bgCtx, "SELECT state FROM downloads WHERE qbit_hash = $1", deletedHash).Scan(&downloadState)
|
||||
if err == nil {
|
||||
assert.Equal(t, "cancelled", downloadState)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMonitorAlbumStream_AutomaticFireAndForget(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) {
|
||||
time.Sleep(1 * time.Second)
|
||||
return newSearchResponse(
|
||||
newSearchItem("Test Artist - Test 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, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
|
||||
stream, err := suite.client.MonitorAlbumStream(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = stream.Send(&pb.MonitorAlbumStreamRequest{
|
||||
Message: &pb.MonitorAlbumStreamRequest_Start{
|
||||
Start: &pb.StartMonitorRequest{
|
||||
AlbumId: "test-album-ext-id",
|
||||
Quality: pb.QualityType_QUALITY_LOSSLESS,
|
||||
Mode: pb.InteractionMode_INTERACTION_MODE_AUTOMATIC,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var statusCount int
|
||||
for i := 0; i < 3; i++ {
|
||||
msg, err := stream.Recv()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if msg.GetStatus() != nil {
|
||||
statusCount++
|
||||
}
|
||||
}
|
||||
assert.GreaterOrEqual(t, statusCount, 1, "should receive at least one status before disconnect")
|
||||
|
||||
cancel()
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
bgCtx := context.Background()
|
||||
var downloadCount int
|
||||
err = suite.pool.QueryRow(bgCtx, "SELECT COUNT(*) FROM downloads").Scan(&downloadCount)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, downloadCount, "workflow should complete and create download despite client disconnect")
|
||||
}
|
||||
|
||||
func TestMonitorAlbumStream_AutomaticDuplicateSubscribes(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) {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
return newSearchResponse(
|
||||
newSearchItem("Test Artist - Test 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
|
||||
}
|
||||
|
||||
ctx1, cancel1 := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel1()
|
||||
|
||||
stream1, err := suite.client.MonitorAlbumStream(ctx1)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = stream1.Send(&pb.MonitorAlbumStreamRequest{
|
||||
Message: &pb.MonitorAlbumStreamRequest_Start{
|
||||
Start: &pb.StartMonitorRequest{
|
||||
AlbumId: "test-album-ext-id",
|
||||
Quality: pb.QualityType_QUALITY_LOSSLESS,
|
||||
Mode: pb.InteractionMode_INTERACTION_MODE_AUTOMATIC,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
ctx2, cancel2 := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel2()
|
||||
|
||||
stream2, err := suite.client.MonitorAlbumStream(ctx2)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = stream2.Send(&pb.MonitorAlbumStreamRequest{
|
||||
Message: &pb.MonitorAlbumStreamRequest_Start{
|
||||
Start: &pb.StartMonitorRequest{
|
||||
AlbumId: "test-album-ext-id",
|
||||
Quality: pb.QualityType_QUALITY_LOSSLESS,
|
||||
Mode: pb.InteractionMode_INTERACTION_MODE_AUTOMATIC,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var stream1Events, stream2Events int
|
||||
|
||||
done1 := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done1)
|
||||
for {
|
||||
msg, err := stream1.Recv()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if msg.GetStatus() != nil || msg.GetResult() != nil || msg.GetError() != nil {
|
||||
stream1Events++
|
||||
}
|
||||
if msg.GetResult() != nil || msg.GetError() != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
done2 := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done2)
|
||||
for {
|
||||
msg, err := stream2.Recv()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if msg.GetStatus() != nil || msg.GetResult() != nil || msg.GetError() != nil {
|
||||
stream2Events++
|
||||
}
|
||||
if msg.GetResult() != nil || msg.GetError() != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done1:
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatal("stream1 timed out")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-done2:
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatal("stream2 timed out")
|
||||
}
|
||||
|
||||
assert.Greater(t, stream1Events, 0, "stream1 should receive events")
|
||||
assert.Greater(t, stream2Events, 0, "stream2 should receive events")
|
||||
|
||||
bgCtx := context.Background()
|
||||
var downloadCount int
|
||||
err = suite.pool.QueryRow(bgCtx, "SELECT COUNT(*) FROM downloads").Scan(&downloadCount)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, downloadCount, "only one download should be created despite two subscribers")
|
||||
}
|
||||
|
||||
func TestMonitorAlbumStream_AutomaticReplayOnReconnect(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"),
|
||||
), 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
|
||||
}
|
||||
|
||||
stream1 := startMonitorStream(t, suite.client, &pb.StartMonitorRequest{
|
||||
AlbumId: "test-album-ext-id",
|
||||
Quality: pb.QualityType_QUALITY_LOSSLESS,
|
||||
Mode: pb.InteractionMode_INTERACTION_MODE_AUTOMATIC,
|
||||
})
|
||||
|
||||
messages1 := collectAllMessages(t, stream1, 0)
|
||||
|
||||
var hasResult1 bool
|
||||
for _, msg := range messages1 {
|
||||
if msg.GetResult() != nil {
|
||||
hasResult1 = true
|
||||
break
|
||||
}
|
||||
}
|
||||
require.True(t, hasResult1, "first workflow should complete")
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
stream2 := startMonitorStream(t, suite.client, &pb.StartMonitorRequest{
|
||||
AlbumId: "test-album-ext-id",
|
||||
Quality: pb.QualityType_QUALITY_LOSSLESS,
|
||||
Mode: pb.InteractionMode_INTERACTION_MODE_AUTOMATIC,
|
||||
})
|
||||
|
||||
messages2 := collectAllMessages(t, stream2, 0)
|
||||
|
||||
var hasResult2 bool
|
||||
for _, msg := range messages2 {
|
||||
if msg.GetResult() != nil {
|
||||
hasResult2 = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, hasResult2, "new workflow should start and complete after registry cleanup")
|
||||
|
||||
bgCtx := context.Background()
|
||||
var eventCount int
|
||||
err := suite.pool.QueryRow(bgCtx, "SELECT COUNT(*) FROM album_events").Scan(&eventCount)
|
||||
require.NoError(t, err)
|
||||
assert.Greater(t, eventCount, len(messages1), "should have events from both runs")
|
||||
}
|
||||
|
||||
func TestMonitorAlbumStream_ManualCancelAfterDownloadSaved(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"),
|
||||
), 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
|
||||
}
|
||||
|
||||
savingCh := make(chan struct{})
|
||||
suite.mocks.torrent.AddMagnetFunc = func(magnetURI string, savePath string) error {
|
||||
close(savingCh)
|
||||
time.Sleep(2 * time.Second)
|
||||
return nil
|
||||
}
|
||||
|
||||
var deleteTorrentCalled bool
|
||||
suite.mocks.torrent.DeleteTorrentFunc = func(hash string) error {
|
||||
deleteTorrentCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
stream, err := suite.client.MonitorAlbumStream(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = stream.Send(&pb.MonitorAlbumStreamRequest{
|
||||
Message: &pb.MonitorAlbumStreamRequest_Start{
|
||||
Start: &pb.StartMonitorRequest{
|
||||
AlbumId: "test-album-ext-id",
|
||||
Quality: pb.QualityType_QUALITY_LOSSLESS,
|
||||
Mode: pb.InteractionMode_INTERACTION_MODE_MANUAL,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, prompt, err := collectUntilPrompt(t, stream, 0)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, prompt)
|
||||
|
||||
sendDecision(t, stream, prompt.PromptId, &pb.UserDecision{
|
||||
Decision: &pb.UserDecision_Confirm{Confirm: true},
|
||||
})
|
||||
|
||||
select {
|
||||
case <-savingCh:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("timeout waiting for saving to start")
|
||||
}
|
||||
|
||||
cancel()
|
||||
|
||||
for {
|
||||
_, err = stream.Recv()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
assert.True(t, deleteTorrentCalled, "DeleteTorrent should be called during cleanup")
|
||||
|
||||
bgCtx := context.Background()
|
||||
var downloadState string
|
||||
err = suite.pool.QueryRow(bgCtx, "SELECT state FROM downloads WHERE qbit_hash = $1", "6ff7af15d0745a3e29d1b9620191cfe01ad3cc70").Scan(&downloadState)
|
||||
if err == nil {
|
||||
assert.Equal(t, "cancelled", downloadState)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMonitorAlbumStream_ManualDisconnectCancels(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) {
|
||||
time.Sleep(2 * time.Second)
|
||||
return newSearchResponse(
|
||||
newSearchItem("Test Artist - Test Album [FLAC]", 50, "magnet:?xt=urn:btih:abc123"),
|
||||
), nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
stream, err := suite.client.MonitorAlbumStream(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = stream.Send(&pb.MonitorAlbumStreamRequest{
|
||||
Message: &pb.MonitorAlbumStreamRequest_Start{
|
||||
Start: &pb.StartMonitorRequest{
|
||||
AlbumId: "test-album-ext-id",
|
||||
Quality: pb.QualityType_QUALITY_LOSSLESS,
|
||||
Mode: pb.InteractionMode_INTERACTION_MODE_MANUAL,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var gotStatus bool
|
||||
for i := 0; i < 3; i++ {
|
||||
msg, err := stream.Recv()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if msg.GetStatus() != nil {
|
||||
gotStatus = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, gotStatus, "should receive at least one status before disconnect")
|
||||
|
||||
cancel()
|
||||
|
||||
_, err = stream.Recv()
|
||||
assert.Error(t, err, "stream.Recv should return error after disconnect")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user