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.
211 lines
3.5 KiB
Go
211 lines
3.5 KiB
Go
package intake
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"raven/internal/filter"
|
|
"raven/internal/imap"
|
|
"raven/internal/tracker"
|
|
|
|
goimap "github.com/emersion/go-imap/v2"
|
|
)
|
|
|
|
func TestConfigValidate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cfg Config
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "valid idle mode",
|
|
cfg: Config{
|
|
Mode: ModeIdle,
|
|
PollInterval: "30s",
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid poll mode",
|
|
cfg: Config{
|
|
Mode: ModePoll,
|
|
PollInterval: "5m",
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid poll mode with long interval",
|
|
cfg: Config{
|
|
Mode: ModePoll,
|
|
PollInterval: "24h",
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "invalid mode",
|
|
cfg: Config{
|
|
Mode: "invalid",
|
|
PollInterval: "30s",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "empty mode",
|
|
cfg: Config{
|
|
Mode: "",
|
|
PollInterval: "30s",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "missing poll interval",
|
|
cfg: Config{
|
|
Mode: ModeIdle,
|
|
PollInterval: "",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid poll interval",
|
|
cfg: Config{
|
|
Mode: ModePoll,
|
|
PollInterval: "invalid",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "zero poll interval",
|
|
cfg: Config{
|
|
Mode: ModePoll,
|
|
PollInterval: "0s",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := tt.cfg.Validate()
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf(
|
|
"Validate() error = %v, wantErr %v",
|
|
err, tt.wantErr,
|
|
)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestModeValid(t *testing.T) {
|
|
tests := []struct {
|
|
mode Mode
|
|
want bool
|
|
}{
|
|
{ModeIdle, true},
|
|
{ModePoll, true},
|
|
{"idle", true},
|
|
{"poll", true},
|
|
{"", false},
|
|
{"invalid", false},
|
|
{"IDLE", false},
|
|
{"POLL", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(string(tt.mode), func(t *testing.T) {
|
|
if got := tt.mode.Valid(); got != tt.want {
|
|
t.Errorf(
|
|
"Mode(%q).Valid() = %v, want %v",
|
|
tt.mode, got, tt.want,
|
|
)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestModeString(t *testing.T) {
|
|
tests := []struct {
|
|
mode Mode
|
|
want string
|
|
}{
|
|
{ModeIdle, "idle"},
|
|
{ModePoll, "poll"},
|
|
{"custom", "custom"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.want, func(t *testing.T) {
|
|
if got := tt.mode.String(); got != tt.want {
|
|
t.Errorf(
|
|
"Mode.String() = %v, want %v",
|
|
got, tt.want,
|
|
)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNewWorkerValidation(t *testing.T) {
|
|
validConfig := Config{
|
|
Mode: ModeIdle,
|
|
PollInterval: "30s",
|
|
}
|
|
validTracker := tracker.New()
|
|
validWork := make(chan goimap.UID, 1)
|
|
|
|
tests := []struct {
|
|
name string
|
|
cfg Config
|
|
tracker *tracker.Tracker
|
|
work chan goimap.UID
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "nil tracker",
|
|
cfg: validConfig,
|
|
tracker: nil,
|
|
work: validWork,
|
|
wantErr: "missing tracker",
|
|
},
|
|
{
|
|
name: "nil work channel",
|
|
cfg: validConfig,
|
|
tracker: validTracker,
|
|
work: nil,
|
|
wantErr: "missing work channel",
|
|
},
|
|
{
|
|
name: "invalid config",
|
|
cfg: Config{
|
|
Mode: "invalid",
|
|
PollInterval: "30s",
|
|
},
|
|
tracker: validTracker,
|
|
work: validWork,
|
|
wantErr: "invalid config",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
_, err := NewWorker(
|
|
tt.cfg,
|
|
filter.Filters{},
|
|
imap.Config{},
|
|
nil, // log
|
|
tt.tracker,
|
|
tt.work,
|
|
)
|
|
if err == nil {
|
|
t.Fatal("NewWorker() expected error, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), tt.wantErr) {
|
|
t.Errorf(
|
|
"NewWorker() error = %q, want containing %q",
|
|
err.Error(), tt.wantErr,
|
|
)
|
|
}
|
|
})
|
|
}
|
|
}
|