Files
EveryPublish/server/internal/ws/dispatcher_platform_test.go
T

132 lines
4.2 KiB
Go

package ws
import (
"context"
"path/filepath"
"sync"
"testing"
"time"
"everypublish/server/internal/config"
"everypublish/server/internal/models"
"everypublish/server/internal/platform"
"everypublish/server/internal/task"
gormsqlite "gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type testPlatformAdapter struct {
mu sync.Mutex
called int
block bool
canceled chan struct{}
}
func (a *testPlatformAdapter) Platform() string { return "test-platform" }
func (a *testPlatformAdapter) BeginLogin(context.Context) (platform.LoginSession, error) {
return platform.LoginSession{}, nil
}
func (a *testPlatformAdapter) PollLogin(context.Context, string) (platform.LoginPoll, error) {
return platform.LoginPoll{}, nil
}
func (a *testPlatformAdapter) SaveCredentials(uint64, uint64, string) error { return nil }
func (a *testPlatformAdapter) Publish(ctx context.Context, in platform.PublishInput) (platform.PublishResult, error) {
a.mu.Lock()
a.called++
a.mu.Unlock()
if a.block {
select {
case <-ctx.Done():
close(a.canceled)
return platform.PublishResult{}, ctx.Err()
case <-time.After(2 * time.Second):
}
}
return platform.PublishResult{URL: "https://example.test/task/" + in.Title, Receipt: "receipt"}, nil
}
func platformTestDB(t *testing.T) *gorm.DB {
t.Helper()
db, err := gorm.Open(gormsqlite.Open(filepath.Join(t.TempDir(), "dispatcher.db")), &gorm.Config{})
if err != nil {
t.Fatal(err)
}
if err = db.AutoMigrate(&models.Task{}, &models.TaskEvent{}, &models.Account{}, &models.Material{}, &models.TransferToken{}, &models.Notification{}); err != nil {
t.Fatal(err)
}
return db
}
func TestDispatcherUsesRegisteredPlatformAndPublishesPerAccount(t *testing.T) {
db := platformTestDB(t)
account := models.Account{WorkspaceID: 1, Platform: "test-platform", Status: "active"}
material := models.Material{WorkspaceID: 1, Name: "video.mp4", Status: "ready", StorageKey: "1/video.mp4"}
if err := db.Create(&account).Error; err != nil {
t.Fatal(err)
}
if err := db.Create(&material).Error; err != nil {
t.Fatal(err)
}
row := models.Task{WorkspaceID: 1, Title: "platform", Status: task.Queued, AccountIDs: "[1]", MaterialIDs: "[1]"}
if err := db.Create(&row).Error; err != nil {
t.Fatal(err)
}
adapter := &testPlatformAdapter{}
registry := platform.NewRegistry()
if err := registry.Register(adapter); err != nil {
t.Fatal(err)
}
d := NewDispatcher(db, NewHub(), &config.Config{ExecutorMode: "web", BaseURL: "http://127.0.0.1:8090"})
d.Registry = registry
if !d.TryDispatch(row.ID) {
t.Fatal("registered adapter should accept queued task")
}
deadline := time.Now().Add(2 * time.Second)
for time.Now().Before(deadline) {
var got models.Task
if err := db.First(&got, row.ID).Error; err != nil {
t.Fatal(err)
}
if got.Status == task.Success {
if got.PublishedURLs != `["https://example.test/task/platform"]` || got.Receipts != `["receipt"]` {
t.Fatalf("unexpected platform result: urls=%s receipts=%s", got.PublishedURLs, got.Receipts)
}
adapter.mu.Lock()
called := adapter.called
adapter.mu.Unlock()
if called != 1 {
t.Fatalf("adapter called %d times", called)
}
return
}
time.Sleep(20 * time.Millisecond)
}
t.Fatal("platform task did not reach success")
}
func TestDispatcherCancelStopsRegisteredPlatform(t *testing.T) {
db := platformTestDB(t)
account := models.Account{WorkspaceID: 1, Platform: "test-platform", Status: "active"}
material := models.Material{WorkspaceID: 1, Name: "video.mp4", Status: "ready", StorageKey: "1/video.mp4"}
_ = db.Create(&account).Error
_ = db.Create(&material).Error
row := models.Task{WorkspaceID: 1, Title: "cancel", Status: task.Queued, AccountIDs: "[1]", MaterialIDs: "[1]"}
_ = db.Create(&row).Error
adapter := &testPlatformAdapter{block: true, canceled: make(chan struct{})}
registry := platform.NewRegistry()
_ = registry.Register(adapter)
d := NewDispatcher(db, NewHub(), &config.Config{ExecutorMode: "web", BaseURL: "http://127.0.0.1:8090"})
d.Registry = registry
if !d.TryDispatch(row.ID) {
t.Fatal("registered adapter should accept queued task")
}
time.Sleep(60 * time.Millisecond)
d.Cancel(row.ID)
select {
case <-adapter.canceled:
case <-time.After(time.Second):
t.Fatal("adapter context was not cancelled")
}
}