@etus/bhono-app 0.1.5 → 0.1.6
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/index.js +0 -0
- package/package.json +5 -1
- package/templates/base/.husky/pre-push +26 -0
- package/templates/base/CLAUDE.md +5 -5
- package/templates/base/README.md +31 -20
- package/templates/base/docs/app_spec.txt +13 -10
- package/templates/base/docs/architecture/README.md +3 -0
- package/templates/base/docs/architecture/data-requirements.md +4 -3
- package/templates/base/docs/architecture/db-bootstrap.md +39 -0
- package/templates/base/docs/architecture/drizzle-migration-plan.md +125 -0
- package/templates/base/docs/architecture/erd.md +1 -1
- package/templates/base/docs/architecture/sql-standards.md +100 -0
- package/templates/base/docs/testing.md +36 -29
- package/templates/base/package.json +6 -5
- package/templates/base/pnpm-lock.yaml +0 -123
- package/templates/base/schema.sql +84 -0
- package/templates/base/scripts/init.sh +244 -59
- package/templates/base/src/client/hooks/use-auth.ts +5 -0
- package/templates/base/src/client/routes/_authenticated/dashboard.tsx +1 -1
- package/templates/base/src/client/routes/index.tsx +1 -1
- package/templates/base/src/server/db/client.ts +3 -5
- package/templates/base/src/server/db/records.ts +81 -0
- package/templates/base/src/server/db/seed.ts +3 -2
- package/templates/base/src/server/db/sql.ts +96 -0
- package/templates/base/src/server/index.ts +16 -2
- package/templates/base/src/server/lib/audit.ts +74 -26
- package/templates/base/src/server/lib/audited-db.ts +219 -109
- package/templates/base/src/server/lib/transaction.ts +10 -16
- package/templates/base/src/server/middleware/account.ts +8 -15
- package/templates/base/src/server/middleware/auth.ts +102 -38
- package/templates/base/src/server/middleware/rate-limit.ts +6 -1
- package/templates/base/src/server/routes/accounts/handlers.ts +18 -6
- package/templates/base/src/server/routes/audits/handlers.ts +3 -1
- package/templates/base/src/server/routes/auth/handlers.ts +14 -9
- package/templates/base/src/server/routes/auth/test-login.ts +99 -45
- package/templates/base/src/server/routes/health/handlers.ts +4 -4
- package/templates/base/src/server/routes/invitations/handlers.ts +6 -3
- package/templates/base/src/server/routes/users/handlers.ts +21 -14
- package/templates/base/src/server/services/accounts.ts +242 -217
- package/templates/base/src/server/services/audits.ts +114 -61
- package/templates/base/src/server/services/auth.ts +310 -180
- package/templates/base/src/server/services/invitations.ts +282 -222
- package/templates/base/src/server/services/users.ts +383 -293
- package/templates/base/src/server/types/index.ts +1 -2
- package/templates/base/{src/server/__tests__/fixtures.ts → tests/fixtures/server.ts} +3 -3
- package/templates/base/{src/client/__tests__/setup-browser.ts → tests/helpers/client-setup-browser.ts} +2 -2
- package/templates/base/{src/client/__tests__/setup.ts → tests/helpers/client-setup.ts} +1 -1
- package/templates/base/{src/client/__tests__/test-utils.tsx → tests/helpers/client-test-utils.tsx} +2 -2
- package/templates/base/{src/server/__tests__/setup.ts → tests/helpers/server.ts} +9 -9
- package/templates/base/tests/integration/accounts/crud.test.ts +2 -11
- package/templates/base/tests/integration/audits/list.test.ts +2 -11
- package/templates/base/tests/integration/auth/auth-service.test.ts +1 -10
- package/templates/base/tests/integration/auth/invitation-token.test.ts +2 -11
- package/templates/base/tests/integration/auth/logout.test.ts +2 -11
- package/templates/base/tests/integration/auth/oauth.test.ts +23 -42
- package/templates/base/tests/integration/auth/refresh-token.test.ts +1 -9
- package/templates/base/tests/integration/auth/session-expiry.test.ts +1 -9
- package/templates/base/tests/integration/auth/session.test.ts +2 -11
- package/templates/base/tests/integration/auth/super-admin.test.ts +1 -9
- package/templates/base/tests/integration/authorization/analytics-role.test.ts +2 -11
- package/templates/base/tests/integration/authorization/billing-role.test.ts +2 -11
- package/templates/base/tests/integration/authorization/guards-roles.test.ts +1 -9
- package/templates/base/tests/integration/authorization/multi-tenancy.test.ts +2 -11
- package/templates/base/tests/integration/authorization/roles.test.ts +2 -11
- package/templates/base/tests/integration/config/production-behavior.test.ts +2 -11
- package/templates/base/tests/integration/health/health.test.ts +25 -44
- package/templates/base/tests/integration/invitations/crud.test.ts +2 -11
- package/templates/base/tests/integration/invitations/email.test.ts +1 -9
- package/templates/base/tests/integration/middleware/auth.test.ts +3 -12
- package/templates/base/tests/integration/middleware/request-logger.test.ts +1 -9
- package/templates/base/tests/integration/performance/response-times.test.ts +1 -9
- package/templates/base/tests/integration/security/cookie-security.test.ts +2 -11
- package/templates/base/tests/integration/security/csrf-protection.test.ts +2 -11
- package/templates/base/tests/integration/security/log-sanitization.test.ts +1 -9
- package/templates/base/tests/integration/security/rate-limiting.test.ts +1 -9
- package/templates/base/tests/integration/security/sql-injection.test.ts +7 -18
- package/templates/base/tests/integration/security/xss-prevention.test.ts +2 -11
- package/templates/base/tests/integration/setup.ts +13 -90
- package/templates/base/tests/integration/smoke.test.ts +3 -2
- package/templates/base/tests/integration/storage/upload.test.ts +2 -11
- package/templates/base/tests/integration/storage/validation.test.ts +2 -11
- package/templates/base/tests/integration/users/crud.test.ts +2 -11
- package/templates/base/tests/integration/users/list.test.ts +2 -11
- package/templates/base/tests/integration/vitest.config.ts +2 -9
- package/templates/base/{src/server/__tests__ → tests}/mocks/db.ts +1 -1
- package/templates/base/{src/server/__tests__ → tests}/mocks/index.ts +1 -1
- package/templates/base/{src/server/__tests__ → tests}/mocks/kv.ts +1 -1
- package/templates/base/{src/server/__tests__ → tests}/mocks/r2.ts +1 -1
- package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/sidebar.test.tsx +1 -1
- package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/avatar.test.tsx +1 -1
- package/templates/base/{src/client/__tests__ → tests/unit/client/components/ui}/button.test.tsx +1 -1
- package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/card.test.tsx +1 -1
- package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/dialog.test.tsx +1 -1
- package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/input.test.tsx +1 -1
- package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/loading-skeleton.test.tsx +1 -1
- package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/skeleton.test.tsx +1 -1
- package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/sonner.test.tsx +1 -1
- package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/tabs.test.tsx +1 -1
- package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/account.test.tsx +1 -1
- package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/integrations.test.tsx +1 -1
- package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/settings.test.tsx +1 -1
- package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/team.test.tsx +1 -1
- package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/authenticated-layout.test.tsx +1 -1
- package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/dashboard.test.tsx +1 -1
- package/templates/base/{src/client/routes/__tests__ → tests/unit/client/routes}/invite.test.tsx +1 -1
- package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/login.test.tsx +1 -1
- package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/navigation.test.tsx +1 -1
- package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/root-layout.test.tsx +1 -1
- package/templates/base/{src/server/auth/__tests__ → tests/unit/server/auth}/guards.test.ts +2 -2
- package/templates/base/{src → tests/unit}/server/auth/permissions.test.ts +1 -1
- package/templates/base/{src → tests/unit}/server/auth/roles.test.ts +1 -1
- package/templates/base/tests/unit/server/db/sql.test.ts +68 -0
- package/templates/base/{src → tests/unit}/server/env.test.ts +1 -1
- package/templates/base/tests/unit/server/lib/audited-db.test.ts +78 -0
- package/templates/base/{src → tests/unit}/server/lib/email.test.ts +1 -1
- package/templates/base/{src → tests/unit}/server/lib/errors.test.ts +1 -1
- package/templates/base/{src → tests/unit}/server/lib/oauth.test.ts +1 -1
- package/templates/base/{src → tests/unit}/server/lib/pagination.test.ts +1 -1
- package/templates/base/{src → tests/unit}/server/lib/password.test.ts +1 -1
- package/templates/base/{src → tests/unit}/server/lib/providers.test.ts +1 -1
- package/templates/base/{src → tests/unit}/server/lib/r2-storage.test.ts +2 -2
- package/templates/base/{src → tests/unit}/server/lib/session.test.ts +2 -2
- package/templates/base/{src → tests/unit}/server/lib/tokens.test.ts +1 -1
- package/templates/base/{src → tests/unit}/server/lib/transaction.test.ts +5 -14
- package/templates/base/{src → tests/unit}/server/middleware/account.test.ts +16 -24
- package/templates/base/{src → tests/unit}/server/middleware/auth.test.ts +71 -42
- package/templates/base/{src → tests/unit}/server/middleware/cors.test.ts +1 -1
- package/templates/base/{src → tests/unit}/server/middleware/error-handler.test.ts +2 -2
- package/templates/base/{src → tests/unit}/server/middleware/rate-limit.test.ts +3 -2
- package/templates/base/{src → tests/unit}/server/middleware/request-context.test.ts +1 -1
- package/templates/base/{src → tests/unit}/server/middleware/request-logger.test.ts +1 -1
- package/templates/base/{src/server/__tests__/mocks/__tests__ → tests/unit/server/mocks}/db.test.ts +1 -1
- package/templates/base/{src/server/__tests__/mocks/__tests__ → tests/unit/server/mocks}/kv.test.ts +1 -1
- package/templates/base/{src/server/__tests__/mocks/__tests__ → tests/unit/server/mocks}/r2.test.ts +1 -1
- package/templates/base/{src/server/routes/accounts/__tests__ → tests/unit/server/routes/accounts}/handlers.test.ts +12 -12
- package/templates/base/{src/server/routes/audits/__tests__ → tests/unit/server/routes/audits}/handlers.test.ts +11 -11
- package/templates/base/{src/server/routes/auth/__tests__ → tests/unit/server/routes/auth}/handlers.test.ts +13 -13
- package/templates/base/{src/server/routes/health/__tests__ → tests/unit/server/routes/health}/handlers.test.ts +27 -23
- package/templates/base/{src/server/routes/invitations/__tests__ → tests/unit/server/routes/invitations}/handlers.test.ts +14 -17
- package/templates/base/{src/server/routes/storage/__tests__ → tests/unit/server/routes/storage}/handlers.test.ts +6 -6
- package/templates/base/{src/server/routes/users/__tests__ → tests/unit/server/routes/users}/handlers.test.ts +12 -12
- package/templates/base/tests/unit/server/services/accounts.test.ts +258 -0
- package/templates/base/tests/unit/server/services/audits.test.ts +141 -0
- package/templates/base/tests/unit/server/services/auth.test.ts +179 -0
- package/templates/base/tests/unit/server/services/invitations.test.ts +165 -0
- package/templates/base/tests/unit/server/services/users.test.ts +351 -0
- package/templates/base/tsconfig.json +2 -1
- package/templates/base/vitest.config.browser.ts +3 -2
- package/templates/base/vitest.config.frontend.ts +3 -2
- package/templates/base/vitest.config.ts +7 -14
- package/templates/base/.claude/settings.local.json +0 -11
- package/templates/base/config/drizzle.config.ts +0 -10
- package/templates/base/src/server/db/schema/accounts.ts +0 -20
- package/templates/base/src/server/db/schema/audit-logs.ts +0 -26
- package/templates/base/src/server/db/schema/index.ts +0 -7
- package/templates/base/src/server/db/schema/invitations.ts +0 -30
- package/templates/base/src/server/db/schema/refresh-tokens.ts +0 -22
- package/templates/base/src/server/db/schema/user-accounts.ts +0 -25
- package/templates/base/src/server/db/schema/users.ts +0 -33
- package/templates/base/src/server/lib/audited-db.test.ts +0 -107
- package/templates/base/src/server/lib/schema-helpers.ts +0 -16
- package/templates/base/src/server/services/__tests__/accounts.test.ts +0 -764
- package/templates/base/src/server/services/__tests__/audits.test.ts +0 -235
- package/templates/base/src/server/services/__tests__/auth.test.ts +0 -765
- package/templates/base/src/server/services/__tests__/invitations.test.ts +0 -704
- package/templates/base/src/server/services/__tests__/users.test.ts +0 -755
- package/templates/base/tests/integration/lib/schema-helpers.test.ts +0 -129
- /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-can-be-collapsed-by-default-1.png +0 -0
- /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-expands-when-collapsed-and-expand-button-is-clicked-1.png +0 -0
- /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-handles-logout-button-click-1.png +0 -0
- /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-handles-navigation-clicks-1.png +0 -0
- /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-hides-navigation-labels-when-collapsed-1.png +0 -0
- /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-highlights-active-route-1.png +0 -0
- /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-renders-sidebar-navigation-items-1.png +0 -0
- /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-shows-keyboard-shortcut-hint-when-expanded-1.png +0 -0
- /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-shows-user-info-when-authenticated-1.png +0 -0
- /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-shows-user-initials-in-avatar-fallback-1.png +0 -0
- /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/error-boundary.test.tsx +0 -0
- /package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/error-fallback.test.tsx +0 -0
- /package/templates/base/{src/client/hooks/__tests__ → tests/unit/client/hooks}/use-auth.test.tsx +0 -0
- /package/templates/base/{src/client/hooks/__tests__ → tests/unit/client/hooks}/use-theme.test.tsx +0 -0
- /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-dashboard-stats-cards-1.png +0 -0
- /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-quick-action-cards-1.png +0 -0
- /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-recent-activity-section-1.png +0 -0
- /package/templates/base/{src/client/__tests__ → tests/unit/client}/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__ → tests/unit/client}/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__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-render-dashboard-when-authenticated-1.png +0 -0
- /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-show-navigation-sidebar-1.png +0 -0
- /package/templates/base/{src/client/__tests__ → tests/unit/client}/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/routes/__tests__ → tests/unit/client/routes}/__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__ → tests/unit/client/routes}/__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__ → tests/unit/client/routes}/__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__ → tests/unit/client/routes}/__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__ → tests/unit/client/routes}/__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__ → tests/unit/client/routes}/__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/__tests__ → tests/unit/client}/routes/__screenshots__/login.test.tsx/Login-Page-should-display-Google-OAuth-login-button-1.png +0 -0
- /package/templates/base/{src/client/__tests__ → tests/unit/client}/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__ → tests/unit/client}/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__ → tests/unit/client}/routes/__screenshots__/login.test.tsx/Login-Page-should-render-login-page-at--login-route-1.png +0 -0
- /package/templates/base/{src/client/__tests__ → tests/unit/client}/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__ → tests/unit/client}/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__ → tests/unit/client}/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__ → tests/unit/client}/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__ → tests/unit/client}/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__ → tests/unit/client}/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__ → tests/unit/client}/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__ → tests/unit/client}/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__ → tests/unit/client}/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__ → tests/unit/client}/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__ → tests/unit/client}/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__ → tests/unit/client}/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__ → tests/unit/client}/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__ → tests/unit/client}/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__ → tests/unit/client}/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__ → tests/unit/client}/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/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__screenshots__/account.test.tsx/Account-Page-should-show-API-Access-section-1.png +0 -0
- /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-Webhooks-section-1.png +0 -0
- /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-connected-count-1.png +0 -0
- /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-page-description-1.png +0 -0
- /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-search-input-1.png +0 -0
- /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-search-functionality-should-search-by-description-1.png +0 -0
- /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-existing-webhook-1.png +0 -0
- /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-webhook-events-1.png +0 -0
- /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-page-rendering-should-display-page-description-1.png +0 -0
- /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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__ → tests/unit/client/routes/_authenticated}/__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/__tests__ → tests/unit/client}/routes/error-components.test.tsx +0 -0
- /package/templates/base/{src/shared/schemas/__tests__ → tests/unit/shared}/schemas.test.ts +0 -0
|
@@ -13,28 +13,10 @@
|
|
|
13
13
|
|
|
14
14
|
import { describe, it, expect, beforeAll } from 'vitest'
|
|
15
15
|
import { Hono } from 'hono'
|
|
16
|
-
import { getEnv,
|
|
16
|
+
import { getEnv, type TestEnv } from '../setup'
|
|
17
17
|
import type { HonoEnv } from '../../../src/server/types'
|
|
18
18
|
import { health } from '../../../src/server/routes/health'
|
|
19
19
|
|
|
20
|
-
/**
|
|
21
|
-
* Creates a database wrapper that adds the `execute` method
|
|
22
|
-
* The better-sqlite3 drizzle doesn't have execute, but D1 does
|
|
23
|
-
* We add it as an alias to `run` for test compatibility
|
|
24
|
-
*/
|
|
25
|
-
function createTestDb() {
|
|
26
|
-
const db = getDb()
|
|
27
|
-
// Add execute method that delegates to run (both execute statements)
|
|
28
|
-
return new Proxy(db, {
|
|
29
|
-
get(target, prop) {
|
|
30
|
-
if (prop === 'execute') {
|
|
31
|
-
return target.run.bind(target)
|
|
32
|
-
}
|
|
33
|
-
return (target as any)[prop]
|
|
34
|
-
},
|
|
35
|
-
})
|
|
36
|
-
}
|
|
37
|
-
|
|
38
20
|
describe('Health Check Integration', () => {
|
|
39
21
|
let app: Hono<HonoEnv>
|
|
40
22
|
let env: TestEnv
|
|
@@ -47,11 +29,6 @@ describe('Health Check Integration', () => {
|
|
|
47
29
|
app.use('*', async (c, next) => {
|
|
48
30
|
// Inject environment bindings (cast to any to allow assignment)
|
|
49
31
|
;(c as any).env = env
|
|
50
|
-
|
|
51
|
-
// Use a wrapped drizzle instance that has execute method for D1 compatibility
|
|
52
|
-
const db = createTestDb()
|
|
53
|
-
c.set('db', db)
|
|
54
|
-
|
|
55
32
|
await next()
|
|
56
33
|
})
|
|
57
34
|
|
|
@@ -225,8 +202,10 @@ describe('Health Check Integration', () => {
|
|
|
225
202
|
// Create an app without db set
|
|
226
203
|
const appWithoutDb = new Hono<HonoEnv>()
|
|
227
204
|
appWithoutDb.use('*', async (c, next) => {
|
|
228
|
-
|
|
229
|
-
//
|
|
205
|
+
const modifiedEnv = { ...env }
|
|
206
|
+
// @ts-expect-error - Removing DB to test database down
|
|
207
|
+
delete modifiedEnv.DB
|
|
208
|
+
;(c as any).env = modifiedEnv
|
|
230
209
|
await next()
|
|
231
210
|
})
|
|
232
211
|
appWithoutDb.route('/health', health)
|
|
@@ -247,7 +226,6 @@ describe('Health Check Integration', () => {
|
|
|
247
226
|
// @ts-expect-error - Removing R2_BUCKET to test line 24
|
|
248
227
|
delete modifiedEnv.R2_BUCKET
|
|
249
228
|
;(c as any).env = modifiedEnv
|
|
250
|
-
c.set('db', createTestDb())
|
|
251
229
|
await next()
|
|
252
230
|
})
|
|
253
231
|
appWithoutR2.route('/health', health)
|
|
@@ -264,14 +242,15 @@ describe('Health Check Integration', () => {
|
|
|
264
242
|
// Create an app with a broken database
|
|
265
243
|
const appWithBrokenDb = new Hono<HonoEnv>()
|
|
266
244
|
appWithBrokenDb.use('*', async (c, next) => {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
245
|
+
const modifiedEnv = {
|
|
246
|
+
...env,
|
|
247
|
+
DB: {
|
|
248
|
+
prepare: () => {
|
|
249
|
+
throw new Error('Database connection failed')
|
|
250
|
+
},
|
|
251
|
+
} as D1Database,
|
|
273
252
|
}
|
|
274
|
-
c
|
|
253
|
+
;(c as any).env = modifiedEnv
|
|
275
254
|
await next()
|
|
276
255
|
})
|
|
277
256
|
appWithBrokenDb.route('/health', health)
|
|
@@ -297,7 +276,6 @@ describe('Health Check Integration', () => {
|
|
|
297
276
|
},
|
|
298
277
|
}
|
|
299
278
|
;(c as any).env = modifiedEnv
|
|
300
|
-
c.set('db', createTestDb())
|
|
301
279
|
await next()
|
|
302
280
|
})
|
|
303
281
|
appWithBrokenR2.route('/health', health)
|
|
@@ -313,8 +291,10 @@ describe('Health Check Integration', () => {
|
|
|
313
291
|
it('should return 503 when readiness check fails due to db being down', async () => {
|
|
314
292
|
const appWithoutDb = new Hono<HonoEnv>()
|
|
315
293
|
appWithoutDb.use('*', async (c, next) => {
|
|
316
|
-
|
|
317
|
-
//
|
|
294
|
+
const modifiedEnv = { ...env }
|
|
295
|
+
// @ts-expect-error - Removing DB to test database down
|
|
296
|
+
delete modifiedEnv.DB
|
|
297
|
+
;(c as any).env = modifiedEnv
|
|
318
298
|
await next()
|
|
319
299
|
})
|
|
320
300
|
appWithoutDb.route('/health', health)
|
|
@@ -329,14 +309,15 @@ describe('Health Check Integration', () => {
|
|
|
329
309
|
it('should return 503 when readiness check throws an error', async () => {
|
|
330
310
|
const appWithBrokenDb = new Hono<HonoEnv>()
|
|
331
311
|
appWithBrokenDb.use('*', async (c, next) => {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
312
|
+
const modifiedEnv = {
|
|
313
|
+
...env,
|
|
314
|
+
DB: {
|
|
315
|
+
prepare: () => {
|
|
316
|
+
throw new Error('Database connection failed')
|
|
317
|
+
},
|
|
318
|
+
} as D1Database,
|
|
338
319
|
}
|
|
339
|
-
c
|
|
320
|
+
;(c as any).env = modifiedEnv
|
|
340
321
|
await next()
|
|
341
322
|
})
|
|
342
323
|
appWithBrokenDb.route('/health', health)
|
|
@@ -28,19 +28,10 @@ import { errorHandler } from '../../../src/server/middleware/error-handler'
|
|
|
28
28
|
import { sessionMiddleware } from '../../../src/server/lib/session'
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
|
-
* Creates a database
|
|
32
|
-
* The better-sqlite3 drizzle doesn't have execute, but D1 does
|
|
31
|
+
* Creates a D1-compatible database instance for tests
|
|
33
32
|
*/
|
|
34
33
|
function createTestDb() {
|
|
35
|
-
|
|
36
|
-
return new Proxy(db, {
|
|
37
|
-
get(target, prop) {
|
|
38
|
-
if (prop === 'execute') {
|
|
39
|
-
return target.run.bind(target)
|
|
40
|
-
}
|
|
41
|
-
return (target as any)[prop]
|
|
42
|
-
},
|
|
43
|
-
})
|
|
34
|
+
return getDb()
|
|
44
35
|
}
|
|
45
36
|
|
|
46
37
|
describe('Invitations CRUD Integration', () => {
|
|
@@ -28,15 +28,7 @@ import { sendInvitationEmail } from '../../../src/server/lib/email'
|
|
|
28
28
|
// ============================================================================
|
|
29
29
|
|
|
30
30
|
function createTestDb() {
|
|
31
|
-
|
|
32
|
-
return new Proxy(db, {
|
|
33
|
-
get(target, prop) {
|
|
34
|
-
if (prop === 'execute') {
|
|
35
|
-
return target.run.bind(target)
|
|
36
|
-
}
|
|
37
|
-
return (target as any)[prop]
|
|
38
|
-
},
|
|
39
|
-
})
|
|
31
|
+
return getDb()
|
|
40
32
|
}
|
|
41
33
|
|
|
42
34
|
describe('Invitation Email Integration Tests', () => {
|
|
@@ -20,19 +20,10 @@ import {
|
|
|
20
20
|
} from '../setup'
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* Creates a database
|
|
24
|
-
* The better-sqlite3 drizzle doesn't have execute, but D1 does
|
|
23
|
+
* Creates a D1-compatible database instance for tests
|
|
25
24
|
*/
|
|
26
25
|
function createTestDb() {
|
|
27
|
-
|
|
28
|
-
return new Proxy(db, {
|
|
29
|
-
get(target, prop) {
|
|
30
|
-
if (prop === 'execute') {
|
|
31
|
-
return (target as any).run.bind(target)
|
|
32
|
-
}
|
|
33
|
-
return (target as any)[prop]
|
|
34
|
-
},
|
|
35
|
-
})
|
|
26
|
+
return getDb()
|
|
36
27
|
}
|
|
37
28
|
|
|
38
29
|
describe('Auth Middleware', () => {
|
|
@@ -57,7 +48,7 @@ describe('Auth Middleware', () => {
|
|
|
57
48
|
const env = getEnv()
|
|
58
49
|
;(c as any).env = env
|
|
59
50
|
|
|
60
|
-
// Set up the db for middleware
|
|
51
|
+
// Set up the db for middleware
|
|
61
52
|
const db = createTestDb()
|
|
62
53
|
c.set('db', db)
|
|
63
54
|
|
|
@@ -21,15 +21,7 @@ import { errorHandler } from '../../../src/server/middleware/error-handler'
|
|
|
21
21
|
* Creates a database wrapper
|
|
22
22
|
*/
|
|
23
23
|
function createTestDb() {
|
|
24
|
-
|
|
25
|
-
return new Proxy(db, {
|
|
26
|
-
get(target, prop) {
|
|
27
|
-
if (prop === 'execute') {
|
|
28
|
-
return target.run.bind(target)
|
|
29
|
-
}
|
|
30
|
-
return (target as any)[prop]
|
|
31
|
-
},
|
|
32
|
-
})
|
|
24
|
+
return getDb()
|
|
33
25
|
}
|
|
34
26
|
|
|
35
27
|
describe('Request Logger Middleware Integration', () => {
|
|
@@ -36,15 +36,7 @@ const THRESHOLDS = {
|
|
|
36
36
|
* Creates a database wrapper that adds the `execute` method
|
|
37
37
|
*/
|
|
38
38
|
function createTestDb() {
|
|
39
|
-
|
|
40
|
-
return new Proxy(db, {
|
|
41
|
-
get(target, prop) {
|
|
42
|
-
if (prop === 'execute') {
|
|
43
|
-
return target.run.bind(target)
|
|
44
|
-
}
|
|
45
|
-
return (target as unknown as Record<string | symbol, unknown>)[prop]
|
|
46
|
-
},
|
|
47
|
-
})
|
|
39
|
+
return getDb()
|
|
48
40
|
}
|
|
49
41
|
|
|
50
42
|
/**
|
|
@@ -27,19 +27,10 @@ import { errorHandler } from '../../../src/server/middleware/error-handler'
|
|
|
27
27
|
import { sessionMiddleware } from '../../../src/server/lib/session'
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
* Creates a database
|
|
31
|
-
* The better-sqlite3 drizzle doesn't have execute, but D1 does
|
|
30
|
+
* Creates a D1-compatible database instance for tests
|
|
32
31
|
*/
|
|
33
32
|
function createTestDb() {
|
|
34
|
-
|
|
35
|
-
return new Proxy(db, {
|
|
36
|
-
get(target, prop) {
|
|
37
|
-
if (prop === 'execute') {
|
|
38
|
-
return target.run.bind(target)
|
|
39
|
-
}
|
|
40
|
-
return (target as any)[prop]
|
|
41
|
-
},
|
|
42
|
-
})
|
|
33
|
+
return getDb()
|
|
43
34
|
}
|
|
44
35
|
|
|
45
36
|
describe('Session Cookie Security', () => {
|
|
@@ -24,19 +24,10 @@ import { configurableCors } from '../../../src/server/middleware/cors'
|
|
|
24
24
|
import { sessionMiddleware } from '../../../src/server/lib/session'
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* Creates a database
|
|
28
|
-
* The better-sqlite3 drizzle doesn't have execute, but D1 does
|
|
27
|
+
* Creates a D1-compatible database instance for tests
|
|
29
28
|
*/
|
|
30
29
|
function createTestDb() {
|
|
31
|
-
|
|
32
|
-
return new Proxy(db, {
|
|
33
|
-
get(target, prop) {
|
|
34
|
-
if (prop === 'execute') {
|
|
35
|
-
return target.run.bind(target)
|
|
36
|
-
}
|
|
37
|
-
return (target as any)[prop]
|
|
38
|
-
},
|
|
39
|
-
})
|
|
30
|
+
return getDb()
|
|
40
31
|
}
|
|
41
32
|
|
|
42
33
|
describe('CSRF Protection', () => {
|
|
@@ -30,15 +30,7 @@ import { sessionMiddleware } from '../../../src/server/lib/session'
|
|
|
30
30
|
* Creates a database wrapper that adds the `execute` method
|
|
31
31
|
*/
|
|
32
32
|
function createTestDb() {
|
|
33
|
-
|
|
34
|
-
return new Proxy(db, {
|
|
35
|
-
get(target, prop) {
|
|
36
|
-
if (prop === 'execute') {
|
|
37
|
-
return target.run.bind(target)
|
|
38
|
-
}
|
|
39
|
-
return (target as any)[prop]
|
|
40
|
-
},
|
|
41
|
-
})
|
|
33
|
+
return getDb()
|
|
42
34
|
}
|
|
43
35
|
|
|
44
36
|
describe('Log Sanitization', () => {
|
|
@@ -31,15 +31,7 @@ import {
|
|
|
31
31
|
* Creates a database wrapper that adds the `execute` method
|
|
32
32
|
*/
|
|
33
33
|
function createTestDb() {
|
|
34
|
-
|
|
35
|
-
return new Proxy(db, {
|
|
36
|
-
get(target, prop) {
|
|
37
|
-
if (prop === 'execute') {
|
|
38
|
-
return target.run.bind(target)
|
|
39
|
-
}
|
|
40
|
-
return (target as any)[prop]
|
|
41
|
-
},
|
|
42
|
-
})
|
|
34
|
+
return getDb()
|
|
43
35
|
}
|
|
44
36
|
|
|
45
37
|
describe('Rate Limiting', () => {
|
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
* SQL Injection Prevention Integration Tests
|
|
3
3
|
*
|
|
4
4
|
* Tests that the API properly prevents SQL injection attacks through:
|
|
5
|
-
* - Parameterized queries via
|
|
5
|
+
* - Parameterized queries via SQL helpers
|
|
6
6
|
* - Zod UUID validation for ID parameters
|
|
7
7
|
* - Safe handling of user input in search queries
|
|
8
8
|
*
|
|
9
|
-
* Since the codebase uses
|
|
10
|
-
* these tests should PASS - demonstrating
|
|
11
|
-
* is
|
|
9
|
+
* Since the codebase uses parameterized SQL queries,
|
|
10
|
+
* these tests should PASS - demonstrating SQL injection protection
|
|
11
|
+
* is in place by default.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { describe, it, expect, beforeAll } from 'vitest'
|
|
15
15
|
import { Hono } from 'hono'
|
|
16
|
-
import { getEnv, getSqlite, type TestEnv } from '../setup'
|
|
16
|
+
import { getEnv, getDb, getSqlite, type TestEnv } from '../setup'
|
|
17
17
|
import { createTestScenario } from '../fixtures'
|
|
18
18
|
import type { HonoEnv } from '../../../src/server/types'
|
|
19
19
|
import { api } from '../../../src/server/routes'
|
|
@@ -21,21 +21,10 @@ import { errorHandler } from '../../../src/server/middleware/error-handler'
|
|
|
21
21
|
import { sessionMiddleware } from '../../../src/server/lib/session'
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
* Creates a database
|
|
25
|
-
* The better-sqlite3 drizzle doesn't have execute, but D1 does
|
|
24
|
+
* Creates a D1-compatible database instance for tests
|
|
26
25
|
*/
|
|
27
26
|
function createTestDb() {
|
|
28
|
-
|
|
29
|
-
const { drizzle } = require('drizzle-orm/better-sqlite3')
|
|
30
|
-
const db = drizzle(sqlite)
|
|
31
|
-
return new Proxy(db, {
|
|
32
|
-
get(target, prop) {
|
|
33
|
-
if (prop === 'execute') {
|
|
34
|
-
return target.run.bind(target)
|
|
35
|
-
}
|
|
36
|
-
return (target as any)[prop]
|
|
37
|
-
},
|
|
38
|
-
})
|
|
27
|
+
return getDb()
|
|
39
28
|
}
|
|
40
29
|
|
|
41
30
|
describe('SQL Injection Prevention', () => {
|
|
@@ -23,19 +23,10 @@ import { errorHandler } from '../../../src/server/middleware/error-handler'
|
|
|
23
23
|
import { sessionMiddleware } from '../../../src/server/lib/session'
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* Creates a database
|
|
27
|
-
* The better-sqlite3 drizzle doesn't have execute, but D1 does
|
|
26
|
+
* Creates a D1-compatible database instance for tests
|
|
28
27
|
*/
|
|
29
28
|
function createTestDb() {
|
|
30
|
-
|
|
31
|
-
return new Proxy(db, {
|
|
32
|
-
get(target, prop) {
|
|
33
|
-
if (prop === 'execute') {
|
|
34
|
-
return target.run.bind(target)
|
|
35
|
-
}
|
|
36
|
-
return (target as any)[prop]
|
|
37
|
-
},
|
|
38
|
-
})
|
|
29
|
+
return getDb()
|
|
39
30
|
}
|
|
40
31
|
|
|
41
32
|
describe('XSS Prevention', () => {
|
|
@@ -10,12 +10,13 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import Database from 'better-sqlite3'
|
|
13
|
-
import {
|
|
13
|
+
import { readFileSync } from 'node:fs'
|
|
14
|
+
import { dirname, resolve } from 'node:path'
|
|
15
|
+
import { fileURLToPath } from 'node:url'
|
|
14
16
|
import { vi, beforeAll, afterAll, beforeEach, afterEach } from 'vitest'
|
|
15
17
|
import { Hono } from 'hono'
|
|
16
18
|
import type { Env } from '../../src/server/env'
|
|
17
19
|
import type { SessionData, HonoEnv } from '../../src/server/types'
|
|
18
|
-
import * as schema from '../../src/server/db/schema'
|
|
19
20
|
|
|
20
21
|
// ============================================================================
|
|
21
22
|
// TYPES
|
|
@@ -50,90 +51,15 @@ export interface MockR2Store {
|
|
|
50
51
|
// DATABASE SCHEMA SQL
|
|
51
52
|
// ============================================================================
|
|
52
53
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
id TEXT PRIMARY KEY,
|
|
57
|
-
google_id TEXT NOT NULL UNIQUE,
|
|
58
|
-
email TEXT NOT NULL,
|
|
59
|
-
name TEXT NOT NULL,
|
|
60
|
-
avatar_url TEXT,
|
|
61
|
-
status TEXT DEFAULT 'active' NOT NULL CHECK (status IN ('active', 'inactive')),
|
|
62
|
-
provider_ids TEXT DEFAULT '[]',
|
|
63
|
-
is_super_admin INTEGER DEFAULT 0 NOT NULL,
|
|
64
|
-
created_at TEXT DEFAULT (datetime('now')) NOT NULL,
|
|
65
|
-
updated_at TEXT DEFAULT (datetime('now')) NOT NULL,
|
|
66
|
-
deleted_at TEXT,
|
|
67
|
-
created_by_id TEXT REFERENCES users(id),
|
|
68
|
-
updated_by_id TEXT REFERENCES users(id),
|
|
69
|
-
deleted_by_id TEXT REFERENCES users(id)
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
-- Accounts table
|
|
73
|
-
CREATE TABLE IF NOT EXISTS accounts (
|
|
74
|
-
id TEXT PRIMARY KEY,
|
|
75
|
-
name TEXT NOT NULL,
|
|
76
|
-
description TEXT,
|
|
77
|
-
domain TEXT UNIQUE,
|
|
78
|
-
created_at TEXT DEFAULT (datetime('now')) NOT NULL,
|
|
79
|
-
updated_at TEXT DEFAULT (datetime('now')) NOT NULL,
|
|
80
|
-
deleted_at TEXT
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
-- User-Accounts junction table
|
|
84
|
-
CREATE TABLE IF NOT EXISTS user_accounts (
|
|
85
|
-
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
86
|
-
account_id TEXT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
|
|
87
|
-
role TEXT NOT NULL CHECK (role IN ('ADMIN', 'MANAGER', 'EDITOR', 'AUTHOR', 'VIEWER', 'BILLING', 'ANALYTICS')),
|
|
88
|
-
PRIMARY KEY (user_id, account_id)
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
-- Refresh tokens table
|
|
92
|
-
CREATE TABLE IF NOT EXISTS refresh_tokens (
|
|
93
|
-
id TEXT PRIMARY KEY,
|
|
94
|
-
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
95
|
-
token_hash TEXT NOT NULL,
|
|
96
|
-
expires_at INTEGER NOT NULL,
|
|
97
|
-
created_at INTEGER DEFAULT (unixepoch()) NOT NULL,
|
|
98
|
-
revoked_at INTEGER
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
-- Invitations table
|
|
102
|
-
CREATE TABLE IF NOT EXISTS invitations (
|
|
103
|
-
id TEXT PRIMARY KEY,
|
|
104
|
-
account_id TEXT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
|
|
105
|
-
email TEXT NOT NULL,
|
|
106
|
-
role TEXT NOT NULL CHECK (role IN ('ADMIN', 'MANAGER', 'EDITOR', 'AUTHOR', 'VIEWER', 'BILLING', 'ANALYTICS')),
|
|
107
|
-
token TEXT NOT NULL UNIQUE,
|
|
108
|
-
invited_by_id TEXT NOT NULL REFERENCES users(id),
|
|
109
|
-
expires_at TEXT NOT NULL,
|
|
110
|
-
accepted_at TEXT,
|
|
111
|
-
created_at TEXT DEFAULT (datetime('now')) NOT NULL
|
|
112
|
-
);
|
|
113
|
-
CREATE UNIQUE INDEX IF NOT EXISTS account_email_idx ON invitations(account_id, email);
|
|
114
|
-
|
|
115
|
-
-- Audit logs table
|
|
116
|
-
CREATE TABLE IF NOT EXISTS audit_logs (
|
|
117
|
-
id TEXT PRIMARY KEY,
|
|
118
|
-
transaction_id TEXT NOT NULL,
|
|
119
|
-
account_id TEXT REFERENCES accounts(id),
|
|
120
|
-
user_id TEXT REFERENCES users(id),
|
|
121
|
-
entity TEXT NOT NULL,
|
|
122
|
-
entity_id TEXT NOT NULL,
|
|
123
|
-
action TEXT NOT NULL CHECK (action IN ('INSERT', 'UPDATE', 'DELETE', 'LOGIN', 'LOGOUT', 'SIGNUP', 'TOKEN_REFRESH', 'LOGIN_FAILED')),
|
|
124
|
-
changes TEXT,
|
|
125
|
-
ip_address TEXT,
|
|
126
|
-
user_agent TEXT,
|
|
127
|
-
timestamp TEXT DEFAULT (datetime('now')) NOT NULL
|
|
128
|
-
);
|
|
129
|
-
`
|
|
54
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
55
|
+
const __dirname = dirname(__filename)
|
|
56
|
+
const SCHEMA_SQL = readFileSync(resolve(__dirname, '..', '..', 'schema.sql'), 'utf8')
|
|
130
57
|
|
|
131
58
|
// ============================================================================
|
|
132
59
|
// GLOBAL STATE
|
|
133
60
|
// ============================================================================
|
|
134
61
|
|
|
135
62
|
let sqliteDb: Database.Database | null = null
|
|
136
|
-
let drizzleDb: ReturnType<typeof drizzle> | null = null
|
|
137
63
|
let mockKV: MockKVStore | null = null
|
|
138
64
|
let mockR2: MockR2Store | null = null
|
|
139
65
|
let testEnv: TestEnv | null = null
|
|
@@ -144,8 +70,8 @@ let testEnv: TestEnv | null = null
|
|
|
144
70
|
|
|
145
71
|
/**
|
|
146
72
|
* Creates a D1-compatible wrapper around better-sqlite3
|
|
147
|
-
* This allows
|
|
148
|
-
*
|
|
73
|
+
* This allows our test helpers to use a Cloudflare D1-like API
|
|
74
|
+
* against an in-memory SQLite database.
|
|
149
75
|
*/
|
|
150
76
|
function createD1CompatibleWrapper(db: Database.Database): D1Database {
|
|
151
77
|
return {
|
|
@@ -582,13 +508,14 @@ export function getEnv(): TestEnv {
|
|
|
582
508
|
}
|
|
583
509
|
|
|
584
510
|
/**
|
|
585
|
-
* Get the
|
|
511
|
+
* Get the D1-compatible database instance
|
|
586
512
|
*/
|
|
587
|
-
export function getDb():
|
|
588
|
-
|
|
513
|
+
export function getDb(): D1Database {
|
|
514
|
+
const env = getEnv()
|
|
515
|
+
if (!env.DB) {
|
|
589
516
|
throw new Error('Database not initialized. Make sure beforeAll has run.')
|
|
590
517
|
}
|
|
591
|
-
return
|
|
518
|
+
return env.DB
|
|
592
519
|
}
|
|
593
520
|
|
|
594
521
|
/**
|
|
@@ -764,9 +691,6 @@ beforeAll(() => {
|
|
|
764
691
|
// Create D1-compatible wrapper
|
|
765
692
|
const d1Wrapper = createD1CompatibleWrapper(sqliteDb)
|
|
766
693
|
|
|
767
|
-
// Create Drizzle instance
|
|
768
|
-
drizzleDb = drizzle(sqliteDb, { schema })
|
|
769
|
-
|
|
770
694
|
// Create mock stores
|
|
771
695
|
mockKV = createMockKV()
|
|
772
696
|
mockR2 = createMockR2()
|
|
@@ -797,7 +721,6 @@ afterAll(() => {
|
|
|
797
721
|
sqliteDb = null
|
|
798
722
|
}
|
|
799
723
|
|
|
800
|
-
drizzleDb = null
|
|
801
724
|
mockKV = null
|
|
802
725
|
mockR2 = null
|
|
803
726
|
testEnv = null
|
|
@@ -55,10 +55,11 @@ describe('Integration Test Infrastructure', () => {
|
|
|
55
55
|
})
|
|
56
56
|
})
|
|
57
57
|
|
|
58
|
-
describe('
|
|
59
|
-
it('should have initialized
|
|
58
|
+
describe('D1 Database', () => {
|
|
59
|
+
it('should have initialized D1 database instance', () => {
|
|
60
60
|
const db = getDb()
|
|
61
61
|
expect(db).toBeDefined()
|
|
62
|
+
expect(typeof db.prepare).toBe('function')
|
|
62
63
|
})
|
|
63
64
|
})
|
|
64
65
|
|
|
@@ -34,19 +34,10 @@ import { sessionMiddleware } from '../../../src/server/lib/session'
|
|
|
34
34
|
// ============================================================================
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
|
-
* Creates a database
|
|
38
|
-
* The better-sqlite3 drizzle doesn't have execute, but D1 does
|
|
37
|
+
* Creates a D1-compatible database instance for tests
|
|
39
38
|
*/
|
|
40
39
|
function createTestDb() {
|
|
41
|
-
|
|
42
|
-
return new Proxy(db, {
|
|
43
|
-
get(target, prop) {
|
|
44
|
-
if (prop === 'execute') {
|
|
45
|
-
return target.run.bind(target)
|
|
46
|
-
}
|
|
47
|
-
return (target as any)[prop]
|
|
48
|
-
},
|
|
49
|
-
})
|
|
40
|
+
return getDb()
|
|
50
41
|
}
|
|
51
42
|
|
|
52
43
|
/**
|
|
@@ -31,19 +31,10 @@ import { sessionMiddleware } from '../../../src/server/lib/session'
|
|
|
31
31
|
// ============================================================================
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* Creates a database
|
|
35
|
-
* The better-sqlite3 drizzle doesn't have execute, but D1 does
|
|
34
|
+
* Creates a D1-compatible database instance for tests
|
|
36
35
|
*/
|
|
37
36
|
function createTestDb() {
|
|
38
|
-
|
|
39
|
-
return new Proxy(db, {
|
|
40
|
-
get(target, prop) {
|
|
41
|
-
if (prop === 'execute') {
|
|
42
|
-
return target.run.bind(target)
|
|
43
|
-
}
|
|
44
|
-
return (target as any)[prop]
|
|
45
|
-
},
|
|
46
|
-
})
|
|
37
|
+
return getDb()
|
|
47
38
|
}
|
|
48
39
|
|
|
49
40
|
/**
|
|
@@ -26,19 +26,10 @@ import { errorHandler } from '../../../src/server/middleware/error-handler'
|
|
|
26
26
|
import { sessionMiddleware } from '../../../src/server/lib/session'
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Creates a database
|
|
30
|
-
* The better-sqlite3 drizzle doesn't have execute, but D1 does
|
|
29
|
+
* Creates a D1-compatible database instance for tests
|
|
31
30
|
*/
|
|
32
31
|
function createTestDb() {
|
|
33
|
-
|
|
34
|
-
return new Proxy(db, {
|
|
35
|
-
get(target, prop) {
|
|
36
|
-
if (prop === 'execute') {
|
|
37
|
-
return target.run.bind(target)
|
|
38
|
-
}
|
|
39
|
-
return (target as any)[prop]
|
|
40
|
-
},
|
|
41
|
-
})
|
|
32
|
+
return getDb()
|
|
42
33
|
}
|
|
43
34
|
|
|
44
35
|
describe('Users CRUD Integration', () => {
|
|
@@ -29,19 +29,10 @@ import { errorHandler } from '../../../src/server/middleware/error-handler'
|
|
|
29
29
|
import { sessionMiddleware } from '../../../src/server/lib/session'
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* Creates a database
|
|
33
|
-
* The better-sqlite3 drizzle doesn't have execute, but D1 does
|
|
32
|
+
* Creates a D1-compatible database instance for tests
|
|
34
33
|
*/
|
|
35
34
|
function createTestDb() {
|
|
36
|
-
|
|
37
|
-
return new Proxy(db, {
|
|
38
|
-
get(target, prop) {
|
|
39
|
-
if (prop === 'execute') {
|
|
40
|
-
return target.run.bind(target)
|
|
41
|
-
}
|
|
42
|
-
return (target as any)[prop]
|
|
43
|
-
},
|
|
44
|
-
})
|
|
35
|
+
return getDb()
|
|
45
36
|
}
|
|
46
37
|
|
|
47
38
|
describe('Users List Integration', () => {
|