mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-28 11:16:37 +08:00
329 lines
9.6 KiB
Go
329 lines
9.6 KiB
Go
package cloud
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestQuarkListAndResolve(t *testing.T) {
|
|
var gotCookie string
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
gotCookie = r.Header.Get("Cookie")
|
|
switch {
|
|
case r.URL.Path == "/file/sort":
|
|
if r.URL.Query().Get("pdir_fid") != "0" {
|
|
t.Errorf("unexpected pdir_fid %q", r.URL.Query().Get("pdir_fid"))
|
|
}
|
|
w.Write([]byte(`{"status":200,"code":0,"data":{"list":[
|
|
{"fid":"d1","file_name":"Movies","dir":true,"size":0},
|
|
{"fid":"f1","file_name":"Inception.mkv","dir":false,"size":123}]}}`))
|
|
case r.URL.Path == "/file/download":
|
|
if r.Method != http.MethodPost {
|
|
t.Errorf("download must be POST, got %s", r.Method)
|
|
}
|
|
w.Write([]byte(`{"status":200,"code":0,"data":[{"fid":"f1","download_url":"https://cdn.quark/x.mkv?sign=1"}]}`))
|
|
default:
|
|
t.Errorf("unexpected path %s", r.URL.Path)
|
|
}
|
|
}))
|
|
defer srv.Close()
|
|
|
|
p, err := New(TypeQuark, map[string]any{"cookie": "kps=abc", "base": srv.URL}, srv.Client())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
entries, err := p.List(context.Background(), "0")
|
|
if err != nil {
|
|
t.Fatalf("list: %v", err)
|
|
}
|
|
if len(entries) != 2 || !entries[0].IsDir || entries[1].Name != "Inception.mkv" || entries[1].Size != 123 {
|
|
t.Fatalf("unexpected entries: %#v", entries)
|
|
}
|
|
if gotCookie != "kps=abc" {
|
|
t.Fatalf("cookie not forwarded: %q", gotCookie)
|
|
}
|
|
link, err := p.Resolve(context.Background(), "f1")
|
|
if err != nil {
|
|
t.Fatalf("resolve: %v", err)
|
|
}
|
|
if link.URL != "https://cdn.quark/x.mkv?sign=1" {
|
|
t.Fatalf("bad url: %s", link.URL)
|
|
}
|
|
if !link.Proxy {
|
|
t.Fatalf("quark should default to proxy mode")
|
|
}
|
|
if link.Headers["Cookie"] != "kps=abc" {
|
|
t.Fatalf("resolve must carry cookie header: %#v", link.Headers)
|
|
}
|
|
}
|
|
|
|
func TestQuarkListPaginates(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/file/sort" {
|
|
t.Fatalf("unexpected path %s", r.URL.Path)
|
|
}
|
|
page, _ := strconv.Atoi(r.URL.Query().Get("_page"))
|
|
w.Write([]byte(`{"status":200,"code":0,"data":{"list":[` + quarkPagePayload(page) + `]}}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
p, err := New(TypeQuark, map[string]any{"cookie": "kps=abc", "base": srv.URL}, srv.Client())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
entries, err := p.List(context.Background(), "0")
|
|
if err != nil {
|
|
t.Fatalf("list: %v", err)
|
|
}
|
|
if len(entries) != 101 {
|
|
t.Fatalf("entries = %d, want 101", len(entries))
|
|
}
|
|
if entries[100].ID != "f100" || entries[100].Name != "Movie.100.mkv" {
|
|
t.Fatalf("last entry wrong: %#v", entries[100])
|
|
}
|
|
}
|
|
|
|
func quarkPagePayload(page int) string {
|
|
count := 100
|
|
offset := 0
|
|
if page > 1 {
|
|
count = 1
|
|
offset = 100
|
|
}
|
|
items := make([]string, 0, count)
|
|
for i := 0; i < count; i++ {
|
|
n := offset + i
|
|
items = append(items, fmt.Sprintf(`{"fid":"f%d","file_name":"Movie.%03d.mkv","dir":false,"size":%d}`, n, n, n))
|
|
}
|
|
return strings.Join(items, ",")
|
|
}
|
|
|
|
func TestQuarkForce302(t *testing.T) {
|
|
p := newQuark(map[string]any{"cookie": "c", "force_302": "true"}, http.DefaultClient)
|
|
if p.proxy {
|
|
t.Fatalf("force_302 should disable proxy mode")
|
|
}
|
|
}
|
|
|
|
func Test115ListAndResolve(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.URL.Path {
|
|
case "/files":
|
|
if r.URL.Query().Get("cid") != "0" {
|
|
t.Errorf("bad cid %q", r.URL.Query().Get("cid"))
|
|
}
|
|
w.Write([]byte(`{"state":true,"data":[
|
|
{"cid":"100","n":"Movies","s":0},
|
|
{"fid":"200","n":"Inception.mkv","s":456,"pc":"pick200"}]}`))
|
|
default:
|
|
t.Errorf("unexpected path %s", r.URL.Path)
|
|
}
|
|
}))
|
|
defer srv.Close()
|
|
|
|
p, err := New(Type115, map[string]any{"cookie": "UID=1; CID=2", "base": srv.URL}, srv.Client())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// The downurl endpoint is m115-encrypted end-to-end (the server side
|
|
// requires 115's private key), so stub the decrypted payload via the seam
|
|
// and assert the pickcode→URL extraction. The live crypto/transport path is
|
|
// exercised by integration testing against the real 115 API.
|
|
p115, ok := p.(*pan115Provider)
|
|
if !ok {
|
|
t.Fatalf("expected *pan115Provider, got %T", p)
|
|
}
|
|
p115.downURLPayload = func(ctx context.Context, pickcode string) ([]byte, error) {
|
|
if pickcode != "pick200" {
|
|
t.Errorf("bad pickcode %q", pickcode)
|
|
}
|
|
return []byte(`{"200":{"file_name":"Inception.mkv","file_size":"456","url":{"url":"https://cdn.115/x.mkv?t=1"}}}`), nil
|
|
}
|
|
entries, err := p.List(context.Background(), "")
|
|
if err != nil {
|
|
t.Fatalf("list: %v", err)
|
|
}
|
|
if len(entries) != 2 {
|
|
t.Fatalf("want 2 entries: %#v", entries)
|
|
}
|
|
if !entries[0].IsDir || entries[0].ID != "100" {
|
|
t.Fatalf("dir entry wrong: %#v", entries[0])
|
|
}
|
|
if entries[1].IsDir || entries[1].PickCode != "pick200" || entries[1].Size != 456 {
|
|
t.Fatalf("file entry wrong: %#v", entries[1])
|
|
}
|
|
link, err := p.Resolve(context.Background(), "pick200")
|
|
if err != nil {
|
|
t.Fatalf("resolve: %v", err)
|
|
}
|
|
if link.URL != "https://cdn.115/x.mkv?t=1" {
|
|
t.Fatalf("bad url: %s", link.URL)
|
|
}
|
|
if link.Proxy {
|
|
t.Fatalf("115 should default to 302 (no proxy)")
|
|
}
|
|
}
|
|
|
|
func Test115ListPaginates(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/files" {
|
|
t.Fatalf("unexpected path %s", r.URL.Path)
|
|
}
|
|
offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
|
|
count := 100
|
|
if offset > 0 {
|
|
count = 1
|
|
}
|
|
items := make([]string, 0, count)
|
|
for i := 0; i < count; i++ {
|
|
n := offset + i
|
|
items = append(items, fmt.Sprintf(`{"fid":"%d","n":"Movie.%03d.mkv","s":%d,"pc":"pick%d"}`, n, n, n, n))
|
|
}
|
|
w.Write([]byte(`{"state":true,"data":[` + strings.Join(items, ",") + `]}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
p, err := New(Type115, map[string]any{"cookie": "UID=1; CID=2", "base": srv.URL}, srv.Client())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
entries, err := p.List(context.Background(), "0")
|
|
if err != nil {
|
|
t.Fatalf("list: %v", err)
|
|
}
|
|
if len(entries) != 101 {
|
|
t.Fatalf("entries = %d, want 101", len(entries))
|
|
}
|
|
if entries[100].ID != "100" || entries[100].PickCode != "pick100" {
|
|
t.Fatalf("last entry wrong: %#v", entries[100])
|
|
}
|
|
}
|
|
|
|
// Test115DownURLEndpointAndError exercises the live fetchDownURLPayload path:
|
|
// it must POST an m115-encrypted `data` body to /app/chrome/downurl?t=... and
|
|
// surface 115's error when state=false (no decryption needed for that branch).
|
|
func Test115DownURLEndpointAndError(t *testing.T) {
|
|
var gotData, gotT string
|
|
pro := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/app/chrome/downurl" {
|
|
t.Errorf("unexpected path %s", r.URL.Path)
|
|
}
|
|
gotT = r.URL.Query().Get("t")
|
|
_ = r.ParseForm()
|
|
gotData = r.PostFormValue("data")
|
|
w.Write([]byte(`{"state":false,"error":"not exist"}`))
|
|
}))
|
|
defer pro.Close()
|
|
|
|
p, err := New(Type115, map[string]any{"cookie": "UID=1", "pro_base": pro.URL}, pro.Client())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err = p.Resolve(context.Background(), "pickX")
|
|
if err == nil || !strings.Contains(err.Error(), "not exist") {
|
|
t.Fatalf("want upstream error surfaced, got %v", err)
|
|
}
|
|
if gotT == "" {
|
|
t.Errorf("missing t query param")
|
|
}
|
|
if gotData == "" {
|
|
t.Errorf("missing encrypted data body")
|
|
}
|
|
if _, derr := base64.StdEncoding.DecodeString(gotData); derr != nil {
|
|
t.Errorf("data body is not base64: %v", derr)
|
|
}
|
|
}
|
|
|
|
func Test115QRFlow(t *testing.T) {
|
|
// status sequence: waiting → scanned → confirmed
|
|
calls := 0
|
|
api := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.URL.Path {
|
|
case "/api/1.0/web/1.0/token/":
|
|
w.Write([]byte(`{"state":1,"data":{"uid":"U1","time":1700,"sign":"S1"}}`))
|
|
case "/get/status/":
|
|
if r.URL.Query().Get("uid") != "U1" {
|
|
t.Errorf("bad uid %q", r.URL.Query().Get("uid"))
|
|
}
|
|
calls++
|
|
switch calls {
|
|
case 1:
|
|
w.Write([]byte(`{"state":1,"data":{"status":0}}`))
|
|
case 2:
|
|
w.Write([]byte(`{"state":1,"data":{"status":1}}`))
|
|
default:
|
|
w.Write([]byte(`{"state":1,"data":{"status":2}}`))
|
|
}
|
|
default:
|
|
t.Errorf("unexpected api path %s", r.URL.Path)
|
|
}
|
|
}))
|
|
defer api.Close()
|
|
passport := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/app/1.0/web/1.0/login/qrcode/" {
|
|
t.Errorf("unexpected passport path %s", r.URL.Path)
|
|
}
|
|
w.Write([]byte(`{"state":1,"data":{"cookie":{"UID":"u","CID":"c","SEID":"s"}}}`))
|
|
}))
|
|
defer passport.Close()
|
|
|
|
oldA, oldP := qr115APIBase, qr115PassportBase
|
|
qr115APIBase, qr115PassportBase = api.URL, passport.URL
|
|
defer func() { qr115APIBase, qr115PassportBase = oldA, oldP }()
|
|
|
|
ctx := context.Background()
|
|
sess, err := QRStart(ctx, api.Client())
|
|
if err != nil {
|
|
t.Fatalf("qr start: %v", err)
|
|
}
|
|
if sess.UID != "U1" || sess.QRImageURL == "" {
|
|
t.Fatalf("bad session: %#v", sess)
|
|
}
|
|
want := []string{"waiting", "scanned", "confirmed"}
|
|
for i, exp := range want {
|
|
st, err := QRPoll(ctx, api.Client(), sess)
|
|
if err != nil {
|
|
t.Fatalf("poll %d: %v", i, err)
|
|
}
|
|
if st.State != exp {
|
|
t.Fatalf("poll %d: want %s got %s", i, exp, st.State)
|
|
}
|
|
if exp == "confirmed" {
|
|
if st.Cookie == "" || !containsAll(st.Cookie, "UID=u", "SEID=s") {
|
|
t.Fatalf("confirmed must yield cookie: %q", st.Cookie)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUnsupportedProvider(t *testing.T) {
|
|
if _, err := New("dropbox", nil, nil); err != ErrUnsupported {
|
|
t.Fatalf("want ErrUnsupported, got %v", err)
|
|
}
|
|
}
|
|
|
|
func containsAll(s string, subs ...string) bool {
|
|
for _, sub := range subs {
|
|
found := false
|
|
for i := 0; i+len(sub) <= len(s); i++ {
|
|
if s[i:i+len(sub)] == sub {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
var _ = time.Second
|