195 lines
7.7 KiB
Go
195 lines
7.7 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.ipao.vip/rogee/creator-hub/internal/account"
|
|
"git.ipao.vip/rogee/creator-hub/internal/creator"
|
|
hub "git.ipao.vip/rogee/creator-hub/internal/environment"
|
|
)
|
|
|
|
func privateWorkerFixture(t *testing.T) (*creator.Store, creator.ListenerState) {
|
|
t.Helper()
|
|
databaseURL := os.Getenv("CREATORHUB_POSTGRES_TEST_URL")
|
|
if databaseURL == "" {
|
|
t.Skip("requires CREATORHUB_POSTGRES_TEST_URL")
|
|
}
|
|
store, accounts, ctx := openCreatorIntegrationStoreForAPITest(t, databaseURL)
|
|
owner := account.Account{ID: "dm-owner", Platform: creator.PlatformDouyin, Name: "本人", PlatformAccountKey: "123", CredentialReference: account.CredentialReference{ID: "dm-credential", Provider: "os_keyring"}, CredentialKey: "creatorhub/dm-owner/cookies"}
|
|
if err := accounts.CreateAccount(ctx, owner, &testCredentialBridge{values: make(map[string]string)}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := store.RecordVerifiedLoginResult(ctx, owner.ID, "123"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
state, err := store.SetListenerEnabled(ctx, owner.ID, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return store, state
|
|
}
|
|
func TestPrivateGatewaySendAndSync(t *testing.T) {
|
|
store, state := privateWorkerFixture(t)
|
|
ctx := context.Background()
|
|
var mode atomic.Int32
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if mode.Load() == 1 {
|
|
w.WriteHeader(502)
|
|
return
|
|
}
|
|
var payload map[string]any
|
|
json.NewDecoder(r.Body).Decode(&payload)
|
|
if payload["expected_uid"] != "123" {
|
|
t.Errorf("identity=%v", payload)
|
|
}
|
|
if r.URL.Path == "/v1/browsers/browser/douyin/action" {
|
|
w.Write([]byte(`{"status":"succeeded","success":true,"message":{"server_id":"100"}}`))
|
|
return
|
|
}
|
|
if r.URL.Path != "/v1/browsers/browser/douyin/inbox" {
|
|
t.Errorf("path=%s", r.URL.Path)
|
|
}
|
|
if mode.Load() == 2 {
|
|
w.Write([]byte(`{"status":"succeeded","account_uid":"123","messages":[]}`))
|
|
return
|
|
}
|
|
if mode.Load() == 3 {
|
|
w.Write([]byte(`{"status":"failed","code":"IM_SDK_NOT_READY"}`))
|
|
return
|
|
}
|
|
if mode.Load() == 4 {
|
|
w.Write([]byte(`{"status":"succeeded","account_uid":"123","messages":[],"peers":[{"peer_uid":"456","peer_name":"改名后昵称","error":""}],"checkpoints":{"456":"101"}}`))
|
|
return
|
|
}
|
|
if mode.Load() == 5 {
|
|
w.Write([]byte(`{"status":"succeeded","account_uid":"123","messages":[],"peers":[{"peer_uid":"456","peer_name":"","error":"昵称读取失败"}],"checkpoints":{"456":"101"}}`))
|
|
return
|
|
}
|
|
w.Write([]byte(`{"status":"succeeded","account_uid":"123","peers":[{"peer_uid":"456","peer_name":"真实昵称","error":""}],"messages":[{"peer_uid":"456","server_id":"101","sender_uid":"456","message_type":"7","content":"{\"aweType\":700,\"text\":\"你好\"}","created_at":"1700000000000"}],"checkpoints":{"456":"101"}}`))
|
|
}))
|
|
defer server.Close()
|
|
accounts := listenerAccountsStub{gateway: hub.Gateway{Endpoint: server.URL, Token: "token"}, environment: hub.EnvironmentContext{Env: hub.Env{Alias: "browser", Gateway: "gateway"}, RuntimeID: "runtime", RuntimeNetworkID: "network", BindingVersion: 7}}
|
|
input := creator.PrivateMessageSendInput{RequestID: "send-one", AccountID: state.AccountID, PeerUID: "456", Text: "你好"}
|
|
reservation, err := store.BeginPrivateMessage(ctx, input)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
result := gatewayPrivateMessageSender(accounts, store)(ctx, input, reservation)
|
|
if result.State != "succeeded" || result.ServerID != "100" {
|
|
t.Fatalf("send=%+v", result)
|
|
}
|
|
checkpoints, err := syncPrivateInbox(ctx, accounts, store, state, map[string]string{"456": "99"})
|
|
if err != nil || checkpoints["456"] != "101" {
|
|
t.Fatalf("sync=%v %v", checkpoints, err)
|
|
}
|
|
page, err := store.ListPrivateMessages(ctx, state.AccountID, "456", 1, 25)
|
|
if err != nil || page.Total != 2 {
|
|
t.Fatalf("page=%+v %v", page, err)
|
|
}
|
|
for _, m := range []int32{1, 2, 3} {
|
|
mode.Store(m)
|
|
if _, err := syncPrivateInbox(ctx, accounts, store, state, checkpoints); err == nil {
|
|
t.Fatalf("accepted mode %d", m)
|
|
}
|
|
}
|
|
mode.Store(4)
|
|
refreshed, err := syncPrivateInbox(ctx, accounts, store, state, checkpoints)
|
|
if err != nil || refreshed["456"] != "101" {
|
|
t.Fatalf("nickname-only sync=%+v %v", refreshed, err)
|
|
}
|
|
conversations, err := store.ListPrivateConversations(ctx, state.AccountID, 1, 25)
|
|
if err != nil || conversations.Data[0].PeerName != "改名后昵称" || conversations.Data[0].MessageCount != 2 {
|
|
t.Fatalf("nickname refresh without new messages=%+v %v", conversations, err)
|
|
}
|
|
mode.Store(5)
|
|
if refreshed, err = syncPrivateInbox(ctx, accounts, store, state, checkpoints); err != nil || refreshed["456"] != "101" {
|
|
t.Fatalf("profile error must not block checkpoint=%+v %v", refreshed, err)
|
|
}
|
|
statuses, err := store.ListPrivateSyncStatus(ctx)
|
|
if err != nil || statuses[0].Error != "昵称读取失败" {
|
|
t.Fatalf("profile error persistence=%+v %v", statuses, err)
|
|
}
|
|
mode.Store(1)
|
|
result = gatewayPrivateMessageSender(accounts, store)(ctx, input, reservation)
|
|
if result.State != "unknown" {
|
|
t.Fatalf("HTTP error=%+v", result)
|
|
}
|
|
invalid := accounts
|
|
invalid.environmentErr = errors.New("missing environment")
|
|
result = gatewayPrivateMessageSender(invalid, store)(ctx, input, reservation)
|
|
if result.State != "failed" {
|
|
t.Fatalf("missing environment=%+v", result)
|
|
}
|
|
if _, err := syncPrivateInbox(ctx, invalid, store, state, nil); err == nil {
|
|
t.Fatal("missing environment accepted")
|
|
}
|
|
if _, err := store.SetListenerEnabled(ctx, state.AccountID, false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
result = gatewayPrivateMessageSender(accounts, store)(ctx, input, reservation)
|
|
if result.State != "failed" {
|
|
t.Fatalf("disabled=%+v", result)
|
|
}
|
|
if _, err := privateListenerState(ctx, store, "missing"); !errors.Is(err, creator.ErrNotFound) {
|
|
t.Fatalf("missing=%v", err)
|
|
}
|
|
}
|
|
func TestPrivateSyncWorkerRecoveryAndErrors(t *testing.T) {
|
|
for _, failure := range []bool{false, true} {
|
|
t.Run(map[bool]string{false: "success", true: "failure"}[failure], func(t *testing.T) {
|
|
store, state := privateWorkerFixture(t)
|
|
input := creator.PrivateMessageSendInput{RequestID: "interrupted", AccountID: state.AccountID, PeerUID: "456", Text: "你好"}
|
|
if _, err := store.BeginPrivateMessage(context.Background(), input); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if failure {
|
|
w.WriteHeader(502)
|
|
return
|
|
}
|
|
w.Write([]byte(`{"status":"succeeded","account_uid":"123","messages":[],"peers":[],"checkpoints":{}}`))
|
|
}))
|
|
defer server.Close()
|
|
accounts := listenerAccountsStub{gateway: hub.Gateway{Endpoint: server.URL}, environment: hub.EnvironmentContext{Env: hub.Env{Alias: "browser", Gateway: "gateway"}, RuntimeID: "runtime", RuntimeNetworkID: "network", BindingVersion: 7}}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
done := make(chan struct{})
|
|
go func() { defer close(done); RunPrivateMessageSync(ctx, accounts, store) }()
|
|
deadline := time.Now().Add(3 * time.Second)
|
|
matched := false
|
|
for time.Now().Before(deadline) {
|
|
statuses, err := store.ListPrivateSyncStatus(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(statuses) == 1 && (failure && statuses[0].Error != "" || !failure && statuses[0].SyncedAt != nil) {
|
|
matched = true
|
|
break
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
if !matched {
|
|
t.Fatal("sync result not persisted")
|
|
}
|
|
cancel()
|
|
select {
|
|
case <-done:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("worker did not stop")
|
|
}
|
|
page, err := store.ListPrivateMessages(context.Background(), state.AccountID, "456", 1, 25)
|
|
if err != nil || page.Data[0].State != "unknown" {
|
|
t.Fatalf("interrupted=%+v %v", page, err)
|
|
}
|
|
})
|
|
}
|
|
}
|