@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,648 @@
1
+ /**
2
+ * Leads API - CRUD Tests
3
+ *
4
+ * Comprehensive test suite for Lead API endpoints.
5
+ * Tests GET, POST, PATCH, DELETE operations.
6
+ *
7
+ * Entity characteristics:
8
+ * - Required fields: companyName, contactName, email
9
+ * - Access: shared within team (all team members see all leads)
10
+ * - Team context: required (x-team-id header)
11
+ * - Special: score field, source enum, status enum
12
+ */
13
+
14
+ /// <reference types="cypress" />
15
+
16
+ import { LeadAPIController } from '../../../src/controllers'
17
+
18
+ describe('Leads API - CRUD Operations', () => {
19
+ // Test constants
20
+ const SUPERADMIN_API_KEY = 'test_api_key_for_testing_purposes_only_not_a_real_secret_key_abc123'
21
+ const TEAM_ID = 'team-tmt-001'
22
+ const BASE_URL = Cypress.config('baseUrl') || 'http://localhost:5173'
23
+
24
+ // Controller instance
25
+ let leadAPI: InstanceType<typeof LeadAPIController>
26
+
27
+ // Track created leads for cleanup
28
+ let createdLeads: any[] = []
29
+
30
+ before(() => {
31
+ // Initialize controller with superadmin credentials
32
+ leadAPI = new LeadAPIController(BASE_URL, SUPERADMIN_API_KEY, TEAM_ID)
33
+ })
34
+
35
+ afterEach(() => {
36
+ // Cleanup created leads after each test
37
+ createdLeads.forEach((lead) => {
38
+ if (lead?.id) {
39
+ leadAPI.delete(lead.id)
40
+ }
41
+ })
42
+ createdLeads = []
43
+ })
44
+
45
+ // ============================================
46
+ // GET /api/v1/leads - List Leads
47
+ // ============================================
48
+ describe('GET /api/v1/leads - List Leads', () => {
49
+ it('LEAD_API_001: Should list leads with valid API key', () => {
50
+ leadAPI.getAll().then((response: any) => {
51
+ leadAPI.validateSuccessResponse(response, 200)
52
+ expect(response.body.data).to.be.an('array')
53
+ expect(response.body.info).to.have.property('page')
54
+ expect(response.body.info).to.have.property('limit')
55
+ expect(response.body.info).to.have.property('total')
56
+ expect(response.body.info).to.have.property('totalPages')
57
+
58
+ cy.log(`Found ${response.body.data.length} leads`)
59
+ })
60
+ })
61
+
62
+ it('LEAD_API_002: Should list leads with pagination', () => {
63
+ leadAPI.getAll({ page: 1, limit: 5 }).then((response: any) => {
64
+ leadAPI.validateSuccessResponse(response, 200)
65
+ expect(response.body.info.page).to.eq(1)
66
+ expect(response.body.info.limit).to.eq(5)
67
+ expect(response.body.data.length).to.be.at.most(5)
68
+
69
+ cy.log(`Page 1 with limit 5: ${response.body.data.length} leads`)
70
+ })
71
+ })
72
+
73
+ it('LEAD_API_003: Should filter leads by status', () => {
74
+ // First create a lead with a specific status
75
+ const testStatus = 'new'
76
+ const leadData = leadAPI.generateRandomData({ status: testStatus })
77
+
78
+ leadAPI.create(leadData).then((createResponse: any) => {
79
+ expect(createResponse.status).to.eq(201)
80
+ createdLeads.push(createResponse.body.data)
81
+
82
+ // Now filter by that status
83
+ leadAPI.getAll({ status: testStatus }).then((response: any) => {
84
+ leadAPI.validateSuccessResponse(response, 200)
85
+ expect(response.body.data).to.be.an('array')
86
+
87
+ // All returned leads should have the specified status
88
+ response.body.data.forEach((lead: any) => {
89
+ expect(lead.status).to.eq(testStatus)
90
+ })
91
+
92
+ cy.log(`Found ${response.body.data.length} leads with status '${testStatus}'`)
93
+ })
94
+ })
95
+ })
96
+
97
+ it('LEAD_API_004: Should filter leads by source', () => {
98
+ // First create a lead with a specific source (valid value from entity config)
99
+ const testSource = 'web'
100
+ const leadData = leadAPI.generateRandomData({ source: testSource })
101
+
102
+ leadAPI.create(leadData).then((createResponse: any) => {
103
+ expect(createResponse.status).to.eq(201)
104
+ createdLeads.push(createResponse.body.data)
105
+
106
+ // Now filter by that source
107
+ leadAPI.getAll({ source: testSource }).then((response: any) => {
108
+ leadAPI.validateSuccessResponse(response, 200)
109
+ expect(response.body.data).to.be.an('array')
110
+
111
+ // All returned leads should have the specified source
112
+ response.body.data.forEach((lead: any) => {
113
+ expect(lead.source).to.eq(testSource)
114
+ })
115
+
116
+ cy.log(`Found ${response.body.data.length} leads with source '${testSource}'`)
117
+ })
118
+ })
119
+ })
120
+
121
+ it('LEAD_API_005: Should search leads by name/email', () => {
122
+ // Create a lead with a unique searchable term
123
+ const uniqueTerm = `SearchLead${Date.now()}`
124
+ const leadData = leadAPI.generateRandomData({
125
+ companyName: `${uniqueTerm} Corporation`
126
+ })
127
+
128
+ leadAPI.create(leadData).then((createResponse: any) => {
129
+ expect(createResponse.status).to.eq(201)
130
+ createdLeads.push(createResponse.body.data)
131
+
132
+ // Search for the unique term
133
+ leadAPI.getAll({ search: uniqueTerm }).then((response: any) => {
134
+ leadAPI.validateSuccessResponse(response, 200)
135
+ expect(response.body.data).to.be.an('array')
136
+ expect(response.body.data.length).to.be.greaterThan(0)
137
+
138
+ // Verify the found lead contains our search term
139
+ const foundLead = response.body.data.find(
140
+ (l: any) => l.id === createResponse.body.data.id
141
+ )
142
+ expect(foundLead).to.exist
143
+ expect(foundLead.companyName).to.include(uniqueTerm)
144
+
145
+ cy.log(`Search found ${response.body.data.length} leads matching '${uniqueTerm}'`)
146
+ })
147
+ })
148
+ })
149
+
150
+ it('LEAD_API_006: Should return empty array for non-matching search', () => {
151
+ const nonExistentTerm = 'NonExistentLeadSearchTerm123456789'
152
+
153
+ leadAPI.getAll({ search: nonExistentTerm }).then((response: any) => {
154
+ leadAPI.validateSuccessResponse(response, 200)
155
+ expect(response.body.data).to.be.an('array')
156
+ expect(response.body.data.length).to.eq(0)
157
+
158
+ cy.log('Search with non-matching term returns empty array')
159
+ })
160
+ })
161
+
162
+ it('LEAD_API_007: Should reject request without API key', () => {
163
+ const noAuthAPI = new LeadAPIController(BASE_URL, null, TEAM_ID)
164
+
165
+ noAuthAPI.getAll().then((response: any) => {
166
+ expect(response.status).to.eq(401)
167
+ expect(response.body).to.have.property('success', false)
168
+
169
+ cy.log('Request without API key rejected with 401')
170
+ })
171
+ })
172
+
173
+ it('LEAD_API_008: Should reject request without x-team-id', () => {
174
+ const noTeamAPI = new LeadAPIController(BASE_URL, SUPERADMIN_API_KEY, null)
175
+
176
+ noTeamAPI.getAll().then((response: any) => {
177
+ expect(response.status).to.eq(400)
178
+ expect(response.body).to.have.property('success', false)
179
+ expect(response.body).to.have.property('code', 'TEAM_CONTEXT_REQUIRED')
180
+
181
+ cy.log('Request without x-team-id rejected with TEAM_CONTEXT_REQUIRED')
182
+ })
183
+ })
184
+ })
185
+
186
+ // ============================================
187
+ // POST /api/v1/leads - Create Lead
188
+ // ============================================
189
+ describe('POST /api/v1/leads - Create Lead', () => {
190
+ it('LEAD_API_010: Should create lead with valid data', () => {
191
+ const leadData = leadAPI.generateRandomData({
192
+ phone: '+1-555-1234',
193
+ website: 'https://www.testcompany.com',
194
+ score: 85,
195
+ industry: 'Technology',
196
+ companySize: '51-200',
197
+ budget: 50000,
198
+ notes: 'High quality lead from tech conference'
199
+ })
200
+
201
+ leadAPI.create(leadData).then((response: any) => {
202
+ leadAPI.validateSuccessResponse(response, 201)
203
+ createdLeads.push(response.body.data)
204
+
205
+ const lead = response.body.data
206
+ leadAPI.validateObject(lead)
207
+
208
+ // Verify provided data
209
+ expect(lead.companyName).to.eq(leadData.companyName)
210
+ expect(lead.contactName).to.eq(leadData.contactName)
211
+ expect(lead.email).to.eq(leadData.email)
212
+ expect(lead.phone).to.eq(leadData.phone)
213
+ expect(lead.website).to.eq(leadData.website)
214
+ expect(Number(lead.score)).to.eq(leadData.score)
215
+ expect(lead.industry).to.eq(leadData.industry)
216
+ expect(lead.companySize).to.eq(leadData.companySize)
217
+ expect(Number(lead.budget)).to.eq(leadData.budget)
218
+ expect(lead.notes).to.eq(leadData.notes)
219
+
220
+ cy.log(`Created lead: ${lead.companyName} (ID: ${lead.id})`)
221
+ })
222
+ })
223
+
224
+ it('LEAD_API_011: Should create lead with minimal data', () => {
225
+ const minimalData = {
226
+ companyName: `Minimal Company ${Date.now()}`,
227
+ contactName: `Minimal Contact ${Date.now()}`,
228
+ email: `minimal-${Date.now()}@test.com`
229
+ }
230
+
231
+ leadAPI.create(minimalData).then((response: any) => {
232
+ leadAPI.validateSuccessResponse(response, 201)
233
+ createdLeads.push(response.body.data)
234
+
235
+ const lead = response.body.data
236
+ leadAPI.validateObject(lead)
237
+
238
+ // Verify required fields
239
+ expect(lead.companyName).to.eq(minimalData.companyName)
240
+ expect(lead.contactName).to.eq(minimalData.contactName)
241
+ expect(lead.email).to.eq(minimalData.email)
242
+
243
+ cy.log(`Created lead with minimal data: ${lead.id}`)
244
+ })
245
+ })
246
+
247
+ it('LEAD_API_012: Should create lead with score and all optional fields', () => {
248
+ const leadData = leadAPI.generateRandomData({
249
+ score: 100,
250
+ source: 'social_media',
251
+ status: 'qualified',
252
+ industry: 'Healthcare',
253
+ companySize: '500+',
254
+ budget: 100000,
255
+ notes: 'Top priority lead'
256
+ })
257
+
258
+ leadAPI.create(leadData).then((response: any) => {
259
+ leadAPI.validateSuccessResponse(response, 201)
260
+ createdLeads.push(response.body.data)
261
+
262
+ const lead = response.body.data
263
+ expect(Number(lead.score)).to.eq(100)
264
+ expect(lead.source).to.eq('social_media')
265
+ expect(lead.status).to.eq('qualified')
266
+ expect(lead.industry).to.eq('Healthcare')
267
+ expect(lead.companySize).to.eq('500+')
268
+ expect(Number(lead.budget)).to.eq(100000)
269
+
270
+ cy.log(`Created lead with score: ${lead.score}`)
271
+ })
272
+ })
273
+
274
+ it('LEAD_API_013: Should reject creation without companyName', () => {
275
+ const invalidData = {
276
+ contactName: 'Test Contact',
277
+ email: `test-${Date.now()}@test.com`
278
+ // Missing: companyName
279
+ }
280
+
281
+ leadAPI.create(invalidData).then((response: any) => {
282
+ leadAPI.validateErrorResponse(response, 400, 'VALIDATION_ERROR')
283
+
284
+ cy.log('Creation without companyName rejected with VALIDATION_ERROR')
285
+ })
286
+ })
287
+
288
+ it('LEAD_API_014: Should reject creation without contactName', () => {
289
+ const invalidData = {
290
+ companyName: 'Test Company',
291
+ email: `test-${Date.now()}@test.com`
292
+ // Missing: contactName
293
+ }
294
+
295
+ leadAPI.create(invalidData).then((response: any) => {
296
+ leadAPI.validateErrorResponse(response, 400, 'VALIDATION_ERROR')
297
+
298
+ cy.log('Creation without contactName rejected with VALIDATION_ERROR')
299
+ })
300
+ })
301
+
302
+ it('LEAD_API_015: Should reject creation without email', () => {
303
+ const invalidData = {
304
+ companyName: 'Test Company',
305
+ contactName: 'Test Contact'
306
+ // Missing: email
307
+ }
308
+
309
+ leadAPI.create(invalidData).then((response: any) => {
310
+ leadAPI.validateErrorResponse(response, 400, 'VALIDATION_ERROR')
311
+
312
+ cy.log('Creation without email rejected with VALIDATION_ERROR')
313
+ })
314
+ })
315
+
316
+ it('LEAD_API_016: Should reject duplicate email', () => {
317
+ // First create a lead
318
+ const leadData = leadAPI.generateRandomData()
319
+
320
+ leadAPI.create(leadData).then((createResponse: any) => {
321
+ expect(createResponse.status).to.eq(201)
322
+ createdLeads.push(createResponse.body.data)
323
+
324
+ // Try to create another lead with the same email
325
+ const duplicateData = leadAPI.generateRandomData({
326
+ email: leadData.email // Same email
327
+ })
328
+
329
+ leadAPI.create(duplicateData).then((response: any) => {
330
+ // Should fail due to UNIQUE constraint
331
+ expect(response.status).to.be.oneOf([400, 409, 500])
332
+ expect(response.body).to.have.property('success', false)
333
+
334
+ cy.log('Duplicate email rejected')
335
+ })
336
+ })
337
+ })
338
+
339
+ it('LEAD_API_017: Should reject creation without x-team-id', () => {
340
+ const noTeamAPI = new LeadAPIController(BASE_URL, SUPERADMIN_API_KEY, null)
341
+ const leadData = noTeamAPI.generateRandomData()
342
+
343
+ noTeamAPI.create(leadData).then((response: any) => {
344
+ expect(response.status).to.eq(400)
345
+ expect(response.body).to.have.property('success', false)
346
+ expect(response.body).to.have.property('code', 'TEAM_CONTEXT_REQUIRED')
347
+
348
+ cy.log('Creation without x-team-id rejected with TEAM_CONTEXT_REQUIRED')
349
+ })
350
+ })
351
+ })
352
+
353
+ // ============================================
354
+ // GET /api/v1/leads/{id} - Get Lead by ID
355
+ // ============================================
356
+ describe('GET /api/v1/leads/{id} - Get Lead by ID', () => {
357
+ it('LEAD_API_020: Should get lead by valid ID', () => {
358
+ // First create a lead
359
+ const leadData = leadAPI.generateRandomData()
360
+
361
+ leadAPI.create(leadData).then((createResponse: any) => {
362
+ expect(createResponse.status).to.eq(201)
363
+ createdLeads.push(createResponse.body.data)
364
+
365
+ const leadId = createResponse.body.data.id
366
+
367
+ // Get the lead by ID
368
+ leadAPI.getById(leadId).then((response: any) => {
369
+ leadAPI.validateSuccessResponse(response, 200)
370
+
371
+ const lead = response.body.data
372
+ leadAPI.validateObject(lead)
373
+ expect(lead.id).to.eq(leadId)
374
+ expect(lead.companyName).to.eq(leadData.companyName)
375
+ expect(lead.email).to.eq(leadData.email)
376
+
377
+ cy.log(`Retrieved lead: ${lead.companyName}`)
378
+ })
379
+ })
380
+ })
381
+
382
+ it('LEAD_API_021: Should return 404 for non-existent lead', () => {
383
+ const fakeId = 'non-existent-lead-id-12345'
384
+
385
+ leadAPI.getById(fakeId).then((response: any) => {
386
+ expect(response.status).to.eq(404)
387
+ expect(response.body).to.have.property('success', false)
388
+
389
+ cy.log('Non-existent lead returns 404')
390
+ })
391
+ })
392
+
393
+ // Note: No LEAD_API_022 test for "access other user's record" because
394
+ // leads entity has shared: true - all team members can see all leads
395
+ })
396
+
397
+ // ============================================
398
+ // PATCH /api/v1/leads/{id} - Update Lead
399
+ // ============================================
400
+ describe('PATCH /api/v1/leads/{id} - Update Lead', () => {
401
+ it('LEAD_API_030: Should update lead with valid data', () => {
402
+ // First create a lead
403
+ leadAPI.createTestRecord().then((testLead: any) => {
404
+ createdLeads.push(testLead)
405
+
406
+ const updateData = {
407
+ companyName: 'Updated Company Name',
408
+ contactName: 'Updated Contact Name',
409
+ phone: '+1-555-9999'
410
+ }
411
+
412
+ leadAPI.update(testLead.id, updateData).then((response: any) => {
413
+ leadAPI.validateSuccessResponse(response, 200)
414
+
415
+ const lead = response.body.data
416
+ expect(lead.companyName).to.eq(updateData.companyName)
417
+ expect(lead.contactName).to.eq(updateData.contactName)
418
+ expect(lead.phone).to.eq(updateData.phone)
419
+ // Original email should be preserved
420
+ expect(lead.email).to.eq(testLead.email)
421
+
422
+ cy.log(`Updated lead: ${lead.companyName}`)
423
+ })
424
+ })
425
+ })
426
+
427
+ it('LEAD_API_031: Should update lead status', () => {
428
+ leadAPI.createTestRecord().then((testLead: any) => {
429
+ createdLeads.push(testLead)
430
+
431
+ const newStatus = 'qualified'
432
+
433
+ leadAPI.update(testLead.id, { status: newStatus }).then((response: any) => {
434
+ leadAPI.validateSuccessResponse(response, 200)
435
+ expect(response.body.data.status).to.eq(newStatus)
436
+
437
+ cy.log(`Updated status to: ${newStatus}`)
438
+ })
439
+ })
440
+ })
441
+
442
+ it('LEAD_API_032: Should update lead score', () => {
443
+ leadAPI.createTestRecord().then((testLead: any) => {
444
+ createdLeads.push(testLead)
445
+
446
+ const newScore = 95
447
+
448
+ leadAPI.update(testLead.id, { score: newScore }).then((response: any) => {
449
+ leadAPI.validateSuccessResponse(response, 200)
450
+ expect(Number(response.body.data.score)).to.eq(newScore)
451
+
452
+ cy.log(`Updated score to: ${newScore}`)
453
+ })
454
+ })
455
+ })
456
+
457
+ it('LEAD_API_033: Should update lead source', () => {
458
+ leadAPI.createTestRecord().then((testLead: any) => {
459
+ createdLeads.push(testLead)
460
+
461
+ // Valid source value from entity config
462
+ const newSource = 'referral'
463
+
464
+ leadAPI.update(testLead.id, { source: newSource }).then((response: any) => {
465
+ leadAPI.validateSuccessResponse(response, 200)
466
+ expect(response.body.data.source).to.eq(newSource)
467
+
468
+ cy.log(`Updated source to: ${newSource}`)
469
+ })
470
+ })
471
+ })
472
+
473
+ it('LEAD_API_034: Should reject update to duplicate email', () => {
474
+ // Create two leads
475
+ leadAPI.createTestRecord().then((lead1: any) => {
476
+ createdLeads.push(lead1)
477
+
478
+ leadAPI.createTestRecord().then((lead2: any) => {
479
+ createdLeads.push(lead2)
480
+
481
+ // Try to update lead2's email to lead1's email
482
+ leadAPI.update(lead2.id, { email: lead1.email }).then((response: any) => {
483
+ // Should fail due to UNIQUE constraint
484
+ expect(response.status).to.be.oneOf([400, 409, 500])
485
+ expect(response.body).to.have.property('success', false)
486
+
487
+ cy.log('Update to duplicate email rejected')
488
+ })
489
+ })
490
+ })
491
+ })
492
+
493
+ it('LEAD_API_035: Should return 404 for non-existent lead', () => {
494
+ const fakeId = 'non-existent-lead-id-12345'
495
+
496
+ leadAPI.update(fakeId, { companyName: 'New Name' }).then((response: any) => {
497
+ expect(response.status).to.eq(404)
498
+ expect(response.body).to.have.property('success', false)
499
+
500
+ cy.log('Update non-existent lead returns 404')
501
+ })
502
+ })
503
+
504
+ it('LEAD_API_036: Should reject empty update body', () => {
505
+ leadAPI.createTestRecord().then((testLead: any) => {
506
+ createdLeads.push(testLead)
507
+
508
+ leadAPI.update(testLead.id, {}).then((response: any) => {
509
+ expect(response.status).to.eq(400)
510
+ expect(response.body).to.have.property('success', false)
511
+ // Error code can be NO_FIELDS or HTTP_400 depending on validation layer
512
+ expect(response.body.code).to.be.oneOf(['NO_FIELDS', 'HTTP_400'])
513
+
514
+ cy.log('Empty update body rejected')
515
+ })
516
+ })
517
+ })
518
+ })
519
+
520
+ // ============================================
521
+ // DELETE /api/v1/leads/{id} - Delete Lead
522
+ // ============================================
523
+ describe('DELETE /api/v1/leads/{id} - Delete Lead', () => {
524
+ it('LEAD_API_040: Should delete lead by valid ID', () => {
525
+ // Create a lead to delete
526
+ const leadData = leadAPI.generateRandomData()
527
+
528
+ leadAPI.create(leadData).then((createResponse: any) => {
529
+ expect(createResponse.status).to.eq(201)
530
+ const leadId = createResponse.body.data.id
531
+
532
+ // Delete the lead
533
+ leadAPI.delete(leadId).then((response: any) => {
534
+ leadAPI.validateSuccessResponse(response, 200)
535
+ expect(response.body.data).to.have.property('success', true)
536
+ expect(response.body.data).to.have.property('id', leadId)
537
+
538
+ cy.log(`Deleted lead: ${leadId}`)
539
+ })
540
+ })
541
+ })
542
+
543
+ it('LEAD_API_041: Should return 404 for non-existent lead', () => {
544
+ const fakeId = 'non-existent-lead-id-12345'
545
+
546
+ leadAPI.delete(fakeId).then((response: any) => {
547
+ expect(response.status).to.eq(404)
548
+ expect(response.body).to.have.property('success', false)
549
+
550
+ cy.log('Delete non-existent lead returns 404')
551
+ })
552
+ })
553
+
554
+ it('LEAD_API_042: Should verify deletion persists', () => {
555
+ // Create a lead
556
+ const leadData = leadAPI.generateRandomData()
557
+
558
+ leadAPI.create(leadData).then((createResponse: any) => {
559
+ expect(createResponse.status).to.eq(201)
560
+ const leadId = createResponse.body.data.id
561
+
562
+ // Delete it
563
+ leadAPI.delete(leadId).then((deleteResponse: any) => {
564
+ expect(deleteResponse.status).to.eq(200)
565
+
566
+ // Verify it's gone
567
+ leadAPI.getById(leadId).then((getResponse: any) => {
568
+ expect(getResponse.status).to.eq(404)
569
+ expect(getResponse.body).to.have.property('success', false)
570
+
571
+ cy.log('Deletion verified - lead no longer exists')
572
+ })
573
+ })
574
+ })
575
+ })
576
+ })
577
+
578
+ // ============================================
579
+ // Integration - Complete CRUD Lifecycle
580
+ // ============================================
581
+ describe('Integration - Complete CRUD Lifecycle', () => {
582
+ it('LEAD_API_100: Should complete full lifecycle: Create -> Read -> Update -> Delete', () => {
583
+ // 1. CREATE - Use valid enum values from entity config
584
+ const leadData = leadAPI.generateRandomData({
585
+ phone: '+1-555-0000',
586
+ website: 'https://www.initialcompany.com',
587
+ source: 'web',
588
+ status: 'new',
589
+ score: 50,
590
+ industry: 'Technology',
591
+ companySize: '11-50',
592
+ budget: 25000,
593
+ notes: 'Initial lead notes'
594
+ })
595
+
596
+ leadAPI.create(leadData).then((createResponse: any) => {
597
+ leadAPI.validateSuccessResponse(createResponse, 201)
598
+ const leadId = createResponse.body.data.id
599
+
600
+ cy.log(`1. Created lead: ${leadId}`)
601
+
602
+ // 2. READ
603
+ leadAPI.getById(leadId).then((readResponse: any) => {
604
+ leadAPI.validateSuccessResponse(readResponse, 200)
605
+ expect(readResponse.body.data.companyName).to.eq(leadData.companyName)
606
+ expect(readResponse.body.data.email).to.eq(leadData.email)
607
+
608
+ cy.log(`2. Read lead: ${readResponse.body.data.companyName}`)
609
+
610
+ // 3. UPDATE
611
+ const updateData = {
612
+ companyName: 'Updated Lifecycle Company',
613
+ contactName: 'Updated Contact',
614
+ phone: '+1-555-9999',
615
+ status: 'qualified',
616
+ score: 95,
617
+ notes: 'Updated lead notes - now qualified'
618
+ }
619
+
620
+ leadAPI.update(leadId, updateData).then((updateResponse: any) => {
621
+ leadAPI.validateSuccessResponse(updateResponse, 200)
622
+ expect(updateResponse.body.data.companyName).to.eq(updateData.companyName)
623
+ expect(updateResponse.body.data.status).to.eq(updateData.status)
624
+ expect(Number(updateResponse.body.data.score)).to.eq(updateData.score)
625
+
626
+ cy.log(`3. Updated lead: ${updateResponse.body.data.companyName}`)
627
+
628
+ // 4. DELETE
629
+ leadAPI.delete(leadId).then((deleteResponse: any) => {
630
+ leadAPI.validateSuccessResponse(deleteResponse, 200)
631
+ expect(deleteResponse.body.data).to.have.property('success', true)
632
+
633
+ cy.log(`4. Deleted lead: ${leadId}`)
634
+
635
+ // 5. VERIFY DELETION
636
+ leadAPI.getById(leadId).then((verifyResponse: any) => {
637
+ expect(verifyResponse.status).to.eq(404)
638
+
639
+ cy.log('5. Verified deletion - lead no longer exists')
640
+ cy.log('Full CRUD lifecycle completed successfully!')
641
+ })
642
+ })
643
+ })
644
+ })
645
+ })
646
+ })
647
+ })
648
+ })