Files
EveryPublish/server/internal/api/d3_test.go
T

222 lines
7.4 KiB
Go

package api_test
import (
"bytes"
"encoding/json"
"fmt"
"mime/multipart"
"net/http/httptest"
"net/textproto"
"strings"
"testing"
"github.com/gin-gonic/gin"
"everypublish/server/internal/models"
)
func multipartBody(t *testing.T, filename, contentType string, content []byte) *bytes.Buffer {
t.Helper()
buf := &bytes.Buffer{}
w := multipart.NewWriter(buf)
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file"; filename="%s"`, filename))
h.Set("Content-Type", contentType)
part, err := w.CreatePart(h)
if err != nil {
t.Fatalf("create part: %v", err)
}
_, _ = part.Write(content)
_ = w.Close()
return buf
}
func uploadMaterial(t *testing.T, access string, filename string, content []byte) (uint64, string) {
t.Helper()
buf := multipartBody(t, filename, "video/mp4", content)
req := httptest.NewRequest("POST", "/api/v1/materials", buf)
req.Header.Set("Content-Type", "multipart/form-data; boundary="+strings.Split(buf.String(), "\r\n")[0][2:])
req.Header.Set("Authorization", "Bearer "+access)
w := httptest.NewRecorder()
testRouter.ServeHTTP(w, req)
var env envelope
_ = json.Unmarshal(w.Body.Bytes(), &env)
if w.Code != 200 || env.Code != 0 {
t.Fatalf("upload failed: %d %s", w.Code, env.Message)
}
var data struct {
Material models.Material `json:"material"`
Dedup bool `json:"dedup"`
}
_ = json.Unmarshal(env.Data, &data)
if data.Dedup {
t.Fatal("first upload should not be dedup")
}
return data.Material.ID, data.Material.SHA256
}
func TestAccountChallengeFlow(t *testing.T) {
resetTestDB()
access, _ := register(t, "ops@test.com")
code, env := doReq(t, "POST", "/api/v1/accounts", access, gin.H{"platform": "douyin", "accountName": "抖音测试号"})
if code != 200 || env.Code != 0 {
t.Fatalf("create account failed: %d %s", code, env.Message)
}
var acc models.Account
_ = json.Unmarshal(env.Data, &acc)
// 不支持的平台
code, _ = doReq(t, "POST", "/api/v1/accounts", access, gin.H{"platform": "tiktok", "accountName": "x"})
if code != 400 {
t.Fatalf("unsupported platform should 400, got %d", code)
}
// 绑定 → 挑战
code, env = doReq(t, "POST", fmt.Sprintf("/api/v1/accounts/%d/bind", acc.ID), access, nil)
if code != 200 || env.Code != 0 {
t.Fatalf("bind failed: %d %s", code, env.Message)
}
// 绑定中账号不可删
code, _ = doReq(t, "DELETE", fmt.Sprintf("/api/v1/accounts/%d", acc.ID), access, nil)
if code != 409 {
t.Fatalf("delete binding account should 409, got %d", code)
}
// 挑战列表
code, env = doReq(t, "GET", "/api/v1/challenges", access, nil)
if code != 200 || env.Code != 0 {
t.Fatalf("challenge list failed: %d %s", code, env.Message)
}
var cl struct {
List []models.Challenge `json:"list"`
}
_ = json.Unmarshal(env.Data, &cl)
if len(cl.List) != 1 || cl.List[0].Kind != "pending" {
t.Fatalf("expect 1 pending challenge, got %d", len(cl.List))
}
// 挂起 → 重发
code, env = doReq(t, "POST", fmt.Sprintf("/api/v1/challenges/%d/suspend", cl.List[0].ID), access, nil)
if code != 200 {
t.Fatalf("suspend failed: %d", code)
}
code, env = doReq(t, "POST", fmt.Sprintf("/api/v1/challenges/%d/resend", cl.List[0].ID), access, nil)
if code != 200 {
t.Fatalf("resend failed: %d", code)
}
}
func TestMaterialAndTaskFlow(t *testing.T) {
resetTestDB()
access, _ := register(t, "pub@test.com")
content := []byte("fake-video-bytes-for-sha256-test-0001")
matID, sha := uploadMaterial(t, access, "demo.mp4", content)
if len(sha) != 64 {
t.Fatalf("bad sha256: %s", sha)
}
// 重复上传 → 去重
buf := multipartBody(t, "demo2.mp4", "video/mp4", content)
req := httptest.NewRequest("POST", "/api/v1/materials", buf)
req.Header.Set("Content-Type", "multipart/form-data; boundary="+strings.Split(buf.String(), "\r\n")[0][2:])
req.Header.Set("Authorization", "Bearer "+access)
w := httptest.NewRecorder()
testRouter.ServeHTTP(w, req)
var env envelope
_ = json.Unmarshal(w.Body.Bytes(), &env)
var data struct {
Material models.Material `json:"material"`
Dedup bool `json:"dedup"`
}
_ = json.Unmarshal(env.Data, &data)
if !data.Dedup || data.Material.ID != matID {
t.Fatalf("dedup expect same id %d, got dedup=%v id=%d", matID, data.Dedup, data.Material.ID)
}
// 签名直链(一次性)
code, env := doReq(t, "GET", fmt.Sprintf("/api/v1/materials/%d/url", matID), access, nil)
if code != 200 {
t.Fatalf("url failed: %d", code)
}
var urld struct {
URL string `json:"url"`
}
_ = json.Unmarshal(env.Data, &urld)
path := strings.TrimPrefix(urld.URL, "http://127.0.0.1:8090")
if path == urld.URL {
t.Fatalf("bad url: %s", urld.URL)
}
code, _ = doReq(t, "GET", path, "", nil)
if code != 200 {
t.Fatalf("file fetch should 200, got %d", code)
}
code, _ = doReq(t, "GET", path, "", nil)
if code != 404 {
t.Fatalf("one-time token should 404 on reuse, got %d", code)
}
// 建任务 → 提交 → 驳回 → 重提 → 通过 → 取消
code, env = doReq(t, "POST", "/api/v1/tasks", access, gin.H{
"title": "新品发布", "content": "正文", "tags": []string{"新品"},
"accountIds": []uint64{1}, "materialIds": []uint64{matID},
})
if code != 200 || env.Code != 0 {
t.Fatalf("create task failed: %d %s", code, env.Message)
}
var taskRow models.Task
_ = json.Unmarshal(env.Data, &taskRow)
if taskRow.Status != "draft" {
t.Fatalf("new task should be draft, got %s", taskRow.Status)
}
// 非法转移:draft 直接 approve 应 409
code, _ = doReq(t, "POST", fmt.Sprintf("/api/v1/tasks/%d/approve", taskRow.ID), access, nil)
if code != 409 {
t.Fatalf("draft approve should 409, got %d", code)
}
code, _ = doReq(t, "POST", fmt.Sprintf("/api/v1/tasks/%d/submit", taskRow.ID), access, nil)
if code != 200 {
t.Fatalf("submit failed: %d", code)
}
code, _ = doReq(t, "POST", fmt.Sprintf("/api/v1/tasks/%d/reject", taskRow.ID), access, gin.H{"note": "标题需修改"})
if code != 200 {
t.Fatalf("reject failed: %d", code)
}
code, env = doReq(t, "GET", fmt.Sprintf("/api/v1/tasks/%d", taskRow.ID), access, nil)
_ = json.Unmarshal(env.Data, &taskRow)
if taskRow.Status != "rejected" {
t.Fatalf("expect rejected, got %s", taskRow.Status)
}
code, _ = doReq(t, "POST", fmt.Sprintf("/api/v1/tasks/%d/resubmit", taskRow.ID), access, nil)
if code != 200 {
t.Fatalf("resubmit failed: %d", code)
}
code, _ = doReq(t, "POST", fmt.Sprintf("/api/v1/tasks/%d/approve", taskRow.ID), access, nil)
if code != 200 {
t.Fatalf("approve failed: %d", code)
}
code, env = doReq(t, "GET", fmt.Sprintf("/api/v1/tasks/%d", taskRow.ID), access, nil)
_ = json.Unmarshal(env.Data, &taskRow)
if taskRow.Status != "queued" {
t.Fatalf("expect queued, got %s", taskRow.Status)
}
code, _ = doReq(t, "POST", fmt.Sprintf("/api/v1/tasks/%d/cancel", taskRow.ID), access, nil)
if code != 200 {
t.Fatalf("cancel failed: %d", code)
}
// 通知:驳回+通过 2 条
code, env = doReq(t, "GET", "/api/v1/notifications", access, nil)
if code != 200 {
t.Fatalf("notifications failed: %d", code)
}
var nl struct {
List []models.Notification `json:"list"`
Unread int64 `json:"unread"`
}
_ = json.Unmarshal(env.Data, &nl)
if nl.Unread < 2 {
t.Fatalf("expect >=2 unread, got %d", nl.Unread)
}
code, _ = doReq(t, "POST", "/api/v1/notifications/read-all", access, nil)
if code != 200 {
t.Fatalf("read-all failed: %d", code)
}
code, env = doReq(t, "GET", "/api/v1/notifications", access, nil)
_ = json.Unmarshal(env.Data, &nl)
if nl.Unread != 0 {
t.Fatalf("expect 0 unread after read-all, got %d", nl.Unread)
}
}