Files
creator-hub/internal/controlplane/api/creator_share_test.go
T
rogee 221ed65e32 feat(creator): 竞品作品同步改用匿名浏览器并限制并发
- 竞品同步不再依赖自有采集账号:每次同步按名额(并发上限 2)创建匿名浏览器,
  导航至抖音首页等待访客 cookie 稳定后经网关受限 fetch 拉取作品
- 调度器对到期竞品并发同步,名额耗尽时排队等待
- 平台风控语义(实测,与公开爬虫项目一致):作品列表匿名仅能取第一页
  (16-20 条),后续页一律空 envelope;空 envelope 视为正常分页终止
- 评论解析适配真实响应:reply_id="0" 为顶层评论、作者取 user 对象、
  纯图片评论(text 空但带 image_list)记为「[图片]」
- fetch 签名与白名单:注入 byted_acrawler.frontierSign 的 a_bogus,
  网关放宽响应上限至 8MB 并允许签名参数
2026-09-27 15:43:03 +08:00

152 lines
4.7 KiB
Go

package api
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"git.ipao.vip/rogee/creator-hub/internal/creator"
hub "git.ipao.vip/rogee/creator-hub/internal/environment"
)
func TestCompetitorSharePlatform(t *testing.T) {
for _, test := range []struct {
name string
url string
want string
}{
{name: "douyin short link", url: "https://v.douyin.com/abc123/", want: creator.PlatformDouyin},
{name: "douyin content", url: "https://www.douyin.com/video/123", want: creator.PlatformDouyin},
} {
t.Run(test.name, func(t *testing.T) {
got, err := competitorSharePlatform(test.url)
if err != nil || got != test.want {
t.Fatalf("competitorSharePlatform(%q) = %q, %v; want %q", test.url, got, err, test.want)
}
})
}
for _, url := range []string{
"http://v.douyin.com/abc123/",
"https://www.douyin.com.evil/video/123",
"https://v.douyin.com/abc123/#fragment",
} {
t.Run(url, func(t *testing.T) {
if _, err := competitorSharePlatform(url); err == nil {
t.Fatalf("competitorSharePlatform(%q) unexpectedly succeeded", url)
}
})
}
}
func TestRandomGatewaySelectsFromRegisteredGateways(t *testing.T) {
gateways := []hub.Gateway{
{Name: "gateway-a"},
{Name: "gateway-b"},
{Name: "gateway-c"},
}
allowed := map[string]bool{
"gateway-a": true,
"gateway-b": true,
"gateway-c": true,
}
for range 32 {
if gateway := randomGateway(gateways); !allowed[gateway.Name] {
t.Fatalf("randomGateway selected unregistered gateway %q", gateway.Name)
}
}
}
func TestAnonymousBrowserLeasePurgesRuntimeAndProfile(t *testing.T) {
var requestBody map[string]any
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete || r.URL.Path != "/v1/browsers/anon-test" {
t.Fatalf("unexpected cleanup request: %s %s", r.Method, r.URL.Path)
}
if err := json.NewDecoder(r.Body).Decode(&requestBody); err != nil {
t.Fatal(err)
}
w.WriteHeader(http.StatusNoContent)
}))
defer server.Close()
err := (anonymousBrowserLease{
gateway: hub.Gateway{Endpoint: server.URL, Token: "test-token"},
environment: hub.EnvironmentContext{
Env: hub.Env{Alias: "anon-test"},
BindingVersion: 1,
RuntimeID: strings.Repeat("a", 64),
RuntimeNetworkID: "native-" + strings.Repeat("b", 32),
},
}).close()
if err != nil {
t.Fatal(err)
}
if requestBody["purge_profile"] != true || requestBody["binding_version"] != float64(1) {
t.Fatalf("cleanup request did not fence and purge the temporary runtime: %#v", requestBody)
}
if _, ok := requestBody["network_exit_id"]; ok {
t.Fatalf("cleanup request must use only the runtime generation fence: %#v", requestBody)
}
}
func TestAnonymousBrowserLeaseReleasesConcurrencySlot(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete || r.URL.Path != "/v1/browsers/anon-test" {
t.Fatalf("unexpected cleanup request: %s %s", r.Method, r.URL.Path)
}
w.WriteHeader(http.StatusNoContent)
}))
defer server.Close()
slots := make(chan struct{}, 1)
slots <- struct{}{}
lease := anonymousBrowserLease{gateway: hub.Gateway{Endpoint: server.URL, Token: "test-token"}, slot: slots, environment: hub.EnvironmentContext{Env: hub.Env{Alias: "anon-test"}, BindingVersion: 1}}
if err := lease.close(); err != nil {
t.Fatal(err)
}
// 释放 = 名额回到池里(能再次非阻塞占用)。
select {
case slots <- struct{}{}:
default:
t.Fatal("close must release the anonymous browser concurrency slot")
}
// 创建失败路径也必须释放名额,否则失败几次后永久占满。
failed := anonymousBrowserLease{gateway: hub.Gateway{Endpoint: "http://127.0.0.1:1", Token: "test-token"}, slot: slots}
if err := failed.close(); err == nil {
t.Fatal("cleanup against unreachable gateway must fail")
}
select {
case slots <- struct{}{}:
default:
t.Fatal("failed close must still release the anonymous browser concurrency slot")
}
}
func TestDouyinWorkKeyFromURL(t *testing.T) {
for _, test := range []struct {
url string
want string
}{
{url: "https://www.douyin.com/video/123", want: "123"},
{url: "https://www.douyin.com/video/123?previous_page=app_code_link", want: "123"},
} {
got, err := douyinWorkKeyFromURL(test.url)
if err != nil || got != test.want {
t.Fatalf("douyinWorkKeyFromURL(%q) = %q, %v; want %q", test.url, got, err, test.want)
}
}
for _, url := range []string{
"https://www.douyin.com/video/0",
"https://www.douyin.com/user/123",
"https://v.douyin.com/abc123",
} {
if _, err := douyinWorkKeyFromURL(url); err == nil {
t.Fatalf("douyinWorkKeyFromURL(%q) unexpectedly succeeded", url)
}
}
}