Line data Source code
1 : #include "test_helpers.h"
2 : #include "mail_rules.h"
3 : #include <fcntl.h>
4 : #include <stdio.h>
5 : #include <stdlib.h>
6 : #include <string.h>
7 : #include <unistd.h>
8 :
9 : /* ── Helpers ─────────────────────────────────────────────────────── */
10 :
11 17 : static MailRules *make_rules(void) {
12 17 : return calloc(1, sizeof(MailRules));
13 : }
14 :
15 17 : static MailRule *add_rule(MailRules *r, const char *name) {
16 17 : if (r->count >= r->cap) {
17 16 : int nc = r->cap ? r->cap * 2 : 4;
18 16 : MailRule *tmp = realloc(r->rules, (size_t)nc * sizeof(MailRule));
19 16 : if (!tmp) return NULL;
20 16 : r->rules = tmp;
21 16 : r->cap = nc;
22 : }
23 17 : MailRule *rule = &r->rules[r->count++];
24 17 : memset(rule, 0, sizeof(*rule));
25 17 : rule->name = strdup(name);
26 17 : return rule;
27 : }
28 :
29 : /* ── Tests ───────────────────────────────────────────────────────── */
30 :
31 1 : static void test_glob_match_basic(void) {
32 1 : MailRules *r = make_rules();
33 1 : MailRule *rule = add_rule(r, "GitHub");
34 1 : rule->if_from = strdup("*@github.com");
35 1 : rule->then_add_label[rule->then_add_count++] = strdup("GitHub");
36 :
37 1 : char **add = NULL, **rm = NULL;
38 1 : int ac = 0, rc2 = 0;
39 1 : int fired = mail_rules_apply(r,
40 : "noreply@github.com", "PR review", NULL, "INBOX",
41 : NULL, (time_t)0, &add, &ac, &rm, &rc2);
42 1 : ASSERT(fired == 1, "rule should fire for *@github.com");
43 1 : ASSERT(ac == 1, "should add 1 label");
44 1 : ASSERT(strcmp(add[0], "GitHub") == 0, "added label should be GitHub");
45 1 : ASSERT(rc2 == 0, "should remove 0 labels");
46 :
47 2 : for (int i = 0; i < ac; i++) free(add[i]);
48 1 : free(add);
49 1 : mail_rules_free(r);
50 : }
51 :
52 1 : static void test_glob_no_match(void) {
53 1 : MailRules *r = make_rules();
54 1 : MailRule *rule = add_rule(r, "GitHub");
55 1 : rule->if_from = strdup("*@github.com");
56 1 : rule->then_add_label[rule->then_add_count++] = strdup("GitHub");
57 :
58 1 : char **add = NULL, **rm = NULL;
59 1 : int ac = 0, rc2 = 0;
60 1 : int fired = mail_rules_apply(r,
61 : "user@example.com", "Hello", NULL, "INBOX",
62 : NULL, (time_t)0, &add, &ac, &rm, &rc2);
63 1 : ASSERT(fired == 0, "rule should NOT fire for non-github address");
64 1 : ASSERT(ac == 0, "should add 0 labels");
65 :
66 1 : mail_rules_free(r);
67 : }
68 :
69 1 : static void test_subject_glob(void) {
70 1 : MailRules *r = make_rules();
71 1 : MailRule *rule = add_rule(r, "Invoices");
72 1 : rule->if_subject = strdup("*invoice*");
73 1 : rule->then_add_label[rule->then_add_count++] = strdup("Invoices");
74 :
75 1 : char **add = NULL, **rm = NULL;
76 1 : int ac = 0, rc2 = 0;
77 1 : int fired = mail_rules_apply(r,
78 : "billing@acme.com", "Invoice April 2026", NULL, "",
79 : NULL, (time_t)0, &add, &ac, &rm, &rc2);
80 1 : ASSERT(fired == 1, "subject glob should match 'Invoice April 2026'");
81 1 : ASSERT(ac == 1, "should add Invoices label");
82 :
83 2 : for (int i = 0; i < ac; i++) free(add[i]);
84 1 : free(add);
85 1 : mail_rules_free(r);
86 : }
87 :
88 1 : static void test_case_insensitive(void) {
89 1 : MailRules *r = make_rules();
90 1 : MailRule *rule = add_rule(r, "CI");
91 1 : rule->if_from = strdup("*@GITHUB.COM");
92 1 : rule->then_add_label[rule->then_add_count++] = strdup("CI");
93 :
94 1 : char **add = NULL, **rm = NULL;
95 1 : int ac = 0, rc2 = 0;
96 1 : int fired = mail_rules_apply(r,
97 : "noreply@github.com", "PR", NULL, "",
98 : NULL, (time_t)0, &add, &ac, &rm, &rc2);
99 1 : ASSERT(fired == 1, "glob match should be case-insensitive");
100 :
101 2 : for (int i = 0; i < ac; i++) free(add[i]);
102 1 : free(add);
103 1 : mail_rules_free(r);
104 : }
105 :
106 1 : static void test_remove_label(void) {
107 1 : MailRules *r = make_rules();
108 1 : MailRule *rule = add_rule(r, "Archive marketing");
109 1 : rule->if_from = strdup("*@marketing.example.com");
110 1 : rule->then_add_label[rule->then_add_count++] = strdup("_spam");
111 1 : rule->then_rm_label[rule->then_rm_count++] = strdup("INBOX");
112 :
113 1 : char **add = NULL, **rm = NULL;
114 1 : int ac = 0, rc2 = 0;
115 1 : int fired = mail_rules_apply(r,
116 : "promo@marketing.example.com", "Big Sale!", NULL,
117 : "INBOX,UNREAD",
118 : NULL, (time_t)0, &add, &ac, &rm, &rc2);
119 1 : ASSERT(fired == 1, "rule should fire");
120 1 : ASSERT(ac == 1 && strcmp(add[0], "_spam") == 0, "should add _spam");
121 1 : ASSERT(rc2 == 1 && strcmp(rm[0], "INBOX") == 0, "should remove INBOX");
122 :
123 2 : for (int i = 0; i < ac; i++) free(add[i]);
124 2 : for (int i = 0; i < rc2; i++) free(rm[i]);
125 1 : free(add); free(rm);
126 1 : mail_rules_free(r);
127 : }
128 :
129 1 : static void test_multiple_rules_chained(void) {
130 : /* Rule 1: if from @acme.com → add Client
131 : * Rule 2: if-label=Client → add Priority, remove INBOX */
132 1 : MailRules *r = make_rules();
133 :
134 1 : MailRule *r1 = add_rule(r, "Acme");
135 1 : r1->if_from = strdup("*@acme.com");
136 1 : r1->then_add_label[r1->then_add_count++] = strdup("Client");
137 :
138 1 : MailRule *r2 = add_rule(r, "Priority");
139 1 : r2->if_label = strdup("Client");
140 1 : r2->then_add_label[r2->then_add_count++] = strdup("Priority");
141 1 : r2->then_rm_label[r2->then_rm_count++] = strdup("INBOX");
142 :
143 1 : char **add = NULL, **rm = NULL;
144 1 : int ac = 0, rc2 = 0;
145 1 : int fired = mail_rules_apply(r,
146 : "ceo@acme.com", "Quarterly results", NULL, "INBOX",
147 : NULL, (time_t)0, &add, &ac, &rm, &rc2);
148 1 : ASSERT(fired == 2, "both rules should fire");
149 1 : ASSERT(ac == 2, "should add Client and Priority");
150 1 : ASSERT(rc2 == 1, "should remove INBOX");
151 :
152 1 : int has_client = 0, has_priority = 0;
153 3 : for (int i = 0; i < ac; i++) {
154 2 : if (strcmp(add[i], "Client") == 0) has_client = 1;
155 2 : if (strcmp(add[i], "Priority") == 0) has_priority = 1;
156 2 : free(add[i]);
157 : }
158 1 : ASSERT(has_client, "Client label should be added");
159 1 : ASSERT(has_priority, "Priority label should be added");
160 2 : for (int i = 0; i < rc2; i++) free(rm[i]);
161 1 : free(add); free(rm);
162 1 : mail_rules_free(r);
163 : }
164 :
165 1 : static void test_no_rules(void) {
166 1 : MailRules *r = make_rules();
167 1 : char **add = NULL, **rm = NULL;
168 1 : int ac = 0, rc2 = 0;
169 1 : int fired = mail_rules_apply(r, "x@y.com", "Hi", NULL, "",
170 : NULL, (time_t)0, &add, &ac, &rm, &rc2);
171 1 : ASSERT(fired == 0, "empty rule set fires 0 rules");
172 1 : mail_rules_free(r);
173 : }
174 :
175 1 : static void test_no_condition_matches_all(void) {
176 : /* Rule with no conditions should match any message */
177 1 : MailRules *r = make_rules();
178 1 : MailRule *rule = add_rule(r, "Catchall");
179 1 : rule->then_add_label[rule->then_add_count++] = strdup("Processed");
180 :
181 1 : char **add = NULL, **rm = NULL;
182 1 : int ac = 0, rc2 = 0;
183 1 : int fired = mail_rules_apply(r, "any@example.com", "Whatever", NULL, "",
184 : NULL, (time_t)0, &add, &ac, &rm, &rc2);
185 1 : ASSERT(fired == 1, "rule with no conditions should always fire");
186 1 : ASSERT(ac == 1, "should add Processed label");
187 :
188 2 : for (int i = 0; i < ac; i++) free(add[i]);
189 1 : free(add);
190 1 : mail_rules_free(r);
191 : }
192 :
193 1 : static void test_body_condition(void) {
194 1 : MailRules *r = make_rules();
195 1 : MailRule *rule = add_rule(r, "Newsletter");
196 1 : rule->if_body = strdup("*unsubscribe*");
197 1 : rule->then_add_label[rule->then_add_count++] = strdup("Newsletter");
198 :
199 1 : char **add = NULL, **rm = NULL;
200 1 : int ac = 0, rc2 = 0;
201 :
202 1 : int fired = mail_rules_apply(r, "news@example.com", "Weekly", NULL, "",
203 : "Please unsubscribe here if needed", (time_t)0,
204 : &add, &ac, &rm, &rc2);
205 1 : ASSERT(fired == 1, "if-body should match");
206 1 : ASSERT(ac == 1, "should add Newsletter label");
207 2 : for (int i = 0; i < ac; i++) free(add[i]);
208 1 : free(add); free(rm); add = NULL; rm = NULL; ac = 0; rc2 = 0;
209 :
210 1 : fired = mail_rules_apply(r, "news@example.com", "Weekly", NULL, "",
211 : "Hello world", (time_t)0, &add, &ac, &rm, &rc2);
212 1 : ASSERT(fired == 0, "if-body should not match");
213 :
214 1 : fired = mail_rules_apply(r, "news@example.com", "Weekly", NULL, "",
215 : NULL, (time_t)0, &add, &ac, &rm, &rc2);
216 1 : ASSERT(fired == 0, "if-body with NULL body should not fire");
217 :
218 1 : mail_rules_free(r);
219 : }
220 :
221 1 : static void test_age_condition(void) {
222 1 : MailRules *r = make_rules();
223 1 : MailRule *rule = add_rule(r, "Old");
224 1 : rule->if_age_gt = 30;
225 1 : rule->then_add_label[rule->then_add_count++] = strdup("Old");
226 :
227 1 : char **add = NULL, **rm = NULL;
228 1 : int ac = 0, rc2 = 0;
229 :
230 1 : time_t now = time(NULL);
231 1 : time_t recent = now - (1 * 86400);
232 1 : time_t old_msg = now - (60 * 86400);
233 :
234 1 : int fired = mail_rules_apply(r, "x@y.com", "Hi", NULL, "",
235 : NULL, recent, &add, &ac, &rm, &rc2);
236 1 : ASSERT(fired == 0, "if-age-gt=30 should not fire for 1-day-old message");
237 :
238 1 : fired = mail_rules_apply(r, "x@y.com", "Hi", NULL, "",
239 : NULL, old_msg, &add, &ac, &rm, &rc2);
240 1 : ASSERT(fired == 1, "if-age-gt=30 should fire for 60-day-old message");
241 2 : for (int i = 0; i < ac; i++) free(add[i]);
242 1 : free(add); free(rm); add = NULL; rm = NULL; ac = 0; rc2 = 0;
243 :
244 1 : fired = mail_rules_apply(r, "x@y.com", "Hi", NULL, "",
245 : NULL, (time_t)0, &add, &ac, &rm, &rc2);
246 1 : ASSERT(fired == 1, "if-age-gt with unknown date should fire (age check skipped)");
247 2 : for (int i = 0; i < ac; i++) free(add[i]);
248 1 : free(add); free(rm);
249 :
250 1 : mail_rules_free(r);
251 : }
252 :
253 1 : static void test_age_lt_condition(void) {
254 1 : MailRules *r = make_rules();
255 1 : MailRule *rule = add_rule(r, "Recent");
256 1 : rule->if_age_lt = 7;
257 1 : rule->then_add_label[rule->then_add_count++] = strdup("Recent");
258 :
259 1 : char **add = NULL, **rm = NULL;
260 1 : int ac = 0, rc2 = 0;
261 :
262 1 : time_t now = time(NULL);
263 1 : time_t new_msg = now - (1 * 86400);
264 1 : time_t old_msg = now - (30 * 86400);
265 :
266 1 : int fired = mail_rules_apply(r, "x@y.com", "Hi", NULL, "",
267 : NULL, new_msg, &add, &ac, &rm, &rc2);
268 1 : ASSERT(fired == 1, "if-age-lt=7 should fire for 1-day-old message");
269 2 : for (int i = 0; i < ac; i++) free(add[i]);
270 1 : free(add); free(rm); add = NULL; rm = NULL; ac = 0; rc2 = 0;
271 :
272 1 : fired = mail_rules_apply(r, "x@y.com", "Hi", NULL, "",
273 : NULL, old_msg, &add, &ac, &rm, &rc2);
274 1 : ASSERT(fired == 0, "if-age-lt=7 should not fire for 30-day-old message");
275 :
276 1 : mail_rules_free(r);
277 : }
278 :
279 1 : static void test_negation_if_not_from(void) {
280 1 : MailRules *r = make_rules();
281 1 : MailRule *rule = add_rule(r, "Not spam");
282 1 : rule->if_not_from = strdup("*@spam.example.com*");
283 1 : rule->then_add_label[rule->then_add_count++] = strdup("Legit");
284 :
285 1 : char **add = NULL, **rm = NULL;
286 1 : int ac = 0, rc2 = 0;
287 :
288 : /* Should NOT match spam sender */
289 1 : int fired = mail_rules_apply(r, "bad@spam.example.com", "Hi", NULL, "",
290 : NULL, (time_t)0, &add, &ac, &rm, &rc2);
291 1 : ASSERT(fired == 0, "if-not-from should reject matching sender");
292 :
293 : /* Should match non-spam sender */
294 1 : fired = mail_rules_apply(r, "good@legit.example.com", "Hi", NULL, "",
295 : NULL, (time_t)0, &add, &ac, &rm, &rc2);
296 1 : ASSERT(fired == 1, "if-not-from should pass non-matching sender");
297 1 : ASSERT(ac == 1, "should add Legit label");
298 2 : for (int i = 0; i < ac; i++) free(add[i]);
299 1 : free(add); free(rm);
300 1 : mail_rules_free(r);
301 : }
302 :
303 1 : static void test_negation_if_not_subject(void) {
304 1 : MailRules *r = make_rules();
305 1 : MailRule *rule = add_rule(r, "Not newsletter");
306 1 : rule->if_not_subject = strdup("*newsletter*");
307 1 : rule->then_add_label[rule->then_add_count++] = strdup("Regular");
308 :
309 1 : char **add = NULL, **rm = NULL;
310 1 : int ac = 0, rc2 = 0;
311 :
312 1 : int fired = mail_rules_apply(r, "x@y.com", "Weekly newsletter digest", NULL, "",
313 : NULL, (time_t)0, &add, &ac, &rm, &rc2);
314 1 : ASSERT(fired == 0, "if-not-subject should reject matching subject");
315 :
316 1 : fired = mail_rules_apply(r, "x@y.com", "Hello world", NULL, "",
317 : NULL, (time_t)0, &add, &ac, &rm, &rc2);
318 1 : ASSERT(fired == 1, "if-not-subject should pass non-matching subject");
319 2 : for (int i = 0; i < ac; i++) free(add[i]);
320 1 : free(add); free(rm);
321 1 : mail_rules_free(r);
322 : }
323 :
324 1 : static void test_negation_combined_with_positive(void) {
325 : /* if-from = *@legit.com AND if-not-subject = *spam* */
326 1 : MailRules *r = make_rules();
327 1 : MailRule *rule = add_rule(r, "Legit non-spam");
328 1 : rule->if_from = strdup("*@legit.com*");
329 1 : rule->if_not_subject = strdup("*spam*");
330 1 : rule->then_add_label[rule->then_add_count++] = strdup("OK");
331 :
332 1 : char **add = NULL, **rm = NULL;
333 1 : int ac = 0, rc2 = 0;
334 :
335 : /* Matches: legit domain, non-spam subject */
336 1 : int fired = mail_rules_apply(r, "user@legit.com", "Hello", NULL, "",
337 : NULL, (time_t)0, &add, &ac, &rm, &rc2);
338 1 : ASSERT(fired == 1, "positive+negation: should match");
339 2 : for (int i = 0; i < ac; i++) free(add[i]);
340 1 : free(add); free(rm); add = NULL; rm = NULL; ac = 0; rc2 = 0;
341 :
342 : /* Fails positive: wrong domain */
343 1 : fired = mail_rules_apply(r, "user@other.com", "Hello", NULL, "",
344 : NULL, (time_t)0, &add, &ac, &rm, &rc2);
345 1 : ASSERT(fired == 0, "positive+negation: wrong domain should not match");
346 :
347 : /* Fails negation: spam subject */
348 1 : fired = mail_rules_apply(r, "user@legit.com", "Big spam offer", NULL, "",
349 : NULL, (time_t)0, &add, &ac, &rm, &rc2);
350 1 : ASSERT(fired == 0, "positive+negation: spam subject should not match");
351 1 : mail_rules_free(r);
352 : }
353 :
354 1 : static void test_load_utf7_folder_name(void) {
355 : /* Write a temp rules.ini with a then-move-folder in IMAP modified UTF-7 */
356 1 : char tmppath[] = "/tmp/test-mail-rules-XXXXXX";
357 1 : int fd = mkstemp(tmppath);
358 1 : ASSERT(fd >= 0, "utf7 load: mkstemp failed");
359 1 : const char *ini =
360 : "[rule \"hivataos\"]\n"
361 : "if-from = *@gov.hu*\n"
362 : /* "hivatalos és pénzügy" in IMAP modified UTF-7 */
363 : "then-move-folder = hivatalos &AOk-s p&AOk-nz&APw-gy\n";
364 1 : ssize_t written = write(fd, ini, strlen(ini));
365 : (void)written;
366 1 : close(fd);
367 :
368 1 : MailRules *r = mail_rules_load_path(tmppath);
369 1 : unlink(tmppath);
370 1 : ASSERT(r != NULL, "utf7 load: load_path returned NULL");
371 1 : ASSERT(r->count == 1, "utf7 load: expected 1 rule");
372 1 : ASSERT(r->rules[0].then_move_folder != NULL, "utf7 load: then_move_folder is NULL");
373 : /* é = U+00E9 = C3 A9; ü = U+00FC = C3 BC */
374 1 : ASSERT(strcmp(r->rules[0].then_move_folder,
375 : "hivatalos \xC3\xA9s p\xC3\xA9nz\xC3\xBCgy") == 0,
376 : "utf7 load: then_move_folder not decoded to UTF-8");
377 1 : mail_rules_free(r);
378 : }
379 :
380 1 : static void test_rules_parse_file_features(void) {
381 : /* Rules file with unnamed rule, when=, then-add/remove-label, then-forward-to */
382 1 : char tmppath[] = "/tmp/test-mail-rules-features-XXXXXX";
383 1 : int fd = mkstemp(tmppath);
384 1 : ASSERT(fd >= 0, "parse features: mkstemp failed");
385 :
386 1 : const char *ini =
387 : "[rule]\n"
388 : "when = from:*@test.com\n"
389 : "then-add-label = TestLabel\n"
390 : "then-remove-label = INBOX\n"
391 : "then-forward-to = fwd@example.com\n";
392 1 : ssize_t w = write(fd, ini, strlen(ini)); (void)w;
393 1 : close(fd);
394 :
395 1 : MailRules *r = mail_rules_load_path(tmppath);
396 1 : unlink(tmppath);
397 :
398 1 : ASSERT(r != NULL, "parse features: load returned non-NULL");
399 1 : ASSERT(r->count == 1, "parse features: 1 rule loaded");
400 1 : ASSERT(r->rules[0].name != NULL, "parse features: name not NULL");
401 1 : ASSERT(strcmp(r->rules[0].name, "(unnamed)") == 0, "parse features: unnamed rule name");
402 1 : ASSERT(r->rules[0].when != NULL, "parse features: when set");
403 1 : ASSERT(strcmp(r->rules[0].when, "from:*@test.com") == 0, "parse features: when value");
404 1 : ASSERT(r->rules[0].then_add_count == 1, "parse features: then_add_count=1");
405 1 : ASSERT(strcmp(r->rules[0].then_add_label[0], "TestLabel") == 0, "parse features: then_add_label");
406 1 : ASSERT(r->rules[0].then_rm_count == 1, "parse features: then_rm_count=1");
407 1 : ASSERT(strcmp(r->rules[0].then_rm_label[0], "INBOX") == 0, "parse features: then_rm_label");
408 1 : ASSERT(r->rules[0].then_forward_to != NULL, "parse features: then_forward_to set");
409 1 : ASSERT(strcmp(r->rules[0].then_forward_to, "fwd@example.com") == 0,
410 : "parse features: then_forward_to value");
411 1 : mail_rules_free(r);
412 : }
413 :
414 1 : static void test_rules_when_expression(void) {
415 1 : MailRules *r = make_rules();
416 1 : MailRule *rule = add_rule(r, "When test");
417 1 : rule->when = strdup("from:*@corp.com");
418 1 : rule->then_add_label[rule->then_add_count++] = strdup("Corp");
419 :
420 1 : char **add = NULL, **rm = NULL;
421 1 : int ac = 0, rc2 = 0;
422 :
423 1 : int fired = mail_rules_apply(r, "boss@corp.com", "Hello", NULL, "",
424 : NULL, (time_t)0, &add, &ac, &rm, &rc2);
425 1 : ASSERT(fired == 1, "when expr: fires for matching from");
426 1 : ASSERT(ac == 1, "when expr: adds 1 label");
427 2 : for (int i = 0; i < ac; i++) free(add[i]);
428 1 : free(add); free(rm); add = NULL; rm = NULL; ac = 0; rc2 = 0;
429 :
430 1 : fired = mail_rules_apply(r, "other@example.com", "Hi", NULL, "",
431 : NULL, (time_t)0, &add, &ac, &rm, &rc2);
432 1 : ASSERT(fired == 0, "when expr: does not fire for non-matching from");
433 :
434 : /* Invalid when expression → rule skipped */
435 1 : free(rule->when);
436 1 : rule->when = strdup("!!!invalid!!!");
437 1 : fired = mail_rules_apply(r, "boss@corp.com", "Hello", NULL, "",
438 : NULL, (time_t)0, &add, &ac, &rm, &rc2);
439 1 : ASSERT(fired == 0, "when expr: invalid expr → rule skipped");
440 :
441 1 : mail_rules_free(r);
442 : }
443 :
444 1 : static void test_rules_then_move_folder(void) {
445 1 : MailRules *r = make_rules();
446 1 : MailRule *rule = add_rule(r, "Move Rule");
447 1 : rule->if_from = strdup("*@move.test");
448 1 : rule->then_move_folder = strdup("Processed");
449 :
450 1 : char **add = NULL, **rm = NULL;
451 1 : char *move_folder = NULL;
452 1 : int ac = 0, rc2 = 0;
453 :
454 1 : int fired = mail_rules_apply_ex(r, "x@move.test", "Msg", NULL, "",
455 : NULL, (time_t)0, &add, &ac, &rm, &rc2,
456 : &move_folder);
457 1 : ASSERT(fired == 1, "move_folder rule: fired");
458 1 : ASSERT(move_folder != NULL, "move_folder rule: move_folder set");
459 1 : if (move_folder)
460 1 : ASSERT(strcmp(move_folder, "Processed") == 0, "move_folder rule: value");
461 1 : free(move_folder);
462 1 : mail_rules_free(r);
463 : }
464 :
465 1 : static void test_rules_save_load_account(void) {
466 1 : char *old_home = getenv("HOME");
467 1 : char *old_xdg = getenv("XDG_CONFIG_HOME");
468 1 : setenv("HOME", "/tmp/email-cli-rules-acct-test", 1);
469 1 : unsetenv("XDG_CONFIG_HOME");
470 :
471 1 : MailRules *r = make_rules();
472 1 : MailRule *rule = add_rule(r, "Save Rule");
473 1 : rule->if_from = strdup("*@save.test");
474 1 : rule->when = strdup("from:*@save.test");
475 1 : rule->then_add_label[rule->then_add_count++] = strdup("Saved");
476 1 : rule->then_rm_label[rule->then_rm_count++] = strdup("INBOX");
477 1 : rule->then_move_folder = strdup("Archive");
478 :
479 1 : int rc = mail_rules_save("save-test@save.test", r);
480 1 : ASSERT(rc == 0, "rules_save: returns 0");
481 1 : mail_rules_free(r);
482 :
483 1 : MailRules *loaded = mail_rules_load("save-test@save.test");
484 1 : ASSERT(loaded != NULL, "rules_load: not NULL");
485 1 : if (loaded) {
486 1 : ASSERT(loaded->count == 1, "rules_load: count=1");
487 1 : ASSERT(loaded->rules[0].when != NULL, "rules_load: when set");
488 1 : if (loaded->rules[0].when)
489 1 : ASSERT(strcmp(loaded->rules[0].when, "from:*@save.test") == 0,
490 : "rules_load: when value");
491 1 : ASSERT(loaded->rules[0].then_add_count == 1, "rules_load: then_add_count");
492 1 : ASSERT(loaded->rules[0].then_rm_count == 1, "rules_load: then_rm_count");
493 1 : ASSERT(loaded->rules[0].then_move_folder != NULL, "rules_load: move_folder");
494 1 : mail_rules_free(loaded);
495 : }
496 :
497 : /* Load non-existent account → NULL */
498 1 : MailRules *miss = mail_rules_load("nobody@nowhere.invalid");
499 1 : ASSERT(miss == NULL, "rules_load: missing account returns NULL");
500 :
501 1 : if (old_home) setenv("HOME", old_home, 1); else unsetenv("HOME");
502 1 : if (old_xdg) setenv("XDG_CONFIG_HOME", old_xdg, 1); else unsetenv("XDG_CONFIG_HOME");
503 : }
504 :
505 : /* ── Registration ────────────────────────────────────────────────── */
506 :
507 1 : void test_mail_rules(void) {
508 1 : RUN_TEST(test_glob_match_basic);
509 1 : RUN_TEST(test_glob_no_match);
510 1 : RUN_TEST(test_subject_glob);
511 1 : RUN_TEST(test_case_insensitive);
512 1 : RUN_TEST(test_remove_label);
513 1 : RUN_TEST(test_multiple_rules_chained);
514 1 : RUN_TEST(test_no_rules);
515 1 : RUN_TEST(test_no_condition_matches_all);
516 1 : RUN_TEST(test_body_condition);
517 1 : RUN_TEST(test_age_condition);
518 1 : RUN_TEST(test_age_lt_condition);
519 1 : RUN_TEST(test_negation_if_not_from);
520 1 : RUN_TEST(test_negation_if_not_subject);
521 1 : RUN_TEST(test_negation_combined_with_positive);
522 1 : RUN_TEST(test_load_utf7_folder_name);
523 1 : RUN_TEST(test_rules_parse_file_features);
524 1 : RUN_TEST(test_rules_when_expression);
525 1 : RUN_TEST(test_rules_then_move_folder);
526 1 : RUN_TEST(test_rules_save_load_account);
527 1 : }
|