package email import ( "bytes" "context" "errors" "strings" "testing" ) func TestMockSendEmailCompatibility(t *testing.T) { t.Parallel() called := false service := NewMockMailService(func(params ...interface{}) { called = true if len(params) != 1 || params[0] != "marker" { t.Fatalf("unexpected callback params: %#v", params) } }, "marker") err := service.SendEmail(MessageWithAttachments{ To: "test@example.com", Subject: "Test Email", Body: "This is a test email.", }) if err != nil { t.Fatalf("SendEmail: %v", err) } if !called { t.Fatal("mock callback was not invoked") } } func TestStructuredMessageRejectsHeaderInjection(t *testing.T) { t.Parallel() service := NewMockMailService(nil) tests := []MessageWithAttachments{ {To: "victim@example.com", Subject: "subject\r\nBcc: attacker@example.com", Body: "body"}, {To: "victim@example.com\r\nBcc: attacker@example.com", Subject: "subject", Body: "body"}, { To: "victim@example.com", Subject: "subject", Body: "body", Attachments: []EmailAttachment{{ Title: "file.txt\r\nX-Injected: yes", File: bytes.NewBufferString("content"), }}, }, } for index, message := range tests { if err := service.SendEmailContext(context.Background(), message); err == nil { t.Fatalf("case %d accepted injected header", index) } } } func TestLegacyConstructorReturnsSafeConfigurationError(t *testing.T) { t.Parallel() service := NewSecure(SecureConfig{Host: "", Port: "587", From: "sender@example.com"}) err := service.SendRaw(RawMessage{To: "recipient@example.com", Body: "Subject: x\r\n\r\nbody"}) var transportError *Error if !errors.As(err, &transportError) || transportError.Kind != ErrorKindPermanent { t.Fatalf("error=%v, want permanent configuration error", err) } if strings.Contains(err.Error(), "recipient@example.com") { t.Fatalf("configuration error leaked message data: %v", err) } }