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

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 (70) hide show
  1. package/package.json +2 -2
  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/tsconfig.json +15 -0
@@ -0,0 +1,545 @@
1
+ /**
2
+ * Pipelines API - CRUD Tests
3
+ *
4
+ * Comprehensive test suite for Pipeline API endpoints.
5
+ * Tests GET, POST, PATCH, DELETE operations.
6
+ *
7
+ * Entity characteristics:
8
+ * - Required fields: name
9
+ * - Optional fields: type, description, rottenDays, isActive, isDefault, stages (array)
10
+ * - Access: Only owner can create/update/delete (but all can read)
11
+ * - Team context: required (x-team-id header)
12
+ * - Special: Stages array with name, color, probability, order
13
+ */
14
+
15
+ /// <reference types="cypress" />
16
+
17
+ import { PipelineAPIController } from '../../../src/controllers'
18
+
19
+ describe('Pipelines 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 pipelineAPI: InstanceType<typeof PipelineAPIController>
27
+
28
+ // Track created pipelines for cleanup
29
+ let createdPipelines: any[] = []
30
+
31
+ before(() => {
32
+ // Initialize controller with superadmin credentials
33
+ pipelineAPI = new PipelineAPIController(BASE_URL, SUPERADMIN_API_KEY, TEAM_ID)
34
+ })
35
+
36
+ afterEach(() => {
37
+ // Cleanup created pipelines after each test
38
+ createdPipelines.forEach((pipeline) => {
39
+ if (pipeline?.id) {
40
+ pipelineAPI.delete(pipeline.id)
41
+ }
42
+ })
43
+ createdPipelines = []
44
+ })
45
+
46
+ // ============================================
47
+ // GET /api/v1/pipelines - List Pipelines
48
+ // ============================================
49
+ describe('GET /api/v1/pipelines - List Pipelines', () => {
50
+ it('PIPE_API_001: Should list pipelines with valid API key', () => {
51
+ pipelineAPI.getAll().then((response: any) => {
52
+ pipelineAPI.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} pipelines`)
60
+ })
61
+ })
62
+
63
+ it('PIPE_API_002: Should list pipelines with pagination', () => {
64
+ pipelineAPI.getAll({ page: 1, limit: 5 }).then((response: any) => {
65
+ pipelineAPI.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} pipelines`)
71
+ })
72
+ })
73
+
74
+ it('PIPE_API_003: Should list pipelines including stages in response', () => {
75
+ // First create a pipeline with stages
76
+ const pipelineData = pipelineAPI.generateRandomData({
77
+ name: `Pipeline with Stages ${Date.now()}`,
78
+ stages: [
79
+ { name: 'Qualification', probability: 10, order: 1 },
80
+ { name: 'Proposal', probability: 50, order: 2 },
81
+ { name: 'Closed Won', probability: 100, order: 3 }
82
+ ]
83
+ })
84
+
85
+ pipelineAPI.create(pipelineData).then((createResponse: any) => {
86
+ expect(createResponse.status).to.eq(201)
87
+ createdPipelines.push(createResponse.body.data)
88
+
89
+ // Get all pipelines
90
+ pipelineAPI.getAll().then((response: any) => {
91
+ pipelineAPI.validateSuccessResponse(response, 200)
92
+ expect(response.body.data).to.be.an('array')
93
+
94
+ // Find our created pipeline
95
+ const foundPipeline = response.body.data.find(
96
+ (p: any) => p.id === createResponse.body.data.id
97
+ )
98
+
99
+ if (foundPipeline) {
100
+ // Verify stages are included in list response
101
+ expect(foundPipeline).to.have.property('stages')
102
+ expect(foundPipeline.stages).to.be.an('array')
103
+ expect(foundPipeline.stages.length).to.eq(3)
104
+
105
+ cy.log(`Pipeline with ${foundPipeline.stages.length} stages found in list`)
106
+ }
107
+ })
108
+ })
109
+ })
110
+
111
+ it('PIPE_API_004: Should reject request without x-team-id', () => {
112
+ const noTeamAPI = new PipelineAPIController(BASE_URL, SUPERADMIN_API_KEY, null)
113
+
114
+ noTeamAPI.getAll().then((response: any) => {
115
+ expect(response.status).to.eq(400)
116
+ expect(response.body).to.have.property('success', false)
117
+ expect(response.body).to.have.property('code', 'TEAM_CONTEXT_REQUIRED')
118
+
119
+ cy.log('Request without x-team-id rejected with TEAM_CONTEXT_REQUIRED')
120
+ })
121
+ })
122
+ })
123
+
124
+ // ============================================
125
+ // POST /api/v1/pipelines - Create Pipeline
126
+ // ============================================
127
+ describe('POST /api/v1/pipelines - Create Pipeline', () => {
128
+ it('PIPE_API_010: Should create pipeline with minimal data (name and stages)', () => {
129
+ const minimalData = {
130
+ name: `Minimal Pipeline ${Date.now()}`,
131
+ stages: [
132
+ { name: 'New', probability: 10, order: 1 },
133
+ { name: 'Won', probability: 100, order: 2 }
134
+ ]
135
+ }
136
+
137
+ pipelineAPI.create(minimalData).then((response: any) => {
138
+ pipelineAPI.validateSuccessResponse(response, 201)
139
+ createdPipelines.push(response.body.data)
140
+
141
+ const pipeline = response.body.data
142
+ pipelineAPI.validateObject(pipeline)
143
+
144
+ // Verify required fields
145
+ expect(pipeline.name).to.eq(minimalData.name)
146
+ expect(pipeline.stages).to.be.an('array')
147
+
148
+ // Verify optional fields have defaults or are null
149
+ expect(pipeline.isActive).to.satisfy((val: any) =>
150
+ val === true || val === null || val === undefined
151
+ )
152
+ expect(pipeline.isDefault).to.satisfy((val: any) =>
153
+ val === false || val === null || val === undefined
154
+ )
155
+
156
+ cy.log(`Created pipeline with minimal data: ${pipeline.id}`)
157
+ })
158
+ })
159
+
160
+ it('PIPE_API_011: Should create pipeline with stages array', () => {
161
+ const pipelineData = {
162
+ name: `Pipeline with Stages ${Date.now()}`,
163
+ stages: [
164
+ { name: 'Discovery', probability: 5, order: 1, color: '#3b82f6' },
165
+ { name: 'Qualification', probability: 10, order: 2, color: '#8b5cf6' },
166
+ { name: 'Needs Analysis', probability: 25, order: 3, color: '#ec4899' },
167
+ { name: 'Proposal', probability: 50, order: 4, color: '#f59e0b' },
168
+ { name: 'Negotiation', probability: 75, order: 5, color: '#10b981' },
169
+ { name: 'Closed Won', probability: 100, order: 6, color: '#22c55e' }
170
+ ]
171
+ }
172
+
173
+ pipelineAPI.create(pipelineData).then((response: any) => {
174
+ pipelineAPI.validateSuccessResponse(response, 201)
175
+ createdPipelines.push(response.body.data)
176
+
177
+ const pipeline = response.body.data
178
+ pipelineAPI.validateObject(pipeline)
179
+
180
+ // Verify name
181
+ expect(pipeline.name).to.eq(pipelineData.name)
182
+
183
+ // Verify stages array
184
+ expect(pipeline.stages).to.be.an('array')
185
+ expect(pipeline.stages.length).to.eq(6)
186
+
187
+ // Verify stage structure
188
+ pipeline.stages.forEach((stage: any, index: number) => {
189
+ expect(stage).to.have.property('name')
190
+ expect(stage).to.have.property('probability')
191
+ expect(stage).to.have.property('order')
192
+ expect(stage.name).to.eq(pipelineData.stages[index].name)
193
+ expect(stage.probability).to.eq(pipelineData.stages[index].probability)
194
+ expect(stage.order).to.eq(pipelineData.stages[index].order)
195
+ })
196
+
197
+ cy.log(`Created pipeline with ${pipeline.stages.length} stages`)
198
+ })
199
+ })
200
+
201
+ it('PIPE_API_012: Should reject creation without name', () => {
202
+ const invalidData = {
203
+ type: 'sales',
204
+ description: 'Pipeline without name'
205
+ // Missing: name
206
+ }
207
+
208
+ pipelineAPI.create(invalidData).then((response: any) => {
209
+ pipelineAPI.validateErrorResponse(response, 400, 'VALIDATION_ERROR')
210
+
211
+ cy.log('Creation without name rejected with VALIDATION_ERROR')
212
+ })
213
+ })
214
+
215
+ it('PIPE_API_013: Should create pipeline with all optional fields', () => {
216
+ const pipelineData = pipelineAPI.generateRandomData({
217
+ name: `Complete Pipeline ${Date.now()}`,
218
+ type: 'sales',
219
+ description: 'Complete pipeline with all fields',
220
+ dealRottenDays: 45,
221
+ isActive: true,
222
+ isDefault: false,
223
+ stages: [
224
+ { name: 'Lead', probability: 5, order: 1 },
225
+ { name: 'Qualified', probability: 20, order: 2 },
226
+ { name: 'Demo', probability: 40, order: 3 },
227
+ { name: 'Proposal', probability: 60, order: 4 },
228
+ { name: 'Closed Won', probability: 100, order: 5 }
229
+ ]
230
+ })
231
+
232
+ pipelineAPI.create(pipelineData).then((response: any) => {
233
+ pipelineAPI.validateSuccessResponse(response, 201)
234
+ createdPipelines.push(response.body.data)
235
+
236
+ const pipeline = response.body.data
237
+
238
+ // Verify all fields
239
+ expect(pipeline.name).to.eq(pipelineData.name)
240
+ expect(pipeline.type).to.eq(pipelineData.type)
241
+ expect(pipeline.description).to.eq(pipelineData.description)
242
+
243
+ // dealRottenDays may be returned as number or string
244
+ expect(String(pipeline.dealRottenDays)).to.eq(String(pipelineData.dealRottenDays))
245
+
246
+ expect(pipeline.isActive).to.eq(pipelineData.isActive)
247
+ expect(pipeline.isDefault).to.eq(pipelineData.isDefault)
248
+ expect(pipeline.stages).to.be.an('array')
249
+ expect(pipeline.stages.length).to.eq(5)
250
+
251
+ cy.log(`Created pipeline with all fields: ${pipeline.id}`)
252
+ })
253
+ })
254
+
255
+ it('PIPE_API_014: Should reject creation without x-team-id', () => {
256
+ const noTeamAPI = new PipelineAPIController(BASE_URL, SUPERADMIN_API_KEY, null)
257
+ const pipelineData = noTeamAPI.generateRandomData()
258
+
259
+ noTeamAPI.create(pipelineData).then((response: any) => {
260
+ expect(response.status).to.eq(400)
261
+ expect(response.body).to.have.property('success', false)
262
+ expect(response.body).to.have.property('code', 'TEAM_CONTEXT_REQUIRED')
263
+
264
+ cy.log('Creation without x-team-id rejected with TEAM_CONTEXT_REQUIRED')
265
+ })
266
+ })
267
+ })
268
+
269
+ // ============================================
270
+ // GET /api/v1/pipelines/{id} - Get Pipeline by ID
271
+ // ============================================
272
+ describe('GET /api/v1/pipelines/{id} - Get Pipeline by ID', () => {
273
+ it('PIPE_API_020: Should get pipeline by valid ID', () => {
274
+ // First create a pipeline
275
+ const pipelineData = pipelineAPI.generateRandomData()
276
+
277
+ pipelineAPI.create(pipelineData).then((createResponse: any) => {
278
+ expect(createResponse.status).to.eq(201)
279
+ createdPipelines.push(createResponse.body.data)
280
+
281
+ const pipelineId = createResponse.body.data.id
282
+
283
+ // Get the pipeline by ID
284
+ pipelineAPI.getById(pipelineId).then((response: any) => {
285
+ pipelineAPI.validateSuccessResponse(response, 200)
286
+
287
+ const pipeline = response.body.data
288
+ pipelineAPI.validateObject(pipeline)
289
+ expect(pipeline.id).to.eq(pipelineId)
290
+ expect(pipeline.name).to.eq(pipelineData.name)
291
+
292
+ // Verify stages are included
293
+ if (pipelineData.stages) {
294
+ expect(pipeline.stages).to.be.an('array')
295
+ expect(pipeline.stages.length).to.eq(pipelineData.stages.length)
296
+ }
297
+
298
+ cy.log(`Retrieved pipeline: ${pipeline.name}`)
299
+ })
300
+ })
301
+ })
302
+
303
+ it('PIPE_API_021: Should return 404 for non-existent pipeline', () => {
304
+ const fakeId = 'non-existent-pipeline-id-12345'
305
+
306
+ pipelineAPI.getById(fakeId).then((response: any) => {
307
+ expect(response.status).to.eq(404)
308
+ expect(response.body).to.have.property('success', false)
309
+
310
+ cy.log('Non-existent pipeline returns 404')
311
+ })
312
+ })
313
+ })
314
+
315
+ // ============================================
316
+ // PATCH /api/v1/pipelines/{id} - Update Pipeline
317
+ // ============================================
318
+ describe('PATCH /api/v1/pipelines/{id} - Update Pipeline', () => {
319
+ it('PIPE_API_030: Should update pipeline name', () => {
320
+ pipelineAPI.createTestRecord().then((testPipeline: any) => {
321
+ createdPipelines.push(testPipeline)
322
+
323
+ const newName = 'Updated Pipeline Name'
324
+
325
+ pipelineAPI.update(testPipeline.id, { name: newName }).then((response: any) => {
326
+ pipelineAPI.validateSuccessResponse(response, 200)
327
+ expect(response.body.data.name).to.eq(newName)
328
+
329
+ cy.log(`Updated name to: ${newName}`)
330
+ })
331
+ })
332
+ })
333
+
334
+ it('PIPE_API_031: Should update pipeline stages array (add stage)', () => {
335
+ // Create pipeline with initial stages
336
+ const initialStages = [
337
+ { name: 'Stage 1', probability: 25, order: 1 },
338
+ { name: 'Stage 2', probability: 50, order: 2 },
339
+ { name: 'Stage 3', probability: 100, order: 3 }
340
+ ]
341
+
342
+ pipelineAPI.createTestRecord({ stages: initialStages }).then((testPipeline: any) => {
343
+ createdPipelines.push(testPipeline)
344
+
345
+ // Add a new stage
346
+ const updatedStages = [
347
+ ...initialStages,
348
+ { name: 'Stage 4', probability: 75, order: 4 }
349
+ ]
350
+
351
+ pipelineAPI.update(testPipeline.id, { stages: updatedStages }).then((response: any) => {
352
+ pipelineAPI.validateSuccessResponse(response, 200)
353
+
354
+ const pipeline = response.body.data
355
+ expect(pipeline.stages).to.be.an('array')
356
+ expect(pipeline.stages.length).to.eq(4)
357
+
358
+ // Verify new stage was added
359
+ const newStage = pipeline.stages.find((s: any) => s.name === 'Stage 4')
360
+ expect(newStage).to.exist
361
+ expect(newStage.probability).to.eq(75)
362
+ expect(newStage.order).to.eq(4)
363
+
364
+ cy.log(`Updated stages array - now has ${pipeline.stages.length} stages`)
365
+ })
366
+ })
367
+ })
368
+
369
+ it('PIPE_API_032: Should update pipeline isDefault (set as default)', () => {
370
+ pipelineAPI.createTestRecord({ isDefault: false }).then((testPipeline: any) => {
371
+ createdPipelines.push(testPipeline)
372
+
373
+ // Set as default
374
+ pipelineAPI.update(testPipeline.id, { isDefault: true }).then((response: any) => {
375
+ pipelineAPI.validateSuccessResponse(response, 200)
376
+ expect(response.body.data.isDefault).to.eq(true)
377
+
378
+ cy.log(`Set pipeline as default`)
379
+ })
380
+ })
381
+ })
382
+
383
+ it('PIPE_API_033: Should update pipeline dealRottenDays', () => {
384
+ pipelineAPI.createTestRecord({ dealRottenDays: 30 }).then((testPipeline: any) => {
385
+ createdPipelines.push(testPipeline)
386
+
387
+ const newDealRottenDays = 60
388
+
389
+ pipelineAPI.update(testPipeline.id, { dealRottenDays: newDealRottenDays }).then((response: any) => {
390
+ pipelineAPI.validateSuccessResponse(response, 200)
391
+
392
+ // dealRottenDays may be returned as number or string
393
+ expect(String(response.body.data.dealRottenDays)).to.eq(String(newDealRottenDays))
394
+
395
+ cy.log(`Updated dealRottenDays to: ${newDealRottenDays}`)
396
+ })
397
+ })
398
+ })
399
+
400
+ it('PIPE_API_034: Should return 404 for non-existent pipeline', () => {
401
+ const fakeId = 'non-existent-pipeline-id-12345'
402
+
403
+ pipelineAPI.update(fakeId, { name: 'New Name' }).then((response: any) => {
404
+ expect(response.status).to.eq(404)
405
+ expect(response.body).to.have.property('success', false)
406
+
407
+ cy.log('Update non-existent pipeline returns 404')
408
+ })
409
+ })
410
+
411
+ it('PIPE_API_035: Should reject empty update body', () => {
412
+ pipelineAPI.createTestRecord().then((testPipeline: any) => {
413
+ createdPipelines.push(testPipeline)
414
+
415
+ pipelineAPI.update(testPipeline.id, {}).then((response: any) => {
416
+ expect(response.status).to.eq(400)
417
+ expect(response.body).to.have.property('success', false)
418
+ // Error code can be NO_FIELDS or HTTP_400 depending on validation layer
419
+ expect(response.body.code).to.be.oneOf(['NO_FIELDS', 'HTTP_400'])
420
+
421
+ cy.log('Empty update body rejected')
422
+ })
423
+ })
424
+ })
425
+ })
426
+
427
+ // ============================================
428
+ // DELETE /api/v1/pipelines/{id} - Delete Pipeline
429
+ // ============================================
430
+ describe('DELETE /api/v1/pipelines/{id} - Delete Pipeline', () => {
431
+ it('PIPE_API_040: Should delete pipeline by valid ID', () => {
432
+ // Create a pipeline to delete
433
+ const pipelineData = pipelineAPI.generateRandomData()
434
+
435
+ pipelineAPI.create(pipelineData).then((createResponse: any) => {
436
+ expect(createResponse.status).to.eq(201)
437
+ const pipelineId = createResponse.body.data.id
438
+
439
+ // Delete the pipeline
440
+ pipelineAPI.delete(pipelineId).then((response: any) => {
441
+ pipelineAPI.validateSuccessResponse(response, 200)
442
+ expect(response.body.data).to.have.property('success', true)
443
+ expect(response.body.data).to.have.property('id', pipelineId)
444
+
445
+ cy.log(`Deleted pipeline: ${pipelineId}`)
446
+ })
447
+ })
448
+ })
449
+
450
+ it('PIPE_API_041: Should return 404 for non-existent pipeline', () => {
451
+ const fakeId = 'non-existent-pipeline-id-12345'
452
+
453
+ pipelineAPI.delete(fakeId).then((response: any) => {
454
+ expect(response.status).to.eq(404)
455
+ expect(response.body).to.have.property('success', false)
456
+
457
+ cy.log('Delete non-existent pipeline returns 404')
458
+ })
459
+ })
460
+ })
461
+
462
+ // ============================================
463
+ // Integration - Complete CRUD Lifecycle
464
+ // ============================================
465
+ describe('Integration - Complete CRUD Lifecycle', () => {
466
+ it('PIPE_API_100: Should complete full lifecycle: Create -> Read -> Update -> Delete', () => {
467
+ // 1. CREATE
468
+ const pipelineData = pipelineAPI.generateRandomData({
469
+ name: 'Lifecycle Test Pipeline',
470
+ type: 'sales',
471
+ description: 'Initial pipeline for lifecycle testing',
472
+ dealRottenDays: 30,
473
+ isActive: true,
474
+ isDefault: false,
475
+ stages: [
476
+ { name: 'Lead', probability: 5, order: 1, color: '#3b82f6' },
477
+ { name: 'Qualified', probability: 20, order: 2, color: '#8b5cf6' },
478
+ { name: 'Demo', probability: 40, order: 3, color: '#ec4899' },
479
+ { name: 'Proposal', probability: 60, order: 4, color: '#f59e0b' },
480
+ { name: 'Closed Won', probability: 100, order: 5, color: '#22c55e' }
481
+ ]
482
+ })
483
+
484
+ pipelineAPI.create(pipelineData).then((createResponse: any) => {
485
+ pipelineAPI.validateSuccessResponse(createResponse, 201)
486
+ const pipelineId = createResponse.body.data.id
487
+
488
+ cy.log(`1. Created pipeline: ${pipelineId}`)
489
+
490
+ // 2. READ
491
+ pipelineAPI.getById(pipelineId).then((readResponse: any) => {
492
+ pipelineAPI.validateSuccessResponse(readResponse, 200)
493
+ expect(readResponse.body.data.name).to.eq(pipelineData.name)
494
+ expect(readResponse.body.data.type).to.eq(pipelineData.type)
495
+ expect(readResponse.body.data.stages.length).to.eq(5)
496
+
497
+ cy.log(`2. Read pipeline: ${readResponse.body.data.name}`)
498
+
499
+ // 3. UPDATE
500
+ const updateData = {
501
+ name: 'Updated Lifecycle Pipeline',
502
+ description: 'Updated pipeline description',
503
+ dealRottenDays: 60,
504
+ isDefault: true,
505
+ stages: [
506
+ { name: 'Discovery', probability: 10, order: 1, color: '#3b82f6' },
507
+ { name: 'Qualification', probability: 25, order: 2, color: '#8b5cf6' },
508
+ { name: 'Needs Analysis', probability: 40, order: 3, color: '#ec4899' },
509
+ { name: 'Proposal', probability: 60, order: 4, color: '#f59e0b' },
510
+ { name: 'Negotiation', probability: 80, order: 5, color: '#10b981' },
511
+ { name: 'Closed Won', probability: 100, order: 6, color: '#22c55e' }
512
+ ]
513
+ }
514
+
515
+ pipelineAPI.update(pipelineId, updateData).then((updateResponse: any) => {
516
+ pipelineAPI.validateSuccessResponse(updateResponse, 200)
517
+ expect(updateResponse.body.data.name).to.eq(updateData.name)
518
+ expect(updateResponse.body.data.description).to.eq(updateData.description)
519
+ expect(String(updateResponse.body.data.dealRottenDays)).to.eq(String(updateData.dealRottenDays))
520
+ expect(updateResponse.body.data.isDefault).to.eq(updateData.isDefault)
521
+ expect(updateResponse.body.data.stages.length).to.eq(6)
522
+
523
+ cy.log(`3. Updated pipeline: ${updateResponse.body.data.name} (${updateResponse.body.data.stages.length} stages)`)
524
+
525
+ // 4. DELETE
526
+ pipelineAPI.delete(pipelineId).then((deleteResponse: any) => {
527
+ pipelineAPI.validateSuccessResponse(deleteResponse, 200)
528
+ expect(deleteResponse.body.data).to.have.property('success', true)
529
+
530
+ cy.log(`4. Deleted pipeline: ${pipelineId}`)
531
+
532
+ // 5. VERIFY DELETION
533
+ pipelineAPI.getById(pipelineId).then((verifyResponse: any) => {
534
+ expect(verifyResponse.status).to.eq(404)
535
+
536
+ cy.log('5. Verified deletion - pipeline no longer exists')
537
+ cy.log('Full CRUD lifecycle completed successfully!')
538
+ })
539
+ })
540
+ })
541
+ })
542
+ })
543
+ })
544
+ })
545
+ })