Files
creator-hub/internal/controlplane/api/gateway_browser_unit_test.go

267 lines
11 KiB
Go

package api
import (
"context"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
hub "git.ipao.vip/rogee/creator-hub/internal/environment"
douyin "git.ipao.vip/rogee/creator-hub/internal/platform/douyin"
)
func testRunnableEnvironment() hub.EnvironmentContext {
return hub.EnvironmentContext{
Env: hub.Env{Alias: "account-1"},
AccountID: "account-1",
AccountStatus: "active",
AuthorizationStatus: "authorized",
BindingID: "binding-1",
RuntimeID: "runtime-1",
RuntimeInstanceID: "runtime-instance-1",
RuntimeNetworkID: "network-1",
Exit: hub.NetworkExit{ID: "exit-1", HealthStatus: "healthy"},
BindingVersion: 1,
}
}
func TestPurgeAccountProfileTreatsMissingRuntimeAsIdempotent(t *testing.T) {
environment := testRunnableEnvironment()
for _, status := range []int{http.StatusNoContent, http.StatusNotFound} {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete || r.URL.Path != "/v1/browsers/account-1" {
t.Fatalf("purge request = %s %s", r.Method, r.URL.Path)
}
w.WriteHeader(status)
}))
if err := purgeAccountProfile(context.Background(), hub.Gateway{Endpoint: server.URL}, environment); err != nil {
t.Errorf("status %d: %v", status, err)
}
server.Close()
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadGateway)
_, _ = w.Write([]byte("gateway failure"))
}))
if err := purgeAccountProfile(context.Background(), hub.Gateway{Endpoint: server.URL}, environment); err == nil {
t.Fatal("gateway rejection was reported as successful")
}
server.Close()
if err := purgeAccountProfile(context.Background(), hub.Gateway{Endpoint: "http://127.0.0.1:1"}, environment); err == nil {
t.Fatal("unreachable gateway was reported as successful")
}
}
func TestCreatorGatewayBrowserOperationsValidateResponses(t *testing.T) {
response := map[string]string{
"identity": `{"uid":"123"}`,
"messages": `{"status":"succeeded","history_source":"live","history_cursor":"next","account_uid":"123","messages":[]}`,
"get": `{"status":200,"body":"ok"}`,
"resolve": `{"url":"https://www.douyin.com/video/1"}`,
"media": `{"status":200,"content_type":"video/mp4","body_base64":"aGk="}`,
"login-qr": `{"content_type":"image/png","body_base64":"aGk=","qr_detected":true}`,
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
key := ""
switch r.URL.Path {
case "/v1/browsers/account-1/douyin/identity":
key = "identity"
case "/v1/browsers/account-1/douyin/messages":
key = "messages"
case "/v1/browsers/account-1/douyin/get":
key = "get"
case "/v1/browsers/account-1/douyin/resolve":
key = "resolve"
case "/v1/browsers/account-1/douyin/media":
key = "media"
case "/v1/browsers/account-1/douyin/login-qr":
key = "login-qr"
default:
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(response[key]))
}))
defer server.Close()
environment := testRunnableEnvironment()
browser := creatorGatewayBrowser{gateway: hub.Gateway{Endpoint: server.URL}, environment: environment}
if uid, err := browser.Identity(context.Background(), "douyin-uid"); err != nil || uid != "123" {
t.Fatalf("identity = %q, %v", uid, err)
}
history, err := browser.MessageHistory(context.Background(), "123", "456", "", 20)
if err != nil || history.HistoryCursor != "next" {
t.Fatalf("history = %#v, %v", history, err)
}
if got, err := browser.Get(context.Background(), "https://www.douyin.com/video/1"); err != nil || got.Status != http.StatusOK || string(got.Body) != "ok" || got.Challenge != douyin.ChallengeNone {
t.Fatalf("get = %#v, %v", got, err)
}
if got, err := browser.Resolve(context.Background(), "https://www.douyin.com/video/1"); err != nil || got == "" {
t.Fatalf("resolve = %q, %v", got, err)
}
destination := filepath.Join(t.TempDir(), "video.mp4")
if err := browser.Media(context.Background(), "https://www.douyin.com/video/1", destination); err != nil {
t.Fatal(err)
}
if data, err := os.ReadFile(destination); err != nil || string(data) != "hi" {
t.Fatalf("media = %q, %v", data, err)
}
douyinBrowser := douyinGatewayBrowser{gateway: hub.Gateway{Endpoint: server.URL, Token: "test-token"}, environment: environment}
qr, err := douyinBrowser.LoginQR(context.Background())
if err != nil || qr.ContentType != "image/png" || !qr.QRDetected {
t.Fatalf("login QR = %#v, %v", qr, err)
}
if got, err := douyinBrowser.Get(context.Background(), "https://www.douyin.com/video/1"); err != nil || got.Status != http.StatusOK {
t.Fatalf("douyin get = %#v, %v", got, err)
}
if _, err := douyinBrowser.request(); err != nil {
t.Fatal(err)
}
}
func TestWriteCreatorMediaRejectsInvalidAndPublishesAtomically(t *testing.T) {
if err := writeCreatorMedia("", []byte("data")); err == nil {
t.Fatal("empty destination was accepted")
}
if err := writeCreatorMedia(filepath.Join(t.TempDir(), "video"), nil); err == nil {
t.Fatal("empty media was accepted")
}
if err := writeCreatorMedia(filepath.Join(t.TempDir(), "video"), make([]byte, maxCreatorMediaBytes+1)); err == nil {
t.Fatal("oversized media was accepted")
}
if err := writeCreatorMedia(filepath.Join(t.TempDir(), "missing", "video"), []byte("data")); err == nil {
t.Fatal("unwritable media destination was accepted")
}
destination := filepath.Join(t.TempDir(), "video")
if err := writeCreatorMedia(destination, []byte("data")); err != nil {
t.Fatal(err)
}
data, err := os.ReadFile(destination)
if err != nil || string(data) != "data" {
t.Fatalf("published media = %q, err = %v", data, err)
}
}
func TestDouyinWorkKeyFromURLValidatesCanonicalVideoURLs(t *testing.T) {
for _, testCase := range []struct {
value string
want string
valid bool
}{
{value: "https://www.douyin.com/video/123", want: "123", valid: true},
{value: "https://www.douyin.com/video/", valid: false},
{value: "https://www.douyin.com/video/0", valid: false},
{value: "https://v.douyin.com/video/123", valid: false},
{value: "https://www.douyin.com/user/123", valid: false},
{value: "https://www.douyin.com/video/abc", valid: false},
} {
got, err := douyinWorkKeyFromURL(testCase.value)
if testCase.valid && (err != nil || got != testCase.want) {
t.Errorf("douyinWorkKeyFromURL(%q) = %q, %v", testCase.value, got, err)
}
if !testCase.valid && err == nil {
t.Errorf("douyinWorkKeyFromURL(%q) was accepted", testCase.value)
}
}
}
func TestCreatorGatewayBrowserRejectsMalformedGatewayData(t *testing.T) {
response := `{"uid":""}`
status := http.StatusOK
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(status)
_, _ = w.Write([]byte(response))
}))
defer server.Close()
environment := testRunnableEnvironment()
browser := creatorGatewayBrowser{gateway: hub.Gateway{Endpoint: server.URL}, environment: environment}
if _, err := browser.Identity(context.Background(), "expected"); err == nil {
t.Fatal("empty identity was accepted")
}
status = http.StatusBadGateway
if _, err := browser.Identity(context.Background(), "expected"); err == nil {
t.Fatal("rejected identity was accepted")
}
status = http.StatusBadGateway
if _, err := browser.MessageHistory(context.Background(), "expected", "target", "", 1); err == nil {
t.Fatal("rejected message history was accepted")
}
if _, err := browser.Get(context.Background(), "target"); err == nil {
t.Fatal("rejected browser request was accepted")
}
if _, err := browser.Resolve(context.Background(), "target"); err == nil {
t.Fatal("rejected share resolution was accepted")
}
if err := browser.Media(context.Background(), "target", filepath.Join(t.TempDir(), "video")); err == nil {
t.Fatal("rejected media request was accepted")
}
douyinBrowser := douyinGatewayBrowser{gateway: hub.Gateway{Endpoint: server.URL, Token: "test-token"}, environment: environment}
if _, err := douyinBrowser.LoginQR(context.Background()); err == nil {
t.Fatal("rejected login QR request was accepted")
}
if _, err := douyinBrowser.Get(context.Background(), "target"); err == nil {
t.Fatal("rejected native browser request was accepted")
}
status = http.StatusOK
for _, args := range [][4]any{
{"", "target", "", 1},
{"expected", "", "", 1},
{"expected", "target", "cursor", 0},
{"expected", "target", "cursor", 201},
} {
if _, err := browser.MessageHistory(context.Background(), args[0].(string), args[1].(string), args[2].(string), args[3].(int)); err == nil {
t.Fatalf("invalid history args %#v were accepted", args)
}
}
response = `{`
if _, err := browser.Get(context.Background(), "target"); err == nil {
t.Fatal("malformed get response was accepted")
}
if _, err := browser.Resolve(context.Background(), "target"); err == nil {
t.Fatal("malformed resolve response was accepted")
}
if err := browser.Media(context.Background(), "target", filepath.Join(t.TempDir(), "video")); err == nil {
t.Fatal("malformed media response was accepted")
}
if _, err := douyinBrowser.LoginQR(context.Background()); err == nil {
t.Fatal("malformed login QR response was accepted")
}
invalid := environment
invalid.RuntimeID = ""
if _, err := (douyinGatewayBrowser{gateway: hub.Gateway{Endpoint: server.URL}, environment: invalid}).request(); err == nil {
t.Fatal("non-running environment was accepted")
}
response = `{"status":200,"body":"ok","challenge":"unexpected"}`
if _, err := douyinBrowser.Get(context.Background(), "target"); err == nil {
t.Fatal("invalid challenge was accepted")
}
response = `{"content_type":"text/plain","body_base64":"aGk="}`
if _, err := douyinBrowser.LoginQR(context.Background()); err == nil {
t.Fatal("non-image login QR was accepted")
}
response = `{"content_type":"image/png","body_base64":"!!!"}`
if _, err := douyinBrowser.LoginQR(context.Background()); err == nil {
t.Fatal("invalid login QR base64 was accepted")
}
response = `{"content_type":"image/png","body_base64":""}`
if _, err := douyinBrowser.LoginQR(context.Background()); err == nil {
t.Fatal("empty login QR was accepted")
}
response = `{"status":200,"content_type":"text/plain","body_base64":"aGk="}`
if err := browser.Media(context.Background(), "target", filepath.Join(t.TempDir(), "video")); err == nil {
t.Fatal("non-video media was accepted")
}
response = `{"status":200,"content_type":"video/mp4","body_base64":"!!!"}`
if err := browser.Media(context.Background(), "target", filepath.Join(t.TempDir(), "video")); err == nil {
t.Fatal("invalid media base64 was accepted")
}
}