@@ -48,6 +48,19 @@ class MockTemplates {
4848 text : undefined ,
4949 subject : "Template Subject" ,
5050 } ;
51+ case "template-with-object-attachment" :
52+ // Simulates a template that returns attachments as an object instead of array
53+ return {
54+ html : "<h1>Template HTML</h1>" ,
55+ subject : "Template Subject" ,
56+ attachments : { filename : "report.pdf" } ,
57+ } ;
58+ case "template-with-null-attachments" :
59+ return {
60+ html : "<h1>Template HTML</h1>" ,
61+ subject : "Template Subject" ,
62+ attachments : null ,
63+ } ;
5164 default :
5265 return { } ;
5366 }
@@ -351,18 +364,13 @@ describe("preparePayload Template Merging", () => {
351364 expect ( result . message . subject ) . toBe ( "Template Subject" ) ;
352365 } ) ;
353366
354- it ( "should handle incorrectly formatted attachments object " , async ( ) => {
367+ it ( "should filter out empty attachment objects with only null values " , async ( ) => {
355368 const payload = {
356- to : "tester@gmx.at " ,
369+ to : "test@example.com " ,
357370 template : {
358- name : "med_order_reply_greimel " ,
371+ name : "html-only-template " ,
359372 data : {
360- address : "Halbenrain 140 Graz" ,
361- doctorName : "Dr. Andreas" ,
362- openingHours : "Mo., Mi., Fr. 8:00-12:00Di., Do. 10:30-15:30" ,
363- orderText : "Some stuff i need" ,
364- userName : "Pfeiler " ,
365- name : "med_order_reply_greimel" ,
373+ name : "Test User" ,
366374 } ,
367375 } ,
368376 message : {
@@ -372,20 +380,20 @@ describe("preparePayload Template Merging", () => {
372380 text : null ,
373381 } ,
374382 ] ,
375- subject : "Bestellbestätigung " ,
383+ subject : "Test Subject " ,
376384 } ,
377385 } ;
378386
379387 const result = await preparePayload ( payload ) ;
380388
381- // Should convert attachments to an empty array since the format is incorrect
389+ // Empty attachment objects should be filtered out
382390 expect ( result . message . attachments ) . toEqual ( [ ] ) ;
383- expect ( result . message . subject ) . toBe ( "Bestellbestätigung " ) ;
384- expect ( result . to ) . toEqual ( [ "tester@gmx.at " ] ) ;
391+ expect ( result . message . subject ) . toBe ( "Template Subject " ) ;
392+ expect ( result . to ) . toEqual ( [ "test@example.com " ] ) ;
385393 } ) ;
386394
387395 describe ( "attachment validation" , ( ) => {
388- it ( "should handle non-array attachments" , async ( ) => {
396+ it ( "should throw clear error for string attachments" , async ( ) => {
389397 const payload = {
390398 to : "test@example.com" ,
391399 message : {
@@ -395,7 +403,30 @@ describe("preparePayload Template Merging", () => {
395403 } ,
396404 } ;
397405
398- await expect ( preparePayload ( payload ) ) . rejects . toThrow ( ) ;
406+ await expect ( preparePayload ( payload ) ) . rejects . toThrow (
407+ "Invalid message configuration: Field 'message.attachments' must be an array"
408+ ) ;
409+ } ) ;
410+
411+ it ( "should throw clear error for invalid attachment httpHeaders" , async ( ) => {
412+ const payload = {
413+ to : "test@example.com" ,
414+ message : {
415+ subject : "Test Subject" ,
416+ text : "Test text" ,
417+ attachments : [
418+ {
419+ filename : "test.txt" ,
420+ href : "https://example.com" ,
421+ httpHeaders : "invalid" ,
422+ } ,
423+ ] ,
424+ } ,
425+ } ;
426+
427+ await expect ( preparePayload ( payload ) ) . rejects . toThrow (
428+ "Invalid message configuration: Field 'message.attachments.0.httpHeaders' must be a map"
429+ ) ;
399430 } ) ;
400431
401432 it ( "should handle null attachments as no attachments" , async ( ) => {
@@ -456,4 +487,51 @@ describe("preparePayload Template Merging", () => {
456487 expect ( result . message . attachments ) . toEqual ( [ ] ) ;
457488 } ) ;
458489 } ) ;
490+
491+ describe ( "template-rendered attachments" , ( ) => {
492+ it ( "should normalize template-returned attachment object to array" , async ( ) => {
493+ // This tests the exact scenario from issue #2550 where a template
494+ // returns attachments as an object instead of an array
495+ const payload = {
496+ to : "test@example.com" ,
497+ template : {
498+ name : "template-with-object-attachment" ,
499+ data : { } ,
500+ } ,
501+ } ;
502+
503+ const result = await preparePayload ( payload ) ;
504+ expect ( result . message . attachments ) . toEqual ( [ { filename : "report.pdf" } ] ) ;
505+ } ) ;
506+
507+ it ( "should handle template-returned null attachments" , async ( ) => {
508+ const payload = {
509+ to : "test@example.com" ,
510+ template : {
511+ name : "template-with-null-attachments" ,
512+ data : { } ,
513+ } ,
514+ } ;
515+
516+ const result = await preparePayload ( payload ) ;
517+ expect ( result . message . attachments ) . toEqual ( [ ] ) ;
518+ } ) ;
519+
520+ it ( "should process template-only payload without message field" , async ( ) => {
521+ // Matches the user's payload structure - template only, no message field
522+ const payload = {
523+ to : "test@example.com" ,
524+ template : {
525+ name : "html-only-template" ,
526+ data : {
527+ someField : "value" ,
528+ } ,
529+ } ,
530+ } ;
531+
532+ const result = await preparePayload ( payload ) ;
533+ expect ( result . message . html ) . toBe ( "<h1>Template HTML</h1>" ) ;
534+ expect ( result . message . subject ) . toBe ( "Template Subject" ) ;
535+ } ) ;
536+ } ) ;
459537} ) ;
0 commit comments