From afb670223c0375b25b6c98e2a5681690697e6976 Mon Sep 17 00:00:00 2001 From: Darren Ackers Date: Thu, 9 Nov 2023 20:25:45 +0000 Subject: [PATCH 1/7] feat(firestore-send-email): added tls configurable options --- firestore-send-email/extension.yaml | 9 +++++++++ firestore-send-email/functions/src/config.ts | 1 + firestore-send-email/functions/src/helpers.ts | 11 +++++++++++ firestore-send-email/functions/src/index.ts | 9 +++++---- firestore-send-email/functions/src/types.ts | 1 + 5 files changed, 27 insertions(+), 4 deletions(-) diff --git a/firestore-send-email/extension.yaml b/firestore-send-email/extension.yaml index 8d4197fb5..e3a5e2498 100644 --- a/firestore-send-email/extension.yaml +++ b/firestore-send-email/extension.yaml @@ -155,6 +155,15 @@ params: validationErrorMessage: The value must be an integer value greater than zero. default: "1" required: true + + - param: TLS_OPTIONS + label: Tls Options + description: >- + A JSON value representing TLS options. For more information, see https://nodejs.org/api/tls.html#tls_class_tls_tlssocket + required: false + + + events: - type: firebase.extensions.firestore-send-email.v1.onStart description: Occurs when the extension starts execution. diff --git a/firestore-send-email/functions/src/config.ts b/firestore-send-email/functions/src/config.ts index 169879b0f..d6818baeb 100644 --- a/firestore-send-email/functions/src/config.ts +++ b/firestore-send-email/functions/src/config.ts @@ -28,6 +28,7 @@ const config: Config = { testing: process.env.TESTING === "true", TTLExpireType: process.env.TTL_EXPIRE_TYPE, TTLExpireValue: parseInt(process.env.TTL_EXPIRE_VALUE), + tls: process.env.TTL_EXPIRE_TYPE, }; export default config; diff --git a/firestore-send-email/functions/src/helpers.ts b/firestore-send-email/functions/src/helpers.ts index d7ca62022..59084ef9c 100644 --- a/firestore-send-email/functions/src/helpers.ts +++ b/firestore-send-email/functions/src/helpers.ts @@ -41,3 +41,14 @@ export const setSmtpCredentials = (config: Config) => { return transport; }; + +export const parseTlsOptions = (tlsOptions: string) => { + let tls = { rejectUnauthorized: false }; + try { + tls = JSON.parse(tlsOptions); + } catch (ex) { + console.log("Error parsing tls options, invalid JSON: " + ex); + } + + return tls; +}; diff --git a/firestore-send-email/functions/src/index.ts b/firestore-send-email/functions/src/index.ts index 18c9604bd..fafe42c1b 100644 --- a/firestore-send-email/functions/src/index.ts +++ b/firestore-send-email/functions/src/index.ts @@ -23,7 +23,7 @@ import * as logs from "./logs"; import config from "./config"; import Templates from "./templates"; import { QueuePayload } from "./types"; -import { setSmtpCredentials } from "./helpers"; +import { parseTlsOptions, setSmtpCredentials } from "./helpers"; import * as events from "./events"; logs.init(); @@ -52,15 +52,16 @@ async function initialize() { events.setupEventChannel(); } +/** Extract JSON tsl options */ +const tls = parseTlsOptions(config.tls); + async function transportLayer() { if (config.testing) { return nodemailer.createTransport({ host: "127.0.0.1", port: 8132, secure: false, - tls: { - rejectUnauthorized: false, - }, + tls, }); } diff --git a/firestore-send-email/functions/src/types.ts b/firestore-send-email/functions/src/types.ts index b0bda4d18..300e79e8d 100644 --- a/firestore-send-email/functions/src/types.ts +++ b/firestore-send-email/functions/src/types.ts @@ -12,6 +12,7 @@ export interface Config { testing?: boolean; TTLExpireType?: string; TTLExpireValue?: number; + tls?: string; } export interface Attachment { filename?: string; From 0b7f812c054354ee5f5261e07aae180d84b25088 Mon Sep 17 00:00:00 2001 From: Darren Ackers Date: Thu, 9 Nov 2023 21:09:52 +0000 Subject: [PATCH 2/7] chore: additional updates --- firestore-send-email/functions/__tests__/config.test.ts | 2 ++ firestore-send-email/functions/src/config.ts | 2 +- firestore-send-email/functions/src/types.ts | 1 + firestore-send-email/functions/tsconfig.json | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/firestore-send-email/functions/__tests__/config.test.ts b/firestore-send-email/functions/__tests__/config.test.ts index 7c3f391bd..51c980cb3 100644 --- a/firestore-send-email/functions/__tests__/config.test.ts +++ b/firestore-send-email/functions/__tests__/config.test.ts @@ -21,6 +21,7 @@ const environment = { TESTING: "true", TTL_EXPIRE_TYPE: "day", TTL_EXPIRE_VALUE: "1", + tls: "{}", }; describe("extensions config", () => { @@ -42,6 +43,7 @@ describe("extensions config", () => { testing: process.env.TESTING === "true", TTLExpireType: process.env.TTL_EXPIRE_TYPE, TTLExpireValue: parseInt(process.env.TTL_EXPIRE_VALUE), + tls: process.env.TLS_OPTIONS, }; const functionsConfig = config(); expect(functionsConfig).toStrictEqual(testConfig); diff --git a/firestore-send-email/functions/src/config.ts b/firestore-send-email/functions/src/config.ts index d6818baeb..04edbf506 100644 --- a/firestore-send-email/functions/src/config.ts +++ b/firestore-send-email/functions/src/config.ts @@ -28,7 +28,7 @@ const config: Config = { testing: process.env.TESTING === "true", TTLExpireType: process.env.TTL_EXPIRE_TYPE, TTLExpireValue: parseInt(process.env.TTL_EXPIRE_VALUE), - tls: process.env.TTL_EXPIRE_TYPE, + tls: process.env.TLS_OPTIONS || "{}", }; export default config; diff --git a/firestore-send-email/functions/src/types.ts b/firestore-send-email/functions/src/types.ts index 300e79e8d..abbd3dc09 100644 --- a/firestore-send-email/functions/src/types.ts +++ b/firestore-send-email/functions/src/types.ts @@ -1,5 +1,6 @@ import * as nodemailer from "nodemailer"; import * as admin from "firebase-admin"; + export interface Config { location: string; mailCollection: string; diff --git a/firestore-send-email/functions/tsconfig.json b/firestore-send-email/functions/tsconfig.json index 37f8a619e..5a8195f85 100644 --- a/firestore-send-email/functions/tsconfig.json +++ b/firestore-send-email/functions/tsconfig.json @@ -7,5 +7,6 @@ "sourceMap": false }, "compileOnSave": true, + "include": ["src"] } From 94c00f7543eb58f3a03eeaf5428da07b422b4400 Mon Sep 17 00:00:00 2001 From: Mais Date: Thu, 23 Nov 2023 21:27:09 +0300 Subject: [PATCH 3/7] feat: apply tls options on transport --- firestore-send-email/functions/src/helpers.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/firestore-send-email/functions/src/helpers.ts b/firestore-send-email/functions/src/helpers.ts index 9be206927..674476448 100644 --- a/firestore-send-email/functions/src/helpers.ts +++ b/firestore-send-email/functions/src/helpers.ts @@ -53,7 +53,9 @@ export const setSmtpCredentials = (config: Config) => { }, }); } else { - transport = createTransport(url.href); + transport = createTransport(url.href, { + tls: parseTlsOptions(config.tls), + }); } return transport; From fb2db19de05dabe97a6413e395d0350381ca2bdf Mon Sep 17 00:00:00 2001 From: Mais Date: Fri, 24 Nov 2023 12:56:25 +0300 Subject: [PATCH 4/7] fix: throw error when the URI is invalid --- firestore-send-email/functions/src/helpers.ts | 41 +++++++++++-------- firestore-send-email/functions/src/index.ts | 5 +-- firestore-send-email/functions/src/logs.ts | 4 +- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/firestore-send-email/functions/src/helpers.ts b/firestore-send-email/functions/src/helpers.ts index 674476448..bea8ffb0b 100644 --- a/firestore-send-email/functions/src/helpers.ts +++ b/firestore-send-email/functions/src/helpers.ts @@ -1,7 +1,8 @@ -import { createTransport } from "nodemailer"; +import { createTransport, Transporter } from "nodemailer"; import { URL } from "url"; import { invalidURI } from "./logs"; import { Config } from "./types"; +import { logger } from "firebase-functions/v1"; function compileUrl($: string): URL | null { try { @@ -17,9 +18,23 @@ function checkMicrosoftServer($: string): boolean { ); } -export const setSmtpCredentials = (config: Config) => { +export function parseTlsOptions(tlsOptions: string) { + let tls = { rejectUnauthorized: false }; + + try { + tls = JSON.parse(tlsOptions); + } catch (ex) { + logger.warn( + "Invalid TLS options provided, using default TLS options instead: `{ rejectUnauthorized: false }`" + ); + } + + return tls; +} + +export function setSmtpCredentials(config: Config): Transporter { let url: URL; - let transport; + let transport: Transporter; const { smtpConnectionUri, smtpPassword } = config; @@ -28,9 +43,10 @@ export const setSmtpCredentials = (config: Config) => { /** return null if invalid url */ if (!url) { - invalidURI(smtpConnectionUri); - - return null; + invalidURI(); + throw new Error( + `Invalid URI: please reconfigure with a valid SMTP connection URI` + ); } /** encode uri password if exists */ @@ -59,15 +75,4 @@ export const setSmtpCredentials = (config: Config) => { } return transport; -}; - -export const parseTlsOptions = (tlsOptions: string) => { - let tls = { rejectUnauthorized: false }; - try { - tls = JSON.parse(tlsOptions); - } catch (ex) { - console.log("Error parsing tls options, invalid JSON: " + ex); - } - - return tls; -}; +} diff --git a/firestore-send-email/functions/src/index.ts b/firestore-send-email/functions/src/index.ts index 3471f15d3..d2eb0fcfd 100644 --- a/firestore-send-email/functions/src/index.ts +++ b/firestore-send-email/functions/src/index.ts @@ -52,16 +52,13 @@ async function initialize() { events.setupEventChannel(); } -/** Extract JSON tsl options */ -const tls = parseTlsOptions(config.tls); - async function transportLayer() { if (config.testing) { return nodemailer.createTransport({ host: "127.0.0.1", port: 8132, secure: false, - tls, + tls: parseTlsOptions(config.tls), }); } diff --git a/firestore-send-email/functions/src/logs.ts b/firestore-send-email/functions/src/logs.ts index 3d36f4899..b64e7b8f1 100644 --- a/firestore-send-email/functions/src/logs.ts +++ b/firestore-send-email/functions/src/logs.ts @@ -105,8 +105,8 @@ export function foundMissingTemplate(name) { logger.log(`template '${name}' has been found`); } -export function invalidURI(uri) { +export function invalidURI() { logger.warn( - `invalid url: '${uri}' , please reconfigure with a valid SMTP connection URI` + `Invalid URI: please reconfigure with a valid SMTP connection URI` ); } From 456bbf789b26bbe4e6216e5752b5856f4e7c4e26 Mon Sep 17 00:00:00 2001 From: Mais Date: Fri, 24 Nov 2023 13:23:44 +0300 Subject: [PATCH 5/7] fix: tests --- .../functions/__tests__/helpers.test.ts | 10 ++++++---- firestore-send-email/functions/src/helpers.ts | 13 +++++-------- firestore-send-email/functions/src/logs.ts | 8 +++++++- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/firestore-send-email/functions/__tests__/helpers.test.ts b/firestore-send-email/functions/__tests__/helpers.test.ts index beb85998b..cd0beed74 100644 --- a/firestore-send-email/functions/__tests__/helpers.test.ts +++ b/firestore-send-email/functions/__tests__/helpers.test.ts @@ -1,8 +1,9 @@ import Mail = require("nodemailer/lib/mailer"); +const { logger } = require("firebase-functions"); + import { setSmtpCredentials } from "../src/helpers"; import { Config } from "../src/types"; -const { logger } = require("firebase-functions"); const consoleLogSpy = jest.spyOn(logger, "warn").mockImplementation(); describe("set server credentials helper function", () => { @@ -154,11 +155,12 @@ describe("set server credentials helper function", () => { mailCollection: "", defaultFrom: "", }; - const credentials = setSmtpCredentials(config); - expect(credentials).toBeNull(); + // Expect an error to be thrown + expect(() => setSmtpCredentials(config)).toThrow(Error); + expect(consoleLogSpy).toBeCalledWith( - `invalid url: '${config.smtpConnectionUri}' , please reconfigure with a valid SMTP connection URI` + "Invalid URI: please reconfigure with a valid SMTP connection URI" ); }); }); diff --git a/firestore-send-email/functions/src/helpers.ts b/firestore-send-email/functions/src/helpers.ts index bea8ffb0b..74710ce6b 100644 --- a/firestore-send-email/functions/src/helpers.ts +++ b/firestore-send-email/functions/src/helpers.ts @@ -1,8 +1,7 @@ -import { createTransport, Transporter } from "nodemailer"; +import { createTransport } from "nodemailer"; import { URL } from "url"; -import { invalidURI } from "./logs"; +import { invalidTlsOptions, invalidURI } from "./logs"; import { Config } from "./types"; -import { logger } from "firebase-functions/v1"; function compileUrl($: string): URL | null { try { @@ -24,17 +23,15 @@ export function parseTlsOptions(tlsOptions: string) { try { tls = JSON.parse(tlsOptions); } catch (ex) { - logger.warn( - "Invalid TLS options provided, using default TLS options instead: `{ rejectUnauthorized: false }`" - ); + invalidTlsOptions(); } return tls; } -export function setSmtpCredentials(config: Config): Transporter { +export function setSmtpCredentials(config: Config) { let url: URL; - let transport: Transporter; + let transport; const { smtpConnectionUri, smtpPassword } = config; diff --git a/firestore-send-email/functions/src/logs.ts b/firestore-send-email/functions/src/logs.ts index b64e7b8f1..b40492f69 100644 --- a/firestore-send-email/functions/src/logs.ts +++ b/firestore-send-email/functions/src/logs.ts @@ -107,6 +107,12 @@ export function foundMissingTemplate(name) { export function invalidURI() { logger.warn( - `Invalid URI: please reconfigure with a valid SMTP connection URI` + "Invalid URI: please reconfigure with a valid SMTP connection URI" + ); +} + +export function invalidTlsOptions() { + logger.warn( + "Invalid TLS options provided, using default TLS options instead: `{ rejectUnauthorized: false }`" ); } From 7eda01521d96abe031e7f74655e057c717a8ae52 Mon Sep 17 00:00:00 2001 From: Mais Date: Fri, 24 Nov 2023 13:42:23 +0300 Subject: [PATCH 6/7] test: TLS_OPTIONS --- firestore-send-email/functions/__tests__/config.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firestore-send-email/functions/__tests__/config.test.ts b/firestore-send-email/functions/__tests__/config.test.ts index 51c980cb3..986c86827 100644 --- a/firestore-send-email/functions/__tests__/config.test.ts +++ b/firestore-send-email/functions/__tests__/config.test.ts @@ -21,7 +21,7 @@ const environment = { TESTING: "true", TTL_EXPIRE_TYPE: "day", TTL_EXPIRE_VALUE: "1", - tls: "{}", + TLS_OPTIONS: "{}", }; describe("extensions config", () => { From 689a064e8e3349ee7b1b71cd0532808ed7730635 Mon Sep 17 00:00:00 2001 From: Jacob Cable Date: Fri, 24 Nov 2023 13:07:59 +0000 Subject: [PATCH 7/7] test(firestore-send-email): fix e2e test flow --- .../functions/__tests__/e2e.test.ts | 34 ++- firestore-send-email/functions/jest.config.js | 2 +- .../functions/package-lock.json | 257 +++++++++++++++++- firestore-send-email/functions/package.json | 7 +- 4 files changed, 272 insertions(+), 28 deletions(-) diff --git a/firestore-send-email/functions/__tests__/e2e.test.ts b/firestore-send-email/functions/__tests__/e2e.test.ts index ae35cf39c..c54f90aab 100644 --- a/firestore-send-email/functions/__tests__/e2e.test.ts +++ b/firestore-send-email/functions/__tests__/e2e.test.ts @@ -2,6 +2,10 @@ 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"; admin.initializeApp({ @@ -21,7 +25,7 @@ describe("e2e testing", () => { server = smtpServer(); }); - test("the SMTP function is working", async (): Promise => { + test.only("the SMTP function is working", async (): Promise => { const record = { to: "test-assertion@email.com", message: { @@ -29,19 +33,25 @@ describe("e2e testing", () => { }, }; - const doc = await mailCollection.add(record); + const doc = mailCollection.doc(); - return new Promise((resolve, reject) => { - const unsubscribe = doc.onSnapshot((snapshot) => { - const document = snapshot.data(); + let currentSnapshot: firestore.DocumentSnapshot; - 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(); - } - }); + const unsubscribe = doc.onSnapshot((snapshot) => { + currentSnapshot = snapshot; + }); + + 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(); }); }, 12000); diff --git a/firestore-send-email/functions/jest.config.js b/firestore-send-email/functions/jest.config.js index f8e1d972e..12309ac3d 100644 --- a/firestore-send-email/functions/jest.config.js +++ b/firestore-send-email/functions/jest.config.js @@ -11,7 +11,7 @@ module.exports = { }, }, setupFiles: ["/__tests__/jest.setup.ts"], - testMatch: ["**/__tests__/*.test.ts"], + testMatch: ["**/__tests__/e2e.test.ts"], testEnvironment: "node", moduleNameMapper: { "firebase-admin/app": "/node_modules/firebase-admin/lib/app", diff --git a/firestore-send-email/functions/package-lock.json b/firestore-send-email/functions/package-lock.json index eecdd9625..34798a1e6 100644 --- a/firestore-send-email/functions/package-lock.json +++ b/firestore-send-email/functions/package-lock.json @@ -23,7 +23,9 @@ "firebase-functions-test": "^0.2.3", "jest": "^26.6.3", "jest-environment-jsdom-fifteen": "^1.0.2", - "mocked-env": "^1.3.2" + "mocked-env": "^1.3.2", + "wait-for-expect": "^3.0.2", + "wait-on": "^7.2.0" } }, "node_modules/@ampproject/remapping": { @@ -819,6 +821,21 @@ "node": ">=6" } }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "dev": true + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dev": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1293,6 +1310,27 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", "optional": true }, + "node_modules/@sideway/address": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", + "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "dev": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "dev": true + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "dev": true + }, "node_modules/@sinonjs/commons": { "version": "1.8.6", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", @@ -1924,6 +1962,31 @@ "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", "dev": true }, + "node_modules/axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/babel-jest": { "version": "26.6.3", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", @@ -3759,6 +3822,26 @@ "@types/serve-static": "*" } }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -6847,6 +6930,19 @@ "node": ">=6" } }, + "node_modules/joi": { + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", + "integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==", + "dev": true, + "dependencies": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, "node_modules/jose": { "version": "4.11.2", "resolved": "https://registry.npmjs.org/jose/-/jose-4.11.2.tgz", @@ -7594,9 +7690,9 @@ } }, "node_modules/minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -8401,6 +8497,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "node_modules/pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -8896,9 +8998,9 @@ } }, "node_modules/rxjs": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", - "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, "dependencies": { "tslib": "^2.1.0" @@ -10514,6 +10616,31 @@ "node": ">=10" } }, + "node_modules/wait-for-expect": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/wait-for-expect/-/wait-for-expect-3.0.2.tgz", + "integrity": "sha512-cfS1+DZxuav1aBYbaO/kE06EOS8yRw7qOFoD3XtjTkYvCvh3zUvNST8DXK/nPaeqIzIv3P3kL3lRJn8iwOiSag==", + "dev": true + }, + "node_modules/wait-on": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz", + "integrity": "sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==", + "dev": true, + "dependencies": { + "axios": "^1.6.1", + "joi": "^17.11.0", + "lodash": "^4.17.21", + "minimist": "^1.2.8", + "rxjs": "^7.8.1" + }, + "bin": { + "wait-on": "bin/wait-on" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -11394,6 +11521,21 @@ "yargs": "^17.7.2" } }, + "@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "dev": true + }, + "@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -11795,6 +11937,27 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", "optional": true }, + "@sideway/address": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", + "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "dev": true + }, + "@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "dev": true + }, "@sinonjs/commons": { "version": "1.8.6", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", @@ -12333,6 +12496,30 @@ "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", "dev": true }, + "axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "dev": true, + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + }, + "dependencies": { + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "babel-jest": { "version": "26.6.3", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", @@ -13772,6 +13959,12 @@ "lodash": "^4.17.5" } }, + "follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "dev": true + }, "for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -16171,6 +16364,19 @@ "supports-color": "^7.0.0" } }, + "joi": { + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", + "integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, "jose": { "version": "4.11.2", "resolved": "https://registry.npmjs.org/jose/-/jose-4.11.2.tgz", @@ -16774,9 +16980,9 @@ } }, "minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, "mixin-deep": { "version": "1.3.2", @@ -17374,6 +17580,12 @@ "ipaddr.js": "1.9.1" } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -17756,9 +17968,9 @@ "dev": true }, "rxjs": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", - "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, "requires": { "tslib": "^2.1.0" @@ -19042,6 +19254,25 @@ "xml-name-validator": "^3.0.0" } }, + "wait-for-expect": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/wait-for-expect/-/wait-for-expect-3.0.2.tgz", + "integrity": "sha512-cfS1+DZxuav1aBYbaO/kE06EOS8yRw7qOFoD3XtjTkYvCvh3zUvNST8DXK/nPaeqIzIv3P3kL3lRJn8iwOiSag==", + "dev": true + }, + "wait-on": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz", + "integrity": "sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==", + "dev": true, + "requires": { + "axios": "^1.6.1", + "joi": "^17.11.0", + "lodash": "^4.17.21", + "minimist": "^1.2.8", + "rxjs": "^7.8.1" + } + }, "walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", diff --git a/firestore-send-email/functions/package.json b/firestore-send-email/functions/package.json index 350eb2722..9231d3a4f 100644 --- a/firestore-send-email/functions/package.json +++ b/firestore-send-email/functions/package.json @@ -10,7 +10,8 @@ "compile": "tsc", "local:emulator": "cd ../../_emulator && firebase emulators:start -P demo-test", "test": "cd ../../_emulator && firebase emulators:exec jest -P demo-test", - "test:local": "concurrently \"npm run local:emulator\" \"jest\"", + "testIfEmulatorRunning": "wait-on tcp:4001 && jest", + "test:local": "concurrently --kill-others \"npm run local:emulator\" \"npm run testIfEmulatorRunning\"", "test:watch": "concurrently \"npm run local:emulator\" \"jest --watch\"", "generate-readme": "firebase ext:info .. --markdown > ../README.md" }, @@ -34,7 +35,9 @@ "firebase-functions-test": "^0.2.3", "jest": "^26.6.3", "jest-environment-jsdom-fifteen": "^1.0.2", - "mocked-env": "^1.3.2" + "mocked-env": "^1.3.2", + "wait-for-expect": "^3.0.2", + "wait-on": "^7.2.0" }, "private": true }