191 lines
7.4 KiB
Go
191 lines
7.4 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
func seriesEpisode(id, seriesID string) Episode {
|
|
ep := sampleEpisode(id)
|
|
ep.SeriesID = seriesID
|
|
return ep
|
|
}
|
|
|
|
func TestRegisterSubscriptionSnapshotAndRollback(t *testing.T) {
|
|
pool := testPool(t, true)
|
|
s := New(pool)
|
|
ctx := context.Background()
|
|
episodes := []Episode{seriesEpisode("ep1", "series-1"), seriesEpisode("ep2", "series-1")}
|
|
created, err := s.RegisterSubscription(ctx, "series-1", "番組", episodes, false)
|
|
if err != nil || !created {
|
|
t.Fatalf("既存回をダウンロードしない登録: %t %v", created, err)
|
|
}
|
|
subs, err := s.ListSubscriptions(ctx)
|
|
if err != nil || len(subs) != 1 || subs[0].LastCheckedAt == nil || subs[0].LastCheckedAt.Before(subs[0].CreatedAt) {
|
|
t.Fatalf("登録時の最終確認日時: %+v %v", subs, err)
|
|
}
|
|
unknown, err := s.UnknownEpisodeIDs(ctx, "series-1", []string{"ep1", "ep2", "ep3"})
|
|
if err != nil || len(unknown) != 1 || unknown[0] != "ep3" {
|
|
t.Fatalf("既知の記録: %v %v", unknown, err)
|
|
}
|
|
var jobs int
|
|
if err := pool.QueryRow(ctx, `SELECT count(*) FROM download_jobs WHERE series_id='series-1'`).Scan(&jobs); err != nil || jobs != 0 {
|
|
t.Fatalf("既存回が投入された: %d %v", jobs, err)
|
|
}
|
|
created, err = s.RegisterSubscription(ctx, "series-1", "別の名前", episodes, true)
|
|
if err != nil || created {
|
|
t.Fatalf("重複登録: %t %v", created, err)
|
|
}
|
|
if err := pool.QueryRow(ctx, `SELECT count(*) FROM download_jobs WHERE series_id='series-1'`).Scan(&jobs); err != nil || jobs != 0 {
|
|
t.Fatalf("重複登録が投入した: %d %v", jobs, err)
|
|
}
|
|
bad := seriesEpisode("bad", "series-bad")
|
|
bad.DurationSeconds = -1
|
|
created, err = s.RegisterSubscription(ctx, "series-bad", "番組", []Episode{seriesEpisode("good", "series-bad"), bad}, true)
|
|
if err == nil || created {
|
|
t.Fatalf("壊れた回の登録が成功: %t %v", created, err)
|
|
}
|
|
for _, table := range []string{"subscriptions", "known_episodes", "download_jobs"} {
|
|
var count int
|
|
if err := pool.QueryRow(ctx, `SELECT count(*) FROM `+table+` WHERE series_id='series-bad'`).Scan(&count); err != nil || count != 0 {
|
|
t.Errorf("取引の巻き戻し %s: %d %v", table, count, err)
|
|
}
|
|
}
|
|
deleted, err := s.DeleteSubscription(ctx, "series-1")
|
|
if err != nil || !deleted {
|
|
t.Fatalf("解除: %t %v", deleted, err)
|
|
}
|
|
if err := pool.QueryRow(ctx, `SELECT count(*) FROM known_episodes WHERE series_id='series-1'`).Scan(&jobs); err != nil || jobs != 0 {
|
|
t.Fatalf("解除後に既知が残った: %d %v", jobs, err)
|
|
}
|
|
created, err = s.RegisterSubscription(ctx, "series-1", "番組", episodes, true)
|
|
if err != nil || !created {
|
|
t.Fatalf("解除後の再登録: %t %v", created, err)
|
|
}
|
|
if err := pool.QueryRow(ctx, `SELECT count(*) FROM download_jobs WHERE series_id='series-1'`).Scan(&jobs); err != nil || jobs != 2 {
|
|
t.Fatalf("再登録で既存回を選び直せない: %d %v", jobs, err)
|
|
}
|
|
}
|
|
|
|
func TestRegisterAndQueueUnseenEpisodes(t *testing.T) {
|
|
pool := testPool(t, true)
|
|
s := New(pool)
|
|
ctx := context.Background()
|
|
manual, _, err := s.Enqueue(ctx, seriesEpisode("manual", "series-2"), "manual")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
created, err := s.RegisterSubscription(ctx, "series-2", "番組", []Episode{seriesEpisode("manual", "series-2"), seriesEpisode("past", "series-2")}, true)
|
|
if err != nil || !created {
|
|
t.Fatalf("公開中の回を投入: %t %v", created, err)
|
|
}
|
|
manualAfter, err := s.GetJob(ctx, manual.ID)
|
|
if err != nil || manualAfter.Source != "manual" {
|
|
t.Fatalf("既存の手動ジョブを変更: %+v %v", manualAfter, err)
|
|
}
|
|
var subscriptionJobs int
|
|
if err := pool.QueryRow(ctx, `SELECT count(*) FROM download_jobs WHERE series_id='series-2' AND source='subscription' AND status='queued'`).Scan(&subscriptionJobs); err != nil || subscriptionJobs != 1 {
|
|
t.Fatalf("登録時に作ったジョブ: %d %v", subscriptionJobs, err)
|
|
}
|
|
subs, err := s.ListSubscriptions(ctx)
|
|
if err != nil || len(subs) != 1 || subs[0].JobCount != 2 || !subs[0].Enabled || subs[0].LastCheckedAt == nil {
|
|
t.Fatalf("登録一覧と関連ジョブ数: %+v %v", subs, err)
|
|
}
|
|
unknown, err := s.UnknownEpisodeIDs(ctx, "series-2", []string{"past", "new", "manual", "new"})
|
|
if err != nil || len(unknown) != 1 || unknown[0] != "new" {
|
|
t.Fatalf("未確認の抽出: %v %v", unknown, err)
|
|
}
|
|
if err := s.SetSubscriptionEnabled(ctx, "series-2", false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.QueueUnseenEpisodes(ctx, "series-2", []Episode{seriesEpisode("new", "series-2")}); !errors.Is(err, ErrSubscriptionPaused) {
|
|
t.Fatalf("一時停止中の投入: %v", err)
|
|
}
|
|
unknown, err = s.UnknownEpisodeIDs(ctx, "series-2", []string{"new"})
|
|
if err != nil || len(unknown) != 1 {
|
|
t.Fatalf("一時停止中に既知となった: %v %v", unknown, err)
|
|
}
|
|
if err := s.SetSubscriptionEnabled(ctx, "series-2", true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
invalid := seriesEpisode("invalid", "series-2")
|
|
invalid.DurationSeconds = -1
|
|
if _, err := s.QueueUnseenEpisodes(ctx, "series-2", []Episode{seriesEpisode("newer", "series-2"), invalid}); err == nil {
|
|
t.Fatal("新規回の投入が途中失敗でも確定した")
|
|
}
|
|
unknown, err = s.UnknownEpisodeIDs(ctx, "series-2", []string{"newer"})
|
|
if err != nil || len(unknown) != 1 {
|
|
t.Fatalf("新規回の投入失敗が巻き戻らない: %v %v", unknown, err)
|
|
}
|
|
count, err := s.QueueUnseenEpisodes(ctx, "series-2", []Episode{seriesEpisode("past", "series-2"), seriesEpisode("new", "series-2")})
|
|
if err != nil || count != 1 {
|
|
t.Fatalf("再開後の新しい回: %d %v", count, err)
|
|
}
|
|
count, err = s.QueueUnseenEpisodes(ctx, "series-2", []Episode{seriesEpisode("new", "series-2")})
|
|
if err != nil || count != 0 {
|
|
t.Fatalf("重複した新しい回: %d %v", count, err)
|
|
}
|
|
var source string
|
|
if err := pool.QueryRow(ctx, `SELECT source FROM download_jobs WHERE episode_id='new'`).Scan(&source); err != nil || source != "subscription" {
|
|
t.Fatalf("新しい回の登録元: %q %v", source, err)
|
|
}
|
|
statuses, err := s.SubscribedSeries(ctx, []string{"series-2", "missing"})
|
|
if err != nil || !statuses["series-2"] || statuses["missing"] {
|
|
t.Fatalf("登録済みシリーズの一括取得: %v %v", statuses, err)
|
|
}
|
|
}
|
|
|
|
func TestQueueUnseenEpisodesConcurrent(t *testing.T) {
|
|
pool := testPool(t, true)
|
|
s := New(pool)
|
|
ctx := context.Background()
|
|
created, err := s.RegisterSubscription(ctx, "series-concurrent", "番組", nil, false)
|
|
if err != nil || !created {
|
|
t.Fatalf("登録: %t %v", created, err)
|
|
}
|
|
episodes := []Episode{
|
|
seriesEpisode("concurrent-1", "series-concurrent"),
|
|
seriesEpisode("concurrent-2", "series-concurrent"),
|
|
}
|
|
const workers = 8
|
|
counts := make(chan int, workers)
|
|
errs := make(chan error, workers)
|
|
var wg sync.WaitGroup
|
|
for range workers {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
count, err := s.QueueUnseenEpisodes(ctx, "series-concurrent", episodes)
|
|
counts <- count
|
|
errs <- err
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
close(counts)
|
|
close(errs)
|
|
for err := range errs {
|
|
if err != nil {
|
|
t.Errorf("同時投入: %v", err)
|
|
}
|
|
}
|
|
total := 0
|
|
positive := 0
|
|
for count := range counts {
|
|
total += count
|
|
if count > 0 {
|
|
positive++
|
|
}
|
|
}
|
|
if total != 2 || positive != 1 {
|
|
t.Fatalf("同時投入の記録件数: total=%d positive workers=%d", total, positive)
|
|
}
|
|
for table := range map[string]struct{}{"known_episodes": {}, "download_jobs": {}} {
|
|
var count int
|
|
if err := pool.QueryRow(ctx, `SELECT count(*) FROM `+table+` WHERE series_id='series-concurrent'`).Scan(&count); err != nil || count != 2 {
|
|
t.Errorf("%s の重複防止: %d %v", table, count, err)
|
|
}
|
|
}
|
|
}
|