package store import ( "context" "testing" ) func newTestStore(t *testing.T) *Store { t.Helper() st, err := Open(t.TempDir() + "/test.db") if err != nil { t.Fatalf("open store: %v", err) } t.Cleanup(func() { _ = st.Close() }) return st } func newDownloading(infohash string) *Download { return &Download{ SourceType: SourceMagnet, SourceRef: "magnet:?xt=urn:btih:" + infohash, Context: "ctx", Infohash: NullString(infohash), IdempotencyKey: NullString(infohash), State: StateDownloading, } } func TestCreateAndGetDownload(t *testing.T) { st := newTestStore(t) ctx := context.Background() id, err := st.CreateDownload(ctx, newDownloading("aabbccddeeff00112233445566778899aabbccdd")) if err != nil { t.Fatalf("create: %v", err) } got, err := st.GetDownload(ctx, id) if err != nil { t.Fatalf("get: %v", err) } if got.State != StateDownloading { t.Errorf("state = %q, want downloading", got.State) } if got.Context != "ctx" { t.Errorf("context = %q", got.Context) } if got.CreatedAt == "" { t.Error("created_at пуст") } } func TestFindActiveByInfohash(t *testing.T) { st := newTestStore(t) ctx := context.Background() const ih = "1111111111111111111111111111111111111111" if d, err := st.FindActiveByInfohash(ctx, ih); err != nil || d != nil { t.Fatalf("ожидался (nil,nil), получили (%v,%v)", d, err) } id, err := st.CreateDownload(ctx, newDownloading(ih)) if err != nil { t.Fatal(err) } d, err := st.FindActiveByInfohash(ctx, ih) if err != nil { t.Fatal(err) } if d == nil || d.ID != id { t.Fatalf("активная задача не найдена: %v", d) } } // Терминальное состояние снимает ключ идемпотентности и позволяет завести // тот же infohash заново (повторная закачка спустя время). func TestTerminalReleasesInfohash(t *testing.T) { st := newTestStore(t) ctx := context.Background() const ih = "2222222222222222222222222222222222222222" id, err := st.CreateDownload(ctx, newDownloading(ih)) if err != nil { t.Fatal(err) } if err := st.SetDownloadState(ctx, id, StateFailed, "qbit_add", "boom"); err != nil { t.Fatal(err) } // После терминального состояния активной задачи нет. if d, err := st.FindActiveByInfohash(ctx, ih); err != nil || d != nil { t.Fatalf("после failed активная задача не должна находиться: (%v,%v)", d, err) } // Ключ идемпотентности снят. got, err := st.GetDownload(ctx, id) if err != nil { t.Fatal(err) } if got.IdempotencyKey.Valid { t.Errorf("idempotency_key должен быть NULL, получили %q", got.IdempotencyKey.String) } if got.ErrorCode.String != "qbit_add" { t.Errorf("error_code = %q", got.ErrorCode.String) } // Тот же infohash заводится заново — unique index не мешает. id2, err := st.CreateDownload(ctx, newDownloading(ih)) if err != nil { t.Fatalf("повторное добавление после терминального должно проходить: %v", err) } if id2 == id { t.Error("ожидалась новая задача") } } // Две активные задачи с одним ключом идемпотентности недопустимы. func TestActiveDuplicateRejected(t *testing.T) { st := newTestStore(t) ctx := context.Background() const ih = "3333333333333333333333333333333333333333" if _, err := st.CreateDownload(ctx, newDownloading(ih)); err != nil { t.Fatal(err) } if _, err := st.CreateDownload(ctx, newDownloading(ih)); err == nil { t.Error("ожидалось нарушение уникальности idempotency_key") } } func TestListAndByState(t *testing.T) { st := newTestStore(t) ctx := context.Background() id1, _ := st.CreateDownload(ctx, newDownloading("4444444444444444444444444444444444444444")) id2, _ := st.CreateDownload(ctx, newDownloading("5555555555555555555555555555555555555555")) if err := st.SetDownloadState(ctx, id2, StateCompleted, "", ""); err != nil { t.Fatal(err) } all, err := st.ListDownloads(ctx) if err != nil { t.Fatal(err) } if len(all) != 2 { t.Fatalf("ListDownloads = %d, want 2", len(all)) } dl, err := st.ListDownloadsByState(ctx, StateDownloading) if err != nil { t.Fatal(err) } if len(dl) != 1 || dl[0].ID != id1 { t.Fatalf("ListDownloadsByState(downloading) = %v", dl) } }