servcraft 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/.claude/settings.local.json +29 -0
  2. package/.github/CODEOWNERS +18 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +46 -0
  4. package/.github/dependabot.yml +59 -0
  5. package/.github/workflows/ci.yml +188 -0
  6. package/.github/workflows/release.yml +195 -0
  7. package/AUDIT.md +602 -0
  8. package/README.md +1070 -1
  9. package/dist/cli/index.cjs +2026 -2168
  10. package/dist/cli/index.cjs.map +1 -1
  11. package/dist/cli/index.js +2026 -2168
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/index.cjs +595 -616
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +114 -52
  16. package/dist/index.d.ts +114 -52
  17. package/dist/index.js +595 -616
  18. package/dist/index.js.map +1 -1
  19. package/docs/CLI-001_MULTI_DB_PLAN.md +546 -0
  20. package/docs/DATABASE_MULTI_ORM.md +399 -0
  21. package/docs/PHASE1_BREAKDOWN.md +346 -0
  22. package/docs/PROGRESS.md +550 -0
  23. package/docs/modules/ANALYTICS.md +226 -0
  24. package/docs/modules/API-VERSIONING.md +252 -0
  25. package/docs/modules/AUDIT.md +192 -0
  26. package/docs/modules/AUTH.md +431 -0
  27. package/docs/modules/CACHE.md +346 -0
  28. package/docs/modules/EMAIL.md +254 -0
  29. package/docs/modules/FEATURE-FLAG.md +291 -0
  30. package/docs/modules/I18N.md +294 -0
  31. package/docs/modules/MEDIA-PROCESSING.md +281 -0
  32. package/docs/modules/MFA.md +266 -0
  33. package/docs/modules/NOTIFICATION.md +311 -0
  34. package/docs/modules/OAUTH.md +237 -0
  35. package/docs/modules/PAYMENT.md +804 -0
  36. package/docs/modules/QUEUE.md +540 -0
  37. package/docs/modules/RATE-LIMIT.md +339 -0
  38. package/docs/modules/SEARCH.md +288 -0
  39. package/docs/modules/SECURITY.md +327 -0
  40. package/docs/modules/SESSION.md +382 -0
  41. package/docs/modules/SWAGGER.md +305 -0
  42. package/docs/modules/UPLOAD.md +296 -0
  43. package/docs/modules/USER.md +505 -0
  44. package/docs/modules/VALIDATION.md +294 -0
  45. package/docs/modules/WEBHOOK.md +270 -0
  46. package/docs/modules/WEBSOCKET.md +691 -0
  47. package/package.json +53 -38
  48. package/prisma/schema.prisma +395 -1
  49. package/src/cli/commands/add-module.ts +520 -87
  50. package/src/cli/commands/db.ts +3 -4
  51. package/src/cli/commands/docs.ts +256 -6
  52. package/src/cli/commands/generate.ts +12 -19
  53. package/src/cli/commands/init.ts +384 -214
  54. package/src/cli/index.ts +0 -4
  55. package/src/cli/templates/repository.ts +6 -1
  56. package/src/cli/templates/routes.ts +6 -21
  57. package/src/cli/utils/docs-generator.ts +6 -7
  58. package/src/cli/utils/env-manager.ts +717 -0
  59. package/src/cli/utils/field-parser.ts +16 -7
  60. package/src/cli/utils/interactive-prompt.ts +223 -0
  61. package/src/cli/utils/template-manager.ts +346 -0
  62. package/src/config/database.config.ts +183 -0
  63. package/src/config/env.ts +0 -10
  64. package/src/config/index.ts +0 -14
  65. package/src/core/server.ts +1 -1
  66. package/src/database/adapters/mongoose.adapter.ts +132 -0
  67. package/src/database/adapters/prisma.adapter.ts +118 -0
  68. package/src/database/connection.ts +190 -0
  69. package/src/database/interfaces/database.interface.ts +85 -0
  70. package/src/database/interfaces/index.ts +7 -0
  71. package/src/database/interfaces/repository.interface.ts +129 -0
  72. package/src/database/models/mongoose/index.ts +7 -0
  73. package/src/database/models/mongoose/payment.schema.ts +347 -0
  74. package/src/database/models/mongoose/user.schema.ts +154 -0
  75. package/src/database/prisma.ts +1 -4
  76. package/src/database/redis.ts +101 -0
  77. package/src/database/repositories/mongoose/index.ts +7 -0
  78. package/src/database/repositories/mongoose/payment.repository.ts +380 -0
  79. package/src/database/repositories/mongoose/user.repository.ts +255 -0
  80. package/src/database/seed.ts +6 -1
  81. package/src/index.ts +9 -20
  82. package/src/middleware/security.ts +2 -6
  83. package/src/modules/analytics/analytics.routes.ts +80 -0
  84. package/src/modules/analytics/analytics.service.ts +364 -0
  85. package/src/modules/analytics/index.ts +18 -0
  86. package/src/modules/analytics/types.ts +180 -0
  87. package/src/modules/api-versioning/index.ts +15 -0
  88. package/src/modules/api-versioning/types.ts +86 -0
  89. package/src/modules/api-versioning/versioning.middleware.ts +120 -0
  90. package/src/modules/api-versioning/versioning.routes.ts +54 -0
  91. package/src/modules/api-versioning/versioning.service.ts +189 -0
  92. package/src/modules/audit/audit.repository.ts +206 -0
  93. package/src/modules/audit/audit.service.ts +27 -59
  94. package/src/modules/auth/auth.controller.ts +2 -2
  95. package/src/modules/auth/auth.middleware.ts +3 -9
  96. package/src/modules/auth/auth.routes.ts +10 -107
  97. package/src/modules/auth/auth.service.ts +126 -23
  98. package/src/modules/auth/index.ts +3 -4
  99. package/src/modules/cache/cache.service.ts +367 -0
  100. package/src/modules/cache/index.ts +10 -0
  101. package/src/modules/cache/types.ts +44 -0
  102. package/src/modules/email/email.service.ts +3 -10
  103. package/src/modules/email/templates.ts +2 -8
  104. package/src/modules/feature-flag/feature-flag.repository.ts +303 -0
  105. package/src/modules/feature-flag/feature-flag.routes.ts +247 -0
  106. package/src/modules/feature-flag/feature-flag.service.ts +566 -0
  107. package/src/modules/feature-flag/index.ts +20 -0
  108. package/src/modules/feature-flag/types.ts +192 -0
  109. package/src/modules/i18n/i18n.middleware.ts +186 -0
  110. package/src/modules/i18n/i18n.routes.ts +191 -0
  111. package/src/modules/i18n/i18n.service.ts +456 -0
  112. package/src/modules/i18n/index.ts +18 -0
  113. package/src/modules/i18n/types.ts +118 -0
  114. package/src/modules/media-processing/index.ts +17 -0
  115. package/src/modules/media-processing/media-processing.routes.ts +111 -0
  116. package/src/modules/media-processing/media-processing.service.ts +245 -0
  117. package/src/modules/media-processing/types.ts +156 -0
  118. package/src/modules/mfa/index.ts +20 -0
  119. package/src/modules/mfa/mfa.repository.ts +206 -0
  120. package/src/modules/mfa/mfa.routes.ts +595 -0
  121. package/src/modules/mfa/mfa.service.ts +572 -0
  122. package/src/modules/mfa/totp.ts +150 -0
  123. package/src/modules/mfa/types.ts +57 -0
  124. package/src/modules/notification/index.ts +20 -0
  125. package/src/modules/notification/notification.repository.ts +356 -0
  126. package/src/modules/notification/notification.service.ts +483 -0
  127. package/src/modules/notification/types.ts +119 -0
  128. package/src/modules/oauth/index.ts +20 -0
  129. package/src/modules/oauth/oauth.repository.ts +219 -0
  130. package/src/modules/oauth/oauth.routes.ts +446 -0
  131. package/src/modules/oauth/oauth.service.ts +293 -0
  132. package/src/modules/oauth/providers/apple.provider.ts +250 -0
  133. package/src/modules/oauth/providers/facebook.provider.ts +181 -0
  134. package/src/modules/oauth/providers/github.provider.ts +248 -0
  135. package/src/modules/oauth/providers/google.provider.ts +189 -0
  136. package/src/modules/oauth/providers/twitter.provider.ts +214 -0
  137. package/src/modules/oauth/types.ts +94 -0
  138. package/src/modules/payment/index.ts +19 -0
  139. package/src/modules/payment/payment.repository.ts +733 -0
  140. package/src/modules/payment/payment.routes.ts +390 -0
  141. package/src/modules/payment/payment.service.ts +354 -0
  142. package/src/modules/payment/providers/mobile-money.provider.ts +274 -0
  143. package/src/modules/payment/providers/paypal.provider.ts +190 -0
  144. package/src/modules/payment/providers/stripe.provider.ts +215 -0
  145. package/src/modules/payment/types.ts +140 -0
  146. package/src/modules/queue/cron.ts +438 -0
  147. package/src/modules/queue/index.ts +87 -0
  148. package/src/modules/queue/queue.routes.ts +600 -0
  149. package/src/modules/queue/queue.service.ts +842 -0
  150. package/src/modules/queue/types.ts +222 -0
  151. package/src/modules/queue/workers.ts +366 -0
  152. package/src/modules/rate-limit/index.ts +59 -0
  153. package/src/modules/rate-limit/rate-limit.middleware.ts +134 -0
  154. package/src/modules/rate-limit/rate-limit.routes.ts +269 -0
  155. package/src/modules/rate-limit/rate-limit.service.ts +348 -0
  156. package/src/modules/rate-limit/stores/memory.store.ts +165 -0
  157. package/src/modules/rate-limit/stores/redis.store.ts +322 -0
  158. package/src/modules/rate-limit/types.ts +153 -0
  159. package/src/modules/search/adapters/elasticsearch.adapter.ts +326 -0
  160. package/src/modules/search/adapters/meilisearch.adapter.ts +261 -0
  161. package/src/modules/search/adapters/memory.adapter.ts +278 -0
  162. package/src/modules/search/index.ts +21 -0
  163. package/src/modules/search/search.service.ts +234 -0
  164. package/src/modules/search/types.ts +214 -0
  165. package/src/modules/security/index.ts +40 -0
  166. package/src/modules/security/sanitize.ts +223 -0
  167. package/src/modules/security/security-audit.service.ts +388 -0
  168. package/src/modules/security/security.middleware.ts +398 -0
  169. package/src/modules/session/index.ts +3 -0
  170. package/src/modules/session/session.repository.ts +159 -0
  171. package/src/modules/session/session.service.ts +340 -0
  172. package/src/modules/session/types.ts +38 -0
  173. package/src/modules/swagger/index.ts +7 -1
  174. package/src/modules/swagger/schema-builder.ts +16 -4
  175. package/src/modules/swagger/swagger.service.ts +9 -10
  176. package/src/modules/swagger/types.ts +0 -2
  177. package/src/modules/upload/index.ts +14 -0
  178. package/src/modules/upload/types.ts +83 -0
  179. package/src/modules/upload/upload.repository.ts +199 -0
  180. package/src/modules/upload/upload.routes.ts +311 -0
  181. package/src/modules/upload/upload.service.ts +448 -0
  182. package/src/modules/user/index.ts +3 -3
  183. package/src/modules/user/user.controller.ts +15 -9
  184. package/src/modules/user/user.repository.ts +237 -113
  185. package/src/modules/user/user.routes.ts +39 -164
  186. package/src/modules/user/user.service.ts +4 -3
  187. package/src/modules/validation/validator.ts +12 -17
  188. package/src/modules/webhook/index.ts +91 -0
  189. package/src/modules/webhook/retry.ts +196 -0
  190. package/src/modules/webhook/signature.ts +135 -0
  191. package/src/modules/webhook/types.ts +181 -0
  192. package/src/modules/webhook/webhook.repository.ts +358 -0
  193. package/src/modules/webhook/webhook.routes.ts +442 -0
  194. package/src/modules/webhook/webhook.service.ts +457 -0
  195. package/src/modules/websocket/features.ts +504 -0
  196. package/src/modules/websocket/index.ts +106 -0
  197. package/src/modules/websocket/middlewares.ts +298 -0
  198. package/src/modules/websocket/types.ts +181 -0
  199. package/src/modules/websocket/websocket.service.ts +692 -0
  200. package/src/utils/errors.ts +7 -0
  201. package/src/utils/pagination.ts +4 -1
  202. package/tests/helpers/db-check.ts +79 -0
  203. package/tests/integration/auth-redis.test.ts +94 -0
  204. package/tests/integration/cache-redis.test.ts +387 -0
  205. package/tests/integration/mongoose-repositories.test.ts +410 -0
  206. package/tests/integration/payment-prisma.test.ts +637 -0
  207. package/tests/integration/queue-bullmq.test.ts +417 -0
  208. package/tests/integration/user-prisma.test.ts +441 -0
  209. package/tests/integration/websocket-socketio.test.ts +552 -0
  210. package/tests/setup.ts +11 -9
  211. package/vitest.config.ts +3 -8
  212. package/npm-cache/_cacache/content-v2/sha512/1c/d0/03440d500a0487621aad1d6402978340698976602046db8e24fa03c01ee6c022c69b0582f969042d9442ee876ac35c038e960dd427d1e622fa24b8eb7dba +0 -0
  213. package/npm-cache/_cacache/content-v2/sha512/42/55/28b493ca491833e5aab0e9c3108d29ab3f36c248ca88f45d4630674fce9130959e56ae308797ac2b6328fa7f09a610b9550ed09cb971d039876d293fc69d +0 -0
  214. package/npm-cache/_cacache/content-v2/sha512/e0/12/f360dc9315ee5f17844a0c8c233ee6bf7c30837c4a02ea0d56c61c7f7ab21c0e958e50ed2c57c59f983c762b93056778c9009b2398ffc26def0183999b13 +0 -0
  215. package/npm-cache/_cacache/content-v2/sha512/ed/b0/fae1161902898f4c913c67d7f6cdf6be0665aec3b389b9c4f4f0a101ca1da59badf1b59c4e0030f5223023b8d63cfe501c46a32c20c895d4fb3f11ca2232 +0 -0
  216. 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
+ });