@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.
- package/dist/cli.d.ts +13 -0
- package/dist/cli.js +46 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.test.d.ts +1 -0
- package/dist/cli.test.js +26 -0
- package/dist/cli.test.js.map +1 -0
- package/dist/generator.d.ts +14 -0
- package/dist/generator.js +142 -0
- package/dist/generator.js.map +1 -0
- package/dist/generator.test.d.ts +1 -0
- package/dist/generator.test.js +127 -0
- package/dist/generator.test.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +97 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts.d.ts +25 -0
- package/dist/prompts.js +83 -0
- package/dist/prompts.js.map +1 -0
- package/dist/prompts.test.d.ts +1 -0
- package/dist/prompts.test.js +24 -0
- package/dist/prompts.test.js.map +1 -0
- package/dist/providers/cloudflare.d.ts +37 -0
- package/dist/providers/cloudflare.js +61 -0
- package/dist/providers/cloudflare.js.map +1 -0
- package/dist/providers/cloudflare.test.d.ts +1 -0
- package/dist/providers/cloudflare.test.js +29 -0
- package/dist/providers/cloudflare.test.js.map +1 -0
- package/dist/providers/github.d.ts +16 -0
- package/dist/providers/github.js +57 -0
- package/dist/providers/github.js.map +1 -0
- package/dist/providers/github.test.d.ts +1 -0
- package/dist/providers/github.test.js +16 -0
- package/dist/providers/github.test.js.map +1 -0
- package/dist/templates.d.ts +8 -0
- package/dist/templates.js +88 -0
- package/dist/templates.js.map +1 -0
- package/dist/templates.test.d.ts +1 -0
- package/dist/templates.test.js +26 -0
- package/dist/templates.test.js.map +1 -0
- package/package.json +36 -0
- package/templates/base/.claude/agents/architect-review.md +160 -0
- package/templates/base/.claude/agents/backend-architect.md +308 -0
- package/templates/base/.claude/agents/code-reviewer.md +170 -0
- package/templates/base/.claude/agents/performance-engineer.md +166 -0
- package/templates/base/.claude/agents/test-automator.md +219 -0
- package/templates/base/.claude/commands/check-skill-rules.md +53 -0
- package/templates/base/.claude/commands/claude-md.md +250 -0
- package/templates/base/.claude/commands/code-prompt.md +212 -0
- package/templates/base/.claude/commands/explain-code.md +194 -0
- package/templates/base/.claude/commands/init-projec.md +89 -0
- package/templates/base/.claude/commands/linear/README.md +297 -0
- package/templates/base/.claude/commands/linear/create-issue.md +190 -0
- package/templates/base/.claude/commands/linear/implement-issue.md +248 -0
- package/templates/base/.claude/commands/linear/process-triage.md +399 -0
- package/templates/base/.claude/commands/linear/setup.md +180 -0
- package/templates/base/.claude/commands/prime.md +9 -0
- package/templates/base/.claude/commands/review-gap.md +10 -0
- package/templates/base/.claude/commands/setup-aa.md +311 -0
- package/templates/base/.claude/commands/ship.md +262 -0
- package/templates/base/.claude/commands/tools.md +3 -0
- package/templates/base/.claude/docs/claude-progress.txt +107 -0
- package/templates/base/.claude/hooks/package-lock.json +556 -0
- package/templates/base/.claude/hooks/package.json +16 -0
- package/templates/base/.claude/hooks/skill-activation-prompt.sh +7 -0
- package/templates/base/.claude/hooks/skill-activation-prompt.ts +142 -0
- package/templates/base/.claude/hooks/tsconfig.json +19 -0
- package/templates/base/.claude/scripts/check-updates.sh +85 -0
- package/templates/base/.claude/scripts/install_pkgs.sh +66 -0
- package/templates/base/.claude/scripts/setup-project.sh +177 -0
- package/templates/base/.claude/scripts/validate-skill-rules.sh +94 -0
- package/templates/base/.claude/settings.json +113 -0
- package/templates/base/.claude/settings.local.json +11 -0
- package/templates/base/.claude/skills/architecture-analyzer/SKILL.md +531 -0
- package/templates/base/.claude/skills/architecture-analyzer/assets/report-template.md +215 -0
- package/templates/base/.claude/skills/architecture-analyzer/references/c4-templates.md +234 -0
- package/templates/base/.claude/skills/architecture-analyzer/references/confidence-levels.md +203 -0
- package/templates/base/.claude/skills/architecture-analyzer/scripts/analyze_structure.py +266 -0
- package/templates/base/.claude/skills/architecture-analyzer/scripts/analyze_tech_debt.py +776 -0
- package/templates/base/.claude/skills/architecture-analyzer/scripts/extract_apis.py +338 -0
- package/templates/base/.claude/skills/architecture-analyzer/scripts/generate_c4.py +283 -0
- package/templates/base/.claude/skills/architecture-analyzer/scripts/generate_erd.py +935 -0
- package/templates/base/.claude/skills/architecture-analyzer/scripts/map_dependencies.py +555 -0
- package/templates/base/.claude/skills/dev-browser/SKILL.md +318 -0
- package/templates/base/.claude/skills/dev-browser/bun.lock +443 -0
- package/templates/base/.claude/skills/dev-browser/package-lock.json +2927 -0
- package/templates/base/.claude/skills/dev-browser/package.json +27 -0
- package/templates/base/.claude/skills/dev-browser/scripts/start-server.ts +117 -0
- package/templates/base/.claude/skills/dev-browser/server.sh +24 -0
- package/templates/base/.claude/skills/dev-browser/src/client.ts +403 -0
- package/templates/base/.claude/skills/dev-browser/src/index.ts +281 -0
- package/templates/base/.claude/skills/dev-browser/src/snapshot/__tests__/snapshot.test.ts +223 -0
- package/templates/base/.claude/skills/dev-browser/src/snapshot/browser-script.ts +877 -0
- package/templates/base/.claude/skills/dev-browser/src/snapshot/index.ts +14 -0
- package/templates/base/.claude/skills/dev-browser/src/snapshot/inject.ts +13 -0
- package/templates/base/.claude/skills/dev-browser/src/types.ts +27 -0
- package/templates/base/.claude/skills/dev-browser/tsconfig.json +36 -0
- package/templates/base/.claude/skills/dev-browser/vitest.config.ts +12 -0
- package/templates/base/.claude/skills/linear/SKILL.md +440 -0
- package/templates/base/.claude/skills/linear/examples.md +262 -0
- package/templates/base/.claude/skills/linear/lib/client.ts +51 -0
- package/templates/base/.claude/skills/linear/lib/config.ts +106 -0
- package/templates/base/.claude/skills/linear/lib/output.ts +34 -0
- package/templates/base/.claude/skills/linear/package-lock.json +698 -0
- package/templates/base/.claude/skills/linear/package.json +27 -0
- package/templates/base/.claude/skills/linear/reference.md +263 -0
- package/templates/base/.claude/skills/linear/scripts/comments/create.ts +47 -0
- package/templates/base/.claude/skills/linear/scripts/comments/list.ts +47 -0
- package/templates/base/.claude/skills/linear/scripts/issues/archive.ts +30 -0
- package/templates/base/.claude/skills/linear/scripts/issues/create.ts +279 -0
- package/templates/base/.claude/skills/linear/scripts/issues/get.ts +68 -0
- package/templates/base/.claude/skills/linear/scripts/issues/list.ts +67 -0
- package/templates/base/.claude/skills/linear/scripts/issues/update.ts +281 -0
- package/templates/base/.claude/skills/linear/scripts/labels/add-to-issue.ts +63 -0
- package/templates/base/.claude/skills/linear/scripts/labels/create.ts +45 -0
- package/templates/base/.claude/skills/linear/scripts/labels/list.ts +30 -0
- package/templates/base/.claude/skills/linear/scripts/list-teams.ts +52 -0
- package/templates/base/.claude/skills/linear/scripts/setup/setup-credentials.ts +96 -0
- package/templates/base/.claude/skills/linear/scripts/status/list.ts +31 -0
- package/templates/base/.claude/skills/linear/scripts/status/set-by-name.ts +78 -0
- package/templates/base/.claude/skills/linear/scripts/status/update.ts +44 -0
- package/templates/base/.claude/skills/linear/scripts/users/list.ts +59 -0
- package/templates/base/.claude/skills/linear/scripts/users/me.ts +20 -0
- package/templates/base/.claude/skills/linear/templates/README.md +203 -0
- package/templates/base/.claude/skills/linear/templates/api-reference.md +258 -0
- package/templates/base/.claude/skills/linear/templates/bug-report.md +99 -0
- package/templates/base/.claude/skills/linear/templates/feature-request.md +118 -0
- package/templates/base/.claude/skills/linear/templates/security-issue.md +162 -0
- package/templates/base/.claude/skills/linear/templates/sprint-task.md +175 -0
- package/templates/base/.claude/skills/linear/templates/tech-debt.md +137 -0
- package/templates/base/.claude/skills/linear/tsconfig.json +17 -0
- package/templates/base/.claude/skills/linear/workflows/issue-lifecycle.md +317 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/SKILL.md +113 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/assets/global-setup.template.js +97 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/assets/playwright.config.template.js +171 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/assets/test-template.spec.js +163 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/examples/README.md +26 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/examples/ads.email-deeplink.spec.ts +12 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/examples/mobile.realism.spec.ts +16 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/examples/smoke.home.spec.ts +6 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/references/architecture.md +578 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/references/best-practices.md +260 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/references/ci-reporting.md +86 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/references/debugging.md +629 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/references/mobile-realism.md +50 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/references/optimization.md +488 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/references/patterns.md +513 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/references/resources.md +44 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/references/visual-a11y.md +66 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/scripts/auth-setup.js +202 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/scripts/performance-analyzer.js +240 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/scripts/trace-url.js +132 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/templates/ci/github-actions.playwright.yml +78 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/templates/fixtures.ts +44 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/templates/global-setup.template.js +97 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/templates/global.setup.ts +35 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/templates/helpers/ad-gpt-observer.ts +80 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/templates/helpers/chromium-mobile-profile.ts +93 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/templates/playwright.config.template.js +171 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/templates/playwright.config.ts +126 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/templates/test-template.spec.js +163 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/templates/tests/email-deeplink.ads.spec.ts +44 -0
- package/templates/base/.claude/skills/skill-rules.json +184 -0
- package/templates/base/.claude/skills/wrangler/SKILL.md +209 -0
- package/templates/base/.claude/skills/wrangler/resources/api.md +494 -0
- package/templates/base/.claude/skills/wrangler/resources/bundling.md +83 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/cert.md +64 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/check.md +66 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/containers.md +157 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/d1.md +843 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/delete.md +27 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/deploy.md +139 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/deployments.md +56 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/dev.md +157 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/dispatch-namespace.md +69 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/docs.md +61 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/how-to-run.md +62 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/hyperdrive.md +425 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/init.md +31 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/kv-bulk.md +265 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/kv-key.md +353 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/kv-namespace.md +265 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/login.md +23 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/logout.md +19 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/mtls-certificate.md +69 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/pages.md +175 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/pipelines.md +76 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/queues.md +132 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/r2-bucket.md +342 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/r2-object.md +267 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/r2-sql.md +65 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/rollback.md +40 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/secret.md +308 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/secrets-store-secret.md +100 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/secrets-store-store.md +60 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/setup.md +67 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/tail.md +37 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/telemetry.md +64 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/triggers.md +39 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/types.md +73 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/vectorize.md +941 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/versions.md +95 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/whoami.md +49 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/workflows.md +117 -0
- package/templates/base/.claude/skills/wrangler/resources/commands.md +138 -0
- package/templates/base/.claude/skills/wrangler/resources/configuration.md +2176 -0
- package/templates/base/.claude/skills/wrangler/resources/custom-builds.md +55 -0
- package/templates/base/.claude/skills/wrangler/resources/deprecations.md +279 -0
- package/templates/base/.claude/skills/wrangler/resources/enviroments.md +416 -0
- package/templates/base/.claude/skills/wrangler/resources/extract_sections.py +119 -0
- package/templates/base/.claude/skills/wrangler/resources/process_content.py +94 -0
- package/templates/base/.claude/skills/wrangler/resources/system-enviroments-variables.md +120 -0
- package/templates/base/.dev.vars.example +15 -0
- package/templates/base/.env.example +29 -0
- package/templates/base/.github/workflows/test.yml +127 -0
- package/templates/base/.nycrc.json +16 -0
- package/templates/base/CLAUDE.md +218 -0
- package/templates/base/README.md +670 -0
- package/templates/base/auth-setup-error.png +0 -0
- package/templates/base/config/drizzle.config.ts +10 -0
- package/templates/base/config/eslint.config.js +364 -0
- package/templates/base/config/wrangler.json +76 -0
- package/templates/base/docs/app_spec.txt +879 -0
- package/templates/base/docs/app_spec_template.md +681 -0
- package/templates/base/docs/architecture/README.md +8 -0
- package/templates/base/docs/architecture/data-requirements.md +109 -0
- package/templates/base/docs/architecture/erd.md +91 -0
- package/templates/base/docs/features/feature_list.json +3128 -0
- package/templates/base/docs/hono-boilerplate-plan.md +1774 -0
- package/templates/base/docs/test-coverage-gap-analysis.md +242 -0
- package/templates/base/docs/testing.md +188 -0
- package/templates/base/index.html +16 -0
- package/templates/base/package.json +115 -0
- package/templates/base/playwright.config.ts +158 -0
- package/templates/base/pnpm-lock.yaml +8175 -0
- package/templates/base/scripts/capture-prod-session.ts +250 -0
- package/templates/base/scripts/generate-openapi.ts +23 -0
- package/templates/base/scripts/init.sh +121 -0
- package/templates/base/src/client/__tests__/button.test.tsx +30 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-dashboard-stats-cards-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-quick-action-cards-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-recent-activity-section-1.png +0 -0
- 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
- 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
- package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-render-dashboard-when-authenticated-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-show-navigation-sidebar-1.png +0 -0
- 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
- package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-display-Google-OAuth-login-button-1.png +0 -0
- 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
- 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
- package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-render-login-page-at--login-route-1.png +0 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/templates/base/src/client/__tests__/routes/authenticated-layout.test.tsx +252 -0
- package/templates/base/src/client/__tests__/routes/dashboard.test.tsx +136 -0
- package/templates/base/src/client/__tests__/routes/error-components.test.tsx +186 -0
- package/templates/base/src/client/__tests__/routes/login.test.tsx +112 -0
- package/templates/base/src/client/__tests__/routes/navigation.test.tsx +272 -0
- package/templates/base/src/client/__tests__/routes/root-layout.test.tsx +65 -0
- package/templates/base/src/client/__tests__/setup-browser.ts +15 -0
- package/templates/base/src/client/__tests__/setup.ts +32 -0
- package/templates/base/src/client/__tests__/test-utils.tsx +135 -0
- package/templates/base/src/client/api/.gitkeep +0 -0
- package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-can-be-collapsed-by-default-1.png +0 -0
- package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-expands-when-collapsed-and-expand-button-is-clicked-1.png +0 -0
- package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-handles-logout-button-click-1.png +0 -0
- package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-handles-navigation-clicks-1.png +0 -0
- package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-hides-navigation-labels-when-collapsed-1.png +0 -0
- package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-highlights-active-route-1.png +0 -0
- package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-renders-sidebar-navigation-items-1.png +0 -0
- package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-shows-keyboard-shortcut-hint-when-expanded-1.png +0 -0
- package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-shows-user-info-when-authenticated-1.png +0 -0
- package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-shows-user-initials-in-avatar-fallback-1.png +0 -0
- package/templates/base/src/client/components/__tests__/error-boundary.test.tsx +97 -0
- package/templates/base/src/client/components/__tests__/sidebar.test.tsx +281 -0
- package/templates/base/src/client/components/error-boundary.tsx +68 -0
- package/templates/base/src/client/components/icons.tsx +106 -0
- package/templates/base/src/client/components/layout/.gitkeep +0 -0
- package/templates/base/src/client/components/sidebar.tsx +267 -0
- package/templates/base/src/client/components/ui/.gitkeep +0 -0
- package/templates/base/src/client/components/ui/__tests__/avatar.test.tsx +308 -0
- package/templates/base/src/client/components/ui/__tests__/card.test.tsx +214 -0
- package/templates/base/src/client/components/ui/__tests__/dialog.test.tsx +297 -0
- package/templates/base/src/client/components/ui/__tests__/error-fallback.test.tsx +145 -0
- package/templates/base/src/client/components/ui/__tests__/input.test.tsx +98 -0
- package/templates/base/src/client/components/ui/__tests__/loading-skeleton.test.tsx +139 -0
- package/templates/base/src/client/components/ui/__tests__/skeleton.test.tsx +44 -0
- package/templates/base/src/client/components/ui/__tests__/sonner.test.tsx +28 -0
- package/templates/base/src/client/components/ui/__tests__/tabs.test.tsx +233 -0
- package/templates/base/src/client/components/ui/avatar.tsx +101 -0
- package/templates/base/src/client/components/ui/badge.tsx +46 -0
- package/templates/base/src/client/components/ui/button.tsx +72 -0
- package/templates/base/src/client/components/ui/card.tsx +86 -0
- package/templates/base/src/client/components/ui/dialog.tsx +140 -0
- package/templates/base/src/client/components/ui/error-fallback.tsx +179 -0
- package/templates/base/src/client/components/ui/form.tsx +172 -0
- package/templates/base/src/client/components/ui/input.tsx +24 -0
- package/templates/base/src/client/components/ui/label.tsx +22 -0
- package/templates/base/src/client/components/ui/loading-skeleton.tsx +154 -0
- package/templates/base/src/client/components/ui/separator.tsx +33 -0
- package/templates/base/src/client/components/ui/skeleton.tsx +16 -0
- package/templates/base/src/client/components/ui/sonner.tsx +29 -0
- package/templates/base/src/client/components/ui/tabs.tsx +121 -0
- package/templates/base/src/client/hooks/.gitkeep +0 -0
- package/templates/base/src/client/hooks/__tests__/use-auth.test.tsx +306 -0
- package/templates/base/src/client/hooks/__tests__/use-theme.test.tsx +172 -0
- package/templates/base/src/client/hooks/use-auth.ts +53 -0
- package/templates/base/src/client/hooks/use-theme.tsx +78 -0
- package/templates/base/src/client/index.css +881 -0
- package/templates/base/src/client/lib/query-client.ts +11 -0
- package/templates/base/src/client/lib/utils.ts +7 -0
- package/templates/base/src/client/main.tsx +26 -0
- package/templates/base/src/client/routeTree.gen.ts +258 -0
- package/templates/base/src/client/router.ts +15 -0
- package/templates/base/src/client/routes/$.tsx +77 -0
- package/templates/base/src/client/routes/.gitkeep +0 -0
- package/templates/base/src/client/routes/__root.tsx +34 -0
- 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
- 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
- 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
- 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
- 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
- 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
- package/templates/base/src/client/routes/__tests__/invite.test.tsx +138 -0
- 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
- 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
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-show-API-Access-section-1.png +0 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-Webhooks-section-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-connected-count-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-page-description-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-search-input-1.png +0 -0
- 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
- 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
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-search-functionality-should-search-by-description-1.png +0 -0
- 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
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-existing-webhook-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-webhook-events-1.png +0 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-page-rendering-should-display-page-description-1.png +0 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/templates/base/src/client/routes/_authenticated/__tests__/account.test.tsx +324 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/integrations.test.tsx +520 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/settings.test.tsx +414 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/team.test.tsx +374 -0
- package/templates/base/src/client/routes/_authenticated/account.tsx +416 -0
- package/templates/base/src/client/routes/_authenticated/dashboard.tsx +151 -0
- package/templates/base/src/client/routes/_authenticated/integrations.tsx +553 -0
- package/templates/base/src/client/routes/_authenticated/settings.tsx +310 -0
- package/templates/base/src/client/routes/_authenticated/team.tsx +395 -0
- package/templates/base/src/client/routes/_authenticated.tsx +69 -0
- package/templates/base/src/client/routes/index.tsx +155 -0
- package/templates/base/src/client/routes/invite.$token.tsx +191 -0
- package/templates/base/src/client/routes/login.tsx +92 -0
- package/templates/base/src/server/__tests__/fixtures.ts +461 -0
- package/templates/base/src/server/__tests__/mocks/__tests__/db.test.ts +239 -0
- package/templates/base/src/server/__tests__/mocks/__tests__/kv.test.ts +293 -0
- package/templates/base/src/server/__tests__/mocks/__tests__/r2.test.ts +363 -0
- package/templates/base/src/server/__tests__/mocks/db.ts +186 -0
- package/templates/base/src/server/__tests__/mocks/index.ts +33 -0
- package/templates/base/src/server/__tests__/mocks/kv.ts +286 -0
- package/templates/base/src/server/__tests__/mocks/r2.ts +397 -0
- package/templates/base/src/server/__tests__/setup.ts +281 -0
- package/templates/base/src/server/auth/__tests__/guards.test.ts +162 -0
- package/templates/base/src/server/auth/guards.ts +92 -0
- package/templates/base/src/server/auth/permissions.test.ts +45 -0
- package/templates/base/src/server/auth/permissions.ts +139 -0
- package/templates/base/src/server/auth/roles.test.ts +169 -0
- package/templates/base/src/server/auth/roles.ts +141 -0
- package/templates/base/src/server/db/client.ts +12 -0
- package/templates/base/src/server/db/schema/accounts.ts +20 -0
- package/templates/base/src/server/db/schema/audit-logs.ts +26 -0
- package/templates/base/src/server/db/schema/index.ts +7 -0
- package/templates/base/src/server/db/schema/invitations.ts +30 -0
- package/templates/base/src/server/db/schema/refresh-tokens.ts +22 -0
- package/templates/base/src/server/db/schema/user-accounts.ts +25 -0
- package/templates/base/src/server/db/schema/users.ts +33 -0
- package/templates/base/src/server/db/seed.ts +267 -0
- package/templates/base/src/server/env.test.ts +84 -0
- package/templates/base/src/server/env.ts +78 -0
- package/templates/base/src/server/index.ts +82 -0
- package/templates/base/src/server/lib/audit.ts +73 -0
- package/templates/base/src/server/lib/audited-db.test.ts +107 -0
- package/templates/base/src/server/lib/audited-db.ts +154 -0
- package/templates/base/src/server/lib/email.test.ts +116 -0
- package/templates/base/src/server/lib/email.ts +82 -0
- package/templates/base/src/server/lib/errors.test.ts +49 -0
- package/templates/base/src/server/lib/errors.ts +64 -0
- package/templates/base/src/server/lib/oauth.test.ts +238 -0
- package/templates/base/src/server/lib/oauth.ts +113 -0
- package/templates/base/src/server/lib/pagination.test.ts +52 -0
- package/templates/base/src/server/lib/pagination.ts +32 -0
- package/templates/base/src/server/lib/password.test.ts +151 -0
- package/templates/base/src/server/lib/password.ts +151 -0
- package/templates/base/src/server/lib/providers.test.ts +105 -0
- package/templates/base/src/server/lib/providers.ts +62 -0
- package/templates/base/src/server/lib/r2-storage.test.ts +202 -0
- package/templates/base/src/server/lib/r2-storage.ts +107 -0
- package/templates/base/src/server/lib/schema-helpers.ts +16 -0
- package/templates/base/src/server/lib/session.test.ts +758 -0
- package/templates/base/src/server/lib/session.ts +267 -0
- package/templates/base/src/server/lib/tokens.test.ts +208 -0
- package/templates/base/src/server/lib/tokens.ts +65 -0
- package/templates/base/src/server/lib/transaction.test.ts +45 -0
- package/templates/base/src/server/lib/transaction.ts +24 -0
- package/templates/base/src/server/middleware/account.test.ts +201 -0
- package/templates/base/src/server/middleware/account.ts +66 -0
- package/templates/base/src/server/middleware/auth.test.ts +345 -0
- package/templates/base/src/server/middleware/auth.ts +146 -0
- package/templates/base/src/server/middleware/cors.test.ts +88 -0
- package/templates/base/src/server/middleware/cors.ts +26 -0
- package/templates/base/src/server/middleware/error-handler.test.ts +69 -0
- package/templates/base/src/server/middleware/error-handler.ts +43 -0
- package/templates/base/src/server/middleware/index.ts +8 -0
- package/templates/base/src/server/middleware/rate-limit.test.ts +472 -0
- package/templates/base/src/server/middleware/rate-limit.ts +294 -0
- package/templates/base/src/server/middleware/request-context.test.ts +175 -0
- package/templates/base/src/server/middleware/request-context.ts +28 -0
- package/templates/base/src/server/middleware/request-logger.test.ts +92 -0
- package/templates/base/src/server/middleware/request-logger.ts +50 -0
- package/templates/base/src/server/routes/accounts/__tests__/handlers.test.ts +460 -0
- package/templates/base/src/server/routes/accounts/handlers.ts +179 -0
- package/templates/base/src/server/routes/accounts/index.ts +55 -0
- package/templates/base/src/server/routes/accounts/routes.ts +205 -0
- package/templates/base/src/server/routes/accounts/schemas.ts +31 -0
- package/templates/base/src/server/routes/api.ts +37 -0
- package/templates/base/src/server/routes/audits/__tests__/handlers.test.ts +349 -0
- package/templates/base/src/server/routes/audits/handlers.ts +37 -0
- package/templates/base/src/server/routes/audits/index.ts +14 -0
- package/templates/base/src/server/routes/audits/routes.ts +29 -0
- package/templates/base/src/server/routes/audits/schemas.ts +43 -0
- package/templates/base/src/server/routes/auth/__tests__/handlers.test.ts +381 -0
- package/templates/base/src/server/routes/auth/handlers.ts +222 -0
- package/templates/base/src/server/routes/auth/index.ts +37 -0
- package/templates/base/src/server/routes/auth/routes.ts +136 -0
- package/templates/base/src/server/routes/auth/schemas.ts +48 -0
- package/templates/base/src/server/routes/auth/test-login.ts +156 -0
- package/templates/base/src/server/routes/health/__tests__/handlers.test.ts +237 -0
- package/templates/base/src/server/routes/health/handlers.ts +83 -0
- package/templates/base/src/server/routes/health/index.ts +13 -0
- package/templates/base/src/server/routes/health/routes.ts +90 -0
- package/templates/base/src/server/routes/index.ts +53 -0
- package/templates/base/src/server/routes/invitations/__tests__/handlers.test.ts +473 -0
- package/templates/base/src/server/routes/invitations/handlers.ts +71 -0
- package/templates/base/src/server/routes/invitations/index.ts +25 -0
- package/templates/base/src/server/routes/invitations/routes.ts +69 -0
- package/templates/base/src/server/routes/invitations/schemas.ts +39 -0
- package/templates/base/src/server/routes/openapi.ts +14 -0
- package/templates/base/src/server/routes/schemas.ts +53 -0
- package/templates/base/src/server/routes/storage/__tests__/handlers.test.ts +408 -0
- package/templates/base/src/server/routes/storage/handlers.ts +100 -0
- package/templates/base/src/server/routes/storage/index.ts +42 -0
- package/templates/base/src/server/routes/storage/routes.ts +91 -0
- package/templates/base/src/server/routes/storage/schemas.ts +56 -0
- package/templates/base/src/server/routes/users/__tests__/handlers.test.ts +526 -0
- package/templates/base/src/server/routes/users/handlers.ts +228 -0
- package/templates/base/src/server/routes/users/index.ts +67 -0
- package/templates/base/src/server/routes/users/routes.ts +265 -0
- package/templates/base/src/server/routes/users/schemas.ts +67 -0
- package/templates/base/src/server/services/__tests__/accounts.test.ts +764 -0
- package/templates/base/src/server/services/__tests__/audits.test.ts +235 -0
- package/templates/base/src/server/services/__tests__/auth.test.ts +765 -0
- package/templates/base/src/server/services/__tests__/invitations.test.ts +704 -0
- package/templates/base/src/server/services/__tests__/users.test.ts +755 -0
- package/templates/base/src/server/services/accounts.ts +269 -0
- package/templates/base/src/server/services/audits.ts +82 -0
- package/templates/base/src/server/services/auth.ts +225 -0
- package/templates/base/src/server/services/index.ts +6 -0
- package/templates/base/src/server/services/invitations.ts +306 -0
- package/templates/base/src/server/services/users.ts +350 -0
- package/templates/base/src/server/types/auth.ts +36 -0
- package/templates/base/src/server/types/index.ts +117 -0
- package/templates/base/src/shared/schemas/.gitkeep +0 -0
- package/templates/base/src/shared/schemas/__tests__/schemas.test.ts +547 -0
- package/templates/base/src/shared/schemas/account.ts +15 -0
- package/templates/base/src/shared/schemas/index.ts +6 -0
- package/templates/base/src/shared/schemas/invitation.ts +9 -0
- package/templates/base/src/shared/schemas/profile.ts +10 -0
- package/templates/base/src/shared/schemas/user.ts +16 -0
- package/templates/base/src/shared/schemas/webhook.ts +12 -0
- package/templates/base/src/shared/types/.gitkeep +0 -0
- package/templates/base/src/shared/types/account.ts +12 -0
- package/templates/base/src/shared/types/api.ts +1399 -0
- package/templates/base/src/shared/types/auth.ts +24 -0
- package/templates/base/src/shared/types/index.ts +5 -0
- package/templates/base/src/shared/types/user.ts +31 -0
- package/templates/base/src/test/vitest-zod-matcher.ts +37 -0
- package/templates/base/src/test/vitest.d.ts +19 -0
- package/templates/base/tests/e2e/README.md +141 -0
- package/templates/base/tests/e2e/a11y/accessibility.spec.ts +925 -0
- package/templates/base/tests/e2e/a11y/keyboard-navigation.spec.ts +610 -0
- package/templates/base/tests/e2e/api/accounts.spec.ts +148 -0
- package/templates/base/tests/e2e/api/audit-logs.spec.ts +130 -0
- package/templates/base/tests/e2e/api/authenticated-api.spec.ts +311 -0
- package/templates/base/tests/e2e/api/storage.spec.ts +109 -0
- package/templates/base/tests/e2e/auth-flows.unauth.spec.ts +117 -0
- package/templates/base/tests/e2e/auth-logout.unauth.spec.ts +103 -0
- package/templates/base/tests/e2e/auth.setup.ts +115 -0
- package/templates/base/tests/e2e/auth.spec.ts +146 -0
- package/templates/base/tests/e2e/compatibility/cross-browser.spec.ts +152 -0
- package/templates/base/tests/e2e/compatibility/cross-browser.spec.ts-snapshots/login-chromium-chromium-darwin.png +0 -0
- package/templates/base/tests/e2e/crud/account.spec.ts +356 -0
- package/templates/base/tests/e2e/crud/integrations.spec.ts +419 -0
- package/templates/base/tests/e2e/crud/team.spec.ts +287 -0
- package/templates/base/tests/e2e/crud/users.spec.ts +239 -0
- package/templates/base/tests/e2e/errors/error-boundary.spec.ts +428 -0
- package/templates/base/tests/e2e/errors/error-handling.spec.ts +47 -0
- package/templates/base/tests/e2e/errors/error-handling.unauth.spec.ts +205 -0
- package/templates/base/tests/e2e/fixtures.ts +266 -0
- package/templates/base/tests/e2e/forms/validation.spec.ts +569 -0
- package/templates/base/tests/e2e/invitations/invite-flow.unauth.spec.ts +204 -0
- package/templates/base/tests/e2e/journeys/account-lifecycle.spec.ts +314 -0
- package/templates/base/tests/e2e/journeys/audit-investigation.spec.ts +299 -0
- package/templates/base/tests/e2e/journeys/auth-onboarding.spec.ts +232 -0
- package/templates/base/tests/e2e/journeys/critical-flows.spec.ts +281 -0
- package/templates/base/tests/e2e/journeys/error-recovery.spec.ts +354 -0
- package/templates/base/tests/e2e/journeys/file-management.spec.ts +307 -0
- package/templates/base/tests/e2e/journeys/integrations.spec.ts +372 -0
- package/templates/base/tests/e2e/journeys/multi-account.spec.ts +317 -0
- package/templates/base/tests/e2e/journeys/rbac-enforcement.spec.ts +389 -0
- package/templates/base/tests/e2e/journeys/settings-profile.spec.ts +400 -0
- package/templates/base/tests/e2e/journeys/team-collaboration.spec.ts +410 -0
- package/templates/base/tests/e2e/mobile/responsive.spec.ts +178 -0
- package/templates/base/tests/e2e/navigation/routing.spec.ts +371 -0
- package/templates/base/tests/e2e/navigation/sidebar.spec.ts +425 -0
- package/templates/base/tests/e2e/pages/ui-features.spec.ts +393 -0
- package/templates/base/tests/e2e/performance/baselines.spec.ts +162 -0
- package/templates/base/tests/e2e/performance/benchmarks.spec.ts +371 -0
- package/templates/base/tests/e2e/smoke.unauth.spec.ts +196 -0
- package/templates/base/tests/e2e/visual/components.spec.ts +650 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/confirmation-dialog-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dark-mode-background-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dark-mode-card-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dark-mode-sidebar-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dashboard-card-single-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dialog-content-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dialog-with-backdrop-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/empty-search-results-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/input-default-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/input-focus-ring-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/primary-button-focus-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/primary-button-hover-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/primary-button-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/sidebar-active-nav-item-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/sidebar-collapsed-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/sidebar-navigation-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/core-components.spec.ts +192 -0
- package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/account-page-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/create-webhook-dialog-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/dashboard-page-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/delete-account-dialog-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/integrations-page-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/invite-member-dialog-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/login-page-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/settings-account-tab-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/settings-profile-tab-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/sidebar-navigation-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/team-page-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/screenshots.spec.ts +230 -0
- package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/404-page-chromium-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/404-page-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/account-page-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/login-page-chromium-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/login-page-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/settings-page-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/team-invite-dialog-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/team-page-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/webhook-create-dialog-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/theme-colors.spec.ts +293 -0
- package/templates/base/tests/e2e/visual/ui-components.spec.ts +502 -0
- package/templates/base/tests/integration/accounts/crud.test.ts +1402 -0
- package/templates/base/tests/integration/audits/list.test.ts +1133 -0
- package/templates/base/tests/integration/auth/auth-service.test.ts +415 -0
- package/templates/base/tests/integration/auth/invitation-token.test.ts +529 -0
- package/templates/base/tests/integration/auth/logout.test.ts +524 -0
- package/templates/base/tests/integration/auth/oauth.test.ts +768 -0
- package/templates/base/tests/integration/auth/refresh-token.test.ts +364 -0
- package/templates/base/tests/integration/auth/session-expiry.test.ts +569 -0
- package/templates/base/tests/integration/auth/session.test.ts +520 -0
- package/templates/base/tests/integration/auth/super-admin.test.ts +451 -0
- package/templates/base/tests/integration/authorization/analytics-role.test.ts +1026 -0
- package/templates/base/tests/integration/authorization/billing-role.test.ts +776 -0
- package/templates/base/tests/integration/authorization/guards-roles.test.ts +539 -0
- package/templates/base/tests/integration/authorization/multi-tenancy.test.ts +1112 -0
- package/templates/base/tests/integration/authorization/role-hierarchy.test.ts +931 -0
- package/templates/base/tests/integration/authorization/roles.test.ts +1347 -0
- package/templates/base/tests/integration/config/production-behavior.test.ts +536 -0
- package/templates/base/tests/integration/db/constraints.test.ts +695 -0
- package/templates/base/tests/integration/fixtures/accounts.ts +136 -0
- package/templates/base/tests/integration/fixtures/index.ts +232 -0
- package/templates/base/tests/integration/fixtures/invitations.ts +195 -0
- package/templates/base/tests/integration/fixtures/users.ts +144 -0
- package/templates/base/tests/integration/health/health.test.ts +351 -0
- package/templates/base/tests/integration/invitations/crud.test.ts +1457 -0
- package/templates/base/tests/integration/invitations/email.test.ts +506 -0
- package/templates/base/tests/integration/lib/email.test.ts +174 -0
- package/templates/base/tests/integration/lib/oauth.test.ts +368 -0
- package/templates/base/tests/integration/lib/password.test.ts +192 -0
- package/templates/base/tests/integration/lib/schema-helpers.test.ts +129 -0
- package/templates/base/tests/integration/lib/tokens.test.ts +304 -0
- package/templates/base/tests/integration/middleware/auth.test.ts +499 -0
- package/templates/base/tests/integration/middleware/cors.test.ts +334 -0
- package/templates/base/tests/integration/middleware/request-context.test.ts +156 -0
- package/templates/base/tests/integration/middleware/request-logger.test.ts +313 -0
- package/templates/base/tests/integration/performance/response-times.test.ts +509 -0
- package/templates/base/tests/integration/security/cookie-security.test.ts +567 -0
- package/templates/base/tests/integration/security/csrf-protection.test.ts +542 -0
- package/templates/base/tests/integration/security/jwt-validation.test.ts +209 -0
- package/templates/base/tests/integration/security/log-sanitization.test.ts +658 -0
- package/templates/base/tests/integration/security/rate-limiting.test.ts +1251 -0
- package/templates/base/tests/integration/security/sql-injection.test.ts +663 -0
- package/templates/base/tests/integration/security/token-hashing.test.ts +371 -0
- package/templates/base/tests/integration/security/xss-prevention.test.ts +541 -0
- package/templates/base/tests/integration/setup.ts +834 -0
- package/templates/base/tests/integration/smoke.test.ts +288 -0
- package/templates/base/tests/integration/storage/upload.test.ts +1162 -0
- package/templates/base/tests/integration/storage/validation.test.ts +746 -0
- package/templates/base/tests/integration/users/crud.test.ts +1297 -0
- package/templates/base/tests/integration/users/list.test.ts +698 -0
- package/templates/base/tests/integration/vitest.config.ts +80 -0
- package/templates/base/tsconfig.app.json +18 -0
- package/templates/base/tsconfig.json +39 -0
- package/templates/base/tsconfig.node.json +16 -0
- package/templates/base/tsconfig.server.json +26 -0
- package/templates/base/tsconfig.tsbuildinfo +1 -0
- package/templates/base/vite.config.ts +46 -0
- package/templates/base/vitest.config.browser.ts +47 -0
- package/templates/base/vitest.config.frontend.ts +47 -0
- package/templates/base/vitest.config.ts +82 -0
- package/templates/base/vitest.workspace.ts +22 -0
- package/templates/modules/audit-logs/.gitkeep +0 -0
- package/templates/modules/billing/.gitkeep +0 -0
- package/templates/modules/invitations/.gitkeep +0 -0
- package/templates/modules/storage/.gitkeep +0 -0
- package/templates/modules/webhooks/.gitkeep +0 -0
- package/templates/providers/auth-email/.gitkeep +0 -0
- package/templates/providers/auth-github/.gitkeep +0 -0
- package/templates/providers/auth-google/.gitkeep +0 -0
- package/templates/providers/email-resend/.gitkeep +0 -0
- 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
|
+
})
|