fix(medusa-plugin-sendgrid): Use correct SendGrid client (#4524)

* fix(medusa-plugin-sendgrid): Use correct SendGrid instance

* Create loud-fans-own.md

* Adds tests

* Use logger
This commit is contained in:
Oliver Windall Juhl
2023-07-13 14:23:07 +02:00
committed by GitHub
parent 5affae9583
commit fe6586e560
3 changed files with 224 additions and 23 deletions

View File

@@ -0,0 +1,5 @@
---
"medusa-plugin-sendgrid": patch
---
fix(medusa-plugin-sendgrid): Use correct SendGrid client

View File

@@ -0,0 +1,196 @@
import SendGridService from "../sendgrid"
import SendGrid from "@sendgrid/mail"
jest.genMockFromModule("@sendgrid/mail")
jest.mock("@sendgrid/mail")
const mockedSendGrid = SendGrid
mockedSendGrid.setApiKey.mockResolvedValue(mockedSendGrid)
mockedSendGrid.send.mockResolvedValue(Promise.resolve())
describe("SendGridService", () => {
let sendGridService
const totalsService = {
withTransaction: function () {
return this
},
getCalculationContext: jest.fn().mockImplementation((order, lineItems) => {
return Promise.resolve({})
}),
getLineItemTotals: jest.fn().mockImplementation(() => {
return Promise.resolve({})
}),
getLineItemRefund: () => {},
getTotal: (o) => {
return o.total || 0
},
getGiftCardableAmount: (o) => {
return o.subtotal || 0
},
getRefundedTotal: (o) => {
return o.refunded_total || 0
},
getShippingTotal: (o) => {
return o.shipping_total || 0
},
getGiftCardTotal: (o) => {
return o.gift_card_total || 0
},
getDiscountTotal: (o) => {
return o.discount_total || 0
},
getTaxTotal: (o) => {
return o.tax_total || 0
},
getSubtotal: (o) => {
return o.subtotal || 0
},
getPaidTotal: (o) => {
return o.paid_total || 0
},
}
beforeEach(() => {
jest.clearAllMocks()
})
it("should call SendGrid.send when template is configured and correct data is passed", async () => {
const orderServiceMock = {
retrieve: jest.fn().mockImplementation((data) => {
return Promise.resolve({
email: "test@test.com",
currency_code: "usd",
items: [],
discounts: [],
gift_cards: [],
created_at: new Date(),
})
}),
}
sendGridService = new SendGridService(
{ orderService: orderServiceMock, totalsService },
{
api_key: "SG.test",
order_placed_template: "lol",
}
)
await sendGridService.sendNotification("order.placed", { id: "test" })
expect(mockedSendGrid.send).toBeCalled()
})
it("should failed to send an email when event does not exist", async () => {
expect.assertions(1)
const orderServiceMock = {
retrieve: jest.fn().mockImplementation((data) => {
return Promise.resolve({
email: "test@test.com",
currency_code: "usd",
items: [],
discounts: [],
gift_cards: [],
created_at: new Date(),
})
}),
}
sendGridService = new SendGridService(
{ orderService: orderServiceMock, totalsService },
{
api_key: "SG.test",
order_placed_template: "lol",
}
)
try {
await sendGridService.sendNotification("some.non-existing_event", {
id: "test",
})
} catch (error) {
expect(error.message).toEqual(
"Sendgrid service: No template was set for event: some.non-existing_event"
)
}
})
it("should failed to send an email when template id is not configured", async () => {
expect.assertions(1)
const orderServiceMock = {
retrieve: jest.fn().mockImplementation((data) => {
return Promise.resolve({
email: "test@test.com",
currency_code: "usd",
items: [],
discounts: [],
gift_cards: [],
created_at: new Date(),
})
}),
}
sendGridService = new SendGridService(
{ orderService: orderServiceMock, totalsService },
{
api_key: "SG.test",
}
)
try {
await sendGridService.sendNotification("order.placed", {
id: "test",
})
} catch (error) {
expect(error.message).toEqual(
"Sendgrid service: No template was set for event: order.placed"
)
}
})
it("should use localized template to send an email", async () => {
const cartServiceMock = {
retrieve: jest.fn().mockImplementation((data) => {
return Promise.resolve({
context: {
locale: "de-DE",
},
})
}),
}
const orderServiceMock = {
retrieve: jest.fn().mockImplementation((data) => {
return Promise.resolve({
email: "test@test.com",
currency_code: "usd",
items: [],
discounts: [],
gift_cards: [],
created_at: new Date(),
cart_id: "test-id",
})
}),
}
sendGridService = new SendGridService(
{
orderService: orderServiceMock,
totalsService,
cartService: cartServiceMock,
},
{
api_key: "SG.test",
localization: {
"de-DE": {
order_placed_template: "lol",
},
},
}
)
await sendGridService.sendNotification("order.placed", {
id: "test",
})
expect(mockedSendGrid.send).toBeCalled()
})
})

View File

@@ -34,6 +34,7 @@ class SendGridService extends NotificationService {
totalsService,
productVariantService,
giftCardService,
logger,
},
options
) {
@@ -53,6 +54,7 @@ class SendGridService extends NotificationService {
this.totalsService_ = totalsService
this.productVariantService_ = productVariantService
this.giftCardService_ = giftCardService
this.logger_ = logger
SendGrid.setApiKey(options.api_key)
}
@@ -219,16 +221,20 @@ class SendGridService extends NotificationService {
}
async sendNotification(event, eventData, attachmentGenerator) {
const data = await this.fetchData(event, eventData, attachmentGenerator)
let templateId = this.getTemplateId(event)
if (!templateId) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, `Sendgrid service: No template was set for event: ${event}`)
if (data.locale) {
templateId = this.getLocalizedTemplateId(event, data.locale) || templateId
}
const data = await this.fetchData(event, eventData, attachmentGenerator)
if (!data) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, "Sendgrid service: Invalid event data was received")
}
if (!templateId) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Sendgrid service: No template was set for event: ${event}`
)
}
const attachments = await this.fetchAttachments(
event,
@@ -236,10 +242,6 @@ class SendGridService extends NotificationService {
attachmentGenerator
)
if (data.locale) {
templateId = this.getLocalizedTemplateId(event, data.locale) || templateId
}
const sendOptions = {
template_id: templateId,
from: this.options_.from,
@@ -261,10 +263,15 @@ class SendGridService extends NotificationService {
})
}
let status
await this.transporter_.sendMail(sendOptions)
.then(() => { status = "sent" })
.catch((error) => { status = "failed"; console.log(error) })
let status
await SendGrid.send(sendOptions)
.then(() => {
status = "sent"
})
.catch((error) => {
status = "failed"
this.logger_.error(error)
})
// We don't want heavy docs stored in DB
delete sendOptions.attachments
@@ -303,18 +310,11 @@ class SendGridService extends NotificationService {
/**
* Sends an email using SendGrid.
* @param {string} templateId - id of template in SendGrid
* @param {string} from - sender of email
* @param {string} to - receiver of email
* @param {Object} data - data to send in mail (match with template)
* @param {Object} options - send options containing to, from, template, and more. Read more here: https://github.com/sendgrid/sendgrid-nodejs/tree/main/packages/mail
* @return {Promise} result of the send operation
*/
async sendEmail(options) {
try {
return SendGrid.send(options)
} catch (error) {
throw error
}
return await SendGrid.send(options)
}
async orderShipmentCreatedData({ id, fulfillment_id }, attachmentGenerator) {