package worker_test import ( "fmt" "testing" "time" "inboxer/src/internal/db" "inboxer/src/internal/imap" "inboxer/src/internal/worker" "inboxer/src/pkg/config" ) // mockClassifier implements worker.AIClassifier for testing type mockClassifier struct { folder string confidence int err error } func (m *mockClassifier) Classify(email imap.EmailSummary) (string, int, error) { return m.folder, m.confidence, m.err } // mockDB wraps the real database for testing (uses in-memory SQLite) type mockDB struct { *db.Database } func newMockDB(t *testing.T) *db.Database { t.Helper() // We need a different approach since NewDatabase needs a file path // Use temporary file database, err := db.NewDatabase(t.TempDir()+"/test.db", "test-secret-key-32-bytes-long!!") if err != nil { t.Fatalf("failed to create test database: %v", err) } return database } func TestNewWorker(t *testing.T) { cfg := &config.Config{ Worker: config.WorkerConfig{ SteadyStateBatchSize: 10, SteadyStateIntervalMinutes: 5, CatchUpBatchSize: 50, CatchUpCooldownSeconds: 5, }, Folders: config.FolderConfig{ Important: "Important", Ecommerce: "eCommerce", Other: "Other", Spam: "Spam", }, } classifier := worker.NewPlaceholderClassifier("Other") w := worker.NewWorker(nil, cfg, classifier) if w == nil { t.Fatal("NewWorker returned nil") } } func TestNewPlaceholderClassifier(t *testing.T) { c := worker.NewPlaceholderClassifier("Other") if c == nil { t.Fatal("NewPlaceholderClassifier returned nil") } folder, confidence, err := c.Classify(imap.EmailSummary{ UID: 1, Subject: "Test", From: "test@example.com", }) if err != nil { t.Fatalf("unexpected error: %v", err) } if folder != "Other" { t.Errorf("expected folder 'Other', got %q", folder) } if confidence != 0 { t.Errorf("expected confidence 0, got %d", confidence) } } func TestPlaceholderClassifierFields(t *testing.T) { c := worker.NewPlaceholderClassifier("Work") if c.DefaultFolder != "Work" { t.Errorf("expected DefaultFolder 'Work', got %q", c.DefaultFolder) } folder, _, _ := c.Classify(imap.EmailSummary{UID: 42}) if folder != "Work" { t.Errorf("expected folder 'Work', got %q", folder) } } func TestPlaceholderClassifierSubjectFrom(t *testing.T) { c := worker.NewPlaceholderClassifier("Spam") tests := []struct { name string email imap.EmailSummary }{ {"normal email", imap.EmailSummary{UID: 1, Subject: "Hello", From: "friend@example.com"}}, {"empty subject", imap.EmailSummary{UID: 2, Subject: "", From: "noreply@example.com"}}, {"empty from", imap.EmailSummary{UID: 3, Subject: "Test", From: ""}}, {"all empty", imap.EmailSummary{UID: 4}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { folder, _, err := c.Classify(tt.email) if err != nil { t.Fatalf("unexpected error: %v", err) } if folder != "Spam" { t.Errorf("expected 'Spam', got %q", folder) } }) } } func TestStartStopWorker(t *testing.T) { database := newMockDB(t) cfg := &config.Config{ Worker: config.WorkerConfig{ SteadyStateBatchSize: 10, SteadyStateIntervalMinutes: 5, CatchUpBatchSize: 50, CatchUpCooldownSeconds: 1, }, Folders: config.FolderConfig{ Important: "Important", Ecommerce: "eCommerce", Other: "Other", Spam: "Spam", }, } classifier := worker.NewPlaceholderClassifier("Other") w := worker.NewWorker(database, cfg, classifier) // Start and immediately stop to test lifecycle w.Start() time.Sleep(100 * time.Millisecond) // Let it process w.Stop() // No assertion errors = pass } func TestProcessNowHandler(t *testing.T) { // Test that calling Start/Stop multiple times is safe database := newMockDB(t) cfg := &config.Config{ Worker: config.WorkerConfig{ SteadyStateBatchSize: 10, SteadyStateIntervalMinutes: 5, CatchUpBatchSize: 50, CatchUpCooldownSeconds: 1, }, Folders: config.FolderConfig{ Important: "Important", Ecommerce: "eCommerce", Other: "Other", Spam: "Spam", }, } classifier := worker.NewPlaceholderClassifier("Other") w := worker.NewWorker(database, cfg, classifier) // Start and stop multiple times (should be safe) w.Start() w.Start() // Second start should be safe time.Sleep(50 * time.Millisecond) w.Stop() w.Stop() // Second stop should be safe // Start one final time w.Start() time.Sleep(50 * time.Millisecond) w.Stop() } func TestMockClassifier(t *testing.T) { // Test with error errorClassifier := &mockClassifier{ folder: "", confidence: 0, err: fmt.Errorf("classification failed"), } _, _, err := errorClassifier.Classify(imap.EmailSummary{UID: 1}) if err == nil { t.Fatal("expected error from mock classifier") } // Test with specific folder successClassifier := &mockClassifier{ folder: "Important", confidence: 95, err: nil, } folder, confidence, err := successClassifier.Classify(imap.EmailSummary{UID: 2}) if err != nil { t.Fatalf("unexpected error: %v", err) } if folder != "Important" { t.Errorf("expected 'Important', got %q", folder) } if confidence != 95 { t.Errorf("expected 95, got %d", confidence) } }