@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,1026 @@
1
+ /**
2
+ * ANALYTICS Role Authorization Integration Tests
3
+ *
4
+ * Tests the ANALYTICS role which is a non-hierarchical (specialized) role
5
+ * designed specifically for analytics, audit log access, and report generation.
6
+ *
7
+ * ANALYTICS Role Characteristics:
8
+ * - Non-hierarchical: Level -1 (does not inherit from VIEWER or any other role)
9
+ * - Permissions: VIEW_ANALYTICS, EXPORT_REPORTS only
10
+ * - Can: view audit logs, access analytics endpoints, view users (authenticated access)
11
+ * - Cannot: manage users, update accounts, create invitations, manage billing
12
+ *
13
+ * Note: Analytics-specific endpoints (e.g., /api/analytics/*) are not yet implemented.
14
+ * These tests focus on verifying the role's access to audit logs and restrictions
15
+ * to other endpoints, documenting expected behavior for future analytics endpoints.
16
+ */
17
+
18
+ import { describe, it, expect, beforeAll } from 'vitest'
19
+ import { Hono } from 'hono'
20
+ import { getEnv, getDb, getSqlite, type TestEnv } from '../setup'
21
+ import {
22
+ createUser,
23
+ createDeletedUser,
24
+ createUserSession,
25
+ createAccount,
26
+ addUserToAccount,
27
+ type Role,
28
+ } from '../fixtures'
29
+ import type { HonoEnv } from '../../../src/server/types'
30
+ import { api } from '../../../src/server/routes'
31
+ import { errorHandler } from '../../../src/server/middleware/error-handler'
32
+ import { sessionMiddleware } from '../../../src/server/lib/session'
33
+ import { hasPermission, Permission } from '../../../src/server/auth/permissions'
34
+ import { hasMinimumRole, isHierarchicalRole, ROLE_HIERARCHY } from '../../../src/server/auth/roles'
35
+
36
+ // ============================================================================
37
+ // TEST SETUP
38
+ // ============================================================================
39
+
40
+ /**
41
+ * Creates a database wrapper that adds the `execute` method
42
+ * The better-sqlite3 drizzle doesn't have execute, but D1 does
43
+ */
44
+ function createTestDb() {
45
+ const db = getDb()
46
+ return new Proxy(db, {
47
+ get(target, prop) {
48
+ if (prop === 'execute') {
49
+ return target.run.bind(target)
50
+ }
51
+ return (target as any)[prop]
52
+ },
53
+ })
54
+ }
55
+
56
+ /**
57
+ * Helper to create a user with a specific role in an account
58
+ */
59
+ async function createUserWithRole(
60
+ accountId: string,
61
+ role: Role,
62
+ options?: { email?: string; name?: string }
63
+ ): Promise<{
64
+ user: Awaited<ReturnType<typeof createUser>>
65
+ sessionId: string
66
+ headers: Record<string, string>
67
+ }> {
68
+ const user = await createUser({
69
+ email: options?.email ?? `${role.toLowerCase()}-user-${crypto.randomUUID().slice(0, 8)}@example.com`,
70
+ name: options?.name ?? `${role} User`,
71
+ })
72
+
73
+ await addUserToAccount(user.id, accountId, role)
74
+
75
+ const { sessionId, headers } = await createUserSession(user.id, {
76
+ email: user.email,
77
+ name: user.name,
78
+ })
79
+
80
+ return { user, sessionId, headers }
81
+ }
82
+
83
+ /**
84
+ * Helper to create an audit log entry directly in the database
85
+ */
86
+ function createAuditLog(options: {
87
+ accountId: string
88
+ userId: string
89
+ entity: string
90
+ entityId: string
91
+ action: 'INSERT' | 'UPDATE' | 'DELETE' | 'LOGIN' | 'LOGOUT' | 'SIGNUP' | 'TOKEN_REFRESH' | 'LOGIN_FAILED'
92
+ changes?: Record<string, unknown> | null
93
+ timestamp?: string
94
+ }) {
95
+ const sqlite = getSqlite()
96
+ const id = crypto.randomUUID()
97
+ const transactionId = crypto.randomUUID()
98
+ const timestamp = options.timestamp ?? new Date().toISOString()
99
+
100
+ sqlite.prepare(`
101
+ INSERT INTO audit_logs (id, transaction_id, account_id, user_id, entity, entity_id, action, changes, ip_address, user_agent, timestamp)
102
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
103
+ `).run(
104
+ id,
105
+ transactionId,
106
+ options.accountId,
107
+ options.userId,
108
+ options.entity,
109
+ options.entityId,
110
+ options.action,
111
+ options.changes ? JSON.stringify(options.changes) : null,
112
+ '127.0.0.1',
113
+ 'IntegrationTest/1.0',
114
+ timestamp
115
+ )
116
+
117
+ return { id, transactionId, timestamp }
118
+ }
119
+
120
+ // ============================================================================
121
+ // TESTS
122
+ // ============================================================================
123
+
124
+ describe('ANALYTICS Role Authorization', () => {
125
+ let app: Hono<HonoEnv>
126
+ let env: TestEnv
127
+
128
+ beforeAll(() => {
129
+ env = getEnv()
130
+ app = new Hono<HonoEnv>()
131
+
132
+ // Set up error handler
133
+ app.onError(errorHandler)
134
+
135
+ // Set up middleware to inject test environment
136
+ app.use('*', async (c, next) => {
137
+ // Inject environment bindings
138
+ ;(c as any).env = env
139
+
140
+ // Set up database
141
+ const db = createTestDb()
142
+ c.set('db', db)
143
+
144
+ // Set up request context variables
145
+ c.set('transactionId', crypto.randomUUID())
146
+ c.set('ip', '127.0.0.1')
147
+ c.set('userAgent', 'IntegrationTest/1.0')
148
+
149
+ await next()
150
+ })
151
+
152
+ // Session middleware - reads session from KV and sets sessionData in context
153
+ app.use('*', sessionMiddleware())
154
+
155
+ // Mount API routes (includes sessionAuth and accountMiddleware)
156
+ app.route('/api', api)
157
+ })
158
+
159
+ // ============================================================================
160
+ // ROLE CHARACTERISTICS UNIT TESTS
161
+ // ============================================================================
162
+
163
+ describe('Role characteristics', () => {
164
+ it('should be defined as a non-hierarchical role (level -1)', () => {
165
+ expect(ROLE_HIERARCHY.ANALYTICS).toBe(-1)
166
+ })
167
+
168
+ it('should NOT be a hierarchical role', () => {
169
+ expect(isHierarchicalRole('ANALYTICS')).toBe(false)
170
+ })
171
+
172
+ it('should have VIEW_ANALYTICS permission', () => {
173
+ expect(hasPermission('ANALYTICS', Permission.VIEW_ANALYTICS)).toBe(true)
174
+ })
175
+
176
+ it('should have EXPORT_REPORTS permission', () => {
177
+ expect(hasPermission('ANALYTICS', Permission.EXPORT_REPORTS)).toBe(true)
178
+ })
179
+
180
+ it('should NOT have MANAGE_ALL_USERS permission', () => {
181
+ expect(hasPermission('ANALYTICS', Permission.MANAGE_ALL_USERS)).toBe(false)
182
+ })
183
+
184
+ it('should NOT have MANAGE_TEAM_USERS permission', () => {
185
+ expect(hasPermission('ANALYTICS', Permission.MANAGE_TEAM_USERS)).toBe(false)
186
+ })
187
+
188
+ it('should NOT have VIEW_ALL_USERS permission', () => {
189
+ expect(hasPermission('ANALYTICS', Permission.VIEW_ALL_USERS)).toBe(false)
190
+ })
191
+
192
+ it('should NOT have MANAGE_TENANT_SETTINGS permission', () => {
193
+ expect(hasPermission('ANALYTICS', Permission.MANAGE_TENANT_SETTINGS)).toBe(false)
194
+ })
195
+
196
+ it('should NOT have MANAGE_BILLING permission', () => {
197
+ expect(hasPermission('ANALYTICS', Permission.MANAGE_BILLING)).toBe(false)
198
+ })
199
+
200
+ it('should NOT have VIEW_BILLING permission', () => {
201
+ expect(hasPermission('ANALYTICS', Permission.VIEW_BILLING)).toBe(false)
202
+ })
203
+
204
+ it('should NOT have content permissions (CREATE_CONTENT, EDIT_OWN_CONTENT, etc.)', () => {
205
+ expect(hasPermission('ANALYTICS', Permission.CREATE_CONTENT)).toBe(false)
206
+ expect(hasPermission('ANALYTICS', Permission.EDIT_OWN_CONTENT)).toBe(false)
207
+ expect(hasPermission('ANALYTICS', Permission.EDIT_ALL_CONTENT)).toBe(false)
208
+ expect(hasPermission('ANALYTICS', Permission.PUBLISH_CONTENT)).toBe(false)
209
+ expect(hasPermission('ANALYTICS', Permission.DELETE_CONTENT)).toBe(false)
210
+ })
211
+ })
212
+
213
+ // ============================================================================
214
+ // NON-HIERARCHICAL BEHAVIOR TESTS
215
+ // ============================================================================
216
+
217
+ describe('Non-hierarchical role behavior', () => {
218
+ it('should NOT satisfy VIEWER minimum role requirement', () => {
219
+ expect(hasMinimumRole('ANALYTICS', 'VIEWER')).toBe(false)
220
+ })
221
+
222
+ it('should NOT satisfy AUTHOR minimum role requirement', () => {
223
+ expect(hasMinimumRole('ANALYTICS', 'AUTHOR')).toBe(false)
224
+ })
225
+
226
+ it('should NOT satisfy EDITOR minimum role requirement', () => {
227
+ expect(hasMinimumRole('ANALYTICS', 'EDITOR')).toBe(false)
228
+ })
229
+
230
+ it('should NOT satisfy MANAGER minimum role requirement', () => {
231
+ expect(hasMinimumRole('ANALYTICS', 'MANAGER')).toBe(false)
232
+ })
233
+
234
+ it('should NOT satisfy ADMIN minimum role requirement', () => {
235
+ expect(hasMinimumRole('ANALYTICS', 'ADMIN')).toBe(false)
236
+ })
237
+
238
+ it('should only satisfy its own role requirement exactly', () => {
239
+ expect(hasMinimumRole('ANALYTICS', 'ANALYTICS')).toBe(true)
240
+ })
241
+
242
+ it('should NOT grant access when other non-hierarchical roles are required', () => {
243
+ expect(hasMinimumRole('ANALYTICS', 'BILLING')).toBe(false)
244
+ })
245
+
246
+ it('should grant access when explicitly added to additionalRoles', () => {
247
+ // ANALYTICS can access if explicitly added to additionalRoles for ADMIN requirement
248
+ expect(hasMinimumRole('ANALYTICS', 'ADMIN', ['ANALYTICS'])).toBe(true)
249
+ })
250
+ })
251
+
252
+ // ============================================================================
253
+ // API ACCESS TESTS - ALLOWED OPERATIONS
254
+ // ============================================================================
255
+
256
+ describe('Allowed operations', () => {
257
+ describe('Audit log access', () => {
258
+ it('should be able to view audit logs', async () => {
259
+ const account = await createAccount({ name: 'Analytics Audit Test' })
260
+ const { user, headers: analyticsHeaders } = await createUserWithRole(account.id, 'ANALYTICS')
261
+
262
+ // Create an audit log entry
263
+ createAuditLog({
264
+ accountId: account.id,
265
+ userId: user.id,
266
+ entity: 'User',
267
+ entityId: crypto.randomUUID(),
268
+ action: 'INSERT',
269
+ })
270
+
271
+ const res = await app.request('/api/audits', {
272
+ method: 'GET',
273
+ headers: {
274
+ ...analyticsHeaders,
275
+ 'User-Agent': 'IntegrationTest/1.0',
276
+ 'account-id': account.id,
277
+ },
278
+ })
279
+
280
+ expect(res.status).toBe(200)
281
+ const body = await res.json()
282
+ expect(body).toHaveProperty('data')
283
+ expect(Array.isArray(body.data)).toBe(true)
284
+ })
285
+
286
+ it('should be able to filter audit logs by entity', async () => {
287
+ const account = await createAccount({ name: 'Analytics Audit Filter Test' })
288
+ const { user, headers: analyticsHeaders } = await createUserWithRole(account.id, 'ANALYTICS')
289
+
290
+ // Create audit log entries for different entities
291
+ createAuditLog({
292
+ accountId: account.id,
293
+ userId: user.id,
294
+ entity: 'User',
295
+ entityId: crypto.randomUUID(),
296
+ action: 'INSERT',
297
+ })
298
+
299
+ createAuditLog({
300
+ accountId: account.id,
301
+ userId: user.id,
302
+ entity: 'Account',
303
+ entityId: crypto.randomUUID(),
304
+ action: 'UPDATE',
305
+ })
306
+
307
+ const res = await app.request('/api/audits?entity=User', {
308
+ method: 'GET',
309
+ headers: {
310
+ ...analyticsHeaders,
311
+ 'User-Agent': 'IntegrationTest/1.0',
312
+ 'account-id': account.id,
313
+ },
314
+ })
315
+
316
+ expect(res.status).toBe(200)
317
+ const body = await res.json()
318
+ expect(body).toHaveProperty('data')
319
+ expect(Array.isArray(body.data)).toBe(true)
320
+ // All returned logs should be for 'User' entity
321
+ for (const log of body.data) {
322
+ expect(log.entity).toBe('User')
323
+ }
324
+ })
325
+
326
+ it('should be able to filter audit logs by action', async () => {
327
+ const account = await createAccount({ name: 'Analytics Audit Action Filter Test' })
328
+ const { user, headers: analyticsHeaders } = await createUserWithRole(account.id, 'ANALYTICS')
329
+
330
+ // Create audit log entries for different actions
331
+ createAuditLog({
332
+ accountId: account.id,
333
+ userId: user.id,
334
+ entity: 'User',
335
+ entityId: crypto.randomUUID(),
336
+ action: 'INSERT',
337
+ })
338
+
339
+ createAuditLog({
340
+ accountId: account.id,
341
+ userId: user.id,
342
+ entity: 'User',
343
+ entityId: crypto.randomUUID(),
344
+ action: 'UPDATE',
345
+ })
346
+
347
+ const res = await app.request('/api/audits?action=INSERT', {
348
+ method: 'GET',
349
+ headers: {
350
+ ...analyticsHeaders,
351
+ 'User-Agent': 'IntegrationTest/1.0',
352
+ 'account-id': account.id,
353
+ },
354
+ })
355
+
356
+ expect(res.status).toBe(200)
357
+ const body = await res.json()
358
+ expect(body).toHaveProperty('data')
359
+ expect(Array.isArray(body.data)).toBe(true)
360
+ // All returned logs should be INSERT actions
361
+ for (const log of body.data) {
362
+ expect(log.action).toBe('INSERT')
363
+ }
364
+ })
365
+ })
366
+
367
+ describe('Authenticated access (view only)', () => {
368
+ it('should be able to view users list', async () => {
369
+ const account = await createAccount({ name: 'Analytics View Users Test' })
370
+ const { headers } = await createUserWithRole(account.id, 'ANALYTICS')
371
+
372
+ const res = await app.request('/api/users', {
373
+ method: 'GET',
374
+ headers: {
375
+ ...headers,
376
+ 'User-Agent': 'IntegrationTest/1.0',
377
+ 'account-id': account.id,
378
+ },
379
+ })
380
+
381
+ expect(res.status).toBe(200)
382
+ const body = await res.json()
383
+ expect(body).toHaveProperty('data')
384
+ expect(Array.isArray(body.data)).toBe(true)
385
+ })
386
+
387
+ it('should be able to view a specific user', async () => {
388
+ const account = await createAccount({ name: 'Analytics View User Test' })
389
+ const { user, headers } = await createUserWithRole(account.id, 'ANALYTICS')
390
+
391
+ const res = await app.request(`/api/users/${user.id}`, {
392
+ method: 'GET',
393
+ headers: {
394
+ ...headers,
395
+ 'User-Agent': 'IntegrationTest/1.0',
396
+ 'account-id': account.id,
397
+ },
398
+ })
399
+
400
+ expect(res.status).toBe(200)
401
+ const body = await res.json()
402
+ expect(body.data.id).toBe(user.id)
403
+ })
404
+
405
+ it('should be able to view accounts list', async () => {
406
+ const account = await createAccount({ name: 'Analytics View Accounts Test' })
407
+ const { headers } = await createUserWithRole(account.id, 'ANALYTICS')
408
+
409
+ const res = await app.request('/api/accounts', {
410
+ method: 'GET',
411
+ headers: {
412
+ ...headers,
413
+ 'User-Agent': 'IntegrationTest/1.0',
414
+ 'account-id': account.id,
415
+ },
416
+ })
417
+
418
+ expect(res.status).toBe(200)
419
+ const body = await res.json()
420
+ expect(body).toHaveProperty('data')
421
+ })
422
+
423
+ it('should be able to view a specific account', async () => {
424
+ const account = await createAccount({ name: 'Analytics View Account Test' })
425
+ const { headers } = await createUserWithRole(account.id, 'ANALYTICS')
426
+
427
+ const res = await app.request(`/api/accounts/${account.id}`, {
428
+ method: 'GET',
429
+ headers: {
430
+ ...headers,
431
+ 'User-Agent': 'IntegrationTest/1.0',
432
+ 'account-id': account.id,
433
+ },
434
+ })
435
+
436
+ expect(res.status).toBe(200)
437
+ const body = await res.json()
438
+ expect(body.data.id).toBe(account.id)
439
+ })
440
+ })
441
+ })
442
+
443
+ // ============================================================================
444
+ // API ACCESS TESTS - FORBIDDEN OPERATIONS
445
+ // ============================================================================
446
+
447
+ describe('Forbidden operations', () => {
448
+ describe('User management', () => {
449
+ it('should NOT be able to update other users', async () => {
450
+ const account = await createAccount({ name: 'Analytics Update User Test' })
451
+ const { headers: analyticsHeaders } = await createUserWithRole(account.id, 'ANALYTICS')
452
+ const { user: targetUser } = await createUserWithRole(account.id, 'VIEWER')
453
+
454
+ const res = await app.request(`/api/users/${targetUser.id}`, {
455
+ method: 'PATCH',
456
+ headers: {
457
+ ...analyticsHeaders,
458
+ 'User-Agent': 'IntegrationTest/1.0',
459
+ 'account-id': account.id,
460
+ 'Content-Type': 'application/json',
461
+ },
462
+ body: JSON.stringify({ name: 'Analytics Updated Name' }),
463
+ })
464
+
465
+ expect(res.status).toBe(403)
466
+ })
467
+
468
+ it('should NOT be able to delete users (soft delete)', async () => {
469
+ const account = await createAccount({ name: 'Analytics Delete User Test' })
470
+ const { headers: analyticsHeaders } = await createUserWithRole(account.id, 'ANALYTICS')
471
+ const { user: targetUser } = await createUserWithRole(account.id, 'VIEWER')
472
+
473
+ const res = await app.request(`/api/users/${targetUser.id}`, {
474
+ method: 'DELETE',
475
+ headers: {
476
+ ...analyticsHeaders,
477
+ 'User-Agent': 'IntegrationTest/1.0',
478
+ 'account-id': account.id,
479
+ },
480
+ })
481
+
482
+ expect(res.status).toBe(403)
483
+ })
484
+
485
+ it('should NOT be able to restore soft-deleted users', async () => {
486
+ const account = await createAccount({ name: 'Analytics Restore User Test' })
487
+ const { headers: analyticsHeaders } = await createUserWithRole(account.id, 'ANALYTICS')
488
+
489
+ const deletedUser = await createDeletedUser({
490
+ email: 'deleted-for-analytics@example.com',
491
+ name: 'Deleted For Analytics',
492
+ })
493
+ await addUserToAccount(deletedUser.id, account.id, 'VIEWER')
494
+
495
+ const res = await app.request(`/api/users/${deletedUser.id}/restore`, {
496
+ method: 'POST',
497
+ headers: {
498
+ ...analyticsHeaders,
499
+ 'User-Agent': 'IntegrationTest/1.0',
500
+ 'account-id': account.id,
501
+ },
502
+ })
503
+
504
+ expect(res.status).toBe(403)
505
+ })
506
+ })
507
+
508
+ describe('Account management', () => {
509
+ it('should NOT be able to update account settings', async () => {
510
+ const account = await createAccount({ name: 'Analytics Account Update Test' })
511
+ const { headers: analyticsHeaders } = await createUserWithRole(account.id, 'ANALYTICS')
512
+
513
+ const res = await app.request(`/api/accounts/${account.id}`, {
514
+ method: 'PATCH',
515
+ headers: {
516
+ ...analyticsHeaders,
517
+ 'User-Agent': 'IntegrationTest/1.0',
518
+ 'account-id': account.id,
519
+ 'Content-Type': 'application/json',
520
+ },
521
+ body: JSON.stringify({ name: 'Analytics Updated Account' }),
522
+ })
523
+
524
+ expect(res.status).toBe(403)
525
+ })
526
+
527
+ it('should NOT be able to delete accounts', async () => {
528
+ const account = await createAccount({ name: 'Analytics Delete Account Test' })
529
+ const { headers: analyticsHeaders } = await createUserWithRole(account.id, 'ANALYTICS')
530
+
531
+ const res = await app.request(`/api/accounts/${account.id}`, {
532
+ method: 'DELETE',
533
+ headers: {
534
+ ...analyticsHeaders,
535
+ 'User-Agent': 'IntegrationTest/1.0',
536
+ 'account-id': account.id,
537
+ },
538
+ })
539
+
540
+ expect(res.status).toBe(403)
541
+ })
542
+ })
543
+
544
+ describe('Invitation management', () => {
545
+ it('should NOT be able to create invitations', async () => {
546
+ const account = await createAccount({ name: 'Analytics Create Invitation Test' })
547
+ const { headers: analyticsHeaders } = await createUserWithRole(account.id, 'ANALYTICS')
548
+
549
+ const res = await app.request('/api/invitations', {
550
+ method: 'POST',
551
+ headers: {
552
+ ...analyticsHeaders,
553
+ 'User-Agent': 'IntegrationTest/1.0',
554
+ 'account-id': account.id,
555
+ 'Content-Type': 'application/json',
556
+ },
557
+ body: JSON.stringify({
558
+ email: 'invited-by-analytics@example.com',
559
+ role: 'VIEWER',
560
+ }),
561
+ })
562
+
563
+ expect(res.status).toBe(403)
564
+ })
565
+
566
+ it('should NOT be able to list invitations', async () => {
567
+ const account = await createAccount({ name: 'Analytics List Invitations Test' })
568
+ const { headers: analyticsHeaders } = await createUserWithRole(account.id, 'ANALYTICS')
569
+
570
+ const res = await app.request('/api/invitations', {
571
+ method: 'GET',
572
+ headers: {
573
+ ...analyticsHeaders,
574
+ 'User-Agent': 'IntegrationTest/1.0',
575
+ 'account-id': account.id,
576
+ },
577
+ })
578
+
579
+ expect(res.status).toBe(403)
580
+ })
581
+ })
582
+ })
583
+
584
+ // ============================================================================
585
+ // COMPARISON WITH OTHER ROLES
586
+ // ============================================================================
587
+
588
+ describe('Comparison with other roles', () => {
589
+ it('should have same user view access as VIEWER', async () => {
590
+ const account = await createAccount({ name: 'Analytics vs Viewer Test' })
591
+ const { headers: analyticsHeaders } = await createUserWithRole(account.id, 'ANALYTICS')
592
+ const { headers: viewerHeaders } = await createUserWithRole(account.id, 'VIEWER')
593
+
594
+ const analyticsRes = await app.request('/api/users', {
595
+ method: 'GET',
596
+ headers: {
597
+ ...analyticsHeaders,
598
+ 'User-Agent': 'IntegrationTest/1.0',
599
+ 'account-id': account.id,
600
+ },
601
+ })
602
+
603
+ const viewerRes = await app.request('/api/users', {
604
+ method: 'GET',
605
+ headers: {
606
+ ...viewerHeaders,
607
+ 'User-Agent': 'IntegrationTest/1.0',
608
+ 'account-id': account.id,
609
+ },
610
+ })
611
+
612
+ expect(analyticsRes.status).toBe(200)
613
+ expect(viewerRes.status).toBe(200)
614
+ })
615
+
616
+ it('should have access to audit logs unlike BILLING role', async () => {
617
+ const account = await createAccount({ name: 'Analytics vs Billing Audit Test' })
618
+ const { user: analyticsUser, headers: analyticsHeaders } = await createUserWithRole(account.id, 'ANALYTICS')
619
+ const { headers: billingHeaders } = await createUserWithRole(account.id, 'BILLING')
620
+
621
+ // Create an audit log
622
+ createAuditLog({
623
+ accountId: account.id,
624
+ userId: analyticsUser.id,
625
+ entity: 'User',
626
+ entityId: crypto.randomUUID(),
627
+ action: 'INSERT',
628
+ })
629
+
630
+ // ANALYTICS should be able to view audits
631
+ const analyticsRes = await app.request('/api/audits', {
632
+ method: 'GET',
633
+ headers: {
634
+ ...analyticsHeaders,
635
+ 'User-Agent': 'IntegrationTest/1.0',
636
+ 'account-id': account.id,
637
+ },
638
+ })
639
+ expect(analyticsRes.status).toBe(200)
640
+
641
+ // BILLING should NOT be able to view audits
642
+ const billingRes = await app.request('/api/audits', {
643
+ method: 'GET',
644
+ headers: {
645
+ ...billingHeaders,
646
+ 'User-Agent': 'IntegrationTest/1.0',
647
+ 'account-id': account.id,
648
+ },
649
+ })
650
+ expect(billingRes.status).toBe(403)
651
+ })
652
+
653
+ it('should NOT have user management access unlike ADMIN role', async () => {
654
+ const account = await createAccount({ name: 'Analytics vs Admin User Mgmt Test' })
655
+ const { headers: analyticsHeaders } = await createUserWithRole(account.id, 'ANALYTICS')
656
+ const { headers: adminHeaders } = await createUserWithRole(account.id, 'ADMIN')
657
+ const { user: targetUser } = await createUserWithRole(account.id, 'VIEWER')
658
+
659
+ // ADMIN should be able to update users
660
+ const adminRes = await app.request(`/api/users/${targetUser.id}`, {
661
+ method: 'PATCH',
662
+ headers: {
663
+ ...adminHeaders,
664
+ 'User-Agent': 'IntegrationTest/1.0',
665
+ 'account-id': account.id,
666
+ 'Content-Type': 'application/json',
667
+ },
668
+ body: JSON.stringify({ name: 'Admin Updated' }),
669
+ })
670
+ expect(adminRes.status).toBe(200)
671
+
672
+ // ANALYTICS should NOT be able to update users
673
+ const analyticsRes = await app.request(`/api/users/${targetUser.id}`, {
674
+ method: 'PATCH',
675
+ headers: {
676
+ ...analyticsHeaders,
677
+ 'User-Agent': 'IntegrationTest/1.0',
678
+ 'account-id': account.id,
679
+ 'Content-Type': 'application/json',
680
+ },
681
+ body: JSON.stringify({ name: 'Analytics Updated' }),
682
+ })
683
+ expect(analyticsRes.status).toBe(403)
684
+ })
685
+
686
+ it('should NOT have invitation management unlike MANAGER role', async () => {
687
+ const account = await createAccount({ name: 'Analytics vs Manager Invite Test' })
688
+ const { headers: analyticsHeaders } = await createUserWithRole(account.id, 'ANALYTICS')
689
+ const { headers: managerHeaders } = await createUserWithRole(account.id, 'MANAGER')
690
+
691
+ // MANAGER should be able to create invitations
692
+ const managerRes = await app.request('/api/invitations', {
693
+ method: 'POST',
694
+ headers: {
695
+ ...managerHeaders,
696
+ 'User-Agent': 'IntegrationTest/1.0',
697
+ 'account-id': account.id,
698
+ 'Content-Type': 'application/json',
699
+ },
700
+ body: JSON.stringify({
701
+ email: 'invited-by-manager-analytics-comp@example.com',
702
+ role: 'VIEWER',
703
+ }),
704
+ })
705
+ expect(managerRes.status).toBe(200)
706
+
707
+ // ANALYTICS should NOT be able to create invitations
708
+ const analyticsRes = await app.request('/api/invitations', {
709
+ method: 'POST',
710
+ headers: {
711
+ ...analyticsHeaders,
712
+ 'User-Agent': 'IntegrationTest/1.0',
713
+ 'account-id': account.id,
714
+ 'Content-Type': 'application/json',
715
+ },
716
+ body: JSON.stringify({
717
+ email: 'invited-by-analytics-comp@example.com',
718
+ role: 'VIEWER',
719
+ }),
720
+ })
721
+ expect(analyticsRes.status).toBe(403)
722
+ })
723
+
724
+ it('should have audit access same as ADMIN', async () => {
725
+ const account = await createAccount({ name: 'Analytics vs Admin Audit Test' })
726
+ const { user: analyticsUser, headers: analyticsHeaders } = await createUserWithRole(account.id, 'ANALYTICS')
727
+ const { headers: adminHeaders } = await createUserWithRole(account.id, 'ADMIN')
728
+
729
+ // Create an audit log
730
+ createAuditLog({
731
+ accountId: account.id,
732
+ userId: analyticsUser.id,
733
+ entity: 'User',
734
+ entityId: crypto.randomUUID(),
735
+ action: 'INSERT',
736
+ })
737
+
738
+ // Both ADMIN and ANALYTICS should be able to view audits
739
+ const adminRes = await app.request('/api/audits', {
740
+ method: 'GET',
741
+ headers: {
742
+ ...adminHeaders,
743
+ 'User-Agent': 'IntegrationTest/1.0',
744
+ 'account-id': account.id,
745
+ },
746
+ })
747
+ expect(adminRes.status).toBe(200)
748
+
749
+ const analyticsRes = await app.request('/api/audits', {
750
+ method: 'GET',
751
+ headers: {
752
+ ...analyticsHeaders,
753
+ 'User-Agent': 'IntegrationTest/1.0',
754
+ 'account-id': account.id,
755
+ },
756
+ })
757
+ expect(analyticsRes.status).toBe(200)
758
+ })
759
+ })
760
+
761
+ // ============================================================================
762
+ // NON-HIERARCHICAL ISOLATION TESTS
763
+ // ============================================================================
764
+
765
+ describe('Non-hierarchical role isolation', () => {
766
+ it('should NOT inherit BILLING permissions', () => {
767
+ // ANALYTICS should not have BILLING permissions
768
+ expect(hasPermission('ANALYTICS', Permission.MANAGE_BILLING)).toBe(false)
769
+ expect(hasPermission('ANALYTICS', Permission.VIEW_BILLING)).toBe(false)
770
+ })
771
+
772
+ it('should NOT grant BILLING access when ANALYTICS role is required', () => {
773
+ expect(hasMinimumRole('BILLING', 'ANALYTICS')).toBe(false)
774
+ })
775
+
776
+ it('should NOT grant ANALYTICS access when BILLING role is required', () => {
777
+ expect(hasMinimumRole('ANALYTICS', 'BILLING')).toBe(false)
778
+ })
779
+
780
+ it('should have different permissions than BILLING', () => {
781
+ // ANALYTICS has VIEW_ANALYTICS, BILLING does not
782
+ expect(hasPermission('ANALYTICS', Permission.VIEW_ANALYTICS)).toBe(true)
783
+ expect(hasPermission('BILLING', Permission.VIEW_ANALYTICS)).toBe(false)
784
+
785
+ // BILLING has MANAGE_BILLING, ANALYTICS does not
786
+ expect(hasPermission('BILLING', Permission.MANAGE_BILLING)).toBe(true)
787
+ expect(hasPermission('ANALYTICS', Permission.MANAGE_BILLING)).toBe(false)
788
+ })
789
+ })
790
+
791
+ // ============================================================================
792
+ // MULTI-TENANT ISOLATION TESTS
793
+ // ============================================================================
794
+
795
+ describe('Multi-tenant isolation', () => {
796
+ it('should NOT access resources from accounts where user is not a member', async () => {
797
+ const account1 = await createAccount({ name: 'Analytics Multi-tenant Account 1' })
798
+ const account2 = await createAccount({ name: 'Analytics Multi-tenant Account 2' })
799
+
800
+ // Create ANALYTICS user only in account1
801
+ const { headers: analyticsHeaders } = await createUserWithRole(account1.id, 'ANALYTICS')
802
+
803
+ // Trying to access account2 resources should fail
804
+ const res = await app.request('/api/users', {
805
+ method: 'GET',
806
+ headers: {
807
+ ...analyticsHeaders,
808
+ 'User-Agent': 'IntegrationTest/1.0',
809
+ 'account-id': account2.id, // Different account
810
+ },
811
+ })
812
+
813
+ // Should be 403 (not a member of this account)
814
+ expect(res.status).toBe(403)
815
+ })
816
+
817
+ it('should only see users from own account', async () => {
818
+ const account1 = await createAccount({ name: 'Analytics User Isolation Account 1' })
819
+ const account2 = await createAccount({ name: 'Analytics User Isolation Account 2' })
820
+
821
+ // Create ANALYTICS user in account1
822
+ const { user: analyticsUser, headers: analyticsHeaders } = await createUserWithRole(account1.id, 'ANALYTICS')
823
+
824
+ // Create user in account2
825
+ const user2 = await createUser({ email: 'account2user-analytics@example.com', name: 'Account 2 User' })
826
+ await addUserToAccount(user2.id, account2.id, 'ADMIN')
827
+
828
+ // ANALYTICS user should only see themselves (and other account1 users)
829
+ const res = await app.request('/api/users', {
830
+ method: 'GET',
831
+ headers: {
832
+ ...analyticsHeaders,
833
+ 'User-Agent': 'IntegrationTest/1.0',
834
+ 'account-id': account1.id,
835
+ },
836
+ })
837
+
838
+ expect(res.status).toBe(200)
839
+ const body = await res.json()
840
+ expect(body.data).toHaveLength(1)
841
+ expect(body.data[0].id).toBe(analyticsUser.id)
842
+ })
843
+
844
+ it('should only see audit logs from own account', async () => {
845
+ const account1 = await createAccount({ name: 'Analytics Audit Isolation Account 1' })
846
+ const account2 = await createAccount({ name: 'Analytics Audit Isolation Account 2' })
847
+
848
+ // Create ANALYTICS user in account1
849
+ const { user: analyticsUser, headers: analyticsHeaders } = await createUserWithRole(account1.id, 'ANALYTICS')
850
+
851
+ // Create user in account2 and create audit log there
852
+ const user2 = await createUser({ email: 'account2user-audit@example.com', name: 'Account 2 User' })
853
+ await addUserToAccount(user2.id, account2.id, 'ADMIN')
854
+
855
+ // Create audit logs in both accounts
856
+ createAuditLog({
857
+ accountId: account1.id,
858
+ userId: analyticsUser.id,
859
+ entity: 'User',
860
+ entityId: crypto.randomUUID(),
861
+ action: 'INSERT',
862
+ })
863
+
864
+ createAuditLog({
865
+ accountId: account2.id,
866
+ userId: user2.id,
867
+ entity: 'User',
868
+ entityId: crypto.randomUUID(),
869
+ action: 'INSERT',
870
+ })
871
+
872
+ // ANALYTICS user should only see audit logs from account1
873
+ const res = await app.request('/api/audits', {
874
+ method: 'GET',
875
+ headers: {
876
+ ...analyticsHeaders,
877
+ 'User-Agent': 'IntegrationTest/1.0',
878
+ 'account-id': account1.id,
879
+ },
880
+ })
881
+
882
+ expect(res.status).toBe(200)
883
+ const body = await res.json()
884
+ // All returned logs should be from account1
885
+ for (const log of body.data) {
886
+ expect(log.accountId).toBe(account1.id)
887
+ }
888
+ })
889
+ })
890
+
891
+ // ============================================================================
892
+ // EDGE CASES
893
+ // ============================================================================
894
+
895
+ describe('Edge cases', () => {
896
+ it('should handle simultaneous ANALYTICS and other role in different accounts', async () => {
897
+ const analyticsAccount = await createAccount({ name: 'Analytics Only Account' })
898
+ const adminAccount = await createAccount({ name: 'Admin Account' })
899
+
900
+ // Create user with ANALYTICS in one account, ADMIN in another
901
+ const user = await createUser({
902
+ email: 'multi-role-analytics@example.com',
903
+ name: 'Multi-Role Analytics User',
904
+ })
905
+ await addUserToAccount(user.id, analyticsAccount.id, 'ANALYTICS')
906
+ await addUserToAccount(user.id, adminAccount.id, 'ADMIN')
907
+
908
+ const { sessionId, headers } = await createUserSession(user.id, {
909
+ email: user.email,
910
+ name: user.name,
911
+ })
912
+
913
+ // In analytics account - should be able to view audits
914
+ // First create an audit log
915
+ createAuditLog({
916
+ accountId: analyticsAccount.id,
917
+ userId: user.id,
918
+ entity: 'User',
919
+ entityId: crypto.randomUUID(),
920
+ action: 'INSERT',
921
+ })
922
+
923
+ const analyticsAuditRes = await app.request('/api/audits', {
924
+ method: 'GET',
925
+ headers: {
926
+ ...headers,
927
+ 'User-Agent': 'IntegrationTest/1.0',
928
+ 'account-id': analyticsAccount.id,
929
+ },
930
+ })
931
+ expect(analyticsAuditRes.status).toBe(200)
932
+
933
+ // In admin account - should be able to create invitations
934
+ const adminRes = await app.request('/api/invitations', {
935
+ method: 'POST',
936
+ headers: {
937
+ ...headers,
938
+ 'User-Agent': 'IntegrationTest/1.0',
939
+ 'account-id': adminAccount.id,
940
+ 'Content-Type': 'application/json',
941
+ },
942
+ body: JSON.stringify({
943
+ email: 'new-invite-analytics@example.com',
944
+ role: 'VIEWER',
945
+ }),
946
+ })
947
+ expect(adminRes.status).toBe(200)
948
+
949
+ // But NOT in analytics account
950
+ const analyticsInviteRes = await app.request('/api/invitations', {
951
+ method: 'POST',
952
+ headers: {
953
+ ...headers,
954
+ 'User-Agent': 'IntegrationTest/1.0',
955
+ 'account-id': analyticsAccount.id,
956
+ 'Content-Type': 'application/json',
957
+ },
958
+ body: JSON.stringify({
959
+ email: 'analytics-invite@example.com',
960
+ role: 'VIEWER',
961
+ }),
962
+ })
963
+ expect(analyticsInviteRes.status).toBe(403)
964
+ })
965
+
966
+ it('should verify ANALYTICS role cannot self-promote to ADMIN', async () => {
967
+ const account = await createAccount({ name: 'Analytics Self-Promote Test' })
968
+ const { user, headers } = await createUserWithRole(account.id, 'ANALYTICS')
969
+
970
+ // Try to update own role to ADMIN (if role update endpoint exists)
971
+ // This tests that ANALYTICS cannot modify user accounts
972
+ const res = await app.request(`/api/users/${user.id}`, {
973
+ method: 'PATCH',
974
+ headers: {
975
+ ...headers,
976
+ 'User-Agent': 'IntegrationTest/1.0',
977
+ 'account-id': account.id,
978
+ 'Content-Type': 'application/json',
979
+ },
980
+ body: JSON.stringify({ name: 'Attempted Self Update' }),
981
+ })
982
+
983
+ expect(res.status).toBe(403)
984
+ })
985
+
986
+ it('should handle ANALYTICS user with both ANALYTICS and BILLING roles in same account', async () => {
987
+ const account = await createAccount({ name: 'Dual Special Role Account' })
988
+
989
+ // Create user with both ANALYTICS role first
990
+ const { user, headers } = await createUserWithRole(account.id, 'ANALYTICS')
991
+
992
+ // This user only has ANALYTICS in this account
993
+ // They should be able to view audits
994
+ createAuditLog({
995
+ accountId: account.id,
996
+ userId: user.id,
997
+ entity: 'User',
998
+ entityId: crypto.randomUUID(),
999
+ action: 'INSERT',
1000
+ })
1001
+
1002
+ const auditRes = await app.request('/api/audits', {
1003
+ method: 'GET',
1004
+ headers: {
1005
+ ...headers,
1006
+ 'User-Agent': 'IntegrationTest/1.0',
1007
+ 'account-id': account.id,
1008
+ },
1009
+ })
1010
+ expect(auditRes.status).toBe(200)
1011
+
1012
+ // But cannot modify users
1013
+ const updateRes = await app.request(`/api/users/${user.id}`, {
1014
+ method: 'PATCH',
1015
+ headers: {
1016
+ ...headers,
1017
+ 'User-Agent': 'IntegrationTest/1.0',
1018
+ 'account-id': account.id,
1019
+ 'Content-Type': 'application/json',
1020
+ },
1021
+ body: JSON.stringify({ name: 'Should Not Update' }),
1022
+ })
1023
+ expect(updateRes.status).toBe(403)
1024
+ })
1025
+ })
1026
+ })