mirror of
https://github.com/krau/SaveAny-Bot.git
synced 2026-08-13 08:23:58 +08:00
179 lines
6.1 KiB
Go
179 lines
6.1 KiB
Go
package tfile
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"strings"
|
||
"sync"
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/gotd/td/tg"
|
||
"github.com/krau/SaveAny-Bot/common/i18n"
|
||
)
|
||
|
||
type progressTestTaskInfo struct{}
|
||
|
||
func (progressTestTaskInfo) TaskID() string { return "task" }
|
||
func (progressTestTaskInfo) FileName() string { return "file.bin" }
|
||
func (progressTestTaskInfo) FileSize() int64 { return 100 << 20 }
|
||
func (progressTestTaskInfo) StoragePath() string { return "file.bin" }
|
||
func (progressTestTaskInfo) StorageName() string { return "test" }
|
||
|
||
func TestShouldUpdateUploadProgress(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
total int64
|
||
uploaded int64
|
||
lastPercent int
|
||
elapsed time.Duration
|
||
want bool
|
||
}{
|
||
{name: "invalid total", total: 0, uploaded: 1, want: false},
|
||
{name: "no uploaded bytes", total: 100, uploaded: 0, want: false},
|
||
{name: "percentage threshold", total: 100 << 20, uploaded: 10 << 20, elapsed: uploadProgressMinInterval, want: true},
|
||
{name: "percentage threshold rate limited", total: 100 << 20, uploaded: 10 << 20, elapsed: uploadProgressMinInterval - time.Millisecond, want: false},
|
||
{name: "maximum time threshold", total: 100 << 20, uploaded: 1 << 20, elapsed: uploadProgressMaxInterval, want: true},
|
||
{name: "below thresholds", total: 100 << 20, uploaded: 1 << 20, elapsed: uploadProgressMaxInterval - time.Millisecond, want: false},
|
||
{name: "completion", total: 100, uploaded: 100, lastPercent: 99, elapsed: uploadProgressMinInterval, want: true},
|
||
{name: "completion rate limited", total: 100, uploaded: 100, lastPercent: 99, elapsed: uploadProgressMinInterval - time.Millisecond, want: false},
|
||
{name: "completion already reported", total: 100, uploaded: 100, lastPercent: 100, elapsed: uploadProgressMinInterval, want: false},
|
||
{name: "out of order callback", total: 100, uploaded: 40, lastPercent: 60, elapsed: uploadProgressMaxInterval, want: false},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
got := shouldUpdateUploadProgress(tt.total, tt.uploaded, tt.lastPercent, tt.elapsed)
|
||
if got != tt.want {
|
||
t.Fatalf("shouldUpdateUploadProgress() = %v, want %v", got, tt.want)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestUploadProgressConcurrentCallbacks(t *testing.T) {
|
||
progress := new(Progress)
|
||
ctx := context.Background()
|
||
info := progressTestTaskInfo{}
|
||
const total = int64(100 << 20)
|
||
|
||
progress.OnUploadStart(ctx, info, total)
|
||
progress.lastUpdateAt.Store(time.Now().Add(-uploadProgressMaxInterval).UnixNano())
|
||
|
||
var wg sync.WaitGroup
|
||
for uploaded := int64(1 << 20); uploaded <= total; uploaded += 1 << 20 {
|
||
uploaded := uploaded
|
||
wg.Go(func() {
|
||
progress.OnUploadProgress(ctx, info, uploaded, total)
|
||
})
|
||
}
|
||
wg.Wait()
|
||
|
||
percent := progress.lastUpdatePercent.Load()
|
||
if percent <= 0 || percent > 100 {
|
||
t.Fatalf("last upload percentage = %d, want a value in (0, 100]", percent)
|
||
}
|
||
if progress.uploadedBytes != total {
|
||
t.Fatalf("maximum uploaded bytes = %d, want %d", progress.uploadedBytes, total)
|
||
}
|
||
}
|
||
|
||
func TestSingleUploadRetryKeepsRichProgressLayout(t *testing.T) {
|
||
i18n.Init("zh-Hans")
|
||
t.Cleanup(func() { i18n.Init("zh-Hans") })
|
||
|
||
message := buildSingleProgressMessage(
|
||
progressTestTaskInfo{},
|
||
singleUploadPhase(2),
|
||
25<<20,
|
||
100<<20,
|
||
5<<20,
|
||
2,
|
||
)
|
||
if message.Err != nil {
|
||
t.Fatalf("buildSingleProgressMessage() failed: %v", message.Err)
|
||
}
|
||
for _, want := range []string{
|
||
"🔁 上传重试",
|
||
"🟩🟩⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️ 25%",
|
||
"尝试次数:2",
|
||
"速度:5.00 MB/s",
|
||
"大小:25.00 MB / 100.00 MB",
|
||
} {
|
||
if !strings.Contains(message.Text, want) {
|
||
t.Fatalf("retry progress does not contain %q:\n%s", want, message.Text)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestSingleProgressTemplateOwnsStylesAndEscapesValues(t *testing.T) {
|
||
i18n.Init("en")
|
||
t.Cleanup(func() { i18n.Init("zh-Hans") })
|
||
info := htmlProgressTestTaskInfo{}
|
||
|
||
message := buildSingleProgressMessage(info, singlePhaseDownloading, 50, 100, 25, 0)
|
||
if message.Err != nil {
|
||
t.Fatalf("buildSingleProgressMessage() failed: %v", message.Err)
|
||
}
|
||
for _, want := range []string{
|
||
`<b>A&B</b>.bin`,
|
||
`[store<&>]:dir/<i>x</i>&`,
|
||
"Speed: 25 B/s",
|
||
} {
|
||
if !strings.Contains(message.Text, want) {
|
||
t.Fatalf("progress text does not contain %q:\n%s", want, message.Text)
|
||
}
|
||
}
|
||
bold, code, blockquote, italic := singleEntityCounts(message.Entities)
|
||
if bold != 2 || code != 6 || blockquote != 1 || italic != 0 {
|
||
t.Fatalf("progress entity counts = bold:%d code:%d blockquote:%d italic:%d", bold, code, blockquote, italic)
|
||
}
|
||
|
||
failure := buildSingleDoneMessage(info, 100, errors.New(`<i>remote & failed</i>`))
|
||
if failure.Err != nil {
|
||
t.Fatalf("buildSingleDoneMessage() failed: %v", failure.Err)
|
||
}
|
||
if !strings.Contains(failure.Text, `<i>remote & failed</i>`) {
|
||
t.Fatalf("failure reason was not preserved literally:\n%s", failure.Text)
|
||
}
|
||
bold, code, blockquote, italic = singleEntityCounts(failure.Entities)
|
||
if bold != 1 || code != 2 || blockquote != 0 || italic != 0 {
|
||
t.Fatalf("failure entity counts = bold:%d code:%d blockquote:%d italic:%d", bold, code, blockquote, italic)
|
||
}
|
||
}
|
||
|
||
func TestSingleDoneSizeUsesActualUploadSize(t *testing.T) {
|
||
progress := new(Progress)
|
||
info := progressTestTaskInfo{}
|
||
progress.OnStart(context.Background(), info)
|
||
progress.OnUploadStart(context.Background(), info, 2048)
|
||
|
||
if got := progress.doneSize(info); got != 2048 {
|
||
t.Fatalf("done size = %d, want actual upload size 2048", got)
|
||
}
|
||
}
|
||
|
||
type htmlProgressTestTaskInfo struct{}
|
||
|
||
func (htmlProgressTestTaskInfo) TaskID() string { return "html-task" }
|
||
func (htmlProgressTestTaskInfo) FileName() string { return `<b>A&B</b>.bin` }
|
||
func (htmlProgressTestTaskInfo) FileSize() int64 { return 100 }
|
||
func (htmlProgressTestTaskInfo) StoragePath() string { return `dir/<i>x</i>&` }
|
||
func (htmlProgressTestTaskInfo) StorageName() string { return `store<&>` }
|
||
|
||
func singleEntityCounts(entities []tg.MessageEntityClass) (bold, code, blockquote, italic int) {
|
||
for _, messageEntity := range entities {
|
||
switch messageEntity.(type) {
|
||
case *tg.MessageEntityBold:
|
||
bold++
|
||
case *tg.MessageEntityCode:
|
||
code++
|
||
case *tg.MessageEntityBlockquote:
|
||
blockquote++
|
||
case *tg.MessageEntityItalic:
|
||
italic++
|
||
}
|
||
}
|
||
return
|
||
}
|