@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,1347 @@
1
+ /**
2
+ * Role Hierarchy Authorization Integration Tests
3
+ *
4
+ * Comprehensive tests for role-based access control (RBAC) across all API endpoints.
5
+ *
6
+ * Role Hierarchy:
7
+ * - ADMIN (level 0) - highest privilege
8
+ * - MANAGER (level 1)
9
+ * - EDITOR (level 2)
10
+ * - AUTHOR (level 3)
11
+ * - VIEWER (level 4) - lowest privilege
12
+ * - BILLING (level -1) - non-hierarchical, billing-only access
13
+ * - ANALYTICS (level -1) - non-hierarchical, analytics/audit access only
14
+ *
15
+ * Permission Matrix:
16
+ * | Operation | ADMIN | MANAGER | EDITOR | AUTHOR | VIEWER | BILLING | ANALYTICS |
17
+ * |-------------------------|-------|---------|--------|--------|--------|---------|-----------|
18
+ * | View users | Y | Y | Y | Y | Y | Y | Y |
19
+ * | Update users | Y | Y | N | N | N | N | N |
20
+ * | Delete users | Y | N | N | N | N | N | N |
21
+ * | Restore users | Y | N | N | N | N | N | N |
22
+ * | View accounts | Y | Y | Y | Y | Y | Y | Y |
23
+ * | Update accounts | Y | Y | N | N | N | N | N |
24
+ * | Delete accounts | super | N | N | N | N | N | N |
25
+ * | Create invitations | Y | Y | N | N | N | N | N |
26
+ * | View invitations | Y | Y | N | N | N | N | N |
27
+ * | Revoke invitations | Y | Y | N | N | N | N | N |
28
+ * | View audit logs | Y | N | N | N | N | N | Y |
29
+ */
30
+
31
+ import { describe, it, expect, beforeAll } from 'vitest'
32
+ import { Hono } from 'hono'
33
+ import { getEnv, getDb, getSqlite, type TestEnv } from '../setup'
34
+ import {
35
+ createUser,
36
+ createDeletedUser,
37
+ createUserSession,
38
+ createAccount,
39
+ addUserToAccount,
40
+ type Role,
41
+ } from '../fixtures'
42
+ import type { HonoEnv } from '../../../src/server/types'
43
+ import { api } from '../../../src/server/routes'
44
+ import { errorHandler } from '../../../src/server/middleware/error-handler'
45
+ import { sessionMiddleware } from '../../../src/server/lib/session'
46
+
47
+ // ============================================================================
48
+ // TEST SETUP
49
+ // ============================================================================
50
+
51
+ /**
52
+ * Creates a database wrapper that adds the `execute` method
53
+ * The better-sqlite3 drizzle doesn't have execute, but D1 does
54
+ */
55
+ function createTestDb() {
56
+ const db = getDb()
57
+ return new Proxy(db, {
58
+ get(target, prop) {
59
+ if (prop === 'execute') {
60
+ return target.run.bind(target)
61
+ }
62
+ return (target as any)[prop]
63
+ },
64
+ })
65
+ }
66
+
67
+ /**
68
+ * Helper to create a user with a specific role in an account
69
+ */
70
+ async function createUserWithRole(
71
+ accountId: string,
72
+ role: Role,
73
+ options?: { email?: string; name?: string }
74
+ ): Promise<{
75
+ user: Awaited<ReturnType<typeof createUser>>
76
+ sessionId: string
77
+ headers: Record<string, string>
78
+ }> {
79
+ const user = await createUser({
80
+ email: options?.email ?? `${role.toLowerCase()}-user-${crypto.randomUUID().slice(0, 8)}@example.com`,
81
+ name: options?.name ?? `${role} User`,
82
+ })
83
+
84
+ await addUserToAccount(user.id, accountId, role)
85
+
86
+ const { sessionId, headers } = await createUserSession(user.id, {
87
+ email: user.email,
88
+ name: user.name,
89
+ })
90
+
91
+ return { user, sessionId, headers }
92
+ }
93
+
94
+ /**
95
+ * Helper to create an audit log entry directly in the database
96
+ */
97
+ function createAuditLog(options: {
98
+ accountId: string
99
+ userId: string
100
+ entity: string
101
+ entityId: string
102
+ action: 'INSERT' | 'UPDATE' | 'DELETE' | 'LOGIN' | 'LOGOUT' | 'SIGNUP' | 'TOKEN_REFRESH' | 'LOGIN_FAILED'
103
+ changes?: Record<string, unknown> | null
104
+ timestamp?: string
105
+ }) {
106
+ const sqlite = getSqlite()
107
+ const id = crypto.randomUUID()
108
+ const transactionId = crypto.randomUUID()
109
+ const timestamp = options.timestamp ?? new Date().toISOString()
110
+
111
+ sqlite.prepare(`
112
+ INSERT INTO audit_logs (id, transaction_id, account_id, user_id, entity, entity_id, action, changes, ip_address, user_agent, timestamp)
113
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
114
+ `).run(
115
+ id,
116
+ transactionId,
117
+ options.accountId,
118
+ options.userId,
119
+ options.entity,
120
+ options.entityId,
121
+ options.action,
122
+ options.changes ? JSON.stringify(options.changes) : null,
123
+ '127.0.0.1',
124
+ 'IntegrationTest/1.0',
125
+ timestamp
126
+ )
127
+
128
+ return { id, transactionId, timestamp }
129
+ }
130
+
131
+ // ============================================================================
132
+ // TESTS
133
+ // ============================================================================
134
+
135
+ describe('Role Hierarchy Authorization', () => {
136
+ let app: Hono<HonoEnv>
137
+ let env: TestEnv
138
+
139
+ beforeAll(() => {
140
+ env = getEnv()
141
+ app = new Hono<HonoEnv>()
142
+
143
+ // Set up error handler
144
+ app.onError(errorHandler)
145
+
146
+ // Set up middleware to inject test environment
147
+ app.use('*', async (c, next) => {
148
+ // Inject environment bindings
149
+ ;(c as any).env = env
150
+
151
+ // Set up database
152
+ const db = createTestDb()
153
+ c.set('db', db)
154
+
155
+ // Set up request context variables
156
+ c.set('transactionId', crypto.randomUUID())
157
+ c.set('ip', '127.0.0.1')
158
+ c.set('userAgent', 'IntegrationTest/1.0')
159
+
160
+ await next()
161
+ })
162
+
163
+ // Session middleware - reads session from KV and sets sessionData in context
164
+ app.use('*', sessionMiddleware())
165
+
166
+ // Mount API routes (includes sessionAuth and accountMiddleware)
167
+ app.route('/api', api)
168
+ })
169
+
170
+ // ============================================================================
171
+ // ADMIN ROLE TESTS
172
+ // ============================================================================
173
+
174
+ describe('ADMIN role', () => {
175
+ it('should be able to view all users', async () => {
176
+ const account = await createAccount({ name: 'Admin Test Account' })
177
+ const { headers } = await createUserWithRole(account.id, 'ADMIN')
178
+
179
+ const res = await app.request('/api/users', {
180
+ method: 'GET',
181
+ headers: {
182
+ ...headers,
183
+ 'User-Agent': 'IntegrationTest/1.0',
184
+ 'account-id': account.id,
185
+ },
186
+ })
187
+
188
+ expect(res.status).toBe(200)
189
+ const body = await res.json()
190
+ expect(body).toHaveProperty('data')
191
+ expect(Array.isArray(body.data)).toBe(true)
192
+ })
193
+
194
+ it('should be able to update any user', async () => {
195
+ const account = await createAccount({ name: 'Admin Update Test' })
196
+ const { headers: adminHeaders } = await createUserWithRole(account.id, 'ADMIN')
197
+ const { user: targetUser } = await createUserWithRole(account.id, 'VIEWER')
198
+
199
+ const res = await app.request(`/api/users/${targetUser.id}`, {
200
+ method: 'PATCH',
201
+ headers: {
202
+ ...adminHeaders,
203
+ 'User-Agent': 'IntegrationTest/1.0',
204
+ 'account-id': account.id,
205
+ 'Content-Type': 'application/json',
206
+ },
207
+ body: JSON.stringify({ name: 'Admin Updated Name' }),
208
+ })
209
+
210
+ expect(res.status).toBe(200)
211
+ const body = await res.json()
212
+ expect(body.data.name).toBe('Admin Updated Name')
213
+ })
214
+
215
+ it('should be able to delete any user', async () => {
216
+ const account = await createAccount({ name: 'Admin Delete Test' })
217
+ const { headers: adminHeaders } = await createUserWithRole(account.id, 'ADMIN')
218
+ const { user: targetUser } = await createUserWithRole(account.id, 'VIEWER')
219
+
220
+ const res = await app.request(`/api/users/${targetUser.id}`, {
221
+ method: 'DELETE',
222
+ headers: {
223
+ ...adminHeaders,
224
+ 'User-Agent': 'IntegrationTest/1.0',
225
+ 'account-id': account.id,
226
+ },
227
+ })
228
+
229
+ expect(res.status).toBe(204)
230
+ })
231
+
232
+ it('should be able to restore soft-deleted users', async () => {
233
+ const account = await createAccount({ name: 'Admin Restore Test' })
234
+ const { headers: adminHeaders } = await createUserWithRole(account.id, 'ADMIN')
235
+
236
+ // Create a deleted user
237
+ const deletedUser = await createDeletedUser({
238
+ email: 'deleted-for-admin@example.com',
239
+ name: 'Deleted For Admin',
240
+ })
241
+ await addUserToAccount(deletedUser.id, account.id, 'VIEWER')
242
+
243
+ const res = await app.request(`/api/users/${deletedUser.id}/restore`, {
244
+ method: 'POST',
245
+ headers: {
246
+ ...adminHeaders,
247
+ 'User-Agent': 'IntegrationTest/1.0',
248
+ 'account-id': account.id,
249
+ },
250
+ })
251
+
252
+ expect(res.status).toBe(200)
253
+ })
254
+
255
+ it('should be able to create invitations', async () => {
256
+ const account = await createAccount({ name: 'Admin Invitation Test' })
257
+ const { headers: adminHeaders } = await createUserWithRole(account.id, 'ADMIN')
258
+
259
+ const res = await app.request('/api/invitations', {
260
+ method: 'POST',
261
+ headers: {
262
+ ...adminHeaders,
263
+ 'User-Agent': 'IntegrationTest/1.0',
264
+ 'account-id': account.id,
265
+ 'Content-Type': 'application/json',
266
+ },
267
+ body: JSON.stringify({
268
+ email: 'invited-by-admin@example.com',
269
+ role: 'VIEWER',
270
+ }),
271
+ })
272
+
273
+ expect(res.status).toBe(200)
274
+ })
275
+
276
+ it('should be able to list invitations', async () => {
277
+ const account = await createAccount({ name: 'Admin List Invitations Test' })
278
+ const { headers: adminHeaders } = await createUserWithRole(account.id, 'ADMIN')
279
+
280
+ const res = await app.request('/api/invitations', {
281
+ method: 'GET',
282
+ headers: {
283
+ ...adminHeaders,
284
+ 'User-Agent': 'IntegrationTest/1.0',
285
+ 'account-id': account.id,
286
+ },
287
+ })
288
+
289
+ expect(res.status).toBe(200)
290
+ })
291
+
292
+ it('should be able to update account', async () => {
293
+ const account = await createAccount({ name: 'Admin Account Update Test' })
294
+ const { headers: adminHeaders } = await createUserWithRole(account.id, 'ADMIN')
295
+
296
+ const res = await app.request(`/api/accounts/${account.id}`, {
297
+ method: 'PATCH',
298
+ headers: {
299
+ ...adminHeaders,
300
+ 'User-Agent': 'IntegrationTest/1.0',
301
+ 'account-id': account.id,
302
+ 'Content-Type': 'application/json',
303
+ },
304
+ body: JSON.stringify({ name: 'Updated Account Name' }),
305
+ })
306
+
307
+ expect(res.status).toBe(200)
308
+ const body = await res.json()
309
+ expect(body.data.name).toBe('Updated Account Name')
310
+ })
311
+
312
+ it('should be able to view audit logs', async () => {
313
+ const account = await createAccount({ name: 'Admin Audit Test' })
314
+ const { user, headers: adminHeaders } = await createUserWithRole(account.id, 'ADMIN')
315
+
316
+ // Create an audit log entry
317
+ createAuditLog({
318
+ accountId: account.id,
319
+ userId: user.id,
320
+ entity: 'User',
321
+ entityId: crypto.randomUUID(),
322
+ action: 'INSERT',
323
+ })
324
+
325
+ const res = await app.request('/api/audits', {
326
+ method: 'GET',
327
+ headers: {
328
+ ...adminHeaders,
329
+ 'User-Agent': 'IntegrationTest/1.0',
330
+ 'account-id': account.id,
331
+ },
332
+ })
333
+
334
+ expect(res.status).toBe(200)
335
+ const body = await res.json()
336
+ expect(body).toHaveProperty('data')
337
+ })
338
+ })
339
+
340
+ // ============================================================================
341
+ // MANAGER ROLE TESTS
342
+ // ============================================================================
343
+
344
+ describe('MANAGER role', () => {
345
+ it('should be able to view all users', async () => {
346
+ const account = await createAccount({ name: 'Manager View Users Test' })
347
+ const { headers } = await createUserWithRole(account.id, 'MANAGER')
348
+
349
+ const res = await app.request('/api/users', {
350
+ method: 'GET',
351
+ headers: {
352
+ ...headers,
353
+ 'User-Agent': 'IntegrationTest/1.0',
354
+ 'account-id': account.id,
355
+ },
356
+ })
357
+
358
+ expect(res.status).toBe(200)
359
+ })
360
+
361
+ it('should be able to update users', async () => {
362
+ const account = await createAccount({ name: 'Manager Update Test' })
363
+ const { headers: managerHeaders } = await createUserWithRole(account.id, 'MANAGER')
364
+ const { user: targetUser } = await createUserWithRole(account.id, 'VIEWER')
365
+
366
+ const res = await app.request(`/api/users/${targetUser.id}`, {
367
+ method: 'PATCH',
368
+ headers: {
369
+ ...managerHeaders,
370
+ 'User-Agent': 'IntegrationTest/1.0',
371
+ 'account-id': account.id,
372
+ 'Content-Type': 'application/json',
373
+ },
374
+ body: JSON.stringify({ name: 'Manager Updated Name' }),
375
+ })
376
+
377
+ expect(res.status).toBe(200)
378
+ })
379
+
380
+ it('should NOT be able to delete users', async () => {
381
+ const account = await createAccount({ name: 'Manager Delete Test' })
382
+ const { headers: managerHeaders } = await createUserWithRole(account.id, 'MANAGER')
383
+ const { user: targetUser } = await createUserWithRole(account.id, 'VIEWER')
384
+
385
+ const res = await app.request(`/api/users/${targetUser.id}`, {
386
+ method: 'DELETE',
387
+ headers: {
388
+ ...managerHeaders,
389
+ 'User-Agent': 'IntegrationTest/1.0',
390
+ 'account-id': account.id,
391
+ },
392
+ })
393
+
394
+ expect(res.status).toBe(403)
395
+ })
396
+
397
+ it('should NOT be able to restore soft-deleted users', async () => {
398
+ const account = await createAccount({ name: 'Manager Restore Test' })
399
+ const { headers: managerHeaders } = await createUserWithRole(account.id, 'MANAGER')
400
+
401
+ const deletedUser = await createDeletedUser({
402
+ email: 'deleted-for-manager@example.com',
403
+ name: 'Deleted For Manager',
404
+ })
405
+ await addUserToAccount(deletedUser.id, account.id, 'VIEWER')
406
+
407
+ const res = await app.request(`/api/users/${deletedUser.id}/restore`, {
408
+ method: 'POST',
409
+ headers: {
410
+ ...managerHeaders,
411
+ 'User-Agent': 'IntegrationTest/1.0',
412
+ 'account-id': account.id,
413
+ },
414
+ })
415
+
416
+ expect(res.status).toBe(403)
417
+ })
418
+
419
+ it('should be able to create invitations', async () => {
420
+ const account = await createAccount({ name: 'Manager Invitation Test' })
421
+ const { headers: managerHeaders } = await createUserWithRole(account.id, 'MANAGER')
422
+
423
+ const res = await app.request('/api/invitations', {
424
+ method: 'POST',
425
+ headers: {
426
+ ...managerHeaders,
427
+ 'User-Agent': 'IntegrationTest/1.0',
428
+ 'account-id': account.id,
429
+ 'Content-Type': 'application/json',
430
+ },
431
+ body: JSON.stringify({
432
+ email: 'invited-by-manager@example.com',
433
+ role: 'VIEWER',
434
+ }),
435
+ })
436
+
437
+ expect(res.status).toBe(200)
438
+ })
439
+
440
+ it('should be able to update account', async () => {
441
+ const account = await createAccount({ name: 'Manager Account Update Test' })
442
+ const { headers: managerHeaders } = await createUserWithRole(account.id, 'MANAGER')
443
+
444
+ const res = await app.request(`/api/accounts/${account.id}`, {
445
+ method: 'PATCH',
446
+ headers: {
447
+ ...managerHeaders,
448
+ 'User-Agent': 'IntegrationTest/1.0',
449
+ 'account-id': account.id,
450
+ 'Content-Type': 'application/json',
451
+ },
452
+ body: JSON.stringify({ name: 'Manager Updated Account' }),
453
+ })
454
+
455
+ expect(res.status).toBe(200)
456
+ })
457
+
458
+ it('should NOT be able to delete account', async () => {
459
+ const account = await createAccount({ name: 'Manager Delete Account Test' })
460
+ const { headers: managerHeaders } = await createUserWithRole(account.id, 'MANAGER')
461
+
462
+ const res = await app.request(`/api/accounts/${account.id}`, {
463
+ method: 'DELETE',
464
+ headers: {
465
+ ...managerHeaders,
466
+ 'User-Agent': 'IntegrationTest/1.0',
467
+ 'account-id': account.id,
468
+ },
469
+ })
470
+
471
+ expect(res.status).toBe(403)
472
+ })
473
+
474
+ it('should NOT be able to view audit logs', async () => {
475
+ const account = await createAccount({ name: 'Manager Audit Test' })
476
+ const { headers: managerHeaders } = await createUserWithRole(account.id, 'MANAGER')
477
+
478
+ const res = await app.request('/api/audits', {
479
+ method: 'GET',
480
+ headers: {
481
+ ...managerHeaders,
482
+ 'User-Agent': 'IntegrationTest/1.0',
483
+ 'account-id': account.id,
484
+ },
485
+ })
486
+
487
+ expect(res.status).toBe(403)
488
+ })
489
+ })
490
+
491
+ // ============================================================================
492
+ // EDITOR ROLE TESTS
493
+ // ============================================================================
494
+
495
+ describe('EDITOR role', () => {
496
+ it('should be able to view all users', async () => {
497
+ const account = await createAccount({ name: 'Editor View Users Test' })
498
+ const { headers } = await createUserWithRole(account.id, 'EDITOR')
499
+
500
+ const res = await app.request('/api/users', {
501
+ method: 'GET',
502
+ headers: {
503
+ ...headers,
504
+ 'User-Agent': 'IntegrationTest/1.0',
505
+ 'account-id': account.id,
506
+ },
507
+ })
508
+
509
+ expect(res.status).toBe(200)
510
+ })
511
+
512
+ it('should NOT be able to update other users', async () => {
513
+ const account = await createAccount({ name: 'Editor Update Test' })
514
+ const { headers: editorHeaders } = await createUserWithRole(account.id, 'EDITOR')
515
+ const { user: targetUser } = await createUserWithRole(account.id, 'VIEWER')
516
+
517
+ const res = await app.request(`/api/users/${targetUser.id}`, {
518
+ method: 'PATCH',
519
+ headers: {
520
+ ...editorHeaders,
521
+ 'User-Agent': 'IntegrationTest/1.0',
522
+ 'account-id': account.id,
523
+ 'Content-Type': 'application/json',
524
+ },
525
+ body: JSON.stringify({ name: 'Editor Updated Name' }),
526
+ })
527
+
528
+ expect(res.status).toBe(403)
529
+ })
530
+
531
+ it('should NOT be able to delete users', async () => {
532
+ const account = await createAccount({ name: 'Editor Delete Test' })
533
+ const { headers: editorHeaders } = await createUserWithRole(account.id, 'EDITOR')
534
+ const { user: targetUser } = await createUserWithRole(account.id, 'VIEWER')
535
+
536
+ const res = await app.request(`/api/users/${targetUser.id}`, {
537
+ method: 'DELETE',
538
+ headers: {
539
+ ...editorHeaders,
540
+ 'User-Agent': 'IntegrationTest/1.0',
541
+ 'account-id': account.id,
542
+ },
543
+ })
544
+
545
+ expect(res.status).toBe(403)
546
+ })
547
+
548
+ it('should NOT be able to create invitations', async () => {
549
+ const account = await createAccount({ name: 'Editor Invitation Test' })
550
+ const { headers: editorHeaders } = await createUserWithRole(account.id, 'EDITOR')
551
+
552
+ const res = await app.request('/api/invitations', {
553
+ method: 'POST',
554
+ headers: {
555
+ ...editorHeaders,
556
+ 'User-Agent': 'IntegrationTest/1.0',
557
+ 'account-id': account.id,
558
+ 'Content-Type': 'application/json',
559
+ },
560
+ body: JSON.stringify({
561
+ email: 'invited-by-editor@example.com',
562
+ role: 'VIEWER',
563
+ }),
564
+ })
565
+
566
+ expect(res.status).toBe(403)
567
+ })
568
+
569
+ it('should NOT be able to update account', async () => {
570
+ const account = await createAccount({ name: 'Editor Account Update Test' })
571
+ const { headers: editorHeaders } = await createUserWithRole(account.id, 'EDITOR')
572
+
573
+ const res = await app.request(`/api/accounts/${account.id}`, {
574
+ method: 'PATCH',
575
+ headers: {
576
+ ...editorHeaders,
577
+ 'User-Agent': 'IntegrationTest/1.0',
578
+ 'account-id': account.id,
579
+ 'Content-Type': 'application/json',
580
+ },
581
+ body: JSON.stringify({ name: 'Editor Updated Account' }),
582
+ })
583
+
584
+ expect(res.status).toBe(403)
585
+ })
586
+
587
+ it('should NOT be able to view audit logs', async () => {
588
+ const account = await createAccount({ name: 'Editor Audit Test' })
589
+ const { headers: editorHeaders } = await createUserWithRole(account.id, 'EDITOR')
590
+
591
+ const res = await app.request('/api/audits', {
592
+ method: 'GET',
593
+ headers: {
594
+ ...editorHeaders,
595
+ 'User-Agent': 'IntegrationTest/1.0',
596
+ 'account-id': account.id,
597
+ },
598
+ })
599
+
600
+ expect(res.status).toBe(403)
601
+ })
602
+ })
603
+
604
+ // ============================================================================
605
+ // AUTHOR ROLE TESTS
606
+ // ============================================================================
607
+
608
+ describe('AUTHOR role', () => {
609
+ it('should be able to view all users', async () => {
610
+ const account = await createAccount({ name: 'Author View Users Test' })
611
+ const { headers } = await createUserWithRole(account.id, 'AUTHOR')
612
+
613
+ const res = await app.request('/api/users', {
614
+ method: 'GET',
615
+ headers: {
616
+ ...headers,
617
+ 'User-Agent': 'IntegrationTest/1.0',
618
+ 'account-id': account.id,
619
+ },
620
+ })
621
+
622
+ expect(res.status).toBe(200)
623
+ })
624
+
625
+ it('should NOT be able to update other users', async () => {
626
+ const account = await createAccount({ name: 'Author Update Test' })
627
+ const { headers: authorHeaders } = await createUserWithRole(account.id, 'AUTHOR')
628
+ const { user: targetUser } = await createUserWithRole(account.id, 'VIEWER')
629
+
630
+ const res = await app.request(`/api/users/${targetUser.id}`, {
631
+ method: 'PATCH',
632
+ headers: {
633
+ ...authorHeaders,
634
+ 'User-Agent': 'IntegrationTest/1.0',
635
+ 'account-id': account.id,
636
+ 'Content-Type': 'application/json',
637
+ },
638
+ body: JSON.stringify({ name: 'Author Updated Name' }),
639
+ })
640
+
641
+ expect(res.status).toBe(403)
642
+ })
643
+
644
+ it('should NOT be able to delete users', async () => {
645
+ const account = await createAccount({ name: 'Author Delete Test' })
646
+ const { headers: authorHeaders } = await createUserWithRole(account.id, 'AUTHOR')
647
+ const { user: targetUser } = await createUserWithRole(account.id, 'VIEWER')
648
+
649
+ const res = await app.request(`/api/users/${targetUser.id}`, {
650
+ method: 'DELETE',
651
+ headers: {
652
+ ...authorHeaders,
653
+ 'User-Agent': 'IntegrationTest/1.0',
654
+ 'account-id': account.id,
655
+ },
656
+ })
657
+
658
+ expect(res.status).toBe(403)
659
+ })
660
+
661
+ it('should NOT be able to create invitations', async () => {
662
+ const account = await createAccount({ name: 'Author Invitation Test' })
663
+ const { headers: authorHeaders } = await createUserWithRole(account.id, 'AUTHOR')
664
+
665
+ const res = await app.request('/api/invitations', {
666
+ method: 'POST',
667
+ headers: {
668
+ ...authorHeaders,
669
+ 'User-Agent': 'IntegrationTest/1.0',
670
+ 'account-id': account.id,
671
+ 'Content-Type': 'application/json',
672
+ },
673
+ body: JSON.stringify({
674
+ email: 'invited-by-author@example.com',
675
+ role: 'VIEWER',
676
+ }),
677
+ })
678
+
679
+ expect(res.status).toBe(403)
680
+ })
681
+
682
+ it('should NOT be able to view audit logs', async () => {
683
+ const account = await createAccount({ name: 'Author Audit Test' })
684
+ const { headers: authorHeaders } = await createUserWithRole(account.id, 'AUTHOR')
685
+
686
+ const res = await app.request('/api/audits', {
687
+ method: 'GET',
688
+ headers: {
689
+ ...authorHeaders,
690
+ 'User-Agent': 'IntegrationTest/1.0',
691
+ 'account-id': account.id,
692
+ },
693
+ })
694
+
695
+ expect(res.status).toBe(403)
696
+ })
697
+ })
698
+
699
+ // ============================================================================
700
+ // VIEWER ROLE TESTS
701
+ // ============================================================================
702
+
703
+ describe('VIEWER role', () => {
704
+ it('should be able to view all users', async () => {
705
+ const account = await createAccount({ name: 'Viewer View Users Test' })
706
+ const { headers } = await createUserWithRole(account.id, 'VIEWER')
707
+
708
+ const res = await app.request('/api/users', {
709
+ method: 'GET',
710
+ headers: {
711
+ ...headers,
712
+ 'User-Agent': 'IntegrationTest/1.0',
713
+ 'account-id': account.id,
714
+ },
715
+ })
716
+
717
+ expect(res.status).toBe(200)
718
+ })
719
+
720
+ it('should be able to view specific user', async () => {
721
+ const account = await createAccount({ name: 'Viewer Get User Test' })
722
+ const { user, headers } = await createUserWithRole(account.id, 'VIEWER')
723
+
724
+ const res = await app.request(`/api/users/${user.id}`, {
725
+ method: 'GET',
726
+ headers: {
727
+ ...headers,
728
+ 'User-Agent': 'IntegrationTest/1.0',
729
+ 'account-id': account.id,
730
+ },
731
+ })
732
+
733
+ expect(res.status).toBe(200)
734
+ })
735
+
736
+ it('should NOT be able to update other users', async () => {
737
+ const account = await createAccount({ name: 'Viewer Update Test' })
738
+ const { headers: viewerHeaders } = await createUserWithRole(account.id, 'VIEWER')
739
+ const { user: targetUser } = await createUserWithRole(account.id, 'AUTHOR')
740
+
741
+ const res = await app.request(`/api/users/${targetUser.id}`, {
742
+ method: 'PATCH',
743
+ headers: {
744
+ ...viewerHeaders,
745
+ 'User-Agent': 'IntegrationTest/1.0',
746
+ 'account-id': account.id,
747
+ 'Content-Type': 'application/json',
748
+ },
749
+ body: JSON.stringify({ name: 'Viewer Updated Name' }),
750
+ })
751
+
752
+ expect(res.status).toBe(403)
753
+ })
754
+
755
+ it('should NOT be able to delete users', async () => {
756
+ const account = await createAccount({ name: 'Viewer Delete Test' })
757
+ const { headers: viewerHeaders } = await createUserWithRole(account.id, 'VIEWER')
758
+ const { user: targetUser } = await createUserWithRole(account.id, 'AUTHOR')
759
+
760
+ const res = await app.request(`/api/users/${targetUser.id}`, {
761
+ method: 'DELETE',
762
+ headers: {
763
+ ...viewerHeaders,
764
+ 'User-Agent': 'IntegrationTest/1.0',
765
+ 'account-id': account.id,
766
+ },
767
+ })
768
+
769
+ expect(res.status).toBe(403)
770
+ })
771
+
772
+ it('should NOT be able to create invitations', async () => {
773
+ const account = await createAccount({ name: 'Viewer Invitation Test' })
774
+ const { headers: viewerHeaders } = await createUserWithRole(account.id, 'VIEWER')
775
+
776
+ const res = await app.request('/api/invitations', {
777
+ method: 'POST',
778
+ headers: {
779
+ ...viewerHeaders,
780
+ 'User-Agent': 'IntegrationTest/1.0',
781
+ 'account-id': account.id,
782
+ 'Content-Type': 'application/json',
783
+ },
784
+ body: JSON.stringify({
785
+ email: 'invited-by-viewer@example.com',
786
+ role: 'VIEWER',
787
+ }),
788
+ })
789
+
790
+ expect(res.status).toBe(403)
791
+ })
792
+
793
+ it('should NOT be able to view audit logs', async () => {
794
+ const account = await createAccount({ name: 'Viewer Audit Test' })
795
+ const { headers: viewerHeaders } = await createUserWithRole(account.id, 'VIEWER')
796
+
797
+ const res = await app.request('/api/audits', {
798
+ method: 'GET',
799
+ headers: {
800
+ ...viewerHeaders,
801
+ 'User-Agent': 'IntegrationTest/1.0',
802
+ 'account-id': account.id,
803
+ },
804
+ })
805
+
806
+ expect(res.status).toBe(403)
807
+ })
808
+
809
+ it('should NOT be able to view invitations', async () => {
810
+ const account = await createAccount({ name: 'Viewer List Invitations Test' })
811
+ const { headers: viewerHeaders } = await createUserWithRole(account.id, 'VIEWER')
812
+
813
+ const res = await app.request('/api/invitations', {
814
+ method: 'GET',
815
+ headers: {
816
+ ...viewerHeaders,
817
+ 'User-Agent': 'IntegrationTest/1.0',
818
+ 'account-id': account.id,
819
+ },
820
+ })
821
+
822
+ expect(res.status).toBe(403)
823
+ })
824
+ })
825
+
826
+ // ============================================================================
827
+ // BILLING ROLE TESTS (Non-hierarchical)
828
+ // ============================================================================
829
+
830
+ describe('BILLING role (non-hierarchical)', () => {
831
+ it('should be able to view users (authenticated access)', async () => {
832
+ const account = await createAccount({ name: 'Billing View Users Test' })
833
+ const { headers } = await createUserWithRole(account.id, 'BILLING')
834
+
835
+ const res = await app.request('/api/users', {
836
+ method: 'GET',
837
+ headers: {
838
+ ...headers,
839
+ 'User-Agent': 'IntegrationTest/1.0',
840
+ 'account-id': account.id,
841
+ },
842
+ })
843
+
844
+ expect(res.status).toBe(200)
845
+ })
846
+
847
+ it('should be able to view accounts (authenticated access)', async () => {
848
+ const account = await createAccount({ name: 'Billing View Accounts Test' })
849
+ const { headers } = await createUserWithRole(account.id, 'BILLING')
850
+
851
+ const res = await app.request('/api/accounts', {
852
+ method: 'GET',
853
+ headers: {
854
+ ...headers,
855
+ 'User-Agent': 'IntegrationTest/1.0',
856
+ 'account-id': account.id,
857
+ },
858
+ })
859
+
860
+ expect(res.status).toBe(200)
861
+ })
862
+
863
+ it('should NOT be able to update users', async () => {
864
+ const account = await createAccount({ name: 'Billing Update User Test' })
865
+ const { headers: billingHeaders } = await createUserWithRole(account.id, 'BILLING')
866
+ const { user: targetUser } = await createUserWithRole(account.id, 'VIEWER')
867
+
868
+ const res = await app.request(`/api/users/${targetUser.id}`, {
869
+ method: 'PATCH',
870
+ headers: {
871
+ ...billingHeaders,
872
+ 'User-Agent': 'IntegrationTest/1.0',
873
+ 'account-id': account.id,
874
+ 'Content-Type': 'application/json',
875
+ },
876
+ body: JSON.stringify({ name: 'Billing Updated Name' }),
877
+ })
878
+
879
+ expect(res.status).toBe(403)
880
+ })
881
+
882
+ it('should NOT be able to delete users', async () => {
883
+ const account = await createAccount({ name: 'Billing Delete User Test' })
884
+ const { headers: billingHeaders } = await createUserWithRole(account.id, 'BILLING')
885
+ const { user: targetUser } = await createUserWithRole(account.id, 'VIEWER')
886
+
887
+ const res = await app.request(`/api/users/${targetUser.id}`, {
888
+ method: 'DELETE',
889
+ headers: {
890
+ ...billingHeaders,
891
+ 'User-Agent': 'IntegrationTest/1.0',
892
+ 'account-id': account.id,
893
+ },
894
+ })
895
+
896
+ expect(res.status).toBe(403)
897
+ })
898
+
899
+ it('should NOT be able to create invitations', async () => {
900
+ const account = await createAccount({ name: 'Billing Invitation Test' })
901
+ const { headers: billingHeaders } = await createUserWithRole(account.id, 'BILLING')
902
+
903
+ const res = await app.request('/api/invitations', {
904
+ method: 'POST',
905
+ headers: {
906
+ ...billingHeaders,
907
+ 'User-Agent': 'IntegrationTest/1.0',
908
+ 'account-id': account.id,
909
+ 'Content-Type': 'application/json',
910
+ },
911
+ body: JSON.stringify({
912
+ email: 'invited-by-billing@example.com',
913
+ role: 'VIEWER',
914
+ }),
915
+ })
916
+
917
+ expect(res.status).toBe(403)
918
+ })
919
+
920
+ it('should NOT be able to view audit logs', async () => {
921
+ const account = await createAccount({ name: 'Billing Audit Test' })
922
+ const { headers: billingHeaders } = await createUserWithRole(account.id, 'BILLING')
923
+
924
+ const res = await app.request('/api/audits', {
925
+ method: 'GET',
926
+ headers: {
927
+ ...billingHeaders,
928
+ 'User-Agent': 'IntegrationTest/1.0',
929
+ 'account-id': account.id,
930
+ },
931
+ })
932
+
933
+ expect(res.status).toBe(403)
934
+ })
935
+
936
+ it('should NOT be able to update account', async () => {
937
+ const account = await createAccount({ name: 'Billing Account Update Test' })
938
+ const { headers: billingHeaders } = await createUserWithRole(account.id, 'BILLING')
939
+
940
+ const res = await app.request(`/api/accounts/${account.id}`, {
941
+ method: 'PATCH',
942
+ headers: {
943
+ ...billingHeaders,
944
+ 'User-Agent': 'IntegrationTest/1.0',
945
+ 'account-id': account.id,
946
+ 'Content-Type': 'application/json',
947
+ },
948
+ body: JSON.stringify({ name: 'Billing Updated Account' }),
949
+ })
950
+
951
+ expect(res.status).toBe(403)
952
+ })
953
+ })
954
+
955
+ // ============================================================================
956
+ // ANALYTICS ROLE TESTS (Non-hierarchical)
957
+ // ============================================================================
958
+
959
+ describe('ANALYTICS role (non-hierarchical)', () => {
960
+ it('should be able to view users (authenticated access)', async () => {
961
+ const account = await createAccount({ name: 'Analytics View Users Test' })
962
+ const { headers } = await createUserWithRole(account.id, 'ANALYTICS')
963
+
964
+ const res = await app.request('/api/users', {
965
+ method: 'GET',
966
+ headers: {
967
+ ...headers,
968
+ 'User-Agent': 'IntegrationTest/1.0',
969
+ 'account-id': account.id,
970
+ },
971
+ })
972
+
973
+ expect(res.status).toBe(200)
974
+ })
975
+
976
+ it('should be able to view audit logs (special permission)', async () => {
977
+ const account = await createAccount({ name: 'Analytics Audit Test' })
978
+ const { user, headers } = await createUserWithRole(account.id, 'ANALYTICS')
979
+
980
+ // Create an audit log entry
981
+ createAuditLog({
982
+ accountId: account.id,
983
+ userId: user.id,
984
+ entity: 'User',
985
+ entityId: crypto.randomUUID(),
986
+ action: 'INSERT',
987
+ })
988
+
989
+ const res = await app.request('/api/audits', {
990
+ method: 'GET',
991
+ headers: {
992
+ ...headers,
993
+ 'User-Agent': 'IntegrationTest/1.0',
994
+ 'account-id': account.id,
995
+ },
996
+ })
997
+
998
+ expect(res.status).toBe(200)
999
+ const body = await res.json()
1000
+ expect(body).toHaveProperty('data')
1001
+ })
1002
+
1003
+ it('should NOT be able to update users', async () => {
1004
+ const account = await createAccount({ name: 'Analytics Update User Test' })
1005
+ const { headers: analyticsHeaders } = await createUserWithRole(account.id, 'ANALYTICS')
1006
+ const { user: targetUser } = await createUserWithRole(account.id, 'VIEWER')
1007
+
1008
+ const res = await app.request(`/api/users/${targetUser.id}`, {
1009
+ method: 'PATCH',
1010
+ headers: {
1011
+ ...analyticsHeaders,
1012
+ 'User-Agent': 'IntegrationTest/1.0',
1013
+ 'account-id': account.id,
1014
+ 'Content-Type': 'application/json',
1015
+ },
1016
+ body: JSON.stringify({ name: 'Analytics Updated Name' }),
1017
+ })
1018
+
1019
+ expect(res.status).toBe(403)
1020
+ })
1021
+
1022
+ it('should NOT be able to delete users', async () => {
1023
+ const account = await createAccount({ name: 'Analytics Delete User Test' })
1024
+ const { headers: analyticsHeaders } = await createUserWithRole(account.id, 'ANALYTICS')
1025
+ const { user: targetUser } = await createUserWithRole(account.id, 'VIEWER')
1026
+
1027
+ const res = await app.request(`/api/users/${targetUser.id}`, {
1028
+ method: 'DELETE',
1029
+ headers: {
1030
+ ...analyticsHeaders,
1031
+ 'User-Agent': 'IntegrationTest/1.0',
1032
+ 'account-id': account.id,
1033
+ },
1034
+ })
1035
+
1036
+ expect(res.status).toBe(403)
1037
+ })
1038
+
1039
+ it('should NOT be able to create invitations', async () => {
1040
+ const account = await createAccount({ name: 'Analytics Invitation Test' })
1041
+ const { headers: analyticsHeaders } = await createUserWithRole(account.id, 'ANALYTICS')
1042
+
1043
+ const res = await app.request('/api/invitations', {
1044
+ method: 'POST',
1045
+ headers: {
1046
+ ...analyticsHeaders,
1047
+ 'User-Agent': 'IntegrationTest/1.0',
1048
+ 'account-id': account.id,
1049
+ 'Content-Type': 'application/json',
1050
+ },
1051
+ body: JSON.stringify({
1052
+ email: 'invited-by-analytics@example.com',
1053
+ role: 'VIEWER',
1054
+ }),
1055
+ })
1056
+
1057
+ expect(res.status).toBe(403)
1058
+ })
1059
+
1060
+ it('should NOT be able to update account', async () => {
1061
+ const account = await createAccount({ name: 'Analytics Account Update Test' })
1062
+ const { headers: analyticsHeaders } = await createUserWithRole(account.id, 'ANALYTICS')
1063
+
1064
+ const res = await app.request(`/api/accounts/${account.id}`, {
1065
+ method: 'PATCH',
1066
+ headers: {
1067
+ ...analyticsHeaders,
1068
+ 'User-Agent': 'IntegrationTest/1.0',
1069
+ 'account-id': account.id,
1070
+ 'Content-Type': 'application/json',
1071
+ },
1072
+ body: JSON.stringify({ name: 'Analytics Updated Account' }),
1073
+ })
1074
+
1075
+ expect(res.status).toBe(403)
1076
+ })
1077
+ })
1078
+
1079
+ // ============================================================================
1080
+ // ROLE HIERARCHY VERIFICATION TESTS
1081
+ // ============================================================================
1082
+
1083
+ describe('Role hierarchy verification', () => {
1084
+ it('should allow higher roles to access lower role endpoints (ADMIN > MANAGER > EDITOR > AUTHOR > VIEWER)', async () => {
1085
+ const account = await createAccount({ name: 'Hierarchy Test' })
1086
+
1087
+ // Create users with different roles
1088
+ const admin = await createUserWithRole(account.id, 'ADMIN')
1089
+ const manager = await createUserWithRole(account.id, 'MANAGER')
1090
+ const editor = await createUserWithRole(account.id, 'EDITOR')
1091
+ const author = await createUserWithRole(account.id, 'AUTHOR')
1092
+ const viewer = await createUserWithRole(account.id, 'VIEWER')
1093
+
1094
+ // Test user list (all should have access)
1095
+ for (const { headers, user } of [admin, manager, editor, author, viewer]) {
1096
+ const res = await app.request('/api/users', {
1097
+ method: 'GET',
1098
+ headers: {
1099
+ ...headers,
1100
+ 'User-Agent': 'IntegrationTest/1.0',
1101
+ 'account-id': account.id,
1102
+ },
1103
+ })
1104
+ expect(res.status).toBe(200)
1105
+ }
1106
+
1107
+ // Test update (only ADMIN and MANAGER should have access)
1108
+ const testTarget = await createUser({ email: 'update-target@example.com', name: 'Target' })
1109
+ await addUserToAccount(testTarget.id, account.id, 'VIEWER')
1110
+
1111
+ // Admin should succeed
1112
+ const adminUpdateRes = await app.request(`/api/users/${testTarget.id}`, {
1113
+ method: 'PATCH',
1114
+ headers: {
1115
+ ...admin.headers,
1116
+ 'User-Agent': 'IntegrationTest/1.0',
1117
+ 'account-id': account.id,
1118
+ 'Content-Type': 'application/json',
1119
+ },
1120
+ body: JSON.stringify({ name: 'Admin Updated' }),
1121
+ })
1122
+ expect(adminUpdateRes.status).toBe(200)
1123
+
1124
+ // Manager should succeed
1125
+ const managerUpdateRes = await app.request(`/api/users/${testTarget.id}`, {
1126
+ method: 'PATCH',
1127
+ headers: {
1128
+ ...manager.headers,
1129
+ 'User-Agent': 'IntegrationTest/1.0',
1130
+ 'account-id': account.id,
1131
+ 'Content-Type': 'application/json',
1132
+ },
1133
+ body: JSON.stringify({ name: 'Manager Updated' }),
1134
+ })
1135
+ expect(managerUpdateRes.status).toBe(200)
1136
+
1137
+ // Editor, Author, Viewer should fail
1138
+ for (const { headers } of [editor, author, viewer]) {
1139
+ const res = await app.request(`/api/users/${testTarget.id}`, {
1140
+ method: 'PATCH',
1141
+ headers: {
1142
+ ...headers,
1143
+ 'User-Agent': 'IntegrationTest/1.0',
1144
+ 'account-id': account.id,
1145
+ 'Content-Type': 'application/json',
1146
+ },
1147
+ body: JSON.stringify({ name: 'Should Fail' }),
1148
+ })
1149
+ expect(res.status).toBe(403)
1150
+ }
1151
+ })
1152
+
1153
+ it('should enforce that non-hierarchical roles (BILLING, ANALYTICS) do not inherit from hierarchy', async () => {
1154
+ const account = await createAccount({ name: 'Non-Hierarchical Test' })
1155
+
1156
+ const billing = await createUserWithRole(account.id, 'BILLING')
1157
+ const analytics = await createUserWithRole(account.id, 'ANALYTICS')
1158
+ const testTarget = await createUser({ email: 'nonhier-target@example.com', name: 'Target' })
1159
+ await addUserToAccount(testTarget.id, account.id, 'VIEWER')
1160
+
1161
+ // Both BILLING and ANALYTICS should NOT have MANAGER or ADMIN powers
1162
+
1163
+ // BILLING should not be able to update users
1164
+ const billingUpdateRes = await app.request(`/api/users/${testTarget.id}`, {
1165
+ method: 'PATCH',
1166
+ headers: {
1167
+ ...billing.headers,
1168
+ 'User-Agent': 'IntegrationTest/1.0',
1169
+ 'account-id': account.id,
1170
+ 'Content-Type': 'application/json',
1171
+ },
1172
+ body: JSON.stringify({ name: 'Billing Update' }),
1173
+ })
1174
+ expect(billingUpdateRes.status).toBe(403)
1175
+
1176
+ // ANALYTICS should not be able to update users
1177
+ const analyticsUpdateRes = await app.request(`/api/users/${testTarget.id}`, {
1178
+ method: 'PATCH',
1179
+ headers: {
1180
+ ...analytics.headers,
1181
+ 'User-Agent': 'IntegrationTest/1.0',
1182
+ 'account-id': account.id,
1183
+ 'Content-Type': 'application/json',
1184
+ },
1185
+ body: JSON.stringify({ name: 'Analytics Update' }),
1186
+ })
1187
+ expect(analyticsUpdateRes.status).toBe(403)
1188
+
1189
+ // BILLING should not be able to create invitations
1190
+ const billingInviteRes = await app.request('/api/invitations', {
1191
+ method: 'POST',
1192
+ headers: {
1193
+ ...billing.headers,
1194
+ 'User-Agent': 'IntegrationTest/1.0',
1195
+ 'account-id': account.id,
1196
+ 'Content-Type': 'application/json',
1197
+ },
1198
+ body: JSON.stringify({ email: 'test@example.com', role: 'VIEWER' }),
1199
+ })
1200
+ expect(billingInviteRes.status).toBe(403)
1201
+
1202
+ // ANALYTICS should not be able to create invitations
1203
+ const analyticsInviteRes = await app.request('/api/invitations', {
1204
+ method: 'POST',
1205
+ headers: {
1206
+ ...analytics.headers,
1207
+ 'User-Agent': 'IntegrationTest/1.0',
1208
+ 'account-id': account.id,
1209
+ 'Content-Type': 'application/json',
1210
+ },
1211
+ body: JSON.stringify({ email: 'test@example.com', role: 'VIEWER' }),
1212
+ })
1213
+ expect(analyticsInviteRes.status).toBe(403)
1214
+ })
1215
+
1216
+ it('should allow ANALYTICS role special access to audit logs while BILLING cannot', async () => {
1217
+ const account = await createAccount({ name: 'Analytics vs Billing Audit Test' })
1218
+
1219
+ const analytics = await createUserWithRole(account.id, 'ANALYTICS')
1220
+ const billing = await createUserWithRole(account.id, 'BILLING')
1221
+
1222
+ // Create an audit log
1223
+ createAuditLog({
1224
+ accountId: account.id,
1225
+ userId: analytics.user.id,
1226
+ entity: 'User',
1227
+ entityId: crypto.randomUUID(),
1228
+ action: 'INSERT',
1229
+ })
1230
+
1231
+ // ANALYTICS should be able to view audits
1232
+ const analyticsAuditRes = await app.request('/api/audits', {
1233
+ method: 'GET',
1234
+ headers: {
1235
+ ...analytics.headers,
1236
+ 'User-Agent': 'IntegrationTest/1.0',
1237
+ 'account-id': account.id,
1238
+ },
1239
+ })
1240
+ expect(analyticsAuditRes.status).toBe(200)
1241
+
1242
+ // BILLING should NOT be able to view audits
1243
+ const billingAuditRes = await app.request('/api/audits', {
1244
+ method: 'GET',
1245
+ headers: {
1246
+ ...billing.headers,
1247
+ 'User-Agent': 'IntegrationTest/1.0',
1248
+ 'account-id': account.id,
1249
+ },
1250
+ })
1251
+ expect(billingAuditRes.status).toBe(403)
1252
+ })
1253
+ })
1254
+
1255
+ // ============================================================================
1256
+ // PERMISSION MATRIX VERIFICATION TESTS
1257
+ // ============================================================================
1258
+
1259
+ describe('Permission matrix verification', () => {
1260
+ const roleOperationMatrix: {
1261
+ role: Role
1262
+ operation: string
1263
+ endpoint: string
1264
+ method: 'GET' | 'POST' | 'PATCH' | 'DELETE'
1265
+ shouldSucceed: boolean
1266
+ body?: Record<string, unknown>
1267
+ }[] = [
1268
+ // ADMIN - should have access to everything
1269
+ { role: 'ADMIN', operation: 'View users', endpoint: '/api/users', method: 'GET', shouldSucceed: true },
1270
+ { role: 'ADMIN', operation: 'View invitations', endpoint: '/api/invitations', method: 'GET', shouldSucceed: true },
1271
+ { role: 'ADMIN', operation: 'View audits', endpoint: '/api/audits', method: 'GET', shouldSucceed: true },
1272
+
1273
+ // MANAGER - limited management access
1274
+ { role: 'MANAGER', operation: 'View users', endpoint: '/api/users', method: 'GET', shouldSucceed: true },
1275
+ { role: 'MANAGER', operation: 'View invitations', endpoint: '/api/invitations', method: 'GET', shouldSucceed: true },
1276
+ { role: 'MANAGER', operation: 'View audits', endpoint: '/api/audits', method: 'GET', shouldSucceed: false },
1277
+
1278
+ // EDITOR - read + content access
1279
+ { role: 'EDITOR', operation: 'View users', endpoint: '/api/users', method: 'GET', shouldSucceed: true },
1280
+ { role: 'EDITOR', operation: 'View invitations', endpoint: '/api/invitations', method: 'GET', shouldSucceed: false },
1281
+ { role: 'EDITOR', operation: 'View audits', endpoint: '/api/audits', method: 'GET', shouldSucceed: false },
1282
+
1283
+ // AUTHOR - own content access
1284
+ { role: 'AUTHOR', operation: 'View users', endpoint: '/api/users', method: 'GET', shouldSucceed: true },
1285
+ { role: 'AUTHOR', operation: 'View invitations', endpoint: '/api/invitations', method: 'GET', shouldSucceed: false },
1286
+ { role: 'AUTHOR', operation: 'View audits', endpoint: '/api/audits', method: 'GET', shouldSucceed: false },
1287
+
1288
+ // VIEWER - read-only access
1289
+ { role: 'VIEWER', operation: 'View users', endpoint: '/api/users', method: 'GET', shouldSucceed: true },
1290
+ { role: 'VIEWER', operation: 'View invitations', endpoint: '/api/invitations', method: 'GET', shouldSucceed: false },
1291
+ { role: 'VIEWER', operation: 'View audits', endpoint: '/api/audits', method: 'GET', shouldSucceed: false },
1292
+
1293
+ // BILLING - billing-only access
1294
+ { role: 'BILLING', operation: 'View users', endpoint: '/api/users', method: 'GET', shouldSucceed: true },
1295
+ { role: 'BILLING', operation: 'View invitations', endpoint: '/api/invitations', method: 'GET', shouldSucceed: false },
1296
+ { role: 'BILLING', operation: 'View audits', endpoint: '/api/audits', method: 'GET', shouldSucceed: false },
1297
+
1298
+ // ANALYTICS - analytics/audit access
1299
+ { role: 'ANALYTICS', operation: 'View users', endpoint: '/api/users', method: 'GET', shouldSucceed: true },
1300
+ { role: 'ANALYTICS', operation: 'View invitations', endpoint: '/api/invitations', method: 'GET', shouldSucceed: false },
1301
+ { role: 'ANALYTICS', operation: 'View audits', endpoint: '/api/audits', method: 'GET', shouldSucceed: true },
1302
+ ]
1303
+
1304
+ for (const testCase of roleOperationMatrix) {
1305
+ it(`${testCase.role} ${testCase.shouldSucceed ? 'CAN' : 'CANNOT'} ${testCase.operation}`, async () => {
1306
+ const account = await createAccount({ name: `Matrix Test ${testCase.role} ${testCase.operation}` })
1307
+ const { user, headers } = await createUserWithRole(account.id, testCase.role)
1308
+
1309
+ // Create audit log for audit tests
1310
+ if (testCase.endpoint === '/api/audits') {
1311
+ createAuditLog({
1312
+ accountId: account.id,
1313
+ userId: user.id,
1314
+ entity: 'Test',
1315
+ entityId: crypto.randomUUID(),
1316
+ action: 'INSERT',
1317
+ })
1318
+ }
1319
+
1320
+ const requestInit: RequestInit = {
1321
+ method: testCase.method,
1322
+ headers: {
1323
+ ...headers,
1324
+ 'User-Agent': 'IntegrationTest/1.0',
1325
+ 'account-id': account.id,
1326
+ },
1327
+ }
1328
+
1329
+ if (testCase.body) {
1330
+ requestInit.headers = {
1331
+ ...requestInit.headers,
1332
+ 'Content-Type': 'application/json',
1333
+ }
1334
+ requestInit.body = JSON.stringify(testCase.body)
1335
+ }
1336
+
1337
+ const res = await app.request(testCase.endpoint, requestInit)
1338
+
1339
+ if (testCase.shouldSucceed) {
1340
+ expect(res.status).toBe(200)
1341
+ } else {
1342
+ expect(res.status).toBe(403)
1343
+ }
1344
+ })
1345
+ }
1346
+ })
1347
+ })