@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,1402 @@
1
+ /**
2
+ * Accounts CRUD Integration Tests
3
+ *
4
+ * Tests the account CRUD operations:
5
+ * - GET /api/accounts - List accounts
6
+ * - GET /api/accounts/:id - Get account by ID
7
+ * - POST /api/accounts - Create account (super-admin only)
8
+ * - PATCH /api/accounts/:id - Update account
9
+ * - DELETE /api/accounts/:id - Soft delete account (super-admin only)
10
+ * - POST /api/accounts/:id/restore - Restore soft-deleted account (super-admin only)
11
+ */
12
+
13
+ import { describe, it, expect, beforeAll } from 'vitest'
14
+ import { Hono } from 'hono'
15
+ import { getEnv, getDb, getSqlite, type TestEnv } from '../setup'
16
+ import {
17
+ createUser,
18
+ createSuperAdmin,
19
+ createUserSession,
20
+ createTestScenario,
21
+ createMultiUserScenario,
22
+ createMultiTenantScenario,
23
+ createAccount,
24
+ createDeletedAccount,
25
+ addUserToAccount,
26
+ } from '../fixtures'
27
+ import type { HonoEnv } from '../../../src/server/types'
28
+ import { api } from '../../../src/server/routes'
29
+ import { errorHandler } from '../../../src/server/middleware/error-handler'
30
+ import { sessionMiddleware } from '../../../src/server/lib/session'
31
+
32
+ /**
33
+ * Creates a database wrapper that adds the `execute` method
34
+ * The better-sqlite3 drizzle doesn't have execute, but D1 does
35
+ */
36
+ function createTestDb() {
37
+ const db = getDb()
38
+ return new Proxy(db, {
39
+ get(target, prop) {
40
+ if (prop === 'execute') {
41
+ return target.run.bind(target)
42
+ }
43
+ return (target as any)[prop]
44
+ },
45
+ })
46
+ }
47
+
48
+ describe('Accounts CRUD Integration', () => {
49
+ let app: Hono<HonoEnv>
50
+ let env: TestEnv
51
+
52
+ beforeAll(() => {
53
+ env = getEnv()
54
+ app = new Hono<HonoEnv>()
55
+
56
+ // Set up error handler
57
+ app.onError(errorHandler)
58
+
59
+ // Set up middleware to inject test environment
60
+ app.use('*', async (c, next) => {
61
+ // Inject environment bindings
62
+ ;(c as any).env = env
63
+
64
+ // Set up database
65
+ const db = createTestDb()
66
+ c.set('db', db)
67
+
68
+ // Set up request context variables
69
+ c.set('transactionId', crypto.randomUUID())
70
+ c.set('ip', '127.0.0.1')
71
+ c.set('userAgent', 'IntegrationTest/1.0')
72
+
73
+ await next()
74
+ })
75
+
76
+ // Session middleware - reads session from KV and sets sessionData in context
77
+ app.use('*', sessionMiddleware())
78
+
79
+ // Mount API routes (includes sessionAuth and accountMiddleware)
80
+ app.route('/api', api)
81
+ })
82
+
83
+ // ============================================================================
84
+ // GET /api/accounts
85
+ // ============================================================================
86
+
87
+ describe('GET /api/accounts', () => {
88
+ describe('Authentication (401)', () => {
89
+ it('should return 401 without session cookie', async () => {
90
+ const res = await app.request('/api/accounts', {
91
+ method: 'GET',
92
+ })
93
+
94
+ expect(res.status).toBe(401)
95
+
96
+ const body = await res.json()
97
+ expect(body).toHaveProperty('error')
98
+ expect(body.error.message).toBe('Not authenticated')
99
+ })
100
+
101
+ it('should return 401 with invalid session ID', async () => {
102
+ const res = await app.request('/api/accounts', {
103
+ method: 'GET',
104
+ headers: {
105
+ Cookie: 'sid=invalid-session-id-that-does-not-exist',
106
+ 'account-id': crypto.randomUUID(),
107
+ },
108
+ })
109
+
110
+ expect(res.status).toBe(401)
111
+
112
+ const body = await res.json()
113
+ expect(body.error.message).toBe('Not authenticated')
114
+ })
115
+ })
116
+
117
+ describe('Successful List (200)', () => {
118
+ it('should return 200 with accounts array', async () => {
119
+ const scenario = await createTestScenario({
120
+ userName: 'List Accounts User',
121
+ userEmail: 'listaccounts@example.com',
122
+ role: 'VIEWER',
123
+ })
124
+
125
+ const res = await app.request('/api/accounts', {
126
+ method: 'GET',
127
+ headers: {
128
+ ...scenario.headers,
129
+ 'User-Agent': 'IntegrationTest/1.0',
130
+ 'account-id': scenario.account.id,
131
+ },
132
+ })
133
+
134
+ expect(res.status).toBe(200)
135
+
136
+ const body = await res.json()
137
+ expect(body).toHaveProperty('data')
138
+ expect(Array.isArray(body.data)).toBe(true)
139
+ expect(body).toHaveProperty('meta')
140
+ })
141
+
142
+ it('should return only accounts the user has access to', async () => {
143
+ const scenario = await createMultiTenantScenario()
144
+
145
+ const res = await app.request('/api/accounts', {
146
+ method: 'GET',
147
+ headers: {
148
+ ...scenario.headers,
149
+ 'User-Agent': 'IntegrationTest/1.0',
150
+ 'account-id': scenario.accounts.withAccess[0].account.id,
151
+ },
152
+ })
153
+
154
+ expect(res.status).toBe(200)
155
+
156
+ const body = await res.json()
157
+ const accountIds = body.data.map((a: any) => a.id)
158
+
159
+ // User should see all accounts they have access to
160
+ for (const { account } of scenario.accounts.withAccess) {
161
+ expect(accountIds).toContain(account.id)
162
+ }
163
+
164
+ // User should NOT see accounts without access
165
+ for (const account of scenario.accounts.withoutAccess) {
166
+ expect(accountIds).not.toContain(account.id)
167
+ }
168
+ })
169
+
170
+ it('should allow super admin to see all accounts', async () => {
171
+ // Create some accounts
172
+ const account1 = await createAccount({ name: 'Super Admin Test 1' })
173
+ const account2 = await createAccount({ name: 'Super Admin Test 2' })
174
+ const account3 = await createAccount({ name: 'Super Admin Test 3' })
175
+
176
+ // Create a super admin (not member of any of these accounts)
177
+ const superAdmin = await createSuperAdmin({
178
+ email: 'superadminlist@example.com',
179
+ name: 'Super Admin Lister',
180
+ })
181
+
182
+ // Super admin still needs an account context for the middleware
183
+ // Create a separate account for the super admin
184
+ const adminAccount = await createAccount({ name: 'Admin Context Account' })
185
+ await addUserToAccount(superAdmin.id, adminAccount.id, 'ADMIN')
186
+
187
+ const { headers } = await createUserSession(superAdmin.id, {
188
+ email: superAdmin.email,
189
+ name: superAdmin.name,
190
+ isSuperAdmin: true,
191
+ })
192
+
193
+ const res = await app.request('/api/accounts', {
194
+ method: 'GET',
195
+ headers: {
196
+ ...headers,
197
+ 'User-Agent': 'IntegrationTest/1.0',
198
+ 'account-id': adminAccount.id,
199
+ },
200
+ })
201
+
202
+ expect(res.status).toBe(200)
203
+
204
+ const body = await res.json()
205
+ const accountIds = body.data.map((a: any) => a.id)
206
+
207
+ // Super admin should see all accounts
208
+ expect(accountIds).toContain(account1.id)
209
+ expect(accountIds).toContain(account2.id)
210
+ expect(accountIds).toContain(account3.id)
211
+ })
212
+
213
+ it('should include pagination meta', async () => {
214
+ const scenario = await createTestScenario({
215
+ userName: 'Pagination Test User',
216
+ userEmail: 'paginationtest@example.com',
217
+ role: 'VIEWER',
218
+ })
219
+
220
+ const res = await app.request('/api/accounts', {
221
+ method: 'GET',
222
+ headers: {
223
+ ...scenario.headers,
224
+ 'User-Agent': 'IntegrationTest/1.0',
225
+ 'account-id': scenario.account.id,
226
+ },
227
+ })
228
+
229
+ expect(res.status).toBe(200)
230
+
231
+ const body = await res.json()
232
+ expect(body.meta).toHaveProperty('totalItems')
233
+ expect(body.meta).toHaveProperty('currentPage')
234
+ expect(body.meta).toHaveProperty('limit')
235
+ expect(body.meta).toHaveProperty('totalPages')
236
+ })
237
+ })
238
+ })
239
+
240
+ // ============================================================================
241
+ // GET /api/accounts/:id
242
+ // ============================================================================
243
+
244
+ describe('GET /api/accounts/:id', () => {
245
+ describe('Authentication (401)', () => {
246
+ it('should return 401 without session cookie', async () => {
247
+ const accountId = crypto.randomUUID()
248
+ const res = await app.request(`/api/accounts/${accountId}`, {
249
+ method: 'GET',
250
+ })
251
+
252
+ expect(res.status).toBe(401)
253
+
254
+ const body = await res.json()
255
+ expect(body).toHaveProperty('error')
256
+ expect(body.error.message).toBe('Not authenticated')
257
+ })
258
+ })
259
+
260
+ describe('Not Found (404)', () => {
261
+ it('should return 404 for non-existent account', async () => {
262
+ const scenario = await createTestScenario({
263
+ userName: 'Get Account User',
264
+ userEmail: 'getaccount@example.com',
265
+ role: 'VIEWER',
266
+ })
267
+
268
+ const nonExistentId = crypto.randomUUID()
269
+ const res = await app.request(`/api/accounts/${nonExistentId}`, {
270
+ method: 'GET',
271
+ headers: {
272
+ ...scenario.headers,
273
+ 'User-Agent': 'IntegrationTest/1.0',
274
+ 'account-id': scenario.account.id,
275
+ },
276
+ })
277
+
278
+ expect(res.status).toBe(404)
279
+
280
+ const body = await res.json()
281
+ expect(body.error.message).toContain('Account')
282
+ })
283
+
284
+ it('should return 404 if user has no access to account (security - no reveal)', async () => {
285
+ const scenario = await createMultiTenantScenario()
286
+
287
+ // Try to access an account the user doesn't have access to
288
+ const accountWithoutAccess = scenario.accounts.withoutAccess[0]
289
+
290
+ const res = await app.request(`/api/accounts/${accountWithoutAccess.id}`, {
291
+ method: 'GET',
292
+ headers: {
293
+ ...scenario.headers,
294
+ 'User-Agent': 'IntegrationTest/1.0',
295
+ 'account-id': scenario.accounts.withAccess[0].account.id,
296
+ },
297
+ })
298
+
299
+ // Returns 404 (not 403) for security - don't reveal account exists
300
+ expect(res.status).toBe(404)
301
+ })
302
+ })
303
+
304
+ describe('Successful Get (200)', () => {
305
+ it('should return 200 with account details', async () => {
306
+ const scenario = await createTestScenario({
307
+ userName: 'Get Account Detail User',
308
+ userEmail: 'getaccountdetail@example.com',
309
+ accountName: 'Test Account Details',
310
+ role: 'VIEWER',
311
+ })
312
+
313
+ const res = await app.request(`/api/accounts/${scenario.account.id}`, {
314
+ method: 'GET',
315
+ headers: {
316
+ ...scenario.headers,
317
+ 'User-Agent': 'IntegrationTest/1.0',
318
+ 'account-id': scenario.account.id,
319
+ },
320
+ })
321
+
322
+ expect(res.status).toBe(200)
323
+
324
+ const body = await res.json()
325
+ expect(body).toHaveProperty('data')
326
+ expect(body.data.id).toBe(scenario.account.id)
327
+ expect(body.data.name).toBe(scenario.account.name)
328
+ })
329
+
330
+ it('should include all account fields in response', async () => {
331
+ const account = await createAccount({
332
+ name: 'Full Fields Account',
333
+ description: 'Test description',
334
+ domain: 'fullfields.example.com',
335
+ })
336
+
337
+ const user = await createUser({
338
+ email: 'fullfields@example.com',
339
+ name: 'Full Fields User',
340
+ })
341
+ await addUserToAccount(user.id, account.id, 'VIEWER')
342
+
343
+ const { headers } = await createUserSession(user.id, {
344
+ email: user.email,
345
+ name: user.name,
346
+ })
347
+
348
+ const res = await app.request(`/api/accounts/${account.id}`, {
349
+ method: 'GET',
350
+ headers: {
351
+ ...headers,
352
+ 'User-Agent': 'IntegrationTest/1.0',
353
+ 'account-id': account.id,
354
+ },
355
+ })
356
+
357
+ expect(res.status).toBe(200)
358
+
359
+ const body = await res.json()
360
+ expect(body.data).toHaveProperty('id')
361
+ expect(body.data).toHaveProperty('name')
362
+ expect(body.data).toHaveProperty('description')
363
+ expect(body.data).toHaveProperty('domain')
364
+ expect(body.data).toHaveProperty('createdAt')
365
+ expect(body.data).toHaveProperty('updatedAt')
366
+ })
367
+
368
+ it('should allow super admin to get any account', async () => {
369
+ // Create an account without super admin membership
370
+ const account = await createAccount({ name: 'Super Admin Get Test' })
371
+
372
+ // Create a super admin with their own account context
373
+ const superAdmin = await createSuperAdmin({
374
+ email: 'superadminget@example.com',
375
+ name: 'Super Admin Getter',
376
+ })
377
+ const adminAccount = await createAccount({ name: 'Admin Get Context' })
378
+ await addUserToAccount(superAdmin.id, adminAccount.id, 'ADMIN')
379
+
380
+ const { headers } = await createUserSession(superAdmin.id, {
381
+ email: superAdmin.email,
382
+ name: superAdmin.name,
383
+ isSuperAdmin: true,
384
+ })
385
+
386
+ const res = await app.request(`/api/accounts/${account.id}`, {
387
+ method: 'GET',
388
+ headers: {
389
+ ...headers,
390
+ 'User-Agent': 'IntegrationTest/1.0',
391
+ 'account-id': adminAccount.id,
392
+ },
393
+ })
394
+
395
+ expect(res.status).toBe(200)
396
+
397
+ const body = await res.json()
398
+ expect(body.data.id).toBe(account.id)
399
+ })
400
+ })
401
+ })
402
+
403
+ // ============================================================================
404
+ // POST /api/accounts
405
+ // ============================================================================
406
+
407
+ describe('POST /api/accounts', () => {
408
+ describe('Authentication (401)', () => {
409
+ it('should return 401 without session cookie', async () => {
410
+ const res = await app.request('/api/accounts', {
411
+ method: 'POST',
412
+ headers: {
413
+ 'Content-Type': 'application/json',
414
+ },
415
+ body: JSON.stringify({ name: 'New Account' }),
416
+ })
417
+
418
+ expect(res.status).toBe(401)
419
+
420
+ const body = await res.json()
421
+ expect(body.error.message).toBe('Not authenticated')
422
+ })
423
+ })
424
+
425
+ describe('Authorization (403)', () => {
426
+ it('should return 403 if user lacks ADMIN role', async () => {
427
+ const scenario = await createTestScenario({
428
+ userName: 'Viewer Create Account',
429
+ userEmail: 'viewercreate@example.com',
430
+ role: 'VIEWER',
431
+ })
432
+
433
+ const res = await app.request('/api/accounts', {
434
+ method: 'POST',
435
+ headers: {
436
+ ...scenario.headers,
437
+ 'User-Agent': 'IntegrationTest/1.0',
438
+ 'account-id': scenario.account.id,
439
+ 'Content-Type': 'application/json',
440
+ },
441
+ body: JSON.stringify({ name: 'New Account' }),
442
+ })
443
+
444
+ expect(res.status).toBe(403)
445
+ })
446
+
447
+ it('should return 403 if user has ADMIN role but is not super-admin', async () => {
448
+ const scenario = await createTestScenario({
449
+ userName: 'Admin Not Super',
450
+ userEmail: 'adminnotsuper@example.com',
451
+ role: 'ADMIN',
452
+ isSuperAdmin: false,
453
+ })
454
+
455
+ const res = await app.request('/api/accounts', {
456
+ method: 'POST',
457
+ headers: {
458
+ ...scenario.headers,
459
+ 'User-Agent': 'IntegrationTest/1.0',
460
+ 'account-id': scenario.account.id,
461
+ 'Content-Type': 'application/json',
462
+ },
463
+ body: JSON.stringify({ name: 'New Account' }),
464
+ })
465
+
466
+ expect(res.status).toBe(403)
467
+
468
+ const body = await res.json()
469
+ expect(body.error.message).toContain('super-admin')
470
+ })
471
+ })
472
+
473
+ describe('Validation (400)', () => {
474
+ it('should return 400 for missing name', async () => {
475
+ const superAdmin = await createSuperAdmin({
476
+ email: 'supervalidation@example.com',
477
+ name: 'Super Validation',
478
+ })
479
+ const account = await createAccount({ name: 'Super Context' })
480
+ await addUserToAccount(superAdmin.id, account.id, 'ADMIN')
481
+
482
+ const { headers } = await createUserSession(superAdmin.id, {
483
+ email: superAdmin.email,
484
+ name: superAdmin.name,
485
+ isSuperAdmin: true,
486
+ })
487
+
488
+ const res = await app.request('/api/accounts', {
489
+ method: 'POST',
490
+ headers: {
491
+ ...headers,
492
+ 'User-Agent': 'IntegrationTest/1.0',
493
+ 'account-id': account.id,
494
+ 'Content-Type': 'application/json',
495
+ },
496
+ body: JSON.stringify({}),
497
+ })
498
+
499
+ expect(res.status).toBe(400)
500
+ })
501
+
502
+ it('should return 400 for empty name', async () => {
503
+ const superAdmin = await createSuperAdmin({
504
+ email: 'superemptyname@example.com',
505
+ name: 'Super Empty Name',
506
+ })
507
+ const account = await createAccount({ name: 'Super Context Empty' })
508
+ await addUserToAccount(superAdmin.id, account.id, 'ADMIN')
509
+
510
+ const { headers } = await createUserSession(superAdmin.id, {
511
+ email: superAdmin.email,
512
+ name: superAdmin.name,
513
+ isSuperAdmin: true,
514
+ })
515
+
516
+ const res = await app.request('/api/accounts', {
517
+ method: 'POST',
518
+ headers: {
519
+ ...headers,
520
+ 'User-Agent': 'IntegrationTest/1.0',
521
+ 'account-id': account.id,
522
+ 'Content-Type': 'application/json',
523
+ },
524
+ body: JSON.stringify({ name: '' }),
525
+ })
526
+
527
+ expect(res.status).toBe(400)
528
+ })
529
+ })
530
+
531
+ describe('Conflict (409)', () => {
532
+ it('should return 409 if domain already exists', async () => {
533
+ // Create account with a specific domain
534
+ await createAccount({
535
+ name: 'Existing Domain Account',
536
+ domain: 'existing-domain.example.com',
537
+ })
538
+
539
+ const superAdmin = await createSuperAdmin({
540
+ email: 'superdomain@example.com',
541
+ name: 'Super Domain',
542
+ })
543
+ const adminAccount = await createAccount({ name: 'Super Context Domain' })
544
+ await addUserToAccount(superAdmin.id, adminAccount.id, 'ADMIN')
545
+
546
+ const { headers } = await createUserSession(superAdmin.id, {
547
+ email: superAdmin.email,
548
+ name: superAdmin.name,
549
+ isSuperAdmin: true,
550
+ })
551
+
552
+ const res = await app.request('/api/accounts', {
553
+ method: 'POST',
554
+ headers: {
555
+ ...headers,
556
+ 'User-Agent': 'IntegrationTest/1.0',
557
+ 'account-id': adminAccount.id,
558
+ 'Content-Type': 'application/json',
559
+ },
560
+ body: JSON.stringify({
561
+ name: 'New Account With Duplicate Domain',
562
+ domain: 'existing-domain.example.com',
563
+ }),
564
+ })
565
+
566
+ expect(res.status).toBe(409)
567
+
568
+ const body = await res.json()
569
+ expect(body.error.message).toContain('domain')
570
+ })
571
+ })
572
+
573
+ describe('Successful Create (201)', () => {
574
+ it('should return 201 on successful creation', async () => {
575
+ const superAdmin = await createSuperAdmin({
576
+ email: 'supercreate@example.com',
577
+ name: 'Super Creator',
578
+ })
579
+ const adminAccount = await createAccount({ name: 'Super Context Create' })
580
+ await addUserToAccount(superAdmin.id, adminAccount.id, 'ADMIN')
581
+
582
+ const { headers } = await createUserSession(superAdmin.id, {
583
+ email: superAdmin.email,
584
+ name: superAdmin.name,
585
+ isSuperAdmin: true,
586
+ })
587
+
588
+ const res = await app.request('/api/accounts', {
589
+ method: 'POST',
590
+ headers: {
591
+ ...headers,
592
+ 'User-Agent': 'IntegrationTest/1.0',
593
+ 'account-id': adminAccount.id,
594
+ 'Content-Type': 'application/json',
595
+ },
596
+ body: JSON.stringify({
597
+ name: 'Newly Created Account',
598
+ description: 'A test account',
599
+ domain: 'newaccount.example.com',
600
+ }),
601
+ })
602
+
603
+ expect(res.status).toBe(201)
604
+
605
+ const body = await res.json()
606
+ expect(body).toHaveProperty('data')
607
+ expect(body.data.name).toBe('Newly Created Account')
608
+ expect(body.data.description).toBe('A test account')
609
+ expect(body.data.domain).toBe('newaccount.example.com')
610
+ })
611
+
612
+ it('should verify account is created in database', async () => {
613
+ const superAdmin = await createSuperAdmin({
614
+ email: 'superverify@example.com',
615
+ name: 'Super Verifier',
616
+ })
617
+ const adminAccount = await createAccount({ name: 'Super Context Verify' })
618
+ await addUserToAccount(superAdmin.id, adminAccount.id, 'ADMIN')
619
+
620
+ const { headers } = await createUserSession(superAdmin.id, {
621
+ email: superAdmin.email,
622
+ name: superAdmin.name,
623
+ isSuperAdmin: true,
624
+ })
625
+
626
+ const res = await app.request('/api/accounts', {
627
+ method: 'POST',
628
+ headers: {
629
+ ...headers,
630
+ 'User-Agent': 'IntegrationTest/1.0',
631
+ 'account-id': adminAccount.id,
632
+ 'Content-Type': 'application/json',
633
+ },
634
+ body: JSON.stringify({
635
+ name: 'Database Verify Account',
636
+ }),
637
+ })
638
+
639
+ expect(res.status).toBe(201)
640
+
641
+ const body = await res.json()
642
+ const createdId = body.data.id
643
+
644
+ // Verify in database
645
+ const sqlite = getSqlite()
646
+ const row = sqlite.prepare('SELECT name FROM accounts WHERE id = ?').get(createdId) as { name: string }
647
+ expect(row.name).toBe('Database Verify Account')
648
+ })
649
+
650
+ it('should allow creation with only required fields', async () => {
651
+ const superAdmin = await createSuperAdmin({
652
+ email: 'superminimal@example.com',
653
+ name: 'Super Minimal',
654
+ })
655
+ const adminAccount = await createAccount({ name: 'Super Context Minimal' })
656
+ await addUserToAccount(superAdmin.id, adminAccount.id, 'ADMIN')
657
+
658
+ const { headers } = await createUserSession(superAdmin.id, {
659
+ email: superAdmin.email,
660
+ name: superAdmin.name,
661
+ isSuperAdmin: true,
662
+ })
663
+
664
+ const res = await app.request('/api/accounts', {
665
+ method: 'POST',
666
+ headers: {
667
+ ...headers,
668
+ 'User-Agent': 'IntegrationTest/1.0',
669
+ 'account-id': adminAccount.id,
670
+ 'Content-Type': 'application/json',
671
+ },
672
+ body: JSON.stringify({
673
+ name: 'Minimal Account',
674
+ }),
675
+ })
676
+
677
+ expect(res.status).toBe(201)
678
+
679
+ const body = await res.json()
680
+ expect(body.data.name).toBe('Minimal Account')
681
+ expect(body.data.description).toBeNull()
682
+ expect(body.data.domain).toBeNull()
683
+ })
684
+ })
685
+ })
686
+
687
+ // ============================================================================
688
+ // PATCH /api/accounts/:id
689
+ // ============================================================================
690
+
691
+ describe('PATCH /api/accounts/:id', () => {
692
+ describe('Authentication (401)', () => {
693
+ it('should return 401 without session cookie', async () => {
694
+ const accountId = crypto.randomUUID()
695
+ const res = await app.request(`/api/accounts/${accountId}`, {
696
+ method: 'PATCH',
697
+ headers: {
698
+ 'Content-Type': 'application/json',
699
+ },
700
+ body: JSON.stringify({ name: 'Updated Name' }),
701
+ })
702
+
703
+ expect(res.status).toBe(401)
704
+
705
+ const body = await res.json()
706
+ expect(body.error.message).toBe('Not authenticated')
707
+ })
708
+ })
709
+
710
+ describe('Not Found (404)', () => {
711
+ it('should return 404 for non-existent account', async () => {
712
+ const scenario = await createTestScenario({
713
+ userName: 'Update Not Found User',
714
+ userEmail: 'updatenotfound@example.com',
715
+ role: 'MANAGER',
716
+ })
717
+
718
+ const nonExistentId = crypto.randomUUID()
719
+ const res = await app.request(`/api/accounts/${nonExistentId}`, {
720
+ method: 'PATCH',
721
+ headers: {
722
+ ...scenario.headers,
723
+ 'User-Agent': 'IntegrationTest/1.0',
724
+ 'account-id': scenario.account.id,
725
+ 'Content-Type': 'application/json',
726
+ },
727
+ body: JSON.stringify({ name: 'Updated Name' }),
728
+ })
729
+
730
+ expect(res.status).toBe(404)
731
+ })
732
+ })
733
+
734
+ describe('Authorization (403/404)', () => {
735
+ it('should return 404 if user has no access to account', async () => {
736
+ const scenario = await createMultiTenantScenario()
737
+ const accountWithoutAccess = scenario.accounts.withoutAccess[0]
738
+
739
+ // Need to use an account with MANAGER role for the account-id header
740
+ const accountWithManagerAccess = scenario.accounts.withAccess.find(a => a.role === 'MANAGER')!
741
+
742
+ const res = await app.request(`/api/accounts/${accountWithoutAccess.id}`, {
743
+ method: 'PATCH',
744
+ headers: {
745
+ ...scenario.headers,
746
+ 'User-Agent': 'IntegrationTest/1.0',
747
+ 'account-id': accountWithManagerAccess.account.id,
748
+ 'Content-Type': 'application/json',
749
+ },
750
+ body: JSON.stringify({ name: 'Should Not Update' }),
751
+ })
752
+
753
+ // Returns 404 for security - don't reveal account exists
754
+ expect(res.status).toBe(404)
755
+ })
756
+ })
757
+
758
+ describe('Validation (400)', () => {
759
+ it('should return 400 for empty name', async () => {
760
+ const scenario = await createTestScenario({
761
+ userName: 'Update Validation User',
762
+ userEmail: 'updatevalidation@example.com',
763
+ role: 'MANAGER',
764
+ })
765
+
766
+ const res = await app.request(`/api/accounts/${scenario.account.id}`, {
767
+ method: 'PATCH',
768
+ headers: {
769
+ ...scenario.headers,
770
+ 'User-Agent': 'IntegrationTest/1.0',
771
+ 'account-id': scenario.account.id,
772
+ 'Content-Type': 'application/json',
773
+ },
774
+ body: JSON.stringify({ name: '' }),
775
+ })
776
+
777
+ expect(res.status).toBe(400)
778
+ })
779
+ })
780
+
781
+ describe('Conflict (409)', () => {
782
+ it('should return 409 if updating to existing domain', async () => {
783
+ // Create account with a domain
784
+ await createAccount({
785
+ name: 'Domain Conflict Target',
786
+ domain: 'conflict-target.example.com',
787
+ })
788
+
789
+ const scenario = await createTestScenario({
790
+ userName: 'Update Domain Conflict',
791
+ userEmail: 'updatedomainconflict@example.com',
792
+ role: 'MANAGER',
793
+ })
794
+
795
+ const res = await app.request(`/api/accounts/${scenario.account.id}`, {
796
+ method: 'PATCH',
797
+ headers: {
798
+ ...scenario.headers,
799
+ 'User-Agent': 'IntegrationTest/1.0',
800
+ 'account-id': scenario.account.id,
801
+ 'Content-Type': 'application/json',
802
+ },
803
+ body: JSON.stringify({ domain: 'conflict-target.example.com' }),
804
+ })
805
+
806
+ expect(res.status).toBe(409)
807
+
808
+ const body = await res.json()
809
+ expect(body.error.message).toContain('domain')
810
+ })
811
+ })
812
+
813
+ describe('Successful Update (200)', () => {
814
+ it('should return 200 on successful name update', async () => {
815
+ const scenario = await createTestScenario({
816
+ userName: 'Update Name User',
817
+ userEmail: 'updatename@example.com',
818
+ accountName: 'Original Account Name',
819
+ role: 'MANAGER',
820
+ })
821
+
822
+ const res = await app.request(`/api/accounts/${scenario.account.id}`, {
823
+ method: 'PATCH',
824
+ headers: {
825
+ ...scenario.headers,
826
+ 'User-Agent': 'IntegrationTest/1.0',
827
+ 'account-id': scenario.account.id,
828
+ 'Content-Type': 'application/json',
829
+ },
830
+ body: JSON.stringify({ name: 'Updated Account Name' }),
831
+ })
832
+
833
+ expect(res.status).toBe(200)
834
+
835
+ const body = await res.json()
836
+ expect(body.data.name).toBe('Updated Account Name')
837
+ })
838
+
839
+ it('should return 200 on successful description update', async () => {
840
+ const scenario = await createTestScenario({
841
+ userName: 'Update Description User',
842
+ userEmail: 'updatedesc@example.com',
843
+ role: 'MANAGER',
844
+ })
845
+
846
+ const res = await app.request(`/api/accounts/${scenario.account.id}`, {
847
+ method: 'PATCH',
848
+ headers: {
849
+ ...scenario.headers,
850
+ 'User-Agent': 'IntegrationTest/1.0',
851
+ 'account-id': scenario.account.id,
852
+ 'Content-Type': 'application/json',
853
+ },
854
+ body: JSON.stringify({ description: 'New description' }),
855
+ })
856
+
857
+ expect(res.status).toBe(200)
858
+
859
+ const body = await res.json()
860
+ expect(body.data.description).toBe('New description')
861
+ })
862
+
863
+ it('should update multiple fields at once', async () => {
864
+ const scenario = await createTestScenario({
865
+ userName: 'Update Multiple User',
866
+ userEmail: 'updatemultiple@example.com',
867
+ role: 'ADMIN',
868
+ })
869
+
870
+ const res = await app.request(`/api/accounts/${scenario.account.id}`, {
871
+ method: 'PATCH',
872
+ headers: {
873
+ ...scenario.headers,
874
+ 'User-Agent': 'IntegrationTest/1.0',
875
+ 'account-id': scenario.account.id,
876
+ 'Content-Type': 'application/json',
877
+ },
878
+ body: JSON.stringify({
879
+ name: 'Multi Update Name',
880
+ description: 'Multi Update Description',
881
+ domain: 'multiupdate.example.com',
882
+ }),
883
+ })
884
+
885
+ expect(res.status).toBe(200)
886
+
887
+ const body = await res.json()
888
+ expect(body.data.name).toBe('Multi Update Name')
889
+ expect(body.data.description).toBe('Multi Update Description')
890
+ expect(body.data.domain).toBe('multiupdate.example.com')
891
+ })
892
+
893
+ it('should verify database is updated after PATCH', async () => {
894
+ const scenario = await createTestScenario({
895
+ userName: 'Verify Update User',
896
+ userEmail: 'verifyupdate@example.com',
897
+ role: 'MANAGER',
898
+ })
899
+
900
+ await app.request(`/api/accounts/${scenario.account.id}`, {
901
+ method: 'PATCH',
902
+ headers: {
903
+ ...scenario.headers,
904
+ 'User-Agent': 'IntegrationTest/1.0',
905
+ 'account-id': scenario.account.id,
906
+ 'Content-Type': 'application/json',
907
+ },
908
+ body: JSON.stringify({ name: 'Database Updated Name' }),
909
+ })
910
+
911
+ // Verify by fetching the account again
912
+ const getRes = await app.request(`/api/accounts/${scenario.account.id}`, {
913
+ method: 'GET',
914
+ headers: {
915
+ ...scenario.headers,
916
+ 'User-Agent': 'IntegrationTest/1.0',
917
+ 'account-id': scenario.account.id,
918
+ },
919
+ })
920
+
921
+ expect(getRes.status).toBe(200)
922
+
923
+ const body = await getRes.json()
924
+ expect(body.data.name).toBe('Database Updated Name')
925
+ })
926
+
927
+ it('should allow ADMIN role to update account', async () => {
928
+ const scenario = await createMultiUserScenario()
929
+
930
+ const res = await app.request(`/api/accounts/${scenario.account.id}`, {
931
+ method: 'PATCH',
932
+ headers: {
933
+ ...scenario.admin.headers,
934
+ 'User-Agent': 'IntegrationTest/1.0',
935
+ 'account-id': scenario.account.id,
936
+ 'Content-Type': 'application/json',
937
+ },
938
+ body: JSON.stringify({ name: 'Admin Updated Name' }),
939
+ })
940
+
941
+ expect(res.status).toBe(200)
942
+ })
943
+ })
944
+ })
945
+
946
+ // ============================================================================
947
+ // DELETE /api/accounts/:id
948
+ // ============================================================================
949
+
950
+ describe('DELETE /api/accounts/:id', () => {
951
+ describe('Authentication (401)', () => {
952
+ it('should return 401 without session cookie', async () => {
953
+ const accountId = crypto.randomUUID()
954
+ const res = await app.request(`/api/accounts/${accountId}`, {
955
+ method: 'DELETE',
956
+ })
957
+
958
+ expect(res.status).toBe(401)
959
+
960
+ const body = await res.json()
961
+ expect(body.error.message).toBe('Not authenticated')
962
+ })
963
+ })
964
+
965
+ describe('Not Found (404)', () => {
966
+ it('should return 404 for non-existent account', async () => {
967
+ const superAdmin = await createSuperAdmin({
968
+ email: 'superdeletenotfound@example.com',
969
+ name: 'Super Delete Not Found',
970
+ })
971
+ const adminAccount = await createAccount({ name: 'Super Delete Context' })
972
+ await addUserToAccount(superAdmin.id, adminAccount.id, 'ADMIN')
973
+
974
+ const { headers } = await createUserSession(superAdmin.id, {
975
+ email: superAdmin.email,
976
+ name: superAdmin.name,
977
+ isSuperAdmin: true,
978
+ })
979
+
980
+ const nonExistentId = crypto.randomUUID()
981
+ const res = await app.request(`/api/accounts/${nonExistentId}`, {
982
+ method: 'DELETE',
983
+ headers: {
984
+ ...headers,
985
+ 'User-Agent': 'IntegrationTest/1.0',
986
+ 'account-id': adminAccount.id,
987
+ },
988
+ })
989
+
990
+ expect(res.status).toBe(404)
991
+ })
992
+ })
993
+
994
+ describe('Authorization (403)', () => {
995
+ it('should return 403 if user lacks ADMIN role', async () => {
996
+ const scenario = await createTestScenario({
997
+ userName: 'Viewer Delete Account',
998
+ userEmail: 'viewerdelete@example.com',
999
+ role: 'VIEWER',
1000
+ })
1001
+
1002
+ const res = await app.request(`/api/accounts/${scenario.account.id}`, {
1003
+ method: 'DELETE',
1004
+ headers: {
1005
+ ...scenario.headers,
1006
+ 'User-Agent': 'IntegrationTest/1.0',
1007
+ 'account-id': scenario.account.id,
1008
+ },
1009
+ })
1010
+
1011
+ expect(res.status).toBe(403)
1012
+ })
1013
+
1014
+ it('should return 403 if user has ADMIN role but is not super-admin', async () => {
1015
+ const scenario = await createTestScenario({
1016
+ userName: 'Admin Not Super Delete',
1017
+ userEmail: 'adminnotsuperdelete@example.com',
1018
+ role: 'ADMIN',
1019
+ isSuperAdmin: false,
1020
+ })
1021
+
1022
+ // Create another account to delete
1023
+ const accountToDelete = await createAccount({ name: 'To Be Deleted' })
1024
+ await addUserToAccount(scenario.user.id, accountToDelete.id, 'ADMIN')
1025
+
1026
+ const res = await app.request(`/api/accounts/${accountToDelete.id}`, {
1027
+ method: 'DELETE',
1028
+ headers: {
1029
+ ...scenario.headers,
1030
+ 'User-Agent': 'IntegrationTest/1.0',
1031
+ 'account-id': scenario.account.id,
1032
+ },
1033
+ })
1034
+
1035
+ expect(res.status).toBe(403)
1036
+
1037
+ const body = await res.json()
1038
+ expect(body.error.message).toContain('super-admin')
1039
+ })
1040
+ })
1041
+
1042
+ describe('Successful Delete (204)', () => {
1043
+ it('should return 204 on successful delete', async () => {
1044
+ const superAdmin = await createSuperAdmin({
1045
+ email: 'superdelete@example.com',
1046
+ name: 'Super Deleter',
1047
+ })
1048
+ const adminAccount = await createAccount({ name: 'Super Context Delete' })
1049
+ await addUserToAccount(superAdmin.id, adminAccount.id, 'ADMIN')
1050
+
1051
+ // Create account to delete
1052
+ const accountToDelete = await createAccount({ name: 'Account To Delete' })
1053
+
1054
+ const { headers } = await createUserSession(superAdmin.id, {
1055
+ email: superAdmin.email,
1056
+ name: superAdmin.name,
1057
+ isSuperAdmin: true,
1058
+ })
1059
+
1060
+ const res = await app.request(`/api/accounts/${accountToDelete.id}`, {
1061
+ method: 'DELETE',
1062
+ headers: {
1063
+ ...headers,
1064
+ 'User-Agent': 'IntegrationTest/1.0',
1065
+ 'account-id': adminAccount.id,
1066
+ },
1067
+ })
1068
+
1069
+ expect(res.status).toBe(204)
1070
+ })
1071
+
1072
+ it('should verify deleted_at is set (soft delete)', async () => {
1073
+ const superAdmin = await createSuperAdmin({
1074
+ email: 'superverifydelete@example.com',
1075
+ name: 'Super Verify Deleter',
1076
+ })
1077
+ const adminAccount = await createAccount({ name: 'Super Context Verify Delete' })
1078
+ await addUserToAccount(superAdmin.id, adminAccount.id, 'ADMIN')
1079
+
1080
+ // Create account to delete
1081
+ const accountToDelete = await createAccount({ name: 'Soft Delete Verify' })
1082
+
1083
+ const { headers } = await createUserSession(superAdmin.id, {
1084
+ email: superAdmin.email,
1085
+ name: superAdmin.name,
1086
+ isSuperAdmin: true,
1087
+ })
1088
+
1089
+ await app.request(`/api/accounts/${accountToDelete.id}`, {
1090
+ method: 'DELETE',
1091
+ headers: {
1092
+ ...headers,
1093
+ 'User-Agent': 'IntegrationTest/1.0',
1094
+ 'account-id': adminAccount.id,
1095
+ },
1096
+ })
1097
+
1098
+ // Verify in database that deleted_at is set
1099
+ const sqlite = getSqlite()
1100
+ const row = sqlite.prepare('SELECT deleted_at FROM accounts WHERE id = ?').get(accountToDelete.id) as { deleted_at: string | null }
1101
+ expect(row.deleted_at).not.toBeNull()
1102
+ })
1103
+
1104
+ it('should not appear in accounts list after deletion', async () => {
1105
+ const superAdmin = await createSuperAdmin({
1106
+ email: 'superlistdelete@example.com',
1107
+ name: 'Super List Deleter',
1108
+ })
1109
+ const adminAccount = await createAccount({ name: 'Super Context List Delete' })
1110
+ await addUserToAccount(superAdmin.id, adminAccount.id, 'ADMIN')
1111
+
1112
+ // Create account to delete
1113
+ const accountToDelete = await createAccount({ name: 'Will Not Appear In List' })
1114
+
1115
+ const { headers } = await createUserSession(superAdmin.id, {
1116
+ email: superAdmin.email,
1117
+ name: superAdmin.name,
1118
+ isSuperAdmin: true,
1119
+ })
1120
+
1121
+ // Delete the account
1122
+ await app.request(`/api/accounts/${accountToDelete.id}`, {
1123
+ method: 'DELETE',
1124
+ headers: {
1125
+ ...headers,
1126
+ 'User-Agent': 'IntegrationTest/1.0',
1127
+ 'account-id': adminAccount.id,
1128
+ },
1129
+ })
1130
+
1131
+ // List accounts and verify deleted account is not included
1132
+ const listRes = await app.request('/api/accounts', {
1133
+ method: 'GET',
1134
+ headers: {
1135
+ ...headers,
1136
+ 'User-Agent': 'IntegrationTest/1.0',
1137
+ 'account-id': adminAccount.id,
1138
+ },
1139
+ })
1140
+
1141
+ expect(listRes.status).toBe(200)
1142
+
1143
+ const body = await listRes.json()
1144
+ const accountIds = body.data.map((a: any) => a.id)
1145
+ expect(accountIds).not.toContain(accountToDelete.id)
1146
+ })
1147
+ })
1148
+ })
1149
+
1150
+ // ============================================================================
1151
+ // POST /api/accounts/:id/restore
1152
+ // ============================================================================
1153
+
1154
+ describe('POST /api/accounts/:id/restore', () => {
1155
+ describe('Authentication (401)', () => {
1156
+ it('should return 401 without session cookie', async () => {
1157
+ const accountId = crypto.randomUUID()
1158
+ const res = await app.request(`/api/accounts/${accountId}/restore`, {
1159
+ method: 'POST',
1160
+ })
1161
+
1162
+ expect(res.status).toBe(401)
1163
+
1164
+ const body = await res.json()
1165
+ expect(body.error.message).toBe('Not authenticated')
1166
+ })
1167
+ })
1168
+
1169
+ describe('Not Found (404)', () => {
1170
+ it('should return 404 for non-existent account', async () => {
1171
+ const superAdmin = await createSuperAdmin({
1172
+ email: 'superrestorenotfound@example.com',
1173
+ name: 'Super Restore Not Found',
1174
+ })
1175
+ const adminAccount = await createAccount({ name: 'Super Restore Context' })
1176
+ await addUserToAccount(superAdmin.id, adminAccount.id, 'ADMIN')
1177
+
1178
+ const { headers } = await createUserSession(superAdmin.id, {
1179
+ email: superAdmin.email,
1180
+ name: superAdmin.name,
1181
+ isSuperAdmin: true,
1182
+ })
1183
+
1184
+ const nonExistentId = crypto.randomUUID()
1185
+ const res = await app.request(`/api/accounts/${nonExistentId}/restore`, {
1186
+ method: 'POST',
1187
+ headers: {
1188
+ ...headers,
1189
+ 'User-Agent': 'IntegrationTest/1.0',
1190
+ 'account-id': adminAccount.id,
1191
+ },
1192
+ })
1193
+
1194
+ expect(res.status).toBe(404)
1195
+ })
1196
+
1197
+ it('should return 404 for account that is not deleted', async () => {
1198
+ const superAdmin = await createSuperAdmin({
1199
+ email: 'superrestorenotdeleted@example.com',
1200
+ name: 'Super Restore Not Deleted',
1201
+ })
1202
+ const adminAccount = await createAccount({ name: 'Super Restore Not Deleted Context' })
1203
+ await addUserToAccount(superAdmin.id, adminAccount.id, 'ADMIN')
1204
+
1205
+ // Create account that is NOT deleted
1206
+ const activeAccount = await createAccount({ name: 'Active Account' })
1207
+
1208
+ const { headers } = await createUserSession(superAdmin.id, {
1209
+ email: superAdmin.email,
1210
+ name: superAdmin.name,
1211
+ isSuperAdmin: true,
1212
+ })
1213
+
1214
+ const res = await app.request(`/api/accounts/${activeAccount.id}/restore`, {
1215
+ method: 'POST',
1216
+ headers: {
1217
+ ...headers,
1218
+ 'User-Agent': 'IntegrationTest/1.0',
1219
+ 'account-id': adminAccount.id,
1220
+ },
1221
+ })
1222
+
1223
+ expect(res.status).toBe(404)
1224
+
1225
+ const body = await res.json()
1226
+ expect(body.error.message).toContain('not deleted')
1227
+ })
1228
+ })
1229
+
1230
+ describe('Authorization (403)', () => {
1231
+ it('should return 403 if user lacks ADMIN role', async () => {
1232
+ const deletedAccount = await createDeletedAccount({ name: 'Deleted For Viewer' })
1233
+
1234
+ const scenario = await createTestScenario({
1235
+ userName: 'Viewer Restore Account',
1236
+ userEmail: 'viewerrestore@example.com',
1237
+ role: 'VIEWER',
1238
+ })
1239
+
1240
+ const res = await app.request(`/api/accounts/${deletedAccount.id}/restore`, {
1241
+ method: 'POST',
1242
+ headers: {
1243
+ ...scenario.headers,
1244
+ 'User-Agent': 'IntegrationTest/1.0',
1245
+ 'account-id': scenario.account.id,
1246
+ },
1247
+ })
1248
+
1249
+ expect(res.status).toBe(403)
1250
+ })
1251
+
1252
+ it('should return 403 if user has ADMIN role but is not super-admin', async () => {
1253
+ const deletedAccount = await createDeletedAccount({ name: 'Deleted For Admin' })
1254
+
1255
+ const scenario = await createTestScenario({
1256
+ userName: 'Admin Not Super Restore',
1257
+ userEmail: 'adminnotsuperrestore@example.com',
1258
+ role: 'ADMIN',
1259
+ isSuperAdmin: false,
1260
+ })
1261
+
1262
+ const res = await app.request(`/api/accounts/${deletedAccount.id}/restore`, {
1263
+ method: 'POST',
1264
+ headers: {
1265
+ ...scenario.headers,
1266
+ 'User-Agent': 'IntegrationTest/1.0',
1267
+ 'account-id': scenario.account.id,
1268
+ },
1269
+ })
1270
+
1271
+ expect(res.status).toBe(403)
1272
+
1273
+ const body = await res.json()
1274
+ expect(body.error.message).toContain('super-admin')
1275
+ })
1276
+ })
1277
+
1278
+ describe('Successful Restore (200)', () => {
1279
+ it('should return 200 on successful restore', async () => {
1280
+ const superAdmin = await createSuperAdmin({
1281
+ email: 'superrestore@example.com',
1282
+ name: 'Super Restorer',
1283
+ })
1284
+ const adminAccount = await createAccount({ name: 'Super Context Restore' })
1285
+ await addUserToAccount(superAdmin.id, adminAccount.id, 'ADMIN')
1286
+
1287
+ // Create deleted account
1288
+ const deletedAccount = await createDeletedAccount({ name: 'Account To Restore' })
1289
+
1290
+ const { headers } = await createUserSession(superAdmin.id, {
1291
+ email: superAdmin.email,
1292
+ name: superAdmin.name,
1293
+ isSuperAdmin: true,
1294
+ })
1295
+
1296
+ const res = await app.request(`/api/accounts/${deletedAccount.id}/restore`, {
1297
+ method: 'POST',
1298
+ headers: {
1299
+ ...headers,
1300
+ 'User-Agent': 'IntegrationTest/1.0',
1301
+ 'account-id': adminAccount.id,
1302
+ },
1303
+ })
1304
+
1305
+ expect(res.status).toBe(200)
1306
+
1307
+ const body = await res.json()
1308
+ expect(body.data.id).toBe(deletedAccount.id)
1309
+ expect(body.data.deletedAt).toBeNull()
1310
+ })
1311
+
1312
+ it('should verify deleted_at is cleared after restore', async () => {
1313
+ const superAdmin = await createSuperAdmin({
1314
+ email: 'superverifyrestore@example.com',
1315
+ name: 'Super Verify Restorer',
1316
+ })
1317
+ const adminAccount = await createAccount({ name: 'Super Context Verify Restore' })
1318
+ await addUserToAccount(superAdmin.id, adminAccount.id, 'ADMIN')
1319
+
1320
+ // Create deleted account
1321
+ const deletedAccount = await createDeletedAccount({ name: 'Verify Deleted At Cleared' })
1322
+
1323
+ const { headers } = await createUserSession(superAdmin.id, {
1324
+ email: superAdmin.email,
1325
+ name: superAdmin.name,
1326
+ isSuperAdmin: true,
1327
+ })
1328
+
1329
+ // Restore the account
1330
+ await app.request(`/api/accounts/${deletedAccount.id}/restore`, {
1331
+ method: 'POST',
1332
+ headers: {
1333
+ ...headers,
1334
+ 'User-Agent': 'IntegrationTest/1.0',
1335
+ 'account-id': adminAccount.id,
1336
+ },
1337
+ })
1338
+
1339
+ // Verify in database that deleted_at is cleared
1340
+ const sqlite = getSqlite()
1341
+ const row = sqlite.prepare('SELECT deleted_at FROM accounts WHERE id = ?').get(deletedAccount.id) as { deleted_at: string | null }
1342
+ expect(row.deleted_at).toBeNull()
1343
+ })
1344
+
1345
+ it('should appear in accounts list after restoration', async () => {
1346
+ const superAdmin = await createSuperAdmin({
1347
+ email: 'superlistrestore@example.com',
1348
+ name: 'Super List Restorer',
1349
+ })
1350
+ const adminAccount = await createAccount({ name: 'Super Context List Restore' })
1351
+ await addUserToAccount(superAdmin.id, adminAccount.id, 'ADMIN')
1352
+
1353
+ // Create deleted account
1354
+ const deletedAccount = await createDeletedAccount({ name: 'Will Appear After Restore' })
1355
+
1356
+ const { headers } = await createUserSession(superAdmin.id, {
1357
+ email: superAdmin.email,
1358
+ name: superAdmin.name,
1359
+ isSuperAdmin: true,
1360
+ })
1361
+
1362
+ // Verify account is NOT in list before restore
1363
+ const listBefore = await app.request('/api/accounts', {
1364
+ method: 'GET',
1365
+ headers: {
1366
+ ...headers,
1367
+ 'User-Agent': 'IntegrationTest/1.0',
1368
+ 'account-id': adminAccount.id,
1369
+ },
1370
+ })
1371
+
1372
+ const bodyBefore = await listBefore.json()
1373
+ const accountIdsBefore = bodyBefore.data.map((a: any) => a.id)
1374
+ expect(accountIdsBefore).not.toContain(deletedAccount.id)
1375
+
1376
+ // Restore the account
1377
+ await app.request(`/api/accounts/${deletedAccount.id}/restore`, {
1378
+ method: 'POST',
1379
+ headers: {
1380
+ ...headers,
1381
+ 'User-Agent': 'IntegrationTest/1.0',
1382
+ 'account-id': adminAccount.id,
1383
+ },
1384
+ })
1385
+
1386
+ // Verify account IS in list after restore
1387
+ const listAfter = await app.request('/api/accounts', {
1388
+ method: 'GET',
1389
+ headers: {
1390
+ ...headers,
1391
+ 'User-Agent': 'IntegrationTest/1.0',
1392
+ 'account-id': adminAccount.id,
1393
+ },
1394
+ })
1395
+
1396
+ const bodyAfter = await listAfter.json()
1397
+ const accountIdsAfter = bodyAfter.data.map((a: any) => a.id)
1398
+ expect(accountIdsAfter).toContain(deletedAccount.id)
1399
+ })
1400
+ })
1401
+ })
1402
+ })