fetcher_test.go

  1package fetcher
  2
  3import (
  4	"bytes"
  5	"strings"
  6	"testing"
  7
  8	"github.com/floatpane/matcha/config"
  9)
 10
 11type testPartHeader map[string]string
 12
 13func (h testPartHeader) Add(key, value string) {
 14	h[key] = value
 15}
 16
 17func (h testPartHeader) Del(key string) {
 18	delete(h, key)
 19}
 20
 21func (h testPartHeader) Get(key string) string {
 22	return h[key]
 23}
 24
 25func (h testPartHeader) Set(key, value string) {
 26	h[key] = value
 27}
 28
 29func TestDecodePartUsesCharsetWhenContentTypeIsMalformed(t *testing.T) {
 30	header := testPartHeader{}
 31	header.Set("Content-Type", "text/plain; charset=iso-8859-1; broken")
 32
 33	decoded, err := decodePart(bytes.NewReader([]byte{0x63, 0x61, 0x66, 0xe9}), header)
 34	if err != nil {
 35		t.Fatalf("decodePart() returned error: %v", err)
 36	}
 37
 38	if decoded != "café" {
 39		t.Fatalf("decodePart() = %q, want %q", decoded, "café")
 40	}
 41}
 42
 43func TestDecodePartFallsBackToUTF8WhenMalformedContentTypeHasNoCharset(t *testing.T) {
 44	header := testPartHeader{}
 45	header.Set("Content-Type", "text/plain; broken")
 46
 47	decoded, err := decodePart(strings.NewReader("hello"), header)
 48	if err != nil {
 49		t.Fatalf("decodePart() returned error: %v", err)
 50	}
 51
 52	if decoded != "hello" {
 53		t.Fatalf("decodePart() = %q, want %q", decoded, "hello")
 54	}
 55}
 56
 57func TestDecodeReaderWithCharsetSurvivesUnknownCharset(t *testing.T) {
 58	decoded, err := decodeReaderWithCharset(strings.NewReader("hello"), "bogus-charset-name")
 59	if err != nil {
 60		t.Fatalf("decodeReaderWithCharset() returned error: %v", err)
 61	}
 62	if string(decoded) != "hello" {
 63		t.Fatalf("decodeReaderWithCharset() = %q, want %q", string(decoded), "hello")
 64	}
 65}
 66
 67func TestLookupCharsetEncodingAlwaysReturnsNonNil(t *testing.T) {
 68	cases := []string{"", "utf-8", "iso-8859-1", "bogus-charset-name", "this/is/not/real"}
 69	for _, name := range cases {
 70		t.Run(name, func(t *testing.T) {
 71			if enc := lookupCharsetEncoding(name); enc == nil {
 72				t.Fatalf("lookupCharsetEncoding(%q) returned nil", name)
 73			}
 74		})
 75	}
 76}
 77
 78func TestFormatPartPathEmptyPath(t *testing.T) {
 79	cases := map[string][]int{
 80		"nil":   nil,
 81		"empty": {},
 82	}
 83	for name, path := range cases {
 84		t.Run(name, func(t *testing.T) {
 85			if got := formatPartPath(path); got != "" {
 86				t.Fatalf("formatPartPath(%v) = %q, want empty string", path, got)
 87			}
 88		})
 89	}
 90}
 91
 92// TestFetchEmails is an integration test that requires a live IMAP server and valid credentials.
 93// NOTE: This test will be skipped if it cannot load a configuration file,
 94// making it safe to run in a CI environment without credentials.
 95// To run this test locally, ensure you have a valid `config.json` file.
 96func TestFetchEmails(t *testing.T) {
 97	// Attempt to load the configuration.
 98	cfg, err := config.LoadConfig()
 99	if err != nil {
100		// If config doesn't exist, skip the test. This is useful for CI environments.
101		t.Skipf("Skipping TestFetchEmails: could not load config: %v", err)
102	}
103
104	// Check if there are any accounts configured
105	if !cfg.HasAccounts() {
106		t.Skip("Skipping TestFetchEmails: no accounts configured.")
107	}
108
109	// Get the first account
110	account := cfg.GetFirstAccount()
111	if account == nil {
112		t.Skip("Skipping TestFetchEmails: no accounts available.")
113	}
114
115	// If the password is a placeholder, skip the test to avoid failed auth attempts.
116	if account.Password == "" || account.Password == "supersecret" {
117		t.Skip("Skipping TestFetchEmails: placeholder or empty password found in config.")
118	}
119
120	emails, err := FetchEmails(account, 10, 10)
121	if err != nil {
122		t.Fatalf("FetchEmails() failed with error: %v", err)
123	}
124
125	if len(emails) == 0 {
126		// This is not necessarily a failure, but we can log it.
127		t.Log("FetchEmails() returned 0 emails. This might be expected.")
128	}
129
130	// Check that the emails are sorted from newest to oldest.
131	// Skip emails with zero/invalid dates when checking sort order.
132	if len(emails) > 1 {
133		var validEmails []Email
134		for _, e := range emails {
135			if !e.Date.IsZero() {
136				validEmails = append(validEmails, e)
137			}
138		}
139		if len(validEmails) > 1 {
140			if validEmails[0].Date.Before(validEmails[len(validEmails)-1].Date) {
141				t.Error("Emails do not appear to be sorted from newest to oldest.")
142			}
143		}
144	}
145
146	// Check a sample email for expected content.
147	for _, email := range emails {
148		if email.Subject == "" && email.From == "" {
149			t.Errorf("Fetched email has empty subject and from fields: %+v", email)
150		}
151	}
152
153	// Verify that AccountID is set on fetched emails
154	for _, email := range emails {
155		if email.AccountID != account.ID {
156			t.Errorf("Expected AccountID %s, got %s", account.ID, email.AccountID)
157		}
158	}
159}
160
161// TestFetchEmailsWithCustomServer tests fetching with a custom server configuration.
162// This test is skipped unless a custom account is configured.
163func TestFetchEmailsWithCustomServer(t *testing.T) {
164	// Attempt to load the configuration.
165	cfg, err := config.LoadConfig()
166	if err != nil {
167		t.Skipf("Skipping TestFetchEmailsWithCustomServer: could not load config: %v", err)
168	}
169
170	// Look for a custom account
171	var customAccount *config.Account
172	for i := range cfg.Accounts {
173		if cfg.Accounts[i].ServiceProvider == "custom" {
174			customAccount = &cfg.Accounts[i]
175			break
176		}
177	}
178
179	if customAccount == nil {
180		t.Skip("Skipping TestFetchEmailsWithCustomServer: no custom account configured.")
181	}
182
183	if customAccount.Password == "" || customAccount.Password == "supersecret" {
184		t.Skip("Skipping TestFetchEmailsWithCustomServer: placeholder or empty password found.")
185	}
186
187	if customAccount.IMAPServer == "" {
188		t.Skip("Skipping TestFetchEmailsWithCustomServer: no IMAP server configured.")
189	}
190
191	emails, err := FetchEmails(customAccount, 5, 0)
192	if err != nil {
193		t.Fatalf("FetchEmails() with custom server failed: %v", err)
194	}
195
196	t.Logf("Fetched %d emails from custom server %s", len(emails), customAccount.IMAPServer)
197}