@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,1457 @@
1
+ /**
2
+ * Invitations CRUD Integration Tests
3
+ *
4
+ * Tests the invitation CRUD operations:
5
+ * - POST /api/invitations - Create invitation
6
+ * - GET /api/invitations - List pending invitations
7
+ * - DELETE /api/invitations/:id - Revoke invitation
8
+ */
9
+
10
+ import { describe, it, expect, beforeAll } from 'vitest'
11
+ import { Hono } from 'hono'
12
+ import { getEnv, getDb, getSqlite, type TestEnv } from '../setup'
13
+ import {
14
+ createUser,
15
+ createUserSession,
16
+ createTestScenario,
17
+ createMultiUserScenario,
18
+ createAccount,
19
+ addUserToAccount,
20
+ createInvitation,
21
+ createExpiredInvitation,
22
+ createAcceptedInvitation,
23
+ getAccountInvitations,
24
+ } from '../fixtures'
25
+ import type { HonoEnv } from '../../../src/server/types'
26
+ import { api } from '../../../src/server/routes'
27
+ import { errorHandler } from '../../../src/server/middleware/error-handler'
28
+ import { sessionMiddleware } from '../../../src/server/lib/session'
29
+
30
+ /**
31
+ * Creates a database wrapper that adds the `execute` method
32
+ * The better-sqlite3 drizzle doesn't have execute, but D1 does
33
+ */
34
+ function createTestDb() {
35
+ const db = getDb()
36
+ return new Proxy(db, {
37
+ get(target, prop) {
38
+ if (prop === 'execute') {
39
+ return target.run.bind(target)
40
+ }
41
+ return (target as any)[prop]
42
+ },
43
+ })
44
+ }
45
+
46
+ describe('Invitations CRUD Integration', () => {
47
+ let app: Hono<HonoEnv>
48
+ let env: TestEnv
49
+
50
+ beforeAll(() => {
51
+ env = getEnv()
52
+ app = new Hono<HonoEnv>()
53
+
54
+ // Set up error handler
55
+ app.onError(errorHandler)
56
+
57
+ // Set up middleware to inject test environment
58
+ app.use('*', async (c, next) => {
59
+ // Inject environment bindings
60
+ ;(c as any).env = env
61
+
62
+ // Set up database
63
+ const db = createTestDb()
64
+ c.set('db', db)
65
+
66
+ // Set up request context variables
67
+ c.set('transactionId', crypto.randomUUID())
68
+ c.set('ip', '127.0.0.1')
69
+ c.set('userAgent', 'IntegrationTest/1.0')
70
+
71
+ await next()
72
+ })
73
+
74
+ // Session middleware - reads session from KV and sets sessionData in context
75
+ app.use('*', sessionMiddleware())
76
+
77
+ // Mount API routes (includes sessionAuth and accountMiddleware)
78
+ app.route('/api', api)
79
+ })
80
+
81
+ // ============================================================================
82
+ // POST /api/invitations
83
+ // ============================================================================
84
+
85
+ describe('POST /api/invitations', () => {
86
+ describe('Authentication (401)', () => {
87
+ it('should return 401 without session cookie', async () => {
88
+ const res = await app.request('/api/invitations', {
89
+ method: 'POST',
90
+ headers: {
91
+ 'Content-Type': 'application/json',
92
+ },
93
+ body: JSON.stringify({ email: 'newuser@example.com', role: 'VIEWER' }),
94
+ })
95
+
96
+ expect(res.status).toBe(401)
97
+
98
+ const body = await res.json()
99
+ expect(body).toHaveProperty('error')
100
+ expect(body.error.message).toBe('Not authenticated')
101
+ })
102
+
103
+ it('should return 401 with invalid session ID', async () => {
104
+ const res = await app.request('/api/invitations', {
105
+ method: 'POST',
106
+ headers: {
107
+ Cookie: 'sid=invalid-session-id-that-does-not-exist',
108
+ 'account-id': crypto.randomUUID(),
109
+ 'Content-Type': 'application/json',
110
+ },
111
+ body: JSON.stringify({ email: 'newuser@example.com', role: 'VIEWER' }),
112
+ })
113
+
114
+ expect(res.status).toBe(401)
115
+
116
+ const body = await res.json()
117
+ expect(body.error.message).toBe('Not authenticated')
118
+ })
119
+ })
120
+
121
+ describe('Authorization (403)', () => {
122
+ it('should return 403 if user lacks permission to invite (VIEWER role)', async () => {
123
+ const scenario = await createTestScenario({
124
+ userName: 'Viewer User',
125
+ userEmail: 'viewer@example.com',
126
+ role: 'VIEWER',
127
+ })
128
+
129
+ const res = await app.request('/api/invitations', {
130
+ method: 'POST',
131
+ headers: {
132
+ ...scenario.headers,
133
+ 'User-Agent': 'IntegrationTest/1.0',
134
+ 'account-id': scenario.account.id,
135
+ 'Content-Type': 'application/json',
136
+ },
137
+ body: JSON.stringify({ email: 'newuser@example.com', role: 'VIEWER' }),
138
+ })
139
+
140
+ expect(res.status).toBe(403)
141
+ })
142
+
143
+ it('should return 403 if user lacks permission to invite (EDITOR role)', async () => {
144
+ const scenario = await createTestScenario({
145
+ userName: 'Editor User',
146
+ userEmail: 'editor@example.com',
147
+ role: 'EDITOR',
148
+ })
149
+
150
+ const res = await app.request('/api/invitations', {
151
+ method: 'POST',
152
+ headers: {
153
+ ...scenario.headers,
154
+ 'User-Agent': 'IntegrationTest/1.0',
155
+ 'account-id': scenario.account.id,
156
+ 'Content-Type': 'application/json',
157
+ },
158
+ body: JSON.stringify({ email: 'newuser@example.com', role: 'VIEWER' }),
159
+ })
160
+
161
+ expect(res.status).toBe(403)
162
+ })
163
+
164
+ it('should return 403 if user tries to assign role higher than own (MANAGER assigns ADMIN)', async () => {
165
+ const scenario = await createTestScenario({
166
+ userName: 'Manager User',
167
+ userEmail: 'manager@example.com',
168
+ role: 'MANAGER',
169
+ })
170
+
171
+ const res = await app.request('/api/invitations', {
172
+ method: 'POST',
173
+ headers: {
174
+ ...scenario.headers,
175
+ 'User-Agent': 'IntegrationTest/1.0',
176
+ 'account-id': scenario.account.id,
177
+ 'Content-Type': 'application/json',
178
+ },
179
+ body: JSON.stringify({ email: 'newuser@example.com', role: 'ADMIN' }),
180
+ })
181
+
182
+ expect(res.status).toBe(403)
183
+
184
+ const body = await res.json()
185
+ expect(body.error.message).toContain('Cannot assign a role higher than your own')
186
+ })
187
+ })
188
+
189
+ describe('Validation (400)', () => {
190
+ it('should return 400 for invalid email', async () => {
191
+ const scenario = await createTestScenario({
192
+ userName: 'Admin User',
193
+ userEmail: 'admin@example.com',
194
+ role: 'ADMIN',
195
+ })
196
+
197
+ const res = await app.request('/api/invitations', {
198
+ method: 'POST',
199
+ headers: {
200
+ ...scenario.headers,
201
+ 'User-Agent': 'IntegrationTest/1.0',
202
+ 'account-id': scenario.account.id,
203
+ 'Content-Type': 'application/json',
204
+ },
205
+ body: JSON.stringify({ email: 'not-an-email', role: 'VIEWER' }),
206
+ })
207
+
208
+ expect(res.status).toBe(400)
209
+ })
210
+
211
+ it('should return 400 for missing email', async () => {
212
+ const scenario = await createTestScenario({
213
+ userName: 'Admin User',
214
+ userEmail: 'admin2@example.com',
215
+ role: 'ADMIN',
216
+ })
217
+
218
+ const res = await app.request('/api/invitations', {
219
+ method: 'POST',
220
+ headers: {
221
+ ...scenario.headers,
222
+ 'User-Agent': 'IntegrationTest/1.0',
223
+ 'account-id': scenario.account.id,
224
+ 'Content-Type': 'application/json',
225
+ },
226
+ body: JSON.stringify({ role: 'VIEWER' }),
227
+ })
228
+
229
+ expect(res.status).toBe(400)
230
+ })
231
+
232
+ it('should return 400 for invalid role', async () => {
233
+ const scenario = await createTestScenario({
234
+ userName: 'Admin User',
235
+ userEmail: 'admin3@example.com',
236
+ role: 'ADMIN',
237
+ })
238
+
239
+ const res = await app.request('/api/invitations', {
240
+ method: 'POST',
241
+ headers: {
242
+ ...scenario.headers,
243
+ 'User-Agent': 'IntegrationTest/1.0',
244
+ 'account-id': scenario.account.id,
245
+ 'Content-Type': 'application/json',
246
+ },
247
+ body: JSON.stringify({ email: 'newuser@example.com', role: 'INVALID_ROLE' }),
248
+ })
249
+
250
+ expect(res.status).toBe(400)
251
+ })
252
+
253
+ it('should return 400 for missing role', async () => {
254
+ const scenario = await createTestScenario({
255
+ userName: 'Admin User',
256
+ userEmail: 'admin4@example.com',
257
+ role: 'ADMIN',
258
+ })
259
+
260
+ const res = await app.request('/api/invitations', {
261
+ method: 'POST',
262
+ headers: {
263
+ ...scenario.headers,
264
+ 'User-Agent': 'IntegrationTest/1.0',
265
+ 'account-id': scenario.account.id,
266
+ 'Content-Type': 'application/json',
267
+ },
268
+ body: JSON.stringify({ email: 'newuser@example.com' }),
269
+ })
270
+
271
+ expect(res.status).toBe(400)
272
+ })
273
+ })
274
+
275
+ describe('Conflict (409)', () => {
276
+ it('should return 409 if invitation already exists for email', async () => {
277
+ const scenario = await createTestScenario({
278
+ userName: 'Admin User',
279
+ userEmail: 'admin5@example.com',
280
+ role: 'ADMIN',
281
+ })
282
+
283
+ // Create an existing invitation
284
+ await createInvitation({
285
+ accountId: scenario.account.id,
286
+ email: 'existing@example.com',
287
+ role: 'VIEWER',
288
+ invitedById: scenario.user.id,
289
+ })
290
+
291
+ // Try to create another invitation for the same email
292
+ const res = await app.request('/api/invitations', {
293
+ method: 'POST',
294
+ headers: {
295
+ ...scenario.headers,
296
+ 'User-Agent': 'IntegrationTest/1.0',
297
+ 'account-id': scenario.account.id,
298
+ 'Content-Type': 'application/json',
299
+ },
300
+ body: JSON.stringify({ email: 'existing@example.com', role: 'EDITOR' }),
301
+ })
302
+
303
+ expect(res.status).toBe(409)
304
+
305
+ const body = await res.json()
306
+ expect(body.error.message).toContain('Pending invitation already exists')
307
+ })
308
+
309
+ it('should return 409 if user is already a member of the account', async () => {
310
+ const scenario = await createTestScenario({
311
+ userName: 'Admin User',
312
+ userEmail: 'admin6@example.com',
313
+ role: 'ADMIN',
314
+ })
315
+
316
+ // Create another user who is already in the account
317
+ const existingUser = await createUser({
318
+ email: 'alreadymember@example.com',
319
+ name: 'Already Member',
320
+ })
321
+ await addUserToAccount(existingUser.id, scenario.account.id, 'VIEWER')
322
+
323
+ // Try to invite the existing member
324
+ const res = await app.request('/api/invitations', {
325
+ method: 'POST',
326
+ headers: {
327
+ ...scenario.headers,
328
+ 'User-Agent': 'IntegrationTest/1.0',
329
+ 'account-id': scenario.account.id,
330
+ 'Content-Type': 'application/json',
331
+ },
332
+ body: JSON.stringify({ email: 'alreadymember@example.com', role: 'EDITOR' }),
333
+ })
334
+
335
+ expect(res.status).toBe(409)
336
+
337
+ const body = await res.json()
338
+ expect(body.error.message).toContain('already a member')
339
+ })
340
+
341
+ it('should not conflict with expired invitations when checking for duplicates', async () => {
342
+ const scenario = await createTestScenario({
343
+ userName: 'Admin User',
344
+ userEmail: 'admin7@example.com',
345
+ role: 'ADMIN',
346
+ })
347
+
348
+ // Create an expired invitation
349
+ const expiredInvitation = await createExpiredInvitation({
350
+ accountId: scenario.account.id,
351
+ email: 'expiredinvite@example.com',
352
+ role: 'VIEWER',
353
+ invitedById: scenario.user.id,
354
+ })
355
+
356
+ // Note: The database has a unique constraint on (account_id, email)
357
+ // So we need to delete the expired invitation first before creating a new one
358
+ // This simulates a cleanup process that removes expired invitations
359
+ const sqlite = getSqlite()
360
+ sqlite.prepare('DELETE FROM invitations WHERE id = ?').run(expiredInvitation.id)
361
+
362
+ // Should be able to create a new invitation after cleanup
363
+ const res = await app.request('/api/invitations', {
364
+ method: 'POST',
365
+ headers: {
366
+ ...scenario.headers,
367
+ 'User-Agent': 'IntegrationTest/1.0',
368
+ 'account-id': scenario.account.id,
369
+ 'Content-Type': 'application/json',
370
+ },
371
+ body: JSON.stringify({ email: 'expiredinvite@example.com', role: 'EDITOR' }),
372
+ })
373
+
374
+ expect(res.status).toBe(200)
375
+ })
376
+
377
+ it('should allow invitation if previous invitation was accepted', async () => {
378
+ const scenario = await createTestScenario({
379
+ userName: 'Admin User',
380
+ userEmail: 'admin8@example.com',
381
+ role: 'ADMIN',
382
+ })
383
+
384
+ // Create a second account
385
+ const otherAccount = await createAccount({ name: 'Other Account' })
386
+ await addUserToAccount(scenario.user.id, otherAccount.id, 'ADMIN')
387
+
388
+ // Create an accepted invitation in other account
389
+ await createAcceptedInvitation({
390
+ accountId: otherAccount.id,
391
+ email: 'acceptedinvite@example.com',
392
+ role: 'VIEWER',
393
+ invitedById: scenario.user.id,
394
+ })
395
+
396
+ // Should be able to create a new invitation in the original account
397
+ const res = await app.request('/api/invitations', {
398
+ method: 'POST',
399
+ headers: {
400
+ ...scenario.headers,
401
+ 'User-Agent': 'IntegrationTest/1.0',
402
+ 'account-id': scenario.account.id,
403
+ 'Content-Type': 'application/json',
404
+ },
405
+ body: JSON.stringify({ email: 'acceptedinvite@example.com', role: 'EDITOR' }),
406
+ })
407
+
408
+ expect(res.status).toBe(200)
409
+ })
410
+ })
411
+
412
+ describe('Successful Creation (200)', () => {
413
+ it('should return 200 on successful invitation creation', async () => {
414
+ const scenario = await createTestScenario({
415
+ userName: 'Admin User',
416
+ userEmail: 'admin9@example.com',
417
+ role: 'ADMIN',
418
+ })
419
+
420
+ const res = await app.request('/api/invitations', {
421
+ method: 'POST',
422
+ headers: {
423
+ ...scenario.headers,
424
+ 'User-Agent': 'IntegrationTest/1.0',
425
+ 'account-id': scenario.account.id,
426
+ 'Content-Type': 'application/json',
427
+ },
428
+ body: JSON.stringify({ email: 'newuser1@example.com', role: 'VIEWER' }),
429
+ })
430
+
431
+ expect(res.status).toBe(200)
432
+
433
+ const body = await res.json()
434
+ expect(body.invited).toBe(true)
435
+ expect(body.linked).toBe(false)
436
+ expect(body.invitation).toBeDefined()
437
+ expect(body.invitation.email).toBe('newuser1@example.com')
438
+ expect(body.invitation.role).toBe('VIEWER')
439
+ expect(body.invitation.expiresAt).toBeDefined()
440
+ })
441
+
442
+ it('should verify invitation is created in database', async () => {
443
+ const scenario = await createTestScenario({
444
+ userName: 'Admin User',
445
+ userEmail: 'admin10@example.com',
446
+ role: 'ADMIN',
447
+ })
448
+
449
+ const testEmail = 'dbverify@example.com'
450
+
451
+ await app.request('/api/invitations', {
452
+ method: 'POST',
453
+ headers: {
454
+ ...scenario.headers,
455
+ 'User-Agent': 'IntegrationTest/1.0',
456
+ 'account-id': scenario.account.id,
457
+ 'Content-Type': 'application/json',
458
+ },
459
+ body: JSON.stringify({ email: testEmail, role: 'EDITOR' }),
460
+ })
461
+
462
+ // Verify in database
463
+ const sqlite = getSqlite()
464
+ const row = sqlite.prepare('SELECT * FROM invitations WHERE email = ?').get(testEmail) as any
465
+ expect(row).toBeDefined()
466
+ expect(row.email).toBe(testEmail)
467
+ expect(row.role).toBe('EDITOR')
468
+ expect(row.account_id).toBe(scenario.account.id)
469
+ expect(row.invited_by_id).toBe(scenario.user.id)
470
+ })
471
+
472
+ it('should link existing user immediately instead of creating invitation', async () => {
473
+ const scenario = await createTestScenario({
474
+ userName: 'Admin User',
475
+ userEmail: 'admin11@example.com',
476
+ role: 'ADMIN',
477
+ })
478
+
479
+ // Create an existing user who is not in the account
480
+ const existingUser = await createUser({
481
+ email: 'existinguser@example.com',
482
+ name: 'Existing User',
483
+ })
484
+
485
+ const res = await app.request('/api/invitations', {
486
+ method: 'POST',
487
+ headers: {
488
+ ...scenario.headers,
489
+ 'User-Agent': 'IntegrationTest/1.0',
490
+ 'account-id': scenario.account.id,
491
+ 'Content-Type': 'application/json',
492
+ },
493
+ body: JSON.stringify({ email: 'existinguser@example.com', role: 'VIEWER' }),
494
+ })
495
+
496
+ expect(res.status).toBe(200)
497
+
498
+ const body = await res.json()
499
+ expect(body.linked).toBe(true)
500
+ expect(body.invited).toBe(false)
501
+ expect(body.user).toBeDefined()
502
+ expect(body.user.id).toBe(existingUser.id)
503
+ expect(body.user.email).toBe(existingUser.email)
504
+
505
+ // Verify in database - user should be in user_accounts
506
+ const sqlite = getSqlite()
507
+ const row = sqlite.prepare('SELECT * FROM user_accounts WHERE user_id = ? AND account_id = ?').get(existingUser.id, scenario.account.id) as any
508
+ expect(row).toBeDefined()
509
+ expect(row.role).toBe('VIEWER')
510
+ })
511
+
512
+ it('should allow MANAGER role to invite with VIEWER role', async () => {
513
+ const scenario = await createTestScenario({
514
+ userName: 'Manager User',
515
+ userEmail: 'manager2@example.com',
516
+ role: 'MANAGER',
517
+ })
518
+
519
+ const res = await app.request('/api/invitations', {
520
+ method: 'POST',
521
+ headers: {
522
+ ...scenario.headers,
523
+ 'User-Agent': 'IntegrationTest/1.0',
524
+ 'account-id': scenario.account.id,
525
+ 'Content-Type': 'application/json',
526
+ },
527
+ body: JSON.stringify({ email: 'newuser2@example.com', role: 'VIEWER' }),
528
+ })
529
+
530
+ expect(res.status).toBe(200)
531
+ })
532
+
533
+ it('should allow MANAGER role to invite with MANAGER role', async () => {
534
+ const scenario = await createTestScenario({
535
+ userName: 'Manager User',
536
+ userEmail: 'manager3@example.com',
537
+ role: 'MANAGER',
538
+ })
539
+
540
+ const res = await app.request('/api/invitations', {
541
+ method: 'POST',
542
+ headers: {
543
+ ...scenario.headers,
544
+ 'User-Agent': 'IntegrationTest/1.0',
545
+ 'account-id': scenario.account.id,
546
+ 'Content-Type': 'application/json',
547
+ },
548
+ body: JSON.stringify({ email: 'newuser3@example.com', role: 'MANAGER' }),
549
+ })
550
+
551
+ expect(res.status).toBe(200)
552
+ })
553
+
554
+ it('should allow ADMIN role to invite with ADMIN role', async () => {
555
+ const scenario = await createTestScenario({
556
+ userName: 'Admin User',
557
+ userEmail: 'admin12@example.com',
558
+ role: 'ADMIN',
559
+ })
560
+
561
+ const res = await app.request('/api/invitations', {
562
+ method: 'POST',
563
+ headers: {
564
+ ...scenario.headers,
565
+ 'User-Agent': 'IntegrationTest/1.0',
566
+ 'account-id': scenario.account.id,
567
+ 'Content-Type': 'application/json',
568
+ },
569
+ body: JSON.stringify({ email: 'newadmin@example.com', role: 'ADMIN' }),
570
+ })
571
+
572
+ expect(res.status).toBe(200)
573
+
574
+ const body = await res.json()
575
+ expect(body.invitation.role).toBe('ADMIN')
576
+ })
577
+ })
578
+ })
579
+
580
+ // ============================================================================
581
+ // GET /api/invitations
582
+ // ============================================================================
583
+
584
+ describe('GET /api/invitations', () => {
585
+ describe('Authentication (401)', () => {
586
+ it('should return 401 without session cookie', async () => {
587
+ const res = await app.request('/api/invitations', {
588
+ method: 'GET',
589
+ })
590
+
591
+ expect(res.status).toBe(401)
592
+
593
+ const body = await res.json()
594
+ expect(body).toHaveProperty('error')
595
+ expect(body.error.message).toBe('Not authenticated')
596
+ })
597
+
598
+ it('should return 401 with invalid session ID', async () => {
599
+ const res = await app.request('/api/invitations', {
600
+ method: 'GET',
601
+ headers: {
602
+ Cookie: 'sid=invalid-session-id-that-does-not-exist',
603
+ 'account-id': crypto.randomUUID(),
604
+ },
605
+ })
606
+
607
+ expect(res.status).toBe(401)
608
+
609
+ const body = await res.json()
610
+ expect(body.error.message).toBe('Not authenticated')
611
+ })
612
+ })
613
+
614
+ describe('Authorization (403)', () => {
615
+ it('should return 403 if user lacks permission to list invitations (VIEWER role)', async () => {
616
+ const scenario = await createTestScenario({
617
+ userName: 'Viewer User',
618
+ userEmail: 'viewerlist@example.com',
619
+ role: 'VIEWER',
620
+ })
621
+
622
+ const res = await app.request('/api/invitations', {
623
+ method: 'GET',
624
+ headers: {
625
+ ...scenario.headers,
626
+ 'User-Agent': 'IntegrationTest/1.0',
627
+ 'account-id': scenario.account.id,
628
+ },
629
+ })
630
+
631
+ expect(res.status).toBe(403)
632
+ })
633
+ })
634
+
635
+ describe('Successful List (200)', () => {
636
+ it('should return 200 with empty array when no invitations', async () => {
637
+ const scenario = await createTestScenario({
638
+ userName: 'Admin User',
639
+ userEmail: 'adminlist1@example.com',
640
+ role: 'ADMIN',
641
+ })
642
+
643
+ const res = await app.request('/api/invitations', {
644
+ method: 'GET',
645
+ headers: {
646
+ ...scenario.headers,
647
+ 'User-Agent': 'IntegrationTest/1.0',
648
+ 'account-id': scenario.account.id,
649
+ },
650
+ })
651
+
652
+ expect(res.status).toBe(200)
653
+
654
+ const body = await res.json()
655
+ expect(body).toHaveProperty('data')
656
+ expect(Array.isArray(body.data)).toBe(true)
657
+ expect(body.data.length).toBe(0)
658
+ })
659
+
660
+ it('should return 200 with invitations array', async () => {
661
+ const scenario = await createTestScenario({
662
+ userName: 'Admin User',
663
+ userEmail: 'adminlist2@example.com',
664
+ role: 'ADMIN',
665
+ })
666
+
667
+ // Create some invitations
668
+ await createInvitation({
669
+ accountId: scenario.account.id,
670
+ email: 'invite1@example.com',
671
+ role: 'VIEWER',
672
+ invitedById: scenario.user.id,
673
+ })
674
+ await createInvitation({
675
+ accountId: scenario.account.id,
676
+ email: 'invite2@example.com',
677
+ role: 'EDITOR',
678
+ invitedById: scenario.user.id,
679
+ })
680
+
681
+ const res = await app.request('/api/invitations', {
682
+ method: 'GET',
683
+ headers: {
684
+ ...scenario.headers,
685
+ 'User-Agent': 'IntegrationTest/1.0',
686
+ 'account-id': scenario.account.id,
687
+ },
688
+ })
689
+
690
+ expect(res.status).toBe(200)
691
+
692
+ const body = await res.json()
693
+ expect(body.data.length).toBe(2)
694
+ })
695
+
696
+ it('should return only invitations for the current account', async () => {
697
+ const scenario = await createTestScenario({
698
+ userName: 'Admin User',
699
+ userEmail: 'adminlist3@example.com',
700
+ role: 'ADMIN',
701
+ })
702
+
703
+ // Create another account with invitations
704
+ const otherAccount = await createAccount({ name: 'Other Account' })
705
+ const otherUser = await createUser({
706
+ email: 'otheruser@example.com',
707
+ name: 'Other User',
708
+ })
709
+ await addUserToAccount(otherUser.id, otherAccount.id, 'ADMIN')
710
+
711
+ // Create invitations in both accounts
712
+ await createInvitation({
713
+ accountId: scenario.account.id,
714
+ email: 'myaccountinvite@example.com',
715
+ role: 'VIEWER',
716
+ invitedById: scenario.user.id,
717
+ })
718
+ await createInvitation({
719
+ accountId: otherAccount.id,
720
+ email: 'otheraccountinvite@example.com',
721
+ role: 'VIEWER',
722
+ invitedById: otherUser.id,
723
+ })
724
+
725
+ const res = await app.request('/api/invitations', {
726
+ method: 'GET',
727
+ headers: {
728
+ ...scenario.headers,
729
+ 'User-Agent': 'IntegrationTest/1.0',
730
+ 'account-id': scenario.account.id,
731
+ },
732
+ })
733
+
734
+ expect(res.status).toBe(200)
735
+
736
+ const body = await res.json()
737
+ expect(body.data.length).toBe(1)
738
+ expect(body.data[0].email).toBe('myaccountinvite@example.com')
739
+ })
740
+
741
+ it('should include invitation details (email, role, createdAt, expiresAt, invitedBy)', async () => {
742
+ const scenario = await createTestScenario({
743
+ userName: 'Admin User',
744
+ userEmail: 'adminlist4@example.com',
745
+ role: 'ADMIN',
746
+ })
747
+
748
+ await createInvitation({
749
+ accountId: scenario.account.id,
750
+ email: 'detailtest@example.com',
751
+ role: 'MANAGER',
752
+ invitedById: scenario.user.id,
753
+ })
754
+
755
+ const res = await app.request('/api/invitations', {
756
+ method: 'GET',
757
+ headers: {
758
+ ...scenario.headers,
759
+ 'User-Agent': 'IntegrationTest/1.0',
760
+ 'account-id': scenario.account.id,
761
+ },
762
+ })
763
+
764
+ expect(res.status).toBe(200)
765
+
766
+ const body = await res.json()
767
+ expect(body.data.length).toBe(1)
768
+ const invitation = body.data[0]
769
+ expect(invitation).toHaveProperty('id')
770
+ expect(invitation).toHaveProperty('email', 'detailtest@example.com')
771
+ expect(invitation).toHaveProperty('role', 'MANAGER')
772
+ expect(invitation).toHaveProperty('createdAt')
773
+ expect(invitation).toHaveProperty('expiresAt')
774
+ expect(invitation).toHaveProperty('invitedBy')
775
+ expect(invitation.invitedBy).toHaveProperty('id', scenario.user.id)
776
+ expect(invitation.invitedBy).toHaveProperty('name', scenario.user.name)
777
+ })
778
+
779
+ it('should not include expired invitations', async () => {
780
+ const scenario = await createTestScenario({
781
+ userName: 'Admin User',
782
+ userEmail: 'adminlist5@example.com',
783
+ role: 'ADMIN',
784
+ })
785
+
786
+ // Create a pending invitation
787
+ await createInvitation({
788
+ accountId: scenario.account.id,
789
+ email: 'pendinginvite@example.com',
790
+ role: 'VIEWER',
791
+ invitedById: scenario.user.id,
792
+ })
793
+
794
+ // Create an expired invitation
795
+ await createExpiredInvitation({
796
+ accountId: scenario.account.id,
797
+ email: 'expiredinvite2@example.com',
798
+ role: 'VIEWER',
799
+ invitedById: scenario.user.id,
800
+ })
801
+
802
+ const res = await app.request('/api/invitations', {
803
+ method: 'GET',
804
+ headers: {
805
+ ...scenario.headers,
806
+ 'User-Agent': 'IntegrationTest/1.0',
807
+ 'account-id': scenario.account.id,
808
+ },
809
+ })
810
+
811
+ expect(res.status).toBe(200)
812
+
813
+ const body = await res.json()
814
+ expect(body.data.length).toBe(1)
815
+ expect(body.data[0].email).toBe('pendinginvite@example.com')
816
+ })
817
+
818
+ it('should not include accepted invitations', async () => {
819
+ const scenario = await createTestScenario({
820
+ userName: 'Admin User',
821
+ userEmail: 'adminlist6@example.com',
822
+ role: 'ADMIN',
823
+ })
824
+
825
+ // Create a pending invitation
826
+ await createInvitation({
827
+ accountId: scenario.account.id,
828
+ email: 'stilpending@example.com',
829
+ role: 'VIEWER',
830
+ invitedById: scenario.user.id,
831
+ })
832
+
833
+ // Create an accepted invitation
834
+ await createAcceptedInvitation({
835
+ accountId: scenario.account.id,
836
+ email: 'alreadyaccepted@example.com',
837
+ role: 'VIEWER',
838
+ invitedById: scenario.user.id,
839
+ })
840
+
841
+ const res = await app.request('/api/invitations', {
842
+ method: 'GET',
843
+ headers: {
844
+ ...scenario.headers,
845
+ 'User-Agent': 'IntegrationTest/1.0',
846
+ 'account-id': scenario.account.id,
847
+ },
848
+ })
849
+
850
+ expect(res.status).toBe(200)
851
+
852
+ const body = await res.json()
853
+ expect(body.data.length).toBe(1)
854
+ expect(body.data[0].email).toBe('stilpending@example.com')
855
+ })
856
+
857
+ it('should allow MANAGER role to list invitations', async () => {
858
+ const scenario = await createTestScenario({
859
+ userName: 'Manager User',
860
+ userEmail: 'managerlist@example.com',
861
+ role: 'MANAGER',
862
+ })
863
+
864
+ const res = await app.request('/api/invitations', {
865
+ method: 'GET',
866
+ headers: {
867
+ ...scenario.headers,
868
+ 'User-Agent': 'IntegrationTest/1.0',
869
+ 'account-id': scenario.account.id,
870
+ },
871
+ })
872
+
873
+ expect(res.status).toBe(200)
874
+ })
875
+ })
876
+ })
877
+
878
+ // ============================================================================
879
+ // DELETE /api/invitations/:id
880
+ // ============================================================================
881
+
882
+ describe('DELETE /api/invitations/:id', () => {
883
+ describe('Authentication (401)', () => {
884
+ it('should return 401 without session cookie', async () => {
885
+ const invitationId = crypto.randomUUID()
886
+ const res = await app.request(`/api/invitations/${invitationId}`, {
887
+ method: 'DELETE',
888
+ })
889
+
890
+ expect(res.status).toBe(401)
891
+
892
+ const body = await res.json()
893
+ expect(body).toHaveProperty('error')
894
+ expect(body.error.message).toBe('Not authenticated')
895
+ })
896
+
897
+ it('should return 401 with invalid session ID', async () => {
898
+ const invitationId = crypto.randomUUID()
899
+ const res = await app.request(`/api/invitations/${invitationId}`, {
900
+ method: 'DELETE',
901
+ headers: {
902
+ Cookie: 'sid=invalid-session-id-that-does-not-exist',
903
+ 'account-id': crypto.randomUUID(),
904
+ },
905
+ })
906
+
907
+ expect(res.status).toBe(401)
908
+
909
+ const body = await res.json()
910
+ expect(body.error.message).toBe('Not authenticated')
911
+ })
912
+ })
913
+
914
+ describe('Not Found (404)', () => {
915
+ it('should return 404 for non-existent invitation', async () => {
916
+ const scenario = await createTestScenario({
917
+ userName: 'Admin User',
918
+ userEmail: 'admindelete1@example.com',
919
+ role: 'ADMIN',
920
+ })
921
+
922
+ const nonExistentId = crypto.randomUUID()
923
+ const res = await app.request(`/api/invitations/${nonExistentId}`, {
924
+ method: 'DELETE',
925
+ headers: {
926
+ ...scenario.headers,
927
+ 'User-Agent': 'IntegrationTest/1.0',
928
+ 'account-id': scenario.account.id,
929
+ },
930
+ })
931
+
932
+ expect(res.status).toBe(404)
933
+ })
934
+
935
+ it('should return 404 for invitation in different account', async () => {
936
+ const scenario = await createTestScenario({
937
+ userName: 'Admin User',
938
+ userEmail: 'admindelete2@example.com',
939
+ role: 'ADMIN',
940
+ })
941
+
942
+ // Create another account with an invitation
943
+ const otherAccount = await createAccount({ name: 'Other Account Delete' })
944
+ const otherUser = await createUser({
945
+ email: 'otherdelete@example.com',
946
+ name: 'Other Delete User',
947
+ })
948
+ await addUserToAccount(otherUser.id, otherAccount.id, 'ADMIN')
949
+
950
+ const otherInvitation = await createInvitation({
951
+ accountId: otherAccount.id,
952
+ email: 'otherinvite@example.com',
953
+ role: 'VIEWER',
954
+ invitedById: otherUser.id,
955
+ })
956
+
957
+ // Try to delete from wrong account
958
+ const res = await app.request(`/api/invitations/${otherInvitation.id}`, {
959
+ method: 'DELETE',
960
+ headers: {
961
+ ...scenario.headers,
962
+ 'User-Agent': 'IntegrationTest/1.0',
963
+ 'account-id': scenario.account.id,
964
+ },
965
+ })
966
+
967
+ expect(res.status).toBe(404)
968
+ })
969
+ })
970
+
971
+ describe('Authorization (403)', () => {
972
+ it('should return 403 if user lacks permission to revoke (VIEWER role)', async () => {
973
+ const scenario = await createTestScenario({
974
+ userName: 'Viewer User',
975
+ userEmail: 'viewerdelete@example.com',
976
+ role: 'VIEWER',
977
+ })
978
+
979
+ // Create invitation using direct DB (since VIEWER can't create via API)
980
+ const adminUser = await createUser({
981
+ email: 'adminforviewer@example.com',
982
+ name: 'Admin For Viewer',
983
+ })
984
+ await addUserToAccount(adminUser.id, scenario.account.id, 'ADMIN')
985
+
986
+ const invitation = await createInvitation({
987
+ accountId: scenario.account.id,
988
+ email: 'viewercannotdelete@example.com',
989
+ role: 'VIEWER',
990
+ invitedById: adminUser.id,
991
+ })
992
+
993
+ const res = await app.request(`/api/invitations/${invitation.id}`, {
994
+ method: 'DELETE',
995
+ headers: {
996
+ ...scenario.headers,
997
+ 'User-Agent': 'IntegrationTest/1.0',
998
+ 'account-id': scenario.account.id,
999
+ },
1000
+ })
1001
+
1002
+ expect(res.status).toBe(403)
1003
+ })
1004
+
1005
+ it('should return 403 if user lacks permission to revoke (EDITOR role)', async () => {
1006
+ const scenario = await createTestScenario({
1007
+ userName: 'Editor User',
1008
+ userEmail: 'editordelete@example.com',
1009
+ role: 'EDITOR',
1010
+ })
1011
+
1012
+ const adminUser = await createUser({
1013
+ email: 'adminforeditor@example.com',
1014
+ name: 'Admin For Editor',
1015
+ })
1016
+ await addUserToAccount(adminUser.id, scenario.account.id, 'ADMIN')
1017
+
1018
+ const invitation = await createInvitation({
1019
+ accountId: scenario.account.id,
1020
+ email: 'editorcannotdelete@example.com',
1021
+ role: 'VIEWER',
1022
+ invitedById: adminUser.id,
1023
+ })
1024
+
1025
+ const res = await app.request(`/api/invitations/${invitation.id}`, {
1026
+ method: 'DELETE',
1027
+ headers: {
1028
+ ...scenario.headers,
1029
+ 'User-Agent': 'IntegrationTest/1.0',
1030
+ 'account-id': scenario.account.id,
1031
+ },
1032
+ })
1033
+
1034
+ expect(res.status).toBe(403)
1035
+ })
1036
+ })
1037
+
1038
+ describe('Successful Revoke (204)', () => {
1039
+ it('should return 204 on successful revoke', async () => {
1040
+ const scenario = await createTestScenario({
1041
+ userName: 'Admin User',
1042
+ userEmail: 'admindelete3@example.com',
1043
+ role: 'ADMIN',
1044
+ })
1045
+
1046
+ const invitation = await createInvitation({
1047
+ accountId: scenario.account.id,
1048
+ email: 'torevoke@example.com',
1049
+ role: 'VIEWER',
1050
+ invitedById: scenario.user.id,
1051
+ })
1052
+
1053
+ const res = await app.request(`/api/invitations/${invitation.id}`, {
1054
+ method: 'DELETE',
1055
+ headers: {
1056
+ ...scenario.headers,
1057
+ 'User-Agent': 'IntegrationTest/1.0',
1058
+ 'account-id': scenario.account.id,
1059
+ },
1060
+ })
1061
+
1062
+ expect(res.status).toBe(204)
1063
+ })
1064
+
1065
+ it('should verify invitation is deleted from database', async () => {
1066
+ const scenario = await createTestScenario({
1067
+ userName: 'Admin User',
1068
+ userEmail: 'admindelete4@example.com',
1069
+ role: 'ADMIN',
1070
+ })
1071
+
1072
+ const invitation = await createInvitation({
1073
+ accountId: scenario.account.id,
1074
+ email: 'todelete@example.com',
1075
+ role: 'VIEWER',
1076
+ invitedById: scenario.user.id,
1077
+ })
1078
+
1079
+ // Verify invitation exists before delete
1080
+ const sqlite = getSqlite()
1081
+ const beforeRow = sqlite.prepare('SELECT * FROM invitations WHERE id = ?').get(invitation.id)
1082
+ expect(beforeRow).toBeDefined()
1083
+
1084
+ // Delete the invitation
1085
+ await app.request(`/api/invitations/${invitation.id}`, {
1086
+ method: 'DELETE',
1087
+ headers: {
1088
+ ...scenario.headers,
1089
+ 'User-Agent': 'IntegrationTest/1.0',
1090
+ 'account-id': scenario.account.id,
1091
+ },
1092
+ })
1093
+
1094
+ // Verify invitation is deleted
1095
+ const afterRow = sqlite.prepare('SELECT * FROM invitations WHERE id = ?').get(invitation.id)
1096
+ expect(afterRow).toBeUndefined()
1097
+ })
1098
+
1099
+ it('should not appear in list after revocation', async () => {
1100
+ const scenario = await createTestScenario({
1101
+ userName: 'Admin User',
1102
+ userEmail: 'admindelete5@example.com',
1103
+ role: 'ADMIN',
1104
+ })
1105
+
1106
+ // Create two invitations
1107
+ const invitationToDelete = await createInvitation({
1108
+ accountId: scenario.account.id,
1109
+ email: 'willbedeleted@example.com',
1110
+ role: 'VIEWER',
1111
+ invitedById: scenario.user.id,
1112
+ })
1113
+ await createInvitation({
1114
+ accountId: scenario.account.id,
1115
+ email: 'willremain@example.com',
1116
+ role: 'EDITOR',
1117
+ invitedById: scenario.user.id,
1118
+ })
1119
+
1120
+ // Delete one invitation
1121
+ await app.request(`/api/invitations/${invitationToDelete.id}`, {
1122
+ method: 'DELETE',
1123
+ headers: {
1124
+ ...scenario.headers,
1125
+ 'User-Agent': 'IntegrationTest/1.0',
1126
+ 'account-id': scenario.account.id,
1127
+ },
1128
+ })
1129
+
1130
+ // List invitations and verify
1131
+ const listRes = await app.request('/api/invitations', {
1132
+ method: 'GET',
1133
+ headers: {
1134
+ ...scenario.headers,
1135
+ 'User-Agent': 'IntegrationTest/1.0',
1136
+ 'account-id': scenario.account.id,
1137
+ },
1138
+ })
1139
+
1140
+ expect(listRes.status).toBe(200)
1141
+
1142
+ const body = await listRes.json()
1143
+ expect(body.data.length).toBe(1)
1144
+ expect(body.data[0].email).toBe('willremain@example.com')
1145
+ })
1146
+
1147
+ it('should allow MANAGER role to revoke invitations', async () => {
1148
+ const scenario = await createTestScenario({
1149
+ userName: 'Manager User',
1150
+ userEmail: 'managerdelete@example.com',
1151
+ role: 'MANAGER',
1152
+ })
1153
+
1154
+ const invitation = await createInvitation({
1155
+ accountId: scenario.account.id,
1156
+ email: 'managerrevoke@example.com',
1157
+ role: 'VIEWER',
1158
+ invitedById: scenario.user.id,
1159
+ })
1160
+
1161
+ const res = await app.request(`/api/invitations/${invitation.id}`, {
1162
+ method: 'DELETE',
1163
+ headers: {
1164
+ ...scenario.headers,
1165
+ 'User-Agent': 'IntegrationTest/1.0',
1166
+ 'account-id': scenario.account.id,
1167
+ },
1168
+ })
1169
+
1170
+ expect(res.status).toBe(204)
1171
+ })
1172
+ })
1173
+ })
1174
+
1175
+ // ============================================================================
1176
+ // Error Handling - Edge Cases
1177
+ // ============================================================================
1178
+
1179
+ describe('Handler Error Cases', () => {
1180
+ describe('Context Fallbacks', () => {
1181
+ it('should use empty string fallbacks for optional context values', async () => {
1182
+ // This test exercises lines 25-27 in handlers.ts - the nullish coalescing fallbacks
1183
+ const scenario = await createTestScenario({
1184
+ userName: 'Fallback Test User',
1185
+ userEmail: 'fallbacktest@example.com',
1186
+ role: 'ADMIN',
1187
+ })
1188
+
1189
+ const appWithMinimalContext = new Hono<HonoEnv>()
1190
+ appWithMinimalContext.onError(errorHandler)
1191
+
1192
+ appWithMinimalContext.use('*', async (c, next) => {
1193
+ ;(c as any).env = env
1194
+ const db = createTestDb()
1195
+ c.set('db', db)
1196
+ c.set('accountId', scenario.account.id)
1197
+ c.set('user', { id: scenario.user.id, email: scenario.user.email, name: scenario.user.name })
1198
+ c.set('userRole', 'ADMIN')
1199
+ // Note: NOT setting transactionId, ip, or userAgent to test fallbacks
1200
+ await next()
1201
+ })
1202
+
1203
+ const { listInvitationsHandler } = await import('../../../src/server/routes/invitations/handlers')
1204
+ appWithMinimalContext.get('/test-list', async (c) => {
1205
+ // @ts-expect-error - Testing with minimal context
1206
+ return listInvitationsHandler(c)
1207
+ })
1208
+
1209
+ // Should succeed even without transactionId/ip/userAgent
1210
+ const res = await appWithMinimalContext.request('/test-list', {
1211
+ method: 'GET',
1212
+ })
1213
+
1214
+ // We expect success since all required context is present
1215
+ // The fallbacks ('' for transactionId, ip, userAgent) should be used
1216
+ expect(res.status).toBe(200)
1217
+ const body = await res.json()
1218
+ expect(body.data).toBeDefined()
1219
+ })
1220
+ })
1221
+
1222
+ describe('Missing Context', () => {
1223
+ it('should return 500 when accountId is missing from context (create)', async () => {
1224
+ // Create an app without account middleware to simulate missing context
1225
+ const appWithoutContext = new Hono<HonoEnv>()
1226
+ appWithoutContext.onError(errorHandler)
1227
+
1228
+ appWithoutContext.use('*', async (c, next) => {
1229
+ ;(c as any).env = env
1230
+ const db = createTestDb()
1231
+ c.set('db', db)
1232
+ c.set('transactionId', crypto.randomUUID())
1233
+ c.set('ip', '127.0.0.1')
1234
+ c.set('userAgent', 'IntegrationTest/1.0')
1235
+ // Note: NOT setting accountId or user
1236
+ await next()
1237
+ })
1238
+
1239
+ // Import handlers directly to bypass auth middleware
1240
+ const { createInvitationHandler } = await import('../../../src/server/routes/invitations/handlers')
1241
+ appWithoutContext.post('/test-create', async (c) => {
1242
+ // @ts-expect-error - Testing error case
1243
+ return createInvitationHandler(c)
1244
+ })
1245
+
1246
+ const res = await appWithoutContext.request('/test-create', {
1247
+ method: 'POST',
1248
+ headers: {
1249
+ 'Content-Type': 'application/json',
1250
+ },
1251
+ body: JSON.stringify({ email: 'test@example.com', role: 'VIEWER' }),
1252
+ })
1253
+
1254
+ expect(res.status).toBe(500)
1255
+ const body = await res.json()
1256
+ expect(body.error.message).toBe('Missing required context')
1257
+ })
1258
+
1259
+ it('should return 500 when user is missing from context (list)', async () => {
1260
+ const scenario = await createTestScenario({
1261
+ userName: 'Test User',
1262
+ userEmail: 'contexttest1@example.com',
1263
+ role: 'ADMIN',
1264
+ })
1265
+
1266
+ const appWithoutUser = new Hono<HonoEnv>()
1267
+ appWithoutUser.onError(errorHandler)
1268
+
1269
+ appWithoutUser.use('*', async (c, next) => {
1270
+ ;(c as any).env = env
1271
+ const db = createTestDb()
1272
+ c.set('db', db)
1273
+ c.set('transactionId', crypto.randomUUID())
1274
+ c.set('ip', '127.0.0.1')
1275
+ c.set('userAgent', 'IntegrationTest/1.0')
1276
+ c.set('accountId', scenario.account.id)
1277
+ // Note: NOT setting user
1278
+ await next()
1279
+ })
1280
+
1281
+ const { listInvitationsHandler } = await import('../../../src/server/routes/invitations/handlers')
1282
+ appWithoutUser.get('/test-list', async (c) => {
1283
+ // @ts-expect-error - Testing error case
1284
+ return listInvitationsHandler(c)
1285
+ })
1286
+
1287
+ const res = await appWithoutUser.request('/test-list', {
1288
+ method: 'GET',
1289
+ })
1290
+
1291
+ expect(res.status).toBe(500)
1292
+ const body = await res.json()
1293
+ expect(body.error.message).toBe('Missing required context')
1294
+ })
1295
+
1296
+ it('should return 500 when context is missing (revoke)', async () => {
1297
+ const testId = crypto.randomUUID()
1298
+ const appWithoutContext = new Hono<HonoEnv>()
1299
+ appWithoutContext.onError(errorHandler)
1300
+
1301
+ appWithoutContext.use('*', async (c, next) => {
1302
+ ;(c as any).env = env
1303
+ const db = createTestDb()
1304
+ c.set('db', db)
1305
+ c.set('transactionId', crypto.randomUUID())
1306
+ c.set('ip', '127.0.0.1')
1307
+ c.set('userAgent', 'IntegrationTest/1.0')
1308
+ // Note: NOT setting accountId or user
1309
+ await next()
1310
+ })
1311
+
1312
+ const { revokeInvitationHandler } = await import('../../../src/server/routes/invitations/handlers')
1313
+ appWithoutContext.delete('/test-revoke/:id', async (c) => {
1314
+ // Mock the valid method to return the param (bypasses OpenAPI validation)
1315
+ const originalValid = c.req.valid.bind(c.req)
1316
+ c.req.valid = ((type: string) => {
1317
+ if (type === 'param') return { id: testId }
1318
+ return originalValid(type)
1319
+ }) as typeof c.req.valid
1320
+ // @ts-expect-error - Testing error case
1321
+ return revokeInvitationHandler(c)
1322
+ })
1323
+
1324
+ const res = await appWithoutContext.request(`/test-revoke/${testId}`, {
1325
+ method: 'DELETE',
1326
+ })
1327
+
1328
+ expect(res.status).toBe(500)
1329
+ const body = await res.json()
1330
+ expect(body.error.message).toBe('Missing required context')
1331
+ })
1332
+ })
1333
+
1334
+ describe('Missing Database', () => {
1335
+ it('should return 500 when database is not initialized (create)', async () => {
1336
+ const scenario = await createTestScenario({
1337
+ userName: 'Test User',
1338
+ userEmail: 'dbtest1@example.com',
1339
+ role: 'ADMIN',
1340
+ })
1341
+
1342
+ const appWithoutDb = new Hono<HonoEnv>()
1343
+ appWithoutDb.onError(errorHandler)
1344
+
1345
+ appWithoutDb.use('*', async (c, next) => {
1346
+ ;(c as any).env = env
1347
+ // Note: NOT setting db
1348
+ c.set('transactionId', crypto.randomUUID())
1349
+ c.set('ip', '127.0.0.1')
1350
+ c.set('userAgent', 'IntegrationTest/1.0')
1351
+ c.set('accountId', scenario.account.id)
1352
+ c.set('user', { id: scenario.user.id, email: scenario.user.email, name: scenario.user.name })
1353
+ await next()
1354
+ })
1355
+
1356
+ const { createInvitationHandler } = await import('../../../src/server/routes/invitations/handlers')
1357
+ appWithoutDb.post('/test-create', async (c) => {
1358
+ // @ts-expect-error - Testing error case
1359
+ return createInvitationHandler(c)
1360
+ })
1361
+
1362
+ const res = await appWithoutDb.request('/test-create', {
1363
+ method: 'POST',
1364
+ headers: {
1365
+ 'Content-Type': 'application/json',
1366
+ },
1367
+ body: JSON.stringify({ email: 'test@example.com', role: 'VIEWER' }),
1368
+ })
1369
+
1370
+ expect(res.status).toBe(500)
1371
+ const body = await res.json()
1372
+ expect(body.error.message).toBe('Database not initialized')
1373
+ })
1374
+
1375
+ it('should return 500 when database is not initialized (list)', async () => {
1376
+ const scenario = await createTestScenario({
1377
+ userName: 'Test User',
1378
+ userEmail: 'dbtest2@example.com',
1379
+ role: 'ADMIN',
1380
+ })
1381
+
1382
+ const appWithoutDb = new Hono<HonoEnv>()
1383
+ appWithoutDb.onError(errorHandler)
1384
+
1385
+ appWithoutDb.use('*', async (c, next) => {
1386
+ ;(c as any).env = env
1387
+ // Note: NOT setting db
1388
+ c.set('transactionId', crypto.randomUUID())
1389
+ c.set('ip', '127.0.0.1')
1390
+ c.set('userAgent', 'IntegrationTest/1.0')
1391
+ c.set('accountId', scenario.account.id)
1392
+ c.set('user', { id: scenario.user.id, email: scenario.user.email, name: scenario.user.name })
1393
+ c.set('userRole', 'ADMIN')
1394
+ await next()
1395
+ })
1396
+
1397
+ const { listInvitationsHandler } = await import('../../../src/server/routes/invitations/handlers')
1398
+ appWithoutDb.get('/test-list', async (c) => {
1399
+ // @ts-expect-error - Testing error case
1400
+ return listInvitationsHandler(c)
1401
+ })
1402
+
1403
+ const res = await appWithoutDb.request('/test-list', {
1404
+ method: 'GET',
1405
+ })
1406
+
1407
+ expect(res.status).toBe(500)
1408
+ const body = await res.json()
1409
+ expect(body.error.message).toBe('Database not initialized')
1410
+ })
1411
+
1412
+ it('should return 500 when database is not initialized (revoke)', async () => {
1413
+ const testId = crypto.randomUUID()
1414
+ const scenario = await createTestScenario({
1415
+ userName: 'Test User',
1416
+ userEmail: 'dbtest3@example.com',
1417
+ role: 'ADMIN',
1418
+ })
1419
+
1420
+ const appWithoutDb = new Hono<HonoEnv>()
1421
+ appWithoutDb.onError(errorHandler)
1422
+
1423
+ appWithoutDb.use('*', async (c, next) => {
1424
+ ;(c as any).env = env
1425
+ // Note: NOT setting db
1426
+ c.set('transactionId', crypto.randomUUID())
1427
+ c.set('ip', '127.0.0.1')
1428
+ c.set('userAgent', 'IntegrationTest/1.0')
1429
+ c.set('accountId', scenario.account.id)
1430
+ c.set('user', { id: scenario.user.id, email: scenario.user.email, name: scenario.user.name })
1431
+ c.set('userRole', 'ADMIN')
1432
+ await next()
1433
+ })
1434
+
1435
+ const { revokeInvitationHandler } = await import('../../../src/server/routes/invitations/handlers')
1436
+ appWithoutDb.delete('/test-revoke/:id', async (c) => {
1437
+ // Mock the valid method to return the param (bypasses OpenAPI validation)
1438
+ const originalValid = c.req.valid.bind(c.req)
1439
+ c.req.valid = ((type: string) => {
1440
+ if (type === 'param') return { id: testId }
1441
+ return originalValid(type)
1442
+ }) as typeof c.req.valid
1443
+ // @ts-expect-error - Testing error case
1444
+ return revokeInvitationHandler(c)
1445
+ })
1446
+
1447
+ const res = await appWithoutDb.request(`/test-revoke/${testId}`, {
1448
+ method: 'DELETE',
1449
+ })
1450
+
1451
+ expect(res.status).toBe(500)
1452
+ const body = await res.json()
1453
+ expect(body.error.message).toBe('Database not initialized')
1454
+ })
1455
+ })
1456
+ })
1457
+ })