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