mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-03 12:26:36 +08:00
优化,添加115接口文档
This commit is contained in:
@@ -3,11 +3,18 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
"github.com/truewhile/MeBox/internal/repository"
|
||||
"github.com/truewhile/MeBox/internal/service/cloud"
|
||||
"github.com/truewhile/MeBox/internal/service/cloud115"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -18,6 +25,7 @@ func TestIs115Blocked(t *testing.T) {
|
||||
}{
|
||||
{errors.New("115 接口返回 HTTP 405:<!doctypehtml>...访问被阻断"), true},
|
||||
{errors.New("115 接口返回 HTTP 405"), true},
|
||||
{errors.New("115 接口触发频控/安全拦截(HTTP 405):阿里云 WAF 拦截页"), true},
|
||||
{errors.New("115 接口错误(770004):访问频率过高"), true},
|
||||
{errors.New("115 接口错误(406):达到访问上限"), true},
|
||||
{errors.New("下载失败:http 403"), false},
|
||||
@@ -164,3 +172,189 @@ func TestRequeueDownloadTask(t *testing.T) {
|
||||
t.Fatalf("task not requeued: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestProcessUpload115DeletesStaleRemoteMetaFirst 验证 115 覆盖上传语义(以本地为准):
|
||||
// 任务携带网盘旧文件 ID 时,必须先调用 /open/ufile/delete 删除旧元数据再上传本地文件,
|
||||
// 避免 115 出现同名重复文件;删除请求应携带 file_ids 与父目录 cid。
|
||||
func TestProcessUpload115DeletesStaleRemoteMetaFirst(t *testing.T) {
|
||||
svc := testStrmService(t)
|
||||
localDir := t.TempDir()
|
||||
localFile := filepath.Join(localDir, "movie.nfo")
|
||||
if err := os.WriteFile(localFile, []byte("local-nfo-data"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
acct := &model.StrmAccount{Name: "fake115", Provider: "cloud115", Config: "{}", Enabled: true}
|
||||
if err := svc.repo.StrmAccount.Create(context.Background(), acct); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var mu sync.Mutex
|
||||
var calls []string
|
||||
deleteForm := map[string]string{}
|
||||
api := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r.ParseForm()
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
switch r.URL.Path {
|
||||
case "/open/ufile/delete":
|
||||
calls = append(calls, "delete")
|
||||
deleteForm["file_ids"] = r.FormValue("file_ids")
|
||||
deleteForm["parent_id"] = r.FormValue("parent_id")
|
||||
w.Write([]byte(`{"state":true,"data":[]}`))
|
||||
case "/open/upload/init":
|
||||
calls = append(calls, "upload")
|
||||
// 返回秒传成功,跳过 OSS 真实上传
|
||||
w.Write([]byte(`{"state":true,"data":{"status":2,"file_id":"new-1","pick_code":"new-pc-1","callback":null}}`))
|
||||
default:
|
||||
t.Errorf("unexpected 115 api path %s", r.URL.Path)
|
||||
w.Write([]byte(`{"state":false,"message":"unexpected path"}`))
|
||||
}
|
||||
}))
|
||||
defer api.Close()
|
||||
oldPro := cloud115.ProAPIBase
|
||||
cloud115.ProAPIBase = api.URL
|
||||
defer func() { cloud115.ProAPIBase = oldPro }()
|
||||
|
||||
task := &model.StrmUploadTask{
|
||||
Base: model.Base{ID: "up-del-1"},
|
||||
SyncPathID: "p1",
|
||||
AccountID: acct.ID,
|
||||
Provider: model.StrmProvider115,
|
||||
FileName: "movie.nfo",
|
||||
LocalPath: localFile,
|
||||
RemotePath: "777",
|
||||
RemoteRef: "old-file-1",
|
||||
Status: model.StrmTaskRunning,
|
||||
}
|
||||
svc.processUpload115(context.Background(), task)
|
||||
|
||||
if task.Status != model.StrmTaskDone {
|
||||
t.Fatalf("upload task should succeed, status = %s, error = %s", task.Status, task.Error)
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if len(calls) != 2 || calls[0] != "delete" || calls[1] != "upload" {
|
||||
t.Fatalf("expected delete before upload, got calls = %v", calls)
|
||||
}
|
||||
if deleteForm["file_ids"] != "old-file-1" {
|
||||
t.Fatalf("delete file_ids = %q, want old-file-1", deleteForm["file_ids"])
|
||||
}
|
||||
if deleteForm["parent_id"] != "777" {
|
||||
t.Fatalf("delete parent_id = %q, want 777", deleteForm["parent_id"])
|
||||
}
|
||||
}
|
||||
|
||||
// TestBatchResolve115Links 验证下载队列的批量换链:同账号多个 115 任务的
|
||||
// pickcode 合并为一次 downurl 请求(官方接口支持逗号分隔多 pick_code),
|
||||
// 重复引用去重、非 115 任务不参与、直链携带绑定 UA。
|
||||
func TestBatchResolve115Links(t *testing.T) {
|
||||
svc := testStrmService(t)
|
||||
acct := &model.StrmAccount{Name: "fake115", Provider: model.StrmProvider115, Config: "{}", Enabled: true}
|
||||
if err := svc.repo.StrmAccount.Create(context.Background(), acct); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var mu sync.Mutex
|
||||
var requests []string
|
||||
api := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/open/ufile/downurl":
|
||||
_ = r.ParseForm()
|
||||
mu.Lock()
|
||||
requests = append(requests, r.PostFormValue("pick_code"))
|
||||
mu.Unlock()
|
||||
w.Write([]byte(`{"state":true,"data":{
|
||||
"111":{"pick_code":"q-pc-a","url":{"url":"http://cdn/a.mkv"}},
|
||||
"222":{"pick_code":"q-pc-b","url":{"url":"http://cdn/b.jpg"}}}}`))
|
||||
default:
|
||||
t.Errorf("unexpected 115 api path %s", r.URL.Path)
|
||||
w.Write([]byte(`{"state":false,"message":"unexpected path"}`))
|
||||
}
|
||||
}))
|
||||
defer api.Close()
|
||||
oldPro := cloud115.ProAPIBase
|
||||
cloud115.ProAPIBase = api.URL
|
||||
defer func() { cloud115.ProAPIBase = oldPro }()
|
||||
defer cloud115.ClearDownloadURLCache("q-pc-a")
|
||||
defer cloud115.ClearDownloadURLCache("q-pc-b")
|
||||
|
||||
tasks := []*model.StrmDownloadTask{
|
||||
{SyncPathID: "p1", AccountID: acct.ID, Provider: model.StrmProvider115, RemoteRef: "q-pc-a"},
|
||||
{SyncPathID: "p1", AccountID: acct.ID, Provider: model.StrmProvider115, RemoteRef: "q-pc-a"}, // 重复引用
|
||||
{SyncPathID: "p1", AccountID: acct.ID, Provider: model.StrmProvider115, RemoteRef: "q-pc-b"},
|
||||
{SyncPathID: "p1", AccountID: acct.ID, Provider: model.StrmProviderOpenList, RemoteRef: "ol-ref"},
|
||||
{SyncPathID: "p1", AccountID: acct.ID, Provider: model.StrmProvider115, RemoteRef: " "}, // 空引用
|
||||
}
|
||||
resolved := svc.batchResolve115Links(context.Background(), tasks)
|
||||
|
||||
if got := resolved[dlResolveKey(acct.ID, "q-pc-a")]; got == nil || got.URL != "http://cdn/a.mkv" {
|
||||
t.Fatalf("missing/bad link for q-pc-a: %+v", resolved)
|
||||
}
|
||||
if got := resolved[dlResolveKey(acct.ID, "q-pc-b")]; got == nil || got.URL != "http://cdn/b.jpg" {
|
||||
t.Fatalf("missing/bad link for q-pc-b: %+v", resolved)
|
||||
}
|
||||
if got := resolved[dlResolveKey(acct.ID, "q-pc-a")].Headers["User-Agent"]; got != cloud115.DefaultUA {
|
||||
t.Fatalf("link UA = %q, want default bound UA", got)
|
||||
}
|
||||
if _, ok := resolved[dlResolveKey(acct.ID, "ol-ref")]; ok {
|
||||
t.Fatal("non-115 task should not be batch resolved")
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if len(requests) != 1 || requests[0] != "q-pc-a,q-pc-b" {
|
||||
t.Fatalf("expected single batched downurl request, got %v", requests)
|
||||
}
|
||||
}
|
||||
|
||||
// TestProcessDownloadTaskUsesPreResolvedLink 验证任务执行时优先使用批量换链
|
||||
// 预取的直链:downurl 接口保持失败,若任务仍走逐个换链则必然失败。
|
||||
func TestProcessDownloadTaskUsesPreResolvedLink(t *testing.T) {
|
||||
svc := testStrmService(t)
|
||||
localDir := t.TempDir()
|
||||
acct := &model.StrmAccount{Name: "fake115", Provider: model.StrmProvider115, Config: "{}", Enabled: true}
|
||||
if err := svc.repo.StrmAccount.Create(context.Background(), acct); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
api := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}))
|
||||
defer api.Close()
|
||||
oldPro := cloud115.ProAPIBase
|
||||
cloud115.ProAPIBase = api.URL
|
||||
defer func() { cloud115.ProAPIBase = oldPro }()
|
||||
|
||||
contentSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("User-Agent") != cloud115.DefaultUA {
|
||||
t.Errorf("download UA = %q, want bound default UA", r.Header.Get("User-Agent"))
|
||||
}
|
||||
_, _ = w.Write([]byte("nfo-content"))
|
||||
}))
|
||||
defer contentSrv.Close()
|
||||
|
||||
task := &model.StrmDownloadTask{
|
||||
SyncPathID: "p1",
|
||||
AccountID: acct.ID,
|
||||
Provider: model.StrmProvider115,
|
||||
FileName: "movie.nfo",
|
||||
RemoteRef: "q-pc-pre",
|
||||
LocalPath: filepath.Join(localDir, "movie.nfo"),
|
||||
Status: model.StrmTaskRunning,
|
||||
}
|
||||
if err := svc.repo.StrmDownload.Create(context.Background(), task); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resolved := map[string]*cloud.DirectLink{
|
||||
dlResolveKey(acct.ID, "q-pc-pre"): {URL: contentSrv.URL, Headers: map[string]string{"User-Agent": cloud115.DefaultUA}},
|
||||
}
|
||||
svc.processDownloadTask(context.Background(), task, resolved)
|
||||
|
||||
if task.Status != model.StrmTaskDone {
|
||||
t.Fatalf("task should be done via pre-resolved link, status = %s, error = %s", task.Status, task.Error)
|
||||
}
|
||||
data, err := os.ReadFile(task.LocalPath)
|
||||
if err != nil || string(data) != "nfo-content" {
|
||||
t.Fatalf("downloaded file mismatch: err = %v, data = %q", err, string(data))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user