@etus/bhono-app 0.1.1

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 (688) hide show
  1. package/dist/cli.d.ts +13 -0
  2. package/dist/cli.js +46 -0
  3. package/dist/cli.js.map +1 -0
  4. package/dist/cli.test.d.ts +1 -0
  5. package/dist/cli.test.js +26 -0
  6. package/dist/cli.test.js.map +1 -0
  7. package/dist/generator.d.ts +14 -0
  8. package/dist/generator.js +142 -0
  9. package/dist/generator.js.map +1 -0
  10. package/dist/generator.test.d.ts +1 -0
  11. package/dist/generator.test.js +127 -0
  12. package/dist/generator.test.js.map +1 -0
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.js +97 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/prompts.d.ts +25 -0
  17. package/dist/prompts.js +83 -0
  18. package/dist/prompts.js.map +1 -0
  19. package/dist/prompts.test.d.ts +1 -0
  20. package/dist/prompts.test.js +24 -0
  21. package/dist/prompts.test.js.map +1 -0
  22. package/dist/providers/cloudflare.d.ts +37 -0
  23. package/dist/providers/cloudflare.js +61 -0
  24. package/dist/providers/cloudflare.js.map +1 -0
  25. package/dist/providers/cloudflare.test.d.ts +1 -0
  26. package/dist/providers/cloudflare.test.js +29 -0
  27. package/dist/providers/cloudflare.test.js.map +1 -0
  28. package/dist/providers/github.d.ts +16 -0
  29. package/dist/providers/github.js +57 -0
  30. package/dist/providers/github.js.map +1 -0
  31. package/dist/providers/github.test.d.ts +1 -0
  32. package/dist/providers/github.test.js +16 -0
  33. package/dist/providers/github.test.js.map +1 -0
  34. package/dist/templates.d.ts +8 -0
  35. package/dist/templates.js +88 -0
  36. package/dist/templates.js.map +1 -0
  37. package/dist/templates.test.d.ts +1 -0
  38. package/dist/templates.test.js +26 -0
  39. package/dist/templates.test.js.map +1 -0
  40. package/package.json +36 -0
  41. package/templates/base/.claude/agents/architect-review.md +160 -0
  42. package/templates/base/.claude/agents/backend-architect.md +308 -0
  43. package/templates/base/.claude/agents/code-reviewer.md +170 -0
  44. package/templates/base/.claude/agents/performance-engineer.md +166 -0
  45. package/templates/base/.claude/agents/test-automator.md +219 -0
  46. package/templates/base/.claude/commands/check-skill-rules.md +53 -0
  47. package/templates/base/.claude/commands/claude-md.md +250 -0
  48. package/templates/base/.claude/commands/code-prompt.md +212 -0
  49. package/templates/base/.claude/commands/explain-code.md +194 -0
  50. package/templates/base/.claude/commands/init-projec.md +89 -0
  51. package/templates/base/.claude/commands/linear/README.md +297 -0
  52. package/templates/base/.claude/commands/linear/create-issue.md +190 -0
  53. package/templates/base/.claude/commands/linear/implement-issue.md +248 -0
  54. package/templates/base/.claude/commands/linear/process-triage.md +399 -0
  55. package/templates/base/.claude/commands/linear/setup.md +180 -0
  56. package/templates/base/.claude/commands/prime.md +9 -0
  57. package/templates/base/.claude/commands/review-gap.md +10 -0
  58. package/templates/base/.claude/commands/setup-aa.md +311 -0
  59. package/templates/base/.claude/commands/ship.md +262 -0
  60. package/templates/base/.claude/commands/tools.md +3 -0
  61. package/templates/base/.claude/docs/claude-progress.txt +107 -0
  62. package/templates/base/.claude/hooks/package-lock.json +556 -0
  63. package/templates/base/.claude/hooks/package.json +16 -0
  64. package/templates/base/.claude/hooks/skill-activation-prompt.sh +7 -0
  65. package/templates/base/.claude/hooks/skill-activation-prompt.ts +142 -0
  66. package/templates/base/.claude/hooks/tsconfig.json +19 -0
  67. package/templates/base/.claude/scripts/check-updates.sh +85 -0
  68. package/templates/base/.claude/scripts/install_pkgs.sh +66 -0
  69. package/templates/base/.claude/scripts/setup-project.sh +177 -0
  70. package/templates/base/.claude/scripts/validate-skill-rules.sh +94 -0
  71. package/templates/base/.claude/settings.json +113 -0
  72. package/templates/base/.claude/settings.local.json +11 -0
  73. package/templates/base/.claude/skills/architecture-analyzer/SKILL.md +531 -0
  74. package/templates/base/.claude/skills/architecture-analyzer/assets/report-template.md +215 -0
  75. package/templates/base/.claude/skills/architecture-analyzer/references/c4-templates.md +234 -0
  76. package/templates/base/.claude/skills/architecture-analyzer/references/confidence-levels.md +203 -0
  77. package/templates/base/.claude/skills/architecture-analyzer/scripts/analyze_structure.py +266 -0
  78. package/templates/base/.claude/skills/architecture-analyzer/scripts/analyze_tech_debt.py +776 -0
  79. package/templates/base/.claude/skills/architecture-analyzer/scripts/extract_apis.py +338 -0
  80. package/templates/base/.claude/skills/architecture-analyzer/scripts/generate_c4.py +283 -0
  81. package/templates/base/.claude/skills/architecture-analyzer/scripts/generate_erd.py +935 -0
  82. package/templates/base/.claude/skills/architecture-analyzer/scripts/map_dependencies.py +555 -0
  83. package/templates/base/.claude/skills/dev-browser/SKILL.md +318 -0
  84. package/templates/base/.claude/skills/dev-browser/bun.lock +443 -0
  85. package/templates/base/.claude/skills/dev-browser/package-lock.json +2927 -0
  86. package/templates/base/.claude/skills/dev-browser/package.json +27 -0
  87. package/templates/base/.claude/skills/dev-browser/scripts/start-server.ts +117 -0
  88. package/templates/base/.claude/skills/dev-browser/server.sh +24 -0
  89. package/templates/base/.claude/skills/dev-browser/src/client.ts +403 -0
  90. package/templates/base/.claude/skills/dev-browser/src/index.ts +281 -0
  91. package/templates/base/.claude/skills/dev-browser/src/snapshot/__tests__/snapshot.test.ts +223 -0
  92. package/templates/base/.claude/skills/dev-browser/src/snapshot/browser-script.ts +877 -0
  93. package/templates/base/.claude/skills/dev-browser/src/snapshot/index.ts +14 -0
  94. package/templates/base/.claude/skills/dev-browser/src/snapshot/inject.ts +13 -0
  95. package/templates/base/.claude/skills/dev-browser/src/types.ts +27 -0
  96. package/templates/base/.claude/skills/dev-browser/tsconfig.json +36 -0
  97. package/templates/base/.claude/skills/dev-browser/vitest.config.ts +12 -0
  98. package/templates/base/.claude/skills/linear/SKILL.md +440 -0
  99. package/templates/base/.claude/skills/linear/examples.md +262 -0
  100. package/templates/base/.claude/skills/linear/lib/client.ts +51 -0
  101. package/templates/base/.claude/skills/linear/lib/config.ts +106 -0
  102. package/templates/base/.claude/skills/linear/lib/output.ts +34 -0
  103. package/templates/base/.claude/skills/linear/package-lock.json +698 -0
  104. package/templates/base/.claude/skills/linear/package.json +27 -0
  105. package/templates/base/.claude/skills/linear/reference.md +263 -0
  106. package/templates/base/.claude/skills/linear/scripts/comments/create.ts +47 -0
  107. package/templates/base/.claude/skills/linear/scripts/comments/list.ts +47 -0
  108. package/templates/base/.claude/skills/linear/scripts/issues/archive.ts +30 -0
  109. package/templates/base/.claude/skills/linear/scripts/issues/create.ts +279 -0
  110. package/templates/base/.claude/skills/linear/scripts/issues/get.ts +68 -0
  111. package/templates/base/.claude/skills/linear/scripts/issues/list.ts +67 -0
  112. package/templates/base/.claude/skills/linear/scripts/issues/update.ts +281 -0
  113. package/templates/base/.claude/skills/linear/scripts/labels/add-to-issue.ts +63 -0
  114. package/templates/base/.claude/skills/linear/scripts/labels/create.ts +45 -0
  115. package/templates/base/.claude/skills/linear/scripts/labels/list.ts +30 -0
  116. package/templates/base/.claude/skills/linear/scripts/list-teams.ts +52 -0
  117. package/templates/base/.claude/skills/linear/scripts/setup/setup-credentials.ts +96 -0
  118. package/templates/base/.claude/skills/linear/scripts/status/list.ts +31 -0
  119. package/templates/base/.claude/skills/linear/scripts/status/set-by-name.ts +78 -0
  120. package/templates/base/.claude/skills/linear/scripts/status/update.ts +44 -0
  121. package/templates/base/.claude/skills/linear/scripts/users/list.ts +59 -0
  122. package/templates/base/.claude/skills/linear/scripts/users/me.ts +20 -0
  123. package/templates/base/.claude/skills/linear/templates/README.md +203 -0
  124. package/templates/base/.claude/skills/linear/templates/api-reference.md +258 -0
  125. package/templates/base/.claude/skills/linear/templates/bug-report.md +99 -0
  126. package/templates/base/.claude/skills/linear/templates/feature-request.md +118 -0
  127. package/templates/base/.claude/skills/linear/templates/security-issue.md +162 -0
  128. package/templates/base/.claude/skills/linear/templates/sprint-task.md +175 -0
  129. package/templates/base/.claude/skills/linear/templates/tech-debt.md +137 -0
  130. package/templates/base/.claude/skills/linear/tsconfig.json +17 -0
  131. package/templates/base/.claude/skills/linear/workflows/issue-lifecycle.md +317 -0
  132. package/templates/base/.claude/skills/playwright-e2e-testing/SKILL.md +113 -0
  133. package/templates/base/.claude/skills/playwright-e2e-testing/assets/global-setup.template.js +97 -0
  134. package/templates/base/.claude/skills/playwright-e2e-testing/assets/playwright.config.template.js +171 -0
  135. package/templates/base/.claude/skills/playwright-e2e-testing/assets/test-template.spec.js +163 -0
  136. package/templates/base/.claude/skills/playwright-e2e-testing/examples/README.md +26 -0
  137. package/templates/base/.claude/skills/playwright-e2e-testing/examples/ads.email-deeplink.spec.ts +12 -0
  138. package/templates/base/.claude/skills/playwright-e2e-testing/examples/mobile.realism.spec.ts +16 -0
  139. package/templates/base/.claude/skills/playwright-e2e-testing/examples/smoke.home.spec.ts +6 -0
  140. package/templates/base/.claude/skills/playwright-e2e-testing/references/architecture.md +578 -0
  141. package/templates/base/.claude/skills/playwright-e2e-testing/references/best-practices.md +260 -0
  142. package/templates/base/.claude/skills/playwright-e2e-testing/references/ci-reporting.md +86 -0
  143. package/templates/base/.claude/skills/playwright-e2e-testing/references/debugging.md +629 -0
  144. package/templates/base/.claude/skills/playwright-e2e-testing/references/mobile-realism.md +50 -0
  145. package/templates/base/.claude/skills/playwright-e2e-testing/references/optimization.md +488 -0
  146. package/templates/base/.claude/skills/playwright-e2e-testing/references/patterns.md +513 -0
  147. package/templates/base/.claude/skills/playwright-e2e-testing/references/resources.md +44 -0
  148. package/templates/base/.claude/skills/playwright-e2e-testing/references/visual-a11y.md +66 -0
  149. package/templates/base/.claude/skills/playwright-e2e-testing/scripts/auth-setup.js +202 -0
  150. package/templates/base/.claude/skills/playwright-e2e-testing/scripts/performance-analyzer.js +240 -0
  151. package/templates/base/.claude/skills/playwright-e2e-testing/scripts/trace-url.js +132 -0
  152. package/templates/base/.claude/skills/playwright-e2e-testing/templates/ci/github-actions.playwright.yml +78 -0
  153. package/templates/base/.claude/skills/playwright-e2e-testing/templates/fixtures.ts +44 -0
  154. package/templates/base/.claude/skills/playwright-e2e-testing/templates/global-setup.template.js +97 -0
  155. package/templates/base/.claude/skills/playwright-e2e-testing/templates/global.setup.ts +35 -0
  156. package/templates/base/.claude/skills/playwright-e2e-testing/templates/helpers/ad-gpt-observer.ts +80 -0
  157. package/templates/base/.claude/skills/playwright-e2e-testing/templates/helpers/chromium-mobile-profile.ts +93 -0
  158. package/templates/base/.claude/skills/playwright-e2e-testing/templates/playwright.config.template.js +171 -0
  159. package/templates/base/.claude/skills/playwright-e2e-testing/templates/playwright.config.ts +126 -0
  160. package/templates/base/.claude/skills/playwright-e2e-testing/templates/test-template.spec.js +163 -0
  161. package/templates/base/.claude/skills/playwright-e2e-testing/templates/tests/email-deeplink.ads.spec.ts +44 -0
  162. package/templates/base/.claude/skills/skill-rules.json +184 -0
  163. package/templates/base/.claude/skills/wrangler/SKILL.md +209 -0
  164. package/templates/base/.claude/skills/wrangler/resources/api.md +494 -0
  165. package/templates/base/.claude/skills/wrangler/resources/bundling.md +83 -0
  166. package/templates/base/.claude/skills/wrangler/resources/commands/cert.md +64 -0
  167. package/templates/base/.claude/skills/wrangler/resources/commands/check.md +66 -0
  168. package/templates/base/.claude/skills/wrangler/resources/commands/containers.md +157 -0
  169. package/templates/base/.claude/skills/wrangler/resources/commands/d1.md +843 -0
  170. package/templates/base/.claude/skills/wrangler/resources/commands/delete.md +27 -0
  171. package/templates/base/.claude/skills/wrangler/resources/commands/deploy.md +139 -0
  172. package/templates/base/.claude/skills/wrangler/resources/commands/deployments.md +56 -0
  173. package/templates/base/.claude/skills/wrangler/resources/commands/dev.md +157 -0
  174. package/templates/base/.claude/skills/wrangler/resources/commands/dispatch-namespace.md +69 -0
  175. package/templates/base/.claude/skills/wrangler/resources/commands/docs.md +61 -0
  176. package/templates/base/.claude/skills/wrangler/resources/commands/how-to-run.md +62 -0
  177. package/templates/base/.claude/skills/wrangler/resources/commands/hyperdrive.md +425 -0
  178. package/templates/base/.claude/skills/wrangler/resources/commands/init.md +31 -0
  179. package/templates/base/.claude/skills/wrangler/resources/commands/kv-bulk.md +265 -0
  180. package/templates/base/.claude/skills/wrangler/resources/commands/kv-key.md +353 -0
  181. package/templates/base/.claude/skills/wrangler/resources/commands/kv-namespace.md +265 -0
  182. package/templates/base/.claude/skills/wrangler/resources/commands/login.md +23 -0
  183. package/templates/base/.claude/skills/wrangler/resources/commands/logout.md +19 -0
  184. package/templates/base/.claude/skills/wrangler/resources/commands/mtls-certificate.md +69 -0
  185. package/templates/base/.claude/skills/wrangler/resources/commands/pages.md +175 -0
  186. package/templates/base/.claude/skills/wrangler/resources/commands/pipelines.md +76 -0
  187. package/templates/base/.claude/skills/wrangler/resources/commands/queues.md +132 -0
  188. package/templates/base/.claude/skills/wrangler/resources/commands/r2-bucket.md +342 -0
  189. package/templates/base/.claude/skills/wrangler/resources/commands/r2-object.md +267 -0
  190. package/templates/base/.claude/skills/wrangler/resources/commands/r2-sql.md +65 -0
  191. package/templates/base/.claude/skills/wrangler/resources/commands/rollback.md +40 -0
  192. package/templates/base/.claude/skills/wrangler/resources/commands/secret.md +308 -0
  193. package/templates/base/.claude/skills/wrangler/resources/commands/secrets-store-secret.md +100 -0
  194. package/templates/base/.claude/skills/wrangler/resources/commands/secrets-store-store.md +60 -0
  195. package/templates/base/.claude/skills/wrangler/resources/commands/setup.md +67 -0
  196. package/templates/base/.claude/skills/wrangler/resources/commands/tail.md +37 -0
  197. package/templates/base/.claude/skills/wrangler/resources/commands/telemetry.md +64 -0
  198. package/templates/base/.claude/skills/wrangler/resources/commands/triggers.md +39 -0
  199. package/templates/base/.claude/skills/wrangler/resources/commands/types.md +73 -0
  200. package/templates/base/.claude/skills/wrangler/resources/commands/vectorize.md +941 -0
  201. package/templates/base/.claude/skills/wrangler/resources/commands/versions.md +95 -0
  202. package/templates/base/.claude/skills/wrangler/resources/commands/whoami.md +49 -0
  203. package/templates/base/.claude/skills/wrangler/resources/commands/workflows.md +117 -0
  204. package/templates/base/.claude/skills/wrangler/resources/commands.md +138 -0
  205. package/templates/base/.claude/skills/wrangler/resources/configuration.md +2176 -0
  206. package/templates/base/.claude/skills/wrangler/resources/custom-builds.md +55 -0
  207. package/templates/base/.claude/skills/wrangler/resources/deprecations.md +279 -0
  208. package/templates/base/.claude/skills/wrangler/resources/enviroments.md +416 -0
  209. package/templates/base/.claude/skills/wrangler/resources/extract_sections.py +119 -0
  210. package/templates/base/.claude/skills/wrangler/resources/process_content.py +94 -0
  211. package/templates/base/.claude/skills/wrangler/resources/system-enviroments-variables.md +120 -0
  212. package/templates/base/.dev.vars.example +15 -0
  213. package/templates/base/.env.example +29 -0
  214. package/templates/base/.github/workflows/test.yml +127 -0
  215. package/templates/base/.nycrc.json +16 -0
  216. package/templates/base/CLAUDE.md +218 -0
  217. package/templates/base/README.md +670 -0
  218. package/templates/base/auth-setup-error.png +0 -0
  219. package/templates/base/config/drizzle.config.ts +10 -0
  220. package/templates/base/config/eslint.config.js +364 -0
  221. package/templates/base/config/wrangler.json +76 -0
  222. package/templates/base/docs/app_spec.txt +879 -0
  223. package/templates/base/docs/app_spec_template.md +681 -0
  224. package/templates/base/docs/architecture/README.md +8 -0
  225. package/templates/base/docs/architecture/data-requirements.md +109 -0
  226. package/templates/base/docs/architecture/erd.md +91 -0
  227. package/templates/base/docs/features/feature_list.json +3128 -0
  228. package/templates/base/docs/hono-boilerplate-plan.md +1774 -0
  229. package/templates/base/docs/test-coverage-gap-analysis.md +242 -0
  230. package/templates/base/docs/testing.md +188 -0
  231. package/templates/base/index.html +16 -0
  232. package/templates/base/package.json +115 -0
  233. package/templates/base/playwright.config.ts +158 -0
  234. package/templates/base/pnpm-lock.yaml +8175 -0
  235. package/templates/base/scripts/capture-prod-session.ts +250 -0
  236. package/templates/base/scripts/generate-openapi.ts +23 -0
  237. package/templates/base/scripts/init.sh +121 -0
  238. package/templates/base/src/client/__tests__/button.test.tsx +30 -0
  239. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-dashboard-stats-cards-1.png +0 -0
  240. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-quick-action-cards-1.png +0 -0
  241. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-recent-activity-section-1.png +0 -0
  242. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-user-first-name-in-welcome-message-1.png +0 -0
  243. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-user-information-in-sidebar-1.png +0 -0
  244. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-render-dashboard-when-authenticated-1.png +0 -0
  245. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-show-navigation-sidebar-1.png +0 -0
  246. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-unauthenticated-should-redirect-to-login-when-not-authenticated-1.png +0 -0
  247. package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-display-Google-OAuth-login-button-1.png +0 -0
  248. package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-have-a-link-back-to-home-page-1.png +0 -0
  249. package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-render-login-content-without-waiting-for-authentication-1.png +0 -0
  250. package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-render-login-page-at--login-route-1.png +0 -0
  251. package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-show-Terms-of-Service-and-Privacy-Policy-links-1.png +0 -0
  252. package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-trigger-OAuth-flow-when-clicking-login-button-1.png +0 -0
  253. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-404-handling-should-display-404-text-on-not-found-page-1.png +0 -0
  254. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-404-handling-should-have-navigation-options-on-404-page-1.png +0 -0
  255. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-404-handling-should-render-404-page-for-unknown-routes-1.png +0 -0
  256. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-account-page-1.png +0 -0
  257. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-integrations-page-1.png +0 -0
  258. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-settings-page-1.png +0 -0
  259. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-team-page-1.png +0 -0
  260. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-home-page-navigation-should-display-navigation-links-on-home-page-1.png +0 -0
  261. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-home-page-navigation-should-have-correct-link-destinations-on-home-page-1.png +0 -0
  262. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-allow-access-to-home-page-without-authentication-1.png +0 -0
  263. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-allow-access-to-login-page-without-authentication-1.png +0 -0
  264. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-redirect-unauthenticated-users-from-dashboard-to-login-1.png +0 -0
  265. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-redirect-unauthenticated-users-from-settings-to-login-1.png +0 -0
  266. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-redirect-unauthenticated-users-from-team-to-login-1.png +0 -0
  267. package/templates/base/src/client/__tests__/routes/authenticated-layout.test.tsx +252 -0
  268. package/templates/base/src/client/__tests__/routes/dashboard.test.tsx +136 -0
  269. package/templates/base/src/client/__tests__/routes/error-components.test.tsx +186 -0
  270. package/templates/base/src/client/__tests__/routes/login.test.tsx +112 -0
  271. package/templates/base/src/client/__tests__/routes/navigation.test.tsx +272 -0
  272. package/templates/base/src/client/__tests__/routes/root-layout.test.tsx +65 -0
  273. package/templates/base/src/client/__tests__/setup-browser.ts +15 -0
  274. package/templates/base/src/client/__tests__/setup.ts +32 -0
  275. package/templates/base/src/client/__tests__/test-utils.tsx +135 -0
  276. package/templates/base/src/client/api/.gitkeep +0 -0
  277. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-can-be-collapsed-by-default-1.png +0 -0
  278. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-expands-when-collapsed-and-expand-button-is-clicked-1.png +0 -0
  279. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-handles-logout-button-click-1.png +0 -0
  280. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-handles-navigation-clicks-1.png +0 -0
  281. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-hides-navigation-labels-when-collapsed-1.png +0 -0
  282. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-highlights-active-route-1.png +0 -0
  283. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-renders-sidebar-navigation-items-1.png +0 -0
  284. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-shows-keyboard-shortcut-hint-when-expanded-1.png +0 -0
  285. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-shows-user-info-when-authenticated-1.png +0 -0
  286. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-shows-user-initials-in-avatar-fallback-1.png +0 -0
  287. package/templates/base/src/client/components/__tests__/error-boundary.test.tsx +97 -0
  288. package/templates/base/src/client/components/__tests__/sidebar.test.tsx +281 -0
  289. package/templates/base/src/client/components/error-boundary.tsx +68 -0
  290. package/templates/base/src/client/components/icons.tsx +106 -0
  291. package/templates/base/src/client/components/layout/.gitkeep +0 -0
  292. package/templates/base/src/client/components/sidebar.tsx +267 -0
  293. package/templates/base/src/client/components/ui/.gitkeep +0 -0
  294. package/templates/base/src/client/components/ui/__tests__/avatar.test.tsx +308 -0
  295. package/templates/base/src/client/components/ui/__tests__/card.test.tsx +214 -0
  296. package/templates/base/src/client/components/ui/__tests__/dialog.test.tsx +297 -0
  297. package/templates/base/src/client/components/ui/__tests__/error-fallback.test.tsx +145 -0
  298. package/templates/base/src/client/components/ui/__tests__/input.test.tsx +98 -0
  299. package/templates/base/src/client/components/ui/__tests__/loading-skeleton.test.tsx +139 -0
  300. package/templates/base/src/client/components/ui/__tests__/skeleton.test.tsx +44 -0
  301. package/templates/base/src/client/components/ui/__tests__/sonner.test.tsx +28 -0
  302. package/templates/base/src/client/components/ui/__tests__/tabs.test.tsx +233 -0
  303. package/templates/base/src/client/components/ui/avatar.tsx +101 -0
  304. package/templates/base/src/client/components/ui/badge.tsx +46 -0
  305. package/templates/base/src/client/components/ui/button.tsx +72 -0
  306. package/templates/base/src/client/components/ui/card.tsx +86 -0
  307. package/templates/base/src/client/components/ui/dialog.tsx +140 -0
  308. package/templates/base/src/client/components/ui/error-fallback.tsx +179 -0
  309. package/templates/base/src/client/components/ui/form.tsx +172 -0
  310. package/templates/base/src/client/components/ui/input.tsx +24 -0
  311. package/templates/base/src/client/components/ui/label.tsx +22 -0
  312. package/templates/base/src/client/components/ui/loading-skeleton.tsx +154 -0
  313. package/templates/base/src/client/components/ui/separator.tsx +33 -0
  314. package/templates/base/src/client/components/ui/skeleton.tsx +16 -0
  315. package/templates/base/src/client/components/ui/sonner.tsx +29 -0
  316. package/templates/base/src/client/components/ui/tabs.tsx +121 -0
  317. package/templates/base/src/client/hooks/.gitkeep +0 -0
  318. package/templates/base/src/client/hooks/__tests__/use-auth.test.tsx +306 -0
  319. package/templates/base/src/client/hooks/__tests__/use-theme.test.tsx +172 -0
  320. package/templates/base/src/client/hooks/use-auth.ts +53 -0
  321. package/templates/base/src/client/hooks/use-theme.tsx +78 -0
  322. package/templates/base/src/client/index.css +881 -0
  323. package/templates/base/src/client/lib/query-client.ts +11 -0
  324. package/templates/base/src/client/lib/utils.ts +7 -0
  325. package/templates/base/src/client/main.tsx +26 -0
  326. package/templates/base/src/client/routeTree.gen.ts +258 -0
  327. package/templates/base/src/client/router.ts +15 -0
  328. package/templates/base/src/client/routes/$.tsx +77 -0
  329. package/templates/base/src/client/routes/.gitkeep +0 -0
  330. package/templates/base/src/client/routes/__root.tsx +34 -0
  331. package/templates/base/src/client/routes/__tests__/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-display-accept-invitation-button-1.png +0 -0
  332. package/templates/base/src/client/routes/__tests__/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-display-decline-button-linking-to-homepage-1.png +0 -0
  333. package/templates/base/src/client/routes/__tests__/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-display-invitation-details--email--workspace--role--1.png +0 -0
  334. package/templates/base/src/client/routes/__tests__/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-have-a-logo-link-to-homepage-1.png +0 -0
  335. package/templates/base/src/client/routes/__tests__/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-render-invitation-page-with-inviter-name-and-workspace-1.png +0 -0
  336. package/templates/base/src/client/routes/__tests__/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-show-terms-of-service-and-privacy-policy-links-1.png +0 -0
  337. package/templates/base/src/client/routes/__tests__/invite.test.tsx +138 -0
  338. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-render-Active-Sessions-section-with-session-cards-1.png +0 -0
  339. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-render-page-with-correct-title--Account--1.png +0 -0
  340. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-show-API-Access-section-1.png +0 -0
  341. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-show-Connected-Accounts-section-with-Google-connected-1.png +0 -0
  342. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-show-Danger-Zone-section-with-delete-button-1.png +0 -0
  343. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-show-Security-section-with-Two-Factor-Authentication-1.png +0 -0
  344. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-API-documentation-section-should-display-API-documentation-link-section-1.png +0 -0
  345. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-Create-Webhook-Dialog-should-have-Add-Webhook-trigger-button-1.png +0 -0
  346. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-category-filters-should-display-all-category-buttons-1.png +0 -0
  347. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-display-all-integration-cards-with-names-1.png +0 -0
  348. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-display-category-badges-on-cards-1.png +0 -0
  349. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-show-Configure-button-for-connected-integrations-1.png +0 -0
  350. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-show-Connect-button-for-not-connected-integrations-1.png +0 -0
  351. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-show-Connected-badge-for-connected-integrations-1.png +0 -0
  352. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-Available-Integrations-section-1.png +0 -0
  353. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-Webhooks-section-1.png +0 -0
  354. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-connected-count-1.png +0 -0
  355. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-page-description-1.png +0 -0
  356. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-search-input-1.png +0 -0
  357. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-render-with-correct-title--Integrations--1.png +0 -0
  358. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-search-functionality-should-filter-integrations-based-on-search-query-1.png +0 -0
  359. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-search-functionality-should-search-by-description-1.png +0 -0
  360. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-search-functionality-should-show-no-results-message-when-search-has-no-matches-1.png +0 -0
  361. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-existing-webhook-1.png +0 -0
  362. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-webhook-events-1.png +0 -0
  363. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-webhook-last-delivery-info-1.png +0 -0
  364. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-webhook-success-status-1.png +0 -0
  365. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-have-Add-Webhook-button-1.png +0 -0
  366. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Account-tab-should-display-connected-accounts-section-with-Google-provider-1.png +0 -0
  367. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Account-tab-should-display-sessions-and-danger-zone-sections-1.png +0 -0
  368. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-display-all-notification-toggle-options-1.png +0 -0
  369. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-display-email-notifications-section-1.png +0 -0
  370. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-display-toggle-switches-for-notification-options-1.png +0 -0
  371. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-have-toggles-checked-by-default-1.png +0 -0
  372. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display--Save-Changes--button-1.png +0 -0
  373. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-personal-information-form-1.png +0 -0
  374. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-profile-picture-section-1.png +0 -0
  375. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-user-email-in-disabled-email-input-1.png +0 -0
  376. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-user-initials-in-avatar-1.png +0 -0
  377. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-user-name-in-the-name-input-1.png +0 -0
  378. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-page-rendering-should-display-all-three-tabs-1.png +0 -0
  379. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-page-rendering-should-display-page-description-1.png +0 -0
  380. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-page-rendering-should-render-with-correct-title--Settings--1.png +0 -0
  381. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-tab-navigation-should-show-Profile-tab-as-default-active-tab-1.png +0 -0
  382. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-tab-navigation-should-switch-to-Account-tab-when-clicked-1.png +0 -0
  383. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-tab-navigation-should-switch-to-Notifications-tab-when-clicked-1.png +0 -0
  384. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/team.test.tsx/Team-Page-should-display-Active-Members-section-with-member-count-1.png +0 -0
  385. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/team.test.tsx/Team-Page-should-display-Pending-Invitations-section-with-invitation-details-1.png +0 -0
  386. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/team.test.tsx/Team-Page-should-have-invite-member-button-that-can-be-clicked-1.png +0 -0
  387. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/team.test.tsx/Team-Page-should-render-page-with-correct-title-and-description-1.png +0 -0
  388. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/team.test.tsx/Team-Page-should-render-search-input-that-filters-team-members-1.png +0 -0
  389. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/team.test.tsx/Team-Page-should-show-current-user-with---you---indicator-and-role-badge-1.png +0 -0
  390. package/templates/base/src/client/routes/_authenticated/__tests__/account.test.tsx +324 -0
  391. package/templates/base/src/client/routes/_authenticated/__tests__/integrations.test.tsx +520 -0
  392. package/templates/base/src/client/routes/_authenticated/__tests__/settings.test.tsx +414 -0
  393. package/templates/base/src/client/routes/_authenticated/__tests__/team.test.tsx +374 -0
  394. package/templates/base/src/client/routes/_authenticated/account.tsx +416 -0
  395. package/templates/base/src/client/routes/_authenticated/dashboard.tsx +151 -0
  396. package/templates/base/src/client/routes/_authenticated/integrations.tsx +553 -0
  397. package/templates/base/src/client/routes/_authenticated/settings.tsx +310 -0
  398. package/templates/base/src/client/routes/_authenticated/team.tsx +395 -0
  399. package/templates/base/src/client/routes/_authenticated.tsx +69 -0
  400. package/templates/base/src/client/routes/index.tsx +155 -0
  401. package/templates/base/src/client/routes/invite.$token.tsx +191 -0
  402. package/templates/base/src/client/routes/login.tsx +92 -0
  403. package/templates/base/src/server/__tests__/fixtures.ts +461 -0
  404. package/templates/base/src/server/__tests__/mocks/__tests__/db.test.ts +239 -0
  405. package/templates/base/src/server/__tests__/mocks/__tests__/kv.test.ts +293 -0
  406. package/templates/base/src/server/__tests__/mocks/__tests__/r2.test.ts +363 -0
  407. package/templates/base/src/server/__tests__/mocks/db.ts +186 -0
  408. package/templates/base/src/server/__tests__/mocks/index.ts +33 -0
  409. package/templates/base/src/server/__tests__/mocks/kv.ts +286 -0
  410. package/templates/base/src/server/__tests__/mocks/r2.ts +397 -0
  411. package/templates/base/src/server/__tests__/setup.ts +281 -0
  412. package/templates/base/src/server/auth/__tests__/guards.test.ts +162 -0
  413. package/templates/base/src/server/auth/guards.ts +92 -0
  414. package/templates/base/src/server/auth/permissions.test.ts +45 -0
  415. package/templates/base/src/server/auth/permissions.ts +139 -0
  416. package/templates/base/src/server/auth/roles.test.ts +169 -0
  417. package/templates/base/src/server/auth/roles.ts +141 -0
  418. package/templates/base/src/server/db/client.ts +12 -0
  419. package/templates/base/src/server/db/schema/accounts.ts +20 -0
  420. package/templates/base/src/server/db/schema/audit-logs.ts +26 -0
  421. package/templates/base/src/server/db/schema/index.ts +7 -0
  422. package/templates/base/src/server/db/schema/invitations.ts +30 -0
  423. package/templates/base/src/server/db/schema/refresh-tokens.ts +22 -0
  424. package/templates/base/src/server/db/schema/user-accounts.ts +25 -0
  425. package/templates/base/src/server/db/schema/users.ts +33 -0
  426. package/templates/base/src/server/db/seed.ts +267 -0
  427. package/templates/base/src/server/env.test.ts +84 -0
  428. package/templates/base/src/server/env.ts +78 -0
  429. package/templates/base/src/server/index.ts +82 -0
  430. package/templates/base/src/server/lib/audit.ts +73 -0
  431. package/templates/base/src/server/lib/audited-db.test.ts +107 -0
  432. package/templates/base/src/server/lib/audited-db.ts +154 -0
  433. package/templates/base/src/server/lib/email.test.ts +116 -0
  434. package/templates/base/src/server/lib/email.ts +82 -0
  435. package/templates/base/src/server/lib/errors.test.ts +49 -0
  436. package/templates/base/src/server/lib/errors.ts +64 -0
  437. package/templates/base/src/server/lib/oauth.test.ts +238 -0
  438. package/templates/base/src/server/lib/oauth.ts +113 -0
  439. package/templates/base/src/server/lib/pagination.test.ts +52 -0
  440. package/templates/base/src/server/lib/pagination.ts +32 -0
  441. package/templates/base/src/server/lib/password.test.ts +151 -0
  442. package/templates/base/src/server/lib/password.ts +151 -0
  443. package/templates/base/src/server/lib/providers.test.ts +105 -0
  444. package/templates/base/src/server/lib/providers.ts +62 -0
  445. package/templates/base/src/server/lib/r2-storage.test.ts +202 -0
  446. package/templates/base/src/server/lib/r2-storage.ts +107 -0
  447. package/templates/base/src/server/lib/schema-helpers.ts +16 -0
  448. package/templates/base/src/server/lib/session.test.ts +758 -0
  449. package/templates/base/src/server/lib/session.ts +267 -0
  450. package/templates/base/src/server/lib/tokens.test.ts +208 -0
  451. package/templates/base/src/server/lib/tokens.ts +65 -0
  452. package/templates/base/src/server/lib/transaction.test.ts +45 -0
  453. package/templates/base/src/server/lib/transaction.ts +24 -0
  454. package/templates/base/src/server/middleware/account.test.ts +201 -0
  455. package/templates/base/src/server/middleware/account.ts +66 -0
  456. package/templates/base/src/server/middleware/auth.test.ts +345 -0
  457. package/templates/base/src/server/middleware/auth.ts +146 -0
  458. package/templates/base/src/server/middleware/cors.test.ts +88 -0
  459. package/templates/base/src/server/middleware/cors.ts +26 -0
  460. package/templates/base/src/server/middleware/error-handler.test.ts +69 -0
  461. package/templates/base/src/server/middleware/error-handler.ts +43 -0
  462. package/templates/base/src/server/middleware/index.ts +8 -0
  463. package/templates/base/src/server/middleware/rate-limit.test.ts +472 -0
  464. package/templates/base/src/server/middleware/rate-limit.ts +294 -0
  465. package/templates/base/src/server/middleware/request-context.test.ts +175 -0
  466. package/templates/base/src/server/middleware/request-context.ts +28 -0
  467. package/templates/base/src/server/middleware/request-logger.test.ts +92 -0
  468. package/templates/base/src/server/middleware/request-logger.ts +50 -0
  469. package/templates/base/src/server/routes/accounts/__tests__/handlers.test.ts +460 -0
  470. package/templates/base/src/server/routes/accounts/handlers.ts +179 -0
  471. package/templates/base/src/server/routes/accounts/index.ts +55 -0
  472. package/templates/base/src/server/routes/accounts/routes.ts +205 -0
  473. package/templates/base/src/server/routes/accounts/schemas.ts +31 -0
  474. package/templates/base/src/server/routes/api.ts +37 -0
  475. package/templates/base/src/server/routes/audits/__tests__/handlers.test.ts +349 -0
  476. package/templates/base/src/server/routes/audits/handlers.ts +37 -0
  477. package/templates/base/src/server/routes/audits/index.ts +14 -0
  478. package/templates/base/src/server/routes/audits/routes.ts +29 -0
  479. package/templates/base/src/server/routes/audits/schemas.ts +43 -0
  480. package/templates/base/src/server/routes/auth/__tests__/handlers.test.ts +381 -0
  481. package/templates/base/src/server/routes/auth/handlers.ts +222 -0
  482. package/templates/base/src/server/routes/auth/index.ts +37 -0
  483. package/templates/base/src/server/routes/auth/routes.ts +136 -0
  484. package/templates/base/src/server/routes/auth/schemas.ts +48 -0
  485. package/templates/base/src/server/routes/auth/test-login.ts +156 -0
  486. package/templates/base/src/server/routes/health/__tests__/handlers.test.ts +237 -0
  487. package/templates/base/src/server/routes/health/handlers.ts +83 -0
  488. package/templates/base/src/server/routes/health/index.ts +13 -0
  489. package/templates/base/src/server/routes/health/routes.ts +90 -0
  490. package/templates/base/src/server/routes/index.ts +53 -0
  491. package/templates/base/src/server/routes/invitations/__tests__/handlers.test.ts +473 -0
  492. package/templates/base/src/server/routes/invitations/handlers.ts +71 -0
  493. package/templates/base/src/server/routes/invitations/index.ts +25 -0
  494. package/templates/base/src/server/routes/invitations/routes.ts +69 -0
  495. package/templates/base/src/server/routes/invitations/schemas.ts +39 -0
  496. package/templates/base/src/server/routes/openapi.ts +14 -0
  497. package/templates/base/src/server/routes/schemas.ts +53 -0
  498. package/templates/base/src/server/routes/storage/__tests__/handlers.test.ts +408 -0
  499. package/templates/base/src/server/routes/storage/handlers.ts +100 -0
  500. package/templates/base/src/server/routes/storage/index.ts +42 -0
  501. package/templates/base/src/server/routes/storage/routes.ts +91 -0
  502. package/templates/base/src/server/routes/storage/schemas.ts +56 -0
  503. package/templates/base/src/server/routes/users/__tests__/handlers.test.ts +526 -0
  504. package/templates/base/src/server/routes/users/handlers.ts +228 -0
  505. package/templates/base/src/server/routes/users/index.ts +67 -0
  506. package/templates/base/src/server/routes/users/routes.ts +265 -0
  507. package/templates/base/src/server/routes/users/schemas.ts +67 -0
  508. package/templates/base/src/server/services/__tests__/accounts.test.ts +764 -0
  509. package/templates/base/src/server/services/__tests__/audits.test.ts +235 -0
  510. package/templates/base/src/server/services/__tests__/auth.test.ts +765 -0
  511. package/templates/base/src/server/services/__tests__/invitations.test.ts +704 -0
  512. package/templates/base/src/server/services/__tests__/users.test.ts +755 -0
  513. package/templates/base/src/server/services/accounts.ts +269 -0
  514. package/templates/base/src/server/services/audits.ts +82 -0
  515. package/templates/base/src/server/services/auth.ts +225 -0
  516. package/templates/base/src/server/services/index.ts +6 -0
  517. package/templates/base/src/server/services/invitations.ts +306 -0
  518. package/templates/base/src/server/services/users.ts +350 -0
  519. package/templates/base/src/server/types/auth.ts +36 -0
  520. package/templates/base/src/server/types/index.ts +117 -0
  521. package/templates/base/src/shared/schemas/.gitkeep +0 -0
  522. package/templates/base/src/shared/schemas/__tests__/schemas.test.ts +547 -0
  523. package/templates/base/src/shared/schemas/account.ts +15 -0
  524. package/templates/base/src/shared/schemas/index.ts +6 -0
  525. package/templates/base/src/shared/schemas/invitation.ts +9 -0
  526. package/templates/base/src/shared/schemas/profile.ts +10 -0
  527. package/templates/base/src/shared/schemas/user.ts +16 -0
  528. package/templates/base/src/shared/schemas/webhook.ts +12 -0
  529. package/templates/base/src/shared/types/.gitkeep +0 -0
  530. package/templates/base/src/shared/types/account.ts +12 -0
  531. package/templates/base/src/shared/types/api.ts +1399 -0
  532. package/templates/base/src/shared/types/auth.ts +24 -0
  533. package/templates/base/src/shared/types/index.ts +5 -0
  534. package/templates/base/src/shared/types/user.ts +31 -0
  535. package/templates/base/src/test/vitest-zod-matcher.ts +37 -0
  536. package/templates/base/src/test/vitest.d.ts +19 -0
  537. package/templates/base/tests/e2e/README.md +141 -0
  538. package/templates/base/tests/e2e/a11y/accessibility.spec.ts +925 -0
  539. package/templates/base/tests/e2e/a11y/keyboard-navigation.spec.ts +610 -0
  540. package/templates/base/tests/e2e/api/accounts.spec.ts +148 -0
  541. package/templates/base/tests/e2e/api/audit-logs.spec.ts +130 -0
  542. package/templates/base/tests/e2e/api/authenticated-api.spec.ts +311 -0
  543. package/templates/base/tests/e2e/api/storage.spec.ts +109 -0
  544. package/templates/base/tests/e2e/auth-flows.unauth.spec.ts +117 -0
  545. package/templates/base/tests/e2e/auth-logout.unauth.spec.ts +103 -0
  546. package/templates/base/tests/e2e/auth.setup.ts +115 -0
  547. package/templates/base/tests/e2e/auth.spec.ts +146 -0
  548. package/templates/base/tests/e2e/compatibility/cross-browser.spec.ts +152 -0
  549. package/templates/base/tests/e2e/compatibility/cross-browser.spec.ts-snapshots/login-chromium-chromium-darwin.png +0 -0
  550. package/templates/base/tests/e2e/crud/account.spec.ts +356 -0
  551. package/templates/base/tests/e2e/crud/integrations.spec.ts +419 -0
  552. package/templates/base/tests/e2e/crud/team.spec.ts +287 -0
  553. package/templates/base/tests/e2e/crud/users.spec.ts +239 -0
  554. package/templates/base/tests/e2e/errors/error-boundary.spec.ts +428 -0
  555. package/templates/base/tests/e2e/errors/error-handling.spec.ts +47 -0
  556. package/templates/base/tests/e2e/errors/error-handling.unauth.spec.ts +205 -0
  557. package/templates/base/tests/e2e/fixtures.ts +266 -0
  558. package/templates/base/tests/e2e/forms/validation.spec.ts +569 -0
  559. package/templates/base/tests/e2e/invitations/invite-flow.unauth.spec.ts +204 -0
  560. package/templates/base/tests/e2e/journeys/account-lifecycle.spec.ts +314 -0
  561. package/templates/base/tests/e2e/journeys/audit-investigation.spec.ts +299 -0
  562. package/templates/base/tests/e2e/journeys/auth-onboarding.spec.ts +232 -0
  563. package/templates/base/tests/e2e/journeys/critical-flows.spec.ts +281 -0
  564. package/templates/base/tests/e2e/journeys/error-recovery.spec.ts +354 -0
  565. package/templates/base/tests/e2e/journeys/file-management.spec.ts +307 -0
  566. package/templates/base/tests/e2e/journeys/integrations.spec.ts +372 -0
  567. package/templates/base/tests/e2e/journeys/multi-account.spec.ts +317 -0
  568. package/templates/base/tests/e2e/journeys/rbac-enforcement.spec.ts +389 -0
  569. package/templates/base/tests/e2e/journeys/settings-profile.spec.ts +400 -0
  570. package/templates/base/tests/e2e/journeys/team-collaboration.spec.ts +410 -0
  571. package/templates/base/tests/e2e/mobile/responsive.spec.ts +178 -0
  572. package/templates/base/tests/e2e/navigation/routing.spec.ts +371 -0
  573. package/templates/base/tests/e2e/navigation/sidebar.spec.ts +425 -0
  574. package/templates/base/tests/e2e/pages/ui-features.spec.ts +393 -0
  575. package/templates/base/tests/e2e/performance/baselines.spec.ts +162 -0
  576. package/templates/base/tests/e2e/performance/benchmarks.spec.ts +371 -0
  577. package/templates/base/tests/e2e/smoke.unauth.spec.ts +196 -0
  578. package/templates/base/tests/e2e/visual/components.spec.ts +650 -0
  579. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/confirmation-dialog-visual-darwin.png +0 -0
  580. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dark-mode-background-visual-darwin.png +0 -0
  581. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dark-mode-card-visual-darwin.png +0 -0
  582. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dark-mode-sidebar-visual-darwin.png +0 -0
  583. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dashboard-card-single-visual-darwin.png +0 -0
  584. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dialog-content-visual-darwin.png +0 -0
  585. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dialog-with-backdrop-visual-darwin.png +0 -0
  586. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/empty-search-results-visual-darwin.png +0 -0
  587. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/input-default-visual-darwin.png +0 -0
  588. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/input-focus-ring-visual-darwin.png +0 -0
  589. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/primary-button-focus-visual-darwin.png +0 -0
  590. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/primary-button-hover-visual-darwin.png +0 -0
  591. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/primary-button-visual-darwin.png +0 -0
  592. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/sidebar-active-nav-item-visual-darwin.png +0 -0
  593. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/sidebar-collapsed-visual-darwin.png +0 -0
  594. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/sidebar-navigation-visual-darwin.png +0 -0
  595. package/templates/base/tests/e2e/visual/core-components.spec.ts +192 -0
  596. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/account-page-visual-darwin.png +0 -0
  597. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/create-webhook-dialog-visual-darwin.png +0 -0
  598. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/dashboard-page-visual-darwin.png +0 -0
  599. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/delete-account-dialog-visual-darwin.png +0 -0
  600. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/integrations-page-visual-darwin.png +0 -0
  601. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/invite-member-dialog-visual-darwin.png +0 -0
  602. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/login-page-visual-darwin.png +0 -0
  603. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/settings-account-tab-visual-darwin.png +0 -0
  604. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/settings-profile-tab-visual-darwin.png +0 -0
  605. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/sidebar-navigation-visual-darwin.png +0 -0
  606. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/team-page-visual-darwin.png +0 -0
  607. package/templates/base/tests/e2e/visual/screenshots.spec.ts +230 -0
  608. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/404-page-chromium-darwin.png +0 -0
  609. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/404-page-visual-darwin.png +0 -0
  610. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/account-page-visual-darwin.png +0 -0
  611. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/login-page-chromium-darwin.png +0 -0
  612. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/login-page-visual-darwin.png +0 -0
  613. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/settings-page-visual-darwin.png +0 -0
  614. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/team-invite-dialog-visual-darwin.png +0 -0
  615. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/team-page-visual-darwin.png +0 -0
  616. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/webhook-create-dialog-visual-darwin.png +0 -0
  617. package/templates/base/tests/e2e/visual/theme-colors.spec.ts +293 -0
  618. package/templates/base/tests/e2e/visual/ui-components.spec.ts +502 -0
  619. package/templates/base/tests/integration/accounts/crud.test.ts +1402 -0
  620. package/templates/base/tests/integration/audits/list.test.ts +1133 -0
  621. package/templates/base/tests/integration/auth/auth-service.test.ts +415 -0
  622. package/templates/base/tests/integration/auth/invitation-token.test.ts +529 -0
  623. package/templates/base/tests/integration/auth/logout.test.ts +524 -0
  624. package/templates/base/tests/integration/auth/oauth.test.ts +768 -0
  625. package/templates/base/tests/integration/auth/refresh-token.test.ts +364 -0
  626. package/templates/base/tests/integration/auth/session-expiry.test.ts +569 -0
  627. package/templates/base/tests/integration/auth/session.test.ts +520 -0
  628. package/templates/base/tests/integration/auth/super-admin.test.ts +451 -0
  629. package/templates/base/tests/integration/authorization/analytics-role.test.ts +1026 -0
  630. package/templates/base/tests/integration/authorization/billing-role.test.ts +776 -0
  631. package/templates/base/tests/integration/authorization/guards-roles.test.ts +539 -0
  632. package/templates/base/tests/integration/authorization/multi-tenancy.test.ts +1112 -0
  633. package/templates/base/tests/integration/authorization/role-hierarchy.test.ts +931 -0
  634. package/templates/base/tests/integration/authorization/roles.test.ts +1347 -0
  635. package/templates/base/tests/integration/config/production-behavior.test.ts +536 -0
  636. package/templates/base/tests/integration/db/constraints.test.ts +695 -0
  637. package/templates/base/tests/integration/fixtures/accounts.ts +136 -0
  638. package/templates/base/tests/integration/fixtures/index.ts +232 -0
  639. package/templates/base/tests/integration/fixtures/invitations.ts +195 -0
  640. package/templates/base/tests/integration/fixtures/users.ts +144 -0
  641. package/templates/base/tests/integration/health/health.test.ts +351 -0
  642. package/templates/base/tests/integration/invitations/crud.test.ts +1457 -0
  643. package/templates/base/tests/integration/invitations/email.test.ts +506 -0
  644. package/templates/base/tests/integration/lib/email.test.ts +174 -0
  645. package/templates/base/tests/integration/lib/oauth.test.ts +368 -0
  646. package/templates/base/tests/integration/lib/password.test.ts +192 -0
  647. package/templates/base/tests/integration/lib/schema-helpers.test.ts +129 -0
  648. package/templates/base/tests/integration/lib/tokens.test.ts +304 -0
  649. package/templates/base/tests/integration/middleware/auth.test.ts +499 -0
  650. package/templates/base/tests/integration/middleware/cors.test.ts +334 -0
  651. package/templates/base/tests/integration/middleware/request-context.test.ts +156 -0
  652. package/templates/base/tests/integration/middleware/request-logger.test.ts +313 -0
  653. package/templates/base/tests/integration/performance/response-times.test.ts +509 -0
  654. package/templates/base/tests/integration/security/cookie-security.test.ts +567 -0
  655. package/templates/base/tests/integration/security/csrf-protection.test.ts +542 -0
  656. package/templates/base/tests/integration/security/jwt-validation.test.ts +209 -0
  657. package/templates/base/tests/integration/security/log-sanitization.test.ts +658 -0
  658. package/templates/base/tests/integration/security/rate-limiting.test.ts +1251 -0
  659. package/templates/base/tests/integration/security/sql-injection.test.ts +663 -0
  660. package/templates/base/tests/integration/security/token-hashing.test.ts +371 -0
  661. package/templates/base/tests/integration/security/xss-prevention.test.ts +541 -0
  662. package/templates/base/tests/integration/setup.ts +834 -0
  663. package/templates/base/tests/integration/smoke.test.ts +288 -0
  664. package/templates/base/tests/integration/storage/upload.test.ts +1162 -0
  665. package/templates/base/tests/integration/storage/validation.test.ts +746 -0
  666. package/templates/base/tests/integration/users/crud.test.ts +1297 -0
  667. package/templates/base/tests/integration/users/list.test.ts +698 -0
  668. package/templates/base/tests/integration/vitest.config.ts +80 -0
  669. package/templates/base/tsconfig.app.json +18 -0
  670. package/templates/base/tsconfig.json +39 -0
  671. package/templates/base/tsconfig.node.json +16 -0
  672. package/templates/base/tsconfig.server.json +26 -0
  673. package/templates/base/tsconfig.tsbuildinfo +1 -0
  674. package/templates/base/vite.config.ts +46 -0
  675. package/templates/base/vitest.config.browser.ts +47 -0
  676. package/templates/base/vitest.config.frontend.ts +47 -0
  677. package/templates/base/vitest.config.ts +82 -0
  678. package/templates/base/vitest.workspace.ts +22 -0
  679. package/templates/modules/audit-logs/.gitkeep +0 -0
  680. package/templates/modules/billing/.gitkeep +0 -0
  681. package/templates/modules/invitations/.gitkeep +0 -0
  682. package/templates/modules/storage/.gitkeep +0 -0
  683. package/templates/modules/webhooks/.gitkeep +0 -0
  684. package/templates/providers/auth-email/.gitkeep +0 -0
  685. package/templates/providers/auth-github/.gitkeep +0 -0
  686. package/templates/providers/auth-google/.gitkeep +0 -0
  687. package/templates/providers/email-resend/.gitkeep +0 -0
  688. package/templates/providers/email-sendgrid/.gitkeep +0 -0
@@ -0,0 +1,1297 @@
1
+ /**
2
+ * Users CRUD Integration Tests
3
+ *
4
+ * Tests the user CRUD operations:
5
+ * - GET /api/users/:id - Get user by ID
6
+ * - PATCH /api/users/:id - Update user
7
+ * - DELETE /api/users/:id - Soft delete user
8
+ * - POST /api/users/:id/restore - Restore soft-deleted user
9
+ */
10
+
11
+ import { describe, it, expect, beforeAll } from 'vitest'
12
+ import { Hono } from 'hono'
13
+ import { getEnv, getDb, getSqlite, type TestEnv } from '../setup'
14
+ import {
15
+ createUser,
16
+ createDeletedUser,
17
+ createUserSession,
18
+ createTestScenario,
19
+ createMultiUserScenario,
20
+ createAccount,
21
+ addUserToAccount,
22
+ } from '../fixtures'
23
+ import type { HonoEnv } from '../../../src/server/types'
24
+ import { api } from '../../../src/server/routes'
25
+ import { errorHandler } from '../../../src/server/middleware/error-handler'
26
+ import { sessionMiddleware } from '../../../src/server/lib/session'
27
+
28
+ /**
29
+ * Creates a database wrapper that adds the `execute` method
30
+ * The better-sqlite3 drizzle doesn't have execute, but D1 does
31
+ */
32
+ function createTestDb() {
33
+ const db = getDb()
34
+ return new Proxy(db, {
35
+ get(target, prop) {
36
+ if (prop === 'execute') {
37
+ return target.run.bind(target)
38
+ }
39
+ return (target as any)[prop]
40
+ },
41
+ })
42
+ }
43
+
44
+ describe('Users CRUD Integration', () => {
45
+ let app: Hono<HonoEnv>
46
+ let env: TestEnv
47
+
48
+ beforeAll(() => {
49
+ env = getEnv()
50
+ app = new Hono<HonoEnv>()
51
+
52
+ // Set up error handler
53
+ app.onError(errorHandler)
54
+
55
+ // Set up middleware to inject test environment
56
+ app.use('*', async (c, next) => {
57
+ // Inject environment bindings
58
+ ;(c as any).env = env
59
+
60
+ // Set up database
61
+ const db = createTestDb()
62
+ c.set('db', db)
63
+
64
+ // Set up request context variables
65
+ c.set('transactionId', crypto.randomUUID())
66
+ c.set('ip', '127.0.0.1')
67
+ c.set('userAgent', 'IntegrationTest/1.0')
68
+
69
+ await next()
70
+ })
71
+
72
+ // Session middleware - reads session from KV and sets sessionData in context
73
+ app.use('*', sessionMiddleware())
74
+
75
+ // Mount API routes (includes sessionAuth and accountMiddleware)
76
+ app.route('/api', api)
77
+ })
78
+
79
+ // ============================================================================
80
+ // GET /api/users/:id
81
+ // ============================================================================
82
+
83
+ describe('GET /api/users/:id', () => {
84
+ describe('Authentication (401)', () => {
85
+ it('should return 401 without session cookie', async () => {
86
+ const userId = crypto.randomUUID()
87
+ const res = await app.request(`/api/users/${userId}`, {
88
+ method: 'GET',
89
+ })
90
+
91
+ expect(res.status).toBe(401)
92
+
93
+ const body = await res.json()
94
+ expect(body).toHaveProperty('error')
95
+ expect(body.error.message).toBe('Not authenticated')
96
+ })
97
+
98
+ it('should return 401 with invalid session ID', async () => {
99
+ const userId = crypto.randomUUID()
100
+ const res = await app.request(`/api/users/${userId}`, {
101
+ method: 'GET',
102
+ headers: {
103
+ Cookie: 'sid=invalid-session-id-that-does-not-exist',
104
+ 'account-id': crypto.randomUUID(),
105
+ },
106
+ })
107
+
108
+ expect(res.status).toBe(401)
109
+
110
+ const body = await res.json()
111
+ expect(body.error.message).toBe('Not authenticated')
112
+ })
113
+ })
114
+
115
+ describe('Not Found (404)', () => {
116
+ it('should return 404 for non-existent user', async () => {
117
+ const scenario = await createTestScenario({
118
+ userName: 'Get Test User',
119
+ userEmail: 'gettest@example.com',
120
+ role: 'ADMIN',
121
+ })
122
+
123
+ const nonExistentId = crypto.randomUUID()
124
+ const res = await app.request(`/api/users/${nonExistentId}`, {
125
+ method: 'GET',
126
+ headers: {
127
+ ...scenario.headers,
128
+ 'User-Agent': 'IntegrationTest/1.0',
129
+ 'account-id': scenario.account.id,
130
+ },
131
+ })
132
+
133
+ expect(res.status).toBe(404)
134
+
135
+ const body = await res.json()
136
+ expect(body.error.message).toContain('User')
137
+ })
138
+
139
+ it('should return 404 if user not in same account', async () => {
140
+ // Create a user in one account
141
+ const scenario = await createTestScenario({
142
+ userName: 'First Account User',
143
+ userEmail: 'firstaccount@example.com',
144
+ role: 'ADMIN',
145
+ })
146
+
147
+ // Create another user in a different account
148
+ const otherUser = await createUser({
149
+ email: 'otheraccount@example.com',
150
+ name: 'Other Account User',
151
+ })
152
+ const otherAccount = await createAccount({
153
+ name: 'Other Account',
154
+ })
155
+ await addUserToAccount(otherUser.id, otherAccount.id, 'ADMIN')
156
+
157
+ // Try to get the other user from the first account
158
+ const res = await app.request(`/api/users/${otherUser.id}`, {
159
+ method: 'GET',
160
+ headers: {
161
+ ...scenario.headers,
162
+ 'User-Agent': 'IntegrationTest/1.0',
163
+ 'account-id': scenario.account.id,
164
+ },
165
+ })
166
+
167
+ // Returns 404 (not 403) for security - don't reveal user exists
168
+ expect(res.status).toBe(404)
169
+ })
170
+ })
171
+
172
+ describe('Successful Get (200)', () => {
173
+ it('should return 200 with user details for authorized request', async () => {
174
+ const scenario = await createTestScenario({
175
+ userName: 'Get User Test',
176
+ userEmail: 'getusertest@example.com',
177
+ role: 'VIEWER',
178
+ })
179
+
180
+ const res = await app.request(`/api/users/${scenario.user.id}`, {
181
+ method: 'GET',
182
+ headers: {
183
+ ...scenario.headers,
184
+ 'User-Agent': 'IntegrationTest/1.0',
185
+ 'account-id': scenario.account.id,
186
+ },
187
+ })
188
+
189
+ expect(res.status).toBe(200)
190
+
191
+ const body = await res.json()
192
+ expect(body).toHaveProperty('data')
193
+ expect(body.data.id).toBe(scenario.user.id)
194
+ expect(body.data.email).toBe(scenario.user.email)
195
+ expect(body.data.name).toBe(scenario.user.name)
196
+ })
197
+
198
+ it('should include all user fields in response', async () => {
199
+ const scenario = await createTestScenario({
200
+ userName: 'Fields Test User',
201
+ userEmail: 'fieldstest@example.com',
202
+ role: 'ADMIN',
203
+ })
204
+
205
+ const res = await app.request(`/api/users/${scenario.user.id}`, {
206
+ method: 'GET',
207
+ headers: {
208
+ ...scenario.headers,
209
+ 'User-Agent': 'IntegrationTest/1.0',
210
+ 'account-id': scenario.account.id,
211
+ },
212
+ })
213
+
214
+ expect(res.status).toBe(200)
215
+
216
+ const body = await res.json()
217
+ expect(body.data).toHaveProperty('id')
218
+ expect(body.data).toHaveProperty('email')
219
+ expect(body.data).toHaveProperty('name')
220
+ expect(body.data).toHaveProperty('status')
221
+ expect(body.data).toHaveProperty('isSuperAdmin')
222
+ expect(body.data).toHaveProperty('createdAt')
223
+ expect(body.data).toHaveProperty('updatedAt')
224
+ })
225
+
226
+ it('should allow user to get another user in same account', async () => {
227
+ const scenario = await createMultiUserScenario()
228
+
229
+ // Admin can get viewer's details
230
+ const res = await app.request(`/api/users/${scenario.viewer.user.id}`, {
231
+ method: 'GET',
232
+ headers: {
233
+ ...scenario.admin.headers,
234
+ 'User-Agent': 'IntegrationTest/1.0',
235
+ 'account-id': scenario.account.id,
236
+ },
237
+ })
238
+
239
+ expect(res.status).toBe(200)
240
+
241
+ const body = await res.json()
242
+ expect(body.data.id).toBe(scenario.viewer.user.id)
243
+ })
244
+ })
245
+ })
246
+
247
+ // ============================================================================
248
+ // PATCH /api/users/:id
249
+ // ============================================================================
250
+
251
+ describe('PATCH /api/users/:id', () => {
252
+ describe('Authentication (401)', () => {
253
+ it('should return 401 without session cookie', async () => {
254
+ const userId = crypto.randomUUID()
255
+ const res = await app.request(`/api/users/${userId}`, {
256
+ method: 'PATCH',
257
+ headers: {
258
+ 'Content-Type': 'application/json',
259
+ },
260
+ body: JSON.stringify({ name: 'Updated Name' }),
261
+ })
262
+
263
+ expect(res.status).toBe(401)
264
+
265
+ const body = await res.json()
266
+ expect(body.error.message).toBe('Not authenticated')
267
+ })
268
+ })
269
+
270
+ describe('Not Found (404)', () => {
271
+ it('should return 404 for non-existent user', async () => {
272
+ const scenario = await createTestScenario({
273
+ userName: 'Update Test User',
274
+ userEmail: 'updatetest@example.com',
275
+ role: 'MANAGER',
276
+ })
277
+
278
+ const nonExistentId = crypto.randomUUID()
279
+ const res = await app.request(`/api/users/${nonExistentId}`, {
280
+ method: 'PATCH',
281
+ headers: {
282
+ ...scenario.headers,
283
+ 'User-Agent': 'IntegrationTest/1.0',
284
+ 'account-id': scenario.account.id,
285
+ 'Content-Type': 'application/json',
286
+ },
287
+ body: JSON.stringify({ name: 'Updated Name' }),
288
+ })
289
+
290
+ expect(res.status).toBe(404)
291
+ })
292
+ })
293
+
294
+ describe('Authorization (403)', () => {
295
+ it('should return 403 if user lacks permission to update (VIEWER role)', async () => {
296
+ const scenario = await createMultiUserScenario()
297
+
298
+ // VIEWER role cannot update users
299
+ const res = await app.request(`/api/users/${scenario.admin.user.id}`, {
300
+ method: 'PATCH',
301
+ headers: {
302
+ ...scenario.viewer.headers,
303
+ 'User-Agent': 'IntegrationTest/1.0',
304
+ 'account-id': scenario.account.id,
305
+ 'Content-Type': 'application/json',
306
+ },
307
+ body: JSON.stringify({ name: 'Should Not Update' }),
308
+ })
309
+
310
+ expect(res.status).toBe(403)
311
+ })
312
+ })
313
+
314
+ describe('Validation (400)', () => {
315
+ it('should return 400 for empty name', async () => {
316
+ const scenario = await createTestScenario({
317
+ userName: 'Validation Test User',
318
+ userEmail: 'validationtest@example.com',
319
+ role: 'MANAGER',
320
+ })
321
+
322
+ const res = await app.request(`/api/users/${scenario.user.id}`, {
323
+ method: 'PATCH',
324
+ headers: {
325
+ ...scenario.headers,
326
+ 'User-Agent': 'IntegrationTest/1.0',
327
+ 'account-id': scenario.account.id,
328
+ 'Content-Type': 'application/json',
329
+ },
330
+ body: JSON.stringify({ name: '' }),
331
+ })
332
+
333
+ expect(res.status).toBe(400)
334
+ })
335
+
336
+ it('should return 400 for invalid status value', async () => {
337
+ const scenario = await createTestScenario({
338
+ userName: 'Invalid Status User',
339
+ userEmail: 'invalidstatus@example.com',
340
+ role: 'MANAGER',
341
+ })
342
+
343
+ const res = await app.request(`/api/users/${scenario.user.id}`, {
344
+ method: 'PATCH',
345
+ headers: {
346
+ ...scenario.headers,
347
+ 'User-Agent': 'IntegrationTest/1.0',
348
+ 'account-id': scenario.account.id,
349
+ 'Content-Type': 'application/json',
350
+ },
351
+ body: JSON.stringify({ status: 'invalid_status' }),
352
+ })
353
+
354
+ expect(res.status).toBe(400)
355
+ })
356
+ })
357
+
358
+ describe('Successful Update (200)', () => {
359
+ it('should return 200 on successful name update', async () => {
360
+ const scenario = await createTestScenario({
361
+ userName: 'Original Name',
362
+ userEmail: 'nameupdate@example.com',
363
+ role: 'MANAGER',
364
+ })
365
+
366
+ const res = await app.request(`/api/users/${scenario.user.id}`, {
367
+ method: 'PATCH',
368
+ headers: {
369
+ ...scenario.headers,
370
+ 'User-Agent': 'IntegrationTest/1.0',
371
+ 'account-id': scenario.account.id,
372
+ 'Content-Type': 'application/json',
373
+ },
374
+ body: JSON.stringify({ name: 'Updated Name' }),
375
+ })
376
+
377
+ expect(res.status).toBe(200)
378
+
379
+ const body = await res.json()
380
+ expect(body.data.name).toBe('Updated Name')
381
+ })
382
+
383
+ it('should return 200 on successful status update', async () => {
384
+ const scenario = await createTestScenario({
385
+ userName: 'Status Update User',
386
+ userEmail: 'statusupdate@example.com',
387
+ role: 'MANAGER',
388
+ })
389
+
390
+ const res = await app.request(`/api/users/${scenario.user.id}`, {
391
+ method: 'PATCH',
392
+ headers: {
393
+ ...scenario.headers,
394
+ 'User-Agent': 'IntegrationTest/1.0',
395
+ 'account-id': scenario.account.id,
396
+ 'Content-Type': 'application/json',
397
+ },
398
+ body: JSON.stringify({ status: 'inactive' }),
399
+ })
400
+
401
+ expect(res.status).toBe(200)
402
+
403
+ const body = await res.json()
404
+ expect(body.data.status).toBe('inactive')
405
+ })
406
+
407
+ it('should update both name and status together', async () => {
408
+ const scenario = await createTestScenario({
409
+ userName: 'Both Fields User',
410
+ userEmail: 'bothfields@example.com',
411
+ role: 'ADMIN',
412
+ })
413
+
414
+ const res = await app.request(`/api/users/${scenario.user.id}`, {
415
+ method: 'PATCH',
416
+ headers: {
417
+ ...scenario.headers,
418
+ 'User-Agent': 'IntegrationTest/1.0',
419
+ 'account-id': scenario.account.id,
420
+ 'Content-Type': 'application/json',
421
+ },
422
+ body: JSON.stringify({
423
+ name: 'New Name',
424
+ status: 'inactive',
425
+ }),
426
+ })
427
+
428
+ expect(res.status).toBe(200)
429
+
430
+ const body = await res.json()
431
+ expect(body.data.name).toBe('New Name')
432
+ expect(body.data.status).toBe('inactive')
433
+ })
434
+
435
+ it('should verify database is updated after PATCH', async () => {
436
+ const scenario = await createTestScenario({
437
+ userName: 'DB Verify User',
438
+ userEmail: 'dbverify@example.com',
439
+ role: 'MANAGER',
440
+ })
441
+
442
+ // Update the user
443
+ await app.request(`/api/users/${scenario.user.id}`, {
444
+ method: 'PATCH',
445
+ headers: {
446
+ ...scenario.headers,
447
+ 'User-Agent': 'IntegrationTest/1.0',
448
+ 'account-id': scenario.account.id,
449
+ 'Content-Type': 'application/json',
450
+ },
451
+ body: JSON.stringify({ name: 'Database Updated Name' }),
452
+ })
453
+
454
+ // Verify by fetching the user again
455
+ const getRes = await app.request(`/api/users/${scenario.user.id}`, {
456
+ method: 'GET',
457
+ headers: {
458
+ ...scenario.headers,
459
+ 'User-Agent': 'IntegrationTest/1.0',
460
+ 'account-id': scenario.account.id,
461
+ },
462
+ })
463
+
464
+ expect(getRes.status).toBe(200)
465
+
466
+ const body = await getRes.json()
467
+ expect(body.data.name).toBe('Database Updated Name')
468
+ })
469
+
470
+ it('should allow MANAGER role to update users', async () => {
471
+ const scenario = await createMultiUserScenario()
472
+
473
+ // Manager updates viewer
474
+ const res = await app.request(`/api/users/${scenario.viewer.user.id}`, {
475
+ method: 'PATCH',
476
+ headers: {
477
+ ...scenario.manager.headers,
478
+ 'User-Agent': 'IntegrationTest/1.0',
479
+ 'account-id': scenario.account.id,
480
+ 'Content-Type': 'application/json',
481
+ },
482
+ body: JSON.stringify({ name: 'Manager Updated Name' }),
483
+ })
484
+
485
+ expect(res.status).toBe(200)
486
+ })
487
+ })
488
+ })
489
+
490
+ // ============================================================================
491
+ // DELETE /api/users/:id
492
+ // ============================================================================
493
+
494
+ describe('DELETE /api/users/:id', () => {
495
+ describe('Authentication (401)', () => {
496
+ it('should return 401 without session cookie', async () => {
497
+ const userId = crypto.randomUUID()
498
+ const res = await app.request(`/api/users/${userId}`, {
499
+ method: 'DELETE',
500
+ })
501
+
502
+ expect(res.status).toBe(401)
503
+
504
+ const body = await res.json()
505
+ expect(body.error.message).toBe('Not authenticated')
506
+ })
507
+ })
508
+
509
+ describe('Not Found (404)', () => {
510
+ it('should return 404 for non-existent user', async () => {
511
+ const scenario = await createTestScenario({
512
+ userName: 'Delete Test User',
513
+ userEmail: 'deletetest@example.com',
514
+ role: 'ADMIN',
515
+ })
516
+
517
+ const nonExistentId = crypto.randomUUID()
518
+ const res = await app.request(`/api/users/${nonExistentId}`, {
519
+ method: 'DELETE',
520
+ headers: {
521
+ ...scenario.headers,
522
+ 'User-Agent': 'IntegrationTest/1.0',
523
+ 'account-id': scenario.account.id,
524
+ },
525
+ })
526
+
527
+ expect(res.status).toBe(404)
528
+ })
529
+ })
530
+
531
+ describe('Authorization (403)', () => {
532
+ it('should return 403 if user lacks permission to delete (MANAGER role)', async () => {
533
+ const scenario = await createMultiUserScenario()
534
+
535
+ // MANAGER role cannot delete users (requires ADMIN)
536
+ const res = await app.request(`/api/users/${scenario.viewer.user.id}`, {
537
+ method: 'DELETE',
538
+ headers: {
539
+ ...scenario.manager.headers,
540
+ 'User-Agent': 'IntegrationTest/1.0',
541
+ 'account-id': scenario.account.id,
542
+ },
543
+ })
544
+
545
+ expect(res.status).toBe(403)
546
+ })
547
+
548
+ it('should return 403 if user lacks permission to delete (VIEWER role)', async () => {
549
+ const scenario = await createMultiUserScenario()
550
+
551
+ const res = await app.request(`/api/users/${scenario.admin.user.id}`, {
552
+ method: 'DELETE',
553
+ headers: {
554
+ ...scenario.viewer.headers,
555
+ 'User-Agent': 'IntegrationTest/1.0',
556
+ 'account-id': scenario.account.id,
557
+ },
558
+ })
559
+
560
+ expect(res.status).toBe(403)
561
+ })
562
+ })
563
+
564
+ describe('Successful Delete (204)', () => {
565
+ it('should return 204 on successful delete', async () => {
566
+ const scenario = await createTestScenario({
567
+ userName: 'Delete Success User',
568
+ userEmail: 'deletesuccess@example.com',
569
+ role: 'ADMIN',
570
+ })
571
+
572
+ // Create a user to delete
573
+ const userToDelete = await createUser({
574
+ email: 'tobedeleted@example.com',
575
+ name: 'To Be Deleted',
576
+ })
577
+ await addUserToAccount(userToDelete.id, scenario.account.id, 'VIEWER')
578
+
579
+ const res = await app.request(`/api/users/${userToDelete.id}`, {
580
+ method: 'DELETE',
581
+ headers: {
582
+ ...scenario.headers,
583
+ 'User-Agent': 'IntegrationTest/1.0',
584
+ 'account-id': scenario.account.id,
585
+ },
586
+ })
587
+
588
+ expect(res.status).toBe(204)
589
+ })
590
+
591
+ it('should verify deleted_at is set (soft delete)', async () => {
592
+ const scenario = await createTestScenario({
593
+ userName: 'Soft Delete User',
594
+ userEmail: 'softdelete@example.com',
595
+ role: 'ADMIN',
596
+ })
597
+
598
+ // Create a user to delete
599
+ const userToDelete = await createUser({
600
+ email: 'softdeleted@example.com',
601
+ name: 'Soft Deleted',
602
+ })
603
+ await addUserToAccount(userToDelete.id, scenario.account.id, 'VIEWER')
604
+
605
+ // Delete the user
606
+ await app.request(`/api/users/${userToDelete.id}`, {
607
+ method: 'DELETE',
608
+ headers: {
609
+ ...scenario.headers,
610
+ 'User-Agent': 'IntegrationTest/1.0',
611
+ 'account-id': scenario.account.id,
612
+ },
613
+ })
614
+
615
+ // Verify in database that deleted_at is set
616
+ const sqlite = getSqlite()
617
+ const row = sqlite.prepare('SELECT deleted_at FROM users WHERE id = ?').get(userToDelete.id) as { deleted_at: string | null }
618
+ expect(row.deleted_at).not.toBeNull()
619
+ })
620
+
621
+ it('should not appear in user list after deletion', async () => {
622
+ const scenario = await createTestScenario({
623
+ userName: 'List After Delete User',
624
+ userEmail: 'listafterdelete@example.com',
625
+ role: 'ADMIN',
626
+ })
627
+
628
+ // Create a user to delete
629
+ const userToDelete = await createUser({
630
+ email: 'willnotappear@example.com',
631
+ name: 'Will Not Appear',
632
+ })
633
+ await addUserToAccount(userToDelete.id, scenario.account.id, 'VIEWER')
634
+
635
+ // Delete the user
636
+ await app.request(`/api/users/${userToDelete.id}`, {
637
+ method: 'DELETE',
638
+ headers: {
639
+ ...scenario.headers,
640
+ 'User-Agent': 'IntegrationTest/1.0',
641
+ 'account-id': scenario.account.id,
642
+ },
643
+ })
644
+
645
+ // List users and verify deleted user is not included
646
+ const listRes = await app.request('/api/users', {
647
+ method: 'GET',
648
+ headers: {
649
+ ...scenario.headers,
650
+ 'User-Agent': 'IntegrationTest/1.0',
651
+ 'account-id': scenario.account.id,
652
+ },
653
+ })
654
+
655
+ expect(listRes.status).toBe(200)
656
+
657
+ const body = await listRes.json()
658
+ const userIds = body.data.map((u: any) => u.id)
659
+ expect(userIds).not.toContain(userToDelete.id)
660
+ })
661
+ })
662
+ })
663
+
664
+ // ============================================================================
665
+ // POST /api/users/accounts (Bulk Create User-Account Relationships)
666
+ // ============================================================================
667
+
668
+ describe('POST /api/users/accounts', () => {
669
+ describe('Authentication (401)', () => {
670
+ it('should return 401 without session cookie', async () => {
671
+ const res = await app.request('/api/users/accounts', {
672
+ method: 'POST',
673
+ headers: {
674
+ 'Content-Type': 'application/json',
675
+ },
676
+ body: JSON.stringify([
677
+ { userId: crypto.randomUUID(), accountId: crypto.randomUUID(), role: 'VIEWER' },
678
+ ]),
679
+ })
680
+
681
+ expect(res.status).toBe(401)
682
+
683
+ const body = await res.json()
684
+ expect(body.error.message).toBe('Not authenticated')
685
+ })
686
+ })
687
+
688
+ describe('Authorization (403)', () => {
689
+ it('should return 403 if user lacks permission (VIEWER role)', async () => {
690
+ const scenario = await createMultiUserScenario()
691
+
692
+ const res = await app.request('/api/users/accounts', {
693
+ method: 'POST',
694
+ headers: {
695
+ ...scenario.viewer.headers,
696
+ 'User-Agent': 'IntegrationTest/1.0',
697
+ 'account-id': scenario.account.id,
698
+ 'Content-Type': 'application/json',
699
+ },
700
+ body: JSON.stringify([
701
+ { userId: scenario.admin.user.id, accountId: scenario.account.id, role: 'VIEWER' },
702
+ ]),
703
+ })
704
+
705
+ expect(res.status).toBe(403)
706
+ })
707
+ })
708
+
709
+ describe('Validation (400)', () => {
710
+ it('should return 400 for empty array', async () => {
711
+ const scenario = await createTestScenario({
712
+ userName: 'Bulk Create User',
713
+ userEmail: 'bulkcreate@example.com',
714
+ role: 'MANAGER',
715
+ })
716
+
717
+ const res = await app.request('/api/users/accounts', {
718
+ method: 'POST',
719
+ headers: {
720
+ ...scenario.headers,
721
+ 'User-Agent': 'IntegrationTest/1.0',
722
+ 'account-id': scenario.account.id,
723
+ 'Content-Type': 'application/json',
724
+ },
725
+ body: JSON.stringify([]),
726
+ })
727
+
728
+ expect(res.status).toBe(400)
729
+ })
730
+
731
+ it('should return 400 for invalid role', async () => {
732
+ const scenario = await createTestScenario({
733
+ userName: 'Invalid Role User',
734
+ userEmail: 'invalidrole@example.com',
735
+ role: 'MANAGER',
736
+ })
737
+
738
+ const res = await app.request('/api/users/accounts', {
739
+ method: 'POST',
740
+ headers: {
741
+ ...scenario.headers,
742
+ 'User-Agent': 'IntegrationTest/1.0',
743
+ 'account-id': scenario.account.id,
744
+ 'Content-Type': 'application/json',
745
+ },
746
+ body: JSON.stringify([
747
+ { userId: crypto.randomUUID(), accountId: crypto.randomUUID(), role: 'INVALID_ROLE' },
748
+ ]),
749
+ })
750
+
751
+ expect(res.status).toBe(400)
752
+ })
753
+ })
754
+
755
+ describe('Successful Create (201)', () => {
756
+ it('should return 201 on successful bulk create', async () => {
757
+ const scenario = await createTestScenario({
758
+ userName: 'Bulk Admin',
759
+ userEmail: 'bulkadmin@example.com',
760
+ role: 'MANAGER',
761
+ })
762
+
763
+ // Create a new user to add to account
764
+ const newUser = await createUser({
765
+ email: 'newuserforaccount@example.com',
766
+ name: 'New User For Account',
767
+ })
768
+
769
+ // Create another account
770
+ const anotherAccount = await createAccount({
771
+ name: 'Another Account for Bulk',
772
+ })
773
+
774
+ const res = await app.request('/api/users/accounts', {
775
+ method: 'POST',
776
+ headers: {
777
+ ...scenario.headers,
778
+ 'User-Agent': 'IntegrationTest/1.0',
779
+ 'account-id': scenario.account.id,
780
+ 'Content-Type': 'application/json',
781
+ },
782
+ body: JSON.stringify([
783
+ { userId: newUser.id, accountId: anotherAccount.id, role: 'VIEWER' },
784
+ ]),
785
+ })
786
+
787
+ expect(res.status).toBe(201)
788
+
789
+ const body = await res.json()
790
+ expect(body.success).toBe(true)
791
+ expect(body.count).toBe(1)
792
+ })
793
+
794
+ it('should update existing role if user-account relationship exists', async () => {
795
+ const scenario = await createTestScenario({
796
+ userName: 'Role Update Admin',
797
+ userEmail: 'roleupdate@example.com',
798
+ role: 'MANAGER',
799
+ })
800
+
801
+ // Create a user and add to the account with VIEWER role
802
+ const existingUser = await createUser({
803
+ email: 'existingforroleupdaet@example.com',
804
+ name: 'Existing For Role Update',
805
+ })
806
+ await addUserToAccount(existingUser.id, scenario.account.id, 'VIEWER')
807
+
808
+ // Now update the role to EDITOR via bulk operation
809
+ const res = await app.request('/api/users/accounts', {
810
+ method: 'POST',
811
+ headers: {
812
+ ...scenario.headers,
813
+ 'User-Agent': 'IntegrationTest/1.0',
814
+ 'account-id': scenario.account.id,
815
+ 'Content-Type': 'application/json',
816
+ },
817
+ body: JSON.stringify([
818
+ { userId: existingUser.id, accountId: scenario.account.id, role: 'EDITOR' },
819
+ ]),
820
+ })
821
+
822
+ expect(res.status).toBe(201)
823
+
824
+ const body = await res.json()
825
+ expect(body.success).toBe(true)
826
+ expect(body.count).toBe(1)
827
+
828
+ // Verify role was updated in database
829
+ const sqlite = getSqlite()
830
+ const row = sqlite.prepare('SELECT role FROM user_accounts WHERE user_id = ? AND account_id = ?').get(existingUser.id, scenario.account.id) as { role: string }
831
+ expect(row.role).toBe('EDITOR')
832
+ })
833
+
834
+ it('should create multiple user-account relationships', async () => {
835
+ const scenario = await createTestScenario({
836
+ userName: 'Multi Bulk Admin',
837
+ userEmail: 'multibulk@example.com',
838
+ role: 'MANAGER',
839
+ })
840
+
841
+ // Create multiple users
842
+ const user1 = await createUser({
843
+ email: 'bulkuser1@example.com',
844
+ name: 'Bulk User 1',
845
+ })
846
+ const user2 = await createUser({
847
+ email: 'bulkuser2@example.com',
848
+ name: 'Bulk User 2',
849
+ })
850
+
851
+ // Create another account
852
+ const newAccount = await createAccount({
853
+ name: 'Bulk Account',
854
+ })
855
+
856
+ const res = await app.request('/api/users/accounts', {
857
+ method: 'POST',
858
+ headers: {
859
+ ...scenario.headers,
860
+ 'User-Agent': 'IntegrationTest/1.0',
861
+ 'account-id': scenario.account.id,
862
+ 'Content-Type': 'application/json',
863
+ },
864
+ body: JSON.stringify([
865
+ { userId: user1.id, accountId: newAccount.id, role: 'VIEWER' },
866
+ { userId: user2.id, accountId: newAccount.id, role: 'EDITOR' },
867
+ ]),
868
+ })
869
+
870
+ expect(res.status).toBe(201)
871
+
872
+ const body = await res.json()
873
+ expect(body.success).toBe(true)
874
+ expect(body.count).toBe(2)
875
+ })
876
+ })
877
+ })
878
+
879
+ // ============================================================================
880
+ // DELETE /api/users/accounts (Bulk Delete User-Account Relationships)
881
+ // ============================================================================
882
+
883
+ describe('DELETE /api/users/accounts', () => {
884
+ describe('Authentication (401)', () => {
885
+ it('should return 401 without session cookie', async () => {
886
+ const res = await app.request('/api/users/accounts', {
887
+ method: 'DELETE',
888
+ headers: {
889
+ 'Content-Type': 'application/json',
890
+ },
891
+ body: JSON.stringify([
892
+ { userId: crypto.randomUUID(), accountId: crypto.randomUUID(), role: 'VIEWER' },
893
+ ]),
894
+ })
895
+
896
+ expect(res.status).toBe(401)
897
+
898
+ const body = await res.json()
899
+ expect(body.error.message).toBe('Not authenticated')
900
+ })
901
+ })
902
+
903
+ describe('Authorization (403)', () => {
904
+ it('should return 403 if user lacks permission (VIEWER role)', async () => {
905
+ const scenario = await createMultiUserScenario()
906
+
907
+ const res = await app.request('/api/users/accounts', {
908
+ method: 'DELETE',
909
+ headers: {
910
+ ...scenario.viewer.headers,
911
+ 'User-Agent': 'IntegrationTest/1.0',
912
+ 'account-id': scenario.account.id,
913
+ 'Content-Type': 'application/json',
914
+ },
915
+ body: JSON.stringify([
916
+ { userId: scenario.admin.user.id, accountId: scenario.account.id, role: 'ADMIN' },
917
+ ]),
918
+ })
919
+
920
+ expect(res.status).toBe(403)
921
+ })
922
+ })
923
+
924
+ describe('Validation (400)', () => {
925
+ it('should return 400 for empty array', async () => {
926
+ const scenario = await createTestScenario({
927
+ userName: 'Bulk Delete User',
928
+ userEmail: 'bulkdelete@example.com',
929
+ role: 'MANAGER',
930
+ })
931
+
932
+ const res = await app.request('/api/users/accounts', {
933
+ method: 'DELETE',
934
+ headers: {
935
+ ...scenario.headers,
936
+ 'User-Agent': 'IntegrationTest/1.0',
937
+ 'account-id': scenario.account.id,
938
+ 'Content-Type': 'application/json',
939
+ },
940
+ body: JSON.stringify([]),
941
+ })
942
+
943
+ expect(res.status).toBe(400)
944
+ })
945
+ })
946
+
947
+ describe('Successful Delete (200)', () => {
948
+ it('should return 200 on successful bulk delete', async () => {
949
+ const scenario = await createTestScenario({
950
+ userName: 'Delete Admin',
951
+ userEmail: 'deleteadmin@example.com',
952
+ role: 'MANAGER',
953
+ })
954
+
955
+ // Create a user and add to another account
956
+ const userToRemove = await createUser({
957
+ email: 'usertoremove@example.com',
958
+ name: 'User To Remove',
959
+ })
960
+ const accountToRemoveFrom = await createAccount({
961
+ name: 'Account To Remove From',
962
+ })
963
+ await addUserToAccount(userToRemove.id, accountToRemoveFrom.id, 'VIEWER')
964
+
965
+ const res = await app.request('/api/users/accounts', {
966
+ method: 'DELETE',
967
+ headers: {
968
+ ...scenario.headers,
969
+ 'User-Agent': 'IntegrationTest/1.0',
970
+ 'account-id': scenario.account.id,
971
+ 'Content-Type': 'application/json',
972
+ },
973
+ body: JSON.stringify([
974
+ { userId: userToRemove.id, accountId: accountToRemoveFrom.id, role: 'VIEWER' },
975
+ ]),
976
+ })
977
+
978
+ expect(res.status).toBe(200)
979
+
980
+ const body = await res.json()
981
+ expect(body.success).toBe(true)
982
+ expect(body.count).toBe(1)
983
+ })
984
+
985
+ it('should verify user-account relationship is deleted from database', async () => {
986
+ const scenario = await createTestScenario({
987
+ userName: 'Verify Delete Admin',
988
+ userEmail: 'verifydelete@example.com',
989
+ role: 'MANAGER',
990
+ })
991
+
992
+ // Create a user and add to another account
993
+ const userToCheck = await createUser({
994
+ email: 'usertocheck@example.com',
995
+ name: 'User To Check',
996
+ })
997
+ const accountToCheck = await createAccount({
998
+ name: 'Account To Check',
999
+ })
1000
+ await addUserToAccount(userToCheck.id, accountToCheck.id, 'EDITOR')
1001
+
1002
+ // Verify relationship exists
1003
+ const sqlite = getSqlite()
1004
+ const beforeRow = sqlite.prepare('SELECT * FROM user_accounts WHERE user_id = ? AND account_id = ?').get(userToCheck.id, accountToCheck.id)
1005
+ expect(beforeRow).toBeDefined()
1006
+
1007
+ // Delete the relationship
1008
+ await app.request('/api/users/accounts', {
1009
+ method: 'DELETE',
1010
+ headers: {
1011
+ ...scenario.headers,
1012
+ 'User-Agent': 'IntegrationTest/1.0',
1013
+ 'account-id': scenario.account.id,
1014
+ 'Content-Type': 'application/json',
1015
+ },
1016
+ body: JSON.stringify([
1017
+ { userId: userToCheck.id, accountId: accountToCheck.id, role: 'EDITOR' },
1018
+ ]),
1019
+ })
1020
+
1021
+ // Verify relationship is deleted
1022
+ const afterRow = sqlite.prepare('SELECT * FROM user_accounts WHERE user_id = ? AND account_id = ?').get(userToCheck.id, accountToCheck.id)
1023
+ expect(afterRow).toBeUndefined()
1024
+ })
1025
+
1026
+ it('should delete multiple user-account relationships', async () => {
1027
+ const scenario = await createTestScenario({
1028
+ userName: 'Multi Delete Admin',
1029
+ userEmail: 'multidelete@example.com',
1030
+ role: 'MANAGER',
1031
+ })
1032
+
1033
+ // Create multiple users and add to an account
1034
+ const user1 = await createUser({
1035
+ email: 'deleteuser1@example.com',
1036
+ name: 'Delete User 1',
1037
+ })
1038
+ const user2 = await createUser({
1039
+ email: 'deleteuser2@example.com',
1040
+ name: 'Delete User 2',
1041
+ })
1042
+ const accountForDelete = await createAccount({
1043
+ name: 'Account For Delete',
1044
+ })
1045
+ await addUserToAccount(user1.id, accountForDelete.id, 'VIEWER')
1046
+ await addUserToAccount(user2.id, accountForDelete.id, 'EDITOR')
1047
+
1048
+ const res = await app.request('/api/users/accounts', {
1049
+ method: 'DELETE',
1050
+ headers: {
1051
+ ...scenario.headers,
1052
+ 'User-Agent': 'IntegrationTest/1.0',
1053
+ 'account-id': scenario.account.id,
1054
+ 'Content-Type': 'application/json',
1055
+ },
1056
+ body: JSON.stringify([
1057
+ { userId: user1.id, accountId: accountForDelete.id, role: 'VIEWER' },
1058
+ { userId: user2.id, accountId: accountForDelete.id, role: 'EDITOR' },
1059
+ ]),
1060
+ })
1061
+
1062
+ expect(res.status).toBe(200)
1063
+
1064
+ const body = await res.json()
1065
+ expect(body.success).toBe(true)
1066
+ expect(body.count).toBe(2)
1067
+ })
1068
+ })
1069
+ })
1070
+
1071
+ // ============================================================================
1072
+ // POST /api/users/:id/restore
1073
+ // ============================================================================
1074
+
1075
+ describe('POST /api/users/:id/restore', () => {
1076
+ describe('Authentication (401)', () => {
1077
+ it('should return 401 without session cookie', async () => {
1078
+ const userId = crypto.randomUUID()
1079
+ const res = await app.request(`/api/users/${userId}/restore`, {
1080
+ method: 'POST',
1081
+ })
1082
+
1083
+ expect(res.status).toBe(401)
1084
+
1085
+ const body = await res.json()
1086
+ expect(body.error.message).toBe('Not authenticated')
1087
+ })
1088
+ })
1089
+
1090
+ describe('Not Found (404)', () => {
1091
+ it('should return 404 for non-existent user', async () => {
1092
+ const scenario = await createTestScenario({
1093
+ userName: 'Restore Test User',
1094
+ userEmail: 'restoretest@example.com',
1095
+ role: 'ADMIN',
1096
+ })
1097
+
1098
+ const nonExistentId = crypto.randomUUID()
1099
+ const res = await app.request(`/api/users/${nonExistentId}/restore`, {
1100
+ method: 'POST',
1101
+ headers: {
1102
+ ...scenario.headers,
1103
+ 'User-Agent': 'IntegrationTest/1.0',
1104
+ 'account-id': scenario.account.id,
1105
+ },
1106
+ })
1107
+
1108
+ expect(res.status).toBe(404)
1109
+ })
1110
+
1111
+ it('should return 404 for user that is not deleted', async () => {
1112
+ const scenario = await createTestScenario({
1113
+ userName: 'Not Deleted User',
1114
+ userEmail: 'notdeleted@example.com',
1115
+ role: 'ADMIN',
1116
+ })
1117
+
1118
+ // Try to restore a user that is not deleted
1119
+ const res = await app.request(`/api/users/${scenario.user.id}/restore`, {
1120
+ method: 'POST',
1121
+ headers: {
1122
+ ...scenario.headers,
1123
+ 'User-Agent': 'IntegrationTest/1.0',
1124
+ 'account-id': scenario.account.id,
1125
+ },
1126
+ })
1127
+
1128
+ expect(res.status).toBe(404)
1129
+
1130
+ const body = await res.json()
1131
+ expect(body.error.message).toContain('not deleted')
1132
+ })
1133
+ })
1134
+
1135
+ describe('Authorization (403)', () => {
1136
+ it('should return 403 if user lacks permission to restore (MANAGER role)', async () => {
1137
+ const scenario = await createMultiUserScenario()
1138
+
1139
+ // Create a deleted user
1140
+ const deletedUser = await createDeletedUser({
1141
+ email: 'deletedformanager@example.com',
1142
+ name: 'Deleted For Manager',
1143
+ })
1144
+ await addUserToAccount(deletedUser.id, scenario.account.id, 'VIEWER')
1145
+
1146
+ // MANAGER role cannot restore users (requires ADMIN)
1147
+ const res = await app.request(`/api/users/${deletedUser.id}/restore`, {
1148
+ method: 'POST',
1149
+ headers: {
1150
+ ...scenario.manager.headers,
1151
+ 'User-Agent': 'IntegrationTest/1.0',
1152
+ 'account-id': scenario.account.id,
1153
+ },
1154
+ })
1155
+
1156
+ expect(res.status).toBe(403)
1157
+ })
1158
+
1159
+ it('should return 403 if user lacks permission to restore (VIEWER role)', async () => {
1160
+ const scenario = await createMultiUserScenario()
1161
+
1162
+ // Create a deleted user
1163
+ const deletedUser = await createDeletedUser({
1164
+ email: 'deletedforviewer@example.com',
1165
+ name: 'Deleted For Viewer',
1166
+ })
1167
+ await addUserToAccount(deletedUser.id, scenario.account.id, 'VIEWER')
1168
+
1169
+ const res = await app.request(`/api/users/${deletedUser.id}/restore`, {
1170
+ method: 'POST',
1171
+ headers: {
1172
+ ...scenario.viewer.headers,
1173
+ 'User-Agent': 'IntegrationTest/1.0',
1174
+ 'account-id': scenario.account.id,
1175
+ },
1176
+ })
1177
+
1178
+ expect(res.status).toBe(403)
1179
+ })
1180
+ })
1181
+
1182
+ describe('Successful Restore (200)', () => {
1183
+ it('should return 200 on successful restore', async () => {
1184
+ const scenario = await createTestScenario({
1185
+ userName: 'Restore Admin User',
1186
+ userEmail: 'restoreadmin@example.com',
1187
+ role: 'ADMIN',
1188
+ })
1189
+
1190
+ // Create a deleted user
1191
+ const deletedUser = await createDeletedUser({
1192
+ email: 'toberestored@example.com',
1193
+ name: 'To Be Restored',
1194
+ })
1195
+ await addUserToAccount(deletedUser.id, scenario.account.id, 'VIEWER')
1196
+
1197
+ const res = await app.request(`/api/users/${deletedUser.id}/restore`, {
1198
+ method: 'POST',
1199
+ headers: {
1200
+ ...scenario.headers,
1201
+ 'User-Agent': 'IntegrationTest/1.0',
1202
+ 'account-id': scenario.account.id,
1203
+ },
1204
+ })
1205
+
1206
+ expect(res.status).toBe(200)
1207
+
1208
+ const body = await res.json()
1209
+ expect(body.data.id).toBe(deletedUser.id)
1210
+ expect(body.data.deletedAt).toBeNull()
1211
+ })
1212
+
1213
+ it('should verify deleted_at is cleared after restore', async () => {
1214
+ const scenario = await createTestScenario({
1215
+ userName: 'Verify Restore User',
1216
+ userEmail: 'verifyrestore@example.com',
1217
+ role: 'ADMIN',
1218
+ })
1219
+
1220
+ // Create a deleted user
1221
+ const deletedUser = await createDeletedUser({
1222
+ email: 'deletedatcleared@example.com',
1223
+ name: 'Deleted At Cleared',
1224
+ })
1225
+ await addUserToAccount(deletedUser.id, scenario.account.id, 'VIEWER')
1226
+
1227
+ // Restore the user
1228
+ await app.request(`/api/users/${deletedUser.id}/restore`, {
1229
+ method: 'POST',
1230
+ headers: {
1231
+ ...scenario.headers,
1232
+ 'User-Agent': 'IntegrationTest/1.0',
1233
+ 'account-id': scenario.account.id,
1234
+ },
1235
+ })
1236
+
1237
+ // Verify in database that deleted_at is cleared
1238
+ const sqlite = getSqlite()
1239
+ const row = sqlite.prepare('SELECT deleted_at FROM users WHERE id = ?').get(deletedUser.id) as { deleted_at: string | null }
1240
+ expect(row.deleted_at).toBeNull()
1241
+ })
1242
+
1243
+ it('should appear in user list after restoration', async () => {
1244
+ const scenario = await createTestScenario({
1245
+ userName: 'List After Restore User',
1246
+ userEmail: 'listafterrestore@example.com',
1247
+ role: 'ADMIN',
1248
+ })
1249
+
1250
+ // Create a deleted user
1251
+ const deletedUser = await createDeletedUser({
1252
+ email: 'willappear@example.com',
1253
+ name: 'Will Appear',
1254
+ })
1255
+ await addUserToAccount(deletedUser.id, scenario.account.id, 'VIEWER')
1256
+
1257
+ // Verify user is NOT in list before restore
1258
+ const listBefore = await app.request('/api/users', {
1259
+ method: 'GET',
1260
+ headers: {
1261
+ ...scenario.headers,
1262
+ 'User-Agent': 'IntegrationTest/1.0',
1263
+ 'account-id': scenario.account.id,
1264
+ },
1265
+ })
1266
+
1267
+ const bodyBefore = await listBefore.json()
1268
+ const userIdsBefore = bodyBefore.data.map((u: any) => u.id)
1269
+ expect(userIdsBefore).not.toContain(deletedUser.id)
1270
+
1271
+ // Restore the user
1272
+ await app.request(`/api/users/${deletedUser.id}/restore`, {
1273
+ method: 'POST',
1274
+ headers: {
1275
+ ...scenario.headers,
1276
+ 'User-Agent': 'IntegrationTest/1.0',
1277
+ 'account-id': scenario.account.id,
1278
+ },
1279
+ })
1280
+
1281
+ // Verify user IS in list after restore
1282
+ const listAfter = await app.request('/api/users', {
1283
+ method: 'GET',
1284
+ headers: {
1285
+ ...scenario.headers,
1286
+ 'User-Agent': 'IntegrationTest/1.0',
1287
+ 'account-id': scenario.account.id,
1288
+ },
1289
+ })
1290
+
1291
+ const bodyAfter = await listAfter.json()
1292
+ const userIdsAfter = bodyAfter.data.map((u: any) => u.id)
1293
+ expect(userIdsAfter).toContain(deletedUser.id)
1294
+ })
1295
+ })
1296
+ })
1297
+ })