@etus/bhono-app 0.1.5 → 0.1.7

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 (300) hide show
  1. package/dist/index.js +0 -0
  2. package/package.json +7 -2
  3. package/templates/base/.claude/commands/check-skill-rules.md +112 -29
  4. package/templates/base/.claude/commands/linear/implement-issue.md +383 -55
  5. package/templates/base/.claude/commands/ship.md +77 -13
  6. package/templates/base/.claude/hooks/package-lock.json +0 -419
  7. package/templates/base/.claude/hooks/skill-activation-prompt.ts +185 -113
  8. package/templates/base/.claude/hooks/skill-tool-guard.sh +6 -0
  9. package/templates/base/.claude/hooks/skill-tool-guard.ts +198 -0
  10. package/templates/base/.claude/scripts/validate-skill-rules.sh +55 -32
  11. package/templates/base/.claude/settings.json +18 -11
  12. package/templates/base/.claude/skills/skill-rules.json +326 -173
  13. package/templates/base/.env.example +3 -0
  14. package/templates/base/CLAUDE.md +5 -5
  15. package/templates/base/README.md +40 -27
  16. package/templates/base/config/eslint.config.js +1 -0
  17. package/templates/base/config/wrangler.json +16 -17
  18. package/templates/base/docs/SETUP-GUIDE.md +566 -0
  19. package/templates/base/docs/app_spec.txt +13 -10
  20. package/templates/base/docs/architecture/README.md +162 -5
  21. package/templates/base/docs/architecture/api-catalog.md +575 -0
  22. package/templates/base/docs/architecture/c4-component.md +309 -0
  23. package/templates/base/docs/architecture/c4-container.md +183 -0
  24. package/templates/base/docs/architecture/c4-context.md +106 -0
  25. package/templates/base/docs/architecture/data-requirements.md +4 -3
  26. package/templates/base/docs/architecture/db-bootstrap.md +39 -0
  27. package/templates/base/docs/architecture/dependencies.md +327 -0
  28. package/templates/base/docs/architecture/drizzle-migration-plan.md +125 -0
  29. package/templates/base/docs/architecture/erd.md +1 -1
  30. package/templates/base/docs/architecture/sql-standards.md +100 -0
  31. package/templates/base/docs/architecture/tech-debt.md +184 -0
  32. package/templates/base/docs/testing.md +36 -29
  33. package/templates/base/package.json +26 -20
  34. package/templates/base/schema.sql +84 -0
  35. package/templates/base/scripts/capture-prod-session.ts +2 -2
  36. package/templates/base/scripts/init.sh +244 -59
  37. package/templates/base/scripts/sync-template.sh +104 -0
  38. package/templates/base/src/client/hooks/use-auth.ts +5 -0
  39. package/templates/base/src/client/routes/_authenticated/dashboard.tsx +1 -1
  40. package/templates/base/src/client/routes/index.tsx +1 -1
  41. package/templates/base/src/server/db/client.ts +3 -5
  42. package/templates/base/src/server/db/records.ts +81 -0
  43. package/templates/base/src/server/db/seed.ts +3 -2
  44. package/templates/base/src/server/db/sql.ts +116 -0
  45. package/templates/base/src/server/index.ts +17 -2
  46. package/templates/base/src/server/lib/audit.ts +74 -26
  47. package/templates/base/src/server/lib/audited-db.ts +219 -109
  48. package/templates/base/src/server/lib/transaction.ts +10 -16
  49. package/templates/base/src/server/middleware/account.ts +9 -16
  50. package/templates/base/src/server/middleware/auth.ts +102 -38
  51. package/templates/base/src/server/middleware/rate-limit.ts +8 -6
  52. package/templates/base/src/server/routes/accounts/handlers.ts +18 -6
  53. package/templates/base/src/server/routes/audits/handlers.ts +3 -1
  54. package/templates/base/src/server/routes/auth/handlers.ts +15 -10
  55. package/templates/base/src/server/routes/auth/test-login.ts +99 -45
  56. package/templates/base/src/server/routes/health/handlers.ts +4 -4
  57. package/templates/base/src/server/routes/index.ts +9 -0
  58. package/templates/base/src/server/routes/invitations/handlers.ts +9 -6
  59. package/templates/base/src/server/routes/openapi.ts +1 -1
  60. package/templates/base/src/server/routes/users/handlers.ts +21 -14
  61. package/templates/base/src/server/services/accounts.ts +242 -217
  62. package/templates/base/src/server/services/audits.ts +114 -61
  63. package/templates/base/src/server/services/auth.ts +310 -180
  64. package/templates/base/src/server/services/invitations.ts +282 -222
  65. package/templates/base/src/server/services/users.ts +383 -293
  66. package/templates/base/src/server/types/index.ts +1 -2
  67. package/templates/base/src/shared/types/api.ts +66 -198
  68. package/templates/base/tests/e2e/auth.setup.ts +1 -1
  69. package/templates/base/{src/server/__tests__/fixtures.ts → tests/fixtures/server.ts} +3 -3
  70. package/templates/base/{src/client/__tests__/setup-browser.ts → tests/helpers/client-setup-browser.ts} +2 -2
  71. package/templates/base/{src/client/__tests__/setup.ts → tests/helpers/client-setup.ts} +1 -1
  72. package/templates/base/{src/client/__tests__/test-utils.tsx → tests/helpers/client-test-utils.tsx} +2 -2
  73. package/templates/base/{src/server/__tests__/setup.ts → tests/helpers/server.ts} +9 -9
  74. package/templates/base/tests/integration/accounts/crud.test.ts +2 -11
  75. package/templates/base/tests/integration/audits/list.test.ts +2 -11
  76. package/templates/base/tests/integration/auth/auth-service.test.ts +1 -10
  77. package/templates/base/tests/integration/auth/invitation-token.test.ts +2 -11
  78. package/templates/base/tests/integration/auth/logout.test.ts +2 -11
  79. package/templates/base/tests/integration/auth/oauth.test.ts +23 -42
  80. package/templates/base/tests/integration/auth/refresh-token.test.ts +1 -9
  81. package/templates/base/tests/integration/auth/session-expiry.test.ts +1 -9
  82. package/templates/base/tests/integration/auth/session.test.ts +2 -11
  83. package/templates/base/tests/integration/auth/super-admin.test.ts +1 -9
  84. package/templates/base/tests/integration/authorization/analytics-role.test.ts +2 -11
  85. package/templates/base/tests/integration/authorization/billing-role.test.ts +2 -11
  86. package/templates/base/tests/integration/authorization/guards-roles.test.ts +1 -9
  87. package/templates/base/tests/integration/authorization/multi-tenancy.test.ts +2 -11
  88. package/templates/base/tests/integration/authorization/roles.test.ts +2 -11
  89. package/templates/base/tests/integration/config/production-behavior.test.ts +2 -11
  90. package/templates/base/tests/integration/health/health.test.ts +25 -44
  91. package/templates/base/tests/integration/invitations/crud.test.ts +2 -11
  92. package/templates/base/tests/integration/invitations/email.test.ts +1 -9
  93. package/templates/base/tests/integration/middleware/auth.test.ts +3 -12
  94. package/templates/base/tests/integration/middleware/request-logger.test.ts +1 -9
  95. package/templates/base/tests/integration/performance/response-times.test.ts +1 -9
  96. package/templates/base/tests/integration/security/cookie-security.test.ts +2 -11
  97. package/templates/base/tests/integration/security/csrf-protection.test.ts +2 -11
  98. package/templates/base/tests/integration/security/log-sanitization.test.ts +1 -9
  99. package/templates/base/tests/integration/security/rate-limiting.test.ts +1 -9
  100. package/templates/base/tests/integration/security/sql-injection.test.ts +7 -18
  101. package/templates/base/tests/integration/security/xss-prevention.test.ts +2 -11
  102. package/templates/base/tests/integration/setup.ts +13 -90
  103. package/templates/base/tests/integration/smoke.test.ts +3 -2
  104. package/templates/base/tests/integration/storage/upload.test.ts +2 -11
  105. package/templates/base/tests/integration/storage/validation.test.ts +2 -11
  106. package/templates/base/tests/integration/users/crud.test.ts +2 -11
  107. package/templates/base/tests/integration/users/list.test.ts +2 -11
  108. package/templates/base/tests/integration/vitest.config.ts +2 -9
  109. package/templates/base/{src/server/__tests__ → tests}/mocks/db.ts +1 -1
  110. package/templates/base/{src/server/__tests__ → tests}/mocks/index.ts +1 -1
  111. package/templates/base/{src/server/__tests__ → tests}/mocks/kv.ts +1 -1
  112. package/templates/base/{src/server/__tests__ → tests}/mocks/r2.ts +1 -1
  113. package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/sidebar.test.tsx +1 -1
  114. package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/avatar.test.tsx +1 -1
  115. package/templates/base/{src/client/__tests__ → tests/unit/client/components/ui}/button.test.tsx +1 -1
  116. package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/card.test.tsx +1 -1
  117. package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/dialog.test.tsx +1 -1
  118. package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/input.test.tsx +1 -1
  119. package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/loading-skeleton.test.tsx +1 -1
  120. package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/skeleton.test.tsx +1 -1
  121. package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/sonner.test.tsx +1 -1
  122. package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/tabs.test.tsx +1 -1
  123. package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/account.test.tsx +1 -1
  124. package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/integrations.test.tsx +1 -1
  125. package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/settings.test.tsx +1 -1
  126. package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/team.test.tsx +1 -1
  127. package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/authenticated-layout.test.tsx +1 -1
  128. package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/dashboard.test.tsx +1 -1
  129. package/templates/base/{src/client/routes/__tests__ → tests/unit/client/routes}/invite.test.tsx +1 -1
  130. package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/login.test.tsx +1 -1
  131. package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/navigation.test.tsx +1 -1
  132. package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/root-layout.test.tsx +1 -1
  133. package/templates/base/{src/server/auth/__tests__ → tests/unit/server/auth}/guards.test.ts +2 -2
  134. package/templates/base/{src → tests/unit}/server/auth/permissions.test.ts +1 -1
  135. package/templates/base/{src → tests/unit}/server/auth/roles.test.ts +1 -1
  136. package/templates/base/tests/unit/server/db/sql.test.ts +68 -0
  137. package/templates/base/{src → tests/unit}/server/env.test.ts +1 -1
  138. package/templates/base/tests/unit/server/lib/audited-db.test.ts +78 -0
  139. package/templates/base/{src → tests/unit}/server/lib/email.test.ts +1 -1
  140. package/templates/base/{src → tests/unit}/server/lib/errors.test.ts +1 -1
  141. package/templates/base/{src → tests/unit}/server/lib/oauth.test.ts +1 -1
  142. package/templates/base/{src → tests/unit}/server/lib/pagination.test.ts +1 -1
  143. package/templates/base/{src → tests/unit}/server/lib/password.test.ts +1 -1
  144. package/templates/base/{src → tests/unit}/server/lib/providers.test.ts +1 -1
  145. package/templates/base/{src → tests/unit}/server/lib/r2-storage.test.ts +2 -2
  146. package/templates/base/{src → tests/unit}/server/lib/session.test.ts +2 -2
  147. package/templates/base/{src → tests/unit}/server/lib/tokens.test.ts +1 -1
  148. package/templates/base/{src → tests/unit}/server/lib/transaction.test.ts +5 -14
  149. package/templates/base/{src → tests/unit}/server/middleware/account.test.ts +16 -24
  150. package/templates/base/tests/unit/server/middleware/auth.test.ts +647 -0
  151. package/templates/base/{src → tests/unit}/server/middleware/cors.test.ts +1 -1
  152. package/templates/base/{src → tests/unit}/server/middleware/error-handler.test.ts +2 -2
  153. package/templates/base/{src → tests/unit}/server/middleware/rate-limit.test.ts +3 -2
  154. package/templates/base/{src → tests/unit}/server/middleware/request-context.test.ts +1 -1
  155. package/templates/base/{src → tests/unit}/server/middleware/request-logger.test.ts +1 -1
  156. package/templates/base/{src/server/__tests__/mocks/__tests__ → tests/unit/server/mocks}/db.test.ts +1 -1
  157. package/templates/base/{src/server/__tests__/mocks/__tests__ → tests/unit/server/mocks}/kv.test.ts +1 -1
  158. package/templates/base/{src/server/__tests__/mocks/__tests__ → tests/unit/server/mocks}/r2.test.ts +1 -1
  159. package/templates/base/{src/server/routes/accounts/__tests__ → tests/unit/server/routes/accounts}/handlers.test.ts +12 -12
  160. package/templates/base/{src/server/routes/audits/__tests__ → tests/unit/server/routes/audits}/handlers.test.ts +11 -11
  161. package/templates/base/{src/server/routes/auth/__tests__ → tests/unit/server/routes/auth}/handlers.test.ts +124 -13
  162. package/templates/base/{src/server/routes/health/__tests__ → tests/unit/server/routes/health}/handlers.test.ts +27 -23
  163. package/templates/base/{src/server/routes/invitations/__tests__ → tests/unit/server/routes/invitations}/handlers.test.ts +14 -17
  164. package/templates/base/{src/server/routes/storage/__tests__ → tests/unit/server/routes/storage}/handlers.test.ts +6 -6
  165. package/templates/base/{src/server/routes/users/__tests__ → tests/unit/server/routes/users}/handlers.test.ts +81 -17
  166. package/templates/base/tests/unit/server/services/accounts.test.ts +406 -0
  167. package/templates/base/tests/unit/server/services/audits.test.ts +360 -0
  168. package/templates/base/tests/unit/server/services/auth.test.ts +656 -0
  169. package/templates/base/tests/unit/server/services/invitations.test.ts +343 -0
  170. package/templates/base/tests/unit/server/services/users.test.ts +706 -0
  171. package/templates/base/{src/shared/schemas/__tests__ → tests/unit/shared}/schemas.test.ts +1 -1
  172. package/templates/base/tsconfig.json +2 -1
  173. package/templates/base/vite.config.ts +3 -1
  174. package/templates/base/vitest.config.browser.ts +3 -2
  175. package/templates/base/vitest.config.frontend.ts +3 -2
  176. package/templates/base/vitest.config.ts +7 -14
  177. package/templates/base/.claude/settings.local.json +0 -11
  178. package/templates/base/.github/workflows/test.yml +0 -127
  179. package/templates/base/auth-setup-error.png +0 -0
  180. package/templates/base/config/drizzle.config.ts +0 -10
  181. package/templates/base/pnpm-lock.yaml +0 -8175
  182. package/templates/base/src/server/db/schema/accounts.ts +0 -20
  183. package/templates/base/src/server/db/schema/audit-logs.ts +0 -26
  184. package/templates/base/src/server/db/schema/index.ts +0 -7
  185. package/templates/base/src/server/db/schema/invitations.ts +0 -30
  186. package/templates/base/src/server/db/schema/refresh-tokens.ts +0 -22
  187. package/templates/base/src/server/db/schema/user-accounts.ts +0 -25
  188. package/templates/base/src/server/db/schema/users.ts +0 -33
  189. package/templates/base/src/server/lib/audited-db.test.ts +0 -107
  190. package/templates/base/src/server/lib/schema-helpers.ts +0 -16
  191. package/templates/base/src/server/middleware/auth.test.ts +0 -345
  192. package/templates/base/src/server/services/__tests__/accounts.test.ts +0 -764
  193. package/templates/base/src/server/services/__tests__/audits.test.ts +0 -235
  194. package/templates/base/src/server/services/__tests__/auth.test.ts +0 -765
  195. package/templates/base/src/server/services/__tests__/invitations.test.ts +0 -704
  196. package/templates/base/src/server/services/__tests__/users.test.ts +0 -755
  197. package/templates/base/tests/e2e/_auth/.gitkeep +0 -0
  198. package/templates/base/tests/integration/lib/schema-helpers.test.ts +0 -129
  199. package/templates/base/tsconfig.tsbuildinfo +0 -1
  200. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-can-be-collapsed-by-default-1.png +0 -0
  201. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-expands-when-collapsed-and-expand-button-is-clicked-1.png +0 -0
  202. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-handles-logout-button-click-1.png +0 -0
  203. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-handles-navigation-clicks-1.png +0 -0
  204. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-hides-navigation-labels-when-collapsed-1.png +0 -0
  205. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-highlights-active-route-1.png +0 -0
  206. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-renders-sidebar-navigation-items-1.png +0 -0
  207. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-shows-keyboard-shortcut-hint-when-expanded-1.png +0 -0
  208. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-shows-user-info-when-authenticated-1.png +0 -0
  209. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-shows-user-initials-in-avatar-fallback-1.png +0 -0
  210. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/error-boundary.test.tsx +0 -0
  211. /package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/error-fallback.test.tsx +0 -0
  212. /package/templates/base/{src/client/hooks/__tests__ → tests/unit/client/hooks}/use-auth.test.tsx +0 -0
  213. /package/templates/base/{src/client/hooks/__tests__ → tests/unit/client/hooks}/use-theme.test.tsx +0 -0
  214. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-dashboard-stats-cards-1.png +0 -0
  215. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-quick-action-cards-1.png +0 -0
  216. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-recent-activity-section-1.png +0 -0
  217. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-user-first-name-in-welcome-message-1.png +0 -0
  218. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-user-information-in-sidebar-1.png +0 -0
  219. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-render-dashboard-when-authenticated-1.png +0 -0
  220. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-show-navigation-sidebar-1.png +0 -0
  221. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-unauthenticated-should-redirect-to-login-when-not-authenticated-1.png +0 -0
  222. /package/templates/base/{src/client/routes/__tests__ → tests/unit/client/routes}/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-display-accept-invitation-button-1.png +0 -0
  223. /package/templates/base/{src/client/routes/__tests__ → tests/unit/client/routes}/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-display-decline-button-linking-to-homepage-1.png +0 -0
  224. /package/templates/base/{src/client/routes/__tests__ → tests/unit/client/routes}/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-display-invitation-details--email--workspace--role--1.png +0 -0
  225. /package/templates/base/{src/client/routes/__tests__ → tests/unit/client/routes}/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-have-a-logo-link-to-homepage-1.png +0 -0
  226. /package/templates/base/{src/client/routes/__tests__ → tests/unit/client/routes}/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-render-invitation-page-with-inviter-name-and-workspace-1.png +0 -0
  227. /package/templates/base/{src/client/routes/__tests__ → tests/unit/client/routes}/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-show-terms-of-service-and-privacy-policy-links-1.png +0 -0
  228. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/login.test.tsx/Login-Page-should-display-Google-OAuth-login-button-1.png +0 -0
  229. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/login.test.tsx/Login-Page-should-have-a-link-back-to-home-page-1.png +0 -0
  230. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/login.test.tsx/Login-Page-should-render-login-content-without-waiting-for-authentication-1.png +0 -0
  231. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/login.test.tsx/Login-Page-should-render-login-page-at--login-route-1.png +0 -0
  232. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/login.test.tsx/Login-Page-should-show-Terms-of-Service-and-Privacy-Policy-links-1.png +0 -0
  233. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/login.test.tsx/Login-Page-should-trigger-OAuth-flow-when-clicking-login-button-1.png +0 -0
  234. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-404-handling-should-display-404-text-on-not-found-page-1.png +0 -0
  235. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-404-handling-should-have-navigation-options-on-404-page-1.png +0 -0
  236. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-404-handling-should-render-404-page-for-unknown-routes-1.png +0 -0
  237. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-account-page-1.png +0 -0
  238. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-integrations-page-1.png +0 -0
  239. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-settings-page-1.png +0 -0
  240. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-team-page-1.png +0 -0
  241. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-home-page-navigation-should-display-navigation-links-on-home-page-1.png +0 -0
  242. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-home-page-navigation-should-have-correct-link-destinations-on-home-page-1.png +0 -0
  243. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-allow-access-to-home-page-without-authentication-1.png +0 -0
  244. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-allow-access-to-login-page-without-authentication-1.png +0 -0
  245. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-redirect-unauthenticated-users-from-dashboard-to-login-1.png +0 -0
  246. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-redirect-unauthenticated-users-from-settings-to-login-1.png +0 -0
  247. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-redirect-unauthenticated-users-from-team-to-login-1.png +0 -0
  248. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/account.test.tsx/Account-Page-should-render-Active-Sessions-section-with-session-cards-1.png +0 -0
  249. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/account.test.tsx/Account-Page-should-render-page-with-correct-title--Account--1.png +0 -0
  250. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/account.test.tsx/Account-Page-should-show-API-Access-section-1.png +0 -0
  251. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/account.test.tsx/Account-Page-should-show-Connected-Accounts-section-with-Google-connected-1.png +0 -0
  252. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/account.test.tsx/Account-Page-should-show-Danger-Zone-section-with-delete-button-1.png +0 -0
  253. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/account.test.tsx/Account-Page-should-show-Security-section-with-Two-Factor-Authentication-1.png +0 -0
  254. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-API-documentation-section-should-display-API-documentation-link-section-1.png +0 -0
  255. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-Create-Webhook-Dialog-should-have-Add-Webhook-trigger-button-1.png +0 -0
  256. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-category-filters-should-display-all-category-buttons-1.png +0 -0
  257. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-display-all-integration-cards-with-names-1.png +0 -0
  258. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-display-category-badges-on-cards-1.png +0 -0
  259. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-show-Configure-button-for-connected-integrations-1.png +0 -0
  260. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-show-Connect-button-for-not-connected-integrations-1.png +0 -0
  261. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-show-Connected-badge-for-connected-integrations-1.png +0 -0
  262. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-Available-Integrations-section-1.png +0 -0
  263. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-Webhooks-section-1.png +0 -0
  264. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-connected-count-1.png +0 -0
  265. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-page-description-1.png +0 -0
  266. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-search-input-1.png +0 -0
  267. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-render-with-correct-title--Integrations--1.png +0 -0
  268. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-search-functionality-should-filter-integrations-based-on-search-query-1.png +0 -0
  269. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-search-functionality-should-search-by-description-1.png +0 -0
  270. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-search-functionality-should-show-no-results-message-when-search-has-no-matches-1.png +0 -0
  271. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-existing-webhook-1.png +0 -0
  272. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-webhook-events-1.png +0 -0
  273. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-webhook-last-delivery-info-1.png +0 -0
  274. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-webhook-success-status-1.png +0 -0
  275. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-have-Add-Webhook-button-1.png +0 -0
  276. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Account-tab-should-display-connected-accounts-section-with-Google-provider-1.png +0 -0
  277. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Account-tab-should-display-sessions-and-danger-zone-sections-1.png +0 -0
  278. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-display-all-notification-toggle-options-1.png +0 -0
  279. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-display-email-notifications-section-1.png +0 -0
  280. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-display-toggle-switches-for-notification-options-1.png +0 -0
  281. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-have-toggles-checked-by-default-1.png +0 -0
  282. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display--Save-Changes--button-1.png +0 -0
  283. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-personal-information-form-1.png +0 -0
  284. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-profile-picture-section-1.png +0 -0
  285. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-user-email-in-disabled-email-input-1.png +0 -0
  286. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-user-initials-in-avatar-1.png +0 -0
  287. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-user-name-in-the-name-input-1.png +0 -0
  288. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-page-rendering-should-display-all-three-tabs-1.png +0 -0
  289. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-page-rendering-should-display-page-description-1.png +0 -0
  290. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-page-rendering-should-render-with-correct-title--Settings--1.png +0 -0
  291. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-tab-navigation-should-show-Profile-tab-as-default-active-tab-1.png +0 -0
  292. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-tab-navigation-should-switch-to-Account-tab-when-clicked-1.png +0 -0
  293. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-tab-navigation-should-switch-to-Notifications-tab-when-clicked-1.png +0 -0
  294. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/team.test.tsx/Team-Page-should-display-Active-Members-section-with-member-count-1.png +0 -0
  295. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/team.test.tsx/Team-Page-should-display-Pending-Invitations-section-with-invitation-details-1.png +0 -0
  296. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/team.test.tsx/Team-Page-should-have-invite-member-button-that-can-be-clicked-1.png +0 -0
  297. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/team.test.tsx/Team-Page-should-render-page-with-correct-title-and-description-1.png +0 -0
  298. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/team.test.tsx/Team-Page-should-render-search-input-that-filters-team-members-1.png +0 -0
  299. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/team.test.tsx/Team-Page-should-show-current-user-with---you---indicator-and-role-badge-1.png +0 -0
  300. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/error-components.test.tsx +0 -0
@@ -1,764 +0,0 @@
1
- // src/server/services/__tests__/accounts.test.ts
2
- import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest'
3
- import { accountsService } from '../accounts'
4
- import { NotFoundError, ForbiddenError, ConflictError } from '../../lib/errors'
5
- import type { ServiceContext, Account, PaginationQuery } from '../../types'
6
- import {
7
- createUserFixture,
8
- createSuperAdminFixture,
9
- createAccountFixture,
10
- createDeletedAccountFixture,
11
- } from '../../__tests__/fixtures'
12
-
13
- // Mock audited-db module
14
- vi.mock('../../lib/audited-db', () => ({
15
- auditedInsert: vi.fn(),
16
- auditedUpdate: vi.fn(),
17
- auditedDelete: vi.fn(),
18
- }))
19
-
20
- import { auditedInsert, auditedUpdate, auditedDelete } from '../../lib/audited-db'
21
-
22
- /**
23
- * Creates a mock Drizzle database instance with chainable methods
24
- */
25
- function createMockDb() {
26
- const db = {
27
- select: vi.fn(),
28
- insert: vi.fn().mockReturnValue({
29
- values: vi.fn().mockReturnValue({
30
- returning: vi.fn().mockResolvedValue([]),
31
- }),
32
- }),
33
- update: vi.fn().mockReturnValue({
34
- set: vi.fn().mockReturnValue({
35
- where: vi.fn().mockReturnValue({
36
- returning: vi.fn().mockResolvedValue([]),
37
- }),
38
- }),
39
- }),
40
- delete: vi.fn().mockReturnValue({
41
- where: vi.fn().mockResolvedValue(),
42
- }),
43
- }
44
-
45
- return db as any
46
- }
47
-
48
- /**
49
- * Creates a standard service context for testing (non-super-admin)
50
- */
51
- function createMockContext(overrides: Partial<ServiceContext> = {}): ServiceContext {
52
- const user = createUserFixture({
53
- id: 'ctx-user-123',
54
- email: 'context@example.com',
55
- name: 'Context User',
56
- })
57
-
58
- return {
59
- accountId: 'account-123',
60
- user,
61
- userRole: 'ADMIN',
62
- transactionId: 'tx-123',
63
- ip: '127.0.0.1',
64
- userAgent: 'TestAgent/1.0',
65
- ...overrides,
66
- }
67
- }
68
-
69
- /**
70
- * Creates a super admin context for testing
71
- */
72
- function createSuperAdminContext(overrides: Partial<ServiceContext> = {}): ServiceContext {
73
- const user = createSuperAdminFixture({
74
- id: 'super-admin-123',
75
- email: 'superadmin@example.com',
76
- name: 'Super Admin',
77
- })
78
-
79
- return {
80
- accountId: 'account-123',
81
- user,
82
- userRole: 'ADMIN',
83
- transactionId: 'tx-123',
84
- ip: '127.0.0.1',
85
- userAgent: 'TestAgent/1.0',
86
- ...overrides,
87
- }
88
- }
89
-
90
- describe('accountsService', () => {
91
- let mockDb: ReturnType<typeof createMockDb>
92
- let ctx: ServiceContext
93
- let superAdminCtx: ServiceContext
94
-
95
- beforeEach(() => {
96
- vi.clearAllMocks()
97
- mockDb = createMockDb()
98
- ctx = createMockContext()
99
- superAdminCtx = createSuperAdminContext()
100
- })
101
-
102
- describe('findAll', () => {
103
- const defaultPagination: PaginationQuery = {
104
- page: 1,
105
- limit: 10,
106
- }
107
-
108
- it('should return paginated accounts', async () => {
109
- const testAccounts = [
110
- createAccountFixture({ id: 'account-1', name: 'Account One' }),
111
- createAccountFixture({ id: 'account-2', name: 'Account Two' }),
112
- ]
113
-
114
- // Mock count query
115
- const countChain = {
116
- from: vi.fn().mockReturnValue({
117
- where: vi.fn().mockResolvedValue([{ count: 2 }]),
118
- }),
119
- }
120
-
121
- // Mock data query
122
- const dataChain = {
123
- from: vi.fn().mockReturnValue({
124
- where: vi.fn().mockReturnValue({
125
- limit: vi.fn().mockReturnValue({
126
- offset: vi.fn().mockReturnValue({
127
- orderBy: vi.fn().mockResolvedValue(testAccounts),
128
- }),
129
- }),
130
- }),
131
- }),
132
- }
133
-
134
- let selectCallCount = 0
135
- mockDb.select = vi.fn().mockImplementation((fields?: any) => {
136
- selectCallCount++
137
- if (selectCallCount === 1 || (fields && 'count' in fields)) {
138
- return countChain
139
- }
140
- return dataChain
141
- })
142
-
143
- const result = await accountsService.findAll(mockDb, superAdminCtx, defaultPagination)
144
-
145
- expect(result.data).toHaveLength(2)
146
- expect(result.meta.totalItems).toBe(2)
147
- expect(result.meta.currentPage).toBe(1)
148
- })
149
-
150
- it('should filter accounts for non-super-admin user', async () => {
151
- const userAccounts = [
152
- createAccountFixture({ id: 'account-1', name: 'User Account' }),
153
- ]
154
-
155
- const countChain = {
156
- from: vi.fn().mockReturnValue({
157
- where: vi.fn().mockResolvedValue([{ count: 1 }]),
158
- }),
159
- }
160
-
161
- const dataChain = {
162
- from: vi.fn().mockReturnValue({
163
- where: vi.fn().mockReturnValue({
164
- limit: vi.fn().mockReturnValue({
165
- offset: vi.fn().mockReturnValue({
166
- orderBy: vi.fn().mockResolvedValue(userAccounts),
167
- }),
168
- }),
169
- }),
170
- }),
171
- }
172
-
173
- let selectCallCount = 0
174
- mockDb.select = vi.fn().mockImplementation((fields?: any) => {
175
- selectCallCount++
176
- if (selectCallCount === 1 || (fields && 'count' in fields)) {
177
- return countChain
178
- }
179
- return dataChain
180
- })
181
-
182
- const result = await accountsService.findAll(mockDb, ctx, defaultPagination)
183
-
184
- // Non-super-admin should only see their associated accounts
185
- expect(result.data).toHaveLength(1)
186
- expect(result.data[0].name).toBe('User Account')
187
- })
188
-
189
- it('should allow super admin to see all accounts', async () => {
190
- const allAccounts = [
191
- createAccountFixture({ id: 'account-1', name: 'Account One' }),
192
- createAccountFixture({ id: 'account-2', name: 'Account Two' }),
193
- createAccountFixture({ id: 'account-3', name: 'Account Three' }),
194
- ]
195
-
196
- const countChain = {
197
- from: vi.fn().mockReturnValue({
198
- where: vi.fn().mockResolvedValue([{ count: 3 }]),
199
- }),
200
- }
201
-
202
- const dataChain = {
203
- from: vi.fn().mockReturnValue({
204
- where: vi.fn().mockReturnValue({
205
- limit: vi.fn().mockReturnValue({
206
- offset: vi.fn().mockReturnValue({
207
- orderBy: vi.fn().mockResolvedValue(allAccounts),
208
- }),
209
- }),
210
- }),
211
- }),
212
- }
213
-
214
- let selectCallCount = 0
215
- mockDb.select = vi.fn().mockImplementation((fields?: any) => {
216
- selectCallCount++
217
- if (selectCallCount === 1 || (fields && 'count' in fields)) {
218
- return countChain
219
- }
220
- return dataChain
221
- })
222
-
223
- const result = await accountsService.findAll(mockDb, superAdminCtx, defaultPagination)
224
-
225
- expect(result.data).toHaveLength(3)
226
- expect(result.meta.totalItems).toBe(3)
227
- })
228
-
229
- it('should return empty array when no accounts found', async () => {
230
- const countChain = {
231
- from: vi.fn().mockReturnValue({
232
- where: vi.fn().mockResolvedValue([{ count: 0 }]),
233
- }),
234
- }
235
-
236
- const dataChain = {
237
- from: vi.fn().mockReturnValue({
238
- where: vi.fn().mockReturnValue({
239
- limit: vi.fn().mockReturnValue({
240
- offset: vi.fn().mockReturnValue({
241
- orderBy: vi.fn().mockResolvedValue([]),
242
- }),
243
- }),
244
- }),
245
- }),
246
- }
247
-
248
- let selectCallCount = 0
249
- mockDb.select = vi.fn().mockImplementation((fields?: any) => {
250
- selectCallCount++
251
- if (selectCallCount === 1 || (fields && 'count' in fields)) {
252
- return countChain
253
- }
254
- return dataChain
255
- })
256
-
257
- const result = await accountsService.findAll(mockDb, ctx, defaultPagination)
258
-
259
- expect(result.data).toHaveLength(0)
260
- expect(result.meta.totalItems).toBe(0)
261
- })
262
-
263
- it('should filter by search query', async () => {
264
- const searchAccount = createAccountFixture({
265
- id: 'account-1',
266
- name: 'Acme Corp',
267
- domain: 'acme.com'
268
- })
269
-
270
- const countChain = {
271
- from: vi.fn().mockReturnValue({
272
- where: vi.fn().mockResolvedValue([{ count: 1 }]),
273
- }),
274
- }
275
-
276
- const dataChain = {
277
- from: vi.fn().mockReturnValue({
278
- where: vi.fn().mockReturnValue({
279
- limit: vi.fn().mockReturnValue({
280
- offset: vi.fn().mockReturnValue({
281
- orderBy: vi.fn().mockResolvedValue([searchAccount]),
282
- }),
283
- }),
284
- }),
285
- }),
286
- }
287
-
288
- let selectCallCount = 0
289
- mockDb.select = vi.fn().mockImplementation((fields?: any) => {
290
- selectCallCount++
291
- if (selectCallCount === 1 || (fields && 'count' in fields)) {
292
- return countChain
293
- }
294
- return dataChain
295
- })
296
-
297
- const paginationWithQuery: PaginationQuery = {
298
- ...defaultPagination,
299
- query: 'Acme',
300
- }
301
-
302
- const result = await accountsService.findAll(mockDb, superAdminCtx, paginationWithQuery)
303
-
304
- expect(result.data).toHaveLength(1)
305
- expect(result.data[0].name).toBe('Acme Corp')
306
- })
307
- })
308
-
309
- describe('findById', () => {
310
- it('should return account by ID', async () => {
311
- const testAccount = createAccountFixture({ id: 'account-123', name: 'Test Account' })
312
-
313
- // Mock finding account
314
- const accountChain = {
315
- from: vi.fn().mockReturnValue({
316
- where: vi.fn().mockReturnValue({
317
- limit: vi.fn().mockResolvedValue([testAccount]),
318
- }),
319
- }),
320
- }
321
-
322
- mockDb.select = vi.fn().mockReturnValue(accountChain)
323
-
324
- const result = await accountsService.findById(mockDb, superAdminCtx, 'account-123')
325
-
326
- expect(result.id).toBe('account-123')
327
- expect(result.name).toBe('Test Account')
328
- })
329
-
330
- it('should throw NotFoundError when account does not exist', async () => {
331
- const emptyChain = {
332
- from: vi.fn().mockReturnValue({
333
- where: vi.fn().mockReturnValue({
334
- limit: vi.fn().mockResolvedValue([]),
335
- }),
336
- }),
337
- }
338
-
339
- mockDb.select = vi.fn().mockReturnValue(emptyChain)
340
-
341
- await expect(accountsService.findById(mockDb, superAdminCtx, 'nonexistent')).rejects.toThrow(
342
- NotFoundError
343
- )
344
- })
345
-
346
- it('should throw NotFoundError for soft-deleted account', async () => {
347
- // Soft-deleted accounts are filtered out by the isNull(deletedAt) condition
348
- const emptyChain = {
349
- from: vi.fn().mockReturnValue({
350
- where: vi.fn().mockReturnValue({
351
- limit: vi.fn().mockResolvedValue([]),
352
- }),
353
- }),
354
- }
355
-
356
- mockDb.select = vi.fn().mockReturnValue(emptyChain)
357
-
358
- await expect(accountsService.findById(mockDb, superAdminCtx, 'deleted-account')).rejects.toThrow(
359
- NotFoundError
360
- )
361
- })
362
-
363
- it('should check membership for non-super-admin', async () => {
364
- const testAccount = createAccountFixture({ id: 'account-123', name: 'Test Account' })
365
-
366
- // Mock finding account
367
- const accountChain = {
368
- from: vi.fn().mockReturnValue({
369
- where: vi.fn().mockReturnValue({
370
- limit: vi.fn().mockResolvedValue([testAccount]),
371
- }),
372
- }),
373
- }
374
-
375
- // Mock membership check
376
- const membershipChain = {
377
- from: vi.fn().mockReturnValue({
378
- where: vi.fn().mockReturnValue({
379
- limit: vi.fn().mockResolvedValue([{ userId: ctx.user.id, accountId: 'account-123' }]),
380
- }),
381
- }),
382
- }
383
-
384
- let selectCallCount = 0
385
- mockDb.select = vi.fn().mockImplementation(() => {
386
- selectCallCount++
387
- if (selectCallCount === 1) {
388
- return accountChain
389
- }
390
- return membershipChain
391
- })
392
-
393
- const result = await accountsService.findById(mockDb, ctx, 'account-123')
394
-
395
- expect(result.id).toBe('account-123')
396
- // Verify membership check was performed
397
- expect(mockDb.select).toHaveBeenCalledTimes(2)
398
- })
399
-
400
- it('should throw NotFoundError when non-super-admin has no access', async () => {
401
- const testAccount = createAccountFixture({ id: 'account-123', name: 'Test Account' })
402
-
403
- // Mock finding account
404
- const accountChain = {
405
- from: vi.fn().mockReturnValue({
406
- where: vi.fn().mockReturnValue({
407
- limit: vi.fn().mockResolvedValue([testAccount]),
408
- }),
409
- }),
410
- }
411
-
412
- // Mock membership check (no membership found)
413
- const emptyMembershipChain = {
414
- from: vi.fn().mockReturnValue({
415
- where: vi.fn().mockReturnValue({
416
- limit: vi.fn().mockResolvedValue([]),
417
- }),
418
- }),
419
- }
420
-
421
- let selectCallCount = 0
422
- mockDb.select = vi.fn().mockImplementation(() => {
423
- selectCallCount++
424
- if (selectCallCount === 1) {
425
- return accountChain
426
- }
427
- return emptyMembershipChain
428
- })
429
-
430
- await expect(accountsService.findById(mockDb, ctx, 'account-123')).rejects.toThrow(
431
- NotFoundError
432
- )
433
- })
434
- })
435
-
436
- describe('create', () => {
437
- it('should create account for super admin', async () => {
438
- const newAccount = createAccountFixture({
439
- id: 'new-account',
440
- name: 'New Account',
441
- description: 'A new account',
442
- })
443
-
444
- // Mock domain uniqueness check
445
- const emptyDomainChain = {
446
- from: vi.fn().mockReturnValue({
447
- where: vi.fn().mockReturnValue({
448
- limit: vi.fn().mockResolvedValue([]),
449
- }),
450
- }),
451
- }
452
-
453
- mockDb.select = vi.fn().mockReturnValue(emptyDomainChain)
454
-
455
- // Mock auditedInsert
456
- ;(auditedInsert as Mock).mockResolvedValue([newAccount])
457
-
458
- const result = await accountsService.create(mockDb, superAdminCtx, {
459
- name: 'New Account',
460
- description: 'A new account',
461
- })
462
-
463
- expect(result.id).toBe('new-account')
464
- expect(result.name).toBe('New Account')
465
- expect(auditedInsert).toHaveBeenCalled()
466
- })
467
-
468
- it('should throw ForbiddenError for non-super-admin', async () => {
469
- await expect(
470
- accountsService.create(mockDb, ctx, {
471
- name: 'New Account',
472
- })
473
- ).rejects.toThrow(ForbiddenError)
474
- })
475
-
476
- it('should throw ConflictError for duplicate domain', async () => {
477
- // Mock domain check finding existing account
478
- const existingDomainChain = {
479
- from: vi.fn().mockReturnValue({
480
- where: vi.fn().mockReturnValue({
481
- limit: vi.fn().mockResolvedValue([
482
- createAccountFixture({ id: 'existing', domain: 'acme.com' }),
483
- ]),
484
- }),
485
- }),
486
- }
487
-
488
- mockDb.select = vi.fn().mockReturnValue(existingDomainChain)
489
-
490
- await expect(
491
- accountsService.create(mockDb, superAdminCtx, {
492
- name: 'New Account',
493
- domain: 'acme.com',
494
- })
495
- ).rejects.toThrow(ConflictError)
496
- })
497
-
498
- it('should create account without domain', async () => {
499
- const newAccount = createAccountFixture({
500
- id: 'new-account',
501
- name: 'No Domain Account',
502
- domain: null,
503
- })
504
-
505
- // No domain check needed when no domain provided
506
- ;(auditedInsert as Mock).mockResolvedValue([newAccount])
507
-
508
- const result = await accountsService.create(mockDb, superAdminCtx, {
509
- name: 'No Domain Account',
510
- })
511
-
512
- expect(result.name).toBe('No Domain Account')
513
- expect(result.domain).toBeNull()
514
- })
515
- })
516
-
517
- describe('update', () => {
518
- it('should update account fields', async () => {
519
- const existingAccount = createAccountFixture({
520
- id: 'account-123',
521
- name: 'Old Name',
522
- description: 'Old description',
523
- })
524
- const updatedAccount = {
525
- ...existingAccount,
526
- name: 'New Name',
527
- description: 'New description',
528
- }
529
-
530
- // Mock findById
531
- const accountChain = {
532
- from: vi.fn().mockReturnValue({
533
- where: vi.fn().mockReturnValue({
534
- limit: vi.fn().mockResolvedValue([existingAccount]),
535
- }),
536
- }),
537
- }
538
-
539
- mockDb.select = vi.fn().mockReturnValue(accountChain)
540
-
541
- // Mock auditedUpdate
542
- ;(auditedUpdate as Mock).mockResolvedValue([updatedAccount])
543
-
544
- const result = await accountsService.update(mockDb, superAdminCtx, 'account-123', {
545
- name: 'New Name',
546
- description: 'New description',
547
- })
548
-
549
- expect(result.name).toBe('New Name')
550
- expect(result.description).toBe('New description')
551
- expect(auditedUpdate).toHaveBeenCalled()
552
- })
553
-
554
- it('should throw NotFoundError for non-existent account', async () => {
555
- const emptyChain = {
556
- from: vi.fn().mockReturnValue({
557
- where: vi.fn().mockReturnValue({
558
- limit: vi.fn().mockResolvedValue([]),
559
- }),
560
- }),
561
- }
562
-
563
- mockDb.select = vi.fn().mockReturnValue(emptyChain)
564
-
565
- await expect(
566
- accountsService.update(mockDb, superAdminCtx, 'nonexistent', { name: 'New Name' })
567
- ).rejects.toThrow(NotFoundError)
568
- })
569
-
570
- it('should throw ConflictError when updating to duplicate domain', async () => {
571
- const existingAccount = createAccountFixture({
572
- id: 'account-123',
573
- name: 'Account',
574
- domain: 'original.com',
575
- })
576
-
577
- // Mock findById (first call)
578
- const accountChain = {
579
- from: vi.fn().mockReturnValue({
580
- where: vi.fn().mockReturnValue({
581
- limit: vi.fn().mockResolvedValue([existingAccount]),
582
- }),
583
- }),
584
- }
585
-
586
- // Mock domain conflict check (second call)
587
- const conflictChain = {
588
- from: vi.fn().mockReturnValue({
589
- where: vi.fn().mockReturnValue({
590
- limit: vi.fn().mockResolvedValue([
591
- createAccountFixture({ id: 'other-account', domain: 'taken.com' }),
592
- ]),
593
- }),
594
- }),
595
- }
596
-
597
- let selectCallCount = 0
598
- mockDb.select = vi.fn().mockImplementation(() => {
599
- selectCallCount++
600
- if (selectCallCount === 1) {
601
- return accountChain
602
- }
603
- return conflictChain
604
- })
605
-
606
- await expect(
607
- accountsService.update(mockDb, superAdminCtx, 'account-123', { domain: 'taken.com' })
608
- ).rejects.toThrow(ConflictError)
609
- })
610
-
611
- it('should update domain when unique', async () => {
612
- const existingAccount = createAccountFixture({
613
- id: 'account-123',
614
- name: 'Account',
615
- domain: null,
616
- })
617
- const updatedAccount = { ...existingAccount, domain: 'new-domain.com' }
618
-
619
- // Mock findById
620
- const accountChain = {
621
- from: vi.fn().mockReturnValue({
622
- where: vi.fn().mockReturnValue({
623
- limit: vi.fn().mockResolvedValue([existingAccount]),
624
- }),
625
- }),
626
- }
627
-
628
- // Mock domain uniqueness check (no conflict)
629
- const emptyChain = {
630
- from: vi.fn().mockReturnValue({
631
- where: vi.fn().mockReturnValue({
632
- limit: vi.fn().mockResolvedValue([]),
633
- }),
634
- }),
635
- }
636
-
637
- let selectCallCount = 0
638
- mockDb.select = vi.fn().mockImplementation(() => {
639
- selectCallCount++
640
- if (selectCallCount === 1) {
641
- return accountChain
642
- }
643
- return emptyChain
644
- })
645
-
646
- ;(auditedUpdate as Mock).mockResolvedValue([updatedAccount])
647
-
648
- const result = await accountsService.update(mockDb, superAdminCtx, 'account-123', {
649
- domain: 'new-domain.com',
650
- })
651
-
652
- expect(result.domain).toBe('new-domain.com')
653
- })
654
- })
655
-
656
- describe('delete', () => {
657
- it('should soft delete account for super admin', async () => {
658
- const existingAccount = createAccountFixture({ id: 'account-123' })
659
-
660
- const accountChain = {
661
- from: vi.fn().mockReturnValue({
662
- where: vi.fn().mockReturnValue({
663
- limit: vi.fn().mockResolvedValue([existingAccount]),
664
- }),
665
- }),
666
- }
667
-
668
- mockDb.select = vi.fn().mockReturnValue(accountChain)
669
-
670
- ;(auditedDelete as Mock).mockResolvedValue()
671
-
672
- await accountsService.delete(mockDb, superAdminCtx, 'account-123')
673
-
674
- expect(auditedDelete).toHaveBeenCalled()
675
- })
676
-
677
- it('should throw ForbiddenError for non-super-admin', async () => {
678
- await expect(accountsService.delete(mockDb, ctx, 'account-123')).rejects.toThrow(
679
- ForbiddenError
680
- )
681
- })
682
-
683
- it('should throw NotFoundError for non-existent account', async () => {
684
- const emptyChain = {
685
- from: vi.fn().mockReturnValue({
686
- where: vi.fn().mockReturnValue({
687
- limit: vi.fn().mockResolvedValue([]),
688
- }),
689
- }),
690
- }
691
-
692
- mockDb.select = vi.fn().mockReturnValue(emptyChain)
693
-
694
- await expect(accountsService.delete(mockDb, superAdminCtx, 'nonexistent')).rejects.toThrow(
695
- NotFoundError
696
- )
697
- })
698
- })
699
-
700
- describe('restore', () => {
701
- it('should restore soft-deleted account for super admin', async () => {
702
- const deletedAccount = createDeletedAccountFixture({ id: 'deleted-account-123' })
703
- const restoredAccount = { ...deletedAccount, deletedAt: null }
704
-
705
- // Mock finding deleted account
706
- const deletedAccountChain = {
707
- from: vi.fn().mockReturnValue({
708
- where: vi.fn().mockReturnValue({
709
- limit: vi.fn().mockResolvedValue([deletedAccount]),
710
- }),
711
- }),
712
- }
713
-
714
- mockDb.select = vi.fn().mockReturnValue(deletedAccountChain)
715
-
716
- ;(auditedUpdate as Mock).mockResolvedValue([restoredAccount])
717
-
718
- const result = await accountsService.restore(mockDb, superAdminCtx, 'deleted-account-123')
719
-
720
- expect(result.id).toBe('deleted-account-123')
721
- expect(result.deletedAt).toBeNull()
722
- expect(auditedUpdate).toHaveBeenCalled()
723
- })
724
-
725
- it('should throw ForbiddenError for non-super-admin', async () => {
726
- await expect(accountsService.restore(mockDb, ctx, 'deleted-account')).rejects.toThrow(
727
- ForbiddenError
728
- )
729
- })
730
-
731
- it('should throw NotFoundError for non-deleted account', async () => {
732
- // When querying for deletedAt is NOT NULL, an active account won't be found
733
- const emptyChain = {
734
- from: vi.fn().mockReturnValue({
735
- where: vi.fn().mockReturnValue({
736
- limit: vi.fn().mockResolvedValue([]),
737
- }),
738
- }),
739
- }
740
-
741
- mockDb.select = vi.fn().mockReturnValue(emptyChain)
742
-
743
- await expect(accountsService.restore(mockDb, superAdminCtx, 'active-account')).rejects.toThrow(
744
- NotFoundError
745
- )
746
- })
747
-
748
- it('should throw NotFoundError for non-existent account', async () => {
749
- const emptyChain = {
750
- from: vi.fn().mockReturnValue({
751
- where: vi.fn().mockReturnValue({
752
- limit: vi.fn().mockResolvedValue([]),
753
- }),
754
- }),
755
- }
756
-
757
- mockDb.select = vi.fn().mockReturnValue(emptyChain)
758
-
759
- await expect(accountsService.restore(mockDb, superAdminCtx, 'nonexistent')).rejects.toThrow(
760
- NotFoundError
761
- )
762
- })
763
- })
764
- })