maddy

Fork https://github.com/foxcpp/maddy

git clone git://git.lin.moe/go/maddy.git

  1/*
  2Maddy Mail Server - Composable all-in-one email server.
  3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
  4
  5This program is free software: you can redistribute it and/or modify
  6it under the terms of the GNU General Public License as published by
  7the Free Software Foundation, either version 3 of the License, or
  8(at your option) any later version.
  9
 10This program is distributed in the hope that it will be useful,
 11but WITHOUT ANY WARRANTY; without even the implied warranty of
 12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13GNU General Public License for more details.
 14
 15You should have received a copy of the GNU General Public License
 16along with this program.  If not, see <https://www.gnu.org/licenses/>.
 17*/
 18
 19package msgpipeline
 20
 21import (
 22	"errors"
 23	"testing"
 24
 25	"github.com/foxcpp/maddy/framework/module"
 26	"github.com/foxcpp/maddy/internal/modify"
 27	"github.com/foxcpp/maddy/internal/testutils"
 28)
 29
 30func TestMsgPipeline_SenderModifier(t *testing.T) {
 31	target := testutils.Target{}
 32	modifier := testutils.Modifier{
 33		InstName: "test_modifier",
 34		MailFrom: map[string]string{
 35			"sender@example.com": "sender2@example.com",
 36		},
 37	}
 38	d := MsgPipeline{
 39		msgpipelineCfg: msgpipelineCfg{
 40			globalModifiers: modify.Group{
 41				Modifiers: []module.Modifier{modifier},
 42			},
 43			perSource: map[string]sourceBlock{},
 44			defaultSource: sourceBlock{
 45				perRcpt: map[string]*rcptBlock{},
 46				defaultRcpt: &rcptBlock{
 47					targets: []module.DeliveryTarget{&target},
 48				},
 49			},
 50		},
 51		Log: testutils.Logger(t, "msgpipeline"),
 52	}
 53
 54	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
 55
 56	if len(target.Messages) != 1 {
 57		t.Fatalf("wrong amount of messages received, want %d, got %d", 1, len(target.Messages))
 58	}
 59
 60	testutils.CheckTestMessage(t, &target, 0, "sender2@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
 61
 62	if modifier.UnclosedStates != 0 {
 63		t.Fatalf("modifier state objects leak or double-closed, counter: %d", modifier.UnclosedStates)
 64	}
 65}
 66
 67func TestMsgPipeline_SenderModifier_Multiple(t *testing.T) {
 68	target := testutils.Target{}
 69	mod1, mod2 := testutils.Modifier{
 70		InstName: "first_modifier",
 71		MailFrom: map[string]string{
 72			"sender@example.com": "sender2@example.com",
 73		},
 74	}, testutils.Modifier{
 75		InstName: "second_modifier",
 76		MailFrom: map[string]string{
 77			"sender2@example.com": "sender3@example.com",
 78		},
 79	}
 80	d := MsgPipeline{
 81		msgpipelineCfg: msgpipelineCfg{
 82			globalModifiers: modify.Group{
 83				Modifiers: []module.Modifier{mod1, mod2},
 84			},
 85			perSource: map[string]sourceBlock{},
 86			defaultSource: sourceBlock{
 87				perRcpt: map[string]*rcptBlock{},
 88				defaultRcpt: &rcptBlock{
 89					targets: []module.DeliveryTarget{&target},
 90				},
 91			},
 92		},
 93		Log: testutils.Logger(t, "msgpipeline"),
 94	}
 95
 96	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
 97
 98	if len(target.Messages) != 1 {
 99		t.Fatalf("wrong amount of messages received, want %d, got %d", 1, len(target.Messages))
100	}
101
102	testutils.CheckTestMessage(t, &target, 0, "sender3@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
103
104	if mod1.UnclosedStates != 0 || mod2.UnclosedStates != 0 {
105		t.Fatalf("modifier state objects leak or double-closed, counter: %d, %d", mod1.UnclosedStates, mod2.UnclosedStates)
106	}
107}
108
109func TestMsgPipeline_SenderModifier_PreDispatch(t *testing.T) {
110	target := testutils.Target{InstName: "target"}
111	mod := testutils.Modifier{
112		InstName: "test_modifier",
113		MailFrom: map[string]string{
114			"sender@example.com": "sender@example.org",
115		},
116	}
117	d := MsgPipeline{
118		msgpipelineCfg: msgpipelineCfg{
119			globalModifiers: modify.Group{
120				Modifiers: []module.Modifier{mod},
121			},
122			perSource: map[string]sourceBlock{
123				"example.org": {
124					perRcpt: map[string]*rcptBlock{},
125					defaultRcpt: &rcptBlock{
126						targets: []module.DeliveryTarget{&target},
127					},
128				},
129			},
130			defaultSource: sourceBlock{rejectErr: errors.New("default src block used")},
131		},
132		Log: testutils.Logger(t, "msgpipeline"),
133	}
134
135	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
136
137	if len(target.Messages) != 1 {
138		t.Fatalf("wrong amount of messages received for target, want %d, got %d", 1, len(target.Messages))
139	}
140	testutils.CheckTestMessage(t, &target, 0, "sender@example.org", []string{"rcpt1@example.com", "rcpt2@example.com"})
141
142	if mod.UnclosedStates != 0 {
143		t.Fatalf("modifier state objects leak or double-closed, counter: %d", mod.UnclosedStates)
144	}
145}
146
147func TestMsgPipeline_SenderModifier_PostDispatch(t *testing.T) {
148	target := testutils.Target{InstName: "target"}
149	mod := testutils.Modifier{
150		InstName: "test_modifier",
151		MailFrom: map[string]string{
152			"sender@example.org": "sender@example.com",
153		},
154	}
155	d := MsgPipeline{
156		msgpipelineCfg: msgpipelineCfg{
157			perSource: map[string]sourceBlock{
158				"example.org": {
159					modifiers: modify.Group{
160						Modifiers: []module.Modifier{mod},
161					},
162					perRcpt: map[string]*rcptBlock{},
163					defaultRcpt: &rcptBlock{
164						targets: []module.DeliveryTarget{&target},
165					},
166				},
167			},
168			defaultSource: sourceBlock{rejectErr: errors.New("default src block used")},
169		},
170		Log: testutils.Logger(t, "msgpipeline"),
171	}
172
173	testutils.DoTestDelivery(t, &d, "sender@example.org", []string{"rcpt1@example.com", "rcpt2@example.com"})
174
175	if len(target.Messages) != 1 {
176		t.Fatalf("wrong amount of messages received for target, want %d, got %d", 1, len(target.Messages))
177	}
178	testutils.CheckTestMessage(t, &target, 0, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
179
180	if mod.UnclosedStates != 0 {
181		t.Fatalf("modifier state objects leak or double-closed, counter: %d", mod.UnclosedStates)
182	}
183}
184
185func TestMsgPipeline_SenderModifier_PerRcpt(t *testing.T) {
186	// Modifier below will be no-op due to implementation limitations.
187
188	comTarget, orgTarget := testutils.Target{InstName: "com_target"}, testutils.Target{InstName: "org_target"}
189	mod := testutils.Modifier{
190		InstName: "test_modifier",
191		MailFrom: map[string]string{
192			"sender@example.com": "sender2@example.com",
193		},
194	}
195	d := MsgPipeline{
196		msgpipelineCfg: msgpipelineCfg{
197			perSource: map[string]sourceBlock{},
198			defaultSource: sourceBlock{
199				perRcpt: map[string]*rcptBlock{
200					"example.com": {
201						modifiers: modify.Group{
202							Modifiers: []module.Modifier{mod},
203						},
204						targets: []module.DeliveryTarget{&comTarget},
205					},
206					"example.org": {
207						modifiers: modify.Group{},
208						targets:   []module.DeliveryTarget{&orgTarget},
209					},
210				},
211			},
212		},
213		Log: testutils.Logger(t, "msgpipeline"),
214	}
215
216	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt@example.com", "rcpt@example.org"})
217
218	if len(comTarget.Messages) != 1 {
219		t.Fatalf("wrong amount of messages received for comTarget, want %d, got %d", 1, len(comTarget.Messages))
220	}
221	testutils.CheckTestMessage(t, &comTarget, 0, "sender@example.com", []string{"rcpt@example.com"})
222
223	if len(orgTarget.Messages) != 1 {
224		t.Fatalf("wrong amount of messages received for orgTarget, want %d, got %d", 1, len(orgTarget.Messages))
225	}
226	testutils.CheckTestMessage(t, &orgTarget, 0, "sender@example.com", []string{"rcpt@example.org"})
227
228	if mod.UnclosedStates != 0 {
229		t.Fatalf("modifier state objects leak or double-closed, counter: %d", mod.UnclosedStates)
230	}
231}
232
233func TestMsgPipeline_RcptModifier(t *testing.T) {
234	target := testutils.Target{}
235	mod := testutils.Modifier{
236		InstName: "test_modifier",
237		RcptTo: map[string][]string{
238			"rcpt1@example.com": []string{"rcpt1-alias@example.com"},
239			"rcpt2@example.com": []string{"rcpt2-alias@example.com"},
240		},
241	}
242	d := MsgPipeline{
243		msgpipelineCfg: msgpipelineCfg{
244			globalModifiers: modify.Group{
245				Modifiers: []module.Modifier{mod},
246			},
247			perSource: map[string]sourceBlock{},
248			defaultSource: sourceBlock{
249				perRcpt: map[string]*rcptBlock{},
250				defaultRcpt: &rcptBlock{
251					targets: []module.DeliveryTarget{&target},
252				},
253			},
254		},
255		Log: testutils.Logger(t, "msgpipeline"),
256	}
257
258	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
259
260	if len(target.Messages) != 1 {
261		t.Fatalf("wrong amount of messages received, want %d, got %d", 1, len(target.Messages))
262	}
263
264	testutils.CheckTestMessage(t, &target, 0, "sender@example.com", []string{"rcpt1-alias@example.com", "rcpt2-alias@example.com"})
265
266	if mod.UnclosedStates != 0 {
267		t.Fatalf("modifier state objects leak or double-closed, counter: %d", mod.UnclosedStates)
268	}
269}
270
271func TestMsgPipeline_RcptModifier_OriginalRcpt(t *testing.T) {
272	target := testutils.Target{}
273	mod := testutils.Modifier{
274		InstName: "test_modifier",
275		RcptTo: map[string][]string{
276			"rcpt1@example.com": []string{"rcpt1-alias@example.com"},
277			"rcpt2@example.com": []string{"rcpt2-alias@example.com"},
278		},
279	}
280	d := MsgPipeline{
281		msgpipelineCfg: msgpipelineCfg{
282			globalModifiers: modify.Group{
283				Modifiers: []module.Modifier{mod},
284			},
285			perSource: map[string]sourceBlock{},
286			defaultSource: sourceBlock{
287				perRcpt: map[string]*rcptBlock{},
288				defaultRcpt: &rcptBlock{
289					targets: []module.DeliveryTarget{&target},
290				},
291			},
292		},
293		Log: testutils.Logger(t, "msgpipeline"),
294	}
295
296	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
297
298	if len(target.Messages) != 1 {
299		t.Fatalf("wrong amount of messages received, want %d, got %d", 1, len(target.Messages))
300	}
301
302	testutils.CheckTestMessage(t, &target, 0, "sender@example.com", []string{"rcpt1-alias@example.com", "rcpt2-alias@example.com"})
303	original1 := target.Messages[0].MsgMeta.OriginalRcpts["rcpt1-alias@example.com"]
304	if original1 != "rcpt1@example.com" {
305		t.Errorf("wrong OriginalRcpts value for first rcpt, want %s, got %s", "rcpt1@example.com", original1)
306	}
307	original2 := target.Messages[0].MsgMeta.OriginalRcpts["rcpt2-alias@example.com"]
308	if original2 != "rcpt2@example.com" {
309		t.Errorf("wrong OriginalRcpts value for first rcpt, want %s, got %s", "rcpt2@example.com", original2)
310	}
311
312	if mod.UnclosedStates != 0 {
313		t.Fatalf("modifier state objects leak or double-closed, counter: %d", mod.UnclosedStates)
314	}
315}
316
317func TestMsgPipeline_RcptModifier_OriginalRcpt_Multiple(t *testing.T) {
318	target := testutils.Target{}
319	mod1, mod2 := testutils.Modifier{
320		InstName: "first_modifier",
321		RcptTo: map[string][]string{
322			"rcpt1@example.com": []string{"rcpt1-alias@example.com"},
323			"rcpt2@example.com": []string{"rcpt2-alias@example.com"},
324		},
325	}, testutils.Modifier{
326		InstName: "second_modifier",
327		RcptTo: map[string][]string{
328			"rcpt1-alias@example.com": []string{"rcpt1-alias2@example.com"},
329			"rcpt2@example.com":       []string{"wtf@example.com"},
330		},
331	}
332	d := MsgPipeline{
333		msgpipelineCfg: msgpipelineCfg{
334			globalModifiers: modify.Group{
335				Modifiers: []module.Modifier{mod1},
336			},
337			perSource: map[string]sourceBlock{},
338			defaultSource: sourceBlock{
339				modifiers: modify.Group{
340					Modifiers: []module.Modifier{mod2},
341				},
342				perRcpt: map[string]*rcptBlock{},
343				defaultRcpt: &rcptBlock{
344					targets: []module.DeliveryTarget{&target},
345				},
346			},
347		},
348		Log: testutils.Logger(t, "msgpipeline"),
349	}
350
351	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
352
353	if len(target.Messages) != 1 {
354		t.Fatalf("wrong amount of messages received, want %d, got %d", 1, len(target.Messages))
355	}
356
357	testutils.CheckTestMessage(t, &target, 0, "sender@example.com", []string{"rcpt1-alias2@example.com", "rcpt2-alias@example.com"})
358	original1 := target.Messages[0].MsgMeta.OriginalRcpts["rcpt1-alias2@example.com"]
359	if original1 != "rcpt1@example.com" {
360		t.Errorf("wrong OriginalRcpts value for first rcpt, want %s, got %s", "rcpt1@example.com", original1)
361	}
362	original2 := target.Messages[0].MsgMeta.OriginalRcpts["rcpt2-alias@example.com"]
363	if original2 != "rcpt2@example.com" {
364		t.Errorf("wrong OriginalRcpts value for first rcpt, want %s, got %s", "rcpt2@example.com", original2)
365	}
366
367	if mod1.UnclosedStates != 0 || mod2.UnclosedStates != 0 {
368		t.Fatalf("modifier state objects leak or double-closed, counter: %d, %d", mod1.UnclosedStates, mod2.UnclosedStates)
369	}
370}
371
372func TestMsgPipeline_RcptModifier_Multiple(t *testing.T) {
373	target := testutils.Target{}
374	mod1, mod2 := testutils.Modifier{
375		InstName: "first_modifier",
376		RcptTo: map[string][]string{
377			"rcpt1@example.com": []string{"rcpt1-alias@example.com"},
378			"rcpt2@example.com": []string{"rcpt2-alias@example.com"},
379		},
380	}, testutils.Modifier{
381		InstName: "second_modifier",
382		RcptTo: map[string][]string{
383			"rcpt1-alias@example.com": []string{"rcpt1-alias2@example.com"},
384			"rcpt2@example.com":       []string{"wtf@example.com"},
385		},
386	}
387	d := MsgPipeline{
388		msgpipelineCfg: msgpipelineCfg{
389			globalModifiers: modify.Group{
390				Modifiers: []module.Modifier{mod1, mod2},
391			},
392			perSource: map[string]sourceBlock{},
393			defaultSource: sourceBlock{
394				perRcpt: map[string]*rcptBlock{},
395				defaultRcpt: &rcptBlock{
396					targets: []module.DeliveryTarget{&target},
397				},
398			},
399		},
400		Log: testutils.Logger(t, "msgpipeline"),
401	}
402
403	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
404
405	if len(target.Messages) != 1 {
406		t.Fatalf("wrong amount of messages received, want %d, got %d", 1, len(target.Messages))
407	}
408
409	testutils.CheckTestMessage(t, &target, 0, "sender@example.com", []string{"rcpt1-alias2@example.com", "rcpt2-alias@example.com"})
410
411	if mod1.UnclosedStates != 0 || mod2.UnclosedStates != 0 {
412		t.Fatalf("modifier state objects leak or double-closed, counter: %d, %d", mod1.UnclosedStates, mod2.UnclosedStates)
413	}
414}
415
416func TestMsgPipeline_RcptModifier_PreDispatch(t *testing.T) {
417	target := testutils.Target{}
418	mod1, mod2 := testutils.Modifier{
419		InstName: "first_modifier",
420		RcptTo: map[string][]string{
421			"rcpt1@example.com": []string{"rcpt1-alias@example.com"},
422			"rcpt2@example.com": []string{"rcpt2-alias@example.com"},
423		},
424	}, testutils.Modifier{
425		InstName: "second_modifier",
426		RcptTo: map[string][]string{
427			"rcpt1-alias@example.com": []string{"rcpt1-alias2@example.com"},
428			"rcpt2@example.com":       []string{"wtf@example.com"},
429		},
430	}
431	d := MsgPipeline{
432		msgpipelineCfg: msgpipelineCfg{
433			globalModifiers: modify.Group{
434				Modifiers: []module.Modifier{mod1},
435			},
436			perSource: map[string]sourceBlock{},
437			defaultSource: sourceBlock{
438				modifiers: modify.Group{Modifiers: []module.Modifier{mod2}},
439				perRcpt: map[string]*rcptBlock{
440					"rcpt2-alias@example.com": {
441						targets: []module.DeliveryTarget{&target},
442					},
443					"rcpt1-alias2@example.com": {
444						targets: []module.DeliveryTarget{&target},
445					},
446				},
447				defaultRcpt: &rcptBlock{
448					rejectErr: errors.New("default rcpt is used"),
449				},
450			},
451		},
452		Log: testutils.Logger(t, "msgpipeline"),
453	}
454
455	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
456
457	if len(target.Messages) != 1 {
458		t.Fatalf("wrong amount of messages received, want %d, got %d", 1, len(target.Messages))
459	}
460
461	testutils.CheckTestMessage(t, &target, 0, "sender@example.com", []string{"rcpt1-alias2@example.com", "rcpt2-alias@example.com"})
462
463	if mod1.UnclosedStates != 0 || mod2.UnclosedStates != 0 {
464		t.Fatalf("modifier state objects leak or double-closed, counter: %d, %d", mod1.UnclosedStates, mod2.UnclosedStates)
465	}
466}
467
468func TestMsgPipeline_RcptModifier_PostDispatch(t *testing.T) {
469	target := testutils.Target{}
470	mod := testutils.Modifier{
471		InstName: "test_modifier",
472		RcptTo: map[string][]string{
473			"rcpt1@example.com": []string{"rcpt1@example.org"},
474			"rcpt2@example.com": []string{"rcpt2@example.org"},
475		},
476	}
477	d := MsgPipeline{
478		msgpipelineCfg: msgpipelineCfg{
479			perSource: map[string]sourceBlock{},
480			defaultSource: sourceBlock{
481				perRcpt: map[string]*rcptBlock{
482					"example.com": {
483						modifiers: modify.Group{
484							Modifiers: []module.Modifier{mod},
485						},
486						targets: []module.DeliveryTarget{&target},
487					},
488					"example.org": {
489						rejectErr: errors.New("wrong rcpt block is used"),
490					},
491				},
492				defaultRcpt: &rcptBlock{
493					rejectErr: errors.New("default rcpt is used"),
494				},
495			},
496		},
497		Log: testutils.Logger(t, "msgpipeline"),
498	}
499
500	testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
501
502	if len(target.Messages) != 1 {
503		t.Fatalf("wrong amount of messages received, want %d, got %d", 1, len(target.Messages))
504	}
505
506	testutils.CheckTestMessage(t, &target, 0, "sender@example.com", []string{"rcpt1@example.org", "rcpt2@example.org"})
507
508	if mod.UnclosedStates != 0 {
509		t.Fatalf("modifier state objects leak or double-closed, counter: %d", mod.UnclosedStates)
510	}
511}
512
513func TestMsgPipeline_GlobalModifier_Errors(t *testing.T) {
514	target := testutils.Target{}
515	mod := testutils.Modifier{
516		InstName:    "test_modifier",
517		InitErr:     errors.New("1"),
518		MailFromErr: errors.New("2"),
519		RcptToErr:   errors.New("3"),
520		BodyErr:     errors.New("4"),
521	}
522	d := MsgPipeline{
523		msgpipelineCfg: msgpipelineCfg{
524			globalModifiers: modify.Group{Modifiers: []module.Modifier{&mod}},
525			perSource:       map[string]sourceBlock{},
526			defaultSource: sourceBlock{
527				defaultRcpt: &rcptBlock{
528					targets: []module.DeliveryTarget{&target},
529				},
530			},
531		},
532		Log: testutils.Logger(t, "msgpipeline"),
533	}
534
535	t.Run("init err", func(t *testing.T) {
536		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
537		if err == nil {
538			t.Fatal("expected error")
539		}
540	})
541
542	mod.InitErr = nil
543
544	t.Run("mail from err", func(t *testing.T) {
545		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
546		if err == nil {
547			t.Fatal("expected error")
548		}
549	})
550
551	mod.MailFromErr = nil
552
553	t.Run("rcpt to err", func(t *testing.T) {
554		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
555		if err == nil {
556			t.Fatal("expected error")
557		}
558	})
559
560	mod.RcptToErr = nil
561
562	t.Run("body err", func(t *testing.T) {
563		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
564		if err == nil {
565			t.Fatal("expected error")
566		}
567	})
568
569	mod.BodyErr = nil
570
571	t.Run("no err", func(t *testing.T) {
572		testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
573	})
574
575	if mod.UnclosedStates != 0 {
576		t.Fatalf("modifier state objects leak or double-closed, counter: %d", mod.UnclosedStates)
577	}
578}
579
580func TestMsgPipeline_SourceModifier_Errors(t *testing.T) {
581	target := testutils.Target{}
582	mod := testutils.Modifier{
583		InstName:    "test_modifier",
584		InitErr:     errors.New("1"),
585		MailFromErr: errors.New("2"),
586		RcptToErr:   errors.New("3"),
587		BodyErr:     errors.New("4"),
588	}
589	// Added to make sure it is freed properly too.
590	globalMod := testutils.Modifier{}
591	d := MsgPipeline{
592		msgpipelineCfg: msgpipelineCfg{
593			perSource:       map[string]sourceBlock{},
594			globalModifiers: modify.Group{Modifiers: []module.Modifier{&globalMod}},
595			defaultSource: sourceBlock{
596				modifiers: modify.Group{Modifiers: []module.Modifier{&mod}},
597				defaultRcpt: &rcptBlock{
598					targets: []module.DeliveryTarget{&target},
599				},
600			},
601		},
602		Log: testutils.Logger(t, "msgpipeline"),
603	}
604
605	t.Run("init err", func(t *testing.T) {
606		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
607		if err == nil {
608			t.Fatal("expected error")
609		}
610	})
611
612	mod.InitErr = nil
613
614	t.Run("mail from err", func(t *testing.T) {
615		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
616		if err == nil {
617			t.Fatal("expected error")
618		}
619	})
620
621	mod.MailFromErr = nil
622
623	t.Run("rcpt to err", func(t *testing.T) {
624		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
625		if err == nil {
626			t.Fatal("expected error")
627		}
628	})
629
630	mod.RcptToErr = nil
631
632	t.Run("body err", func(t *testing.T) {
633		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
634		if err == nil {
635			t.Fatal("expected error")
636		}
637	})
638
639	mod.BodyErr = nil
640
641	t.Run("no err", func(t *testing.T) {
642		testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
643	})
644
645	if mod.UnclosedStates != 0 || globalMod.UnclosedStates != 0 {
646		t.Fatalf("modifier state objects leak or double-closed, counters: %d, %d",
647			mod.UnclosedStates, globalMod.UnclosedStates)
648	}
649}
650
651func TestMsgPipeline_RcptModifier_Errors(t *testing.T) {
652	target := testutils.Target{}
653	mod := testutils.Modifier{
654		InstName:  "test_modifier",
655		InitErr:   errors.New("1"),
656		RcptToErr: errors.New("3"),
657	}
658	// Added to make sure it is freed properly too.
659	globalMod := testutils.Modifier{}
660	sourceMod := testutils.Modifier{}
661
662	d := MsgPipeline{
663		msgpipelineCfg: msgpipelineCfg{
664			perSource:       map[string]sourceBlock{},
665			globalModifiers: modify.Group{Modifiers: []module.Modifier{&globalMod}},
666			defaultSource: sourceBlock{
667				modifiers: modify.Group{Modifiers: []module.Modifier{&sourceMod}},
668				defaultRcpt: &rcptBlock{
669					modifiers: modify.Group{Modifiers: []module.Modifier{&mod}},
670					targets:   []module.DeliveryTarget{&target},
671				},
672			},
673		},
674		Log: testutils.Logger(t, "msgpipeline"),
675	}
676
677	t.Run("init err", func(t *testing.T) {
678		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
679		if err == nil {
680			t.Fatal("expected error")
681		}
682	})
683
684	mod.InitErr = nil
685
686	// MailFromErr test is inapplicable since RewriteSender is not called for per-rcpt
687	// modifiers.
688
689	t.Run("rcpt to err", func(t *testing.T) {
690		_, err := testutils.DoTestDeliveryErr(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
691		if err == nil {
692			t.Fatal("expected error")
693		}
694	})
695
696	mod.RcptToErr = nil
697
698	// BodyErr test is inapplicable since RewriteBody is not called for per-rcpt
699	// modifiers.
700
701	t.Run("no err", func(t *testing.T) {
702		testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt1@example.com", "rcpt2@example.com"})
703	})
704
705	if mod.UnclosedStates != 0 || globalMod.UnclosedStates != 0 || sourceMod.UnclosedStates != 0 {
706		t.Fatalf("modifier state objects leak or double-closed, counters: %d, %d, %d",
707			mod.UnclosedStates, globalMod.UnclosedStates, sourceMod.UnclosedStates)
708	}
709}