Files
creator-hub/internal/controlplane/api/creator_helper_test.go
T
rogee c567080248 refactor(api): 删除运营链路与 runtime_use_lease
删除任务链(drafts/confirmations/tasks/attempts)、creator 运营链(策略/事件/操作/会话/消息/监听器/素材/关系)、runtime_use_lease 机制及其 API/UI;taskstate 包与对应测试一并移除。
2026-09-28 17:14:00 +08:00

168 lines
7.2 KiB
Go

package api
import (
"context"
"encoding/base64"
"errors"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
"git.ipao.vip/rogee/creator-hub/internal/creator"
hub "git.ipao.vip/rogee/creator-hub/internal/environment"
"github.com/gofiber/fiber/v3"
)
func TestCreatorHelperBranches(t *testing.T) {
if got := flattenActionEvidence(map[string]string{}, "evidence", "text"); got != 1 {
t.Fatalf("flatten string count = %d", got)
}
values := map[string]string{}
if got := flattenActionEvidence(values, "evidence", map[string]any{"nested": "value", "empty": "", "number": 1}); got != 1 || values["evidence.nested"] != "value" {
t.Fatalf("flatten map = %d, %#v", got, values)
}
if got := flattenActionEvidence(values, "evidence", []any{"ignored"}); got != 0 {
t.Fatalf("flatten unsupported count = %d", got)
}
if _, err := verifyCreatorPlatformIdentity(context.Background(), "unsupported", hub.Gateway{}, hub.EnvironmentContext{}, "key"); !errors.Is(err, creator.ErrUnavailable) {
t.Fatalf("unsupported identity platform = %v", err)
}
if _, _, err := newCreatorCollector(context.Background(), creator.PlatformDouyin, hub.Gateway{}, hub.EnvironmentContext{}, "", "target", "", creator.SourceCompetitor, "id"); !errors.Is(err, creator.ErrInvalid) {
t.Fatalf("missing collector key = %v", err)
}
if _, err := decodeBase64("not-base64"); err == nil {
t.Fatal("invalid base64 must fail")
}
encoded := base64.StdEncoding.EncodeToString([]byte("media"))
if data, err := decodeBase64(encoded); err != nil || string(data) != "media" {
t.Fatalf("decode media = %q, %v", data, err)
}
if err := validateDouyinCompetitor(creator.CompetitorInput{Platform: creator.PlatformDouyin}); err != nil {
t.Fatal(err)
}
if err := validateDouyinCompetitor(creator.CompetitorInput{Platform: "unsupported"}); !errors.Is(err, creator.ErrInvalid) {
t.Fatalf("unsupported competitor platform = %v", err)
}
}
func TestCreatorAccountMetricSnapshotFailurePropagates(t *testing.T) {
// 浏览器不可用(网关零值)时快照拉取失败必须向上返回错误,绝不静默吞掉;
// 调度器对 syncCreatorOwned 的失败会记 warning 日志(runCreatorScheduleOnce)。
if err := recordCreatorAccountMetricSnapshot(context.Background(), nil, creatorGatewayBrowser{}, "account-1"); err == nil {
t.Fatal("account metric snapshot failure must propagate error")
}
}
func TestCreatorPageQueryValidation(t *testing.T) {
app := fiber.New()
app.Get("/", func(c fiber.Ctx) error {
page, pageSize, enabled, err := creatorPageQuery(c)
if err != nil {
return creatorError(c, err)
}
return c.JSON(map[string]any{"page": page, "page_size": pageSize, "enabled": enabled})
})
for _, query := range []string{"", "?page=2&page_size=10&enabled=false"} {
response, err := app.Test(httptest.NewRequest(http.MethodGet, "/"+query, nil))
if err != nil || response.StatusCode != http.StatusOK {
t.Fatalf("valid query %s: %d %v", query, response.StatusCode, err)
}
}
for _, query := range []string{"?page=bad"} {
response, err := app.Test(httptest.NewRequest(http.MethodGet, "/"+query, nil))
if err != nil || response.StatusCode != http.StatusBadRequest {
t.Fatalf("invalid query %s: %d %v", query, response.StatusCode, err)
}
}
}
func TestCreatorControlPlaneGuards(t *testing.T) {
ctx := context.Background()
if _, _, err := newCreatorCollector(ctx, "unsupported", hub.Gateway{}, hub.EnvironmentContext{}, "viewer", "target", "", creator.SourceOwned, "id"); !errors.Is(err, creator.ErrUnavailable) {
t.Fatalf("unsupported collector platform = %v", err)
}
}
func TestCreatorSchedulerAndPreviewGuards(t *testing.T) {
ctx := context.Background()
checks := []struct {
name string
call func() error
}{
{"verify account", func() error { _, err := verifyCreatorAccount(ctx, nil, nil, nil, "account"); return err }},
{"login QR", func() error { _, err := creatorLoginQRCode(ctx, nil, nil, nil, "account"); return err }},
{"preview competitor", func() error {
_, err := previewDouyinCompetitor(ctx, nil, nil, nil, "account", creator.CompetitorInput{})
return err
}},
{"sync competitor", func() error { _, err := syncCreatorCompetitor(ctx, nil, nil, "competitor"); return err }},
{"sync due competitor", func() error {
_, err := syncCreatorCompetitorDue(ctx, nil, nil, "competitor")
return err
}},
{"sync owned", func() error { return syncCreatorOwned(ctx, nil, nil, nil, "account", time.Now()) }},
{"select collection account", func() error {
_, err := creatorCollectionAccount(ctx, nil, nil, nil, creator.PlatformDouyin)
return err
}},
}
for _, check := range checks {
if err := check.call(); err == nil {
t.Fatalf("%s unexpectedly succeeded", check.name)
}
}
}
func TestCreatorGatewayBrowserHistoryAndMedia(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch {
case strings.HasSuffix(r.URL.Path, "/douyin/messages"):
_, _ = w.Write([]byte(`{"status":"succeeded","account_uid":"account","history_source":"douyin","messages":[]}`))
case strings.HasSuffix(r.URL.Path, "/douyin/media"):
_, _ = w.Write([]byte(`{"status":200,"content_type":"video/mp4","body_base64":"` + base64.StdEncoding.EncodeToString([]byte("video")) + `"}`))
default:
http.NotFound(w, r)
}
}))
defer server.Close()
browser := creatorGatewayBrowser{gateway: hub.Gateway{Endpoint: server.URL, Token: "token"}, environment: hub.EnvironmentContext{Env: hub.Env{Alias: "browser"}, RuntimeID: "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", RuntimeNetworkID: "native-dddddddddddddddddddddddddddddddd", BindingVersion: 1}}
history, err := browser.MessageHistory(context.Background(), "account", "peer", "", 10)
if err != nil || history.Status != "succeeded" || history.HistorySource != "douyin" {
t.Fatalf("history = %+v, %v", history, err)
}
if _, err := browser.MessageHistory(context.Background(), "", "peer", "", 10); err == nil {
t.Fatal("empty history identity must fail")
}
path := filepath.Join(t.TempDir(), "media.mp4")
if err := browser.Media(context.Background(), "https://video.example/media", path); err != nil {
t.Fatal(err)
}
if data, err := os.ReadFile(path); err != nil || string(data) != "video" {
t.Fatalf("media output = %q, %v", data, err)
}
}
func TestCreatorGatewayBrowserIdentity(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost || !strings.Contains(r.URL.Path, "/douyin/identity") {
t.Fatalf("unexpected identity request: %s %s", r.Method, r.URL.Path)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"uid":"verified-uid"}`))
}))
defer server.Close()
browser := creatorGatewayBrowser{
gateway: hub.Gateway{Endpoint: server.URL, Token: "token"},
environment: hub.EnvironmentContext{Env: hub.Env{Alias: "browser"}, RuntimeID: "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", RuntimeNetworkID: "native-dddddddddddddddddddddddddddddddd", BindingVersion: 1},
}
uid, err := browser.Identity(context.Background(), "expected-key")
if err != nil || uid != "verified-uid" {
t.Fatalf("identity = %q, %v", uid, err)
}
}