backend-manager 5.0.49 → 5.0.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cli/commands/setup-tests/hosting-rewrites.js +5 -5
- package/src/manager/functions/core/actions/api/admin/backup.js +1 -1
- package/src/manager/functions/core/actions/api/admin/create-post.js +1 -1
- package/src/manager/functions/core/actions/api/admin/cron.js +1 -1
- package/src/manager/functions/core/actions/api/admin/database-read.js +4 -2
- package/src/manager/functions/core/actions/api/admin/database-write.js +4 -2
- package/src/manager/functions/core/actions/api/admin/edit-post.js +1 -1
- package/src/manager/functions/core/actions/api/admin/firestore-query.js +4 -2
- package/src/manager/functions/core/actions/api/admin/firestore-read.js +4 -2
- package/src/manager/functions/core/actions/api/admin/firestore-write.js +4 -2
- package/src/manager/functions/core/actions/api/admin/get-stats.js +4 -2
- package/src/manager/functions/core/actions/api/admin/payment-processor.js +4 -2
- package/src/manager/functions/core/actions/api/admin/run-hook.js +1 -1
- package/src/manager/functions/core/actions/api/admin/send-email.js +4 -2
- package/src/manager/functions/core/actions/api/admin/send-notification copy.js +1 -1
- package/src/manager/functions/core/actions/api/admin/send-notification.js +4 -2
- package/src/manager/functions/core/actions/api/admin/sync-users.js +1 -1
- package/src/manager/functions/core/actions/api/admin/write-repo-content.js +1 -1
- package/src/manager/functions/core/actions/api.js +1 -40
- package/src/manager/helpers/bem-router.js +51 -0
- package/src/manager/index.js +32 -2
- package/test/functions/admin/firestore-query.js +1 -1
- package/test/functions/general/add-marketing-contact.js +5 -5
package/package.json
CHANGED
|
@@ -14,10 +14,10 @@ class HostingRewritesTest extends BaseTest {
|
|
|
14
14
|
// Check first rule is correct
|
|
15
15
|
const firstIsCorrect = firstRewrite?.source === '{/backend-manager,/backend-manager/**}' && firstRewrite?.function === 'bm_api';
|
|
16
16
|
|
|
17
|
-
// Check no duplicates exist (only one
|
|
18
|
-
const
|
|
17
|
+
// Check no duplicates exist (only one bm_api rule allowed)
|
|
18
|
+
const bmApiCount = rewrites.filter(r => r.function === 'bm_api').length;
|
|
19
19
|
|
|
20
|
-
return firstIsCorrect &&
|
|
20
|
+
return firstIsCorrect && bmApiCount === 1;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
async fix() {
|
|
@@ -26,8 +26,8 @@ class HostingRewritesTest extends BaseTest {
|
|
|
26
26
|
// Set default
|
|
27
27
|
hosting.rewrites = hosting.rewrites || [];
|
|
28
28
|
|
|
29
|
-
// Remove any existing
|
|
30
|
-
hosting.rewrites = hosting.rewrites.filter(rewrite =>
|
|
29
|
+
// Remove any existing bm_api rewrites
|
|
30
|
+
hosting.rewrites = hosting.rewrites.filter(rewrite => rewrite.function !== 'bm_api');
|
|
31
31
|
|
|
32
32
|
// Add to top
|
|
33
33
|
hosting.rewrites.unshift({
|
|
@@ -19,7 +19,7 @@ Module.prototype.main = function () {
|
|
|
19
19
|
payload.data.payload.deletionRegex = payload.data.payload.deletionRegex ? powertools.regexify(payload.data.payload.deletionRegex) : payload.data.payload.deletionRegex;
|
|
20
20
|
|
|
21
21
|
if (!payload.user.roles.admin && assistant.isProduction()) {
|
|
22
|
-
return reject(assistant.errorify(`Admin required.`, {code:
|
|
22
|
+
return reject(assistant.errorify(`Admin required.`, {code: 403}));
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
// https://googleapis.dev/nodejs/firestore/latest/v1.FirestoreAdminClient.html#exportDocuments
|
|
@@ -27,7 +27,7 @@ Module.prototype.main = function () {
|
|
|
27
27
|
try {
|
|
28
28
|
// Perform checks
|
|
29
29
|
if (!payload.user.roles.admin && !payload.user.roles.blogger) {
|
|
30
|
-
return reject(assistant.errorify(`Admin required.`, {code:
|
|
30
|
+
return reject(assistant.errorify(`Admin required.`, {code: 403}));
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// Log payload
|
|
@@ -13,7 +13,7 @@ Module.prototype.main = function () {
|
|
|
13
13
|
|
|
14
14
|
// Check if the user is an admin
|
|
15
15
|
if (!payload.user.roles.admin && assistant.isProduction()) {
|
|
16
|
-
return reject(assistant.errorify(`Admin required.`, {code:
|
|
16
|
+
return reject(assistant.errorify(`Admin required.`, {code: 403}));
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
// Check if the ID is set
|
|
@@ -15,8 +15,10 @@ Module.prototype.main = function () {
|
|
|
15
15
|
payload.data.payload.options = payload.data.payload.options || {};
|
|
16
16
|
|
|
17
17
|
// Perform checks
|
|
18
|
-
if (!payload.user.
|
|
19
|
-
return reject(assistant.errorify(`
|
|
18
|
+
if (!payload.user.authenticated) {
|
|
19
|
+
return reject(assistant.errorify(`Authentication required.`, {code: 401}));
|
|
20
|
+
} else if (!payload.user.roles.admin) {
|
|
21
|
+
return reject(assistant.errorify(`Admin required.`, {code: 403}));
|
|
20
22
|
} else if (!payload.data.payload.path) {
|
|
21
23
|
return reject(assistant.errorify(`<path> parameter required`, {code: 400}));
|
|
22
24
|
}
|
|
@@ -16,8 +16,10 @@ Module.prototype.main = function () {
|
|
|
16
16
|
payload.data.payload.options = payload.data.payload.options || {};
|
|
17
17
|
|
|
18
18
|
// Perform checks
|
|
19
|
-
if (!payload.user.
|
|
20
|
-
return reject(assistant.errorify(`
|
|
19
|
+
if (!payload.user.authenticated) {
|
|
20
|
+
return reject(assistant.errorify(`Authentication required.`, {code: 401}));
|
|
21
|
+
} else if (!payload.user.roles.admin) {
|
|
22
|
+
return reject(assistant.errorify(`Admin required.`, {code: 403}));
|
|
21
23
|
} else if (!payload.data.payload.path) {
|
|
22
24
|
return reject(assistant.errorify(`<path> parameter required`, {code: 400}));
|
|
23
25
|
}
|
|
@@ -21,7 +21,7 @@ Module.prototype.main = function () {
|
|
|
21
21
|
try {
|
|
22
22
|
// Perform checks
|
|
23
23
|
if (!payload.user.roles.admin && !payload.user.roles.blogger) {
|
|
24
|
-
return reject(assistant.errorify(`Admin required.`, {code:
|
|
24
|
+
return reject(assistant.errorify(`Admin required.`, {code: 403}));
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
// Check for GitHub configuration
|
|
@@ -14,8 +14,10 @@ Module.prototype.main = function () {
|
|
|
14
14
|
|
|
15
15
|
return new Promise(async function(resolve, reject) {
|
|
16
16
|
// Perform checks
|
|
17
|
-
if (!payload.user.
|
|
18
|
-
return reject(assistant.errorify(`
|
|
17
|
+
if (!payload.user.authenticated) {
|
|
18
|
+
return reject(assistant.errorify(`Authentication required.`, {code: 401}));
|
|
19
|
+
} else if (!payload.user.roles.admin) {
|
|
20
|
+
return reject(assistant.errorify(`Admin required.`, {code: 403}));
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
// Run queries
|
|
@@ -15,8 +15,10 @@ Module.prototype.main = function () {
|
|
|
15
15
|
payload.data.payload.options = payload.data.payload.options || {};
|
|
16
16
|
|
|
17
17
|
// Perform checks
|
|
18
|
-
if (!payload.user.
|
|
19
|
-
return reject(assistant.errorify(`
|
|
18
|
+
if (!payload.user.authenticated) {
|
|
19
|
+
return reject(assistant.errorify(`Authentication required.`, {code: 401}));
|
|
20
|
+
} else if (!payload.user.roles.admin) {
|
|
21
|
+
return reject(assistant.errorify(`Admin required.`, {code: 403}));
|
|
20
22
|
} else if (!payload.data.payload.path) {
|
|
21
23
|
return reject(assistant.errorify(`<path> parameter required`, {code: 400}));
|
|
22
24
|
}
|
|
@@ -18,8 +18,10 @@ Module.prototype.main = function () {
|
|
|
18
18
|
payload.data.payload.options.metadataTag = typeof payload.data.payload.options.metadataTag === 'undefined' ? 'admin:firestore-write' : payload.data.payload.options.metadataTag;
|
|
19
19
|
|
|
20
20
|
// Perform checks
|
|
21
|
-
if (!payload.user.
|
|
22
|
-
return reject(assistant.errorify(`
|
|
21
|
+
if (!payload.user.authenticated) {
|
|
22
|
+
return reject(assistant.errorify(`Authentication required.`, {code: 401}));
|
|
23
|
+
} else if (!payload.user.roles.admin) {
|
|
24
|
+
return reject(assistant.errorify(`Admin required.`, {code: 403}));
|
|
23
25
|
} else if (!payload.data.payload.path) {
|
|
24
26
|
return reject(assistant.errorify(`Path parameter required.`, {code: 400}));
|
|
25
27
|
}
|
|
@@ -19,8 +19,10 @@ Module.prototype.main = function () {
|
|
|
19
19
|
payload.data.payload.update = payload.data.payload.update || false;
|
|
20
20
|
|
|
21
21
|
// Perform checks
|
|
22
|
-
if (!payload.user.
|
|
23
|
-
return reject(assistant.errorify(`
|
|
22
|
+
if (!payload.user.authenticated) {
|
|
23
|
+
return reject(assistant.errorify(`Authentication required.`, {code: 401}));
|
|
24
|
+
} else if (!payload.user.roles.admin) {
|
|
25
|
+
return reject(assistant.errorify(`Admin required.`, {code: 403}));
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
// Get stats ref
|
|
@@ -16,8 +16,10 @@ Module.prototype.main = function () {
|
|
|
16
16
|
|
|
17
17
|
return new Promise(async function(resolve, reject) {
|
|
18
18
|
// Check for admin
|
|
19
|
-
if (!payload.user.
|
|
20
|
-
return reject(assistant.errorify(`
|
|
19
|
+
if (!payload.user.authenticated) {
|
|
20
|
+
return reject(assistant.errorify(`Authentication required.`, {code: 401}));
|
|
21
|
+
} else if (!payload.user.roles.admin) {
|
|
22
|
+
return reject(assistant.errorify(`Admin required.`, {code: 403}));
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
const productId = payload?.data?.payload?.payload?.details?.productIdGlobal;
|
|
@@ -17,7 +17,7 @@ Module.prototype.main = function () {
|
|
|
17
17
|
return new Promise(async function(resolve, reject) {
|
|
18
18
|
// Perform checks
|
|
19
19
|
if (!payload.user.roles.admin && assistant.isProduction()) {
|
|
20
|
-
return reject(assistant.errorify(`Admin required.`, {code:
|
|
20
|
+
return reject(assistant.errorify(`Admin required.`, {code: 403}));
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
// Check for required options
|
|
@@ -25,8 +25,10 @@ Module.prototype.main = function () {
|
|
|
25
25
|
self.sendgrid = sendgrid;
|
|
26
26
|
|
|
27
27
|
// Check if user is admin
|
|
28
|
-
if (!payload.user.
|
|
29
|
-
return reject(assistant.errorify(`
|
|
28
|
+
if (!payload.user.authenticated) {
|
|
29
|
+
return reject(assistant.errorify(`Authentication required.`, {code: 401}));
|
|
30
|
+
} else if (!payload.user.roles.admin) {
|
|
31
|
+
return reject(assistant.errorify(`Admin required.`, { code: 403 }));
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
// Check for SendGrid key
|
|
@@ -55,7 +55,7 @@ Module.prototype.main = function () {
|
|
|
55
55
|
|
|
56
56
|
// Check if user is admin
|
|
57
57
|
if (!payload.user.roles.admin) {
|
|
58
|
-
return reject(assistant.errorify(`Admin required.`, {code:
|
|
58
|
+
return reject(assistant.errorify(`Admin required.`, {code: 403}));
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
// Check if title and body are set
|
|
@@ -71,8 +71,10 @@ Module.prototype.main = function () {
|
|
|
71
71
|
assistant.log('Resolved notification payload', notification)
|
|
72
72
|
|
|
73
73
|
// Check if user is admin
|
|
74
|
-
if (!payload.user.
|
|
75
|
-
return reject(assistant.errorify(`
|
|
74
|
+
if (!payload.user.authenticated) {
|
|
75
|
+
return reject(assistant.errorify(`Authentication required.`, {code: 401}));
|
|
76
|
+
} else if (!payload.user.roles.admin) {
|
|
77
|
+
return reject(assistant.errorify(`Admin required.`, {code: 403}));
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
// Check if title and body are set
|
|
@@ -15,7 +15,7 @@ Module.prototype.main = function () {
|
|
|
15
15
|
|
|
16
16
|
// If the user is not an admin, reject
|
|
17
17
|
if (!payload.user.roles.admin && assistant.isProduction()) {
|
|
18
|
-
return reject(assistant.errorify(`Admin required.`, {code:
|
|
18
|
+
return reject(assistant.errorify(`Admin required.`, {code: 403}));
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
// Get lastPageToken from meta/stats
|
|
@@ -21,7 +21,7 @@ Module.prototype.main = function () {
|
|
|
21
21
|
try {
|
|
22
22
|
// Perform checks
|
|
23
23
|
if (!payload.user.roles.admin && !payload.user.roles.blogger) {
|
|
24
|
-
return reject(assistant.errorify(`Admin required.`, {code:
|
|
24
|
+
return reject(assistant.errorify(`Admin required.`, {code: 403}));
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
// Check for GitHub configuration
|
|
@@ -48,28 +48,7 @@ Module.prototype.main = function() {
|
|
|
48
48
|
|
|
49
49
|
return new Promise(async function(resolve, reject) {
|
|
50
50
|
return libraries.cors(req, res, async () => {
|
|
51
|
-
//
|
|
52
|
-
// New style: command with '/' (e.g., 'general/uuid') or URL path without command
|
|
53
|
-
// Legacy: command with ':' (e.g., 'general:generate-uuid')
|
|
54
|
-
const urlPath = req.path || '';
|
|
55
|
-
const command = assistant.request.data.command || '';
|
|
56
|
-
|
|
57
|
-
// Strip prefix: /backend-manager/ (hosting rewrite) or /bm_api/ (direct function URL) or just leading slash
|
|
58
|
-
const pathAfterBm = urlPath
|
|
59
|
-
.replace(/^\/(backend-manager|bm_api)\/?/, '')
|
|
60
|
-
.replace(/^\//, '');
|
|
61
|
-
|
|
62
|
-
// New style if: command contains '/' OR (URL has path AND no command)
|
|
63
|
-
const isNewStyleCommand = command.includes('/') && !command.includes(':');
|
|
64
|
-
const isNewStyleUrl = pathAfterBm.length > 0 && !command;
|
|
65
|
-
const isNewStyleRequest = isNewStyleCommand || isNewStyleUrl;
|
|
66
|
-
|
|
67
|
-
if (isNewStyleRequest) {
|
|
68
|
-
// Route path is either from command or URL
|
|
69
|
-
const routePath = isNewStyleCommand ? command : pathAfterBm;
|
|
70
|
-
return self.routeToMiddleware(routePath);
|
|
71
|
-
}
|
|
72
|
-
|
|
51
|
+
// Legacy command-based API only - new-style requests are routed via RequestRouter
|
|
73
52
|
// Set properties
|
|
74
53
|
self.payload.data = assistant.request.data;
|
|
75
54
|
self.payload.user = await assistant.authenticate();
|
|
@@ -401,24 +380,6 @@ Module.prototype.resolveApiPath = function (command) {
|
|
|
401
380
|
}
|
|
402
381
|
};
|
|
403
382
|
|
|
404
|
-
Module.prototype.routeToMiddleware = function (routePath) {
|
|
405
|
-
const self = this;
|
|
406
|
-
const Manager = self.Manager;
|
|
407
|
-
const req = self.req;
|
|
408
|
-
const res = self.res;
|
|
409
|
-
|
|
410
|
-
// Set paths for BEM internal routes/schemas
|
|
411
|
-
// __dirname is src/manager/functions/core/actions, so we need to go up to src/manager
|
|
412
|
-
const bemRoutesDir = path.resolve(__dirname, '../../../routes');
|
|
413
|
-
const bemSchemasDir = path.resolve(__dirname, '../../../schemas');
|
|
414
|
-
|
|
415
|
-
return Manager.Middleware(req, res).run(routePath, {
|
|
416
|
-
routesDir: bemRoutesDir,
|
|
417
|
-
schemasDir: bemSchemasDir,
|
|
418
|
-
schema: routePath,
|
|
419
|
-
});
|
|
420
|
-
};
|
|
421
|
-
|
|
422
383
|
function stripUrl(url) {
|
|
423
384
|
const newUrl = new URL(url);
|
|
424
385
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BemRouter
|
|
3
|
+
* Routes incoming requests to either the legacy command-based API
|
|
4
|
+
* or the new RESTful middleware system.
|
|
5
|
+
*
|
|
6
|
+
* Detection rules:
|
|
7
|
+
* - Legacy: command with ':' AND no meaningful route path
|
|
8
|
+
* - Direct function call: /us-central1/bm_api (no path after prefix)
|
|
9
|
+
* - Hosting rewrite: /backend-manager (no path after prefix)
|
|
10
|
+
* - New: URL path like /backend-manager/user/sign-up (has path after prefix)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
function BemRouter(Manager, req, res) {
|
|
14
|
+
const self = this;
|
|
15
|
+
|
|
16
|
+
self.Manager = Manager;
|
|
17
|
+
self.req = req;
|
|
18
|
+
self.res = res;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
BemRouter.prototype.resolve = function () {
|
|
22
|
+
const self = this;
|
|
23
|
+
const req = self.req;
|
|
24
|
+
|
|
25
|
+
// Extract command from body/query (legacy format)
|
|
26
|
+
const body = req.body || {};
|
|
27
|
+
const query = req.query || {};
|
|
28
|
+
const command = body.command || query.command || '';
|
|
29
|
+
|
|
30
|
+
// Extract URL path
|
|
31
|
+
const urlPath = req.path || '';
|
|
32
|
+
|
|
33
|
+
// Strip prefix: /backend-manager/ or /bm_api/ or leading slash
|
|
34
|
+
const routePath = urlPath
|
|
35
|
+
.replace(/^\/(backend-manager|bm_api)\/?/, '')
|
|
36
|
+
.replace(/^\//, '');
|
|
37
|
+
|
|
38
|
+
// Legacy if: command contains ':' AND routePath is empty
|
|
39
|
+
// (called via direct function URL or hosting rewrite without a sub-path)
|
|
40
|
+
const isLegacy = command.includes(':') && !routePath;
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
type: isLegacy ? 'legacy' : 'middleware',
|
|
44
|
+
command: command,
|
|
45
|
+
routePath: routePath,
|
|
46
|
+
isLegacy: isLegacy,
|
|
47
|
+
isNewStyle: !isLegacy,
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
module.exports = BemRouter;
|
package/src/manager/index.js
CHANGED
|
@@ -396,6 +396,21 @@ Manager.prototype._preProcess = function (mod) {
|
|
|
396
396
|
});
|
|
397
397
|
};
|
|
398
398
|
|
|
399
|
+
Manager.prototype._processMiddleware = function (req, res, routePath) {
|
|
400
|
+
const self = this;
|
|
401
|
+
|
|
402
|
+
// Set paths for BEM internal routes/schemas
|
|
403
|
+
const bemRoutesDir = path.resolve(__dirname, './routes');
|
|
404
|
+
const bemSchemasDir = path.resolve(__dirname, './schemas');
|
|
405
|
+
|
|
406
|
+
// Route directly through middleware (no hooks for new system)
|
|
407
|
+
return self.Middleware(req, res).run(routePath, {
|
|
408
|
+
routesDir: bemRoutesDir,
|
|
409
|
+
schemasDir: bemSchemasDir,
|
|
410
|
+
schema: routePath,
|
|
411
|
+
});
|
|
412
|
+
};
|
|
413
|
+
|
|
399
414
|
// Manager.prototype.Assistant = function(ref, options) {
|
|
400
415
|
// const self = this;
|
|
401
416
|
// ref = ref || {};
|
|
@@ -469,6 +484,12 @@ Manager.prototype.Middleware = function () {
|
|
|
469
484
|
return new self.libraries.Middleware(self, ...arguments);
|
|
470
485
|
};
|
|
471
486
|
|
|
487
|
+
Manager.prototype.BemRouter = function (req, res) {
|
|
488
|
+
const self = this;
|
|
489
|
+
self.libraries.BemRouter = self.libraries.BemRouter || require('./helpers/bem-router.js');
|
|
490
|
+
return new self.libraries.BemRouter(self, req, res);
|
|
491
|
+
};
|
|
492
|
+
|
|
472
493
|
Manager.prototype.EventMiddleware = function (payload) {
|
|
473
494
|
const self = this;
|
|
474
495
|
self.libraries.EventMiddleware = self.libraries.EventMiddleware || require('./helpers/event-middleware.js');
|
|
@@ -707,8 +728,17 @@ Manager.prototype.setupFunctions = function (exporter, options) {
|
|
|
707
728
|
exporter.bm_api =
|
|
708
729
|
self.libraries.functions
|
|
709
730
|
.runWith({memory: '256MB', timeoutSeconds: 60 * 5})
|
|
710
|
-
|
|
711
|
-
|
|
731
|
+
.https.onRequest(async (req, res) => {
|
|
732
|
+
const route = self.BemRouter(req, res).resolve();
|
|
733
|
+
|
|
734
|
+
if (route.isLegacy) {
|
|
735
|
+
// Legacy command-based API -> goes through api.js + _process() for hooks
|
|
736
|
+
return self._process((new (require(`${core}/actions/api.js`))()).init(self, { req, res }));
|
|
737
|
+
} else {
|
|
738
|
+
// New RESTful middleware system -> direct to middleware (no hooks)
|
|
739
|
+
return self._processMiddleware(req, res, route.routePath);
|
|
740
|
+
}
|
|
741
|
+
});
|
|
712
742
|
|
|
713
743
|
// Setup legacy functions
|
|
714
744
|
if (options.setupFunctionsLegacy) {
|
|
@@ -195,7 +195,7 @@ module.exports = {
|
|
|
195
195
|
{
|
|
196
196
|
name: 'non-admin-rejected',
|
|
197
197
|
async run({ http, assert }) {
|
|
198
|
-
const queryResponse = await http.as('
|
|
198
|
+
const queryResponse = await http.as('basic').command('admin:firestore-query', {
|
|
199
199
|
queries: [{ collection: TEST_COLLECTION }],
|
|
200
200
|
});
|
|
201
201
|
|
|
@@ -225,12 +225,12 @@ module.exports = {
|
|
|
225
225
|
|
|
226
226
|
assert.isSuccess(response, 'Add marketing contact with specific providers should succeed');
|
|
227
227
|
|
|
228
|
-
//
|
|
229
|
-
if (response.data?.providers) {
|
|
230
|
-
assert.hasProperty(response.data.providers, 'sendgrid', 'Should have SendGrid result');
|
|
231
|
-
}
|
|
232
|
-
|
|
228
|
+
// Only check providers if TEST_EXTENDED_MODE is set (external APIs are called)
|
|
233
229
|
if (process.env.TEST_EXTENDED_MODE) {
|
|
230
|
+
// Should only have sendgrid result
|
|
231
|
+
if (response.data?.providers) {
|
|
232
|
+
assert.hasProperty(response.data.providers, 'sendgrid', 'Should have SendGrid result');
|
|
233
|
+
}
|
|
234
234
|
state.sendgridAdded = response.data?.providers?.sendgrid?.success;
|
|
235
235
|
// Beehiiv not called since we only specified sendgrid
|
|
236
236
|
}
|