feat: add thumbnail preview, audio-only detection, /download command, tests, and bugfixes
- Add thumbnail preview with metadata (title, uploader, duration) before download - Auto-detect audio-only sites (Spotify, SoundCloud, etc.) and skip type selection - Add /download command for feature parity with UI button - Fix yt-dlp defer bug: StatusFailed was overwritten to StatusCompleted - Fix handleURLInput using editText with msgID=0 (now uses sendWithKB) - Fix filename detection: use job ID prefix for reliable file finding - Fix double answerCb between handleBackStep and handleDownloadPrompt - Fix runDownload unused msgID parameter - Fix context for download goroutine (use context.Background) - Remove unused startWebhook h parameter - Remove dead buildToggleKeyboard function - Add formatBytes, formatDuration, formatLabelForDisplay tests - Add IsAudioOnlyContent, MediaType.String tests - Add repo integration tests (users, preferences, quotas, history, delete) - Add cmd/bot tests (parseAllowedUsers, env helpers) - Add edge case tests for progress parser - Add zero/negative safety guard in formatDuration - Add .gitea, TODO.md, opencode.json to gitignore
This commit is contained in:
214
internal/repo/store_test.go
Normal file
214
internal/repo/store_test.go
Normal file
@@ -0,0 +1,214 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"uptodownBot/internal/domain"
|
||||
)
|
||||
|
||||
func newTestStore(t *testing.T) *Store {
|
||||
t.Helper()
|
||||
s, err := NewStore("file::memory:?cache=shared")
|
||||
if err != nil {
|
||||
t.Fatalf("NewStore() failed: %v", err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func TestGetOrCreateUser(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
defer s.Close()
|
||||
|
||||
id, err := s.GetOrCreateUser(12345)
|
||||
if err != nil {
|
||||
t.Fatalf("GetOrCreateUser() failed: %v", err)
|
||||
}
|
||||
if id == 0 {
|
||||
t.Fatal("GetOrCreateUser() returned 0")
|
||||
}
|
||||
|
||||
// Second call should return same ID
|
||||
id2, err := s.GetOrCreateUser(12345)
|
||||
if err != nil {
|
||||
t.Fatalf("GetOrCreateUser() second call failed: %v", err)
|
||||
}
|
||||
if id != id2 {
|
||||
t.Errorf("GetOrCreateUser() returned different IDs: %d vs %d", id, id2)
|
||||
}
|
||||
|
||||
// Different chat_id returns different user ID
|
||||
id3, err := s.GetOrCreateUser(67890)
|
||||
if err != nil {
|
||||
t.Fatalf("GetOrCreateUser() third call failed: %v", err)
|
||||
}
|
||||
if id3 == id {
|
||||
t.Error("GetOrCreateUser() should return different IDs for different chat_ids")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreferences(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
defer s.Close()
|
||||
|
||||
userID, err := s.GetOrCreateUser(12345)
|
||||
if err != nil {
|
||||
t.Fatalf("GetOrCreateUser() failed: %v", err)
|
||||
}
|
||||
|
||||
prefs, err := s.GetOrCreatePreferences(userID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetOrCreatePreferences() failed: %v", err)
|
||||
}
|
||||
if prefs.DefaultMediaType != domain.MediaTypeVideoAudio {
|
||||
t.Errorf("default media type = %v, want VideoAudio", prefs.DefaultMediaType)
|
||||
}
|
||||
|
||||
prefs.DefaultMediaType = domain.MediaTypeAudio
|
||||
prefs.DefaultQuality = "best"
|
||||
prefs.DefaultContainer = "mp3"
|
||||
prefs.DefaultLanguage = "en"
|
||||
if err := s.UpdatePreferences(userID, prefs); err != nil {
|
||||
t.Fatalf("UpdatePreferences() failed: %v", err)
|
||||
}
|
||||
|
||||
got, err := s.GetOrCreatePreferences(userID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetOrCreatePreferences() after update failed: %v", err)
|
||||
}
|
||||
if got.DefaultMediaType != domain.MediaTypeAudio {
|
||||
t.Errorf("media type = %v, want Audio", got.DefaultMediaType)
|
||||
}
|
||||
if got.DefaultQuality != "best" {
|
||||
t.Errorf("quality = %q, want best", got.DefaultQuality)
|
||||
}
|
||||
if got.DefaultContainer != "mp3" {
|
||||
t.Errorf("container = %q, want mp3", got.DefaultContainer)
|
||||
}
|
||||
if got.DefaultLanguage != "en" {
|
||||
t.Errorf("language = %q, want en", got.DefaultLanguage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuotas(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
defer s.Close()
|
||||
|
||||
userID, err := s.GetOrCreateUser(12345)
|
||||
if err != nil {
|
||||
t.Fatalf("GetOrCreateUser() failed: %v", err)
|
||||
}
|
||||
|
||||
q, err := s.GetOrCreateQuotas(userID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetOrCreateQuotas() failed: %v", err)
|
||||
}
|
||||
if q.DailyLimitMB != domain.DefaultDailyQuotaMB {
|
||||
t.Errorf("daily limit = %d, want %d", q.DailyLimitMB, domain.DefaultDailyQuotaMB)
|
||||
}
|
||||
|
||||
q.DailyUsedMB = 50
|
||||
q.WeeklyUsedMB = 200
|
||||
q.MonthlyUsedMB = 500
|
||||
if err := s.UpdateQuotas(userID, q); err != nil {
|
||||
t.Fatalf("UpdateQuotas() failed: %v", err)
|
||||
}
|
||||
|
||||
got, err := s.GetOrCreateQuotas(userID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetOrCreateQuotas() after update failed: %v", err)
|
||||
}
|
||||
if got.DailyUsedMB != 50 {
|
||||
t.Errorf("daily used = %d, want 50", got.DailyUsedMB)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHistory(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
defer s.Close()
|
||||
|
||||
userID, err := s.GetOrCreateUser(12345)
|
||||
if err != nil {
|
||||
t.Fatalf("GetOrCreateUser() failed: %v", err)
|
||||
}
|
||||
|
||||
rec := &domain.DownloadRecord{
|
||||
URL: "https://example.com/video",
|
||||
Title: "Test Video",
|
||||
MediaType: "video",
|
||||
FormatID: "137",
|
||||
Container: "mp4",
|
||||
FileSize: 1048576,
|
||||
Status: "completed",
|
||||
}
|
||||
if err := s.AddHistory(userID, rec); err != nil {
|
||||
t.Fatalf("AddHistory() failed: %v", err)
|
||||
}
|
||||
|
||||
records, err := s.GetHistory(userID, 10, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("GetHistory() failed: %v", err)
|
||||
}
|
||||
if len(records) != 1 {
|
||||
t.Fatalf("GetHistory() returned %d records, want 1", len(records))
|
||||
}
|
||||
if records[0].URL != rec.URL {
|
||||
t.Errorf("record URL = %q, want %q", records[0].URL, rec.URL)
|
||||
}
|
||||
|
||||
count, err := s.GetHistoryCount(userID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetHistoryCount() failed: %v", err)
|
||||
}
|
||||
if count != 1 {
|
||||
t.Errorf("history count = %d, want 1", count)
|
||||
}
|
||||
|
||||
// Test pagination
|
||||
records2, err := s.GetHistory(userID, 10, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("GetHistory() with offset failed: %v", err)
|
||||
}
|
||||
if len(records2) != 0 {
|
||||
t.Errorf("GetHistory() with offset 1 returned %d records, want 0", len(records2))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteUserData(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
defer s.Close()
|
||||
|
||||
userID, err := s.GetOrCreateUser(12345)
|
||||
if err != nil {
|
||||
t.Fatalf("GetOrCreateUser() failed: %v", err)
|
||||
}
|
||||
|
||||
_, err = s.GetOrCreatePreferences(userID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetOrCreatePreferences() failed: %v", err)
|
||||
}
|
||||
_, err = s.GetOrCreateQuotas(userID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetOrCreateQuotas() failed: %v", err)
|
||||
}
|
||||
s.AddHistory(userID, &domain.DownloadRecord{
|
||||
URL: "https://example.com", Title: "Test", Status: "completed",
|
||||
})
|
||||
|
||||
if err := s.DeleteUserData(userID); err != nil {
|
||||
t.Fatalf("DeleteUserData() failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify user no longer exists
|
||||
_, err = s.GetOrCreateUser(12345)
|
||||
if err != nil {
|
||||
t.Fatalf("GetOrCreateUser() after delete failed: %v", err)
|
||||
}
|
||||
|
||||
count, err := s.GetHistoryCount(userID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetHistoryCount() after delete failed: %v", err)
|
||||
}
|
||||
if count != 0 {
|
||||
t.Errorf("history count after delete = %d, want 0", count)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user