Add intake worker
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.
This commit is contained in:
210
internal/intake/intake_test.go
Normal file
210
internal/intake/intake_test.go
Normal file
@@ -0,0 +1,210 @@
|
||||
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,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user