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:
2026-06-25 15:07:31 +03:30
parent 05fcdf7458
commit cd27b4d28d
15 changed files with 631 additions and 64 deletions

View File

@@ -0,0 +1,84 @@
package handler
import (
"testing"
"uptodownBot/internal/domain"
)
func TestFormatBytes(t *testing.T) {
tests := []struct {
input int64
want string
}{
{0, "0B"},
{1, "1B"},
{1023, "1023B"},
{1024, "1.0KB"},
{1536, "1.5KB"},
{1048576, "1.0MB"},
{1073741824, "1.0GB"},
{1099511627776, "1.0TB"},
}
for _, tt := range tests {
got := formatBytes(tt.input)
if got != tt.want {
t.Errorf("formatBytes(%d) = %q, want %q", tt.input, got, tt.want)
}
}
}
func TestFormatDuration(t *testing.T) {
tests := []struct {
input float64
want string
}{
{0, "0:00"},
{5, "0:05"},
{60, "1:00"},
{65, "1:05"},
{3600, "1:00:00"},
{3661, "1:01:01"},
{86400, "24:00:00"},
}
for _, tt := range tests {
got := formatDuration(tt.input)
if got != tt.want {
t.Errorf("formatDuration(%f) = %q, want %q", tt.input, got, tt.want)
}
}
}
func TestFormatLabelForDisplay(t *testing.T) {
tests := []struct {
f domain.Format
want string
}{
{
f: domain.Format{Resolution: "1920x1080", Filesize: 1048576},
want: "1920x1080 (1.0MB)",
},
{
f: domain.Format{Resolution: "1920x1080"},
want: "1920x1080",
},
{
f: domain.Format{Bitrate: "128k", Filesize: 1048576},
want: "128k (1.0MB)",
},
{
f: domain.Format{Bitrate: "128k"},
want: "128k",
},
{
f: domain.Format{ID: "137"},
want: "137",
},
}
for _, tt := range tests {
got := formatLabelForDisplay(tt.f)
if got != tt.want {
t.Errorf("formatLabelForDisplay(%+v) = %q, want %q", tt.f, got, tt.want)
}
}
}