267 lines
11 KiB
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")
|
|
}
|
|
|
|
}
|