Files
creator-hub/cmd/control-plane/creator_helper_test.go
T

236 lines
10 KiB
Go

package main
import (
"context"
"encoding/base64"
"errors"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
"git.ipao.vip/rogee/creator-hub/internal/creator"
"git.ipao.vip/rogee/creator-hub/internal/hub"
"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 token, err := materialClaimToken(); err != nil || len(token) != 32 {
t.Fatalf("material claim token = %q, %v", token, err)
}
if _, err := creatorMaterialHasAudio(context.Background(), "/does/not/exist"); err == nil {
t.Fatal("missing media must not report audio")
}
mediaPath := filepath.Join(t.TempDir(), "media.bin")
if err := writeCreatorMedia(mediaPath, []byte("media")); err != nil {
t.Fatal(err)
}
if data, err := os.ReadFile(mediaPath); err != nil || string(data) != "media" || !fileExists(mediaPath) {
t.Fatalf("media write: %q %v", data, err)
}
if err := os.WriteFile(filepath.Join(filepath.Dir(mediaPath), "empty"), nil, 0o600); err != nil {
t.Fatal(err)
}
if fileExists(filepath.Join(filepath.Dir(mediaPath), "empty")) || fileExists("/does/not/exist") {
t.Fatal("empty or missing media reported as existing")
}
for _, input := range [][2]string{{"", ""}, {"unsupported", "model"}, {"whisper", "model"}} {
if _, err := transcribeCreatorAudio(context.Background(), "/does/not/exist", input[0], input[1]); err == nil {
t.Fatalf("invalid transcription config accepted: %v", input)
}
}
if err := validateTranscriptionBinary("/does/not/exist"); err == nil {
t.Fatal("missing transcription binary accepted")
}
if _, err := processCreatorMaterial(context.Background(), nil, nil, nil, "../escape"); !errors.Is(err, creator.ErrInvalid) {
t.Fatalf("invalid material path = %v", err)
}
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 := decodeXiaohongshuResponse([]byte("not-json")); err == nil {
t.Fatal("malformed Xiaohongshu response must fail")
}
if response, err := decodeXiaohongshuResponse([]byte(`{"status":200,"body":"ok","challenge":""}`)); err != nil || response.Status != 200 || string(response.Body) != "ok" {
t.Fatalf("decode Xiaohongshu response = %+v, %v", response, 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 := validateXiaohongshuSource("", ""); err == nil {
t.Fatal("empty Xiaohongshu source must fail")
}
if err := validateXiaohongshuCompetitor(creator.CompetitorInput{Platform: creator.PlatformDouyin}); err != nil {
t.Fatal(err)
}
}
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 := persistDouyinMessageHistory(ctx, nil, creator.Conversation{Platform: creator.PlatformXiaohongshu}, "uid", nil); !errors.Is(err, creator.ErrInvalid) {
t.Fatalf("invalid history store/platform = %v", err)
}
conversation := creator.Conversation{Platform: creator.PlatformDouyin, AccountID: "account", PeerUID: "peer"}
cases := []douyinHistoryMessage{
{},
{ServerID: "id", SenderUID: "sender", Content: []byte("not-json")},
{ServerID: "id", SenderUID: "sender", CreatedAt: "not-a-time"},
}
for _, item := range cases {
if _, err := persistDouyinMessageHistory(ctx, nil, conversation, "account", []douyinHistoryMessage{item}); !errors.Is(err, creator.ErrInvalid) {
t.Fatalf("invalid history item %v = %v", item, err)
}
}
if _, err := (creatorGatewayActionExecutor{}).Execute(ctx, creator.ActionRequest{}); !errors.Is(err, creator.ErrUnavailable) {
t.Fatalf("empty action executor = %v", err)
}
if err := (creatorMaterialDownloader{}).Download(ctx, creator.Work{Platform: "unsupported"}, "/tmp/media"); !errors.Is(err, creator.ErrUnavailable) {
t.Fatalf("unsupported material platform = %v", err)
}
if _, err := newXiaohongshuReadCollector(ctx, nil, nil, nil, "", creator.SourceOwned, "id"); !errors.Is(err, creator.ErrUnavailable) {
t.Fatalf("empty Xiaohongshu collector = %v", err)
}
if listenerBoundaryPointer(time.Time{}) != nil {
t.Fatal("zero listener boundary should be nil")
}
}
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, nil, "competitor", "account"); return err }},
{"sync due competitor", func() error {
_, err := syncCreatorCompetitorDue(ctx, nil, nil, nil, "competitor", "account")
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: "runtime", RuntimeNetworkID: "network", 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 TestXiaohongshuGatewayMedia(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
if !strings.HasSuffix(r.URL.Path, "/xiaohongshu/media") {
http.NotFound(w, r)
return
}
_, _ = w.Write([]byte(`{"status":200,"content_type":"image/jpeg","body_base64":"` + base64.StdEncoding.EncodeToString([]byte("image")) + `"}`))
}))
defer server.Close()
browser := xiaohongshuGatewayBrowser{gateway: hub.Gateway{Endpoint: server.URL, Token: "token"}, environment: hub.EnvironmentContext{Env: hub.Env{Alias: "browser"}, AccountID: "account", AccountStatus: "active", AuthorizationStatus: "authorized", BindingID: "binding", RuntimeInstanceID: "instance", RuntimeID: "runtime", RuntimeNetworkID: "network", BindingVersion: 1, Exit: hub.NetworkExit{ID: "exit", HealthStatus: "healthy"}}}
data, contentType, err := browser.Media(context.Background(), "https://www.xiaohongshu.com/explore/abc")
if err != nil || string(data) != "image" || contentType != "image/jpeg" {
t.Fatalf("media = %q, %q, %v", data, contentType, 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: "runtime", RuntimeNetworkID: "network", BindingVersion: 1},
}
uid, err := browser.Identity(context.Background(), "expected-key")
if err != nil || uid != "verified-uid" {
t.Fatalf("identity = %q, %v", uid, err)
}
}