package store import ( "context" "errors" "testing" ) // Запись байтов .torrent на ветке создания и чтение их воркером. func TestTorrentBlobWriteRead(t *testing.T) { st := newTestStore(t) ctx := context.Background() blob := []byte("d8:announce…fake torrent bytes") d := &Download{SourceType: SourceTorrent, SourceRef: "Some.Release", State: StateCatched} existing, err := st.CreateDownloadIfNoActive(ctx, d, []string{hashN(1)}, blob) if err != nil || existing != nil { t.Fatalf("create: err=%v existing=%v", err, existing) } got, err := st.GetTorrentData(ctx, d.ID) if err != nil { t.Fatalf("get: %v", err) } if string(got) != string(blob) { t.Errorf("blob = %q, want %q", got, blob) } } // При дедупе на активную задачу байты не пишутся (второй приём того же хеша). func TestTorrentBlobNotWrittenOnDedup(t *testing.T) { st := newTestStore(t) ctx := context.Background() first := &Download{SourceType: SourceTorrent, SourceRef: "A", State: StateCatched} if _, err := st.CreateDownloadIfNoActive(ctx, first, []string{hashN(2)}, []byte("first")); err != nil { t.Fatalf("create first: %v", err) } // Второй приём того же инфохэша — дедуп; его байты писаться не должны. second := &Download{SourceType: SourceTorrent, SourceRef: "B", State: StateCatched} existing, err := st.CreateDownloadIfNoActive(ctx, second, []string{hashN(2)}, []byte("second")) if err != nil { t.Fatalf("create second: %v", err) } if existing == nil || existing.ID != first.ID { t.Fatalf("ожидался дедуп на первую задачу, got %v", existing) } got, err := st.GetTorrentData(ctx, first.ID) if err != nil { t.Fatalf("get first: %v", err) } if string(got) != "first" { t.Errorf("байты первой задачи перезаписаны: %q", got) } } // Нет байтов (magnet-источник) → ErrNotFound. func TestTorrentBlobMissing(t *testing.T) { st := newTestStore(t) ctx := context.Background() d := &Download{SourceType: SourceMagnet, SourceRef: "magnet:?xt=urn:btih:" + hashN(3), State: StateCatched} if _, err := st.CreateDownloadIfNoActive(ctx, d, []string{hashN(3)}, nil); err != nil { t.Fatalf("create: %v", err) } if _, err := st.GetTorrentData(ctx, d.ID); !errors.Is(err, ErrNotFound) { t.Errorf("ожидался ErrNotFound, got %v", err) } } // F6: апгрейд пойманного magnet до torrent сохраняет байты и меняет source_type. func TestUpgradeCatchedMagnetToTorrent(t *testing.T) { st := newTestStore(t) ctx := context.Background() d := &Download{SourceType: SourceMagnet, SourceRef: "magnet:?xt=urn:btih:" + hashN(1), State: StateCatched} if _, err := st.CreateDownloadIfNoActive(ctx, d, []string{hashN(1)}, nil); err != nil { t.Fatalf("create: %v", err) } blob := []byte("d8:announce…real torrent") upgraded, err := st.UpgradeCatchedMagnetToTorrent(ctx, d.ID, blob) if err != nil || !upgraded { t.Fatalf("upgrade: upgraded=%v err=%v", upgraded, err) } got, err := st.GetDownload(ctx, d.ID) if err != nil { t.Fatalf("get: %v", err) } if got.SourceType != SourceTorrent { t.Errorf("source_type = %q, want torrent", got.SourceType) } data, err := st.GetTorrentData(ctx, d.ID) if err != nil { t.Fatalf("get torrent data: %v", err) } if string(data) != string(blob) { t.Errorf("blob = %q, want %q", data, blob) } } // Апгрейд применим только в catched: уже добавленный (downloading) magnet не // трогаем — его судьба решается retry/сверкой, а не приёмом. func TestUpgradeSkippedWhenNotCatched(t *testing.T) { st := newTestStore(t) ctx := context.Background() d := &Download{SourceType: SourceMagnet, SourceRef: "m", State: StateDownloading} if _, err := st.CreateDownloadIfNoActive(ctx, d, []string{hashN(2)}, nil); err != nil { t.Fatalf("create: %v", err) } upgraded, err := st.UpgradeCatchedMagnetToTorrent(ctx, d.ID, []byte("bytes")) if err != nil { t.Fatalf("upgrade: %v", err) } if upgraded { t.Error("downloading-magnet апгрейдить не должны") } got, err := st.GetDownload(ctx, d.ID) if err != nil { t.Fatalf("get: %v", err) } if got.SourceType != SourceMagnet { t.Errorf("source_type сменился на %q, а не должен", got.SourceType) } if _, err := st.GetTorrentData(ctx, d.ID); !errors.Is(err, ErrNotFound) { t.Errorf("байты не должны сохраняться, got err=%v", err) } } // Не-magnet источник апгрейд не трогает (гард source_type=magnet), байты не // перезаписывает. func TestUpgradeSkippedForTorrentSource(t *testing.T) { st := newTestStore(t) ctx := context.Background() d := &Download{SourceType: SourceTorrent, SourceRef: "Rel", State: StateCatched} if _, err := st.CreateDownloadIfNoActive(ctx, d, []string{hashN(3)}, []byte("orig")); err != nil { t.Fatalf("create: %v", err) } upgraded, err := st.UpgradeCatchedMagnetToTorrent(ctx, d.ID, []byte("new")) if err != nil { t.Fatalf("upgrade: %v", err) } if upgraded { t.Error("torrent-источник апгрейдить не нужно") } data, err := st.GetTorrentData(ctx, d.ID) if err != nil { t.Fatalf("get torrent data: %v", err) } if string(data) != "orig" { t.Errorf("байты перезаписаны: %q", data) } } // Пустые байты — защитный no-op. func TestUpgradeEmptyBlobNoOp(t *testing.T) { st := newTestStore(t) ctx := context.Background() d := &Download{SourceType: SourceMagnet, SourceRef: "m", State: StateCatched} if _, err := st.CreateDownloadIfNoActive(ctx, d, []string{hashN(4)}, nil); err != nil { t.Fatalf("create: %v", err) } upgraded, err := st.UpgradeCatchedMagnetToTorrent(ctx, d.ID, nil) if err != nil || upgraded { t.Fatalf("пустой блоб: upgraded=%v err=%v", upgraded, err) } got, err := st.GetDownload(ctx, d.ID) if err != nil { t.Fatalf("get: %v", err) } if got.SourceType != SourceMagnet { t.Errorf("source_type сменился на %q при пустом блобе", got.SourceType) } }