Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions broker/scheduler/service/email_sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type pullslipEmailData struct {
To []string `json:"to"`
TemplateLabel string `json:"templateLabel"`
IncludePdf bool `json:"includePdf"`
SendEmpty bool `json:"sendEmpty"`
}

type EmailSenderService struct {
Expand Down Expand Up @@ -119,10 +120,14 @@ func (s *EmailSenderService) generateAndEmailPullslip(ctx common.ExtendedContext
ctx.Logger().Warn("email batch truncated: selector matched more records than the per-email limit",
"matched", fullCount, "limit", MAX_RECORDS_PER_EMAIL)
}
if len(prs) == 0 && !emailData.SendEmpty {
return events.EventStatusSuccess, &events.EventResult{CommonEventData: events.CommonEventData{Note: "no patron requests matched the selector"}}
}

// Optionally generate a pull-slip PDF and attach it.
var pdfAttachment *email.PdfAttach
if emailData.IncludePdf {
includePdf := emailData.IncludePdf && len(prs) > 0
if includePdf {
if s.pdf == nil {
return events.NewErrorResult("pdf not configured", "no PDF generator is available on this service instance")
}
Expand Down Expand Up @@ -152,7 +157,7 @@ func (s *EmailSenderService) generateAndEmailPullslip(ctx common.ExtendedContext
Subject: subject,
Body: body,
IsHTML: template.ContentType == string(proapi.Html),
IncludePdf: emailData.IncludePdf,
IncludePdf: includePdf,
}

raw, err := email.BuildRawMessage(*owner.CustomData.FromEmail, messageData, pdfAttachment)
Expand Down Expand Up @@ -195,10 +200,12 @@ func extractEmailData(eventData events.EventData) (pullslipEmailData, error) {

templateLabel, _ := eventData.CustomData["templateLabel"].(string)
includePdf, _ := eventData.CustomData["includePdf"].(bool)
sendEmpty, _ := eventData.CustomData["sendEmpty"].(bool)

return pullslipEmailData{
To: toAddrs,
TemplateLabel: templateLabel,
IncludePdf: includePdf,
SendEmpty: sendEmpty,
}, nil
}
94 changes: 66 additions & 28 deletions broker/scheduler/service/email_sender_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,15 +324,18 @@ func TestGenerateAndEmailPullslip_TemplateEmptySubject(t *testing.T) {
}

func TestGenerateAndEmailPullslip_TemplateInvalidBody(t *testing.T) {
prRepo := &mockEmailPrRepo{template: pr_db.Template{
ID: "template-id",
Subject: pgtype.Text{
Valid: true,
String: "Subject",
prRepo := &mockEmailPrRepo{
template: pr_db.Template{
ID: "template-id",
Subject: pgtype.Text{
Valid: true,
String: "Subject",
},
Body: "Body {{.Invalid text",
ContentType: "text",
},
Body: "Body {{.Invalid text",
ContentType: "text",
}}
listResult: []pr_db.PatronRequest{{ID: "pr-1"}},
}
svc := newEmailSvc(prRepo, &mockEmailService{}, nil)
status, result := svc.generateAndEmailPullslip(testCtx, validEmailEvent())
assert.Equal(t, events.EventStatusError, status)
Expand All @@ -341,15 +344,18 @@ func TestGenerateAndEmailPullslip_TemplateInvalidBody(t *testing.T) {
}

func TestGenerateAndEmailPullslip_TemplateInvalidSubject(t *testing.T) {
prRepo := &mockEmailPrRepo{template: pr_db.Template{
ID: "template-id",
Subject: pgtype.Text{
Valid: true,
String: "Subject {{.Invalid text",
prRepo := &mockEmailPrRepo{
template: pr_db.Template{
ID: "template-id",
Subject: pgtype.Text{
Valid: true,
String: "Subject {{.Invalid text",
},
Body: "Body",
ContentType: "text",
},
Body: "Body",
ContentType: "text",
}}
listResult: []pr_db.PatronRequest{{ID: "pr-1"}},
}
svc := newEmailSvc(prRepo, &mockEmailService{}, nil)
status, result := svc.generateAndEmailPullslip(testCtx, validEmailEvent())
assert.Equal(t, events.EventStatusError, status)
Expand Down Expand Up @@ -379,7 +385,7 @@ func TestGenerateAndEmailPullslip_ListPatronRequestsError(t *testing.T) {
}

func TestGenerateAndEmailPullslip_SMTPError(t *testing.T) {
prRepo := &mockEmailPrRepo{listResult: []pr_db.PatronRequest{}}
prRepo := &mockEmailPrRepo{listResult: []pr_db.PatronRequest{{ID: "pr-1"}}}
mailer := &mockEmailService{err: errors.New("SMTP unavailable")}
svc := newEmailSvc(prRepo, mailer, nil)
status, result := svc.generateAndEmailPullslip(testCtx, validEmailEvent())
Expand All @@ -388,10 +394,40 @@ func TestGenerateAndEmailPullslip_SMTPError(t *testing.T) {
assert.True(t, mailer.called)
}

func TestGenerateAndEmailPullslip_Success(t *testing.T) {
func TestGenerateAndEmailPullslip_SuccessNoEmail(t *testing.T) {
prRepo := &mockEmailPrRepo{listResult: []pr_db.PatronRequest{}}
mailer := &mockEmailService{}
svc := newEmailSvc(prRepo, mailer, nil)
status, result := svc.generateAndEmailPullslip(testCtx, validEmailEvent())
assert.Equal(t, events.EventStatusSuccess, status)
assert.Equal(t, "no patron requests matched the selector", result.Note)
assert.False(t, mailer.called)
}

func TestGenerateAndEmailPullslip_SuccessEmailWithZero(t *testing.T) {
prRepo := &mockEmailPrRepo{listResult: []pr_db.PatronRequest{}}
mailer := &mockEmailService{}
svc := newEmailSvc(prRepo, mailer, nil)
event := validEmailEvent()
event.EventData.CustomData["sendEmpty"] = true
status, result := svc.generateAndEmailPullslip(testCtx, event)
assert.Equal(t, events.EventStatusSuccess, status)
assert.Nil(t, result)
assert.True(t, mailer.called)

assert.True(t, strings.Contains(string(mailer.data), "user@example.com"))
assert.Equal(t, pr_db.GetTemplateByPurposeAudienceLabelAndOwnerParams{
Owner: "ISIL:OWNER",
Purpose: "email",
Label: "pullslips",
Audience: "staff",
}, prRepo.gotTemplate)
}

func TestGenerateAndEmailPullslip_Success(t *testing.T) {
prRepo := &mockEmailPrRepo{listResult: []pr_db.PatronRequest{{ID: "pr-1"}}}
mailer := &mockEmailService{}
svc := newEmailSvc(prRepo, mailer, nil)
status, result := svc.generateAndEmailPullslip(testCtx, validEmailEvent())
assert.Equal(t, events.EventStatusSuccess, status)
assert.Nil(t, result)
Expand Down Expand Up @@ -430,12 +466,14 @@ func TestGenerateAndEmailPullslip_PerformsPlaceholderSubstitution(t *testing.T)
}

func TestGenerateAndEmailPullslip_HtmlTemplate(t *testing.T) {
prRepo := &mockEmailPrRepo{template: pr_db.Template{
ID: "template-id",
Subject: pgtype.Text{String: "Subject", Valid: true},
Body: "<p>Body</p>",
ContentType: "html",
}}
prRepo := &mockEmailPrRepo{
listResult: []pr_db.PatronRequest{{ID: "pr-1"}},
template: pr_db.Template{
ID: "template-id",
Subject: pgtype.Text{String: "Subject", Valid: true},
Body: "<p>Body</p>",
ContentType: "html",
}}
mailer := &mockEmailService{}
svc := newEmailSvc(prRepo, mailer, nil)

Expand All @@ -447,7 +485,7 @@ func TestGenerateAndEmailPullslip_HtmlTemplate(t *testing.T) {
}

func TestGenerateAndEmailPullslip_WithPDF_Success(t *testing.T) {
prRepo := &mockEmailPrRepo{listResult: []pr_db.PatronRequest{}}
prRepo := &mockEmailPrRepo{listResult: []pr_db.PatronRequest{{ID: "pr-1"}}}
mailer := &mockEmailService{}
pdf := &mockPdfGen{data: []byte("%PDF fake")}
svc := newEmailSvc(prRepo, mailer, pdf)
Expand All @@ -463,7 +501,7 @@ func TestGenerateAndEmailPullslip_WithPDF_Success(t *testing.T) {
}

func TestGenerateAndEmailPullslip_WithPDF_NilGenerator(t *testing.T) {
prRepo := &mockEmailPrRepo{listResult: []pr_db.PatronRequest{}}
prRepo := &mockEmailPrRepo{listResult: []pr_db.PatronRequest{{ID: "pr-1"}}}
mailer := &mockEmailService{}
// pdf generator is nil — IncludePdf=true must return an error, not panic.
svc := newEmailSvc(prRepo, mailer, nil)
Expand All @@ -478,7 +516,7 @@ func TestGenerateAndEmailPullslip_WithPDF_NilGenerator(t *testing.T) {
}

func TestGenerateAndEmailPullslip_WithPDF_GenerateError(t *testing.T) {
prRepo := &mockEmailPrRepo{listResult: []pr_db.PatronRequest{}}
prRepo := &mockEmailPrRepo{listResult: []pr_db.PatronRequest{{ID: "pr-1"}}}
mailer := &mockEmailService{}
pdf := &mockPdfGen{err: errors.New("pdf engine failure")}
svc := newEmailSvc(prRepo, mailer, pdf)
Expand All @@ -497,7 +535,7 @@ func TestGenerateAndEmailPullslip_WithPDF_GenerateError(t *testing.T) {
// ---------------------------------------------------------------------------

func TestEmailPullslip_WhenReadyToSend_SendsEmail(t *testing.T) {
prRepo := &mockEmailPrRepo{listResult: []pr_db.PatronRequest{}}
prRepo := &mockEmailPrRepo{listResult: []pr_db.PatronRequest{{ID: "pr-1"}}}
mailer := &mockEmailService{ready: true}
svc := EmailSenderServiceWithClient(prRepo, &mockEmailIllRepo{fromEmail: "from@example.com"}, mailer, nil)

Expand Down