@nextsparkjs/theme-crm 0.1.0-beta.19 → 0.1.0-beta.24

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 (74) hide show
  1. package/package.json +3 -3
  2. package/tests/cypress/e2e/api/activities/activities-crud.cy.ts +686 -0
  3. package/tests/cypress/e2e/api/campaigns/campaigns-crud.cy.ts +592 -0
  4. package/tests/cypress/e2e/api/companies/companies-crud.cy.ts +682 -0
  5. package/tests/cypress/e2e/api/contacts/contacts-crud.cy.ts +668 -0
  6. package/tests/cypress/e2e/api/leads/leads-crud.cy.ts +648 -0
  7. package/tests/cypress/e2e/api/notes/notes-crud.cy.ts +424 -0
  8. package/tests/cypress/e2e/api/opportunities/opportunities-crud.cy.ts +865 -0
  9. package/tests/cypress/e2e/api/pipelines/pipelines-crud.cy.ts +545 -0
  10. package/tests/cypress/e2e/api/products/products-crud.cy.ts +447 -0
  11. package/tests/cypress/e2e/ui/activities/activities-admin.cy.ts +268 -0
  12. package/tests/cypress/e2e/ui/activities/activities-member.cy.ts +257 -0
  13. package/tests/cypress/e2e/ui/activities/activities-owner.cy.ts +268 -0
  14. package/tests/cypress/e2e/ui/companies/companies-admin.cy.ts +188 -0
  15. package/tests/cypress/e2e/ui/companies/companies-member.cy.ts +166 -0
  16. package/tests/cypress/e2e/ui/companies/companies-owner.cy.ts +189 -0
  17. package/tests/cypress/e2e/ui/contacts/contacts-admin.cy.ts +252 -0
  18. package/tests/cypress/e2e/ui/contacts/contacts-member.cy.ts +224 -0
  19. package/tests/cypress/e2e/ui/contacts/contacts-owner.cy.ts +236 -0
  20. package/tests/cypress/e2e/ui/leads/leads-admin.cy.ts +286 -0
  21. package/tests/cypress/e2e/ui/leads/leads-member.cy.ts +193 -0
  22. package/tests/cypress/e2e/ui/leads/leads-owner.cy.ts +210 -0
  23. package/tests/cypress/e2e/ui/opportunities/opportunities-admin.cy.ts +197 -0
  24. package/tests/cypress/e2e/ui/opportunities/opportunities-member.cy.ts +229 -0
  25. package/tests/cypress/e2e/ui/opportunities/opportunities-owner.cy.ts +196 -0
  26. package/tests/cypress/e2e/ui/pipelines/pipelines-admin.cy.ts +320 -0
  27. package/tests/cypress/e2e/ui/pipelines/pipelines-member.cy.ts +262 -0
  28. package/tests/cypress/e2e/ui/pipelines/pipelines-owner.cy.ts +282 -0
  29. package/tests/cypress/fixtures/blocks.json +9 -0
  30. package/tests/cypress/fixtures/entities.json +240 -0
  31. package/tests/cypress/src/components/CRMDataTable.js +223 -0
  32. package/tests/cypress/src/components/CRMMobileNav.js +138 -0
  33. package/tests/cypress/src/components/CRMSidebar.js +145 -0
  34. package/tests/cypress/src/components/CRMTopBar.js +194 -0
  35. package/tests/cypress/src/components/DealCard.js +197 -0
  36. package/tests/cypress/src/components/EntityDetail.ts +290 -0
  37. package/tests/cypress/src/components/EntityForm.ts +357 -0
  38. package/tests/cypress/src/components/EntityList.ts +360 -0
  39. package/tests/cypress/src/components/PipelineKanban.js +204 -0
  40. package/tests/cypress/src/components/StageColumn.js +196 -0
  41. package/tests/cypress/src/components/index.js +13 -0
  42. package/tests/cypress/src/components/index.ts +22 -0
  43. package/tests/cypress/src/controllers/ActivityAPIController.ts +113 -0
  44. package/tests/cypress/src/controllers/BaseAPIController.ts +307 -0
  45. package/tests/cypress/src/controllers/CampaignAPIController.ts +114 -0
  46. package/tests/cypress/src/controllers/CompanyAPIController.ts +112 -0
  47. package/tests/cypress/src/controllers/ContactAPIController.ts +104 -0
  48. package/tests/cypress/src/controllers/LeadAPIController.ts +96 -0
  49. package/tests/cypress/src/controllers/NoteAPIController.ts +130 -0
  50. package/tests/cypress/src/controllers/OpportunityAPIController.ts +134 -0
  51. package/tests/cypress/src/controllers/PipelineAPIController.ts +116 -0
  52. package/tests/cypress/src/controllers/ProductAPIController.ts +113 -0
  53. package/tests/cypress/src/controllers/index.ts +35 -0
  54. package/tests/cypress/src/entities/ActivitiesPOM.ts +130 -0
  55. package/tests/cypress/src/entities/CompaniesPOM.ts +117 -0
  56. package/tests/cypress/src/entities/ContactsPOM.ts +117 -0
  57. package/tests/cypress/src/entities/LeadsPOM.ts +129 -0
  58. package/tests/cypress/src/entities/OpportunitiesPOM.ts +178 -0
  59. package/tests/cypress/src/entities/PipelinesPOM.ts +341 -0
  60. package/tests/cypress/src/entities/index.ts +31 -0
  61. package/tests/cypress/src/forms/OpportunityForm.js +316 -0
  62. package/tests/cypress/src/forms/PipelineForm.js +243 -0
  63. package/tests/cypress/src/forms/index.js +8 -0
  64. package/tests/cypress/src/index.js +22 -0
  65. package/tests/cypress/src/index.ts +68 -0
  66. package/tests/cypress/src/selectors.ts +50 -0
  67. package/tests/cypress/src/session-helpers.ts +94 -0
  68. package/tests/cypress/support/e2e.ts +89 -0
  69. package/tests/cypress.config.ts +165 -0
  70. package/tests/jest/__mocks__/jose.js +22 -0
  71. package/tests/jest/__mocks__/next-server.js +56 -0
  72. package/tests/jest/jest.config.cjs +127 -0
  73. package/tests/jest/setup.ts +170 -0
  74. package/tests/tsconfig.json +15 -0
@@ -0,0 +1,447 @@
1
+ /**
2
+ * Products API - CRUD Tests
3
+ *
4
+ * Comprehensive test suite for Product API endpoints.
5
+ * Tests GET, POST, PATCH, DELETE operations.
6
+ *
7
+ * Entity characteristics:
8
+ * - Required fields: name, price
9
+ * - Optional fields: sku, category, description, isActive
10
+ * - Access: shared within team (all team members see all products)
11
+ * - Team context: required (x-team-id header)
12
+ * - Type: Config entity (typically lower test count)
13
+ */
14
+
15
+ /// <reference types="cypress" />
16
+
17
+ import { ProductAPIController } from '../../../src/controllers'
18
+
19
+ describe('Products API - CRUD Operations', () => {
20
+ // Test constants
21
+ const SUPERADMIN_API_KEY = 'test_api_key_for_testing_purposes_only_not_a_real_secret_key_abc123'
22
+ const TEAM_ID = 'team-tmt-001'
23
+ const BASE_URL = Cypress.config('baseUrl') || 'http://localhost:5173'
24
+
25
+ // Controller instance
26
+ let productAPI: InstanceType<typeof ProductAPIController>
27
+
28
+ // Track created products for cleanup
29
+ let createdProducts: any[] = []
30
+
31
+ before(() => {
32
+ // Initialize controller with superadmin credentials
33
+ productAPI = new ProductAPIController(BASE_URL, SUPERADMIN_API_KEY, TEAM_ID)
34
+ })
35
+
36
+ afterEach(() => {
37
+ // Cleanup created products after each test
38
+ createdProducts.forEach((product) => {
39
+ if (product?.id) {
40
+ productAPI.delete(product.id)
41
+ }
42
+ })
43
+ createdProducts = []
44
+ })
45
+
46
+ // ============================================
47
+ // GET /api/v1/products - List Products
48
+ // ============================================
49
+ describe('GET /api/v1/products - List Products', () => {
50
+ it('PROD_API_001: Should list products with valid API key', () => {
51
+ productAPI.getAll().then((response: any) => {
52
+ productAPI.validateSuccessResponse(response, 200)
53
+ expect(response.body.data).to.be.an('array')
54
+ expect(response.body.info).to.have.property('page')
55
+ expect(response.body.info).to.have.property('limit')
56
+ expect(response.body.info).to.have.property('total')
57
+ expect(response.body.info).to.have.property('totalPages')
58
+
59
+ cy.log(`Found ${response.body.data.length} products`)
60
+ })
61
+ })
62
+
63
+ it('PROD_API_002: Should list products with pagination', () => {
64
+ productAPI.getAll({ page: 1, limit: 5 }).then((response: any) => {
65
+ productAPI.validateSuccessResponse(response, 200)
66
+ expect(response.body.info.page).to.eq(1)
67
+ expect(response.body.info.limit).to.eq(5)
68
+ expect(response.body.data.length).to.be.at.most(5)
69
+
70
+ cy.log(`Page 1 with limit 5: ${response.body.data.length} products`)
71
+ })
72
+ })
73
+
74
+ it('PROD_API_003: Should filter products by category', () => {
75
+ // First create a product with a specific category
76
+ const testCategory = 'Software'
77
+ const productData = productAPI.generateRandomData({ category: testCategory })
78
+
79
+ productAPI.create(productData).then((createResponse: any) => {
80
+ expect(createResponse.status).to.eq(201)
81
+ createdProducts.push(createResponse.body.data)
82
+
83
+ // Now filter by that category
84
+ productAPI.getAll({ category: testCategory }).then((response: any) => {
85
+ productAPI.validateSuccessResponse(response, 200)
86
+ expect(response.body.data).to.be.an('array')
87
+
88
+ // All returned products should have the specified category
89
+ response.body.data.forEach((product: any) => {
90
+ expect(product.category).to.eq(testCategory)
91
+ })
92
+
93
+ cy.log(`Found ${response.body.data.length} products with category '${testCategory}'`)
94
+ })
95
+ })
96
+ })
97
+
98
+ it('PROD_API_004: Should filter products by active status', () => {
99
+ // First create active and inactive products
100
+ const activeProduct = productAPI.generateRandomData({ isActive: true })
101
+ const inactiveProduct = productAPI.generateRandomData({ isActive: false })
102
+
103
+ productAPI.create(activeProduct).then((createResponse1: any) => {
104
+ expect(createResponse1.status).to.eq(201)
105
+ createdProducts.push(createResponse1.body.data)
106
+
107
+ productAPI.create(inactiveProduct).then((createResponse2: any) => {
108
+ expect(createResponse2.status).to.eq(201)
109
+ createdProducts.push(createResponse2.body.data)
110
+
111
+ // Filter by active status
112
+ productAPI.getAll({ isActive: true }).then((response: any) => {
113
+ productAPI.validateSuccessResponse(response, 200)
114
+ expect(response.body.data).to.be.an('array')
115
+
116
+ // Verify our active product is in results
117
+ const foundActive = response.body.data.find(
118
+ (p: any) => p.id === createResponse1.body.data.id
119
+ )
120
+ expect(foundActive).to.exist
121
+ expect(foundActive.isActive).to.be.true
122
+
123
+ cy.log(`Found ${response.body.data.length} active products`)
124
+ })
125
+ })
126
+ })
127
+ })
128
+ })
129
+
130
+ // ============================================
131
+ // POST /api/v1/products - Create Product
132
+ // ============================================
133
+ describe('POST /api/v1/products - Create Product', () => {
134
+ it('PROD_API_010: Should create product with valid data', () => {
135
+ const productData = productAPI.generateRandomData({
136
+ code: 'ENT-LIC-001',
137
+ name: 'Enterprise License',
138
+ price: 9999.99,
139
+ category: 'Software',
140
+ description: 'Full enterprise license with all features',
141
+ isActive: true
142
+ })
143
+
144
+ productAPI.create(productData).then((response: any) => {
145
+ productAPI.validateSuccessResponse(response, 201)
146
+ createdProducts.push(response.body.data)
147
+
148
+ const product = response.body.data
149
+ productAPI.validateObject(product)
150
+
151
+ // Verify provided data
152
+ expect(product.name).to.eq(productData.name)
153
+ expect(product.price).to.satisfy((val: any) =>
154
+ typeof val === 'number' ? val === productData.price : parseFloat(val) === productData.price
155
+ )
156
+ expect(product.code).to.eq(productData.code)
157
+ expect(product.category).to.eq(productData.category)
158
+ expect(product.description).to.eq(productData.description)
159
+ expect(product.isActive).to.eq(productData.isActive)
160
+
161
+ cy.log(`Created product: ${product.name} (ID: ${product.id})`)
162
+ })
163
+ })
164
+
165
+ it('PROD_API_011: Should create product with minimal data (code, name, price only)', () => {
166
+ const minimalData = {
167
+ code: `MIN-${Date.now()}`,
168
+ name: `Minimal Product ${Date.now()}`,
169
+ price: 99.99
170
+ }
171
+
172
+ productAPI.create(minimalData).then((response: any) => {
173
+ productAPI.validateSuccessResponse(response, 201)
174
+ createdProducts.push(response.body.data)
175
+
176
+ const product = response.body.data
177
+ productAPI.validateObject(product)
178
+
179
+ // Verify required fields
180
+ expect(product.name).to.eq(minimalData.name)
181
+ expect(product.price).to.satisfy((val: any) =>
182
+ typeof val === 'number' ? val === minimalData.price : parseFloat(val) === minimalData.price
183
+ )
184
+
185
+ cy.log(`Created product with minimal data: ${product.id}`)
186
+ })
187
+ })
188
+
189
+ it('PROD_API_012: Should create product with all optional fields', () => {
190
+ const productData = productAPI.generateRandomData({
191
+ code: 'COMPLETE-001',
192
+ name: `Complete Product ${Date.now()}`,
193
+ price: 4999.99,
194
+ category: 'Services',
195
+ description: 'Product with all fields populated',
196
+ isActive: true
197
+ })
198
+
199
+ productAPI.create(productData).then((response: any) => {
200
+ productAPI.validateSuccessResponse(response, 201)
201
+ createdProducts.push(response.body.data)
202
+
203
+ const product = response.body.data
204
+
205
+ // Verify all fields
206
+ expect(product.name).to.eq(productData.name)
207
+ expect(product.code).to.eq(productData.code)
208
+ expect(product.category).to.eq(productData.category)
209
+ expect(product.description).to.eq(productData.description)
210
+ expect(product.isActive).to.eq(productData.isActive)
211
+
212
+ cy.log(`Created product with all fields: ${product.id}`)
213
+ })
214
+ })
215
+
216
+ it('PROD_API_013: Should reject creation without name', () => {
217
+ const invalidData = {
218
+ code: 'NO-NAME-001',
219
+ price: 99.99
220
+ // Missing: name
221
+ }
222
+
223
+ productAPI.create(invalidData).then((response: any) => {
224
+ productAPI.validateErrorResponse(response, 400, 'VALIDATION_ERROR')
225
+
226
+ cy.log('Creation without name rejected with VALIDATION_ERROR')
227
+ })
228
+ })
229
+ })
230
+
231
+ // ============================================
232
+ // GET /api/v1/products/{id} - Get Product by ID
233
+ // ============================================
234
+ describe('GET /api/v1/products/{id} - Get Product by ID', () => {
235
+ it('PROD_API_020: Should get product by valid ID', () => {
236
+ // First create a product
237
+ const productData = productAPI.generateRandomData()
238
+
239
+ productAPI.create(productData).then((createResponse: any) => {
240
+ expect(createResponse.status).to.eq(201)
241
+ createdProducts.push(createResponse.body.data)
242
+
243
+ const productId = createResponse.body.data.id
244
+
245
+ // Get the product by ID
246
+ productAPI.getById(productId).then((response: any) => {
247
+ productAPI.validateSuccessResponse(response, 200)
248
+
249
+ const product = response.body.data
250
+ productAPI.validateObject(product)
251
+ expect(product.id).to.eq(productId)
252
+ expect(product.name).to.eq(productData.name)
253
+
254
+ cy.log(`Retrieved product: ${product.name}`)
255
+ })
256
+ })
257
+ })
258
+
259
+ it('PROD_API_021: Should return 404 for non-existent product', () => {
260
+ const fakeId = 'non-existent-product-id-12345'
261
+
262
+ productAPI.getById(fakeId).then((response: any) => {
263
+ expect(response.status).to.eq(404)
264
+ expect(response.body).to.have.property('success', false)
265
+
266
+ cy.log('Non-existent product returns 404')
267
+ })
268
+ })
269
+ })
270
+
271
+ // ============================================
272
+ // PATCH /api/v1/products/{id} - Update Product
273
+ // ============================================
274
+ describe('PATCH /api/v1/products/{id} - Update Product', () => {
275
+ it('PROD_API_030: Should update product with multiple fields', () => {
276
+ // First create a product
277
+ productAPI.createTestRecord().then((testProduct: any) => {
278
+ createdProducts.push(testProduct)
279
+
280
+ const updateData = {
281
+ name: 'Updated Product Name',
282
+ price: 1999.99,
283
+ category: 'Training',
284
+ description: 'Updated product description'
285
+ }
286
+
287
+ productAPI.update(testProduct.id, updateData).then((response: any) => {
288
+ productAPI.validateSuccessResponse(response, 200)
289
+
290
+ const product = response.body.data
291
+ expect(product.name).to.eq(updateData.name)
292
+ expect(product.category).to.eq(updateData.category)
293
+ expect(product.description).to.eq(updateData.description)
294
+
295
+ cy.log(`Updated product: ${product.name}`)
296
+ })
297
+ })
298
+ })
299
+
300
+ it('PROD_API_031: Should update product price', () => {
301
+ productAPI.createTestRecord().then((testProduct: any) => {
302
+ createdProducts.push(testProduct)
303
+
304
+ const newPrice = 2999.99
305
+
306
+ productAPI.update(testProduct.id, { price: newPrice }).then((response: any) => {
307
+ productAPI.validateSuccessResponse(response, 200)
308
+ expect(response.body.data.price).to.satisfy((val: any) =>
309
+ typeof val === 'number' ? val === newPrice : parseFloat(val) === newPrice
310
+ )
311
+
312
+ cy.log(`Updated price to: ${newPrice}`)
313
+ })
314
+ })
315
+ })
316
+
317
+ it('PROD_API_032: Should update product active status', () => {
318
+ productAPI.createTestRecord().then((testProduct: any) => {
319
+ createdProducts.push(testProduct)
320
+
321
+ const newStatus = false
322
+
323
+ productAPI.update(testProduct.id, { isActive: newStatus }).then((response: any) => {
324
+ productAPI.validateSuccessResponse(response, 200)
325
+ expect(response.body.data.isActive).to.eq(newStatus)
326
+
327
+ cy.log(`Updated isActive to: ${newStatus}`)
328
+ })
329
+ })
330
+ })
331
+
332
+ it('PROD_API_033: Should return 404 for non-existent product', () => {
333
+ const fakeId = 'non-existent-product-id-12345'
334
+
335
+ productAPI.update(fakeId, { name: 'New Name' }).then((response: any) => {
336
+ expect(response.status).to.eq(404)
337
+ expect(response.body).to.have.property('success', false)
338
+
339
+ cy.log('Update non-existent product returns 404')
340
+ })
341
+ })
342
+ })
343
+
344
+ // ============================================
345
+ // DELETE /api/v1/products/{id} - Delete Product
346
+ // ============================================
347
+ describe('DELETE /api/v1/products/{id} - Delete Product', () => {
348
+ it('PROD_API_040: Should delete product by valid ID', () => {
349
+ // Create a product to delete
350
+ const productData = productAPI.generateRandomData()
351
+
352
+ productAPI.create(productData).then((createResponse: any) => {
353
+ expect(createResponse.status).to.eq(201)
354
+ const productId = createResponse.body.data.id
355
+
356
+ // Delete the product
357
+ productAPI.delete(productId).then((response: any) => {
358
+ productAPI.validateSuccessResponse(response, 200)
359
+ expect(response.body.data).to.have.property('success', true)
360
+ expect(response.body.data).to.have.property('id', productId)
361
+
362
+ cy.log(`Deleted product: ${productId}`)
363
+ })
364
+ })
365
+ })
366
+
367
+ it('PROD_API_041: Should return 404 for non-existent product', () => {
368
+ const fakeId = 'non-existent-product-id-12345'
369
+
370
+ productAPI.delete(fakeId).then((response: any) => {
371
+ expect(response.status).to.eq(404)
372
+ expect(response.body).to.have.property('success', false)
373
+
374
+ cy.log('Delete non-existent product returns 404')
375
+ })
376
+ })
377
+ })
378
+
379
+ // ============================================
380
+ // Integration - Complete CRUD Lifecycle
381
+ // ============================================
382
+ describe('Integration - Complete CRUD Lifecycle', () => {
383
+ it('PROD_API_100: Should complete full lifecycle: Create -> Read -> Update -> Delete', () => {
384
+ // 1. CREATE
385
+ const productData = productAPI.generateRandomData({
386
+ code: 'LIFECYCLE-001',
387
+ name: 'Lifecycle Test Product',
388
+ price: 1999.99,
389
+ category: 'Software',
390
+ description: 'Initial product for lifecycle testing',
391
+ isActive: true
392
+ })
393
+
394
+ productAPI.create(productData).then((createResponse: any) => {
395
+ productAPI.validateSuccessResponse(createResponse, 201)
396
+ const productId = createResponse.body.data.id
397
+
398
+ cy.log(`1. Created product: ${productId}`)
399
+
400
+ // 2. READ
401
+ productAPI.getById(productId).then((readResponse: any) => {
402
+ productAPI.validateSuccessResponse(readResponse, 200)
403
+ expect(readResponse.body.data.name).to.eq(productData.name)
404
+ expect(readResponse.body.data.code).to.eq(productData.code)
405
+
406
+ cy.log(`2. Read product: ${readResponse.body.data.name}`)
407
+
408
+ // 3. UPDATE
409
+ const updateData = {
410
+ name: 'Updated Lifecycle Product',
411
+ price: 2999.99,
412
+ code: 'LIFECYCLE-002',
413
+ category: 'Services',
414
+ description: 'Updated product description',
415
+ isActive: false
416
+ }
417
+
418
+ productAPI.update(productId, updateData).then((updateResponse: any) => {
419
+ productAPI.validateSuccessResponse(updateResponse, 200)
420
+ expect(updateResponse.body.data.name).to.eq(updateData.name)
421
+ expect(updateResponse.body.data.code).to.eq(updateData.code)
422
+ expect(updateResponse.body.data.category).to.eq(updateData.category)
423
+ expect(updateResponse.body.data.isActive).to.eq(updateData.isActive)
424
+
425
+ cy.log(`3. Updated product: ${updateResponse.body.data.name}`)
426
+
427
+ // 4. DELETE
428
+ productAPI.delete(productId).then((deleteResponse: any) => {
429
+ productAPI.validateSuccessResponse(deleteResponse, 200)
430
+ expect(deleteResponse.body.data).to.have.property('success', true)
431
+
432
+ cy.log(`4. Deleted product: ${productId}`)
433
+
434
+ // 5. VERIFY DELETION
435
+ productAPI.getById(productId).then((verifyResponse: any) => {
436
+ expect(verifyResponse.status).to.eq(404)
437
+
438
+ cy.log('5. Verified deletion - product no longer exists')
439
+ cy.log('Full CRUD lifecycle completed successfully!')
440
+ })
441
+ })
442
+ })
443
+ })
444
+ })
445
+ })
446
+ })
447
+ })