package p115 import ( "bytes" "context" "crypto/sha1" "encoding/hex" "errors" "io" "os" "strings" "testing" "time" "github.com/video-site/backend/internal/drives" ) func TestIsTransient115ListError(t *testing.T) { cases := []struct { name string err error want bool }{ {name: "nil", err: nil, want: false}, {name: "blocked html without status context", err: errors.New(`405Sorry, your request has been blocked as it may cause potential threats to the server's security.`), want: false}, {name: "chinese waf", err: errors.New("很抱歉,由于您访问的URL有可能对网站造成安全威胁,您的访问被阻断。"), want: false}, {name: "status 405", err: errors.New("request failed with status: 405"), want: true}, {name: "rate limit", err: errors.New("429 too many requests"), want: true}, {name: "regular auth error", err: errors.New("invalid credential"), want: false}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { if got := isTransient115ListError(tc.err); got != tc.want { t.Fatalf("isTransient115ListError(%v) = %v, want %v", tc.err, got, tc.want) } }) } } func TestWrap115StreamTransientError(t *testing.T) { cases := []struct { name string err error wantRateLimit bool }{ {name: "unexpected", err: errors.New("unexpected error"), wantRateLimit: false}, {name: "405 blocked", err: errors.New("405 request has been blocked"), wantRateLimit: true}, {name: "429", err: errors.New("429 too many requests"), wantRateLimit: true}, {name: "blocked", err: errors.New("blocked by waf"), wantRateLimit: false}, {name: "auth", err: errors.New("invalid credential"), wantRateLimit: false}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { got := wrap115StreamTransientError("115 get file", tc.err) var rateLimit *drives.RateLimitError isRateLimit := errors.As(got, &rateLimit) if isRateLimit != tc.wantRateLimit { t.Fatalf("rate limit = %v, want %v; err=%v", isRateLimit, tc.wantRateLimit, got) } if !strings.Contains(got.Error(), "115 get file") { t.Fatalf("err = %v, want operation prefix", got) } if tc.wantRateLimit { if rateLimit.Provider != "p115" { t.Fatalf("provider = %q, want p115", rateLimit.Provider) } if rateLimit.RetryAfter != 10*time.Minute { t.Fatalf("retry after = %s, want 10m", rateLimit.RetryAfter) } } }) } } // TestBufferAndHashSha1 验证 bufferAndHashSha1: // // - 把 reader 的全部字节落到 tmp 文件 // - SHA1 与标准库一致(HEX 大写) // - declaredSize=0 时不校验,>0 时严格校验 // - 调用方拿到的 *os.File 可以 Seek 回 0 重新读出原文(OSS SDK 上传需要) func TestBufferAndHashSha1(t *testing.T) { body := []byte("hello-115-upload-test") want := sha1.Sum(body) wantHex := strings.ToUpper(hex.EncodeToString(want[:])) t.Run("declared size matches", func(t *testing.T) { tmp, gotHex, n, err := bufferAndHashSha1(bytes.NewReader(body), int64(len(body))) if err != nil { t.Fatalf("bufferAndHashSha1 returned error: %v", err) } defer cleanup(tmp) if gotHex != wantHex { t.Errorf("sha1 = %s, want %s", gotHex, wantHex) } if n != int64(len(body)) { t.Errorf("written = %d, want %d", n, len(body)) } // Seek 回 0,应能读出原文 if _, err := tmp.Seek(0, io.SeekStart); err != nil { t.Fatalf("seek: %v", err) } got, err := io.ReadAll(tmp) if err != nil { t.Fatalf("read tmp: %v", err) } if !bytes.Equal(got, body) { t.Errorf("tmp content mismatch: got %q want %q", string(got), string(body)) } }) t.Run("declared size mismatch returns error", func(t *testing.T) { _, _, _, err := bufferAndHashSha1(bytes.NewReader(body), int64(len(body))+1) if err == nil { t.Fatal("expected size mismatch error, got nil") } }) t.Run("declared size zero is unchecked", func(t *testing.T) { tmp, gotHex, n, err := bufferAndHashSha1(bytes.NewReader(body), 0) if err != nil { t.Fatalf("bufferAndHashSha1 returned error: %v", err) } defer cleanup(tmp) if gotHex != wantHex { t.Errorf("sha1 = %s, want %s", gotHex, wantHex) } if n != int64(len(body)) { t.Errorf("written = %d, want %d", n, len(body)) } }) } // TestUploadAndReportSha1RejectsInvalidArgs 检查空 reader / 空 name / 负 size 在 // 客户端未初始化前就被拒绝,避免下游 SDK 在错误参数下做异步初始化和真实网络调用。 func TestUploadAndReportSha1RejectsInvalidArgs(t *testing.T) { d := New(Config{ID: "p115-test"}) // 注意:未调 Init,因此 d.client == nil,第一道防线就会拒绝。 cases := []struct { name string parentID string fname string body io.Reader size int64 wantSubst string }{ {name: "nil client", parentID: "0", fname: "x.mp4", body: bytes.NewReader([]byte("ok")), size: 2, wantSubst: "not initialized"}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { _, err := d.UploadAndReportSha1(context.Background(), c.parentID, c.fname, c.body, c.size) if err == nil { t.Fatalf("expected error, got nil") } if !strings.Contains(err.Error(), c.wantSubst) { t.Fatalf("err = %v, want containing %q", err, c.wantSubst) } }) } } func cleanup(f *os.File) { if f == nil { return } _ = f.Close() _ = os.Remove(f.Name()) }