@podosoft/podokit 0.2.0 → 0.4.0
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/README.md +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/fullstack-nest-svelte/apps/api/package.json +29 -11
- package/dist/templates/fullstack-nest-svelte/apps/api/src/app.module.ts +3 -0
- package/dist/templates/fullstack-nest-svelte/apps/api/tsconfig.json +14 -5
- package/dist/templates/fullstack-nest-svelte/apps/web/package.json +8 -6
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/api.ts +5 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/alert/alert-action.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/alert/alert-description.svelte +23 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/alert/alert-title.svelte +23 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/alert/alert.svelte +43 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/alert/index.ts +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/avatar-badge.svelte +26 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/avatar-fallback.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/avatar-group-count.svelte +23 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/avatar-group.svelte +23 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/avatar-image.svelte +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/avatar.svelte +26 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/index.ts +22 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/badge/badge.svelte +49 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/badge/index.ts +2 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-close.svelte +11 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-content.svelte +48 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-description.svelte +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-footer.svelte +32 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-header.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-overlay.svelte +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-portal.svelte +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-title.svelte +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-trigger.svelte +11 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog.svelte +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/index.ts +34 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +44 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte +31 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte +27 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte +24 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +16 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +34 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/index.ts +54 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/index.ts +37 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-content.svelte +45 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-group-heading.svelte +21 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-group.svelte +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-item.svelte +40 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-label.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-portal.svelte +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-scroll-down-button.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-scroll-up-button.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-separator.svelte +18 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-trigger.svelte +29 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select.svelte +11 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/separator/index.ts +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/separator/separator.svelte +23 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/skeleton/index.ts +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/skeleton/skeleton.svelte +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/sonner/index.ts +1 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/sonner/sonner.svelte +34 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/index.ts +28 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-body.svelte +15 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-caption.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-cell.svelte +15 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-footer.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-head.svelte +15 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-header.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-row.svelte +15 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table.svelte +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/index.ts +18 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/tabs-content.svelte +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/tabs-list.svelte +40 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/tabs-trigger.svelte +23 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/tabs.svelte +19 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tooltip/index.ts +19 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tooltip/tooltip-content.svelte +52 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tooltip/tooltip-portal.svelte +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tooltip/tooltip-provider.svelte +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tooltip/tooltip-trigger.svelte +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tooltip/tooltip.svelte +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/i18n/ko.ts +5 -2
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/api.ts +19 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/backend-proxy.ts +43 -4
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/+page.svelte +8 -3
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/[...path]/+server.ts +12 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/auth/[...all]/+server.ts +12 -0
- package/dist/templates/fullstack-nest-svelte/dot-gitignore +5 -0
- package/dist/templates/fullstack-nest-svelte/package.json +11 -3
- package/dist/templates/fullstack-nest-svelte/tests/README.md +15 -0
- package/dist/templates/fullstack-nest-svelte/tests/api/health.api.spec.ts +14 -0
- package/dist/templates/fullstack-nest-svelte/tests/package.json +14 -0
- package/dist/templates/fullstack-nest-svelte/tests/playwright.config.ts +25 -0
- package/dist/templates/fullstack-nest-svelte/tests/ui/smoke.ui.spec.ts +8 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/app.d.ts +23 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/hooks.server.ts +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/app-sidebar.svelte +59 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/language-switch.svelte +33 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/sidebar-user-menu.svelte +44 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/table-pagination.svelte +40 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/theme-toggle.svelte +27 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/collapsible-content.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/collapsible-trigger.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/collapsible.svelte +11 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/index.ts +13 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/index.ts +31 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-content.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-ellipsis.svelte +22 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-item.svelte +14 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-link.svelte +42 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-next-button.svelte +31 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-next.svelte +26 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-prev-button.svelte +31 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-previous.svelte +26 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination.svelte +28 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/index.ts +34 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-close.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-content.svelte +55 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-description.svelte +17 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-footer.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-header.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-overlay.svelte +17 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-portal.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-title.svelte +17 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-trigger.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/constants.ts +6 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/context.svelte.ts +81 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/index.ts +75 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-content.svelte +24 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-footer.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group-action.svelte +33 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group-content.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group-label.svelte +33 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-header.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-input.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-inset.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-action.svelte +37 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte +24 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-button.svelte +102 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-item.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte +36 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte +39 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-provider.svelte +53 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-rail.svelte +36 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-separator.svelte +19 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-trigger.svelte +36 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar.svelte +108 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/user-avatar.svelte +12 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/hooks/is-mobile.svelte.ts +9 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/index.ts +27 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/messages.ts +359 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/+layout.server.ts +14 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/+layout.svelte +50 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/+page.svelte +25 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/account/+page.server.ts +5 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/account/+page.svelte +374 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/audit/+page.server.ts +8 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/audit/+page.svelte +78 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/sessions/+page.server.ts +8 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/sessions/+page.svelte +122 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/users/+page.server.ts +9 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/users/+page.svelte +406 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/+layout.svelte +13 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/forgot-password/+page.svelte +54 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/login/+page.svelte +59 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/reset-password/+page.svelte +51 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/signup/+page.svelte +60 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+layout.server.ts +22 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+layout.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+page.svelte +22 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/audit.api.spec.ts +48 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/auth.api.spec.ts +43 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/users-admin.api.spec.ts +134 -0
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/accounts.ts +6 -0
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/hydration.ts +9 -0
- package/dist/templates/modules/admin-dashboard/files/tests/playwright.config.ts +26 -0
- package/dist/templates/modules/admin-dashboard/files/tests/seed.setup.ts +29 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/account.ui.spec.ts +55 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/audit.ui.spec.ts +16 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/auth.ui.spec.ts +45 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/dashboard.ui.spec.ts +25 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/guards.ui.spec.ts +21 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/i18n.ui.spec.ts +13 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/sessions.ui.spec.ts +15 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/sidebar.ui.spec.ts +16 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/users-crud.ui.spec.ts +129 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/users.ui.spec.ts +19 -0
- package/dist/templates/modules/admin-dashboard/module.manifest.json +41 -0
- package/dist/templates/modules/api-key-auth/files/apps/api/src/api-key/api-key.decorator.ts +9 -0
- package/dist/templates/modules/api-key-auth/files/apps/api/src/api-key/api-key.guard.ts +29 -0
- package/dist/templates/modules/api-key-auth/files/apps/api/src/api-key/api-key.module.ts +9 -0
- package/dist/templates/modules/api-key-auth/files/apps/api/src/machine/machine.controller.ts +14 -0
- package/dist/templates/modules/api-key-auth/module.manifest.json +19 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/README.md +59 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-events.ts +34 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-hook.ts +70 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-log.entity.ts +46 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.controller.ts +21 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.decorator.ts +23 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.interceptor.ts +54 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.module.ts +14 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.service.ts +36 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/migrations/1720300000000-InitAuditLogs.ts +31 -0
- package/dist/templates/modules/audit-log/module.manifest.json +39 -0
- package/dist/templates/modules/auth/files/apps/api/src/account/account.controller.ts +30 -0
- package/dist/templates/modules/auth/files/apps/api/src/account/account.module.ts +5 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.ts +59 -0
- package/dist/templates/modules/auth/files/apps/api/src/health/health.controller.ts +26 -0
- package/dist/templates/modules/auth/module.manifest.json +70 -0
- package/dist/templates/modules/bullmq/module.manifest.json +11 -3
- package/dist/templates/modules/logging/files/apps/api/src/logging/logging.module.ts +29 -0
- package/dist/templates/modules/logging/module.manifest.json +27 -0
- package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.module.ts +26 -0
- package/dist/templates/modules/rate-limit/module.manifest.json +25 -0
- package/dist/templates/todo/apps/api/package.json +29 -11
- package/dist/templates/todo/apps/api/src/app.module.ts +3 -0
- package/dist/templates/todo/apps/api/tsconfig.json +14 -5
- package/dist/templates/todo/apps/web/package.json +8 -6
- package/dist/templates/todo/apps/web/src/lib/api.ts +5 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/alert/alert-action.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/alert/alert-description.svelte +23 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/alert/alert-title.svelte +23 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/alert/alert.svelte +43 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/alert/index.ts +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/avatar-badge.svelte +26 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/avatar-fallback.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/avatar-group-count.svelte +23 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/avatar-group.svelte +23 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/avatar-image.svelte +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/avatar.svelte +26 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/index.ts +22 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/badge/badge.svelte +49 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/badge/index.ts +2 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-close.svelte +11 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-content.svelte +48 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-description.svelte +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-footer.svelte +32 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-header.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-overlay.svelte +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-portal.svelte +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-title.svelte +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-trigger.svelte +11 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog.svelte +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/index.ts +34 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +44 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte +31 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte +27 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte +24 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +16 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +34 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/index.ts +54 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/select/index.ts +37 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-content.svelte +45 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-group-heading.svelte +21 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-group.svelte +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-item.svelte +40 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-label.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-portal.svelte +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-scroll-down-button.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-scroll-up-button.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-separator.svelte +18 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-trigger.svelte +29 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/select/select.svelte +11 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/separator/index.ts +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/separator/separator.svelte +23 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/skeleton/index.ts +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/skeleton/skeleton.svelte +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/sonner/index.ts +1 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/sonner/sonner.svelte +34 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/table/index.ts +28 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-body.svelte +15 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-caption.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-cell.svelte +15 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-footer.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-head.svelte +15 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-header.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-row.svelte +15 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/table/table.svelte +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/index.ts +18 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/tabs-content.svelte +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/tabs-list.svelte +40 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/tabs-trigger.svelte +23 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/tabs.svelte +19 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/tooltip/index.ts +19 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/tooltip/tooltip-content.svelte +52 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/tooltip/tooltip-portal.svelte +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/tooltip/tooltip-provider.svelte +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/tooltip/tooltip-trigger.svelte +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/tooltip/tooltip.svelte +7 -0
- package/dist/templates/todo/apps/web/src/lib/i18n/ko.ts +5 -2
- package/dist/templates/todo/apps/web/src/lib/server/api.ts +19 -0
- package/dist/templates/todo/apps/web/src/lib/server/backend-proxy.ts +43 -4
- package/dist/templates/todo/apps/web/src/routes/+page.svelte +19 -18
- package/dist/templates/todo/apps/web/src/routes/api/[...path]/+server.ts +12 -0
- package/dist/templates/todo/apps/web/src/routes/api/auth/[...all]/+server.ts +12 -0
- package/dist/templates/todo/dot-gitignore +5 -0
- package/dist/templates/todo/package.json +11 -3
- package/dist/templates/todo/tests/README.md +15 -0
- package/dist/templates/todo/tests/api/health.api.spec.ts +14 -0
- package/dist/templates/todo/tests/package.json +14 -0
- package/dist/templates/todo/tests/playwright.config.ts +25 -0
- package/dist/templates/todo/tests/ui/smoke.ui.spec.ts +8 -0
- package/package.json +2 -2
- package/dist/templates/fullstack-nest-svelte/apps/api/test/health.e2e-spec.ts +0 -23
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/health/+server.ts +0 -12
- package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/auth.controller.ts +0 -31
- package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/auth.module.ts +0 -22
- package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/auth.service.ts +0 -44
- package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/dto/login.dto.ts +0 -12
- package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/dto/register.dto.ts +0 -13
- package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/jwt-auth.guard.ts +0 -5
- package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/jwt.strategy.ts +0 -23
- package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/user.entity.ts +0 -16
- package/dist/templates/modules/auth-jwt/files/apps/api/src/migrations/1720200000000-InitUsers.ts +0 -23
- package/dist/templates/modules/auth-jwt/module.manifest.json +0 -31
- package/dist/templates/todo/apps/api/test/health.e2e-spec.ts +0 -23
- package/dist/templates/todo/apps/web/src/routes/api/health/+server.ts +0 -12
- package/dist/templates/todo/apps/web/src/routes/api/todos/+server.ts +0 -24
- package/dist/templates/todo/apps/web/src/routes/api/todos/[id]/+server.ts +0 -24
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import "../app.css";
|
|
3
|
+
import { ModeWatcher } from "mode-watcher";
|
|
4
|
+
import { setI18nContext } from "$lib/i18n";
|
|
5
|
+
import { messages, resolveLocale } from "$lib/i18n/messages";
|
|
6
|
+
|
|
7
|
+
let { children, data } = $props();
|
|
8
|
+
const locale = $derived(resolveLocale(data.locale));
|
|
9
|
+
setI18nContext({
|
|
10
|
+
get t() {
|
|
11
|
+
return messages[locale];
|
|
12
|
+
},
|
|
13
|
+
get locale() {
|
|
14
|
+
return locale;
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<ModeWatcher />
|
|
20
|
+
{@render children()}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Button } from "$lib/components/ui/button";
|
|
3
|
+
import LanguageSwitch from "$lib/components/language-switch.svelte";
|
|
4
|
+
import ThemeToggle from "$lib/components/theme-toggle.svelte";
|
|
5
|
+
import { getI18n } from "$lib/i18n";
|
|
6
|
+
|
|
7
|
+
const i18n = getI18n();
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<div class="flex min-h-svh flex-col">
|
|
11
|
+
<header class="flex items-center justify-end gap-2 p-4">
|
|
12
|
+
<LanguageSwitch />
|
|
13
|
+
<ThemeToggle />
|
|
14
|
+
</header>
|
|
15
|
+
<main class="flex flex-1 flex-col items-center justify-center gap-6 px-4 text-center">
|
|
16
|
+
<div class="flex flex-col gap-3">
|
|
17
|
+
<h1 class="text-3xl font-semibold">{i18n.t.landing.title}</h1>
|
|
18
|
+
<p class="text-muted-foreground max-w-md">{i18n.t.landing.subtitle}</p>
|
|
19
|
+
</div>
|
|
20
|
+
<Button href="/admin">{i18n.t.landing.openAdmin}</Button>
|
|
21
|
+
</main>
|
|
22
|
+
</div>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { ADMIN, USER } from "../helpers/accounts";
|
|
3
|
+
|
|
4
|
+
const base = process.env.E2E_BASE_URL ?? "http://localhost:5173";
|
|
5
|
+
const origin = { origin: base };
|
|
6
|
+
|
|
7
|
+
async function signedIn(
|
|
8
|
+
playwright: import("@playwright/test").PlaywrightWorkerArgs["playwright"],
|
|
9
|
+
account: { email: string; password: string },
|
|
10
|
+
) {
|
|
11
|
+
const ctx = await playwright.request.newContext({ baseURL: base, extraHTTPHeaders: origin });
|
|
12
|
+
await ctx.post("/api/auth/sign-in/email", { data: account });
|
|
13
|
+
return ctx;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test("audit log records auth/admin actions", async ({ playwright }) => {
|
|
17
|
+
const admin = await signedIn(playwright, ADMIN);
|
|
18
|
+
const probe = await admin.get("/api/audit-logs");
|
|
19
|
+
test.skip(probe.status() === 404, "audit-log module not installed");
|
|
20
|
+
|
|
21
|
+
// an admin action that flows through better-auth (bypasses the interceptor)
|
|
22
|
+
const email = `audit-${Date.now()}@example.com`;
|
|
23
|
+
const created = await admin.post("/api/auth/admin/create-user", {
|
|
24
|
+
data: { email, password: "password123", name: "Audit", role: "user" },
|
|
25
|
+
});
|
|
26
|
+
expect(created.ok()).toBeTruthy();
|
|
27
|
+
const userId = (await created.json())?.user?.id as string;
|
|
28
|
+
|
|
29
|
+
const res = await admin.get("/api/audit-logs");
|
|
30
|
+
expect(res.ok()).toBeTruthy();
|
|
31
|
+
const entries = (await res.json()) as Array<{ action: string; actorEmail: string | null; targetLabel: string | null }>;
|
|
32
|
+
const entry = entries.find((e) => e.action === "user.create" && e.targetLabel === email);
|
|
33
|
+
expect(entry, "user.create should be audited with the target").toBeTruthy();
|
|
34
|
+
expect(entry?.actorEmail, "audit entry records the acting admin").toBe(ADMIN.email);
|
|
35
|
+
|
|
36
|
+
await admin.post("/api/auth/admin/remove-user", { data: { userId } });
|
|
37
|
+
await admin.dispose();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("the audit log is admin-only", async ({ playwright }) => {
|
|
41
|
+
const admin = await signedIn(playwright, ADMIN);
|
|
42
|
+
test.skip((await admin.get("/api/audit-logs")).status() === 404, "audit-log module not installed");
|
|
43
|
+
await admin.dispose();
|
|
44
|
+
|
|
45
|
+
const user = await signedIn(playwright, USER);
|
|
46
|
+
expect((await user.get("/api/audit-logs")).status()).toBe(403);
|
|
47
|
+
await user.dispose();
|
|
48
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { ADMIN, USER } from "../helpers/accounts";
|
|
3
|
+
|
|
4
|
+
const base = process.env.E2E_BASE_URL ?? "http://localhost:5173";
|
|
5
|
+
const origin = { origin: base };
|
|
6
|
+
|
|
7
|
+
// Accounts are created by the `setup` project (this `api` project depends on it).
|
|
8
|
+
test("account/me is 401 when unauthenticated @smoke", async ({ request }) => {
|
|
9
|
+
const res = await request.get("/api/account/me", { headers: origin });
|
|
10
|
+
expect(res.status()).toBe(401);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("sign-in with a wrong password is rejected", async ({ request }) => {
|
|
14
|
+
const res = await request.post("/api/auth/sign-in/email", {
|
|
15
|
+
headers: origin,
|
|
16
|
+
data: { email: ADMIN.email, password: "definitely-wrong" },
|
|
17
|
+
});
|
|
18
|
+
expect(res.ok()).toBeFalsy();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("ADMIN_EMAILS account has the admin role @smoke", async ({ playwright }) => {
|
|
22
|
+
const ctx = await playwright.request.newContext({ baseURL: base, extraHTTPHeaders: origin });
|
|
23
|
+
await ctx.post("/api/auth/sign-in/email", { data: { email: ADMIN.email, password: ADMIN.password } });
|
|
24
|
+
const session = await ctx.get("/api/auth/get-session");
|
|
25
|
+
const body = await session.json();
|
|
26
|
+
expect(body?.user?.email).toBe(ADMIN.email);
|
|
27
|
+
expect(body?.user?.role).toBe("admin");
|
|
28
|
+
await ctx.dispose();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("admin can list users, a normal user cannot (403)", async ({ playwright }) => {
|
|
32
|
+
const adminCtx = await playwright.request.newContext({ baseURL: base, extraHTTPHeaders: origin });
|
|
33
|
+
await adminCtx.post("/api/auth/sign-in/email", { data: { email: ADMIN.email, password: ADMIN.password } });
|
|
34
|
+
const asAdmin = await adminCtx.get("/api/auth/admin/list-users?limit=5");
|
|
35
|
+
expect(asAdmin.ok()).toBeTruthy();
|
|
36
|
+
await adminCtx.dispose();
|
|
37
|
+
|
|
38
|
+
const userCtx = await playwright.request.newContext({ baseURL: base, extraHTTPHeaders: origin });
|
|
39
|
+
await userCtx.post("/api/auth/sign-in/email", { data: { email: USER.email, password: USER.password } });
|
|
40
|
+
const asUser = await userCtx.get("/api/auth/admin/list-users?limit=5");
|
|
41
|
+
expect(asUser.status()).toBe(403);
|
|
42
|
+
await userCtx.dispose();
|
|
43
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { ADMIN, USER } from "../helpers/accounts";
|
|
3
|
+
|
|
4
|
+
const base = process.env.E2E_BASE_URL ?? "http://localhost:5173";
|
|
5
|
+
const origin = { origin: base };
|
|
6
|
+
|
|
7
|
+
async function signedIn(
|
|
8
|
+
playwright: import("@playwright/test").PlaywrightWorkerArgs["playwright"],
|
|
9
|
+
account: { email: string; password: string },
|
|
10
|
+
) {
|
|
11
|
+
const ctx = await playwright.request.newContext({ baseURL: base, extraHTTPHeaders: origin });
|
|
12
|
+
await ctx.post("/api/auth/sign-in/email", { data: account });
|
|
13
|
+
return ctx;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test("admin can create a user, set its password, and remove it", async ({ playwright }) => {
|
|
17
|
+
const admin = await signedIn(playwright, ADMIN);
|
|
18
|
+
const email = `api-crud-${Date.now()}@example.com`;
|
|
19
|
+
|
|
20
|
+
const created = await admin.post("/api/auth/admin/create-user", {
|
|
21
|
+
data: { email, password: "password123", name: "API CRUD", role: "user" },
|
|
22
|
+
});
|
|
23
|
+
expect(created.ok()).toBeTruthy();
|
|
24
|
+
const id = (await created.json())?.user?.id as string;
|
|
25
|
+
expect(id).toBeTruthy();
|
|
26
|
+
|
|
27
|
+
const setPw = await admin.post("/api/auth/admin/set-user-password", {
|
|
28
|
+
data: { userId: id, newPassword: "newpass1234" },
|
|
29
|
+
});
|
|
30
|
+
expect(setPw.ok()).toBeTruthy();
|
|
31
|
+
|
|
32
|
+
// the new password works
|
|
33
|
+
const fresh = await playwright.request.newContext({ baseURL: base, extraHTTPHeaders: origin });
|
|
34
|
+
const signin = await fresh.post("/api/auth/sign-in/email", { data: { email, password: "newpass1234" } });
|
|
35
|
+
expect(signin.ok()).toBeTruthy();
|
|
36
|
+
await fresh.dispose();
|
|
37
|
+
|
|
38
|
+
const removed = await admin.post("/api/auth/admin/remove-user", { data: { userId: id } });
|
|
39
|
+
expect(removed.ok()).toBeTruthy();
|
|
40
|
+
await admin.dispose();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("a normal user cannot create users (403)", async ({ playwright }) => {
|
|
44
|
+
const user = await signedIn(playwright, USER);
|
|
45
|
+
const res = await user.post("/api/auth/admin/create-user", {
|
|
46
|
+
data: { email: `nope-${Date.now()}@example.com`, password: "password123", name: "Nope", role: "user" },
|
|
47
|
+
});
|
|
48
|
+
expect(res.status()).toBe(403);
|
|
49
|
+
await user.dispose();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("admin can list and revoke a user's sessions", async ({ playwright }) => {
|
|
53
|
+
const admin = await signedIn(playwright, ADMIN);
|
|
54
|
+
const email = `api-sess-${Date.now()}@example.com`;
|
|
55
|
+
const created = await admin.post("/api/auth/admin/create-user", {
|
|
56
|
+
data: { email, password: "password123", name: "API Sess", role: "user" },
|
|
57
|
+
});
|
|
58
|
+
const userId = (await created.json())?.user?.id as string;
|
|
59
|
+
|
|
60
|
+
// the user signs in -> creates a session
|
|
61
|
+
const theirs = await playwright.request.newContext({ baseURL: base, extraHTTPHeaders: origin });
|
|
62
|
+
expect((await theirs.post("/api/auth/sign-in/email", { data: { email, password: "password123" } })).ok()).toBeTruthy();
|
|
63
|
+
await theirs.dispose();
|
|
64
|
+
|
|
65
|
+
const listed = await admin.post("/api/auth/admin/list-user-sessions", { data: { userId } });
|
|
66
|
+
expect(listed.ok()).toBeTruthy();
|
|
67
|
+
expect(((await listed.json())?.sessions ?? []).length).toBeGreaterThan(0);
|
|
68
|
+
|
|
69
|
+
const revoked = await admin.post("/api/auth/admin/revoke-user-sessions", { data: { userId } });
|
|
70
|
+
expect(revoked.ok()).toBeTruthy();
|
|
71
|
+
await admin.post("/api/auth/admin/remove-user", { data: { userId } });
|
|
72
|
+
await admin.dispose();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("revokeOtherSessions keeps the current session valid", async ({ playwright }) => {
|
|
76
|
+
// Use a throwaway account so we don't revoke the shared USER's ui storageState.
|
|
77
|
+
const admin = await signedIn(playwright, ADMIN);
|
|
78
|
+
const email = `api-revoke-others-${Date.now()}@example.com`;
|
|
79
|
+
await admin.post("/api/auth/admin/create-user", { data: { email, password: "password123", name: "RO", role: "user" } });
|
|
80
|
+
await admin.dispose();
|
|
81
|
+
|
|
82
|
+
const ctx = await playwright.request.newContext({ baseURL: base, extraHTTPHeaders: origin });
|
|
83
|
+
await ctx.post("/api/auth/sign-in/email", { data: { email, password: "password123" } });
|
|
84
|
+
expect((await ctx.post("/api/auth/revoke-other-sessions")).ok()).toBeTruthy();
|
|
85
|
+
const after = await ctx.get("/api/auth/get-session");
|
|
86
|
+
expect((await after.json())?.user?.email).toBe(email); // still signed in
|
|
87
|
+
await ctx.dispose();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("sessions record a client IP (forwarded through the proxy)", async ({ playwright }) => {
|
|
91
|
+
const ctx = await signedIn(playwright, ADMIN);
|
|
92
|
+
const res = await ctx.get("/api/auth/list-sessions");
|
|
93
|
+
const sessions = (await res.json()) as Array<{ ipAddress?: string | null }>;
|
|
94
|
+
expect(sessions.length).toBeGreaterThan(0);
|
|
95
|
+
expect(sessions[0]?.ipAddress).toBeTruthy();
|
|
96
|
+
expect(sessions[0]?.ipAddress).not.toBe("0000:0000:0000:0000:0000:0000:0000:0000");
|
|
97
|
+
await ctx.dispose();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("account capabilities reports optional-feature flags", async ({ playwright }) => {
|
|
101
|
+
const ctx = await signedIn(playwright, ADMIN);
|
|
102
|
+
const res = await ctx.get("/api/account/capabilities");
|
|
103
|
+
expect(res.ok()).toBeTruthy();
|
|
104
|
+
const caps = await res.json();
|
|
105
|
+
expect(typeof caps.twoFactor).toBe("boolean");
|
|
106
|
+
expect(Array.isArray(caps.providers)).toBe(true);
|
|
107
|
+
expect(typeof caps.deleteAccount).toBe("boolean");
|
|
108
|
+
await ctx.dispose();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("admin can update a user and impersonate them", async ({ playwright }) => {
|
|
112
|
+
const admin = await signedIn(playwright, ADMIN);
|
|
113
|
+
const email = `api-edit-${Date.now()}@example.com`;
|
|
114
|
+
const created = await admin.post("/api/auth/admin/create-user", {
|
|
115
|
+
data: { email, password: "password123", name: "Edit Me", role: "user" },
|
|
116
|
+
});
|
|
117
|
+
const userId = (await created.json())?.user?.id as string;
|
|
118
|
+
|
|
119
|
+
const updated = await admin.post("/api/auth/admin/update-user", { data: { userId, data: { name: "Updated" } } });
|
|
120
|
+
expect(updated.ok()).toBeTruthy();
|
|
121
|
+
|
|
122
|
+
// impersonate on a fresh context so shared sessions are untouched
|
|
123
|
+
const imp = await playwright.request.newContext({ baseURL: base, extraHTTPHeaders: origin });
|
|
124
|
+
await imp.post("/api/auth/sign-in/email", { data: { email: ADMIN.email, password: ADMIN.password } });
|
|
125
|
+
expect((await imp.post("/api/auth/admin/impersonate-user", { data: { userId } })).ok()).toBeTruthy();
|
|
126
|
+
const session = await (await imp.get("/api/auth/get-session")).json();
|
|
127
|
+
expect(session?.user?.email).toBe(email);
|
|
128
|
+
expect(session?.session?.impersonatedBy).toBeTruthy();
|
|
129
|
+
await imp.post("/api/auth/admin/stop-impersonating");
|
|
130
|
+
await imp.dispose();
|
|
131
|
+
|
|
132
|
+
await admin.post("/api/auth/admin/remove-user", { data: { userId } });
|
|
133
|
+
await admin.dispose();
|
|
134
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type Account = { name: string; email: string; password: string };
|
|
2
|
+
export const ADMIN = { name: "Admin", email: "admin@example.com", password: "password123" };
|
|
3
|
+
export const USER = { name: "Normal User", email: "user@example.com", password: "password123" };
|
|
4
|
+
export const adminState = "playwright/.auth/admin.json";
|
|
5
|
+
export const userState = "playwright/.auth/user.json";
|
|
6
|
+
export const anonState = { cookies: [], origins: [] } as const;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Page } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
// SvelteKit dev hydration can attach event handlers slightly after load;
|
|
4
|
+
// wait for the page to settle before driving client-only interactions.
|
|
5
|
+
export async function ready(page: Page, path: string): Promise<void> {
|
|
6
|
+
await page.goto(path);
|
|
7
|
+
await page.waitForLoadState("load");
|
|
8
|
+
await page.waitForTimeout(400);
|
|
9
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineConfig, devices } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
// admin-dashboard overlay: adds a `setup` project that seeds admin/user sessions
|
|
4
|
+
// (storageState) which the `ui` project reuses. Serial + single worker because
|
|
5
|
+
// tests share one backend/DB. Runs against a live stack on E2E_BASE_URL.
|
|
6
|
+
const baseURL = process.env.E2E_BASE_URL ?? "http://localhost:5173";
|
|
7
|
+
|
|
8
|
+
export default defineConfig({
|
|
9
|
+
testDir: ".",
|
|
10
|
+
fullyParallel: false,
|
|
11
|
+
forbidOnly: !!process.env.CI,
|
|
12
|
+
retries: process.env.CI ? 2 : 0,
|
|
13
|
+
workers: 1,
|
|
14
|
+
reporter: [["list"], ["html", { open: "never" }]],
|
|
15
|
+
use: { baseURL, trace: "on-first-retry", screenshot: "only-on-failure" },
|
|
16
|
+
projects: [
|
|
17
|
+
{ name: "api", testMatch: /.*\.api\.spec\.ts/, dependencies: ["setup"] },
|
|
18
|
+
{ name: "setup", testMatch: /.*\.setup\.ts/ },
|
|
19
|
+
{
|
|
20
|
+
name: "ui",
|
|
21
|
+
testMatch: /.*\.ui\.spec\.ts/,
|
|
22
|
+
dependencies: ["setup"],
|
|
23
|
+
use: { ...devices["Desktop Chrome"], storageState: "playwright/.auth/admin.json" },
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { test as setup, expect } from "@playwright/test";
|
|
2
|
+
import { ADMIN, USER, adminState, userState, type Account } from "./helpers/accounts";
|
|
3
|
+
|
|
4
|
+
const base = process.env.E2E_BASE_URL ?? "http://localhost:5173";
|
|
5
|
+
|
|
6
|
+
// Seed a session via the API (reliable, no UI hydration races) and save its
|
|
7
|
+
// cookies as storageState for the browser `ui` project to reuse.
|
|
8
|
+
async function seedSession(
|
|
9
|
+
playwright: import("@playwright/test").PlaywrightWorkerArgs["playwright"],
|
|
10
|
+
account: Account,
|
|
11
|
+
path: string,
|
|
12
|
+
): Promise<void> {
|
|
13
|
+
const ctx = await playwright.request.newContext({ baseURL: base, extraHTTPHeaders: { origin: base } });
|
|
14
|
+
await ctx.post("/api/auth/sign-up/email", { data: account }).catch(() => undefined); // idempotent
|
|
15
|
+
const res = await ctx.post("/api/auth/sign-in/email", {
|
|
16
|
+
data: { email: account.email, password: account.password },
|
|
17
|
+
});
|
|
18
|
+
expect(res.ok(), `sign-in ${account.email}`).toBeTruthy();
|
|
19
|
+
await ctx.storageState({ path });
|
|
20
|
+
await ctx.dispose();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
setup("seed admin session", async ({ playwright }) => {
|
|
24
|
+
await seedSession(playwright, ADMIN, adminState);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
setup("seed user session", async ({ playwright }) => {
|
|
28
|
+
await seedSession(playwright, USER, userState);
|
|
29
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { ready } from "../helpers/hydration";
|
|
3
|
+
|
|
4
|
+
// admin storageState (project default)
|
|
5
|
+
test("account opens on the profile section @smoke", async ({ page }) => {
|
|
6
|
+
await ready(page, "/admin/account");
|
|
7
|
+
await expect(page.getByRole("heading", { name: "Account" })).toBeVisible();
|
|
8
|
+
await expect(page.getByLabel("Name")).toBeVisible();
|
|
9
|
+
await expect(page.getByLabel("Email")).toHaveValue("admin@example.com");
|
|
10
|
+
// save is enabled only once the name changes
|
|
11
|
+
await expect(page.getByRole("button", { name: "Save changes" })).toBeDisabled();
|
|
12
|
+
await page.getByLabel("Name").fill("Admin Renamed");
|
|
13
|
+
await expect(page.getByRole("button", { name: "Save changes" })).toBeEnabled();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("admin can update their profile name", async ({ page }) => {
|
|
17
|
+
await ready(page, "/admin/account");
|
|
18
|
+
await page.getByLabel("Name").fill("Admin Renamed");
|
|
19
|
+
await page.getByRole("button", { name: "Save changes" }).click();
|
|
20
|
+
await expect(page.getByText("Profile updated")).toBeVisible();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("account security and sessions sub-navigation", async ({ page }) => {
|
|
24
|
+
await ready(page, "/admin/account");
|
|
25
|
+
await page.getByRole("button", { name: "Security" }).click();
|
|
26
|
+
await expect(page.getByLabel("Current password")).toBeVisible();
|
|
27
|
+
await page.getByRole("button", { name: "Sessions" }).click();
|
|
28
|
+
await expect(page.getByText("Active sessions")).toBeVisible();
|
|
29
|
+
await expect(page.getByText("Current", { exact: true })).toBeVisible();
|
|
30
|
+
await expect(page.getByRole("button", { name: "Sign out other sessions" })).toBeVisible();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("account nav shows the core sections", async ({ page }) => {
|
|
34
|
+
await ready(page, "/admin/account");
|
|
35
|
+
await expect(page.getByRole("button", { name: "Profile" })).toBeVisible();
|
|
36
|
+
await expect(page.getByRole("button", { name: "Security" })).toBeVisible();
|
|
37
|
+
await expect(page.getByRole("button", { name: "Sessions" })).toBeVisible();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("two-factor setup is available when enabled", async ({ page }) => {
|
|
41
|
+
await ready(page, "/admin/account");
|
|
42
|
+
await page.getByRole("button", { name: "Security" }).click();
|
|
43
|
+
const heading = page.getByText("Two-factor authentication");
|
|
44
|
+
test.skip((await heading.count()) === 0, "two-factor not enabled");
|
|
45
|
+
await expect(heading).toBeVisible();
|
|
46
|
+
await expect(page.getByRole("button", { name: "Enable" })).toBeVisible();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("danger zone offers account deletion when enabled", async ({ page }) => {
|
|
50
|
+
await ready(page, "/admin/account");
|
|
51
|
+
const danger = page.getByRole("button", { name: "Danger zone" });
|
|
52
|
+
test.skip((await danger.count()) === 0, "account deletion not enabled");
|
|
53
|
+
await danger.click();
|
|
54
|
+
await expect(page.getByRole("button", { name: "Delete account" })).toBeVisible();
|
|
55
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { ready } from "../helpers/hydration";
|
|
3
|
+
|
|
4
|
+
// admin storageState (project default). The Audit log page is gated by the
|
|
5
|
+
// audit-log module; skip when it isn't installed in the generated app.
|
|
6
|
+
test("admin can view audit entries when the module is installed", async ({ page }) => {
|
|
7
|
+
await ready(page, "/admin");
|
|
8
|
+
const auditNav = page.getByRole("link", { name: "Audit log" });
|
|
9
|
+
test.skip((await auditNav.count()) === 0, "audit-log module not installed");
|
|
10
|
+
|
|
11
|
+
await auditNav.click();
|
|
12
|
+
await expect(page).toHaveURL(/\/admin\/audit/);
|
|
13
|
+
await expect(page.getByRole("heading", { name: "Audit log" })).toBeVisible();
|
|
14
|
+
// seed sign-ins are audited via the better-auth hook, so an action row is present
|
|
15
|
+
await expect(page.getByText("auth.login").first()).toBeVisible();
|
|
16
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { anonState, ADMIN } from "../helpers/accounts";
|
|
3
|
+
import { ready } from "../helpers/hydration";
|
|
4
|
+
|
|
5
|
+
test.use({ storageState: anonState });
|
|
6
|
+
|
|
7
|
+
test("login page renders @smoke", async ({ page }) => {
|
|
8
|
+
await page.goto("/login");
|
|
9
|
+
await expect(page.getByLabel("Email")).toBeVisible();
|
|
10
|
+
await expect(page.getByLabel("Password")).toBeVisible();
|
|
11
|
+
await expect(page.getByRole("button", { name: "Sign in", exact: true })).toBeVisible();
|
|
12
|
+
await expect(page.getByRole("link", { name: "Sign up" })).toBeVisible();
|
|
13
|
+
await expect(page.getByRole("link", { name: "Forgot?" })).toBeVisible();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("invalid credentials show an error", async ({ page }) => {
|
|
17
|
+
await ready(page, "/login");
|
|
18
|
+
await page.getByLabel("Email").fill("nobody@example.com");
|
|
19
|
+
await page.getByLabel("Password").fill("wrong-password");
|
|
20
|
+
await page.getByRole("button", { name: "Sign in", exact: true }).click();
|
|
21
|
+
await expect(page.getByRole("alert")).toBeVisible();
|
|
22
|
+
await expect(page).toHaveURL(/\/login/);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("signup page renders", async ({ page }) => {
|
|
26
|
+
await page.goto("/signup");
|
|
27
|
+
await expect(page.getByLabel("Name")).toBeVisible();
|
|
28
|
+
await expect(page.getByRole("button", { name: "Create account" })).toBeVisible();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("forgot-password shows a sent confirmation", async ({ page }) => {
|
|
32
|
+
await ready(page, "/forgot-password");
|
|
33
|
+
// Retry the submit to absorb any first-load hydration timing.
|
|
34
|
+
await expect(async () => {
|
|
35
|
+
await page.getByLabel("Email").fill(ADMIN.email);
|
|
36
|
+
await page.getByRole("button", { name: "Send reset link" }).click();
|
|
37
|
+
await expect(page.getByRole("alert")).toBeVisible({ timeout: 2000 });
|
|
38
|
+
}).toPass({ timeout: 15000 });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("reset-password without a token is disabled", async ({ page }) => {
|
|
42
|
+
await page.goto("/reset-password");
|
|
43
|
+
await expect(page.getByRole("alert")).toBeVisible();
|
|
44
|
+
await expect(page.getByRole("button", { name: "Update password" })).toBeDisabled();
|
|
45
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { ADMIN } from "../helpers/accounts";
|
|
3
|
+
import { ready } from "../helpers/hydration";
|
|
4
|
+
|
|
5
|
+
// admin storageState (project default)
|
|
6
|
+
test("overview shows the signed-in admin @smoke", async ({ page }) => {
|
|
7
|
+
await page.goto("/admin");
|
|
8
|
+
await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible();
|
|
9
|
+
// email appears in the sidebar user menu too — scope to the main content.
|
|
10
|
+
await expect(page.getByRole("main").getByText(ADMIN.email)).toBeVisible();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("sidebar navigates to sessions and account", async ({ page }) => {
|
|
14
|
+
await ready(page, "/admin");
|
|
15
|
+
await page.getByRole("link", { name: "Sessions" }).click();
|
|
16
|
+
await expect(page).toHaveURL(/\/admin\/sessions/);
|
|
17
|
+
await expect(page.getByRole("heading", { name: "User sessions" })).toBeVisible();
|
|
18
|
+
// Account is reached from the user menu at the bottom of the sidebar, not the nav
|
|
19
|
+
await expect(page.getByRole("link", { name: "Account" })).toHaveCount(0);
|
|
20
|
+
await page.getByRole("button", { name: /admin@example.com/ }).click();
|
|
21
|
+
await page.getByRole("menuitem", { name: "Account" }).click();
|
|
22
|
+
await expect(page).toHaveURL(/\/admin\/account/);
|
|
23
|
+
await expect(page.getByRole("heading", { name: "Account" })).toBeVisible();
|
|
24
|
+
await expect(page.getByLabel("Name")).toBeVisible(); // profile section
|
|
25
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { anonState, userState } from "../helpers/accounts";
|
|
3
|
+
|
|
4
|
+
test.describe("unauthenticated", () => {
|
|
5
|
+
test.use({ storageState: anonState });
|
|
6
|
+
test("protected route redirects to login @smoke", async ({ page }) => {
|
|
7
|
+
await page.goto("/admin");
|
|
8
|
+
await expect(page).toHaveURL(/\/login\?redirect=/);
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test.describe("normal user (non-admin)", () => {
|
|
13
|
+
test.use({ storageState: userState });
|
|
14
|
+
test("admin-only nav is hidden and admin routes are forbidden", async ({ page }) => {
|
|
15
|
+
await page.goto("/admin");
|
|
16
|
+
await expect(page.getByRole("link", { name: "Users" })).toHaveCount(0);
|
|
17
|
+
await expect(page.getByRole("link", { name: "Sessions" })).toHaveCount(0);
|
|
18
|
+
expect((await page.goto("/admin/users"))?.status()).toBe(403);
|
|
19
|
+
expect((await page.goto("/admin/sessions"))?.status()).toBe(403);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { anonState } from "../helpers/accounts";
|
|
3
|
+
import { ready } from "../helpers/hydration";
|
|
4
|
+
|
|
5
|
+
test.use({ storageState: anonState });
|
|
6
|
+
|
|
7
|
+
test("language switch toggles Korean and sets <html lang>", async ({ page }) => {
|
|
8
|
+
await ready(page, "/login");
|
|
9
|
+
await page.getByRole("button", { name: "Language" }).click();
|
|
10
|
+
await page.getByRole("menuitem", { name: /한국어/ }).click();
|
|
11
|
+
await expect(page.locator("html")).toHaveAttribute("lang", "ko");
|
|
12
|
+
await expect(page.getByRole("button", { name: "로그인", exact: true })).toBeVisible();
|
|
13
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { ready } from "../helpers/hydration";
|
|
3
|
+
|
|
4
|
+
// admin storageState (project default) — sessions is an admin-only view
|
|
5
|
+
test("admin sees user sessions across users @smoke", async ({ page }) => {
|
|
6
|
+
await ready(page, "/admin/sessions");
|
|
7
|
+
await expect(page.getByRole("heading", { name: "User sessions" })).toBeVisible();
|
|
8
|
+
// another user's session is listed here (the across-users admin view); scope to
|
|
9
|
+
// the table, not the sidebar, and match the first row.
|
|
10
|
+
await expect(page.getByRole("main").getByText("user@example.com").first()).toBeVisible();
|
|
11
|
+
// row actions are available
|
|
12
|
+
await page.getByRole("row", { name: /user@example.com/ }).first().getByRole("button").click();
|
|
13
|
+
await expect(page.getByRole("menuitem", { name: "Revoke session" })).toBeVisible();
|
|
14
|
+
await expect(page.getByRole("menuitem", { name: "Revoke all for user" })).toBeVisible();
|
|
15
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { ready } from "../helpers/hydration";
|
|
3
|
+
|
|
4
|
+
// admin storageState (project default)
|
|
5
|
+
test("collapsing the sidebar hides the brand instead of overflowing @smoke", async ({ page }) => {
|
|
6
|
+
await ready(page, "/admin");
|
|
7
|
+
const brand = page.getByTestId("sidebar-brand");
|
|
8
|
+
const toggle = page.getByRole("button", { name: "Toggle Sidebar" });
|
|
9
|
+
await expect(brand).toBeVisible();
|
|
10
|
+
|
|
11
|
+
await toggle.click();
|
|
12
|
+
await expect(brand).toBeHidden(); // icon-collapsed: label is hidden, not spilling out
|
|
13
|
+
|
|
14
|
+
await toggle.click();
|
|
15
|
+
await expect(brand).toBeVisible();
|
|
16
|
+
});
|