servcraft 0.1.0 → 0.1.3
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/.claude/settings.local.json +30 -0
- package/.github/CODEOWNERS +18 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +46 -0
- package/.github/dependabot.yml +59 -0
- package/.github/workflows/ci.yml +188 -0
- package/.github/workflows/release.yml +195 -0
- package/AUDIT.md +602 -0
- package/LICENSE +21 -0
- package/README.md +1102 -1
- package/dist/cli/index.cjs +2026 -2168
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +2026 -2168
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +595 -616
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +114 -52
- package/dist/index.d.ts +114 -52
- package/dist/index.js +595 -616
- package/dist/index.js.map +1 -1
- package/docs/CLI-001_MULTI_DB_PLAN.md +546 -0
- package/docs/DATABASE_MULTI_ORM.md +399 -0
- package/docs/PHASE1_BREAKDOWN.md +346 -0
- package/docs/PROGRESS.md +550 -0
- package/docs/modules/ANALYTICS.md +226 -0
- package/docs/modules/API-VERSIONING.md +252 -0
- package/docs/modules/AUDIT.md +192 -0
- package/docs/modules/AUTH.md +431 -0
- package/docs/modules/CACHE.md +346 -0
- package/docs/modules/EMAIL.md +254 -0
- package/docs/modules/FEATURE-FLAG.md +291 -0
- package/docs/modules/I18N.md +294 -0
- package/docs/modules/MEDIA-PROCESSING.md +281 -0
- package/docs/modules/MFA.md +266 -0
- package/docs/modules/NOTIFICATION.md +311 -0
- package/docs/modules/OAUTH.md +237 -0
- package/docs/modules/PAYMENT.md +804 -0
- package/docs/modules/QUEUE.md +540 -0
- package/docs/modules/RATE-LIMIT.md +339 -0
- package/docs/modules/SEARCH.md +288 -0
- package/docs/modules/SECURITY.md +327 -0
- package/docs/modules/SESSION.md +382 -0
- package/docs/modules/SWAGGER.md +305 -0
- package/docs/modules/UPLOAD.md +296 -0
- package/docs/modules/USER.md +505 -0
- package/docs/modules/VALIDATION.md +294 -0
- package/docs/modules/WEBHOOK.md +270 -0
- package/docs/modules/WEBSOCKET.md +691 -0
- package/package.json +53 -38
- package/prisma/schema.prisma +395 -1
- package/src/cli/commands/add-module.ts +520 -87
- package/src/cli/commands/db.ts +3 -4
- package/src/cli/commands/docs.ts +256 -6
- package/src/cli/commands/generate.ts +12 -19
- package/src/cli/commands/init.ts +384 -214
- package/src/cli/index.ts +0 -4
- package/src/cli/templates/repository.ts +6 -1
- package/src/cli/templates/routes.ts +6 -21
- package/src/cli/utils/docs-generator.ts +6 -7
- package/src/cli/utils/env-manager.ts +717 -0
- package/src/cli/utils/field-parser.ts +16 -7
- package/src/cli/utils/interactive-prompt.ts +223 -0
- package/src/cli/utils/template-manager.ts +346 -0
- package/src/config/database.config.ts +183 -0
- package/src/config/env.ts +0 -10
- package/src/config/index.ts +0 -14
- package/src/core/server.ts +1 -1
- package/src/database/adapters/mongoose.adapter.ts +132 -0
- package/src/database/adapters/prisma.adapter.ts +118 -0
- package/src/database/connection.ts +190 -0
- package/src/database/interfaces/database.interface.ts +85 -0
- package/src/database/interfaces/index.ts +7 -0
- package/src/database/interfaces/repository.interface.ts +129 -0
- package/src/database/models/mongoose/index.ts +7 -0
- package/src/database/models/mongoose/payment.schema.ts +347 -0
- package/src/database/models/mongoose/user.schema.ts +154 -0
- package/src/database/prisma.ts +1 -4
- package/src/database/redis.ts +101 -0
- package/src/database/repositories/mongoose/index.ts +7 -0
- package/src/database/repositories/mongoose/payment.repository.ts +380 -0
- package/src/database/repositories/mongoose/user.repository.ts +255 -0
- package/src/database/seed.ts +6 -1
- package/src/index.ts +9 -20
- package/src/middleware/security.ts +2 -6
- package/src/modules/analytics/analytics.routes.ts +80 -0
- package/src/modules/analytics/analytics.service.ts +364 -0
- package/src/modules/analytics/index.ts +18 -0
- package/src/modules/analytics/types.ts +180 -0
- package/src/modules/api-versioning/index.ts +15 -0
- package/src/modules/api-versioning/types.ts +86 -0
- package/src/modules/api-versioning/versioning.middleware.ts +120 -0
- package/src/modules/api-versioning/versioning.routes.ts +54 -0
- package/src/modules/api-versioning/versioning.service.ts +189 -0
- package/src/modules/audit/audit.repository.ts +206 -0
- package/src/modules/audit/audit.service.ts +27 -59
- package/src/modules/auth/auth.controller.ts +2 -2
- package/src/modules/auth/auth.middleware.ts +3 -9
- package/src/modules/auth/auth.routes.ts +10 -107
- package/src/modules/auth/auth.service.ts +126 -23
- package/src/modules/auth/index.ts +3 -4
- package/src/modules/cache/cache.service.ts +367 -0
- package/src/modules/cache/index.ts +10 -0
- package/src/modules/cache/types.ts +44 -0
- package/src/modules/email/email.service.ts +3 -10
- package/src/modules/email/templates.ts +2 -8
- package/src/modules/feature-flag/feature-flag.repository.ts +303 -0
- package/src/modules/feature-flag/feature-flag.routes.ts +247 -0
- package/src/modules/feature-flag/feature-flag.service.ts +566 -0
- package/src/modules/feature-flag/index.ts +20 -0
- package/src/modules/feature-flag/types.ts +192 -0
- package/src/modules/i18n/i18n.middleware.ts +186 -0
- package/src/modules/i18n/i18n.routes.ts +191 -0
- package/src/modules/i18n/i18n.service.ts +456 -0
- package/src/modules/i18n/index.ts +18 -0
- package/src/modules/i18n/types.ts +118 -0
- package/src/modules/media-processing/index.ts +17 -0
- package/src/modules/media-processing/media-processing.routes.ts +111 -0
- package/src/modules/media-processing/media-processing.service.ts +245 -0
- package/src/modules/media-processing/types.ts +156 -0
- package/src/modules/mfa/index.ts +20 -0
- package/src/modules/mfa/mfa.repository.ts +206 -0
- package/src/modules/mfa/mfa.routes.ts +595 -0
- package/src/modules/mfa/mfa.service.ts +572 -0
- package/src/modules/mfa/totp.ts +150 -0
- package/src/modules/mfa/types.ts +57 -0
- package/src/modules/notification/index.ts +20 -0
- package/src/modules/notification/notification.repository.ts +356 -0
- package/src/modules/notification/notification.service.ts +483 -0
- package/src/modules/notification/types.ts +119 -0
- package/src/modules/oauth/index.ts +20 -0
- package/src/modules/oauth/oauth.repository.ts +219 -0
- package/src/modules/oauth/oauth.routes.ts +446 -0
- package/src/modules/oauth/oauth.service.ts +293 -0
- package/src/modules/oauth/providers/apple.provider.ts +250 -0
- package/src/modules/oauth/providers/facebook.provider.ts +181 -0
- package/src/modules/oauth/providers/github.provider.ts +248 -0
- package/src/modules/oauth/providers/google.provider.ts +189 -0
- package/src/modules/oauth/providers/twitter.provider.ts +214 -0
- package/src/modules/oauth/types.ts +94 -0
- package/src/modules/payment/index.ts +19 -0
- package/src/modules/payment/payment.repository.ts +733 -0
- package/src/modules/payment/payment.routes.ts +390 -0
- package/src/modules/payment/payment.service.ts +354 -0
- package/src/modules/payment/providers/mobile-money.provider.ts +274 -0
- package/src/modules/payment/providers/paypal.provider.ts +190 -0
- package/src/modules/payment/providers/stripe.provider.ts +215 -0
- package/src/modules/payment/types.ts +140 -0
- package/src/modules/queue/cron.ts +438 -0
- package/src/modules/queue/index.ts +87 -0
- package/src/modules/queue/queue.routes.ts +600 -0
- package/src/modules/queue/queue.service.ts +842 -0
- package/src/modules/queue/types.ts +222 -0
- package/src/modules/queue/workers.ts +366 -0
- package/src/modules/rate-limit/index.ts +59 -0
- package/src/modules/rate-limit/rate-limit.middleware.ts +134 -0
- package/src/modules/rate-limit/rate-limit.routes.ts +269 -0
- package/src/modules/rate-limit/rate-limit.service.ts +348 -0
- package/src/modules/rate-limit/stores/memory.store.ts +165 -0
- package/src/modules/rate-limit/stores/redis.store.ts +322 -0
- package/src/modules/rate-limit/types.ts +153 -0
- package/src/modules/search/adapters/elasticsearch.adapter.ts +326 -0
- package/src/modules/search/adapters/meilisearch.adapter.ts +261 -0
- package/src/modules/search/adapters/memory.adapter.ts +278 -0
- package/src/modules/search/index.ts +21 -0
- package/src/modules/search/search.service.ts +234 -0
- package/src/modules/search/types.ts +214 -0
- package/src/modules/security/index.ts +40 -0
- package/src/modules/security/sanitize.ts +223 -0
- package/src/modules/security/security-audit.service.ts +388 -0
- package/src/modules/security/security.middleware.ts +398 -0
- package/src/modules/session/index.ts +3 -0
- package/src/modules/session/session.repository.ts +159 -0
- package/src/modules/session/session.service.ts +340 -0
- package/src/modules/session/types.ts +38 -0
- package/src/modules/swagger/index.ts +7 -1
- package/src/modules/swagger/schema-builder.ts +16 -4
- package/src/modules/swagger/swagger.service.ts +9 -10
- package/src/modules/swagger/types.ts +0 -2
- package/src/modules/upload/index.ts +14 -0
- package/src/modules/upload/types.ts +83 -0
- package/src/modules/upload/upload.repository.ts +199 -0
- package/src/modules/upload/upload.routes.ts +311 -0
- package/src/modules/upload/upload.service.ts +448 -0
- package/src/modules/user/index.ts +3 -3
- package/src/modules/user/user.controller.ts +15 -9
- package/src/modules/user/user.repository.ts +237 -113
- package/src/modules/user/user.routes.ts +39 -164
- package/src/modules/user/user.service.ts +4 -3
- package/src/modules/validation/validator.ts +12 -17
- package/src/modules/webhook/index.ts +91 -0
- package/src/modules/webhook/retry.ts +196 -0
- package/src/modules/webhook/signature.ts +135 -0
- package/src/modules/webhook/types.ts +181 -0
- package/src/modules/webhook/webhook.repository.ts +358 -0
- package/src/modules/webhook/webhook.routes.ts +442 -0
- package/src/modules/webhook/webhook.service.ts +457 -0
- package/src/modules/websocket/features.ts +504 -0
- package/src/modules/websocket/index.ts +106 -0
- package/src/modules/websocket/middlewares.ts +298 -0
- package/src/modules/websocket/types.ts +181 -0
- package/src/modules/websocket/websocket.service.ts +692 -0
- package/src/utils/errors.ts +7 -0
- package/src/utils/pagination.ts +4 -1
- package/tests/helpers/db-check.ts +79 -0
- package/tests/integration/auth-redis.test.ts +94 -0
- package/tests/integration/cache-redis.test.ts +387 -0
- package/tests/integration/mongoose-repositories.test.ts +410 -0
- package/tests/integration/payment-prisma.test.ts +637 -0
- package/tests/integration/queue-bullmq.test.ts +417 -0
- package/tests/integration/user-prisma.test.ts +441 -0
- package/tests/integration/websocket-socketio.test.ts +552 -0
- package/tests/setup.ts +11 -9
- package/vitest.config.ts +3 -8
- package/npm-cache/_cacache/content-v2/sha512/1c/d0/03440d500a0487621aad1d6402978340698976602046db8e24fa03c01ee6c022c69b0582f969042d9442ee876ac35c038e960dd427d1e622fa24b8eb7dba +0 -0
- package/npm-cache/_cacache/content-v2/sha512/42/55/28b493ca491833e5aab0e9c3108d29ab3f36c248ca88f45d4630674fce9130959e56ae308797ac2b6328fa7f09a610b9550ed09cb971d039876d293fc69d +0 -0
- package/npm-cache/_cacache/content-v2/sha512/e0/12/f360dc9315ee5f17844a0c8c233ee6bf7c30837c4a02ea0d56c61c7f7ab21c0e958e50ed2c57c59f983c762b93056778c9009b2398ffc26def0183999b13 +0 -0
- package/npm-cache/_cacache/content-v2/sha512/ed/b0/fae1161902898f4c913c67d7f6cdf6be0665aec3b389b9c4f4f0a101ca1da59badf1b59c4e0030f5223023b8d63cfe501c46a32c20c895d4fb3f11ca2232 +0 -0
- package/npm-cache/_cacache/index-v5/58/94/c2cba79e0f16b4c10e95a87e32255741149e8222cc314a476aab67c39cc0 +0 -5
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
|
|
2
|
+
import mongoose from 'mongoose';
|
|
3
|
+
import { MongooseAdapter } from '../../src/database/adapters/mongoose.adapter.js';
|
|
4
|
+
import { MongooseUserRepository } from '../../src/database/repositories/mongoose/user.repository.js';
|
|
5
|
+
import { MongoosePaymentRepository } from '../../src/database/repositories/mongoose/payment.repository.js';
|
|
6
|
+
|
|
7
|
+
// Skip if MongoDB is not available (requires local MongoDB instance)
|
|
8
|
+
const skipMongo = !process.env.MONGODB_URI && process.env.SKIP_MONGO_TESTS !== 'false';
|
|
9
|
+
|
|
10
|
+
describe.skipIf(skipMongo)('Mongoose Repositories Integration', () => {
|
|
11
|
+
let adapter: MongooseAdapter;
|
|
12
|
+
let userRepo: MongooseUserRepository;
|
|
13
|
+
let paymentRepo: MongoosePaymentRepository;
|
|
14
|
+
|
|
15
|
+
beforeAll(async () => {
|
|
16
|
+
// Connect to MongoDB (use test database)
|
|
17
|
+
adapter = new MongooseAdapter({
|
|
18
|
+
orm: 'mongoose',
|
|
19
|
+
database: 'mongodb',
|
|
20
|
+
url: process.env.MONGODB_URI || 'mongodb://localhost:27017/servcraft_test',
|
|
21
|
+
logging: false,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
await adapter.connect();
|
|
25
|
+
|
|
26
|
+
userRepo = new MongooseUserRepository();
|
|
27
|
+
paymentRepo = new MongoosePaymentRepository();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterAll(async () => {
|
|
31
|
+
await adapter.disconnect();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
beforeEach(async () => {
|
|
35
|
+
// Clean database before each test
|
|
36
|
+
await mongoose.connection.dropDatabase();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// ==========================================
|
|
40
|
+
// USER REPOSITORY TESTS
|
|
41
|
+
// ==========================================
|
|
42
|
+
|
|
43
|
+
describe('User Repository', () => {
|
|
44
|
+
it('should create a user', async () => {
|
|
45
|
+
const user = await userRepo.create({
|
|
46
|
+
email: 'test@example.com',
|
|
47
|
+
password: 'password123',
|
|
48
|
+
name: 'Test User',
|
|
49
|
+
role: 'user',
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(user.id).toBeDefined();
|
|
53
|
+
expect(user.email).toBe('test@example.com');
|
|
54
|
+
expect(user.name).toBe('Test User');
|
|
55
|
+
expect(user.role).toBe('user');
|
|
56
|
+
expect(user.status).toBe('active');
|
|
57
|
+
expect(user.emailVerified).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should hash password on create', async () => {
|
|
61
|
+
const user = await userRepo.create({
|
|
62
|
+
email: 'hash@example.com',
|
|
63
|
+
password: 'plaintext',
|
|
64
|
+
name: 'Hash Test',
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Password should be hashed (not equal to plain)
|
|
68
|
+
expect(user.password).not.toBe('plaintext');
|
|
69
|
+
expect(user.password).toContain('$2');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should find user by ID', async () => {
|
|
73
|
+
const created = await userRepo.create({
|
|
74
|
+
email: 'find@example.com',
|
|
75
|
+
password: 'password',
|
|
76
|
+
name: 'Find Me',
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const found = await userRepo.findById(created.id);
|
|
80
|
+
expect(found).not.toBeNull();
|
|
81
|
+
expect(found?.email).toBe('find@example.com');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should find user by email', async () => {
|
|
85
|
+
await userRepo.create({
|
|
86
|
+
email: 'email@example.com',
|
|
87
|
+
password: 'password',
|
|
88
|
+
name: 'Email User',
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const user = await userRepo.findByEmail('email@example.com');
|
|
92
|
+
expect(user).not.toBeNull();
|
|
93
|
+
expect(user?.name).toBe('Email User');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should update user', async () => {
|
|
97
|
+
const user = await userRepo.create({
|
|
98
|
+
email: 'update@example.com',
|
|
99
|
+
password: 'password',
|
|
100
|
+
name: 'Original Name',
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const updated = await userRepo.update(user.id, {
|
|
104
|
+
name: 'Updated Name',
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(updated).not.toBeNull();
|
|
108
|
+
expect(updated?.name).toBe('Updated Name');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should delete user', async () => {
|
|
112
|
+
const user = await userRepo.create({
|
|
113
|
+
email: 'delete@example.com',
|
|
114
|
+
password: 'password',
|
|
115
|
+
name: 'Delete Me',
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const deleted = await userRepo.delete(user.id);
|
|
119
|
+
expect(deleted).toBe(true);
|
|
120
|
+
|
|
121
|
+
const found = await userRepo.findById(user.id);
|
|
122
|
+
expect(found).toBeNull();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should paginate users', async () => {
|
|
126
|
+
// Create 15 users
|
|
127
|
+
for (let i = 0; i < 15; i++) {
|
|
128
|
+
await userRepo.create({
|
|
129
|
+
email: `user${i}@example.com`,
|
|
130
|
+
password: 'password',
|
|
131
|
+
name: `User ${i}`,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Get first page
|
|
136
|
+
const page1 = await userRepo.findMany({}, { page: 1, limit: 10 });
|
|
137
|
+
expect(page1.data.length).toBe(10);
|
|
138
|
+
expect(page1.pagination.total).toBe(15);
|
|
139
|
+
expect(page1.pagination.totalPages).toBe(2);
|
|
140
|
+
expect(page1.pagination.hasNext).toBe(true);
|
|
141
|
+
expect(page1.pagination.hasPrev).toBe(false);
|
|
142
|
+
|
|
143
|
+
// Get second page
|
|
144
|
+
const page2 = await userRepo.findMany({}, { page: 2, limit: 10 });
|
|
145
|
+
expect(page2.data.length).toBe(5);
|
|
146
|
+
expect(page2.pagination.hasNext).toBe(false);
|
|
147
|
+
expect(page2.pagination.hasPrev).toBe(true);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should verify password', async () => {
|
|
151
|
+
const user = await userRepo.create({
|
|
152
|
+
email: 'verify@example.com',
|
|
153
|
+
password: 'correct-password',
|
|
154
|
+
name: 'Verify User',
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const validPassword = await userRepo.verifyPassword(user.id, 'correct-password');
|
|
158
|
+
expect(validPassword).toBe(true);
|
|
159
|
+
|
|
160
|
+
const invalidPassword = await userRepo.verifyPassword(user.id, 'wrong-password');
|
|
161
|
+
expect(invalidPassword).toBe(false);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should update password', async () => {
|
|
165
|
+
const user = await userRepo.create({
|
|
166
|
+
email: 'updatepw@example.com',
|
|
167
|
+
password: 'old-password',
|
|
168
|
+
name: 'Update PW',
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const updated = await userRepo.updatePassword(user.id, 'new-password');
|
|
172
|
+
expect(updated).toBe(true);
|
|
173
|
+
|
|
174
|
+
// Verify old password doesn't work
|
|
175
|
+
const oldValid = await userRepo.verifyPassword(user.id, 'old-password');
|
|
176
|
+
expect(oldValid).toBe(false);
|
|
177
|
+
|
|
178
|
+
// Verify new password works
|
|
179
|
+
const newValid = await userRepo.verifyPassword(user.id, 'new-password');
|
|
180
|
+
expect(newValid).toBe(true);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should verify email', async () => {
|
|
184
|
+
const user = await userRepo.create({
|
|
185
|
+
email: 'verifyemail@example.com',
|
|
186
|
+
password: 'password',
|
|
187
|
+
name: 'Verify Email',
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const verified = await userRepo.verifyEmail(user.id);
|
|
191
|
+
expect(verified).toBe(true);
|
|
192
|
+
|
|
193
|
+
const updated = await userRepo.findById(user.id);
|
|
194
|
+
expect(updated?.emailVerified).toBe(true);
|
|
195
|
+
expect(updated?.emailVerifiedAt).toBeDefined();
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// ==========================================
|
|
200
|
+
// PAYMENT REPOSITORY TESTS
|
|
201
|
+
// ==========================================
|
|
202
|
+
|
|
203
|
+
describe('Payment Repository', () => {
|
|
204
|
+
let userId: string;
|
|
205
|
+
|
|
206
|
+
beforeEach(async () => {
|
|
207
|
+
const user = await userRepo.create({
|
|
208
|
+
email: 'payment@example.com',
|
|
209
|
+
password: 'password',
|
|
210
|
+
name: 'Payment User',
|
|
211
|
+
});
|
|
212
|
+
userId = user.id;
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should create payment', async () => {
|
|
216
|
+
const payment = await paymentRepo.create({
|
|
217
|
+
userId,
|
|
218
|
+
provider: 'stripe',
|
|
219
|
+
method: 'card',
|
|
220
|
+
amount: 99.99,
|
|
221
|
+
currency: 'USD',
|
|
222
|
+
status: 'pending',
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
expect(payment.id).toBeDefined();
|
|
226
|
+
expect(payment.userId).toBe(userId);
|
|
227
|
+
expect(payment.amount).toBe(99.99);
|
|
228
|
+
expect(payment.status).toBe('pending');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should find payment by ID', async () => {
|
|
232
|
+
const created = await paymentRepo.create({
|
|
233
|
+
userId,
|
|
234
|
+
provider: 'paypal',
|
|
235
|
+
method: 'paypal',
|
|
236
|
+
amount: 49.99,
|
|
237
|
+
currency: 'EUR',
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const found = await paymentRepo.findById(created.id);
|
|
241
|
+
expect(found).not.toBeNull();
|
|
242
|
+
expect(found?.amount).toBe(49.99);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('should update payment status', async () => {
|
|
246
|
+
const payment = await paymentRepo.create({
|
|
247
|
+
userId,
|
|
248
|
+
provider: 'stripe',
|
|
249
|
+
method: 'card',
|
|
250
|
+
amount: 199.99,
|
|
251
|
+
currency: 'USD',
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const updated = await paymentRepo.updateStatus(payment.id, 'completed');
|
|
255
|
+
expect(updated).not.toBeNull();
|
|
256
|
+
expect(updated?.status).toBe('completed');
|
|
257
|
+
expect(updated?.completedAt).toBeDefined();
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should find payments by user', async () => {
|
|
261
|
+
// Create multiple payments
|
|
262
|
+
await paymentRepo.create({
|
|
263
|
+
userId,
|
|
264
|
+
provider: 'stripe',
|
|
265
|
+
method: 'card',
|
|
266
|
+
amount: 10,
|
|
267
|
+
currency: 'USD',
|
|
268
|
+
});
|
|
269
|
+
await paymentRepo.create({
|
|
270
|
+
userId,
|
|
271
|
+
provider: 'paypal',
|
|
272
|
+
method: 'paypal',
|
|
273
|
+
amount: 20,
|
|
274
|
+
currency: 'USD',
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const result = await paymentRepo.findByUserId(userId);
|
|
278
|
+
expect(result.data.length).toBe(2);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Subscription tests
|
|
282
|
+
it('should create subscription', async () => {
|
|
283
|
+
const plan = await paymentRepo.createPlan({
|
|
284
|
+
name: 'Pro Plan',
|
|
285
|
+
amount: 29.99,
|
|
286
|
+
currency: 'USD',
|
|
287
|
+
interval: 'month',
|
|
288
|
+
intervalCount: 1,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const subscription = await paymentRepo.createSubscription({
|
|
292
|
+
userId,
|
|
293
|
+
planId: plan.id,
|
|
294
|
+
provider: 'stripe',
|
|
295
|
+
status: 'active',
|
|
296
|
+
currentPeriodStart: new Date(),
|
|
297
|
+
currentPeriodEnd: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // +30 days
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
expect(subscription.id).toBeDefined();
|
|
301
|
+
expect(subscription.planId).toBe(plan.id);
|
|
302
|
+
expect(subscription.status).toBe('active');
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('should cancel subscription', async () => {
|
|
306
|
+
const plan = await paymentRepo.createPlan({
|
|
307
|
+
name: 'Basic Plan',
|
|
308
|
+
amount: 9.99,
|
|
309
|
+
currency: 'USD',
|
|
310
|
+
interval: 'month',
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
const subscription = await paymentRepo.createSubscription({
|
|
314
|
+
userId,
|
|
315
|
+
planId: plan.id,
|
|
316
|
+
provider: 'stripe',
|
|
317
|
+
status: 'active',
|
|
318
|
+
currentPeriodStart: new Date(),
|
|
319
|
+
currentPeriodEnd: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const cancelled = await paymentRepo.cancelSubscription(subscription.id);
|
|
323
|
+
expect(cancelled).not.toBeNull();
|
|
324
|
+
expect(cancelled?.status).toBe('cancelled');
|
|
325
|
+
expect(cancelled?.cancelledAt).toBeDefined();
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// Plan tests
|
|
329
|
+
it('should create plan', async () => {
|
|
330
|
+
const plan = await paymentRepo.createPlan({
|
|
331
|
+
name: 'Enterprise Plan',
|
|
332
|
+
amount: 99.99,
|
|
333
|
+
currency: 'USD',
|
|
334
|
+
interval: 'month',
|
|
335
|
+
intervalCount: 1,
|
|
336
|
+
trialPeriodDays: 14,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
expect(plan.id).toBeDefined();
|
|
340
|
+
expect(plan.name).toBe('Enterprise Plan');
|
|
341
|
+
expect(plan.amount).toBe(99.99);
|
|
342
|
+
expect(plan.trialPeriodDays).toBe(14);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('should find active plans', async () => {
|
|
346
|
+
await paymentRepo.createPlan({
|
|
347
|
+
name: 'Active Plan',
|
|
348
|
+
amount: 19.99,
|
|
349
|
+
currency: 'USD',
|
|
350
|
+
interval: 'month',
|
|
351
|
+
active: true,
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
await paymentRepo.createPlan({
|
|
355
|
+
name: 'Inactive Plan',
|
|
356
|
+
amount: 9.99,
|
|
357
|
+
currency: 'USD',
|
|
358
|
+
interval: 'month',
|
|
359
|
+
active: false,
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
const activePlans = await paymentRepo.findActivePlans();
|
|
363
|
+
expect(activePlans.length).toBe(1);
|
|
364
|
+
expect(activePlans[0].name).toBe('Active Plan');
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// Webhook tests
|
|
368
|
+
it('should store webhook event', async () => {
|
|
369
|
+
const webhook = await paymentRepo.storeWebhookEvent({
|
|
370
|
+
provider: 'stripe',
|
|
371
|
+
type: 'payment.succeeded',
|
|
372
|
+
data: { paymentId: 'pi_123', amount: 100 },
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
expect(webhook.id).toBeDefined();
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it('should mark webhook as processed', async () => {
|
|
379
|
+
const webhook = await paymentRepo.storeWebhookEvent({
|
|
380
|
+
provider: 'paypal',
|
|
381
|
+
type: 'payment.completed',
|
|
382
|
+
data: { orderId: 'order_123' },
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const marked = await paymentRepo.markWebhookProcessed(webhook.id);
|
|
386
|
+
expect(marked).toBe(true);
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// ==========================================
|
|
391
|
+
// ADAPTER TESTS
|
|
392
|
+
// ==========================================
|
|
393
|
+
|
|
394
|
+
describe('Mongoose Adapter', () => {
|
|
395
|
+
it('should connect to MongoDB', async () => {
|
|
396
|
+
const health = await adapter.healthCheck();
|
|
397
|
+
expect(health).toBe(true);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it('should return mongoose type', () => {
|
|
401
|
+
expect(adapter.getType()).toBe('mongoose');
|
|
402
|
+
expect(adapter.getDatabaseType()).toBe('mongodb');
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('should get raw mongoose instance', () => {
|
|
406
|
+
const mongoose = adapter.getRawClient();
|
|
407
|
+
expect(mongoose).toBeDefined();
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
});
|