Files
EveryPublish/server/internal/api/handlers/account_platform_test.go
T

113 lines
3.3 KiB
Go

package handlers
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"sync"
"testing"
"time"
"everypublish/server/internal/models"
"everypublish/server/internal/platform"
"github.com/gin-gonic/gin"
gormsqlite "gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type loginTestAdapter struct {
mu sync.Mutex
polls int
saved string
}
func (a *loginTestAdapter) Platform() string { return "test-platform" }
func (a *loginTestAdapter) BeginLogin(context.Context) (platform.LoginSession, error) {
return platform.LoginSession{Token: "token-1", URL: "https://qr.test/1"}, nil
}
func (a *loginTestAdapter) PollLogin(context.Context, string) (platform.LoginPoll, error) {
a.mu.Lock()
defer a.mu.Unlock()
a.polls++
if a.polls == 1 {
return platform.LoginPoll{State: platform.LoginPending, Prompt: "等待扫码"}, nil
}
return platform.LoginPoll{State: platform.LoginConfirmed, Cookies: "cookie-value"}, nil
}
func (a *loginTestAdapter) SaveCredentials(_ uint64, _ uint64, cookies string) error {
a.mu.Lock()
a.saved = cookies
a.mu.Unlock()
return nil
}
func (a *loginTestAdapter) Publish(context.Context, platform.PublishInput) (platform.PublishResult, error) {
return platform.PublishResult{}, nil
}
func TestAccountBindPollsAdapterAndActivatesAccount(t *testing.T) {
db, err := gorm.Open(gormsqlite.Open(t.TempDir()+"/account.db"), &gorm.Config{})
if err != nil {
t.Fatal(err)
}
if err = db.AutoMigrate(&models.Account{}, &models.Challenge{}, &models.Notification{}, &models.AuditLog{}); err != nil {
t.Fatal(err)
}
account := models.Account{WorkspaceID: 9, Platform: "test-platform", Status: "unbound"}
if err = db.Create(&account).Error; err != nil {
t.Fatal(err)
}
adapter := &loginTestAdapter{}
registry := platform.NewRegistry()
if err = registry.Register(adapter); err != nil {
t.Fatal(err)
}
h := &AccountHandler{DB: db, Registry: registry, ExecutorMode: "web", PollInterval: 5 * time.Millisecond}
gin.SetMode(gin.TestMode)
req := httptest.NewRequest(http.MethodPost, "/accounts/1/bind", nil)
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Request = req
ctx.Params = gin.Params{{Key: "id", Value: "1"}}
ctx.Set("wsid", uint64(9))
ctx.Set("uid", uint64(3))
h.Bind(ctx)
if recorder.Code != http.StatusOK {
t.Fatalf("bind status=%d body=%s", recorder.Code, recorder.Body.String())
}
var envelope struct {
Data struct {
ChallengeID uint64 `json:"challengeId"`
QRURL string `json:"qrUrl"`
} `json:"data"`
}
if err = json.Unmarshal(recorder.Body.Bytes(), &envelope); err != nil {
t.Fatal(err)
}
if envelope.Data.ChallengeID == 0 || envelope.Data.QRURL != "https://qr.test/1" {
t.Fatalf("unexpected bind response: %s", recorder.Body.String())
}
deadline := time.Now().Add(time.Second)
for time.Now().Before(deadline) {
var got models.Account
_ = db.First(&got, account.ID).Error
if got.Status == "active" {
adapter.mu.Lock()
saved := adapter.saved
adapter.mu.Unlock()
if saved != "cookie-value" {
t.Fatalf("credentials not saved: %q", saved)
}
var challenge models.Challenge
_ = db.First(&challenge, envelope.Data.ChallengeID).Error
if challenge.Status != "solved" {
t.Fatalf("challenge status=%s", challenge.Status)
}
return
}
time.Sleep(10 * time.Millisecond)
}
t.Fatal("adapter login worker did not activate account")
}