create-blitzpack 0.1.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/dist/index.js +452 -0
- package/package.json +57 -0
- package/template/.dockerignore +59 -0
- package/template/.github/workflows/ci.yml +157 -0
- package/template/.husky/pre-commit +1 -0
- package/template/.husky/pre-push +1 -0
- package/template/.lintstagedrc.cjs +4 -0
- package/template/.nvmrc +1 -0
- package/template/.prettierrc +9 -0
- package/template/.vscode/settings.json +13 -0
- package/template/CLAUDE.md +175 -0
- package/template/CONTRIBUTING.md +32 -0
- package/template/Dockerfile +90 -0
- package/template/GETTING_STARTED.md +35 -0
- package/template/LICENSE +21 -0
- package/template/README.md +116 -0
- package/template/apps/api/.dockerignore +51 -0
- package/template/apps/api/.env.local.example +62 -0
- package/template/apps/api/emails/account-deleted-email.tsx +69 -0
- package/template/apps/api/emails/components/email-layout.tsx +154 -0
- package/template/apps/api/emails/config.ts +22 -0
- package/template/apps/api/emails/password-changed-email.tsx +88 -0
- package/template/apps/api/emails/password-reset-email.tsx +86 -0
- package/template/apps/api/emails/verification-email.tsx +85 -0
- package/template/apps/api/emails/welcome-email.tsx +70 -0
- package/template/apps/api/package.json +84 -0
- package/template/apps/api/prisma/migrations/20251012111439_init/migration.sql +13 -0
- package/template/apps/api/prisma/migrations/20251018162629_add_better_auth_fields/migration.sql +67 -0
- package/template/apps/api/prisma/migrations/20251019142208_add_user_role_enum/migration.sql +5 -0
- package/template/apps/api/prisma/migrations/20251019182151_user_auth/migration.sql +7 -0
- package/template/apps/api/prisma/migrations/20251019211416_faster_session_lookup/migration.sql +2 -0
- package/template/apps/api/prisma/migrations/20251119124337_add_upload_model/migration.sql +26 -0
- package/template/apps/api/prisma/migrations/20251120071241_add_scope_to_account/migration.sql +2 -0
- package/template/apps/api/prisma/migrations/20251120072608_add_oauth_token_expiration_fields/migration.sql +10 -0
- package/template/apps/api/prisma/migrations/20251120144705_add_audit_logs/migration.sql +29 -0
- package/template/apps/api/prisma/migrations/20251127123614_remove_impersonated_by/migration.sql +8 -0
- package/template/apps/api/prisma/migrations/20251127125630_remove_audit_logs/migration.sql +11 -0
- package/template/apps/api/prisma/migrations/migration_lock.toml +3 -0
- package/template/apps/api/prisma/schema.prisma +116 -0
- package/template/apps/api/prisma/seed.ts +159 -0
- package/template/apps/api/prisma.config.ts +14 -0
- package/template/apps/api/src/app.ts +377 -0
- package/template/apps/api/src/common/logger.service.ts +227 -0
- package/template/apps/api/src/config/env.ts +60 -0
- package/template/apps/api/src/config/rate-limit.ts +29 -0
- package/template/apps/api/src/hooks/auth.ts +122 -0
- package/template/apps/api/src/plugins/auth.ts +198 -0
- package/template/apps/api/src/plugins/database.ts +45 -0
- package/template/apps/api/src/plugins/logger.ts +33 -0
- package/template/apps/api/src/plugins/multipart.ts +16 -0
- package/template/apps/api/src/plugins/scalar.ts +20 -0
- package/template/apps/api/src/plugins/schedule.ts +52 -0
- package/template/apps/api/src/plugins/services.ts +66 -0
- package/template/apps/api/src/plugins/swagger.ts +56 -0
- package/template/apps/api/src/routes/accounts.ts +91 -0
- package/template/apps/api/src/routes/admin-sessions.ts +92 -0
- package/template/apps/api/src/routes/metrics.ts +71 -0
- package/template/apps/api/src/routes/password.ts +46 -0
- package/template/apps/api/src/routes/sessions.ts +53 -0
- package/template/apps/api/src/routes/stats.ts +38 -0
- package/template/apps/api/src/routes/uploads-serve.ts +27 -0
- package/template/apps/api/src/routes/uploads.ts +154 -0
- package/template/apps/api/src/routes/users.ts +114 -0
- package/template/apps/api/src/routes/verification.ts +90 -0
- package/template/apps/api/src/server.ts +34 -0
- package/template/apps/api/src/services/accounts.service.ts +125 -0
- package/template/apps/api/src/services/authorization.service.ts +162 -0
- package/template/apps/api/src/services/email.service.ts +170 -0
- package/template/apps/api/src/services/file-storage.service.ts +267 -0
- package/template/apps/api/src/services/metrics.service.ts +175 -0
- package/template/apps/api/src/services/password.service.ts +56 -0
- package/template/apps/api/src/services/sessions.service.spec.ts +134 -0
- package/template/apps/api/src/services/sessions.service.ts +276 -0
- package/template/apps/api/src/services/stats.service.ts +273 -0
- package/template/apps/api/src/services/uploads.service.ts +163 -0
- package/template/apps/api/src/services/users.service.spec.ts +249 -0
- package/template/apps/api/src/services/users.service.ts +198 -0
- package/template/apps/api/src/utils/file-validation.ts +108 -0
- package/template/apps/api/start.sh +33 -0
- package/template/apps/api/test/helpers/fastify-app.ts +24 -0
- package/template/apps/api/test/helpers/mock-authorization.ts +16 -0
- package/template/apps/api/test/helpers/mock-logger.ts +28 -0
- package/template/apps/api/test/helpers/mock-prisma.ts +30 -0
- package/template/apps/api/test/helpers/test-db.ts +125 -0
- package/template/apps/api/test/integration/auth-flow.integration.spec.ts +449 -0
- package/template/apps/api/test/integration/password.integration.spec.ts +427 -0
- package/template/apps/api/test/integration/rate-limit.integration.spec.ts +51 -0
- package/template/apps/api/test/integration/sessions.integration.spec.ts +445 -0
- package/template/apps/api/test/integration/users.integration.spec.ts +211 -0
- package/template/apps/api/test/setup.ts +31 -0
- package/template/apps/api/tsconfig.json +26 -0
- package/template/apps/api/vitest.config.ts +35 -0
- package/template/apps/web/.env.local.example +11 -0
- package/template/apps/web/components.json +24 -0
- package/template/apps/web/next.config.ts +22 -0
- package/template/apps/web/package.json +56 -0
- package/template/apps/web/postcss.config.js +5 -0
- package/template/apps/web/public/apple-icon.png +0 -0
- package/template/apps/web/public/icon.png +0 -0
- package/template/apps/web/public/robots.txt +3 -0
- package/template/apps/web/src/app/(admin)/admin/layout.tsx +222 -0
- package/template/apps/web/src/app/(admin)/admin/page.tsx +157 -0
- package/template/apps/web/src/app/(admin)/admin/sessions/page.tsx +18 -0
- package/template/apps/web/src/app/(admin)/admin/users/page.tsx +20 -0
- package/template/apps/web/src/app/(auth)/forgot-password/page.tsx +177 -0
- package/template/apps/web/src/app/(auth)/login/page.tsx +159 -0
- package/template/apps/web/src/app/(auth)/reset-password/page.tsx +245 -0
- package/template/apps/web/src/app/(auth)/signup/page.tsx +153 -0
- package/template/apps/web/src/app/dashboard/change-password/page.tsx +255 -0
- package/template/apps/web/src/app/dashboard/page.tsx +296 -0
- package/template/apps/web/src/app/error.tsx +32 -0
- package/template/apps/web/src/app/examples/file-upload/page.tsx +200 -0
- package/template/apps/web/src/app/favicon.ico +0 -0
- package/template/apps/web/src/app/global-error.tsx +96 -0
- package/template/apps/web/src/app/globals.css +22 -0
- package/template/apps/web/src/app/icon.png +0 -0
- package/template/apps/web/src/app/layout.tsx +34 -0
- package/template/apps/web/src/app/not-found.tsx +28 -0
- package/template/apps/web/src/app/page.tsx +192 -0
- package/template/apps/web/src/components/admin/activity-feed.tsx +101 -0
- package/template/apps/web/src/components/admin/charts/auth-breakdown-chart.tsx +114 -0
- package/template/apps/web/src/components/admin/charts/chart-tooltip.tsx +124 -0
- package/template/apps/web/src/components/admin/charts/realtime-metrics-chart.tsx +511 -0
- package/template/apps/web/src/components/admin/charts/role-distribution-chart.tsx +102 -0
- package/template/apps/web/src/components/admin/charts/session-activity-chart.tsx +90 -0
- package/template/apps/web/src/components/admin/charts/user-growth-chart.tsx +108 -0
- package/template/apps/web/src/components/admin/health-indicator.tsx +175 -0
- package/template/apps/web/src/components/admin/refresh-control.tsx +90 -0
- package/template/apps/web/src/components/admin/session-revoke-all-dialog.tsx +79 -0
- package/template/apps/web/src/components/admin/session-revoke-dialog.tsx +74 -0
- package/template/apps/web/src/components/admin/sessions-management-table.tsx +372 -0
- package/template/apps/web/src/components/admin/stat-card.tsx +137 -0
- package/template/apps/web/src/components/admin/user-create-dialog.tsx +152 -0
- package/template/apps/web/src/components/admin/user-delete-dialog.tsx +73 -0
- package/template/apps/web/src/components/admin/user-edit-dialog.tsx +170 -0
- package/template/apps/web/src/components/admin/users-management-table.tsx +285 -0
- package/template/apps/web/src/components/auth/email-verification-banner.tsx +85 -0
- package/template/apps/web/src/components/auth/github-button.tsx +40 -0
- package/template/apps/web/src/components/auth/google-button.tsx +54 -0
- package/template/apps/web/src/components/auth/protected-route.tsx +66 -0
- package/template/apps/web/src/components/auth/redirect-if-authenticated.tsx +31 -0
- package/template/apps/web/src/components/auth/with-auth.tsx +30 -0
- package/template/apps/web/src/components/error/error-card.tsx +47 -0
- package/template/apps/web/src/components/error/forbidden.tsx +25 -0
- package/template/apps/web/src/components/landing/command-block.tsx +64 -0
- package/template/apps/web/src/components/landing/feature-card.tsx +60 -0
- package/template/apps/web/src/components/landing/included-feature-card.tsx +63 -0
- package/template/apps/web/src/components/landing/logo.tsx +41 -0
- package/template/apps/web/src/components/landing/tech-badge.tsx +11 -0
- package/template/apps/web/src/components/layout/auth-nav.tsx +58 -0
- package/template/apps/web/src/components/layout/footer.tsx +3 -0
- package/template/apps/web/src/config/landing-data.ts +152 -0
- package/template/apps/web/src/config/site.ts +5 -0
- package/template/apps/web/src/hooks/api/__tests__/use-users.test.tsx +181 -0
- package/template/apps/web/src/hooks/api/use-admin-sessions.ts +75 -0
- package/template/apps/web/src/hooks/api/use-admin-stats.ts +33 -0
- package/template/apps/web/src/hooks/api/use-sessions.ts +52 -0
- package/template/apps/web/src/hooks/api/use-uploads.ts +156 -0
- package/template/apps/web/src/hooks/api/use-users.ts +149 -0
- package/template/apps/web/src/hooks/use-mobile.ts +21 -0
- package/template/apps/web/src/hooks/use-realtime-metrics.ts +120 -0
- package/template/apps/web/src/lib/__tests__/utils.test.ts +29 -0
- package/template/apps/web/src/lib/api.ts +151 -0
- package/template/apps/web/src/lib/auth.ts +13 -0
- package/template/apps/web/src/lib/env.ts +52 -0
- package/template/apps/web/src/lib/form-utils.ts +11 -0
- package/template/apps/web/src/lib/utils.ts +1 -0
- package/template/apps/web/src/providers.tsx +34 -0
- package/template/apps/web/src/store/atoms.ts +15 -0
- package/template/apps/web/src/test/helpers/test-utils.tsx +44 -0
- package/template/apps/web/src/test/setup.ts +8 -0
- package/template/apps/web/tailwind.config.ts +5 -0
- package/template/apps/web/tsconfig.json +26 -0
- package/template/apps/web/vitest.config.ts +32 -0
- package/template/assets/logo-512.png +0 -0
- package/template/assets/logo.svg +4 -0
- package/template/docker-compose.prod.yml +66 -0
- package/template/docker-compose.yml +36 -0
- package/template/eslint.config.ts +119 -0
- package/template/package.json +77 -0
- package/template/packages/tailwind-config/package.json +9 -0
- package/template/packages/tailwind-config/theme.css +179 -0
- package/template/packages/types/package.json +29 -0
- package/template/packages/types/src/__tests__/schemas.test.ts +255 -0
- package/template/packages/types/src/api-response.ts +53 -0
- package/template/packages/types/src/health-check.ts +11 -0
- package/template/packages/types/src/pagination.ts +41 -0
- package/template/packages/types/src/role.ts +5 -0
- package/template/packages/types/src/session.ts +48 -0
- package/template/packages/types/src/stats.ts +113 -0
- package/template/packages/types/src/upload.ts +51 -0
- package/template/packages/types/src/user.ts +36 -0
- package/template/packages/types/tsconfig.json +5 -0
- package/template/packages/types/vitest.config.ts +21 -0
- package/template/packages/ui/components.json +21 -0
- package/template/packages/ui/package.json +108 -0
- package/template/packages/ui/src/__tests__/button.test.tsx +70 -0
- package/template/packages/ui/src/alert-dialog.tsx +141 -0
- package/template/packages/ui/src/alert.tsx +66 -0
- package/template/packages/ui/src/animated-theme-toggler.tsx +167 -0
- package/template/packages/ui/src/avatar.tsx +53 -0
- package/template/packages/ui/src/badge.tsx +36 -0
- package/template/packages/ui/src/button.tsx +84 -0
- package/template/packages/ui/src/card.tsx +92 -0
- package/template/packages/ui/src/checkbox.tsx +32 -0
- package/template/packages/ui/src/data-table/data-table-column-header.tsx +68 -0
- package/template/packages/ui/src/data-table/data-table-pagination.tsx +99 -0
- package/template/packages/ui/src/data-table/data-table-toolbar.tsx +55 -0
- package/template/packages/ui/src/data-table/data-table-view-options.tsx +63 -0
- package/template/packages/ui/src/data-table/data-table.tsx +167 -0
- package/template/packages/ui/src/dialog.tsx +143 -0
- package/template/packages/ui/src/dropdown-menu.tsx +257 -0
- package/template/packages/ui/src/empty-state.tsx +52 -0
- package/template/packages/ui/src/file-upload-input.tsx +202 -0
- package/template/packages/ui/src/form.tsx +168 -0
- package/template/packages/ui/src/hooks/use-mobile.ts +19 -0
- package/template/packages/ui/src/icons/brand-icons.tsx +16 -0
- package/template/packages/ui/src/input.tsx +21 -0
- package/template/packages/ui/src/label.tsx +24 -0
- package/template/packages/ui/src/lib/utils.ts +6 -0
- package/template/packages/ui/src/password-input.tsx +102 -0
- package/template/packages/ui/src/popover.tsx +48 -0
- package/template/packages/ui/src/radio-group.tsx +45 -0
- package/template/packages/ui/src/scroll-area.tsx +58 -0
- package/template/packages/ui/src/select.tsx +187 -0
- package/template/packages/ui/src/separator.tsx +28 -0
- package/template/packages/ui/src/sheet.tsx +139 -0
- package/template/packages/ui/src/sidebar.tsx +726 -0
- package/template/packages/ui/src/skeleton-variants.tsx +87 -0
- package/template/packages/ui/src/skeleton.tsx +13 -0
- package/template/packages/ui/src/slider.tsx +63 -0
- package/template/packages/ui/src/sonner.tsx +25 -0
- package/template/packages/ui/src/spinner.tsx +16 -0
- package/template/packages/ui/src/switch.tsx +31 -0
- package/template/packages/ui/src/table.tsx +116 -0
- package/template/packages/ui/src/tabs.tsx +66 -0
- package/template/packages/ui/src/textarea.tsx +18 -0
- package/template/packages/ui/src/tooltip.tsx +61 -0
- package/template/packages/ui/src/user-avatar.tsx +97 -0
- package/template/packages/ui/test-config.js +3 -0
- package/template/packages/ui/tsconfig.json +12 -0
- package/template/packages/ui/turbo.json +18 -0
- package/template/packages/ui/vitest.config.ts +17 -0
- package/template/packages/ui/vitest.setup.ts +1 -0
- package/template/packages/utils/package.json +23 -0
- package/template/packages/utils/src/__tests__/utils.test.ts +223 -0
- package/template/packages/utils/src/array.ts +18 -0
- package/template/packages/utils/src/async.ts +3 -0
- package/template/packages/utils/src/date.ts +77 -0
- package/template/packages/utils/src/errors.ts +73 -0
- package/template/packages/utils/src/number.ts +11 -0
- package/template/packages/utils/src/string.ts +13 -0
- package/template/packages/utils/tsconfig.json +5 -0
- package/template/packages/utils/vitest.config.ts +21 -0
- package/template/pnpm-workspace.yaml +4 -0
- package/template/tsconfig.base.json +32 -0
- package/template/turbo.json +133 -0
- package/template/vitest.shared.ts +26 -0
- package/template/vitest.workspace.ts +9 -0
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import { NotFoundError } from '@repo/packages-utils/errors';
|
|
2
|
+
import { createMockLogger } from '@test/helpers/mock-logger';
|
|
3
|
+
import { getTestPrisma, resetTestDatabase } from '@test/helpers/test-db';
|
|
4
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
+
|
|
6
|
+
import type { LoggerService } from '@/common/logger.service';
|
|
7
|
+
import { SessionsService } from '@/services/sessions.service';
|
|
8
|
+
|
|
9
|
+
describe('Sessions Service Integration Tests', () => {
|
|
10
|
+
let service: SessionsService;
|
|
11
|
+
let logger: LoggerService;
|
|
12
|
+
|
|
13
|
+
beforeEach(async () => {
|
|
14
|
+
await resetTestDatabase();
|
|
15
|
+
|
|
16
|
+
logger = createMockLogger();
|
|
17
|
+
const prisma = getTestPrisma();
|
|
18
|
+
service = new SessionsService(prisma);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('getUserSessions', () => {
|
|
22
|
+
it('should return all active sessions for a user', async () => {
|
|
23
|
+
const prisma = getTestPrisma();
|
|
24
|
+
|
|
25
|
+
// Create test user
|
|
26
|
+
const user = await prisma.user.create({
|
|
27
|
+
data: {
|
|
28
|
+
email: 'sessions@test.com',
|
|
29
|
+
name: 'Sessions User',
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Create multiple sessions
|
|
34
|
+
await prisma.session.createMany({
|
|
35
|
+
data: [
|
|
36
|
+
{
|
|
37
|
+
userId: user.id,
|
|
38
|
+
token: 'session-1',
|
|
39
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
40
|
+
ipAddress: '192.168.1.1',
|
|
41
|
+
userAgent: 'Chrome Desktop',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
userId: user.id,
|
|
45
|
+
token: 'session-2',
|
|
46
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
47
|
+
ipAddress: '192.168.1.2',
|
|
48
|
+
userAgent: 'Safari Mobile',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
userId: user.id,
|
|
52
|
+
token: 'session-3',
|
|
53
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
54
|
+
ipAddress: '10.0.0.1',
|
|
55
|
+
userAgent: 'Firefox Desktop',
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const sessions = await service.getUserSessions(user.id);
|
|
61
|
+
|
|
62
|
+
expect(sessions).toHaveLength(3);
|
|
63
|
+
expect(sessions[0].ipAddress).toBeDefined();
|
|
64
|
+
expect(sessions[0].userAgent).toBeDefined();
|
|
65
|
+
expect(sessions[0].expiresAt).toBeInstanceOf(Date);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should exclude expired sessions', async () => {
|
|
69
|
+
const prisma = getTestPrisma();
|
|
70
|
+
|
|
71
|
+
const user = await prisma.user.create({
|
|
72
|
+
data: {
|
|
73
|
+
email: 'expired@test.com',
|
|
74
|
+
name: 'Expired User',
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Create active and expired sessions
|
|
79
|
+
await prisma.session.createMany({
|
|
80
|
+
data: [
|
|
81
|
+
{
|
|
82
|
+
userId: user.id,
|
|
83
|
+
token: 'active-session',
|
|
84
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
userId: user.id,
|
|
88
|
+
token: 'expired-session-1',
|
|
89
|
+
expiresAt: new Date(Date.now() - 1000),
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
userId: user.id,
|
|
93
|
+
token: 'expired-session-2',
|
|
94
|
+
expiresAt: new Date(Date.now() - 5000),
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const sessions = await service.getUserSessions(user.id);
|
|
100
|
+
|
|
101
|
+
expect(sessions).toHaveLength(1);
|
|
102
|
+
expect(sessions[0].expiresAt.getTime()).toBeGreaterThan(Date.now());
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should return sessions ordered by most recently updated', async () => {
|
|
106
|
+
const prisma = getTestPrisma();
|
|
107
|
+
|
|
108
|
+
const user = await prisma.user.create({
|
|
109
|
+
data: {
|
|
110
|
+
email: 'ordered@test.com',
|
|
111
|
+
name: 'Ordered User',
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const now = Date.now();
|
|
116
|
+
|
|
117
|
+
// Create sessions with different update times
|
|
118
|
+
const session1 = await prisma.session.create({
|
|
119
|
+
data: {
|
|
120
|
+
userId: user.id,
|
|
121
|
+
token: 'oldest',
|
|
122
|
+
expiresAt: new Date(now + 7 * 24 * 60 * 60 * 1000),
|
|
123
|
+
updatedAt: new Date(now - 3000),
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const session2 = await prisma.session.create({
|
|
128
|
+
data: {
|
|
129
|
+
userId: user.id,
|
|
130
|
+
token: 'middle',
|
|
131
|
+
expiresAt: new Date(now + 7 * 24 * 60 * 60 * 1000),
|
|
132
|
+
updatedAt: new Date(now - 2000),
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const session3 = await prisma.session.create({
|
|
137
|
+
data: {
|
|
138
|
+
userId: user.id,
|
|
139
|
+
token: 'newest',
|
|
140
|
+
expiresAt: new Date(now + 7 * 24 * 60 * 60 * 1000),
|
|
141
|
+
updatedAt: new Date(now - 1000),
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const sessions = await service.getUserSessions(user.id);
|
|
146
|
+
|
|
147
|
+
expect(sessions).toHaveLength(3);
|
|
148
|
+
expect(sessions[0].id).toBe(session3.id); // Newest first
|
|
149
|
+
expect(sessions[1].id).toBe(session2.id);
|
|
150
|
+
expect(sessions[2].id).toBe(session1.id);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should return empty array for user with no sessions', async () => {
|
|
154
|
+
const prisma = getTestPrisma();
|
|
155
|
+
|
|
156
|
+
const user = await prisma.user.create({
|
|
157
|
+
data: {
|
|
158
|
+
email: 'nosessions@test.com',
|
|
159
|
+
name: 'No Sessions User',
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const sessions = await service.getUserSessions(user.id);
|
|
164
|
+
|
|
165
|
+
expect(sessions).toHaveLength(0);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('revokeSession', () => {
|
|
170
|
+
it('should delete a specific session', async () => {
|
|
171
|
+
const prisma = getTestPrisma();
|
|
172
|
+
|
|
173
|
+
const user = await prisma.user.create({
|
|
174
|
+
data: {
|
|
175
|
+
email: 'revoke@test.com',
|
|
176
|
+
name: 'Revoke User',
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const session = await prisma.session.create({
|
|
181
|
+
data: {
|
|
182
|
+
userId: user.id,
|
|
183
|
+
token: 'to-revoke',
|
|
184
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
await service.revokeSession(user.id, session.id);
|
|
189
|
+
|
|
190
|
+
const deletedSession = await prisma.session.findUnique({
|
|
191
|
+
where: { id: session.id },
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
expect(deletedSession).toBeNull();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should throw NotFoundError when session does not exist', async () => {
|
|
198
|
+
const prisma = getTestPrisma();
|
|
199
|
+
|
|
200
|
+
const user = await prisma.user.create({
|
|
201
|
+
data: {
|
|
202
|
+
email: 'notfound@test.com',
|
|
203
|
+
name: 'Not Found User',
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
await expect(
|
|
208
|
+
service.revokeSession(user.id, 'non-existent-session-id')
|
|
209
|
+
).rejects.toThrow(NotFoundError);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should throw NotFoundError when session belongs to different user', async () => {
|
|
213
|
+
const prisma = getTestPrisma();
|
|
214
|
+
|
|
215
|
+
const user1 = await prisma.user.create({
|
|
216
|
+
data: {
|
|
217
|
+
email: 'user1@test.com',
|
|
218
|
+
name: 'User 1',
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const user2 = await prisma.user.create({
|
|
223
|
+
data: {
|
|
224
|
+
email: 'user2@test.com',
|
|
225
|
+
name: 'User 2',
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const user2Session = await prisma.session.create({
|
|
230
|
+
data: {
|
|
231
|
+
userId: user2.id,
|
|
232
|
+
token: 'user2-session',
|
|
233
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// User1 tries to revoke User2's session
|
|
238
|
+
await expect(
|
|
239
|
+
service.revokeSession(user1.id, user2Session.id)
|
|
240
|
+
).rejects.toThrow(NotFoundError);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('should only delete the specified session, not others', async () => {
|
|
244
|
+
const prisma = getTestPrisma();
|
|
245
|
+
|
|
246
|
+
const user = await prisma.user.create({
|
|
247
|
+
data: {
|
|
248
|
+
email: 'multiple@test.com',
|
|
249
|
+
name: 'Multiple Sessions User',
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const session1 = await prisma.session.create({
|
|
254
|
+
data: {
|
|
255
|
+
userId: user.id,
|
|
256
|
+
token: 'keep-this',
|
|
257
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const session2 = await prisma.session.create({
|
|
262
|
+
data: {
|
|
263
|
+
userId: user.id,
|
|
264
|
+
token: 'delete-this',
|
|
265
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
await service.revokeSession(user.id, session2.id);
|
|
270
|
+
|
|
271
|
+
const remainingSessions = await prisma.session.findMany({
|
|
272
|
+
where: { userId: user.id },
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
expect(remainingSessions).toHaveLength(1);
|
|
276
|
+
expect(remainingSessions[0].id).toBe(session1.id);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe('revokeAllSessions', () => {
|
|
281
|
+
it('should delete all sessions except current one', async () => {
|
|
282
|
+
const prisma = getTestPrisma();
|
|
283
|
+
|
|
284
|
+
const user = await prisma.user.create({
|
|
285
|
+
data: {
|
|
286
|
+
email: 'revokeall@test.com',
|
|
287
|
+
name: 'Revoke All User',
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const currentSession = await prisma.session.create({
|
|
292
|
+
data: {
|
|
293
|
+
userId: user.id,
|
|
294
|
+
token: 'current',
|
|
295
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
await prisma.session.createMany({
|
|
300
|
+
data: [
|
|
301
|
+
{
|
|
302
|
+
userId: user.id,
|
|
303
|
+
token: 'other-1',
|
|
304
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
userId: user.id,
|
|
308
|
+
token: 'other-2',
|
|
309
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
310
|
+
},
|
|
311
|
+
],
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
await service.revokeAllSessions(user.id, currentSession.id);
|
|
315
|
+
|
|
316
|
+
const remainingSessions = await prisma.session.findMany({
|
|
317
|
+
where: { userId: user.id },
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
expect(remainingSessions).toHaveLength(1);
|
|
321
|
+
expect(remainingSessions[0].id).toBe(currentSession.id);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('should delete all sessions when no current session provided', async () => {
|
|
325
|
+
const prisma = getTestPrisma();
|
|
326
|
+
|
|
327
|
+
const user = await prisma.user.create({
|
|
328
|
+
data: {
|
|
329
|
+
email: 'deleteall@test.com',
|
|
330
|
+
name: 'Delete All User',
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
await prisma.session.createMany({
|
|
335
|
+
data: [
|
|
336
|
+
{
|
|
337
|
+
userId: user.id,
|
|
338
|
+
token: 'session-1',
|
|
339
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
userId: user.id,
|
|
343
|
+
token: 'session-2',
|
|
344
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
userId: user.id,
|
|
348
|
+
token: 'session-3',
|
|
349
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
350
|
+
},
|
|
351
|
+
],
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
await service.revokeAllSessions(user.id);
|
|
355
|
+
|
|
356
|
+
const remainingSessions = await prisma.session.findMany({
|
|
357
|
+
where: { userId: user.id },
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
expect(remainingSessions).toHaveLength(0);
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
describe('revokeAllUserSessions', () => {
|
|
365
|
+
it('should delete all sessions for a user', async () => {
|
|
366
|
+
const prisma = getTestPrisma();
|
|
367
|
+
|
|
368
|
+
const user = await prisma.user.create({
|
|
369
|
+
data: {
|
|
370
|
+
email: 'total-revoke@test.com',
|
|
371
|
+
name: 'Total Revoke User',
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
await prisma.session.createMany({
|
|
376
|
+
data: [
|
|
377
|
+
{
|
|
378
|
+
userId: user.id,
|
|
379
|
+
token: 'session-1',
|
|
380
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
userId: user.id,
|
|
384
|
+
token: 'session-2',
|
|
385
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
386
|
+
},
|
|
387
|
+
],
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
await service.revokeAllUserSessions(user.id);
|
|
391
|
+
|
|
392
|
+
const sessions = await prisma.session.findMany({
|
|
393
|
+
where: { userId: user.id },
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
expect(sessions).toHaveLength(0);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('should not affect other users sessions', async () => {
|
|
400
|
+
const prisma = getTestPrisma();
|
|
401
|
+
|
|
402
|
+
const user1 = await prisma.user.create({
|
|
403
|
+
data: {
|
|
404
|
+
email: 'user1-revoke@test.com',
|
|
405
|
+
name: 'User 1',
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
const user2 = await prisma.user.create({
|
|
410
|
+
data: {
|
|
411
|
+
email: 'user2-keep@test.com',
|
|
412
|
+
name: 'User 2',
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
await prisma.session.create({
|
|
417
|
+
data: {
|
|
418
|
+
userId: user1.id,
|
|
419
|
+
token: 'user1-session',
|
|
420
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
421
|
+
},
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
await prisma.session.create({
|
|
425
|
+
data: {
|
|
426
|
+
userId: user2.id,
|
|
427
|
+
token: 'user2-session',
|
|
428
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
429
|
+
},
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
await service.revokeAllUserSessions(user1.id);
|
|
433
|
+
|
|
434
|
+
const user1Sessions = await prisma.session.findMany({
|
|
435
|
+
where: { userId: user1.id },
|
|
436
|
+
});
|
|
437
|
+
const user2Sessions = await prisma.session.findMany({
|
|
438
|
+
where: { userId: user2.id },
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
expect(user1Sessions).toHaveLength(0);
|
|
442
|
+
expect(user2Sessions).toHaveLength(1);
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
});
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { createMockAuthorizationService } from '@test/helpers/mock-authorization';
|
|
2
|
+
import { createMockLogger } from '@test/helpers/mock-logger';
|
|
3
|
+
import { getTestPrisma, resetTestDatabase } from '@test/helpers/test-db';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
+
|
|
6
|
+
import type { LoggerService } from '@/common/logger.service';
|
|
7
|
+
import type { AuthorizationService } from '@/services/authorization.service';
|
|
8
|
+
import { UsersService } from '@/services/users.service';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Integration tests for UsersService
|
|
12
|
+
* These tests use a real PostgreSQL database (app_dev_test)
|
|
13
|
+
* The database is automatically created and migrated in test/setup.ts
|
|
14
|
+
*/
|
|
15
|
+
describe('UsersService Integration Tests', () => {
|
|
16
|
+
let service: UsersService;
|
|
17
|
+
let logger: LoggerService;
|
|
18
|
+
let authorizationService: AuthorizationService;
|
|
19
|
+
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
// Reset database between tests
|
|
22
|
+
await resetTestDatabase();
|
|
23
|
+
|
|
24
|
+
// Create service with real Prisma client
|
|
25
|
+
logger = createMockLogger();
|
|
26
|
+
authorizationService = createMockAuthorizationService();
|
|
27
|
+
const prisma = getTestPrisma();
|
|
28
|
+
service = new UsersService(prisma, logger, authorizationService);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterEach(async () => {
|
|
32
|
+
// Additional cleanup if needed
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('User CRUD Operations', () => {
|
|
36
|
+
it('should create and retrieve a user', async () => {
|
|
37
|
+
// Create user
|
|
38
|
+
const createdUser = await service.createUser({
|
|
39
|
+
email: 'integration@test.com',
|
|
40
|
+
name: 'Integration Test User',
|
|
41
|
+
role: 'user',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
expect(createdUser.id).toBeDefined();
|
|
45
|
+
expect(createdUser.email).toBe('integration@test.com');
|
|
46
|
+
expect(createdUser.name).toBe('Integration Test User');
|
|
47
|
+
expect(createdUser.role).toBe('user');
|
|
48
|
+
|
|
49
|
+
// Retrieve user by ID
|
|
50
|
+
const retrievedUser = await service.getUserById(createdUser.id);
|
|
51
|
+
|
|
52
|
+
expect(retrievedUser).toBeDefined();
|
|
53
|
+
expect(retrievedUser?.id).toBe(createdUser.id);
|
|
54
|
+
expect(retrievedUser?.email).toBe('integration@test.com');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should update an existing user', async () => {
|
|
58
|
+
// Create user
|
|
59
|
+
const user = await service.createUser({
|
|
60
|
+
email: 'update@test.com',
|
|
61
|
+
name: 'Original Name',
|
|
62
|
+
role: 'user',
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Update user
|
|
66
|
+
const updatedUser = await service.updateUser(
|
|
67
|
+
'actor-id',
|
|
68
|
+
'super_admin',
|
|
69
|
+
user.id,
|
|
70
|
+
{
|
|
71
|
+
name: 'Updated Name',
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
expect(updatedUser).toBeDefined();
|
|
76
|
+
expect(updatedUser?.name).toBe('Updated Name');
|
|
77
|
+
expect(updatedUser?.email).toBe('update@test.com'); // Email unchanged
|
|
78
|
+
|
|
79
|
+
// Verify update persisted
|
|
80
|
+
const retrievedUser = await service.getUserById(user.id);
|
|
81
|
+
expect(retrievedUser?.name).toBe('Updated Name');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should delete a user', async () => {
|
|
85
|
+
// Create user
|
|
86
|
+
const user = await service.createUser({
|
|
87
|
+
email: 'delete@test.com',
|
|
88
|
+
name: 'To Be Deleted',
|
|
89
|
+
role: 'user',
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Delete user
|
|
93
|
+
await service.deleteUser('actor-id', 'super_admin', user.id);
|
|
94
|
+
|
|
95
|
+
// Verify user is deleted (should throw NotFoundError)
|
|
96
|
+
await expect(service.getUserById(user.id)).rejects.toThrow(
|
|
97
|
+
'User not found'
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should throw NotFoundError when updating non-existent user', async () => {
|
|
102
|
+
await expect(
|
|
103
|
+
service.updateUser('actor-id', 'super_admin', 'non-existent-id', {
|
|
104
|
+
name: 'New Name',
|
|
105
|
+
})
|
|
106
|
+
).rejects.toThrow('User not found');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should throw NotFoundError when deleting non-existent user', async () => {
|
|
110
|
+
await expect(
|
|
111
|
+
service.deleteUser('actor-id', 'super_admin', 'non-existent-id')
|
|
112
|
+
).rejects.toThrow('User not found');
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('User Pagination and Filtering', () => {
|
|
117
|
+
beforeEach(async () => {
|
|
118
|
+
// Create test users
|
|
119
|
+
await service.createUser({
|
|
120
|
+
email: 'alice@test.com',
|
|
121
|
+
name: 'Alice Smith',
|
|
122
|
+
role: 'user',
|
|
123
|
+
});
|
|
124
|
+
await service.createUser({
|
|
125
|
+
email: 'bob@test.com',
|
|
126
|
+
name: 'Bob Johnson',
|
|
127
|
+
role: 'admin',
|
|
128
|
+
});
|
|
129
|
+
await service.createUser({
|
|
130
|
+
email: 'charlie@test.com',
|
|
131
|
+
name: 'Charlie Brown',
|
|
132
|
+
role: 'user',
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should return paginated users', async () => {
|
|
137
|
+
const result = await service.getUsers({
|
|
138
|
+
page: 1,
|
|
139
|
+
limit: 10,
|
|
140
|
+
sortBy: 'createdAt',
|
|
141
|
+
sortOrder: 'desc',
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
expect(result.data).toHaveLength(3);
|
|
145
|
+
expect(result.pagination.total).toBe(3);
|
|
146
|
+
expect(result.pagination.totalPages).toBe(1);
|
|
147
|
+
expect(result.pagination.page).toBe(1);
|
|
148
|
+
expect(result.pagination.limit).toBe(10);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should handle pagination correctly', async () => {
|
|
152
|
+
const result = await service.getUsers({
|
|
153
|
+
page: 1,
|
|
154
|
+
limit: 2,
|
|
155
|
+
sortBy: 'createdAt',
|
|
156
|
+
sortOrder: 'desc',
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(result.data).toHaveLength(2);
|
|
160
|
+
expect(result.pagination.total).toBe(3);
|
|
161
|
+
expect(result.pagination.totalPages).toBe(2);
|
|
162
|
+
expect(result.pagination.page).toBe(1);
|
|
163
|
+
expect(result.pagination.limit).toBe(2);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should filter users by search query', async () => {
|
|
167
|
+
const result = await service.getUsers({
|
|
168
|
+
page: 1,
|
|
169
|
+
limit: 10,
|
|
170
|
+
sortBy: 'createdAt',
|
|
171
|
+
sortOrder: 'desc',
|
|
172
|
+
search: 'alice',
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
expect(result.data).toHaveLength(1);
|
|
176
|
+
expect(result.data[0].email).toBe('alice@test.com');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should sort users by email', async () => {
|
|
180
|
+
const result = await service.getUsers({
|
|
181
|
+
page: 1,
|
|
182
|
+
limit: 10,
|
|
183
|
+
sortBy: 'email',
|
|
184
|
+
sortOrder: 'asc',
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
expect(result.data[0].email).toBe('alice@test.com');
|
|
188
|
+
expect(result.data[1].email).toBe('bob@test.com');
|
|
189
|
+
expect(result.data[2].email).toBe('charlie@test.com');
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('Database Constraints', () => {
|
|
194
|
+
it('should enforce unique email constraint', async () => {
|
|
195
|
+
await service.createUser({
|
|
196
|
+
email: 'unique@test.com',
|
|
197
|
+
name: 'First User',
|
|
198
|
+
role: 'user',
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Attempting to create another user with same email should fail
|
|
202
|
+
await expect(
|
|
203
|
+
service.createUser({
|
|
204
|
+
email: 'unique@test.com',
|
|
205
|
+
name: 'Second User',
|
|
206
|
+
role: 'user',
|
|
207
|
+
})
|
|
208
|
+
).rejects.toThrow();
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { afterAll, beforeAll } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { cleanupTestDatabase, setupTestDatabase } from './helpers/test-db';
|
|
4
|
+
|
|
5
|
+
beforeAll(async () => {
|
|
6
|
+
// Set test environment variables before database setup
|
|
7
|
+
process.env.NODE_ENV = 'test';
|
|
8
|
+
|
|
9
|
+
// Set minimal required env vars for tests
|
|
10
|
+
// These are only needed for test infrastructure, not for actual test assertions
|
|
11
|
+
process.env.BETTER_AUTH_SECRET =
|
|
12
|
+
process.env.BETTER_AUTH_SECRET || 'test-secret-minimum-32-characters-long';
|
|
13
|
+
process.env.BETTER_AUTH_URL =
|
|
14
|
+
process.env.BETTER_AUTH_URL || 'http://localhost:8080';
|
|
15
|
+
process.env.API_URL = process.env.API_URL || 'http://localhost:8080';
|
|
16
|
+
process.env.FRONTEND_URL =
|
|
17
|
+
process.env.FRONTEND_URL || 'http://localhost:3000';
|
|
18
|
+
process.env.COOKIE_SECRET = process.env.COOKIE_SECRET || 'test-cookie-secret';
|
|
19
|
+
process.env.PORT = process.env.PORT || '8080';
|
|
20
|
+
process.env.LOG_LEVEL = process.env.LOG_LEVEL || 'minimal';
|
|
21
|
+
|
|
22
|
+
// DATABASE_URL is set in test-db.ts based on your .env.local
|
|
23
|
+
|
|
24
|
+
// Setup test database (runs migrations)
|
|
25
|
+
await setupTestDatabase();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterAll(async () => {
|
|
29
|
+
// Cleanup test database
|
|
30
|
+
await cleanupTestDatabase();
|
|
31
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"removeComments": true,
|
|
8
|
+
"allowSyntheticDefaultImports": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"target": "ES2022",
|
|
11
|
+
"lib": ["ES2022", "DOM"],
|
|
12
|
+
"sourceMap": true,
|
|
13
|
+
"outDir": "./dist",
|
|
14
|
+
"baseUrl": "./",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"skipLibCheck": true,
|
|
17
|
+
"resolveJsonModule": true,
|
|
18
|
+
"jsx": "react",
|
|
19
|
+
"paths": {
|
|
20
|
+
"@/*": ["src/*"],
|
|
21
|
+
"@test/*": ["test/*"]
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"include": ["src/**/*", "test/**/*", "emails/**/*"],
|
|
25
|
+
"exclude": ["node_modules", "dist", "src/generated"]
|
|
26
|
+
}
|