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
57// TestFetchEmails is an integration test that requires a live IMAP server and valid credentials.
58// NOTE: This test will be skipped if it cannot load a configuration file,
59// making it safe to run in a CI environment without credentials.
60// To run this test locally, ensure you have a valid `config.json` file.
61func TestFetchEmails(t *testing.T) {
62 // Attempt to load the configuration.
63 cfg, err := config.LoadConfig()
64 if err != nil {
65 // If config doesn't exist, skip the test. This is useful for CI environments.
66 t.Skipf("Skipping TestFetchEmails: could not load config: %v", err)
67 }
68
69 // Check if there are any accounts configured
70 if !cfg.HasAccounts() {
71 t.Skip("Skipping TestFetchEmails: no accounts configured.")
72 }
73
74 // Get the first account
75 account := cfg.GetFirstAccount()
76 if account == nil {
77 t.Skip("Skipping TestFetchEmails: no accounts available.")
78 }
79
80 // If the password is a placeholder, skip the test to avoid failed auth attempts.
81 if account.Password == "" || account.Password == "supersecret" {
82 t.Skip("Skipping TestFetchEmails: placeholder or empty password found in config.")
83 }
84
85 emails, err := FetchEmails(account, 10, 10)
86 if err != nil {
87 t.Fatalf("FetchEmails() failed with error: %v", err)
88 }
89
90 if len(emails) == 0 {
91 // This is not necessarily a failure, but we can log it.
92 t.Log("FetchEmails() returned 0 emails. This might be expected.")
93 }
94
95 // Check that the emails are sorted from newest to oldest.
96 // Skip emails with zero/invalid dates when checking sort order.
97 if len(emails) > 1 {
98 var validEmails []Email
99 for _, e := range emails {
100 if !e.Date.IsZero() {
101 validEmails = append(validEmails, e)
102 }
103 }
104 if len(validEmails) > 1 {
105 if validEmails[0].Date.Before(validEmails[len(validEmails)-1].Date) {
106 t.Error("Emails do not appear to be sorted from newest to oldest.")
107 }
108 }
109 }
110
111 // Check a sample email for expected content.
112 for _, email := range emails {
113 if email.Subject == "" && email.From == "" {
114 t.Errorf("Fetched email has empty subject and from fields: %+v", email)
115 }
116 }
117
118 // Verify that AccountID is set on fetched emails
119 for _, email := range emails {
120 if email.AccountID != account.ID {
121 t.Errorf("Expected AccountID %s, got %s", account.ID, email.AccountID)
122 }
123 }
124}
125
126// TestFetchEmailsWithCustomServer tests fetching with a custom server configuration.
127// This test is skipped unless a custom account is configured.
128func TestFetchEmailsWithCustomServer(t *testing.T) {
129 // Attempt to load the configuration.
130 cfg, err := config.LoadConfig()
131 if err != nil {
132 t.Skipf("Skipping TestFetchEmailsWithCustomServer: could not load config: %v", err)
133 }
134
135 // Look for a custom account
136 var customAccount *config.Account
137 for i := range cfg.Accounts {
138 if cfg.Accounts[i].ServiceProvider == "custom" {
139 customAccount = &cfg.Accounts[i]
140 break
141 }
142 }
143
144 if customAccount == nil {
145 t.Skip("Skipping TestFetchEmailsWithCustomServer: no custom account configured.")
146 }
147
148 if customAccount.Password == "" || customAccount.Password == "supersecret" {
149 t.Skip("Skipping TestFetchEmailsWithCustomServer: placeholder or empty password found.")
150 }
151
152 if customAccount.IMAPServer == "" {
153 t.Skip("Skipping TestFetchEmailsWithCustomServer: no IMAP server configured.")
154 }
155
156 emails, err := FetchEmails(customAccount, 5, 0)
157 if err != nil {
158 t.Fatalf("FetchEmails() with custom server failed: %v", err)
159 }
160
161 t.Logf("Fetched %d emails from custom server %s", len(emails), customAccount.IMAPServer)
162}