Subsystem to monitor IMAP mailbox for new messages. Introduces: - intake: worker that uses IDLE or polling to detect new emails. - imap: client wrapper for connection management and IMAP commands. - filter: logic for IMAP search and sender allow-list. - tracker: concurrency control to prevent processing the same UID twice. - backoff: for handling connection retries with jitter.
95 lines
1.6 KiB
Go
95 lines
1.6 KiB
Go
package tracker
|
|
|
|
import (
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/emersion/go-imap/v2"
|
|
)
|
|
|
|
func TestNew(t *testing.T) {
|
|
tr := New()
|
|
if tr == nil {
|
|
t.Fatal("New() returned nil")
|
|
}
|
|
if tr.ids == nil {
|
|
t.Fatal("New() initialized with nil map")
|
|
}
|
|
}
|
|
|
|
func TestLifecycle(t *testing.T) {
|
|
tr := New()
|
|
uid := imap.UID(123)
|
|
|
|
// 1. First acquire should succeed.
|
|
if !tr.TryAcquire(uid) {
|
|
t.Fatalf(
|
|
"expected TryAcquire(%d) to return true, got false",
|
|
uid,
|
|
)
|
|
}
|
|
|
|
// 2. Second acquire on same UID should fail.
|
|
if tr.TryAcquire(uid) {
|
|
t.Fatalf(
|
|
"expected TryAcquire(%d) to return false when already held, got true",
|
|
uid,
|
|
)
|
|
}
|
|
|
|
// 3. Different UID should succeed.
|
|
otherUID := imap.UID(456)
|
|
if !tr.TryAcquire(otherUID) {
|
|
t.Fatalf(
|
|
"expected TryAcquire(%d) to return true, got false",
|
|
otherUID,
|
|
)
|
|
}
|
|
|
|
// 4. Release first UID.
|
|
tr.Release(uid)
|
|
|
|
// 5. First UID should be acquirable again.
|
|
if !tr.TryAcquire(uid) {
|
|
t.Fatalf(
|
|
"expected TryAcquire(%d) to return true after Release, got false",
|
|
uid,
|
|
)
|
|
}
|
|
}
|
|
|
|
func TestConcurrency(t *testing.T) {
|
|
tr := New()
|
|
uid := imap.UID(1)
|
|
routines := 16
|
|
var wg sync.WaitGroup
|
|
|
|
// Counter for successful acquisitions.
|
|
successCount := 0
|
|
var mu sync.Mutex
|
|
|
|
for range routines {
|
|
wg.Go(func() {
|
|
if tr.TryAcquire(uid) {
|
|
mu.Lock()
|
|
successCount++
|
|
mu.Unlock()
|
|
}
|
|
})
|
|
}
|
|
wg.Wait()
|
|
|
|
if successCount != 1 {
|
|
t.Errorf(
|
|
"expected exactly 1 successful acquisition for concurrent access to same UID, got %d",
|
|
successCount,
|
|
)
|
|
}
|
|
|
|
// Verify we can release safely after concurrent hammer.
|
|
tr.Release(uid)
|
|
if !tr.TryAcquire(uid) {
|
|
t.Error("failed to acquire UID after concurrent test release")
|
|
}
|
|
}
|