Line data Source code
1 : #include "test_helpers.h"
2 : #include "compose_service.h"
3 : #include <fcntl.h>
4 : #include <stdio.h>
5 : #include <stdlib.h>
6 : #include <string.h>
7 : #include <sys/stat.h>
8 : #include <unistd.h>
9 :
10 : /* ── compose_build_message ───────────────────────────────────────────── */
11 :
12 1 : static void test_build_null_params(void) {
13 1 : char *out = NULL;
14 1 : size_t len = 0;
15 1 : ASSERT(compose_build_message(NULL, &out, &len) == -1,
16 : "build: NULL params → -1");
17 : }
18 :
19 1 : static void test_build_missing_fields(void) {
20 1 : char *out = NULL;
21 1 : size_t len = 0;
22 1 : ComposeParams p = {0};
23 1 : ASSERT(compose_build_message(&p, &out, &len) == -1,
24 : "build: missing from/to/subject → -1");
25 :
26 1 : p.from = "a@b.com";
27 1 : ASSERT(compose_build_message(&p, &out, &len) == -1,
28 : "build: missing to/subject → -1");
29 :
30 1 : p.to = "c@d.com";
31 1 : ASSERT(compose_build_message(&p, &out, &len) == -1,
32 : "build: missing subject → -1");
33 : }
34 :
35 1 : static void test_build_null_out(void) {
36 1 : ComposeParams p = {.from = "a@b.com", .to = "c@d.com", .subject = "Hi"};
37 1 : size_t len = 0;
38 1 : ASSERT(compose_build_message(&p, NULL, &len) == -1,
39 : "build: NULL out → -1");
40 : }
41 :
42 1 : static void test_build_basic(void) {
43 1 : ComposeParams p = {
44 : .from = "alice@example.com",
45 : .to = "bob@example.com",
46 : .subject = "Test subject",
47 : .body = "Hello, world!\n"
48 : };
49 1 : char *out = NULL;
50 1 : size_t len = 0;
51 1 : int rc = compose_build_message(&p, &out, &len);
52 1 : ASSERT(rc == 0, "build basic: success");
53 1 : ASSERT(out != NULL, "build basic: out not NULL");
54 1 : ASSERT(len > 0, "build basic: len > 0");
55 :
56 : /* Verify required headers */
57 1 : ASSERT(strstr(out, "From: alice@example.com\r\n") != NULL,
58 : "build basic: From header");
59 1 : ASSERT(strstr(out, "To: bob@example.com\r\n") != NULL,
60 : "build basic: To header");
61 1 : ASSERT(strstr(out, "Subject: Test subject\r\n") != NULL,
62 : "build basic: Subject header");
63 1 : ASSERT(strstr(out, "Date: ") != NULL,
64 : "build basic: Date header");
65 1 : ASSERT(strstr(out, "Message-ID: <") != NULL,
66 : "build basic: Message-ID header");
67 1 : ASSERT(strstr(out, "MIME-Version: 1.0\r\n") != NULL,
68 : "build basic: MIME-Version header");
69 1 : ASSERT(strstr(out, "Content-Type: text/plain; charset=UTF-8\r\n") != NULL,
70 : "build basic: Content-Type header");
71 :
72 : /* Verify header/body separator */
73 1 : ASSERT(strstr(out, "\r\n\r\n") != NULL,
74 : "build basic: CRLF separator");
75 :
76 : /* Verify body is present after separator */
77 1 : const char *body_start = strstr(out, "\r\n\r\n");
78 1 : ASSERT(body_start && strstr(body_start, "Hello, world!") != NULL,
79 : "build basic: body present");
80 :
81 : /* No In-Reply-To for non-reply */
82 1 : ASSERT(strstr(out, "In-Reply-To") == NULL,
83 : "build basic: no In-Reply-To");
84 :
85 1 : free(out);
86 : }
87 :
88 1 : static void test_build_reply(void) {
89 1 : ComposeParams p = {
90 : .from = "alice@example.com",
91 : .to = "bob@example.com",
92 : .subject = "Re: Original",
93 : .body = "Thanks!",
94 : .reply_to_msg_id = "<orig123@example.com>"
95 : };
96 1 : char *out = NULL;
97 1 : size_t len = 0;
98 1 : int rc = compose_build_message(&p, &out, &len);
99 1 : ASSERT(rc == 0, "build reply: success");
100 1 : ASSERT(strstr(out, "In-Reply-To: <orig123@example.com>\r\n") != NULL,
101 : "build reply: In-Reply-To header");
102 1 : ASSERT(strstr(out, "References: <orig123@example.com>\r\n") != NULL,
103 : "build reply: References header");
104 1 : free(out);
105 : }
106 :
107 1 : static void test_build_empty_body(void) {
108 1 : ComposeParams p = {
109 : .from = "a@b.com",
110 : .to = "c@d.com",
111 : .subject = "Empty",
112 : .body = NULL
113 : };
114 1 : char *out = NULL;
115 1 : size_t len = 0;
116 1 : int rc = compose_build_message(&p, &out, &len);
117 1 : ASSERT(rc == 0, "build empty body: success");
118 1 : ASSERT(out != NULL, "build empty body: not NULL");
119 1 : free(out);
120 : }
121 :
122 1 : static void test_build_lf_to_crlf(void) {
123 1 : ComposeParams p = {
124 : .from = "a@b.com",
125 : .to = "c@d.com",
126 : .subject = "Lines",
127 : .body = "line1\nline2\nline3"
128 : };
129 1 : char *out = NULL;
130 1 : size_t len = 0;
131 1 : int rc = compose_build_message(&p, &out, &len);
132 1 : ASSERT(rc == 0, "build LF→CRLF: success");
133 : /* Body should have CRLF line endings */
134 1 : const char *body = strstr(out, "\r\n\r\n");
135 1 : ASSERT(body && strstr(body, "line1\r\nline2\r\nline3") != NULL,
136 : "build LF→CRLF: body has CRLF");
137 1 : free(out);
138 : }
139 :
140 : /* ── compose_extract_reply_meta ──────────────────────────────────────── */
141 :
142 1 : static void test_extract_null(void) {
143 1 : char *rt = NULL, *subj = NULL, *msgid = NULL;
144 1 : ASSERT(compose_extract_reply_meta(NULL, &rt, &subj, &msgid) == -1,
145 : "extract: NULL raw_msg → -1");
146 1 : ASSERT(compose_extract_reply_meta("From: a\r\n\r\n", NULL, &subj, &msgid) == -1,
147 : "extract: NULL reply_to_out → -1");
148 : }
149 :
150 1 : static void test_extract_basic(void) {
151 1 : const char *raw =
152 : "From: Alice <alice@example.com>\r\n"
153 : "Subject: Hello\r\n"
154 : "Message-ID: <abc123@example.com>\r\n"
155 : "\r\n"
156 : "Body.\r\n";
157 1 : char *rt = NULL, *subj = NULL, *msgid = NULL;
158 1 : int rc = compose_extract_reply_meta(raw, &rt, &subj, &msgid);
159 1 : ASSERT(rc == 0, "extract basic: success");
160 1 : ASSERT(rt != NULL, "extract basic: reply_to not NULL");
161 1 : ASSERT(strstr(rt, "alice@example.com") != NULL,
162 : "extract basic: reply_to has address");
163 1 : ASSERT(subj != NULL && strncmp(subj, "Re: ", 4) == 0,
164 : "extract basic: subject starts with Re:");
165 1 : ASSERT(strstr(subj, "Hello") != NULL,
166 : "extract basic: subject has original");
167 1 : ASSERT(msgid != NULL && strcmp(msgid, "<abc123@example.com>") == 0,
168 : "extract basic: message-id correct");
169 1 : free(rt); free(subj); free(msgid);
170 : }
171 :
172 1 : static void test_extract_re_dedup(void) {
173 1 : const char *raw =
174 : "From: Bob\r\n"
175 : "Subject: Re: Re: Re: Original\r\n"
176 : "Message-ID: <x@y>\r\n"
177 : "\r\n";
178 1 : char *rt = NULL, *subj = NULL, *msgid = NULL;
179 1 : int rc = compose_extract_reply_meta(raw, &rt, &subj, &msgid);
180 1 : ASSERT(rc == 0, "extract re dedup: success");
181 1 : ASSERT(strcmp(subj, "Re: Original") == 0,
182 : "extract re dedup: single Re: prefix");
183 1 : free(rt); free(subj); free(msgid);
184 : }
185 :
186 1 : static void test_extract_reply_to_header(void) {
187 1 : const char *raw =
188 : "From: Alice\r\n"
189 : "Reply-To: noreply@example.com\r\n"
190 : "Subject: Test\r\n"
191 : "Message-ID: <z@w>\r\n"
192 : "\r\n";
193 1 : char *rt = NULL, *subj = NULL, *msgid = NULL;
194 1 : int rc = compose_extract_reply_meta(raw, &rt, &subj, &msgid);
195 1 : ASSERT(rc == 0, "extract Reply-To: success");
196 1 : ASSERT(strstr(rt, "noreply@example.com") != NULL,
197 : "extract Reply-To: prefers Reply-To over From");
198 1 : free(rt); free(subj); free(msgid);
199 : }
200 :
201 : /* ── Registration ────────────────────────────────────────────────────── */
202 :
203 1 : static void test_build_cc(void) {
204 1 : ComposeParams p = {
205 : .from = "alice@example.com",
206 : .to = "bob@example.com",
207 : .cc = "carol@example.com",
208 : .subject = "Hello",
209 : .body = "Hi"
210 : };
211 1 : char *out = NULL; size_t len = 0;
212 1 : ASSERT(compose_build_message(&p, &out, &len) == 0, "build cc: success");
213 1 : ASSERT(strstr(out, "Cc: carol@example.com\r\n") != NULL,
214 : "build cc: Cc header present");
215 1 : ASSERT(strstr(out, "Bcc:") == NULL, "build cc: no Bcc when bcc NULL");
216 1 : free(out);
217 : }
218 :
219 1 : static void test_build_bcc(void) {
220 1 : ComposeParams p = {
221 : .from = "alice@example.com",
222 : .to = "bob@example.com",
223 : .bcc = "secret@example.com",
224 : .subject = "Hello",
225 : .body = "Hi"
226 : };
227 1 : char *out = NULL; size_t len = 0;
228 1 : ASSERT(compose_build_message(&p, &out, &len) == 0, "build bcc: success");
229 1 : ASSERT(strstr(out, "Bcc: secret@example.com\r\n") != NULL,
230 : "build bcc: Bcc header present");
231 1 : ASSERT(strstr(out, "Cc:") == NULL, "build bcc: no Cc when cc NULL");
232 1 : free(out);
233 : }
234 :
235 1 : static void test_build_cc_and_bcc(void) {
236 1 : ComposeParams p = {
237 : .from = "alice@example.com",
238 : .to = "bob@example.com",
239 : .cc = "carol@example.com, dave@example.com",
240 : .bcc = "eve@example.com",
241 : .subject = "Meeting",
242 : .body = "See you there"
243 : };
244 1 : char *out = NULL; size_t len = 0;
245 1 : ASSERT(compose_build_message(&p, &out, &len) == 0, "build cc+bcc: success");
246 1 : ASSERT(strstr(out, "Cc: carol@example.com, dave@example.com\r\n") != NULL,
247 : "build cc+bcc: Cc header");
248 1 : ASSERT(strstr(out, "Bcc: eve@example.com\r\n") != NULL,
249 : "build cc+bcc: Bcc header");
250 1 : ASSERT(strstr(out, "To: bob@example.com\r\n") != NULL,
251 : "build cc+bcc: To header still present");
252 1 : free(out);
253 : }
254 :
255 1 : static void test_build_empty_cc_omitted(void) {
256 1 : ComposeParams p = {
257 : .from = "a@b.com", .to = "c@d.com",
258 : .cc = "", .bcc = "",
259 : .subject = "X", .body = "Y"
260 : };
261 1 : char *out = NULL; size_t len = 0;
262 1 : ASSERT(compose_build_message(&p, &out, &len) == 0, "build empty cc: success");
263 1 : ASSERT(strstr(out, "Cc:") == NULL, "build empty cc: no Cc header");
264 1 : ASSERT(strstr(out, "Bcc:") == NULL, "build empty cc: no Bcc header");
265 1 : free(out);
266 : }
267 :
268 : /* ── compose_build_message with attachments (US-84) ─────────────────── */
269 :
270 : /* Write a small temp file and return a heap-allocated path, or NULL. */
271 5 : static char *make_tmp_attach(const char *content)
272 : {
273 5 : char *path = strdup("/tmp/test-compose-attach-XXXXXX");
274 5 : int fd = mkstemp(path);
275 5 : if (fd < 0) { free(path); return NULL; }
276 5 : size_t len = strlen(content);
277 5 : ssize_t written = write(fd, content, len);
278 5 : close(fd);
279 5 : if (written < 0 || (size_t)written != len) {
280 0 : unlink(path);
281 0 : free(path);
282 0 : return NULL;
283 : }
284 5 : return path;
285 : }
286 :
287 :
288 : /* Write a temp file that keeps a given extension, so that the MIME type can be
289 : * inferred from it. mkstemps() reserves the trailing suffix. */
290 16 : static char *make_tmp_attach_ext(const char *content, const char *suffix)
291 : {
292 16 : char *path = NULL;
293 16 : if (asprintf(&path, "/tmp/test-compose-mime-XXXXXX%s", suffix) == -1) return NULL;
294 16 : int fd = mkstemps(path, (int)strlen(suffix));
295 16 : if (fd < 0) { free(path); return NULL; }
296 16 : size_t len = strlen(content);
297 16 : ssize_t written = write(fd, content, len);
298 16 : close(fd);
299 16 : if (written < 0 || (size_t)written != len) {
300 0 : unlink(path); free(path); return NULL;
301 : }
302 16 : return path;
303 : }
304 :
305 : /* Every extension the builder knows must reach the outgoing part's
306 : * Content-Type; an unknown one must fall back to application/octet-stream.
307 : * Attachments carry their type from the filename alone, so a wrong mapping
308 : * silently mislabels what the recipient receives. */
309 1 : static void test_attach_mime_type_per_extension(void)
310 : {
311 : static const struct { const char *suffix; const char *ctype; } cases[] = {
312 : { ".pdf", "application/pdf" },
313 : { ".txt", "text/plain" },
314 : { ".html", "text/html" },
315 : { ".htm", "text/html" },
316 : { ".jpg", "image/jpeg" },
317 : { ".jpeg", "image/jpeg" },
318 : { ".png", "image/png" },
319 : { ".gif", "image/gif" },
320 : { ".zip", "application/zip" },
321 : { ".gz", "application/gzip" },
322 : { ".csv", "text/csv" },
323 : { ".json", "application/json" },
324 : { ".xml", "application/xml" },
325 : { ".PDF", "application/pdf" }, /* extension match is case-insensitive */
326 : { ".bin", "application/octet-stream" }, /* unknown extension */
327 : };
328 :
329 16 : for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
330 15 : char *path = make_tmp_attach_ext("payload", cases[i].suffix);
331 15 : ASSERT(path != NULL, "mime: temp attachment created");
332 15 : if (!path) continue;
333 :
334 15 : const char *atts[] = { path, NULL };
335 15 : ComposeParams p = {
336 : .from = "a@b.com", .to = "c@d.com",
337 : .subject = "MIME", .body = "Hi",
338 : .attachments = atts, .attach_count = 1
339 : };
340 15 : char *out = NULL; size_t len = 0;
341 15 : int rc = compose_build_message(&p, &out, &len);
342 15 : ASSERT(rc == 0, "mime: build succeeds");
343 15 : ASSERT(out && strstr(out, cases[i].ctype) != NULL,
344 : "mime: Content-Type inferred from the extension");
345 15 : free(out);
346 15 : unlink(path);
347 15 : free(path);
348 : }
349 : }
350 :
351 : /* A file with no dot at all has no extension to infer from. */
352 1 : static void test_attach_mime_type_without_extension(void)
353 : {
354 1 : char *path = make_tmp_attach_ext("payload", "");
355 1 : ASSERT(path != NULL, "mime: extensionless temp file created");
356 1 : if (!path) return;
357 :
358 1 : const char *atts[] = { path, NULL };
359 1 : ComposeParams p = {
360 : .from = "a@b.com", .to = "c@d.com",
361 : .subject = "MIME", .body = "Hi",
362 : .attachments = atts, .attach_count = 1
363 : };
364 1 : char *out = NULL; size_t len = 0;
365 1 : ASSERT(compose_build_message(&p, &out, &len) == 0, "mime: build succeeds");
366 1 : ASSERT(out && strstr(out, "application/octet-stream") != NULL,
367 : "mime: no extension → application/octet-stream");
368 1 : free(out);
369 1 : unlink(path);
370 1 : free(path);
371 : }
372 :
373 1 : static void test_build_no_attach_is_text_plain(void)
374 : {
375 : /* Zero attachments → must still produce text/plain (backward compat) */
376 1 : ComposeParams p = {
377 : .from = "a@b.com", .to = "c@d.com",
378 : .subject = "X", .body = "Hi",
379 : .attachments = NULL, .attach_count = 0
380 : };
381 1 : char *out = NULL; size_t len = 0;
382 1 : ASSERT(compose_build_message(&p, &out, &len) == 0,
383 : "attach: no attach → success");
384 1 : ASSERT(strstr(out, "Content-Type: text/plain") != NULL,
385 : "attach: no attach → text/plain Content-Type");
386 1 : ASSERT(strstr(out, "multipart/mixed") == NULL,
387 : "attach: no attach → no multipart/mixed");
388 1 : free(out);
389 : }
390 :
391 1 : static void test_build_one_attachment_multipart(void)
392 : {
393 : /* One attachment → multipart/mixed with text part + attachment part */
394 1 : char *path = make_tmp_attach("attachment data\n");
395 1 : ASSERT(path != NULL, "attach one: tmp file created");
396 :
397 1 : const char *attachments[] = { path, NULL };
398 1 : ComposeParams p = {
399 : .from = "a@b.com", .to = "c@d.com",
400 : .subject = "With attach", .body = "See attached.",
401 : .attachments = attachments, .attach_count = 1
402 : };
403 1 : char *out = NULL; size_t len = 0;
404 1 : int rc = compose_build_message(&p, &out, &len);
405 1 : ASSERT(rc == 0, "attach one: success");
406 1 : ASSERT(strstr(out, "multipart/mixed") != NULL,
407 : "attach one: Content-Type is multipart/mixed");
408 1 : ASSERT(strstr(out, "boundary=") != NULL,
409 : "attach one: boundary present");
410 1 : ASSERT(strstr(out, "Content-Type: text/plain") != NULL,
411 : "attach one: text part present");
412 1 : ASSERT(strstr(out, "Content-Disposition: attachment") != NULL,
413 : "attach one: attachment part has Content-Disposition");
414 1 : ASSERT(strstr(out, "Content-Transfer-Encoding: base64") != NULL,
415 : "attach one: attachment encoded as base64");
416 1 : ASSERT(strstr(out, "See attached.") != NULL,
417 : "attach one: body text present");
418 1 : free(out);
419 1 : unlink(path);
420 1 : free(path);
421 : }
422 :
423 1 : static void test_build_attachment_filename_in_disposition(void)
424 : {
425 : /* Content-Disposition must include filename="<basename>" */
426 1 : char *path = make_tmp_attach("pdf stub");
427 1 : ASSERT(path != NULL, "attach filename: tmp file created");
428 :
429 : /* Rename to something with a meaningful extension */
430 : char named[256];
431 1 : snprintf(named, sizeof(named), "/tmp/report-%d.pdf", (int)getpid());
432 1 : rename(path, named);
433 1 : free(path);
434 :
435 1 : const char *attachments[] = { named, NULL };
436 1 : ComposeParams p = {
437 : .from = "a@b.com", .to = "c@d.com",
438 : .subject = "PDF", .body = "Attached.",
439 : .attachments = attachments, .attach_count = 1
440 : };
441 1 : char *out = NULL; size_t len = 0;
442 1 : ASSERT(compose_build_message(&p, &out, &len) == 0,
443 : "attach filename: success");
444 1 : ASSERT(strstr(out, "filename=") != NULL,
445 : "attach filename: filename= in Content-Disposition");
446 : /* Should contain just the basename, not the full path */
447 : char basename_check[64];
448 1 : snprintf(basename_check, sizeof(basename_check), "report-%d.pdf", (int)getpid());
449 1 : ASSERT(strstr(out, basename_check) != NULL,
450 : "attach filename: basename in Content-Disposition");
451 1 : free(out);
452 1 : unlink(named);
453 : }
454 :
455 1 : static void test_build_two_attachments(void)
456 : {
457 : /* Two attachments → two MIME attachment parts */
458 1 : char *path1 = make_tmp_attach("file one");
459 1 : char *path2 = make_tmp_attach("file two");
460 1 : ASSERT(path1 && path2, "attach two: tmp files created");
461 :
462 1 : const char *attachments[] = { path1, path2, NULL };
463 1 : ComposeParams p = {
464 : .from = "a@b.com", .to = "c@d.com",
465 : .subject = "Two", .body = "Both attached.",
466 : .attachments = attachments, .attach_count = 2
467 : };
468 1 : char *out = NULL; size_t len = 0;
469 1 : ASSERT(compose_build_message(&p, &out, &len) == 0,
470 : "attach two: success");
471 1 : ASSERT(strstr(out, "multipart/mixed") != NULL,
472 : "attach two: multipart/mixed");
473 : /* Count Content-Disposition: attachment occurrences — must be 2 */
474 1 : int count = 0;
475 1 : const char *p2 = out;
476 3 : while ((p2 = strstr(p2, "Content-Disposition: attachment")) != NULL) {
477 2 : count++;
478 2 : p2++;
479 : }
480 1 : ASSERT(count == 2, "attach two: exactly 2 attachment parts");
481 1 : free(out);
482 1 : unlink(path1); free(path1);
483 1 : unlink(path2); free(path2);
484 : }
485 :
486 1 : static void test_build_attachment_boundary_unique(void)
487 : {
488 : /* Two separate messages must have different MIME boundaries */
489 1 : char *path = make_tmp_attach("data");
490 1 : ASSERT(path != NULL, "attach boundary: tmp file created");
491 :
492 1 : const char *attachments[] = { path, NULL };
493 1 : ComposeParams p = {
494 : .from = "a@b.com", .to = "c@d.com",
495 : .subject = "B", .body = ".",
496 : .attachments = attachments, .attach_count = 1
497 : };
498 1 : char *out1 = NULL, *out2 = NULL;
499 1 : size_t len1 = 0, len2 = 0;
500 1 : ASSERT(compose_build_message(&p, &out1, &len1) == 0, "boundary: msg1 ok");
501 1 : ASSERT(compose_build_message(&p, &out2, &len2) == 0, "boundary: msg2 ok");
502 :
503 : /* Extract boundary values — they must differ (include pid/time/random) */
504 1 : const char *b1 = strstr(out1, "boundary=\"");
505 1 : const char *b2 = strstr(out2, "boundary=\"");
506 1 : ASSERT(b1 && b2, "boundary: both have boundary");
507 : /* At minimum the two messages should not be byte-identical */
508 1 : ASSERT(strcmp(out1, out2) != 0 || len1 != len2,
509 : "boundary: two messages differ (unique boundary or Message-ID)");
510 1 : free(out1); free(out2);
511 1 : unlink(path); free(path);
512 : }
513 :
514 1 : static void test_build_attachment_nonexistent_file(void)
515 : {
516 : /* Non-existent file path → compose_build_message returns -1 */
517 1 : const char *attachments[] = { "/tmp/no-such-file-xyz-404.bin", NULL };
518 1 : ComposeParams p = {
519 : .from = "a@b.com", .to = "c@d.com",
520 : .subject = "Bad", .body = ".",
521 : .attachments = attachments, .attach_count = 1
522 : };
523 1 : char *out = NULL; size_t len = 0;
524 1 : ASSERT(compose_build_message(&p, &out, &len) == -1,
525 : "attach nonexistent: returns -1");
526 1 : ASSERT(out == NULL, "attach nonexistent: out is NULL");
527 : }
528 :
529 1 : void test_compose_service(void) {
530 1 : RUN_TEST(test_build_null_params);
531 1 : RUN_TEST(test_build_missing_fields);
532 1 : RUN_TEST(test_build_null_out);
533 1 : RUN_TEST(test_build_basic);
534 1 : RUN_TEST(test_build_reply);
535 1 : RUN_TEST(test_build_empty_body);
536 1 : RUN_TEST(test_build_lf_to_crlf);
537 1 : RUN_TEST(test_build_cc);
538 1 : RUN_TEST(test_build_bcc);
539 1 : RUN_TEST(test_build_cc_and_bcc);
540 1 : RUN_TEST(test_build_empty_cc_omitted);
541 1 : RUN_TEST(test_extract_null);
542 1 : RUN_TEST(test_extract_basic);
543 1 : RUN_TEST(test_extract_re_dedup);
544 1 : RUN_TEST(test_extract_reply_to_header);
545 : /* US-84: attachment tests — fail until compose_build_message handles them */
546 1 : RUN_TEST(test_attach_mime_type_per_extension);
547 1 : RUN_TEST(test_attach_mime_type_without_extension);
548 1 : RUN_TEST(test_build_no_attach_is_text_plain);
549 1 : RUN_TEST(test_build_one_attachment_multipart);
550 1 : RUN_TEST(test_build_attachment_filename_in_disposition);
551 1 : RUN_TEST(test_build_two_attachments);
552 1 : RUN_TEST(test_build_attachment_boundary_unique);
553 1 : RUN_TEST(test_build_attachment_nonexistent_file);
554 1 : }
|