Skip to content

Commit

Permalink
feat(firestore-send-email): support SendGrid Dynamic Templates (#1892)
Browse files Browse the repository at this point in the history
  • Loading branch information
pr-Mais authored and cabljac committed Mar 21, 2024
1 parent 2836a0c commit ebfe022
Show file tree
Hide file tree
Showing 13 changed files with 520 additions and 186 deletions.
9 changes: 6 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,19 @@ jobs:
node-version: ${{ matrix.node }}
cache: "npm"
cache-dependency-path: "**/package-lock.json"
- name: NPM INSTALL
- name: npm install
run: npm i
- name: build emulator functions
- name: Build emulator functions
run: cd _emulator/functions && npm i && npm run build & cd ../..
- name: Install firebase CLI
- name: Install Firebase CLI
uses: nick-invision/retry@v1
with:
timeout_minutes: 10
retry_wait_seconds: 60
max_attempts: 3
command: npm i -g firebase-tools@11
- name: Setup e2e secrets
run: |
echo SMTP_PASSWORD=${{ secrets.SENDGRID_API_KEY }} >> _emulator/extensions/firestore-send-email-sendgrid.secret.local
- name: npm test
run: npm run test:ci
2 changes: 1 addition & 1 deletion _emulator/.firebaserc
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"projects": {
"default": "demo-test"
}
}
}
6 changes: 6 additions & 0 deletions _emulator/extensions/firestore-send-email-sendgrid.env.local
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
DEFAULT_FROM=test-assertion@email.com
firebaseextensions.v1beta.function/location=us-central1
MAIL_COLLECTION=mail-sg
SMTP_CONNECTION_URI=smtps://apikey@smtp.sendgrid.net:465
TTL_EXPIRE_TYPE=never
TTL_EXPIRE_VALUE=1
Empty file.
3 changes: 2 additions & 1 deletion _emulator/firebase.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"delete-user-data": "../delete-user-data",
"storage-resize-images": "../storage-resize-images",
"firestore-counter": "../firestore-counter",
"firestore-bigquery-export": "../firestore-bigquery-export"
"firestore-bigquery-export": "../firestore-bigquery-export",
"firestore-send-email-sendgrid": "../firestore-send-email"
},
"storage": {
"rules": "storage.rules"
Expand Down
142 changes: 101 additions & 41 deletions firestore-send-email/functions/__tests__/e2e.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import * as admin from "firebase-admin";
import { smtpServer } from "./createSMTPServer";
import { FieldValue } from "firebase-admin/firestore";

// import wait-for-expect
import waitForExpect from "wait-for-expect";
import { firestore } from "firebase-admin";

process.env.FIRESTORE_EMULATOR_HOST = "127.0.0.1:8080";

Expand All @@ -15,6 +10,9 @@ admin.initializeApp({
const mail = "mail";
const mailCollection = admin.firestore().collection(mail);

const mailSg = "mail-sg";
const mailSgCollection = admin.firestore().collection(mailSg);

const templates = "templates";
const templatesCollection = admin.firestore().collection(templates);

Expand All @@ -33,27 +31,24 @@ describe("e2e testing", () => {
},
};

const doc = mailCollection.doc();

let currentSnapshot: firestore.DocumentSnapshot;

const unsubscribe = doc.onSnapshot((snapshot) => {
currentSnapshot = snapshot;
});
const doc = await mailCollection.add(record);

await doc.create(record);

await waitForExpect(() => {
expect(currentSnapshot).toHaveProperty("exists");
expect(currentSnapshot.exists).toBeTruthy();
const currentDocumentData = currentSnapshot.data();
expect(currentDocumentData).toHaveProperty("delivery");
expect(currentDocumentData.delivery).toHaveProperty("info");
expect(currentDocumentData.delivery.info.accepted[0]).toEqual(record.to);
expect(currentDocumentData.delivery.info.response).toContain("250");
unsubscribe();
return new Promise((resolve, reject) => {
const unsubscribe = doc.onSnapshot(async (snapshot) => {
const currentDocumentData = snapshot.data();
if (currentDocumentData.delivery && currentDocumentData.delivery.info) {
expect(currentDocumentData).toHaveProperty("delivery");
expect(currentDocumentData.delivery).toHaveProperty("info");
expect(currentDocumentData.delivery.info.accepted[0]).toEqual(
record.to
);
expect(currentDocumentData.delivery.info.response).toContain("250");
unsubscribe();
resolve();
}
});
});
}, 12000);
});

test("the expireAt field should be added, with value 5 days later than startTime", async (): Promise<void> => {
const record = {
Expand All @@ -66,23 +61,22 @@ describe("e2e testing", () => {
const doc = await mailCollection.add(record);

return new Promise((resolve, reject) => {
const unsubscribe = doc.onSnapshot((snapshot) => {
const document = snapshot.data();

const unsubscribe = doc.onSnapshot(async (snapshot) => {
const currentDocumentData = snapshot.data();
if (
document.delivery &&
document.delivery.info &&
document.delivery.expireAt
currentDocumentData.delivery &&
currentDocumentData.delivery.info &&
currentDocumentData.delivery.expireAt
) {
const startAt = document.delivery.startTime.toDate();
const expireAt = document.delivery.expireAt.toDate();
const startAt = currentDocumentData.delivery.startTime.toDate();
const expireAt = currentDocumentData.delivery.expireAt.toDate();
expect(expireAt.getTime() - startAt.getTime()).toEqual(5 * 86400000);
unsubscribe();
resolve();
}
});
});
}, 12000);
});

test("empty template attachments should default to message attachments", async (): Promise<void> => {
//create template
Expand All @@ -101,11 +95,13 @@ describe("e2e testing", () => {

const doc = await mailCollection.add(record);

return new Promise((resolve, reject) => {
const unsubscribe = doc.onSnapshot((snapshot) => {
return new Promise((resolve) => {
const unsubscribe = doc.onSnapshot(async (snapshot) => {
const document = snapshot.data();

if (document.delivery && document.delivery.info) {
const startAt = document.delivery.startTime.toDate();
const expireAt = document.delivery.expireAt.toDate();
expect(document.delivery.info.accepted[0]).toEqual(record.to);
expect(document.delivery.info.response).toContain("250 Accepted");
expect(document.message.attachments.length).toEqual(1);
Expand All @@ -114,7 +110,7 @@ describe("e2e testing", () => {
}
});
});
}, 8000);
});

test("should successfully send an email with a basic template", async (): Promise<void> => {
/** create basic template */
Expand All @@ -136,21 +132,85 @@ describe("e2e testing", () => {
/** Add a new mail document */
const doc = await mailCollection.add(record);

/** Check the email response */
return new Promise((resolve) => {
const unsubscribe = doc.onSnapshot(async (snapshot) => {
const document = snapshot.data();

if (document.delivery && document.delivery.info) {
if (document.delivery && document.delivery.info) {
expect(document.delivery.info.accepted[0]).toEqual(record.to);
expect(document.delivery.info.response).toContain("250 Accepted");

unsubscribe();
resolve();
}
}
});
});
});

test("should successfully send an email with a SendGrid template", async (): Promise<void> => {
/** Add a record with the template and no message object */
const record = {
to: "test-assertion@email.com",
sendGrid: {
templateId: "d-61eb136ddb8146f2b6e1fe7b54a1dcf0",
mailSettings: {
sandbox_mode: {
enable: true,
},
},
},
};

const doc = await mailSgCollection.add(record);

return new Promise((resolve, reject) => {
const unsubscribe = doc.onSnapshot((snapshot) => {
const document = snapshot.data();

if (document.delivery && document.delivery.info) {
expect(document.delivery.info.accepted[0]).toEqual(record.to);
expect(document.delivery.info.response).toContain("250 Accepted");
expect(document.delivery.state).toEqual("SUCCESS");
unsubscribe();
resolve();
} else {
if (document.delivery && document.delivery.error) {
unsubscribe();
reject(document.delivery.error);
}
}
});
});
}, 12000);

test("should error when sending an email with an empty SendGrid template", async (): Promise<void> => {
const record = {
to: "test-assertion@email.com",
sendGrid: {
mailSettings: {
sandbox_mode: {
enable: true,
},
},
},
};

const doc = await mailSgCollection.add(record);

return new Promise((resolve) => {
const unsubscribe = doc.onSnapshot(async (snapshot) => {
const document = snapshot.data();

if (document.delivery && document.delivery.error) {
expect(document.delivery.state).toEqual("ERROR");
expect(document.delivery.error).toEqual(
`Error: SendGrid templateId is not provided, if you're using SendGrid Dynamic Templates, please provide a valid templateId, otherwise provide a \`text\` or \`html\` content.`
);
unsubscribe();
resolve();
}
});
});
});
}, 12000);

afterAll(() => {
server.close();
Expand Down
12 changes: 12 additions & 0 deletions firestore-send-email/functions/__tests__/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,4 +215,16 @@ describe("set server credentials helper function", () => {

expect(regex.test(config.smtpConnectionUri)).toBe(false);
});

test("return a SendGrid transporter if the host is smtp.sendgrid.net", () => {
const config: Config = {
smtpConnectionUri: "smtps://apikey@smtp.sendgrid.net:465",
location: "",
mailCollection: "",
defaultFrom: "",
};

const credentials = setSmtpCredentials(config);
expect(credentials.transporter.name === "nodemailer-sendgrid").toBe(true);
});
});
Loading

0 comments on commit ebfe022

Please sign in to comment.