Files
music-agregator/test/component/workflow_event_test.go
T
2026-05-11 15:54:25 +02:00

423 lines
12 KiB
Go

package component
import (
"context"
"errors"
"testing"
"github.com/jackc/pgx/v5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"homelab.lan/music-agregator/internal"
"homelab.lan/music-agregator/internal/database"
)
func insertTestArtistAndAlbum(t *testing.T, suite *testSuite) string {
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 ('test-artist-ext', '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', $1, 'Test Album', 'album', 10, 1, 'Test Label', ARRAY['Rock'], 'http://cover.com', 'monitored')
RETURNING id
`, artistID).Scan(&albumID)
require.NoError(t, err)
return albumID
}
func TestWorkflowRun_Create(t *testing.T) {
suite := setupSuite(t)
cleanTables(t, suite.pool)
ctx := context.Background()
albumID := insertTestArtistAndAlbum(t, suite)
repo := database.NewWorkflowRunRepository(suite.pool)
run := &database.WorkflowRun{
AlbumID: albumID,
Quality: "flac",
}
err := repo.Create(ctx, run)
require.NoError(t, err)
assert.NotEmpty(t, run.ID)
assert.False(t, run.StartedAt.IsZero())
fetched, err := repo.GetByID(ctx, run.ID)
require.NoError(t, err)
assert.Equal(t, "running", fetched.Status)
}
func TestWorkflowRun_DuplicateRunningRejected(t *testing.T) {
suite := setupSuite(t)
cleanTables(t, suite.pool)
ctx := context.Background()
albumID := insertTestArtistAndAlbum(t, suite)
repo := database.NewWorkflowRunRepository(suite.pool)
run1 := &database.WorkflowRun{AlbumID: albumID, Quality: "flac"}
err := repo.Create(ctx, run1)
require.NoError(t, err)
run2 := &database.WorkflowRun{AlbumID: albumID, Quality: "flac"}
err = repo.Create(ctx, run2)
assert.ErrorIs(t, err, database.ErrWorkflowAlreadyRunning)
}
func TestWorkflowRun_DuplicateAllowedAfterCompletion(t *testing.T) {
suite := setupSuite(t)
cleanTables(t, suite.pool)
ctx := context.Background()
albumID := insertTestArtistAndAlbum(t, suite)
repo := database.NewWorkflowRunRepository(suite.pool)
run1 := &database.WorkflowRun{AlbumID: albumID, Quality: "flac"}
err := repo.Create(ctx, run1)
require.NoError(t, err)
err = repo.SetCompleted(ctx, run1.ID)
require.NoError(t, err)
run2 := &database.WorkflowRun{AlbumID: albumID, Quality: "flac"}
err = repo.Create(ctx, run2)
require.NoError(t, err)
assert.NotEmpty(t, run2.ID)
assert.NotEqual(t, run1.ID, run2.ID)
}
func TestWorkflowRun_SetCompleted(t *testing.T) {
suite := setupSuite(t)
cleanTables(t, suite.pool)
ctx := context.Background()
albumID := insertTestArtistAndAlbum(t, suite)
repo := database.NewWorkflowRunRepository(suite.pool)
run := &database.WorkflowRun{AlbumID: albumID, Quality: "flac"}
err := repo.Create(ctx, run)
require.NoError(t, err)
err = repo.SetCompleted(ctx, run.ID)
require.NoError(t, err)
updated, err := repo.GetByID(ctx, run.ID)
require.NoError(t, err)
assert.Equal(t, "completed", updated.Status)
assert.NotNil(t, updated.CompletedAt)
}
func TestWorkflowRun_SetFailed(t *testing.T) {
suite := setupSuite(t)
cleanTables(t, suite.pool)
ctx := context.Background()
albumID := insertTestArtistAndAlbum(t, suite)
repo := database.NewWorkflowRunRepository(suite.pool)
run := &database.WorkflowRun{AlbumID: albumID, Quality: "flac"}
err := repo.Create(ctx, run)
require.NoError(t, err)
err = repo.SetFailed(ctx, run.ID, "something went wrong")
require.NoError(t, err)
updated, err := repo.GetByID(ctx, run.ID)
require.NoError(t, err)
assert.Equal(t, "failed", updated.Status)
require.NotNil(t, updated.ErrorMessage)
assert.Equal(t, "something went wrong", *updated.ErrorMessage)
}
func TestWorkflowRun_SetCancelled(t *testing.T) {
suite := setupSuite(t)
cleanTables(t, suite.pool)
ctx := context.Background()
albumID := insertTestArtistAndAlbum(t, suite)
repo := database.NewWorkflowRunRepository(suite.pool)
run := &database.WorkflowRun{AlbumID: albumID, Quality: "flac"}
err := repo.Create(ctx, run)
require.NoError(t, err)
err = repo.SetCancelled(ctx, run.ID)
require.NoError(t, err)
updated, err := repo.GetByID(ctx, run.ID)
require.NoError(t, err)
assert.Equal(t, "cancelled", updated.Status)
}
func TestWorkflowRun_GetByAlbumAndQuality(t *testing.T) {
suite := setupSuite(t)
cleanTables(t, suite.pool)
ctx := context.Background()
albumID := insertTestArtistAndAlbum(t, suite)
repo := database.NewWorkflowRunRepository(suite.pool)
run := &database.WorkflowRun{AlbumID: albumID, Quality: "flac"}
err := repo.Create(ctx, run)
require.NoError(t, err)
found, err := repo.GetByAlbumAndQuality(ctx, albumID, "flac")
require.NoError(t, err)
assert.Equal(t, run.ID, found.ID)
_, err = repo.GetByAlbumAndQuality(ctx, albumID, "mp3")
assert.True(t, errors.Is(err, pgx.ErrNoRows) || err != nil)
}
func TestWorkflowRun_GetRunning(t *testing.T) {
suite := setupSuite(t)
cleanTables(t, suite.pool)
ctx := context.Background()
albumID := insertTestArtistAndAlbum(t, suite)
repo := database.NewWorkflowRunRepository(suite.pool)
run1 := &database.WorkflowRun{AlbumID: albumID, Quality: "flac"}
err := repo.Create(ctx, run1)
require.NoError(t, err)
run2 := &database.WorkflowRun{AlbumID: albumID, Quality: "mp3"}
err = repo.Create(ctx, run2)
require.NoError(t, err)
run3 := &database.WorkflowRun{AlbumID: albumID, Quality: "opus"}
err = repo.Create(ctx, run3)
require.NoError(t, err)
err = repo.SetCompleted(ctx, run3.ID)
require.NoError(t, err)
running, err := repo.GetRunning(ctx)
require.NoError(t, err)
assert.Len(t, running, 2)
ids := []string{running[0].ID, running[1].ID}
assert.Contains(t, ids, run1.ID)
assert.Contains(t, ids, run2.ID)
}
func TestAlbumEvent_Create(t *testing.T) {
suite := setupSuite(t)
cleanTables(t, suite.pool)
ctx := context.Background()
albumID := insertTestArtistAndAlbum(t, suite)
workflowRepo := database.NewWorkflowRunRepository(suite.pool)
eventRepo := database.NewAlbumEventRepository(suite.pool)
run := &database.WorkflowRun{AlbumID: albumID, Quality: "flac"}
err := workflowRepo.Create(ctx, run)
require.NoError(t, err)
event := &database.AlbumEvent{
WorkflowRunID: run.ID,
AlbumID: albumID,
EventType: "info",
Step: "searching",
Message: "started search",
DataJSON: []byte(`{"query":"test"}`),
}
err = eventRepo.Create(ctx, event)
require.NoError(t, err)
assert.NotEmpty(t, event.ID)
assert.Greater(t, event.Seq, int64(0))
}
func TestAlbumEvent_GetByWorkflowRun(t *testing.T) {
suite := setupSuite(t)
cleanTables(t, suite.pool)
ctx := context.Background()
albumID := insertTestArtistAndAlbum(t, suite)
workflowRepo := database.NewWorkflowRunRepository(suite.pool)
eventRepo := database.NewAlbumEventRepository(suite.pool)
run := &database.WorkflowRun{AlbumID: albumID, Quality: "flac"}
err := workflowRepo.Create(ctx, run)
require.NoError(t, err)
for i := 0; i < 3; i++ {
event := &database.AlbumEvent{
WorkflowRunID: run.ID,
AlbumID: albumID,
EventType: "info",
Step: "step",
Message: "msg",
}
err = eventRepo.Create(ctx, event)
require.NoError(t, err)
}
events, err := eventRepo.GetByWorkflowRun(ctx, run.ID)
require.NoError(t, err)
assert.Len(t, events, 3)
assert.Less(t, events[0].Seq, events[1].Seq)
assert.Less(t, events[1].Seq, events[2].Seq)
}
func TestAlbumEvent_GetByAlbum(t *testing.T) {
suite := setupSuite(t)
cleanTables(t, suite.pool)
ctx := context.Background()
albumID := insertTestArtistAndAlbum(t, suite)
workflowRepo := database.NewWorkflowRunRepository(suite.pool)
eventRepo := database.NewAlbumEventRepository(suite.pool)
run := &database.WorkflowRun{AlbumID: albumID, Quality: "flac"}
err := workflowRepo.Create(ctx, run)
require.NoError(t, err)
var seqs []int64
for i := 0; i < 5; i++ {
event := &database.AlbumEvent{
WorkflowRunID: run.ID,
AlbumID: albumID,
EventType: "info",
Step: "step",
Message: "msg",
}
err = eventRepo.Create(ctx, event)
require.NoError(t, err)
seqs = append(seqs, event.Seq)
}
events, err := eventRepo.GetByAlbum(ctx, albumID, seqs[1], 10)
require.NoError(t, err)
assert.Len(t, events, 3)
assert.Equal(t, seqs[2], events[0].Seq)
assert.Equal(t, seqs[3], events[1].Seq)
assert.Equal(t, seqs[4], events[2].Seq)
}
func TestAlbumEvent_GetLatestSeq(t *testing.T) {
suite := setupSuite(t)
cleanTables(t, suite.pool)
ctx := context.Background()
eventRepo := database.NewAlbumEventRepository(suite.pool)
seq, err := eventRepo.GetLatestSeq(ctx)
require.NoError(t, err)
assert.Equal(t, int64(0), seq)
albumID := insertTestArtistAndAlbum(t, suite)
workflowRepo := database.NewWorkflowRunRepository(suite.pool)
run := &database.WorkflowRun{AlbumID: albumID, Quality: "flac"}
err = workflowRepo.Create(ctx, run)
require.NoError(t, err)
var lastSeq int64
for i := 0; i < 3; i++ {
event := &database.AlbumEvent{
WorkflowRunID: run.ID,
AlbumID: albumID,
EventType: "info",
Step: "step",
Message: "msg",
}
err = eventRepo.Create(ctx, event)
require.NoError(t, err)
lastSeq = event.Seq
}
seq, err = eventRepo.GetLatestSeq(ctx)
require.NoError(t, err)
assert.Equal(t, lastSeq, seq)
}
func TestRecovery_StaleWorkflowWithDownload(t *testing.T) {
suite := setupSuite(t)
cleanTables(t, suite.pool)
ctx := context.Background()
albumID := insertTestArtistAndAlbum(t, suite)
workflowRepo := database.NewWorkflowRunRepository(suite.pool)
run := &database.WorkflowRun{AlbumID: albumID, Quality: "flac"}
err := workflowRepo.Create(ctx, run)
require.NoError(t, err)
_, err = suite.pool.Exec(ctx, `
UPDATE workflow_runs SET started_at = NOW() - INTERVAL '10 minutes' WHERE id = $1
`, run.ID)
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, 'test-hash', 'test-tracker', 'Test Torrent', '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)
VALUES ($1, $2, 'FLAC', '16-44', 'downloading', 'test-hash')
`, torrentID, albumID)
require.NoError(t, err)
service := createRecoveryTestService(suite)
service.RecoverWorkflows(ctx)
updated, err := workflowRepo.GetByID(ctx, run.ID)
require.NoError(t, err)
assert.Equal(t, "completed", updated.Status)
}
func TestRecovery_StaleWorkflowWithoutDownload(t *testing.T) {
suite := setupSuite(t)
cleanTables(t, suite.pool)
ctx := context.Background()
albumID := insertTestArtistAndAlbum(t, suite)
workflowRepo := database.NewWorkflowRunRepository(suite.pool)
run := &database.WorkflowRun{AlbumID: albumID, Quality: "flac"}
err := workflowRepo.Create(ctx, run)
require.NoError(t, err)
_, err = suite.pool.Exec(ctx, `
UPDATE workflow_runs SET started_at = NOW() - INTERVAL '10 minutes' WHERE id = $1
`, run.ID)
require.NoError(t, err)
service := createRecoveryTestService(suite)
service.RecoverWorkflows(ctx)
updated, err := workflowRepo.GetByID(ctx, run.ID)
require.NoError(t, err)
assert.Equal(t, "failed", updated.Status)
require.NotNil(t, updated.ErrorMessage)
assert.Contains(t, *updated.ErrorMessage, "server restarted")
}
func createRecoveryTestService(suite *testSuite) *internal.MusicAgregatorService {
return internal.NewMusicAgregatorServiceWithDeps(
nil,
nil,
nil,
nil,
nil,
nil,
suite.db,
)
}