backend-manager 5.0.39 → 5.0.41

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.
Files changed (258) hide show
  1. package/.codeclimate.yml +32 -0
  2. package/CHANGELOG.md +25 -0
  3. package/CLAUDE.md +3 -2
  4. package/README.md +1 -0
  5. package/REFACTOR-BEM-API.md +6 -2
  6. package/TODO.md +149 -0
  7. package/bin/bem +1 -1
  8. package/package.json +2 -1
  9. package/src/cli/commands/base-command.js +59 -32
  10. package/src/cli/commands/emulators.js +39 -18
  11. package/src/cli/commands/setup-tests/emulators-config.js +2 -0
  12. package/src/cli/commands/setup-tests/hosting-rewrites.js +1 -0
  13. package/src/cli/commands/test.js +14 -19
  14. package/src/manager/cron/daily/ghostii-auto-publisher.js +278 -0
  15. package/src/manager/cron/daily/reset-usage.js +140 -0
  16. package/src/manager/cron/daily.js +54 -0
  17. package/src/manager/events/auth/before-create.js +52 -0
  18. package/src/manager/events/auth/before-signin.js +42 -0
  19. package/src/manager/events/auth/on-create.js +270 -0
  20. package/src/manager/events/auth/on-delete.js +94 -0
  21. package/src/manager/events/firestore/notifications/on-write.js +77 -0
  22. package/src/manager/functions/{core → _legacy}/actions/create-post-handler.js +10 -9
  23. package/src/manager/functions/core/actions/api/admin/edit-post.js +1 -1
  24. package/src/manager/functions/core/actions/api/admin/send-email.js +3 -3
  25. package/src/manager/functions/core/actions/api/general/add-marketing-contact.js +604 -0
  26. package/src/manager/functions/core/actions/api/general/remove-marketing-contact.js +234 -0
  27. package/src/manager/functions/core/actions/api/general/send-email.js +7 -3
  28. package/src/manager/functions/core/actions/api/user/delete.js +1 -1
  29. package/src/manager/functions/core/actions/api/user/oauth2.js +1 -1
  30. package/src/manager/functions/core/actions/api.js +40 -0
  31. package/src/manager/helpers/assistant.js +0 -1
  32. package/src/manager/helpers/event-middleware.js +94 -0
  33. package/src/manager/helpers/middleware.js +26 -23
  34. package/src/manager/helpers/settings.js +17 -13
  35. package/src/manager/index.js +35 -19
  36. package/src/manager/libraries/disposable-domains.json +839 -0
  37. package/src/manager/routes/admin/backup/post.js +105 -0
  38. package/src/manager/routes/admin/cron/post.js +37 -0
  39. package/src/manager/routes/admin/database/get.js +33 -0
  40. package/src/manager/routes/admin/database/post.js +32 -0
  41. package/src/manager/routes/admin/email/post.js +451 -0
  42. package/src/manager/routes/admin/firestore/get.js +33 -0
  43. package/src/manager/routes/admin/firestore/post.js +54 -0
  44. package/src/manager/routes/admin/firestore/query/post.js +126 -0
  45. package/src/manager/routes/admin/hook/post.js +83 -0
  46. package/src/manager/routes/admin/notification/post.js +260 -0
  47. package/src/manager/routes/admin/payment/post.js +56 -0
  48. package/src/manager/routes/admin/post/post.js +301 -0
  49. package/src/manager/routes/admin/post/put.js +136 -0
  50. package/src/manager/routes/admin/post/templates/post.html +15 -0
  51. package/src/manager/routes/admin/repo/content/post.js +102 -0
  52. package/src/manager/routes/admin/stats/get.js +213 -0
  53. package/src/manager/routes/admin/users/sync/post.js +119 -0
  54. package/src/manager/routes/content/post/get.js +104 -0
  55. package/src/manager/routes/firebase/providers/get.js +80 -0
  56. package/src/manager/routes/general/email/post.js +99 -0
  57. package/src/manager/routes/general/email/templates/download-app-link.js +25 -0
  58. package/src/manager/routes/general/uuid/post.js +34 -0
  59. package/src/manager/routes/handler/post/post.js +131 -0
  60. package/src/manager/routes/index.js +4 -20
  61. package/src/manager/routes/marketing/contact/delete.js +207 -0
  62. package/src/manager/routes/marketing/contact/post.js +504 -0
  63. package/src/manager/routes/restart/index.js +10 -22
  64. package/src/manager/routes/special/electron-client/post.js +72 -0
  65. package/src/manager/routes/test/authenticate/get.js +8 -0
  66. package/src/manager/routes/test/health/get.js +13 -0
  67. package/src/manager/routes/test/index.js +2 -26
  68. package/src/manager/routes/test/lab/post.js +17 -0
  69. package/src/manager/routes/test/redirect/get.js +9 -0
  70. package/src/manager/routes/test/schema/post.js +20 -0
  71. package/src/manager/routes/test/usage/post.js +50 -0
  72. package/src/manager/routes/test/webhook/post.js +21 -0
  73. package/src/manager/routes/user/api-keys/post.js +49 -0
  74. package/src/manager/routes/user/delete.js +71 -0
  75. package/src/manager/routes/user/feedback/post.js +85 -0
  76. package/src/manager/routes/user/get.js +14 -0
  77. package/src/manager/routes/user/oauth2/post.js +326 -0
  78. package/src/manager/routes/user/oauth2/providers/discord.js +47 -0
  79. package/src/manager/routes/user/oauth2/providers/google.js +38 -0
  80. package/src/manager/routes/user/sessions/delete.js +93 -0
  81. package/src/manager/routes/user/sessions/get.js +38 -0
  82. package/src/manager/routes/user/settings/validate/post.js +85 -0
  83. package/src/manager/routes/user/signup/post.js +185 -0
  84. package/src/manager/routes/user/subscription/get.js +61 -0
  85. package/src/manager/routes/user/token/post.js +28 -0
  86. package/src/manager/schemas/admin/backup/post.js +6 -0
  87. package/src/manager/schemas/admin/cron/post.js +6 -0
  88. package/src/manager/schemas/admin/database/get.js +7 -0
  89. package/src/manager/schemas/admin/database/post.js +12 -0
  90. package/src/manager/schemas/admin/email/post.js +20 -0
  91. package/src/manager/schemas/admin/firestore/get.js +7 -0
  92. package/src/manager/schemas/admin/firestore/post.js +22 -0
  93. package/src/manager/schemas/admin/firestore/query/post.js +7 -0
  94. package/src/manager/schemas/admin/hook/post.js +6 -0
  95. package/src/manager/schemas/admin/notification/post.js +21 -0
  96. package/src/manager/schemas/admin/payment/post.js +6 -0
  97. package/src/manager/schemas/admin/post/post.js +20 -0
  98. package/src/manager/schemas/admin/post/put.js +11 -0
  99. package/src/manager/schemas/admin/repo/content/post.js +10 -0
  100. package/src/manager/schemas/admin/stats/get.js +7 -0
  101. package/src/manager/schemas/admin/users/sync/post.js +6 -0
  102. package/src/manager/schemas/content/post/get.js +6 -0
  103. package/src/manager/schemas/firebase/providers/get.js +6 -0
  104. package/src/manager/schemas/general/email/post.js +8 -0
  105. package/src/manager/schemas/general/uuid/post.js +22 -0
  106. package/src/manager/schemas/handler/post/post.js +11 -0
  107. package/src/manager/schemas/marketing/contact/delete.js +7 -0
  108. package/src/manager/schemas/marketing/contact/post.js +13 -0
  109. package/src/manager/schemas/restart/index.js +6 -13
  110. package/src/manager/schemas/special/electron-client/post.js +9 -0
  111. package/src/manager/schemas/test/authenticate/get.js +1 -0
  112. package/src/manager/schemas/test/health/get.js +1 -0
  113. package/src/manager/schemas/test/index.js +6 -13
  114. package/src/manager/schemas/test/lab/post.js +1 -0
  115. package/src/manager/schemas/test/redirect/get.js +7 -0
  116. package/src/manager/schemas/test/schema/post.js +145 -0
  117. package/src/manager/schemas/test/usage/post.js +7 -0
  118. package/src/manager/schemas/test/webhook/post.js +17 -0
  119. package/src/manager/schemas/user/api-keys/post.js +12 -0
  120. package/src/manager/schemas/user/delete.js +7 -0
  121. package/src/manager/schemas/user/feedback/post.js +22 -0
  122. package/src/manager/schemas/user/get.js +1 -0
  123. package/src/manager/schemas/user/oauth2/post.js +77 -0
  124. package/src/manager/schemas/user/sessions/delete.js +12 -0
  125. package/src/manager/schemas/user/sessions/get.js +12 -0
  126. package/src/manager/schemas/user/settings/validate/post.js +22 -0
  127. package/src/manager/schemas/user/signup/post.js +22 -0
  128. package/src/manager/schemas/user/subscription/get.js +7 -0
  129. package/src/manager/schemas/user/token/post.js +7 -0
  130. package/src/test/run-tests.js +5 -2
  131. package/src/test/runner.js +123 -8
  132. package/src/test/test-accounts.js +113 -53
  133. package/src/test/utils/http-client.js +116 -131
  134. package/templates/_.env +11 -7
  135. package/test/_init/accounts-validation.js +58 -0
  136. package/test/_legacy/ai/index.js +231 -0
  137. package/test/_legacy/ai/test.jpg +0 -0
  138. package/test/_legacy/ai/test.pdf +0 -0
  139. package/test/_legacy/cli-commands.test.js +224 -0
  140. package/test/_legacy/index.js +31 -0
  141. package/test/_legacy/payment-resolver/chargebee/orders/refunded.json +0 -0
  142. package/test/_legacy/payment-resolver/chargebee/orders/unpaid.json +98 -0
  143. package/test/_legacy/payment-resolver/chargebee/subscriptions/active.json +578 -0
  144. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-in-trial.json +38 -0
  145. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-skipped-to-active.json +135 -0
  146. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-active-to-cancelled.json +42 -0
  147. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-active.json +44 -0
  148. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-cancelled.json +39 -0
  149. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-refund.json +143 -0
  150. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-suspended.json +48 -0
  151. package/test/_legacy/payment-resolver/coinbase/orders/regular.json +204 -0
  152. package/test/_legacy/payment-resolver/coinbase/subscriptions/cancelled.json +204 -0
  153. package/test/_legacy/payment-resolver/coinbase/subscriptions/paid-2.json +125 -0
  154. package/test/_legacy/payment-resolver/index.js +1558 -0
  155. package/test/_legacy/payment-resolver/paypal/orders/refunded.json +0 -0
  156. package/test/_legacy/payment-resolver/paypal/orders/regular.json +120 -0
  157. package/test/_legacy/payment-resolver/paypal/subscriptions/active-refund-previous-stmnt.json +323 -0
  158. package/test/_legacy/payment-resolver/paypal/subscriptions/active.json +192 -0
  159. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-in-trial.json +125 -0
  160. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-payment-not-complete.json +76 -0
  161. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-payment-overdue-2.json +114 -0
  162. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-payment-overdue.json +114 -0
  163. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-active-to-cancelled.json +111 -0
  164. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-active.json +132 -0
  165. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-cancelled.json +107 -0
  166. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-expired.json +91 -0
  167. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-refund.json +147 -0
  168. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-suspended.json +120 -0
  169. package/test/_legacy/payment-resolver/stripe/orders/refunded.json +0 -0
  170. package/test/_legacy/payment-resolver/stripe/orders/regular.json +91 -0
  171. package/test/_legacy/payment-resolver/stripe/subscriptions/active.json +421 -0
  172. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-in-trial.json +349 -0
  173. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-active-failed-authorization.json +430 -0
  174. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-active.json +319 -0
  175. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-cancelled.json +319 -0
  176. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-refund.json +414 -0
  177. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-suspended.json +319 -0
  178. package/test/_legacy/payment-resolver/stripe/subscriptions/unsure.json +339 -0
  179. package/test/_legacy/test-new +1178 -0
  180. package/test/_legacy/test.md +89 -0
  181. package/test/_legacy/usage.js +170 -0
  182. package/test/_legacy/user.js +31 -0
  183. package/test/functions/admin/database-read.js +148 -0
  184. package/test/functions/admin/database-write.js +173 -0
  185. package/test/functions/admin/edit-post.js +366 -0
  186. package/test/functions/admin/firestore-query.js +221 -0
  187. package/test/functions/admin/firestore-read.js +144 -0
  188. package/test/functions/admin/firestore-write.js +116 -0
  189. package/test/functions/admin/get-stats.js +115 -0
  190. package/test/functions/admin/send-email.js +206 -0
  191. package/test/functions/admin/send-notification.js +208 -0
  192. package/test/functions/admin/write-repo-content.js +223 -0
  193. package/test/functions/general/add-marketing-contact.js +373 -0
  194. package/test/functions/general/fetch-post.js +54 -0
  195. package/test/functions/general/generate-uuid.js +114 -0
  196. package/test/functions/test/authenticate.js +64 -0
  197. package/test/functions/user/create-custom-token.js +73 -0
  198. package/test/functions/user/delete.js +135 -0
  199. package/test/functions/user/get-active-sessions.js +111 -0
  200. package/test/functions/user/get-subscription-info.js +105 -0
  201. package/test/functions/user/regenerate-api-keys.js +156 -0
  202. package/test/functions/user/resolve.js +52 -0
  203. package/test/functions/user/sign-out-all-sessions.js +94 -0
  204. package/test/functions/user/sign-up.js +214 -0
  205. package/test/functions/user/submit-feedback.js +104 -0
  206. package/test/functions/user/validate-settings.js +82 -0
  207. package/test/routes/admin/database.js +144 -0
  208. package/test/routes/admin/email.js +163 -0
  209. package/test/routes/admin/firestore-query.js +217 -0
  210. package/test/routes/admin/firestore.js +141 -0
  211. package/test/routes/admin/notification.js +198 -0
  212. package/test/routes/admin/post.js +366 -0
  213. package/test/routes/admin/repo-content.js +223 -0
  214. package/test/routes/admin/stats.js +112 -0
  215. package/test/routes/content/post.js +54 -0
  216. package/test/routes/general/uuid.js +115 -0
  217. package/test/routes/marketing/contact.js +375 -0
  218. package/test/routes/test/authenticate.js +59 -0
  219. package/test/routes/test/schema.js +554 -0
  220. package/test/routes/test/usage.js +228 -0
  221. package/test/routes/user/api-keys.js +156 -0
  222. package/test/routes/user/delete.js +136 -0
  223. package/test/routes/user/feedback.js +104 -0
  224. package/test/routes/user/sessions.js +199 -0
  225. package/test/routes/user/settings-validate.js +82 -0
  226. package/test/routes/user/signup.js +214 -0
  227. package/test/routes/user/subscription.js +105 -0
  228. package/test/routes/user/token.js +73 -0
  229. package/test/routes/user/user.js +153 -0
  230. package/test/rules/notifications.js +410 -0
  231. package/test/rules/user.js +366 -0
  232. package/.gitignore FROM BEM TEMPALTE +0 -74
  233. package/_backup/analytics copy.js +0 -217
  234. package/_backup/assistant-old-auth.js +0 -116
  235. package/_backup/cli.js +0 -1435
  236. package/_backup/create-post-old.js +0 -215
  237. package/_backup/get-providers copy.js +0 -154
  238. package/firebase-debug.log +0 -444
  239. package/src/manager/functions/core/actions/api/handler/create-post.js +0 -145
  240. package/src/manager/functions/core/cron/daily/ghostii-auto-publisher.js +0 -376
  241. package/src/manager/functions/core/cron/daily/reset-usage.js +0 -173
  242. package/src/manager/functions/core/cron/daily.js +0 -114
  243. package/src/manager/functions/core/events/auth/before-create.js +0 -79
  244. package/src/manager/functions/core/events/auth/before-signin.js +0 -69
  245. package/src/manager/functions/core/events/auth/on-create.js +0 -360
  246. package/src/manager/functions/core/events/auth/on-delete.js +0 -135
  247. package/src/manager/functions/core/events/firestore/notifications/on-write.js +0 -118
  248. /package/src/cli/{cli-refactored.js → index.js} +0 -0
  249. /package/src/manager/functions/{core → _legacy}/actions/generate-uuid.js +0 -0
  250. /package/src/manager/functions/{core → _legacy}/actions/sign-up-handler.js +0 -0
  251. /package/src/manager/functions/{core → _legacy}/admin/create-post.js +0 -0
  252. /package/src/manager/functions/{core → _legacy}/admin/firestore-write.js +0 -0
  253. /package/src/manager/functions/{core → _legacy}/admin/get-stats.js +0 -0
  254. /package/src/manager/functions/{core → _legacy}/admin/query.js +0 -0
  255. /package/src/manager/functions/{core → _legacy}/admin/send-notification.js +0 -0
  256. /package/src/manager/functions/{core/actions/api → _legacy}/template.js +0 -0
  257. /package/src/manager/functions/{test → _legacy/test}/authenticate.js +0 -0
  258. /package/src/manager/functions/{test → _legacy/test}/webhook.js +0 -0
@@ -0,0 +1,32 @@
1
+ version: "2" # required to adjust maintainability checks
2
+ checks:
3
+ # argument-count:
4
+ # config:
5
+ # threshold: 4
6
+ # complex-logic:
7
+ # config:
8
+ # threshold: 4
9
+ # file-lines:
10
+ # config:
11
+ # threshold: 250
12
+ # method-complexity:
13
+ # config:
14
+ # threshold: 5
15
+ # method-count:
16
+ # config:
17
+ # threshold: 20
18
+ method-lines:
19
+ config:
20
+ threshold: 200
21
+ # nested-control-flow:
22
+ # config:
23
+ # threshold: 4
24
+ # return-statements:
25
+ # config:
26
+ # threshold: 4
27
+ # similar-code:
28
+ # config:
29
+ # threshold: # language-specific defaults. an override will affect all languages.
30
+ identical-code:
31
+ config:
32
+ threshold: 10
package/CHANGELOG.md CHANGED
@@ -14,6 +14,31 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
14
14
  - `Fixed` for any bug fixes.
15
15
  - `Security` in case of vulnerabilities.
16
16
 
17
+ # [5.0.39] - 2025-01-12
18
+ ### Added
19
+ - New test infrastructure with Firebase emulator support for reliable, isolated testing.
20
+ - Test runner with emulator auto-detection and startup.
21
+ - Test types: standalone, suite (sequential with shared state), group (independent).
22
+ - Built-in test accounts with SSOT configuration (basic, admin, premium-active, etc.).
23
+ - Firestore security rules testing support.
24
+ - HTTP client with auth helpers (`http.as('admin').command()`).
25
+ - Rich assertion library (`isSuccess`, `isError`, `hasProperty`, etc.).
26
+ - New `bm emulators` command for standalone emulator management.
27
+ - Enhanced `bm test` with path filtering and parallel test support.
28
+
29
+ ### Changed
30
+ - Reorganized test files to `test/functions/` with `admin/`, `user/`, `general/` categories.
31
+ - Standardized auth test naming to `unauthenticated-rejected`.
32
+ - Auth rejection tests moved to end of test files (before cleanup).
33
+
34
+ ### Fixed
35
+ - Changed unauthenticated API error from 500 to 401 with proper "Authentication required" message.
36
+
37
+ ### Removed
38
+ - Removed legacy test files (moved to `test/_legacy/`).
39
+ - Removed deprecated CLI files and templates.
40
+ - Consolidated test account creation from API endpoint to test runner.
41
+
17
42
  # [5.0.31] - 2025-01-17
18
43
  ### Changed
19
44
  - Refactored CLI to modular command architecture with individual command classes and test files for better maintainability.
package/CLAUDE.md CHANGED
@@ -71,7 +71,7 @@ src/
71
71
  routes/ # Built-in routes
72
72
  schemas/ # Built-in schemas
73
73
  cli/
74
- cli-refactored.js # CLI entry point
74
+ index.js # CLI entry point
75
75
  commands/ # CLI commands
76
76
  templates/
77
77
  backend-manager-config.json # Config template
@@ -521,13 +521,14 @@ assert.fail(message) // Explicit fail
521
521
  | Batch utilities | `src/manager/helpers/utilities.js` |
522
522
  | Main API handler | `src/manager/functions/core/actions/api.js` |
523
523
  | Config template | `templates/backend-manager-config.json` |
524
- | CLI entry | `src/cli/cli-refactored.js` |
524
+ | CLI entry | `src/cli/index.js` |
525
525
 
526
526
  ## Environment Detection
527
527
 
528
528
  ```javascript
529
529
  assistant.isDevelopment() // true when ENVIRONMENT !== 'production' or in emulator
530
530
  assistant.isProduction() // true when ENVIRONMENT === 'production'
531
+ assistant.isTesting() // true when running tests (via npx bm test)
531
532
  ```
532
533
 
533
534
  ## Response Headers
package/README.md CHANGED
@@ -447,6 +447,7 @@ assistant.debug('Debug message');
447
447
  // Environment
448
448
  assistant.isDevelopment(); // true in emulator
449
449
  assistant.isProduction(); // true in production
450
+ assistant.isTesting(); // true when running tests
450
451
 
451
452
  // File uploads
452
453
  const { fields, files } = await assistant.parseMultipartFormData();
@@ -65,8 +65,12 @@ I would like each new route to have a great name clearly indicating its purpose,
65
65
 
66
66
  Since we can build this new API system however we want, i also expect you to rewrite and refactor the BEM api endppints to be kickass, modern, and well designed.
67
67
 
68
+ Also, certain VERBS should be removed from the actual file/function names since they are implied by the HTTP method. For example, instead of having a generate-uuid.js file, we could just have uuid.js since the POST method implies that we are generating/creating a new one, or insetad of add-marketing-contact we could just have marketing-contact.js since the POST method implies adding a new one and GET would imply fetching them.
68
69
 
70
+ Next, some fucntions have a lot crammed isnide them that could use some separation. For example, in add-marketing-contact.js we have code for handling multiple email providers (SendGrid, Beehiiv) all jammed into a single file. I think we should refactor this to have a separate file for each provider in subfolder that handles the specifics of that provider, and then the main route file just calls those provider-specific files as needed. This will make it much easier to maintain and extend in the future as we add more providers.
69
71
 
70
-
71
-
72
+ Also, sometimes there are two endpoints that should be combined,for example
73
+ * /Users/ian/Developer/Repositories/ITW-Creative-Works/backend-manager/src/manager/functions/core/actions/api/general/add-marketing-contact.js
74
+ * /Users/ian/Developer/Repositories/ITW-Creative-Works/backend-manager/src/manager/functions/core/actions/api/general/remove-marketing-contact.js
75
+ These could be combined into a single marketing-contact.js file that handles both adding and removing based on the HTTP method (POST for add, DELETE for remove). This will make the API more RESTful and easier to understand.
72
76
 
package/TODO.md CHANGED
@@ -26,3 +26,152 @@ BEM
26
26
 
27
27
  ADD HEALTHCHECK TO BEM!!!
28
28
  ✗ https://api.clockii.com/backend-manager?command=healthcheck → fetch failed
29
+
30
+ TODO
31
+
32
+ # TEST REWRRK
33
+ btw... the account.json needs to be removed. remove it from BEM and the consumong project. when we make our test system, we DO NOT NEED TO STORE THE ACCOUBT IN A JSON file. we just need a source of truth in BEM for what uid/emails to look for
34
+
35
+ need a "projectScripts" that creates the npm start, npm test, etc scripts in the consuming project.
36
+
37
+ during test run, we need to check that the test accounts exist. we also need to ALWAYS reset them at the begining of the run to ensure they are in the proper format, specifically by resetting their usage, roles, amd subscriptipn
38
+
39
+ needa new account type that we can change the subscription level of to test the sub events? maybe _test-upgrade that starts free and we test how it progresses from free to paid to canceled?
40
+
41
+ # Use gitignore from backend-manager-tempalte
42
+
43
+ # CLEAN
44
+ having different ID and UID is messy and annoying, just make it the same
45
+ admin: {
46
+ id: 'admin',
47
+ uid: '_test-admin',
48
+ email: '_test.admin@{domain}',
49
+
50
+ should be "admin" for ALL
51
+
52
+
53
+ # TODO
54
+ Update deps
55
+ Remove unused deps like
56
+ * node-fetch
57
+
58
+ # MOVE LEGACY BEM INDIVIDUAL FUNCTIONS TO AN _OLD FOLDER
59
+ /Users/ian/Developer/Repositories/ITW-Creative-Works/backend-manager/src/manager/functions/core/actions/create-post-handler.js
60
+ /Users/ian/Developer/Repositories/ITW-Creative-Works/backend-manager/src/manager/functions/core/actions/generate-uuid.js
61
+ /Users/ian/Developer/Repositories/ITW-Creative-Works/backend-manager/src/manager/functions/core/actions/sign-up-handler.js
62
+ /Users/ian/Developer/Repositories/ITW-Creative-Works/backend-manager/src/manager/functions/test
63
+ /Users/ian/Developer/Repositories/ITW-Creative-Works/backend-manager/src/manager/functions/core/admin
64
+
65
+ # Things to deprecate
66
+ * api manager
67
+
68
+ # Email
69
+ Move the sendEmail function to HERE instead of calling ITW
70
+
71
+ # BEM TESTS
72
+ # User Auth rules
73
+ * User can only access their own user doc
74
+ * fails when trying to access another user's doc
75
+ * succeeds when accessing their own,
76
+ * fails when NOT authed
77
+ * fails when trying to perform an admin action (can create/use the test:admin http event to test this)
78
+ * fails when a user tries to write to a restricted field/key like roles, subscription, usage, etc (SEE RULES)
79
+ * Any other rules: /Users/ian/Developer/Repositories/ITW-Creative-Works/backend-manager/templates/firestore.rules
80
+ * User can delete their own user doc (can use _test-delete via user:delete http event)
81
+ * fails when trying to delete another user's doc
82
+ * succeeds when deleting their own,
83
+ * fails when NOT authed
84
+
85
+ # Signup Flow
86
+ * Create a referrer user and a referred user then use user:signup http event on the referred user to test that
87
+ * the referral code works and the bonus is applied to the referrer
88
+ * something is added to the account to denote it received the signup event??? not sure
89
+ * context data is added to the referred user's account
90
+ * a second signup event
91
+
92
+ # Test
93
+ * test:authenticate http event
94
+ * success when valid uid
95
+ * fail when invalid uid
96
+ * fail when no uid
97
+ * test admin
98
+ * all admin functions fail when not authed as admin
99
+ * test each admin function
100
+
101
+ # Usage
102
+ * Using an account with a specific plan, test that the usage limits are enforced
103
+ * success if user has access to the plan
104
+ * success if the user is within the usage limits
105
+ * fail if the user exceeds the usage limits
106
+ * fail if the user is on a plan that does not allow usage (free plan)
107
+ * successful usage should increment the usage count
108
+
109
+ # Events
110
+ * trigger cron daily
111
+ * it should reset temp usage for all users
112
+ * reset userdocs to current usage = 0
113
+
114
+ # Payment
115
+ * test subscription upgrade flow via http event
116
+ * start with free plan
117
+ * upgrade to paid plan
118
+ * verify plan is updated
119
+ * verify usage limits are updated
120
+ * downgrade back to free plan
121
+ * verify plan is updated
122
+ * verify usage limits are updated
123
+
124
+ # BEM API
125
+ # Admin
126
+ * All functions need to fail when not authed as admin
127
+ * then test each one
128
+
129
+ # Advanced
130
+ * test sub accounts
131
+
132
+ # payment system
133
+ * dont store a "resolved" status, but make a universal library that frontend and backend use to determine whther a user has access to a plan currently
134
+
135
+ # New bem api and test to make
136
+ # test:usage
137
+ * the fnction itself just utilizes the usage API to increment an arbitrary usage item
138
+
139
+ the test should check the usage storage and ensure that it was incremented successfully
140
+
141
+ # test for bm_cronDaily
142
+ then, we need to test the bm_cronDaily function to ensure that it does its things like clearing the usage storage properly
143
+
144
+ # test for schema
145
+ * a comprehesive schema with fields that test EVERY possible field type including required, default, min, max, "value" etc (both static and FUNCTIONS)
146
+ * test multiple plan levels including unathenticated, basic, & premium
147
+
148
+ # quetsion
149
+ * how does usage get reset? does it reset daily? or monthly?
150
+ * how do we set usage limits?
151
+ * per plan? or when users prmeium is updated do we set it inside their doc?
152
+ * if we store it per plan, where should we store the plan data? firestore or in code?
153
+ * if its firestore we will use lots of reads, if we store in code we have to push new code to update plan limits (not a huge issue)
154
+
155
+
156
+ change name from routes/content/post to maye routes/blog/post??? whatfdo you think??
157
+
158
+
159
+ # MIGRATIONS
160
+ ## user
161
+ affiliate: {
162
+ referrer: affiliateCode,
163
+ },
164
+ -->
165
+ attribution.affiliate.code
166
+
167
+
168
+
169
+
170
+ ## OLD TESTS
171
+ "_test": "npm run prepare && ./node_modules/mocha/bin/mocha test/ --recursive --timeout=10000",
172
+ "test": "./node_modules/mocha/bin/mocha test/ --recursive --timeout=10000",
173
+ "test:cli": "./node_modules/mocha/bin/mocha test/cli-commands.test.js --timeout=10000",
174
+ "test:usage": "./node_modules/mocha/bin/mocha test/usage.js --timeout=10000",
175
+ "test:payment-resolver": "./node_modules/mocha/bin/mocha test/payment-resolver/index.js --timeout=10000",
176
+ "test:user": "./node_modules/mocha/bin/mocha test/user.js --timeout=10000",
177
+ "test:ai": "./node_modules/mocha/bin/mocha test/ai/index.js --timeout=10000",
package/bin/bem CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- let Main = new (require('../src/cli/cli-refactored.js'))(process.argv);
2
+ let Main = new (require('../src/cli/index.js'))(process.argv);
3
3
  (async function() {
4
4
  'use strict';
5
5
  await Main.process(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-manager",
3
- "version": "5.0.39",
3
+ "version": "5.0.41",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -44,6 +44,7 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@firebase/rules-unit-testing": "^5.0.0",
47
+ "@google-cloud/pubsub": "^5.2.2",
47
48
  "@google-cloud/storage": "^7.16.0",
48
49
  "@octokit/rest": "^19.0.13",
49
50
  "@sendgrid/mail": "^7.7.0",
@@ -42,11 +42,12 @@ class BaseCommand {
42
42
  .filter(([_, port]) => port)
43
43
  .map(([name, port]) => ({ name, port }));
44
44
 
45
+ // Collect ALL processes on each blocked port
45
46
  const blockedPorts = [];
46
47
  for (const { name, port } of portsToCheck) {
47
- const processInfo = this.getProcessOnPort(port);
48
- if (processInfo) {
49
- blockedPorts.push({ name, port, ...processInfo });
48
+ const processes = this.getProcessesOnPort(port);
49
+ if (processes) {
50
+ blockedPorts.push({ name, port, processes });
50
51
  }
51
52
  }
52
53
 
@@ -55,9 +56,11 @@ class BaseCommand {
55
56
  }
56
57
 
57
58
  this.log(chalk.yellow('\n The following ports are in use:'));
58
- for (const { name, port, pid, processName, command } of blockedPorts) {
59
- const cmdInfo = command ? ` ${command}` : '';
60
- this.log(chalk.gray(` - ${name} emulator (port ${port}) - PID ${pid} (${processName})${cmdInfo}`));
59
+ for (const { name, port, processes } of blockedPorts) {
60
+ for (const { pid, processName, command } of processes) {
61
+ const cmdInfo = command ? ` ${command}` : '';
62
+ this.log(chalk.gray(` - ${name} emulator (port ${port}) - PID ${pid} (${processName})${cmdInfo}`));
63
+ }
61
64
  }
62
65
 
63
66
  const { shouldKill } = await inquirer.prompt([{
@@ -72,13 +75,19 @@ class BaseCommand {
72
75
  return false;
73
76
  }
74
77
 
75
- for (const { name, port, pid } of blockedPorts) {
76
- try {
77
- process.kill(pid, 'SIGTERM');
78
- this.log(chalk.green(` ✓ Killed process ${pid} on port ${port} (${name})`));
79
- } catch (error) {
80
- this.logError(` Failed to kill process ${pid}: ${error.message}`);
81
- return false;
78
+ // Kill ALL processes on each blocked port
79
+ for (const { name, port, processes } of blockedPorts) {
80
+ for (const { pid } of processes) {
81
+ try {
82
+ process.kill(pid, 'SIGKILL');
83
+ this.log(chalk.green(` Killed process ${pid} on port ${port} (${name})`));
84
+ } catch (error) {
85
+ // ESRCH means process already dead - that's fine
86
+ if (error.code !== 'ESRCH') {
87
+ this.logError(` ✗ Failed to kill process ${pid}: ${error.message}`);
88
+ return false;
89
+ }
90
+ }
82
91
  }
83
92
  }
84
93
 
@@ -88,39 +97,57 @@ class BaseCommand {
88
97
  }
89
98
 
90
99
  /**
91
- * Get info about a process using a specific port
100
+ * Get info about ALL processes using a specific port
92
101
  * @param {number} port - Port number to check
93
- * @returns {object|null} - Process info if port is in use, null otherwise
102
+ * @returns {object[]|null} - Array of process info if port is in use, null otherwise
94
103
  */
95
- getProcessOnPort(port) {
104
+ getProcessesOnPort(port) {
96
105
  try {
97
106
  const result = execSync(`lsof -ti:${port} 2>/dev/null`, { encoding: 'utf8' });
98
- const pid = parseInt(result.trim().split('\n')[0], 10);
99
- if (isNaN(pid)) {
107
+ const pids = result.trim().split('\n')
108
+ .map(line => parseInt(line.trim(), 10))
109
+ .filter(pid => !isNaN(pid));
110
+
111
+ if (pids.length === 0) {
100
112
  return null;
101
113
  }
102
114
 
103
- // Get more info about the process
104
- let processName = 'unknown';
105
- let command = '';
106
- try {
107
- const psResult = execSync(`ps -p ${pid} -o comm=,args= 2>/dev/null`, { encoding: 'utf8' });
108
- const parts = psResult.trim().split(/\s+/);
109
- processName = parts[0] || 'unknown';
110
- command = parts.slice(1).join(' ').substring(0, 100);
111
- if (command.length === 100) {
112
- command += '...';
115
+ // Get unique PIDs (lsof can return duplicates for multiple connections)
116
+ const uniquePids = [...new Set(pids)];
117
+
118
+ const processes = uniquePids.map(pid => {
119
+ let processName = 'unknown';
120
+ let command = '';
121
+ try {
122
+ const psResult = execSync(`ps -p ${pid} -o comm=,args= 2>/dev/null`, { encoding: 'utf8' });
123
+ const parts = psResult.trim().split(/\s+/);
124
+ processName = parts[0] || 'unknown';
125
+ command = parts.slice(1).join(' ').substring(0, 100);
126
+ if (command.length === 100) {
127
+ command += '...';
128
+ }
129
+ } catch (e) {
130
+ // Ignore - just use defaults
113
131
  }
114
- } catch (e) {
115
- // Ignore - just use defaults
116
- }
132
+ return { pid, processName, command };
133
+ });
117
134
 
118
- return { pid, processName, command };
135
+ return processes.length > 0 ? processes : null;
119
136
  } catch (error) {
120
137
  return null;
121
138
  }
122
139
  }
123
140
 
141
+ /**
142
+ * Get info about a process using a specific port (returns first process only for backwards compatibility)
143
+ * @param {number} port - Port number to check
144
+ * @returns {object|null} - Process info if port is in use, null otherwise
145
+ */
146
+ getProcessOnPort(port) {
147
+ const processes = this.getProcessesOnPort(port);
148
+ return processes ? processes[0] : null;
149
+ }
150
+
124
151
  /**
125
152
  * Check if a port is in use
126
153
  * @param {number} port - Port number to check
@@ -9,39 +9,60 @@ const { DEFAULT_EMULATOR_PORTS } = require('./setup-tests/emulators-config');
9
9
 
10
10
  class EmulatorsCommand extends BaseCommand {
11
11
  async execute() {
12
- const projectDir = this.main.firebaseProjectPath;
13
-
14
- // Load emulator ports from firebase.json
15
- const emulatorPorts = this.loadEmulatorPorts(projectDir);
16
-
17
- // Check for port conflicts before starting emulators
18
- const canProceed = await this.checkAndKillBlockingProcesses(emulatorPorts);
19
- if (!canProceed) {
20
- return;
21
- }
22
-
23
12
  this.log(chalk.cyan('\n Starting Firebase emulators (keep-alive mode)...\n'));
24
13
  this.log(chalk.gray(' Emulators will stay running until you press Ctrl+C\n'));
25
14
 
15
+ // Warn if TEST_EXTENDED_MODE is enabled
16
+ if (process.env.TEST_EXTENDED_MODE) {
17
+ this.log(chalk.yellow.bold('\n ⚠️⚠️⚠️ WARNING: TEST_EXTENDED_MODE IS TRUE ⚠️⚠️⚠️'));
18
+ this.log(chalk.yellow(' External API calls (emails, SendGrid, etc.) are ENABLED!'));
19
+ this.log(chalk.yellow(' This will send real emails and make real API calls.\n'));
20
+ }
21
+
26
22
  // Start BEM watcher in background
27
23
  const watcher = new WatchCommand(this.main);
28
24
  watcher.startBackground();
29
25
 
30
- // Start emulators with a long-running command to keep them alive
31
- // BEM_TESTING=true is passed so Functions skip external API calls (emails, SendGrid)
32
- const emulatorsCommand = `BEM_TESTING=true firebase emulators:exec --only functions,firestore,auth,database --ui 'echo ""; echo "Emulators ready. Press Ctrl+C to shut down..."; sleep 86400'`;
26
+ // Run emulators with keep-alive command (use single quotes since runWithEmulators wraps in double quotes)
27
+ const keepAliveCommand = "echo ''; echo 'Emulators ready. Press Ctrl+C to shut down...'; sleep 86400";
33
28
 
34
29
  try {
35
- await powertools.execute(emulatorsCommand, {
36
- log: true,
37
- cwd: projectDir,
38
- });
30
+ await this.runWithEmulators(keepAliveCommand);
39
31
  } catch (error) {
40
32
  // User pressed Ctrl+C - this is expected
41
33
  this.log(chalk.gray('\n Emulators stopped.\n'));
42
34
  }
43
35
  }
44
36
 
37
+ /**
38
+ * Run a command with Firebase emulators
39
+ * @param {string} command - The command to execute inside emulators:exec
40
+ * @returns {Promise<void>}
41
+ */
42
+ async runWithEmulators(command) {
43
+ const projectDir = this.main.firebaseProjectPath;
44
+
45
+ // Load emulator ports from firebase.json
46
+ const emulatorPorts = this.loadEmulatorPorts(projectDir);
47
+
48
+ // Check for port conflicts before starting emulators
49
+ const canProceed = await this.checkAndKillBlockingProcesses(emulatorPorts);
50
+ if (!canProceed) {
51
+ throw new Error('Port conflicts could not be resolved');
52
+ }
53
+
54
+ // BEM_TESTING=true is passed so Functions skip external API calls (emails, SendGrid)
55
+ // hosting is included so localhost:5002 rewrites work (e.g., /backend-manager -> bm_api)
56
+ // pubsub is included so scheduled functions (bm_cronDaily) can be triggered in tests
57
+ // Use double quotes for command wrapper since the command may contain single quotes (JSON strings)
58
+ const emulatorsCommand = `BEM_TESTING=true firebase emulators:exec --only functions,firestore,auth,database,hosting,pubsub --ui "${command}"`;
59
+
60
+ await powertools.execute(emulatorsCommand, {
61
+ log: true,
62
+ cwd: projectDir,
63
+ });
64
+ }
65
+
45
66
  /**
46
67
  * Load emulator ports from firebase.json or use defaults
47
68
  */
@@ -10,6 +10,7 @@ const DEFAULT_EMULATOR_PORTS = {
10
10
  database: 9000,
11
11
  hosting: 5002,
12
12
  storage: 9199,
13
+ pubsub: 8085,
13
14
  ui: 4000,
14
15
  };
15
16
 
@@ -20,6 +21,7 @@ const REQUIRED_EMULATORS = {
20
21
  database: { port: DEFAULT_EMULATOR_PORTS.database },
21
22
  hosting: { port: DEFAULT_EMULATOR_PORTS.hosting },
22
23
  storage: { port: DEFAULT_EMULATOR_PORTS.storage },
24
+ pubsub: { port: DEFAULT_EMULATOR_PORTS.pubsub },
23
25
  ui: { enabled: true },
24
26
  };
25
27
 
@@ -1,5 +1,6 @@
1
1
  const BaseTest = require('./base-test');
2
2
  const jetpack = require('fs-jetpack');
3
+ const _ = require('lodash');
3
4
 
4
5
  class HostingRewritesTest extends BaseTest {
5
6
  getName() {
@@ -5,6 +5,7 @@ const jetpack = require('fs-jetpack');
5
5
  const JSON5 = require('json5');
6
6
  const powertools = require('node-powertools');
7
7
  const { DEFAULT_EMULATOR_PORTS } = require('./setup-tests/emulators-config');
8
+ const EmulatorsCommand = require('./emulators');
8
9
 
9
10
  class TestCommand extends BaseCommand {
10
11
  async execute() {
@@ -28,12 +29,14 @@ class TestCommand extends BaseCommand {
28
29
  }
29
30
 
30
31
  // Build unified test config object
32
+ // Use hosting URL for all API requests (rewrites to bm_api function)
31
33
  const testConfig = {
32
34
  ...projectConfig,
33
- functionsUrl: `http://127.0.0.1:${emulatorPorts.functions}/${projectConfig.projectId}/us-central1`,
35
+ hostingUrl: `http://127.0.0.1:${emulatorPorts.hosting}`,
34
36
  projectDir,
35
37
  testPaths,
36
38
  emulatorPorts,
39
+ includeLegacy: argv.legacy || false, // Include legacy tests from test/functions/
37
40
  };
38
41
 
39
42
  // Build the test command
@@ -47,7 +50,7 @@ class TestCommand extends BaseCommand {
47
50
  await this.runTestsDirectly(testCommand, functionsDir, emulatorPorts);
48
51
  } else {
49
52
  this.log(chalk.cyan('Starting emulators and running tests...'));
50
- await this.runEmulatorTests(testCommand, projectDir, emulatorPorts);
53
+ await this.runEmulatorTests(testCommand);
51
54
  }
52
55
  }
53
56
 
@@ -103,6 +106,7 @@ class TestCommand extends BaseCommand {
103
106
  const projectId = config.firebaseConfig?.projectId;
104
107
  const backendManagerKey = argv.key || process.env.BACKEND_MANAGER_KEY;
105
108
  const appId = config.brand?.id;
109
+ const brandName = config.brand?.name;
106
110
  const githubRepoWebsite = config.github?.repo_website;
107
111
 
108
112
  // Extract domain from brand.contact.email (e.g., 'support@example.com' -> 'example.com')
@@ -131,7 +135,7 @@ class TestCommand extends BaseCommand {
131
135
  return null;
132
136
  }
133
137
 
134
- return { appId, projectId, backendManagerKey, domain, githubRepoWebsite };
138
+ return { appId, projectId, backendManagerKey, domain, brandName, githubRepoWebsite };
135
139
  }
136
140
 
137
141
  /**
@@ -140,9 +144,9 @@ class TestCommand extends BaseCommand {
140
144
  buildTestCommand(testConfig) {
141
145
  const testScriptPath = path.join(__dirname, '..', '..', 'test', 'run-tests.js');
142
146
 
143
- // Pass entire config as JSON, plus emulator hosts
147
+ // Pass entire config as base64-encoded JSON to avoid shell escaping issues
144
148
  const testEnv = {
145
- BEM_TEST_CONFIG: JSON.stringify(testConfig),
149
+ BEM_TEST_CONFIG: Buffer.from(JSON.stringify(testConfig)).toString('base64'),
146
150
  FIRESTORE_EMULATOR_HOST: `127.0.0.1:${testConfig.emulatorPorts.firestore}`,
147
151
  FIREBASE_AUTH_EMULATOR_HOST: `127.0.0.1:${testConfig.emulatorPorts.auth}`,
148
152
  };
@@ -167,7 +171,7 @@ class TestCommand extends BaseCommand {
167
171
  * Run tests directly (emulators already running)
168
172
  */
169
173
  async runTestsDirectly(testCommand, functionsDir, emulatorPorts) {
170
- this.log(chalk.gray(` Functions: http://127.0.0.1:${emulatorPorts.functions}`));
174
+ this.log(chalk.gray(` Hosting: http://127.0.0.1:${emulatorPorts.hosting}`));
171
175
  this.log(chalk.gray(` Firestore: 127.0.0.1:${emulatorPorts.firestore}`));
172
176
  this.log(chalk.gray(` Auth: 127.0.0.1:${emulatorPorts.auth}`));
173
177
  this.log(chalk.gray(` UI: http://127.0.0.1:${emulatorPorts.ui}\n`));
@@ -185,23 +189,14 @@ class TestCommand extends BaseCommand {
185
189
  /**
186
190
  * Run tests with Firebase emulators (starts emulators, runs tests, shuts down)
187
191
  */
188
- async runEmulatorTests(testCommand, projectDir, emulatorPorts) {
189
- // Check for port conflicts before starting emulators
190
- const canProceed = await this.checkAndKillBlockingProcesses(emulatorPorts);
191
- if (!canProceed) {
192
- return;
193
- }
194
-
192
+ async runEmulatorTests(testCommand) {
195
193
  this.log(chalk.gray(' Starting Firebase emulators...\n'));
196
194
 
197
- // BEM_TESTING=true is passed to the emulator so Functions can skip external API calls (emails, SendGrid)
198
- const emulatorsCommand = `BEM_TESTING=true firebase emulators:exec --only functions,firestore,auth,database --ui '${testCommand}'`;
195
+ // Use EmulatorsCommand to run tests with emulators
196
+ const emulatorsCmd = new EmulatorsCommand(this.main);
199
197
 
200
198
  try {
201
- await powertools.execute(emulatorsCommand, {
202
- log: true,
203
- cwd: projectDir,
204
- });
199
+ await emulatorsCmd.runWithEmulators(testCommand);
205
200
  } catch (error) {
206
201
  // Only exit with error if it wasn't a user-initiated exit
207
202
  if (error.code !== 0) {