package channel import ( "context" "sync/atomic" "testing" "time" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/internal/worker" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" ) type workerDispatchListener struct { name string count atomic.Int32 } func (l *workerDispatchListener) Name() string { return l.name } func (l *workerDispatchListener) OnEvent(ctx context.Context, event *ChannelEvent) error { l.count.Add(1) return nil } func newChannelWorkerDB(t *testing.T) *gorm.DB { t.Helper() db, err := gorm.Open(sqlite.Open("file:channel-dispatcher-worker?mode=memory&cache=shared"), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)}) if err != nil { t.Fatalf("open sqlite: %v", err) } sqlDB, err := db.DB() if err != nil { t.Fatalf("sqlite db handle: %v", err) } sqlDB.SetMaxOpenConns(1) if err := db.AutoMigrate(&model.BackgroundJob{}); err != nil { t.Fatalf("migrate background jobs: %v", err) } t.Cleanup(func() { db.Exec("DELETE FROM background_jobs") sqlDB.Close() }) return db } func TestDispatcherDispatchAsyncEnqueuesDurableJob(t *testing.T) { db := newChannelWorkerDB(t) wp := worker.NewWorkerPoolWithOptions(db, worker.WithNow(func() time.Time { return time.Date(2026, 6, 5, 11, 0, 0, 0, time.UTC) })) dispatcher := NewDispatcher(wp) listener := &workerDispatchListener{name: "capture"} dispatcher.Register(listener) event := NewChannelEvent(EventConversationCreated, ChannelWebWidget, 1, 2) if err := dispatcher.DispatchAsync(context.Background(), event); err != nil { t.Fatalf("dispatch async: %v", err) } if listener.count.Load() != 0 { t.Fatalf("listener ran synchronously before worker processed job") } var count int64 if err := db.Model(&model.BackgroundJob{}).Where("job_type = ? AND status = ?", TaskTypeEventDispatch, model.BackgroundJobStatusQueued).Count(&count).Error; err != nil { t.Fatalf("count jobs: %v", err) } if count != 1 { t.Fatalf("expected one queued dispatch job, got %d", count) } processed, err := wp.ProcessOne(context.Background()) if err != nil || !processed { t.Fatalf("process dispatch job: processed=%v err=%v", processed, err) } if listener.count.Load() != 1 { t.Fatalf("listener was not called by durable dispatch job") } }