create-easy-stack 1.0.3

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 (327) hide show
  1. package/bin/create.mjs +244 -0
  2. package/infra/email/files/src/core/config/email.env.ts +73 -0
  3. package/infra/email/files/src/core/infrastructure/email/email-dispatcher.port.ts +13 -0
  4. package/infra/email/files/src/core/infrastructure/email/email-layout.spec.ts +83 -0
  5. package/infra/email/files/src/core/infrastructure/email/email-layout.ts +88 -0
  6. package/infra/email/files/src/core/infrastructure/email/email-sender.factory.spec.ts +87 -0
  7. package/infra/email/files/src/core/infrastructure/email/email-sender.factory.ts +35 -0
  8. package/infra/email/files/src/core/infrastructure/email/email-sender.port.ts +19 -0
  9. package/infra/email/files/src/core/infrastructure/email/email.job.spec.ts +101 -0
  10. package/infra/email/files/src/core/infrastructure/email/email.job.ts +64 -0
  11. package/infra/email/files/src/core/infrastructure/email/email.module.spec.ts +60 -0
  12. package/infra/email/files/src/core/infrastructure/email/email.module.ts +34 -0
  13. package/infra/email/files/src/core/infrastructure/email/log-email-sender.spec.ts +34 -0
  14. package/infra/email/files/src/core/infrastructure/email/log-email-sender.ts +17 -0
  15. package/infra/email/files/src/core/infrastructure/email/smtp-email-sender.spec.ts +99 -0
  16. package/infra/email/files/src/core/infrastructure/email/smtp-email-sender.ts +63 -0
  17. package/infra/email/infra.json +58 -0
  18. package/infra/prisma-postgres/docker.service.yml +12 -0
  19. package/infra/prisma-postgres/files/prisma/schema.prisma +10 -0
  20. package/infra/prisma-postgres/files/prisma.config.mjs +10 -0
  21. package/infra/prisma-postgres/files/src/core/config/database.env.ts +13 -0
  22. package/infra/prisma-postgres/files/src/core/infrastructure/database/database.module.ts +9 -0
  23. package/infra/prisma-postgres/files/src/core/infrastructure/database/prisma.service.ts +26 -0
  24. package/infra/prisma-postgres/infra.json +60 -0
  25. package/infra/rabbitmq/docker.service.yml +16 -0
  26. package/infra/rabbitmq/files/src/core/config/messaging.env.ts +29 -0
  27. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/inline-job-queue.spec.ts +60 -0
  28. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/inline-job-queue.ts +33 -0
  29. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-failure.spec.ts +23 -0
  30. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-failure.ts +19 -0
  31. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-payload-cipher.spec.ts +80 -0
  32. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-payload-cipher.ts +66 -0
  33. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-queue.boundary.spec.ts +34 -0
  34. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-queue.factory.ts +21 -0
  35. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-queue.module.spec.ts +75 -0
  36. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-queue.module.ts +30 -0
  37. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/job-queue.port.ts +59 -0
  38. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/rabbitmq-job-queue.spec.ts +510 -0
  39. package/infra/rabbitmq/files/src/core/infrastructure/messaging/jobs/rabbitmq-job-queue.ts +353 -0
  40. package/infra/rabbitmq/files/src/core/infrastructure/messaging/rabbitmq/rabbitmq.module.ts +9 -0
  41. package/infra/rabbitmq/files/src/core/infrastructure/messaging/rabbitmq/rabbitmq.service.spec.ts +289 -0
  42. package/infra/rabbitmq/files/src/core/infrastructure/messaging/rabbitmq/rabbitmq.service.ts +207 -0
  43. package/infra/rabbitmq/infra.json +55 -0
  44. package/infra/redis/docker.service.yml +10 -0
  45. package/infra/redis/files/src/core/http/decorators/rate-limit.decorator.ts +26 -0
  46. package/infra/redis/files/src/core/http/interceptors/rate-limit.interceptor.spec.ts +180 -0
  47. package/infra/redis/files/src/core/http/interceptors/rate-limit.interceptor.ts +105 -0
  48. package/infra/redis/files/src/core/infrastructure/redis/cache.constants.ts +67 -0
  49. package/infra/redis/files/src/core/infrastructure/redis/idempotency/idempotency.module.ts +9 -0
  50. package/infra/redis/files/src/core/infrastructure/redis/idempotency/idempotency.service.spec.ts +146 -0
  51. package/infra/redis/files/src/core/infrastructure/redis/idempotency/idempotency.service.ts +131 -0
  52. package/infra/redis/files/src/core/infrastructure/redis/lock/lock.module.ts +9 -0
  53. package/infra/redis/files/src/core/infrastructure/redis/lock/lock.service.integration-spec.ts +111 -0
  54. package/infra/redis/files/src/core/infrastructure/redis/lock/lock.service.ts +82 -0
  55. package/infra/redis/files/src/core/infrastructure/redis/rate-limit/rate-limit.module.ts +15 -0
  56. package/infra/redis/files/src/core/infrastructure/redis/rate-limit/rate-limit.service.spec.ts +45 -0
  57. package/infra/redis/files/src/core/infrastructure/redis/rate-limit/rate-limit.service.ts +38 -0
  58. package/infra/redis/files/src/core/infrastructure/redis/redis.module.ts +9 -0
  59. package/infra/redis/files/src/core/infrastructure/redis/redis.service.ts +196 -0
  60. package/infra/redis/infra.json +53 -0
  61. package/infra/storage-cloudinary/files/src/core/config/storage.env.ts +43 -0
  62. package/infra/storage-cloudinary/files/src/core/infrastructure/storage/cloudinary-image-storage.spec.ts +275 -0
  63. package/infra/storage-cloudinary/files/src/core/infrastructure/storage/cloudinary-image-storage.ts +158 -0
  64. package/infra/storage-cloudinary/files/src/core/infrastructure/storage/image-storage.factory.ts +30 -0
  65. package/infra/storage-cloudinary/files/src/core/infrastructure/storage/image-storage.module.ts +17 -0
  66. package/infra/storage-cloudinary/files/src/core/infrastructure/storage/image-storage.port.ts +38 -0
  67. package/infra/storage-cloudinary/files/src/core/infrastructure/storage/image-type.spec.ts +48 -0
  68. package/infra/storage-cloudinary/files/src/core/infrastructure/storage/image-type.ts +25 -0
  69. package/infra/storage-cloudinary/files/src/core/infrastructure/storage/unconfigured-image-storage.ts +19 -0
  70. package/infra/storage-cloudinary/infra.json +73 -0
  71. package/lib/fs-utils.mjs +115 -0
  72. package/lib/registry.mjs +50 -0
  73. package/lib/scaffold.mjs +157 -0
  74. package/package.json +37 -0
  75. package/templates/nest-ddd/files/.env.example +5 -0
  76. package/templates/nest-ddd/files/.husky/commit-msg +1 -0
  77. package/templates/nest-ddd/files/.husky/pre-commit +1 -0
  78. package/templates/nest-ddd/files/.oxlintrc.json +10 -0
  79. package/templates/nest-ddd/files/.prettierrc +4 -0
  80. package/templates/nest-ddd/files/_gitignore +40 -0
  81. package/templates/nest-ddd/files/commitlint.config.js +3 -0
  82. package/templates/nest-ddd/files/nest-cli.json +8 -0
  83. package/templates/nest-ddd/files/package.json +66 -0
  84. package/templates/nest-ddd/files/plop-templates/command/command.ts.hbs +5 -0
  85. package/templates/nest-ddd/files/plop-templates/command/handler.ts.hbs +30 -0
  86. package/templates/nest-ddd/files/plop-templates/module/application/commands/create-{{kebab}}.command.ts.hbs +5 -0
  87. package/templates/nest-ddd/files/plop-templates/module/application/commands/create-{{kebab}}.handler.spec.ts.hbs +35 -0
  88. package/templates/nest-ddd/files/plop-templates/module/application/commands/create-{{kebab}}.handler.ts.hbs +25 -0
  89. package/templates/nest-ddd/files/plop-templates/module/application/index.ts.hbs +5 -0
  90. package/templates/nest-ddd/files/plop-templates/module/application/queries/get-{{kebab}}-by-id.handler.ts.hbs +29 -0
  91. package/templates/nest-ddd/files/plop-templates/module/application/queries/get-{{kebab}}-by-id.query.ts.hbs +5 -0
  92. package/templates/nest-ddd/files/plop-templates/module/domain/entities/{{kebab}}.entity.ts.hbs +37 -0
  93. package/templates/nest-ddd/files/plop-templates/module/domain/events/{{kebab}}-created.event.ts.hbs +10 -0
  94. package/templates/nest-ddd/files/plop-templates/module/domain/exceptions/{{kebab}}.exception.ts.hbs +11 -0
  95. package/templates/nest-ddd/files/plop-templates/module/domain/index.ts.hbs +4 -0
  96. package/templates/nest-ddd/files/plop-templates/module/domain/repositories/{{kebab}}.repository.ts.hbs +7 -0
  97. package/templates/nest-ddd/files/plop-templates/module/presentation/controllers/{{kebab}}.controller.ts.hbs +53 -0
  98. package/templates/nest-ddd/files/plop-templates/module/presentation/dtos/create-{{kebab}}.dto.ts.hbs +10 -0
  99. package/templates/nest-ddd/files/plop-templates/module/presentation/dtos/{{kebab}}-response.dto.ts.hbs +15 -0
  100. package/templates/nest-ddd/files/plop-templates/module/presentation/index.ts.hbs +4 -0
  101. package/templates/nest-ddd/files/plop-templates/module/presentation/mappers/{{kebab}}.mapper.ts.hbs +13 -0
  102. package/templates/nest-ddd/files/plop-templates/module/{{kebab}}.module.ts.hbs +40 -0
  103. package/templates/nest-ddd/files/plop-templates/persistence-memory/infrastructure/database/repositories/in-memory-{{kebab}}.repository.ts.hbs +17 -0
  104. package/templates/nest-ddd/files/plop-templates/persistence-memory/infrastructure/index.ts.hbs +1 -0
  105. package/templates/nest-ddd/files/plop-templates/persistence-prisma/infrastructure/database/mappers/{{kebab}}.mapper.ts.hbs +29 -0
  106. package/templates/nest-ddd/files/plop-templates/persistence-prisma/infrastructure/database/repositories/prisma-{{kebab}}.repository.ts.hbs +28 -0
  107. package/templates/nest-ddd/files/plop-templates/persistence-prisma/infrastructure/index.ts.hbs +2 -0
  108. package/templates/nest-ddd/files/plop-templates/query/handler.ts.hbs +28 -0
  109. package/templates/nest-ddd/files/plop-templates/query/query.ts.hbs +5 -0
  110. package/templates/nest-ddd/files/plop-templates/snippets/error-codes.hbs +14 -0
  111. package/templates/nest-ddd/files/plop-templates/snippets/prisma-model.hbs +7 -0
  112. package/templates/nest-ddd/files/plopfile.mjs +186 -0
  113. package/templates/nest-ddd/files/src/app.controller.ts +13 -0
  114. package/templates/nest-ddd/files/src/app.module.ts +26 -0
  115. package/templates/nest-ddd/files/src/core/config/app.config.ts +9 -0
  116. package/templates/nest-ddd/files/src/core/config/env.validation.ts +30 -0
  117. package/templates/nest-ddd/files/src/core/domain/base/aggregate-root.base.ts +18 -0
  118. package/templates/nest-ddd/files/src/core/domain/base/domain-event.base.ts +7 -0
  119. package/templates/nest-ddd/files/src/core/domain/base/entity.base.ts +36 -0
  120. package/templates/nest-ddd/files/src/core/domain/base/value-object.base.ts +17 -0
  121. package/templates/nest-ddd/files/src/core/domain/exceptions/domain.exception.ts +16 -0
  122. package/templates/nest-ddd/files/src/core/domain/exceptions/error-codes.ts +28 -0
  123. package/templates/nest-ddd/files/src/core/domain/types/pagination.ts +9 -0
  124. package/templates/nest-ddd/files/src/core/http/decorators/client-info.decorator.ts +24 -0
  125. package/templates/nest-ddd/files/src/core/http/decorators/current-user.decorator.ts +19 -0
  126. package/templates/nest-ddd/files/src/core/http/dtos/paginated-response.dto.ts +29 -0
  127. package/templates/nest-ddd/files/src/core/http/dtos/pagination-query.dto.ts +17 -0
  128. package/templates/nest-ddd/files/src/core/http/filters/all-exceptions.filter.spec.ts +163 -0
  129. package/templates/nest-ddd/files/src/core/http/filters/all-exceptions.filter.ts +97 -0
  130. package/templates/nest-ddd/files/src/core/http/guards/max-body-size.guard.spec.ts +47 -0
  131. package/templates/nest-ddd/files/src/core/http/guards/max-body-size.guard.ts +31 -0
  132. package/templates/nest-ddd/files/src/core/http/interceptors/logging.interceptor.ts +37 -0
  133. package/templates/nest-ddd/files/src/core/http/interceptors/response.interceptor.ts +53 -0
  134. package/templates/nest-ddd/files/src/core/http/logging/app.logger.ts +34 -0
  135. package/templates/nest-ddd/files/src/core/index.ts +33 -0
  136. package/templates/nest-ddd/files/src/core/utils/correlation-id.util.spec.ts +29 -0
  137. package/templates/nest-ddd/files/src/core/utils/correlation-id.util.ts +15 -0
  138. package/templates/nest-ddd/files/src/core/utils/hash.util.spec.ts +61 -0
  139. package/templates/nest-ddd/files/src/core/utils/hash.util.ts +47 -0
  140. package/templates/nest-ddd/files/src/core/utils/snowflake.util.spec.ts +40 -0
  141. package/templates/nest-ddd/files/src/core/utils/snowflake.util.ts +99 -0
  142. package/templates/nest-ddd/files/src/core/utils/string.util.spec.ts +20 -0
  143. package/templates/nest-ddd/files/src/core/utils/string.util.ts +21 -0
  144. package/templates/nest-ddd/files/src/core/utils/time.util.spec.ts +25 -0
  145. package/templates/nest-ddd/files/src/core/utils/time.util.ts +25 -0
  146. package/templates/nest-ddd/files/src/core/utils/ttl.util.ts +22 -0
  147. package/templates/nest-ddd/files/src/main.ts +61 -0
  148. package/templates/nest-ddd/files/test/setup.ts +6 -0
  149. package/templates/nest-ddd/files/tsconfig.build.json +9 -0
  150. package/templates/nest-ddd/files/tsconfig.json +28 -0
  151. package/templates/nest-ddd/files/vitest.config.e2e.ts +15 -0
  152. package/templates/nest-ddd/files/vitest.config.ts +16 -0
  153. package/templates/nest-ddd/template.json +8 -0
  154. package/templates/node-express/files/.env.example +28 -0
  155. package/templates/node-express/files/docker-compose.yml +52 -0
  156. package/templates/node-express/files/package-lock.json +11880 -0
  157. package/templates/node-express/files/package.json +43 -0
  158. package/templates/node-express/files/server.js +13 -0
  159. package/templates/node-express/files/src/app.js +72 -0
  160. package/templates/node-express/files/src/auth/authUtils.js +116 -0
  161. package/templates/node-express/files/src/auth/checkAuth.js +61 -0
  162. package/templates/node-express/files/src/bucket_pattern/comment.model.js +27 -0
  163. package/templates/node-express/files/src/bucket_pattern/comment.service.js +50 -0
  164. package/templates/node-express/files/src/config/config.aws-s3.js +28 -0
  165. package/templates/node-express/files/src/config/config.cloudinary.js +13 -0
  166. package/templates/node-express/files/src/config/config.mongodb.js +25 -0
  167. package/templates/node-express/files/src/config/config.multer.js +25 -0
  168. package/templates/node-express/files/src/config/config.nodemailer.js +16 -0
  169. package/templates/node-express/files/src/controllers/access.controller.js +49 -0
  170. package/templates/node-express/files/src/controllers/cart.controller.js +38 -0
  171. package/templates/node-express/files/src/controllers/checkout.controller.js +15 -0
  172. package/templates/node-express/files/src/controllers/comment.controller.js +29 -0
  173. package/templates/node-express/files/src/controllers/discount.controller.js +68 -0
  174. package/templates/node-express/files/src/controllers/email.controller.js +16 -0
  175. package/templates/node-express/files/src/controllers/inventory.controller.js +15 -0
  176. package/templates/node-express/files/src/controllers/notification.controller.js +15 -0
  177. package/templates/node-express/files/src/controllers/product.controller.js +175 -0
  178. package/templates/node-express/files/src/controllers/profile.controller.js +65 -0
  179. package/templates/node-express/files/src/controllers/rbac.controller.js +41 -0
  180. package/templates/node-express/files/src/controllers/template.controller.js +20 -0
  181. package/templates/node-express/files/src/controllers/upload.controller.js +56 -0
  182. package/templates/node-express/files/src/controllers/user.controller.js +36 -0
  183. package/templates/node-express/files/src/core/error.response.js +84 -0
  184. package/templates/node-express/files/src/core/success.response.js +44 -0
  185. package/templates/node-express/files/src/database/init.mongodb.level0.js +16 -0
  186. package/templates/node-express/files/src/database/init.mongodb.level1.js +36 -0
  187. package/templates/node-express/files/src/database/init.redis.js +119 -0
  188. package/templates/node-express/files/src/helpers/asyncHandler.js +7 -0
  189. package/templates/node-express/files/src/helpers/check.connect.js +29 -0
  190. package/templates/node-express/files/src/keys/private_key.pem +28 -0
  191. package/templates/node-express/files/src/keys/public_key.pem +9 -0
  192. package/templates/node-express/files/src/logger/discord.log.js +26 -0
  193. package/templates/node-express/files/src/logger/discord.log.v2.js +56 -0
  194. package/templates/node-express/files/src/logger/mylogger.log.js +130 -0
  195. package/templates/node-express/files/src/logger/winston.log.js +27 -0
  196. package/templates/node-express/files/src/middlewares/index.js +21 -0
  197. package/templates/node-express/files/src/middlewares/rbac.js +37 -0
  198. package/templates/node-express/files/src/middlewares/role.middleware.js +12 -0
  199. package/templates/node-express/files/src/models/KeyToken.model.js +37 -0
  200. package/templates/node-express/files/src/models/apiKey.model.js +31 -0
  201. package/templates/node-express/files/src/models/cart.model.js +47 -0
  202. package/templates/node-express/files/src/models/comment.model.js +43 -0
  203. package/templates/node-express/files/src/models/discount.model.js +92 -0
  204. package/templates/node-express/files/src/models/inventory.model.js +42 -0
  205. package/templates/node-express/files/src/models/notification.model.js +52 -0
  206. package/templates/node-express/files/src/models/order.model.js +68 -0
  207. package/templates/node-express/files/src/models/otp.model.js +32 -0
  208. package/templates/node-express/files/src/models/product.model.js +144 -0
  209. package/templates/node-express/files/src/models/repository/cart.repo.js +71 -0
  210. package/templates/node-express/files/src/models/repository/discount.repo.js +63 -0
  211. package/templates/node-express/files/src/models/repository/inventory.repo.js +45 -0
  212. package/templates/node-express/files/src/models/repository/product.repo.js +145 -0
  213. package/templates/node-express/files/src/models/repository/shop.repo.js +18 -0
  214. package/templates/node-express/files/src/models/repository/spu.repo.js +18 -0
  215. package/templates/node-express/files/src/models/repository/user.repo.js +20 -0
  216. package/templates/node-express/files/src/models/resource.model.js +32 -0
  217. package/templates/node-express/files/src/models/role.model.js +57 -0
  218. package/templates/node-express/files/src/models/shop.model.js +43 -0
  219. package/templates/node-express/files/src/models/sku.model.js +81 -0
  220. package/templates/node-express/files/src/models/spu.model.js +101 -0
  221. package/templates/node-express/files/src/models/template.model.js +25 -0
  222. package/templates/node-express/files/src/models/user.model.js +82 -0
  223. package/templates/node-express/files/src/postman/access.post.http +22 -0
  224. package/templates/node-express/files/src/routes/access/index.route.js +21 -0
  225. package/templates/node-express/files/src/routes/cart/index.route.js +20 -0
  226. package/templates/node-express/files/src/routes/checkout/index.route.js +10 -0
  227. package/templates/node-express/files/src/routes/comment/index.route.js +16 -0
  228. package/templates/node-express/files/src/routes/discount/index.route.js +26 -0
  229. package/templates/node-express/files/src/routes/index.route.js +27 -0
  230. package/templates/node-express/files/src/routes/inventory/index.route.js +11 -0
  231. package/templates/node-express/files/src/routes/notification/index.route.js +14 -0
  232. package/templates/node-express/files/src/routes/product/index.route.js +38 -0
  233. package/templates/node-express/files/src/routes/profile/index.route.js +15 -0
  234. package/templates/node-express/files/src/routes/rbac/index.route.js +21 -0
  235. package/templates/node-express/files/src/routes/template/index.route.js +11 -0
  236. package/templates/node-express/files/src/routes/upload/index.route.js +28 -0
  237. package/templates/node-express/files/src/routes/user/index.route.js +13 -0
  238. package/templates/node-express/files/src/services/access.service.js +257 -0
  239. package/templates/node-express/files/src/services/apiKey.service.js +15 -0
  240. package/templates/node-express/files/src/services/cart.service.js +115 -0
  241. package/templates/node-express/files/src/services/checkout.service.js +218 -0
  242. package/templates/node-express/files/src/services/comment.service.js +185 -0
  243. package/templates/node-express/files/src/services/discount.service.js +282 -0
  244. package/templates/node-express/files/src/services/email.service.js +71 -0
  245. package/templates/node-express/files/src/services/inventory.service.js +35 -0
  246. package/templates/node-express/files/src/services/keyToken.service.js +77 -0
  247. package/templates/node-express/files/src/services/notification.service.js +58 -0
  248. package/templates/node-express/files/src/services/otp.service.js +36 -0
  249. package/templates/node-express/files/src/services/product.service.js +228 -0
  250. package/templates/node-express/files/src/services/rbac.service.js +143 -0
  251. package/templates/node-express/files/src/services/redis.service.js +57 -0
  252. package/templates/node-express/files/src/services/redisPubSub.service.js +72 -0
  253. package/templates/node-express/files/src/services/shop.service.js +21 -0
  254. package/templates/node-express/files/src/services/sku.service.js +52 -0
  255. package/templates/node-express/files/src/services/spu.service.js +89 -0
  256. package/templates/node-express/files/src/services/template.service.js +41 -0
  257. package/templates/node-express/files/src/services/upload.s3.service.js +55 -0
  258. package/templates/node-express/files/src/services/upload.service.js +74 -0
  259. package/templates/node-express/files/src/services/user.service.js +92 -0
  260. package/templates/node-express/files/src/temp/1734420604996-ci-cd.png +0 -0
  261. package/templates/node-express/files/src/temp/1734421222213-cover-fb.jpg +0 -0
  262. package/templates/node-express/files/src/temp/1734421222214-cover-fb-01.jpg +0 -0
  263. package/templates/node-express/files/src/temp/1734421293437-cover-fb-01.jpg +0 -0
  264. package/templates/node-express/files/src/temp/1734421293437-cover-fb.jpg +0 -0
  265. package/templates/node-express/files/src/tests/inventory.test.js +21 -0
  266. package/templates/node-express/files/src/tests/message_queue/kafka/consumer.js +23 -0
  267. package/templates/node-express/files/src/tests/message_queue/kafka/producer.js +21 -0
  268. package/templates/node-express/files/src/tests/message_queue/rabbitMQ/consumer.js +28 -0
  269. package/templates/node-express/files/src/tests/message_queue/rabbitMQ/ordered.consumer.js +26 -0
  270. package/templates/node-express/files/src/tests/message_queue/rabbitMQ/ordered.producer.js +31 -0
  271. package/templates/node-express/files/src/tests/message_queue/rabbitMQ/producer.js +26 -0
  272. package/templates/node-express/files/src/tests/message_queue/rabbitMQ/producerDLX.js +44 -0
  273. package/templates/node-express/files/src/tests/mysql/connect.js +59 -0
  274. package/templates/node-express/files/src/tests/product.test.js +14 -0
  275. package/templates/node-express/files/src/utils/index.js +78 -0
  276. package/templates/node-express/files/src/utils/reasonPhrases.js +344 -0
  277. package/templates/node-express/files/src/utils/statusCodes.js +344 -0
  278. package/templates/node-express/files/src/utils/template.html.js +38 -0
  279. package/templates/node-express/template.json +11 -0
  280. package/templates/react-fsd/files/index.html +13 -0
  281. package/templates/react-fsd/files/package.json +15 -0
  282. package/templates/react-fsd/files/public/favicon.svg +1 -0
  283. package/templates/react-fsd/files/public/icons.svg +24 -0
  284. package/templates/react-fsd/files/src/assets/hero.png +0 -0
  285. package/templates/react-fsd/files/src/assets/typescript.svg +1 -0
  286. package/templates/react-fsd/files/src/assets/vite.svg +1 -0
  287. package/templates/react-fsd/files/src/counter.ts +9 -0
  288. package/templates/react-fsd/files/src/main.ts +60 -0
  289. package/templates/react-fsd/files/src/style.css +296 -0
  290. package/templates/react-fsd/files/tsconfig.json +24 -0
  291. package/templates/react-fsd/template.json +11 -0
  292. package/templates/react-monolithic/files/index.html +13 -0
  293. package/templates/react-monolithic/files/package.json +15 -0
  294. package/templates/react-monolithic/files/public/favicon.svg +1 -0
  295. package/templates/react-monolithic/files/public/icons.svg +24 -0
  296. package/templates/react-monolithic/files/src/assets/hero.png +0 -0
  297. package/templates/react-monolithic/files/src/assets/typescript.svg +1 -0
  298. package/templates/react-monolithic/files/src/assets/vite.svg +1 -0
  299. package/templates/react-monolithic/files/src/counter.ts +9 -0
  300. package/templates/react-monolithic/files/src/main.ts +60 -0
  301. package/templates/react-monolithic/files/src/style.css +296 -0
  302. package/templates/react-monolithic/files/tsconfig.json +24 -0
  303. package/templates/react-monolithic/template.json +11 -0
  304. package/templates/vue-fsd/files/index.html +13 -0
  305. package/templates/vue-fsd/files/package.json +15 -0
  306. package/templates/vue-fsd/files/public/favicon.svg +1 -0
  307. package/templates/vue-fsd/files/public/icons.svg +24 -0
  308. package/templates/vue-fsd/files/src/assets/hero.png +0 -0
  309. package/templates/vue-fsd/files/src/assets/typescript.svg +1 -0
  310. package/templates/vue-fsd/files/src/assets/vite.svg +1 -0
  311. package/templates/vue-fsd/files/src/counter.ts +9 -0
  312. package/templates/vue-fsd/files/src/main.ts +60 -0
  313. package/templates/vue-fsd/files/src/style.css +296 -0
  314. package/templates/vue-fsd/files/tsconfig.json +24 -0
  315. package/templates/vue-fsd/template.json +11 -0
  316. package/templates/vue-monolithic/files/index.html +13 -0
  317. package/templates/vue-monolithic/files/package.json +15 -0
  318. package/templates/vue-monolithic/files/public/favicon.svg +1 -0
  319. package/templates/vue-monolithic/files/public/icons.svg +24 -0
  320. package/templates/vue-monolithic/files/src/assets/hero.png +0 -0
  321. package/templates/vue-monolithic/files/src/assets/typescript.svg +1 -0
  322. package/templates/vue-monolithic/files/src/assets/vite.svg +1 -0
  323. package/templates/vue-monolithic/files/src/counter.ts +9 -0
  324. package/templates/vue-monolithic/files/src/main.ts +60 -0
  325. package/templates/vue-monolithic/files/src/style.css +296 -0
  326. package/templates/vue-monolithic/files/tsconfig.json +24 -0
  327. package/templates/vue-monolithic/template.json +11 -0
@@ -0,0 +1,510 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { Logger, ServiceUnavailableException } from '@nestjs/common';
3
+ import { RabbitMqJobQueue } from './rabbitmq-job-queue.js';
4
+ import { defineJob } from './job-queue.port.js';
5
+ import type { RabbitMQService } from '../rabbitmq/rabbitmq.service.js';
6
+
7
+ interface Delivery {
8
+ content: Buffer;
9
+ properties: { headers?: Record<string, unknown>; messageId?: string };
10
+ }
11
+
12
+ /** A channel that records what was declared and lets a test deliver messages to the consumer. */
13
+ class FakeChannel {
14
+ exchanges: string[] = [];
15
+ queues = new Map<string, { durable?: boolean; arguments?: any }>();
16
+ bindings: Array<[string, string, string]> = [];
17
+ consumers = new Map<string, (message: any) => void>();
18
+ cancelled: string[] = [];
19
+ prefetchCalls: number[] = [];
20
+ acked: Delivery[] = [];
21
+ nacked: Array<{ message: Delivery; requeue: boolean }> = [];
22
+ order: string[] = [];
23
+ private tag = 0;
24
+
25
+ assertExchange = vi.fn(async (name: string) => {
26
+ this.exchanges.push(name);
27
+ });
28
+ assertQueue = vi.fn(async (name: string, options: any) => {
29
+ this.queues.set(name, options);
30
+ });
31
+ bindQueue = vi.fn(async (queue: string, exchange: string, key: string) => {
32
+ this.bindings.push([queue, exchange, key]);
33
+ });
34
+ prefetch = vi.fn(async (count: number) => {
35
+ this.prefetchCalls.push(count);
36
+ });
37
+ consume = vi.fn(async (queue: string, callback: (m: any) => void) => {
38
+ const consumerTag = `tag-${++this.tag}`;
39
+ this.consumers.set(queue, callback);
40
+ this.consumerTags.set(consumerTag, queue);
41
+ return { consumerTag };
42
+ });
43
+ consumerTags = new Map<string, string>();
44
+ cancel = vi.fn(async (tag: string) => {
45
+ this.cancelled.push(tag);
46
+ });
47
+ ack = vi.fn((message: Delivery) => {
48
+ this.order.push('ack');
49
+ this.acked.push(message);
50
+ });
51
+ nack = vi.fn((message: Delivery, _all: boolean, requeue: boolean) => {
52
+ this.order.push('nack');
53
+ this.nacked.push({ message, requeue });
54
+ });
55
+
56
+ deliver(queue: string, body: unknown, attempt = 0): Delivery {
57
+ const message: Delivery = {
58
+ content: Buffer.from(
59
+ typeof body === 'string' ? body : JSON.stringify(body),
60
+ ),
61
+ properties: { headers: { 'x-attempt': attempt }, messageId: 'msg-1' },
62
+ };
63
+ this.consumers.get(queue)!(message);
64
+ return message;
65
+ }
66
+ }
67
+
68
+ /** Stands in for RabbitMQService: connecting means running the registered hooks on a channel. */
69
+ class FakeRabbit {
70
+ hooks: Array<(channel: any) => Promise<void>> = [];
71
+ channel: FakeChannel | null = null;
72
+ published: Array<{
73
+ exchange: string;
74
+ key: string;
75
+ content: Buffer;
76
+ options: any;
77
+ channel?: any;
78
+ }> = [];
79
+ publishError: Error | null = null;
80
+ order: string[] | null = null;
81
+
82
+ start = vi.fn();
83
+ onConnected = vi.fn((hook: (channel: any) => Promise<void>) => {
84
+ this.hooks.push(hook);
85
+ });
86
+ getChannel = () => this.channel as any;
87
+ publish = vi.fn(
88
+ async (
89
+ exchange: string,
90
+ key: string,
91
+ content: Buffer,
92
+ options: any,
93
+ channel?: any,
94
+ ) => {
95
+ if (this.publishError) throw this.publishError;
96
+ this.order?.push('publish');
97
+ this.published.push({ exchange, key, content, options, channel });
98
+ },
99
+ );
100
+
101
+ async connect(channel = new FakeChannel()) {
102
+ this.channel = channel;
103
+ channel.order = this.order ?? channel.order;
104
+ for (const hook of this.hooks) await hook(channel);
105
+ return channel;
106
+ }
107
+ }
108
+
109
+ const EMAIL = defineJob<{ n: number }>({
110
+ name: 'send-email',
111
+ retryDelaysMs: [10_000, 30_000, 90_000],
112
+ concurrency: 5,
113
+ });
114
+ const OTHER = defineJob<{ id: string }>({
115
+ name: 'other-job',
116
+ retryDelaysMs: [1_000],
117
+ });
118
+
119
+ /** Let queued promise callbacks run */
120
+ const settle = () => new Promise((resolve) => setTimeout(resolve, 0));
121
+
122
+ describe('RabbitMqJobQueue', () => {
123
+ let rabbit: FakeRabbit;
124
+ let queue: RabbitMqJobQueue;
125
+ let logged: string[];
126
+
127
+ beforeEach(() => {
128
+ logged = [];
129
+ for (const level of ['log', 'warn', 'error'] as const) {
130
+ vi.spyOn(Logger.prototype, level).mockImplementation((message: any) => {
131
+ logged.push(String(message));
132
+ });
133
+ }
134
+ rabbit = new FakeRabbit();
135
+ queue = new RabbitMqJobQueue(rabbit as unknown as RabbitMQService);
136
+ queue.onModuleInit();
137
+ });
138
+ afterEach(() => vi.restoreAllMocks());
139
+
140
+ describe('start-up', () => {
141
+ it('starts connecting and asks to be set up again after every connection (AC-8)', () => {
142
+ expect(rabbit.start).toHaveBeenCalledOnce();
143
+ expect(rabbit.onConnected).toHaveBeenCalledOnce();
144
+ });
145
+ });
146
+
147
+ describe('topology', () => {
148
+ it('declares the exchange, the main queue, one queue per retry with its own TTL, and the dead-letter queue', async () => {
149
+ queue.register(EMAIL, async () => {});
150
+ const channel = await rabbit.connect();
151
+
152
+ expect(channel.exchanges).toContain('jobs');
153
+ expect(channel.bindings).toContainEqual([
154
+ 'jobs.send-email',
155
+ 'jobs',
156
+ 'send-email',
157
+ ]);
158
+ expect([...channel.queues.keys()]).toEqual([
159
+ 'jobs.send-email',
160
+ 'jobs.send-email.retry.1.10000ms',
161
+ 'jobs.send-email.retry.2.30000ms',
162
+ 'jobs.send-email.retry.3.90000ms',
163
+ 'jobs.send-email.dlq',
164
+ ]);
165
+ for (const [n, ttl] of [
166
+ [1, 10_000],
167
+ [2, 30_000],
168
+ [3, 90_000],
169
+ ]) {
170
+ expect(
171
+ channel.queues.get(`jobs.send-email.retry.${n}.${ttl}ms`),
172
+ ).toEqual({
173
+ durable: true,
174
+ arguments: {
175
+ 'x-message-ttl': ttl,
176
+ 'x-dead-letter-exchange': 'jobs',
177
+ 'x-dead-letter-routing-key': 'send-email',
178
+ },
179
+ });
180
+ }
181
+ });
182
+
183
+ it('makes every queue durable', async () => {
184
+ queue.register(EMAIL, async () => {});
185
+ const channel = await rabbit.connect();
186
+
187
+ for (const options of channel.queues.values()) {
188
+ expect(options.durable).toBe(true);
189
+ }
190
+ });
191
+
192
+ it('prefixes names with the namespace and scales the retry delays (for tests)', async () => {
193
+ queue = new RabbitMqJobQueue(rabbit as unknown as RabbitMQService, {
194
+ namespace: 't1.',
195
+ retryDelayScale: 0.01,
196
+ });
197
+ queue.onModuleInit();
198
+ queue.register(EMAIL, async () => {});
199
+ const channel = await rabbit.connect();
200
+
201
+ expect(channel.queues.has('t1.jobs.send-email')).toBe(true);
202
+ expect(
203
+ channel.queues.get('t1.jobs.send-email.retry.1.100ms')!.arguments[
204
+ 'x-message-ttl'
205
+ ],
206
+ ).toBe(100);
207
+ });
208
+
209
+ it.each([0, -1, 1.5, 1001, Number.NaN])(
210
+ 'rejects a concurrency of %s: 0 would mean no limit at all in RabbitMQ',
211
+ (concurrency) => {
212
+ expect(() =>
213
+ queue.register(
214
+ defineJob({ name: 'ok-name', retryDelaysMs: [], concurrency }),
215
+ async () => {},
216
+ ),
217
+ ).toThrow(/concurrency/);
218
+ },
219
+ );
220
+
221
+ it('rejects an invalid job name or a non-positive retry delay', () => {
222
+ expect(() =>
223
+ queue.register(
224
+ defineJob({ name: 'Bad Name', retryDelaysMs: [] }),
225
+ async () => {},
226
+ ),
227
+ ).toThrow(/Invalid job name/);
228
+ expect(() =>
229
+ queue.register(
230
+ defineJob({ name: 'ok-name', retryDelaysMs: [0] }),
231
+ async () => {},
232
+ ),
233
+ ).toThrow(/non-positive/);
234
+ });
235
+ });
236
+
237
+ describe('enqueue', () => {
238
+ it('publishes a persistent JSON message to the exchange with the job name as routing key and waits for the broker (AC-1)', async () => {
239
+ await rabbit.connect();
240
+
241
+ await queue.enqueue(EMAIL, { n: 7 });
242
+
243
+ expect(rabbit.published).toHaveLength(1);
244
+ const sent = rabbit.published[0];
245
+ expect(sent.exchange).toBe('jobs');
246
+ expect(sent.key).toBe('send-email');
247
+ expect(JSON.parse(sent.content.toString())).toEqual({ n: 7 });
248
+ expect(sent.options.headers).toEqual({ 'x-attempt': 0 });
249
+ expect(sent.options.contentType).toBe('application/json');
250
+ expect(sent.options.messageId).toMatch(/^[0-9a-f-]{36}$/);
251
+ });
252
+
253
+ it('declares the queues before publishing, even for a job nobody listens to here', async () => {
254
+ const channel = await rabbit.connect();
255
+
256
+ await queue.enqueue(OTHER, { id: 'x' });
257
+
258
+ expect(channel.queues.has('jobs.other-job')).toBe(true);
259
+ expect(channel.queues.has('jobs.other-job.dlq')).toBe(true);
260
+ });
261
+
262
+ it('answers 503 at once when there is no connection (AC-7)', async () => {
263
+ await expect(queue.enqueue(EMAIL, { n: 1 })).rejects.toBeInstanceOf(
264
+ ServiceUnavailableException,
265
+ );
266
+ expect(rabbit.published).toHaveLength(0);
267
+ });
268
+
269
+ it('answers 503 when the broker does not confirm the message (AC-7)', async () => {
270
+ await rabbit.connect();
271
+ rabbit.publishError = new ServiceUnavailableException();
272
+
273
+ await expect(queue.enqueue(EMAIL, { n: 1 })).rejects.toBeInstanceOf(
274
+ ServiceUnavailableException,
275
+ );
276
+ });
277
+ });
278
+
279
+ describe('workers', () => {
280
+ it('listens on the main queue with the job concurrency as prefetch and acknowledges by hand (AC-2)', async () => {
281
+ queue.register(EMAIL, async () => {});
282
+ const channel = await rabbit.connect();
283
+
284
+ expect(channel.prefetchCalls).toEqual([5]);
285
+ expect(channel.consume).toHaveBeenCalledWith(
286
+ 'jobs.send-email',
287
+ expect.any(Function),
288
+ { noAck: false },
289
+ );
290
+ });
291
+
292
+ it('runs the handler with the parsed data and acknowledges (AC-2)', async () => {
293
+ const handler = vi.fn().mockResolvedValue(undefined);
294
+ queue.register(EMAIL, handler);
295
+ const channel = await rabbit.connect();
296
+
297
+ const message = channel.deliver('jobs.send-email', { n: 3 });
298
+ await settle();
299
+
300
+ expect(handler).toHaveBeenCalledWith({ n: 3 });
301
+ expect(channel.acked).toEqual([message]);
302
+ expect(rabbit.published).toHaveLength(0);
303
+ });
304
+
305
+ it('starts a worker at once when the handler is registered after connecting', async () => {
306
+ const channel = await rabbit.connect();
307
+
308
+ queue.register(EMAIL, async () => {});
309
+ await settle();
310
+
311
+ expect(channel.consumers.has('jobs.send-email')).toBe(true);
312
+ });
313
+
314
+ it('starts the workers again on a new channel after a reconnect, and declares the queues again (AC-8)', async () => {
315
+ const handler = vi.fn().mockResolvedValue(undefined);
316
+ queue.register(EMAIL, handler);
317
+ await rabbit.connect();
318
+
319
+ const second = await rabbit.connect(new FakeChannel());
320
+
321
+ expect(second.queues.has('jobs.send-email.dlq')).toBe(true);
322
+ second.deliver('jobs.send-email', { n: 9 });
323
+ await settle();
324
+ expect(handler).toHaveBeenCalledWith({ n: 9 });
325
+ });
326
+ });
327
+
328
+ describe('retrying', () => {
329
+ let channel: FakeChannel;
330
+
331
+ beforeEach(async () => {
332
+ rabbit.order = [];
333
+ queue.register(EMAIL, async () => {
334
+ throw Object.assign(new Error('550 <a@example.com> unknown'), {
335
+ code: 'EENVELOPE',
336
+ });
337
+ });
338
+ channel = await rabbit.connect();
339
+ });
340
+
341
+ it('publishes a copy to the first retry queue with the attempt counted up, and only then acknowledges (AC-3)', async () => {
342
+ const message = channel.deliver('jobs.send-email', { n: 1 });
343
+ await settle();
344
+
345
+ expect(rabbit.published).toHaveLength(1);
346
+ const copy = rabbit.published[0];
347
+ expect(copy.exchange).toBe('');
348
+ expect(copy.key).toBe('jobs.send-email.retry.1.10000ms');
349
+ expect(copy.content).toEqual(message.content);
350
+ expect(copy.options.headers['x-attempt']).toBe(1);
351
+ expect(copy.options.messageId).toBe('msg-1');
352
+ expect(rabbit.order).toEqual(['publish', 'ack']);
353
+ });
354
+
355
+ it.each([
356
+ [1, 'jobs.send-email.retry.2.30000ms', 2],
357
+ [2, 'jobs.send-email.retry.3.90000ms', 3],
358
+ ])(
359
+ 'moves a job that already failed %i time(s) to %s (AC-3)',
360
+ async (attempt, retryQueue, nextAttempt) => {
361
+ channel.deliver('jobs.send-email', { n: 1 }, attempt);
362
+ await settle();
363
+
364
+ expect(rabbit.published[0].key).toBe(retryQueue);
365
+ expect(rabbit.published[0].options.headers['x-attempt']).toBe(
366
+ nextAttempt,
367
+ );
368
+ },
369
+ );
370
+
371
+ it('sends a job that failed after the last retry to the dead-letter queue, with the reason, and acknowledges (AC-4)', async () => {
372
+ channel.deliver('jobs.send-email', { n: 1 }, 3);
373
+ await settle();
374
+
375
+ expect(rabbit.published).toHaveLength(1);
376
+ const dead = rabbit.published[0];
377
+ expect(dead.key).toBe('jobs.send-email.dlq');
378
+ expect(dead.options.headers['x-attempt']).toBe(4);
379
+ expect(dead.options.headers['x-failure']).toBe('EENVELOPE Error');
380
+ expect(rabbit.order).toEqual(['publish', 'ack']);
381
+ });
382
+
383
+ it('logs the failure without the payload, the address or the provider message (AC-4)', async () => {
384
+ channel.deliver('jobs.send-email', { n: 424242 }, 3);
385
+ await settle();
386
+
387
+ const everything = logged.join('\n');
388
+ expect(everything).toContain('failed for good');
389
+ expect(everything).toContain('EENVELOPE');
390
+ expect(everything).not.toContain('424242');
391
+ expect(everything).not.toContain('a@example.com');
392
+ });
393
+
394
+ it('dead-letters an unreadable message at once without running the handler (AC-4)', async () => {
395
+ channel.deliver('jobs.send-email', 'not json {');
396
+ await settle();
397
+
398
+ expect(rabbit.published[0].key).toBe('jobs.send-email.dlq');
399
+ expect(rabbit.published[0].options.headers['x-failure']).toBe(
400
+ 'UNREADABLE',
401
+ );
402
+ });
403
+
404
+ it('puts the job back on its queue, after a pause, when the copy cannot be published, so it is never lost', async () => {
405
+ vi.useFakeTimers();
406
+ try {
407
+ rabbit.publishError = new ServiceUnavailableException();
408
+ const message = channel.deliver('jobs.send-email', { n: 1 });
409
+ await vi.advanceTimersByTimeAsync(0);
410
+ expect(channel.nacked).toHaveLength(0);
411
+
412
+ await vi.advanceTimersByTimeAsync(1_000);
413
+
414
+ expect(channel.nacked).toEqual([{ message, requeue: true }]);
415
+ expect(channel.acked).toHaveLength(0);
416
+ } finally {
417
+ vi.useRealTimers();
418
+ }
419
+ });
420
+
421
+ it('does not let a closed channel crash the worker when acknowledging', async () => {
422
+ channel.ack.mockImplementation(() => {
423
+ throw new Error('Channel closed');
424
+ });
425
+
426
+ channel.deliver('jobs.send-email', { n: 1 });
427
+ await settle();
428
+
429
+ expect(logged.join('\n')).toContain('will redeliver');
430
+ });
431
+ });
432
+
433
+ describe('the broker takes a worker away', () => {
434
+ it('listens again when it cancels the consumer, e.g. because the queue was deleted', async () => {
435
+ queue.register(EMAIL, async () => {});
436
+ const channel = await rabbit.connect();
437
+ expect(channel.consume).toHaveBeenCalledTimes(1);
438
+
439
+ // RabbitMQ signals a cancelled consumer by delivering null
440
+ channel.consumers.get('jobs.send-email')!(null);
441
+ await settle();
442
+
443
+ expect(channel.consume).toHaveBeenCalledTimes(2);
444
+ expect(logged.join('\n')).toContain('cancelled by the broker');
445
+ });
446
+ });
447
+
448
+ describe('publishing a retry', () => {
449
+ it('publishes on the channel the message came from, so the ack follows on the same channel', async () => {
450
+ queue.register(EMAIL, async () => {
451
+ throw new Error('boom');
452
+ });
453
+ const channel = await rabbit.connect();
454
+
455
+ channel.deliver('jobs.send-email', { n: 1 });
456
+ await settle();
457
+
458
+ expect(rabbit.published[0].channel).toBe(channel);
459
+ });
460
+ });
461
+
462
+ describe('a second kind of job (AC-11)', () => {
463
+ it('runs, retries and dead-letters on its own queues, without touching the first', async () => {
464
+ const first = vi.fn().mockResolvedValue(undefined);
465
+ const second = vi.fn().mockRejectedValue(new Error('boom'));
466
+ queue.register(EMAIL, first);
467
+ queue.register(OTHER, second);
468
+ const channel = await rabbit.connect();
469
+
470
+ channel.deliver('jobs.other-job', { id: 'a' }, 1);
471
+ await settle();
472
+
473
+ expect(second).toHaveBeenCalledWith({ id: 'a' });
474
+ expect(first).not.toHaveBeenCalled();
475
+ expect(rabbit.published[0].key).toBe('jobs.other-job.dlq');
476
+ });
477
+ });
478
+
479
+ describe('shutting down (AC-10)', () => {
480
+ it('stops listening, then waits for the job that is running before returning', async () => {
481
+ let finish!: () => void;
482
+ const running = new Promise<void>((resolve) => (finish = resolve));
483
+ queue.register(EMAIL, () => running);
484
+ const channel = await rabbit.connect();
485
+ channel.deliver('jobs.send-email', { n: 1 });
486
+
487
+ let done = false;
488
+ const shutdown = queue.beforeApplicationShutdown().then(() => {
489
+ done = true;
490
+ });
491
+ await settle();
492
+
493
+ expect(channel.cancelled).toHaveLength(1);
494
+ expect(done).toBe(false);
495
+
496
+ finish();
497
+ await shutdown;
498
+ expect(done).toBe(true);
499
+ });
500
+
501
+ it('does not start workers once it is shutting down', async () => {
502
+ await queue.beforeApplicationShutdown();
503
+ queue.register(EMAIL, async () => {});
504
+
505
+ const channel = await rabbit.connect();
506
+
507
+ expect(channel.consumers.size).toBe(0);
508
+ });
509
+ });
510
+ });