- 竞品同步不再依赖自有采集账号:每次同步按名额(并发上限 2)创建匿名浏览器, 导航至抖音首页等待访客 cookie 稳定后经网关受限 fetch 拉取作品 - 调度器对到期竞品并发同步,名额耗尽时排队等待 - 平台风控语义(实测,与公开爬虫项目一致):作品列表匿名仅能取第一页 (16-20 条),后续页一律空 envelope;空 envelope 视为正常分页终止 - 评论解析适配真实响应:reply_id="0" 为顶层评论、作者取 user 对象、 纯图片评论(text 空但带 image_list)记为「[图片]」 - fetch 签名与白名单:注入 byted_acrawler.frontierSign 的 a_bogus, 网关放宽响应上限至 8MB 并允许签名参数
86 lines
3.3 KiB
Go
86 lines
3.3 KiB
Go
package api
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v3"
|
|
)
|
|
|
|
func TestCreatorUpdatesStreamStopsOnRequestCancellation(t *testing.T) {
|
|
app := fiber.New()
|
|
registerCreatorWithServices(app, nil, nil, nil, nil, nil, nil)
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "/api/creator/updates", http.NoBody)
|
|
response, err := app.Test(request, fiber.TestConfig{
|
|
Timeout: 200 * time.Millisecond,
|
|
FailOnTimeout: false,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer response.Body.Close()
|
|
|
|
body, readErr := io.ReadAll(response.Body)
|
|
if readErr != nil && !strings.Contains(readErr.Error(), "unexpected EOF") {
|
|
t.Fatalf("read stream response: %v", readErr)
|
|
}
|
|
if response.StatusCode != http.StatusOK {
|
|
t.Fatalf("stream status = %d", response.StatusCode)
|
|
}
|
|
if !strings.Contains(string(body), "retry: 5000") {
|
|
t.Fatalf("stream body = %q", body)
|
|
}
|
|
}
|
|
|
|
func TestCreatorWriteRoutesRejectMalformedInputBeforeStoreAccess(t *testing.T) {
|
|
app := fiber.New()
|
|
registerCreatorWithServices(app, nil, nil, nil, nil, nil, nil)
|
|
routes := []struct {
|
|
method string
|
|
path string
|
|
}{
|
|
{method: http.MethodPut, path: "/api/creator/settings"},
|
|
{method: http.MethodPut, path: "/api/creator/accounts/account-1/profile"},
|
|
{method: http.MethodPut, path: "/api/creator/accounts/account-1/tags"},
|
|
{method: http.MethodPost, path: "/api/creator/accounts/account-1/login-result"},
|
|
{method: http.MethodPost, path: "/api/creator/accounts/account-1/big-account"},
|
|
{method: http.MethodPost, path: "/api/creator/relations"},
|
|
{method: http.MethodPost, path: "/api/creator/accounts/account-1/strategies"},
|
|
{method: http.MethodPut, path: "/api/creator/strategies/strategy-1"},
|
|
{method: http.MethodPost, path: "/api/creator/competitor-share-jobs"},
|
|
{method: http.MethodPut, path: "/api/creator/competitors/competitor-1"},
|
|
// sync 路由不解析请求体(强制同步走匿名浏览器,无输入字段),不适用畸形 body 校验。
|
|
{method: http.MethodPost, path: "/api/creator/test/works"},
|
|
{method: http.MethodPost, path: "/api/creator/works/work-1/metrics"},
|
|
{method: http.MethodPost, path: "/api/creator/works/work-1/material/rewrite/confirm"},
|
|
{method: http.MethodPut, path: "/api/creator/works/work-1/material/rewrite"},
|
|
{method: http.MethodPost, path: "/api/creator/test/comments"},
|
|
{method: http.MethodPost, path: "/api/creator/rules"},
|
|
{method: http.MethodPut, path: "/api/creator/rules/rule-1"},
|
|
{method: http.MethodPost, path: "/api/creator/comments/analyze"},
|
|
{method: http.MethodPost, path: "/api/creator/comments/comment-1/analyze"},
|
|
{method: http.MethodPost, path: "/api/creator/test/events"},
|
|
{method: http.MethodPost, path: "/api/creator/events/process"},
|
|
{method: http.MethodPost, path: "/api/creator/messages"},
|
|
}
|
|
for _, route := range routes {
|
|
route := route
|
|
t.Run(route.method+" "+route.path, func(t *testing.T) {
|
|
request := httptest.NewRequest(route.method, route.path, strings.NewReader("{"))
|
|
request.Header.Set("Content-Type", "application/json")
|
|
response, err := app.Test(request)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if response.StatusCode != http.StatusBadRequest && response.StatusCode != http.StatusConflict {
|
|
t.Fatalf("malformed request status = %d", response.StatusCode)
|
|
}
|
|
})
|
|
}
|
|
}
|