164 lines
5.9 KiB
Go
164 lines
5.9 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
func completeTestJob(t *testing.T, s *Store, id string) Job {
|
|
t.Helper()
|
|
ctx := context.Background()
|
|
job, _, err := s.Enqueue(ctx, sampleEpisode(id), "manual")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
claimed, err := s.ClaimNext(ctx)
|
|
if err != nil || claimed.ID != job.ID {
|
|
t.Fatalf("claim: %+v %v", claimed, err)
|
|
}
|
|
if err := s.Complete(ctx, job.ID, "番組/video.mp4", 1234); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
result, err := s.GetSucceededJob(ctx, job.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func TestLibrarySeriesAggregationAndJobs(t *testing.T) {
|
|
s := New(testPool(t, true))
|
|
ctx := context.Background()
|
|
complete := func(ep Episode, size int64, finished time.Time) Job {
|
|
t.Helper()
|
|
job, _, err := s.Enqueue(ctx, ep, "manual")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
claimed, err := s.ClaimNext(ctx)
|
|
if err != nil || claimed.ID != job.ID {
|
|
t.Fatalf("claim: %+v %v", claimed, err)
|
|
}
|
|
if err := s.Complete(ctx, job.ID, ep.SeriesTitle+"/video.mp4", size); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.pool.Exec(ctx, `UPDATE download_jobs SET finished_at=$2 WHERE id=$1`, job.ID, finished); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
result, err := s.GetSucceededJob(ctx, job.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return result
|
|
}
|
|
older := time.Date(2026, 10, 1, 0, 0, 0, 0, time.UTC)
|
|
newer := older.Add(time.Hour)
|
|
newest := newer.Add(time.Hour)
|
|
first := complete(Episode{ID: "aggregate-a1", SeriesID: "seriesA", SeriesTitle: "旧番組名", Title: "第1話", BroadcasterName: "旧局", ThumbnailPath: "/old.jpg"}, 100, older)
|
|
second := complete(Episode{ID: "aggregate-a2", SeriesID: "seriesA", SeriesTitle: "新番組名", Title: "第2話", BroadcasterName: "新局", ThumbnailPath: "/new.jpg"}, 250, newer)
|
|
complete(Episode{ID: "aggregate-b1", SeriesID: "seriesB", SeriesTitle: "別番組", Title: "第1話", BroadcasterName: "別局", ThumbnailPath: "/other.jpg"}, 500, newest)
|
|
if _, err := s.pool.Exec(ctx, `UPDATE download_jobs SET file_size_bytes=NULL WHERE id=$1`, first.ID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.pool.Exec(ctx, `INSERT INTO subscriptions (series_id, series_title) VALUES ('seriesA', '登録名')`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
series, err := s.ListLibrarySeries(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(series) != 2 || series[0].SeriesID != "seriesB" || series[1].SeriesID != "seriesA" {
|
|
t.Fatalf("番組の並び: %+v", series)
|
|
}
|
|
got := series[1]
|
|
if got.SeriesTitle != "新番組名" || got.LatestTitle != "第2話" || got.BroadcasterName != "新局" || got.ThumbnailPath != "/new.jpg" || got.EpisodeCount != 2 || got.TotalSizeBytes != 250 || got.LatestFinishedAt == nil || !got.LatestFinishedAt.Equal(newer) || !got.Subscribed {
|
|
t.Fatalf("番組集計: %+v", got)
|
|
}
|
|
if series[0].Subscribed || series[0].TotalSizeBytes != 500 {
|
|
t.Fatalf("未登録番組: %+v", series[0])
|
|
}
|
|
jobs, err := s.ListSucceededJobsBySeries(ctx, "seriesA")
|
|
if err != nil || len(jobs) != 2 || jobs[0].ID != second.ID || jobs[1].ID != first.ID || jobs[1].FileSizeBytes != nil {
|
|
t.Fatalf("番組の回: %+v %v", jobs, err)
|
|
}
|
|
if jobs, err := s.ListSucceededJobsBySeries(ctx, "missing"); err != nil || len(jobs) != 0 {
|
|
t.Fatalf("存在しない番組: %+v %v", jobs, err)
|
|
}
|
|
if _, err := s.ListSucceededJobsBySeries(ctx, ""); err == nil {
|
|
t.Fatal("空の番組IDを許した")
|
|
}
|
|
}
|
|
|
|
func TestLibraryStoreOperations(t *testing.T) {
|
|
s := New(testPool(t, true))
|
|
ctx := context.Background()
|
|
first := completeTestJob(t, s, "library-1")
|
|
second := completeTestJob(t, s, "library-2")
|
|
jobs, err := s.ListSucceededJobs(ctx, 1)
|
|
if err != nil || len(jobs) != 1 || jobs[0].ID != second.ID {
|
|
t.Fatalf("完了一覧: %+v %v", jobs, err)
|
|
}
|
|
if first.FilePath == nil || *first.FilePath != "番組/video.mp4" || first.FileSizeBytes == nil || *first.FileSizeBytes != 1234 || first.FinishedAt == nil {
|
|
t.Fatalf("完了情報: %+v", first)
|
|
}
|
|
queued, _, err := s.Enqueue(ctx, sampleEpisode("not-finished"), "manual")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.GetSucceededJob(ctx, queued.ID); !errors.Is(err, pgx.ErrNoRows) {
|
|
t.Fatalf("未完了の取得: %v", err)
|
|
}
|
|
if err := s.DeleteSucceededJob(ctx, queued.ID); !errors.Is(err, ErrInvalidTransition) {
|
|
t.Fatalf("未完了の削除: %v", err)
|
|
}
|
|
if err := s.DeleteSucceededJob(ctx, first.ID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.GetJob(ctx, first.ID); !errors.Is(err, pgx.ErrNoRows) {
|
|
t.Fatalf("削除後の取得: %v", err)
|
|
}
|
|
if err := s.DeleteSucceededJob(ctx, first.ID); !errors.Is(err, ErrInvalidTransition) {
|
|
t.Fatalf("再削除: %v", err)
|
|
}
|
|
if _, err := s.ListSucceededJobs(ctx, 0); err == nil {
|
|
t.Fatal("0件の上限を許した")
|
|
}
|
|
}
|
|
|
|
func TestDeleteSucceededJobKeepsKnownEpisodeAndAllowsManualRequeue(t *testing.T) {
|
|
s := New(testPool(t, true))
|
|
ctx := context.Background()
|
|
ep := seriesEpisode("known-library", "known-series")
|
|
created, err := s.RegisterSubscription(ctx, "known-series", "番組", []Episode{ep}, false)
|
|
if err != nil || !created {
|
|
t.Fatalf("番組登録: %t %v", created, err)
|
|
}
|
|
job, created, err := s.Enqueue(ctx, ep, "manual")
|
|
if err != nil || !created {
|
|
t.Fatalf("手動投入: %+v %t %v", job, created, err)
|
|
}
|
|
claimed, err := s.ClaimNext(ctx)
|
|
if err != nil || claimed.ID != job.ID {
|
|
t.Fatalf("取得: %+v %v", claimed, err)
|
|
}
|
|
if err := s.Complete(ctx, job.ID, "番組/video.mp4", 10); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.DeleteSucceededJob(ctx, job.ID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
unknown, err := s.UnknownEpisodeIDs(ctx, "known-series", []string{ep.ID})
|
|
if err != nil || len(unknown) != 0 {
|
|
t.Fatalf("削除後の既知記録: %v %v", unknown, err)
|
|
}
|
|
requeued, created, err := s.Enqueue(ctx, ep, "manual")
|
|
if err != nil || !created || requeued.Status != "queued" || requeued.Source != "manual" {
|
|
t.Fatalf("削除後の手動再投入: %+v %t %v", requeued, created, err)
|
|
}
|
|
}
|