146 lines
3.1 KiB
Go
146 lines
3.1 KiB
Go
package poster
|
|
|
|
import (
|
|
"context"
|
|
"path/filepath"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"pete/internal/config"
|
|
"pete/internal/storage"
|
|
)
|
|
|
|
// mockMatrix implements the subset of matrix.Client that Queue needs.
|
|
// We test via the public Enqueue/Start interface and check storage state.
|
|
|
|
func setupTestEnv(t *testing.T) {
|
|
t.Helper()
|
|
dbPath := filepath.Join(t.TempDir(), "test.db")
|
|
if err := storage.Init(dbPath); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { storage.Close() })
|
|
}
|
|
|
|
func TestQueue_EnqueueAndDrain(t *testing.T) {
|
|
setupTestEnv(t)
|
|
|
|
q := &Queue{
|
|
queues: make(map[string][]QueueItem),
|
|
config: config.PostingConfig{
|
|
MinIntervalSeconds: 0,
|
|
BurstCapCount: 10,
|
|
BurstCapWindowSeconds: 1800,
|
|
},
|
|
done: make(chan struct{}),
|
|
}
|
|
|
|
// Enqueue directly (bypass ChannelRoomID check)
|
|
q.mu.Lock()
|
|
q.queues["tech"] = append(q.queues["tech"], QueueItem{
|
|
GUID: "g1",
|
|
Channel: "tech",
|
|
})
|
|
q.mu.Unlock()
|
|
|
|
// Verify item is in queue
|
|
q.mu.Lock()
|
|
if len(q.queues["tech"]) != 1 {
|
|
t.Fatalf("expected 1 item in queue, got %d", len(q.queues["tech"]))
|
|
}
|
|
q.mu.Unlock()
|
|
}
|
|
|
|
func TestQueueItem_MaxRetries(t *testing.T) {
|
|
// Verify the retry counter works
|
|
item := QueueItem{GUID: "g1", Channel: "tech", retries: 0}
|
|
item.retries++
|
|
if item.retries != 1 {
|
|
t.Errorf("expected retries=1, got %d", item.retries)
|
|
}
|
|
item.retries++
|
|
item.retries++
|
|
if item.retries < maxRetries {
|
|
t.Errorf("expected retries >= maxRetries after 3 increments, got %d", item.retries)
|
|
}
|
|
}
|
|
|
|
func TestQueue_StartAndWait(t *testing.T) {
|
|
setupTestEnv(t)
|
|
|
|
q := &Queue{
|
|
queues: make(map[string][]QueueItem),
|
|
config: config.PostingConfig{
|
|
MinIntervalSeconds: 0,
|
|
BurstCapCount: 10,
|
|
BurstCapWindowSeconds: 1800,
|
|
},
|
|
done: make(chan struct{}),
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
q.Start(ctx)
|
|
}()
|
|
|
|
// Cancel immediately — Start should return promptly
|
|
cancel()
|
|
|
|
done := make(chan struct{})
|
|
go func() {
|
|
q.Wait()
|
|
close(done)
|
|
}()
|
|
|
|
select {
|
|
case <-done:
|
|
// good
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("queue.Wait() did not return after cancel")
|
|
}
|
|
}
|
|
|
|
func TestQueue_BurstCap(t *testing.T) {
|
|
setupTestEnv(t)
|
|
|
|
cfg := config.PostingConfig{
|
|
MinIntervalSeconds: 0,
|
|
BurstCapCount: 2,
|
|
BurstCapWindowSeconds: 3600,
|
|
}
|
|
|
|
q := &Queue{
|
|
queues: make(map[string][]QueueItem),
|
|
config: cfg,
|
|
done: make(chan struct{}),
|
|
}
|
|
|
|
// Simulate 2 posts already in window
|
|
now := time.Now().Unix()
|
|
storage.Get().Exec(`INSERT INTO post_log (guid, channel, event_id, posted_at) VALUES (?, ?, ?, ?)`,
|
|
"prev1", "tech", "$e1", now-100)
|
|
storage.Get().Exec(`INSERT INTO post_log (guid, channel, event_id, posted_at) VALUES (?, ?, ?, ?)`,
|
|
"prev2", "tech", "$e2", now-50)
|
|
|
|
// Queue a new item
|
|
q.mu.Lock()
|
|
q.queues["tech"] = []QueueItem{{GUID: "g3", Channel: "tech"}}
|
|
q.mu.Unlock()
|
|
|
|
// drainChannel should not post (burst cap reached)
|
|
q.drainChannel("tech")
|
|
|
|
q.mu.Lock()
|
|
remaining := len(q.queues["tech"])
|
|
q.mu.Unlock()
|
|
|
|
if remaining != 1 {
|
|
t.Errorf("expected item to remain in queue (burst cap), got %d remaining", remaining)
|
|
}
|
|
}
|