@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,592 @@
1
+ /**
2
+ * Campaigns API - CRUD Tests
3
+ *
4
+ * Comprehensive test suite for Campaign API endpoints.
5
+ * Tests GET, POST, PATCH, DELETE operations.
6
+ *
7
+ * Entity characteristics:
8
+ * - Required fields: name, type, status
9
+ * - Optional fields: startDate, endDate, budget, actualCost, expectedRevenue, description
10
+ * - Access: shared within team (all team members see all campaigns)
11
+ * - Team context: required (x-team-id header)
12
+ */
13
+
14
+ /// <reference types="cypress" />
15
+
16
+ import { CampaignAPIController } from '../../../src/controllers'
17
+
18
+ describe('Campaigns 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 campaignAPI: InstanceType<typeof CampaignAPIController>
26
+
27
+ // Track created campaigns for cleanup
28
+ let createdCampaigns: any[] = []
29
+
30
+ before(() => {
31
+ // Initialize controller with superadmin credentials
32
+ campaignAPI = new CampaignAPIController(BASE_URL, SUPERADMIN_API_KEY, TEAM_ID)
33
+ })
34
+
35
+ afterEach(() => {
36
+ // Cleanup created campaigns after each test
37
+ createdCampaigns.forEach((campaign) => {
38
+ if (campaign?.id) {
39
+ campaignAPI.delete(campaign.id)
40
+ }
41
+ })
42
+ createdCampaigns = []
43
+ // Small delay to allow database connections to be released
44
+ cy.wait(200)
45
+ })
46
+
47
+ // ============================================
48
+ // GET /api/v1/campaigns - List Campaigns
49
+ // ============================================
50
+ describe('GET /api/v1/campaigns - List Campaigns', () => {
51
+ it('CAMP_API_001: Should list campaigns with valid API key', () => {
52
+ campaignAPI.getAll().then((response: any) => {
53
+ campaignAPI.validateSuccessResponse(response, 200)
54
+ expect(response.body.data).to.be.an('array')
55
+ expect(response.body.info).to.have.property('page')
56
+ expect(response.body.info).to.have.property('limit')
57
+ expect(response.body.info).to.have.property('total')
58
+ expect(response.body.info).to.have.property('totalPages')
59
+
60
+ cy.log(`Found ${response.body.data.length} campaigns`)
61
+ })
62
+ })
63
+
64
+ it('CAMP_API_002: Should list campaigns with pagination', () => {
65
+ campaignAPI.getAll({ page: 1, limit: 5 }).then((response: any) => {
66
+ campaignAPI.validateSuccessResponse(response, 200)
67
+ expect(response.body.info.page).to.eq(1)
68
+ expect(response.body.info.limit).to.eq(5)
69
+ expect(response.body.data.length).to.be.at.most(5)
70
+
71
+ cy.log(`Page 1 with limit 5: ${response.body.data.length} campaigns`)
72
+ })
73
+ })
74
+
75
+ it('CAMP_API_003: Should filter campaigns by type', () => {
76
+ // First create a campaign with a specific type
77
+ const testType = 'email'
78
+ const campaignData = campaignAPI.generateRandomData({ type: testType })
79
+
80
+ campaignAPI.create(campaignData).then((createResponse: any) => {
81
+ expect(createResponse.status).to.eq(201)
82
+ createdCampaigns.push(createResponse.body.data)
83
+
84
+ // Now filter by that type
85
+ campaignAPI.getAll({ type: testType }).then((response: any) => {
86
+ campaignAPI.validateSuccessResponse(response, 200)
87
+ expect(response.body.data).to.be.an('array')
88
+
89
+ // All returned campaigns should have the specified type
90
+ response.body.data.forEach((campaign: any) => {
91
+ expect(campaign.type).to.eq(testType)
92
+ })
93
+
94
+ cy.log(`Found ${response.body.data.length} campaigns with type '${testType}'`)
95
+ })
96
+ })
97
+ })
98
+
99
+ it('CAMP_API_004: Should filter campaigns by status', () => {
100
+ // First create a campaign with a specific status
101
+ const testStatus = 'active'
102
+ const campaignData = campaignAPI.generateRandomData({ status: testStatus })
103
+
104
+ campaignAPI.create(campaignData).then((createResponse: any) => {
105
+ expect(createResponse.status).to.eq(201)
106
+ createdCampaigns.push(createResponse.body.data)
107
+
108
+ // Now filter by that status
109
+ campaignAPI.getAll({ status: testStatus }).then((response: any) => {
110
+ campaignAPI.validateSuccessResponse(response, 200)
111
+ expect(response.body.data).to.be.an('array')
112
+
113
+ // All returned campaigns should have the specified status
114
+ response.body.data.forEach((campaign: any) => {
115
+ expect(campaign.status).to.eq(testStatus)
116
+ })
117
+
118
+ cy.log(`Found ${response.body.data.length} campaigns with status '${testStatus}'`)
119
+ })
120
+ })
121
+ })
122
+
123
+ it('CAMP_API_005: Should search campaigns by name/description', () => {
124
+ // Create a campaign with a unique searchable term
125
+ const uniqueTerm = `SearchCampaign${Date.now()}`
126
+ const campaignData = campaignAPI.generateRandomData({
127
+ name: `${uniqueTerm} Campaign`,
128
+ description: `Campaign with searchable term ${uniqueTerm}`
129
+ })
130
+
131
+ campaignAPI.create(campaignData).then((createResponse: any) => {
132
+ expect(createResponse.status).to.eq(201)
133
+ createdCampaigns.push(createResponse.body.data)
134
+
135
+ // Search for the unique term
136
+ campaignAPI.getAll({ search: uniqueTerm }).then((response: any) => {
137
+ campaignAPI.validateSuccessResponse(response, 200)
138
+ expect(response.body.data).to.be.an('array')
139
+ expect(response.body.data.length).to.be.greaterThan(0)
140
+
141
+ // Verify the found campaign contains our search term
142
+ const foundCampaign = response.body.data.find(
143
+ (c: any) => c.id === createResponse.body.data.id
144
+ )
145
+ expect(foundCampaign).to.exist
146
+ expect(foundCampaign.name + foundCampaign.description).to.include(uniqueTerm)
147
+
148
+ cy.log(`Search found ${response.body.data.length} campaigns matching '${uniqueTerm}'`)
149
+ })
150
+ })
151
+ })
152
+
153
+ it('CAMP_API_006: Should return empty array for non-matching search', () => {
154
+ const nonExistentTerm = 'NonExistentCampaignSearchTerm123456789'
155
+
156
+ campaignAPI.getAll({ search: nonExistentTerm }).then((response: any) => {
157
+ campaignAPI.validateSuccessResponse(response, 200)
158
+ expect(response.body.data).to.be.an('array')
159
+ expect(response.body.data.length).to.eq(0)
160
+
161
+ cy.log('Search with non-matching term returns empty array')
162
+ })
163
+ })
164
+
165
+ it('CAMP_API_007: Should reject request without API key', () => {
166
+ const noAuthAPI = new CampaignAPIController(BASE_URL, null, TEAM_ID)
167
+
168
+ noAuthAPI.getAll().then((response: any) => {
169
+ expect(response.status).to.eq(401)
170
+ expect(response.body).to.have.property('success', false)
171
+
172
+ cy.log('Request without API key rejected with 401')
173
+ })
174
+ })
175
+
176
+ it('CAMP_API_008: Should reject request without x-team-id', () => {
177
+ const noTeamAPI = new CampaignAPIController(BASE_URL, SUPERADMIN_API_KEY, null)
178
+
179
+ noTeamAPI.getAll().then((response: any) => {
180
+ expect(response.status).to.eq(400)
181
+ expect(response.body).to.have.property('success', false)
182
+ expect(response.body).to.have.property('code', 'TEAM_CONTEXT_REQUIRED')
183
+
184
+ cy.log('Request without x-team-id rejected with TEAM_CONTEXT_REQUIRED')
185
+ })
186
+ })
187
+ })
188
+
189
+ // ============================================
190
+ // POST /api/v1/campaigns - Create Campaign
191
+ // ============================================
192
+ describe('POST /api/v1/campaigns - Create Campaign', () => {
193
+ it('CAMP_API_010: Should create campaign with valid data', () => {
194
+ const campaignData = campaignAPI.generateRandomData({
195
+ name: 'Q4 Launch Campaign',
196
+ type: 'email',
197
+ status: 'planned',
198
+ budget: 50000,
199
+ expectedRevenue: 250000,
200
+ description: 'Major product launch campaign'
201
+ })
202
+
203
+ campaignAPI.create(campaignData).then((response: any) => {
204
+ campaignAPI.validateSuccessResponse(response, 201)
205
+ createdCampaigns.push(response.body.data)
206
+
207
+ const campaign = response.body.data
208
+ campaignAPI.validateObject(campaign)
209
+
210
+ // Verify provided data
211
+ expect(campaign.name).to.eq(campaignData.name)
212
+ expect(campaign.type).to.eq(campaignData.type)
213
+ expect(campaign.status).to.eq(campaignData.status)
214
+ expect(campaign.budget).to.satisfy((val: any) =>
215
+ typeof val === 'number' ? val === campaignData.budget : parseFloat(val) === campaignData.budget
216
+ )
217
+ expect(campaign.description).to.eq(campaignData.description)
218
+
219
+ cy.log(`Created campaign: ${campaign.name} (ID: ${campaign.id})`)
220
+ })
221
+ })
222
+
223
+ it('CAMP_API_011: Should create campaign with minimal data (name and required dates)', () => {
224
+ // Note: campaigns require name, startDate, and endDate as required fields
225
+ const minimalData = {
226
+ name: `Minimal Campaign ${Date.now()}`,
227
+ startDate: '2025-01-01',
228
+ endDate: '2025-01-31'
229
+ }
230
+
231
+ campaignAPI.create(minimalData).then((response: any) => {
232
+ campaignAPI.validateSuccessResponse(response, 201)
233
+ createdCampaigns.push(response.body.data)
234
+
235
+ const campaign = response.body.data
236
+ campaignAPI.validateObject(campaign)
237
+
238
+ // Verify required fields
239
+ expect(campaign.name).to.eq(minimalData.name)
240
+ // Dates are returned as ISO strings
241
+ expect(campaign.startDate).to.include('2025-01-01')
242
+ expect(campaign.endDate).to.include('2025-01-31')
243
+
244
+ cy.log(`Created campaign with minimal data: ${campaign.id}`)
245
+ })
246
+ })
247
+
248
+ it('CAMP_API_012: Should create campaign with all optional fields', () => {
249
+ const campaignData = campaignAPI.generateRandomData({
250
+ name: `Complete Campaign ${Date.now()}`,
251
+ type: 'webinar',
252
+ status: 'active',
253
+ startDate: '2025-01-15',
254
+ endDate: '2025-03-31',
255
+ budget: 75000,
256
+ actualCost: 50000,
257
+ expectedRevenue: 500000,
258
+ description: 'Campaign with all fields populated'
259
+ })
260
+
261
+ campaignAPI.create(campaignData).then((response: any) => {
262
+ campaignAPI.validateSuccessResponse(response, 201)
263
+ createdCampaigns.push(response.body.data)
264
+
265
+ const campaign = response.body.data
266
+
267
+ // Verify all fields
268
+ expect(campaign.name).to.eq(campaignData.name)
269
+ expect(campaign.type).to.eq(campaignData.type)
270
+ expect(campaign.status).to.eq(campaignData.status)
271
+ // Dates are returned as ISO strings, use include() for partial match
272
+ expect(campaign.startDate).to.include('2025-01-15')
273
+ expect(campaign.endDate).to.include('2025-03-31')
274
+ expect(campaign.description).to.eq(campaignData.description)
275
+
276
+ cy.log(`Created campaign with all fields: ${campaign.id}`)
277
+ })
278
+ })
279
+
280
+ it('CAMP_API_013: Should reject creation without name', () => {
281
+ const invalidData = {
282
+ type: 'email',
283
+ status: 'planned'
284
+ // Missing: name
285
+ }
286
+
287
+ campaignAPI.create(invalidData).then((response: any) => {
288
+ campaignAPI.validateErrorResponse(response, 400, 'VALIDATION_ERROR')
289
+
290
+ cy.log('Creation without name rejected with VALIDATION_ERROR')
291
+ })
292
+ })
293
+
294
+ it('CAMP_API_014: Should reject creation without type', () => {
295
+ const invalidData = {
296
+ name: 'No Type Campaign',
297
+ status: 'planned'
298
+ // Missing: type
299
+ }
300
+
301
+ campaignAPI.create(invalidData).then((response: any) => {
302
+ campaignAPI.validateErrorResponse(response, 400, 'VALIDATION_ERROR')
303
+
304
+ cy.log('Creation without type rejected with VALIDATION_ERROR')
305
+ })
306
+ })
307
+
308
+ it('CAMP_API_015: Should reject creation without status', () => {
309
+ const invalidData = {
310
+ name: 'No Status Campaign',
311
+ type: 'email'
312
+ // Missing: status
313
+ }
314
+
315
+ campaignAPI.create(invalidData).then((response: any) => {
316
+ campaignAPI.validateErrorResponse(response, 400, 'VALIDATION_ERROR')
317
+
318
+ cy.log('Creation without status rejected with VALIDATION_ERROR')
319
+ })
320
+ })
321
+ })
322
+
323
+ // ============================================
324
+ // GET /api/v1/campaigns/{id} - Get Campaign by ID
325
+ // ============================================
326
+ describe('GET /api/v1/campaigns/{id} - Get Campaign by ID', () => {
327
+ it('CAMP_API_020: Should get campaign by valid ID', () => {
328
+ // First create a campaign
329
+ const campaignData = campaignAPI.generateRandomData()
330
+
331
+ campaignAPI.create(campaignData).then((createResponse: any) => {
332
+ expect(createResponse.status).to.eq(201)
333
+ createdCampaigns.push(createResponse.body.data)
334
+
335
+ const campaignId = createResponse.body.data.id
336
+
337
+ // Get the campaign by ID
338
+ campaignAPI.getById(campaignId).then((response: any) => {
339
+ campaignAPI.validateSuccessResponse(response, 200)
340
+
341
+ const campaign = response.body.data
342
+ campaignAPI.validateObject(campaign)
343
+ expect(campaign.id).to.eq(campaignId)
344
+ expect(campaign.name).to.eq(campaignData.name)
345
+ expect(campaign.type).to.eq(campaignData.type)
346
+
347
+ cy.log(`Retrieved campaign: ${campaign.name}`)
348
+ })
349
+ })
350
+ })
351
+
352
+ it('CAMP_API_021: Should return 404 for non-existent campaign', () => {
353
+ const fakeId = 'non-existent-campaign-id-12345'
354
+
355
+ campaignAPI.getById(fakeId).then((response: any) => {
356
+ expect(response.status).to.eq(404)
357
+ expect(response.body).to.have.property('success', false)
358
+
359
+ cy.log('Non-existent campaign returns 404')
360
+ })
361
+ })
362
+ })
363
+
364
+ // ============================================
365
+ // PATCH /api/v1/campaigns/{id} - Update Campaign
366
+ // ============================================
367
+ describe('PATCH /api/v1/campaigns/{id} - Update Campaign', () => {
368
+ it('CAMP_API_030: Should update campaign with multiple fields', () => {
369
+ // First create a campaign
370
+ campaignAPI.createTestRecord().then((testCampaign: any) => {
371
+ createdCampaigns.push(testCampaign)
372
+
373
+ const updateData = {
374
+ name: 'Updated Campaign Name',
375
+ type: 'event',
376
+ status: 'active',
377
+ budget: 100000,
378
+ description: 'Updated campaign description'
379
+ }
380
+
381
+ campaignAPI.update(testCampaign.id, updateData).then((response: any) => {
382
+ campaignAPI.validateSuccessResponse(response, 200)
383
+
384
+ const campaign = response.body.data
385
+ expect(campaign.name).to.eq(updateData.name)
386
+ expect(campaign.type).to.eq(updateData.type)
387
+ expect(campaign.status).to.eq(updateData.status)
388
+ expect(campaign.description).to.eq(updateData.description)
389
+
390
+ cy.log(`Updated campaign: ${campaign.name}`)
391
+ })
392
+ })
393
+ })
394
+
395
+ it('CAMP_API_031: Should update campaign status', () => {
396
+ campaignAPI.createTestRecord().then((testCampaign: any) => {
397
+ createdCampaigns.push(testCampaign)
398
+
399
+ const newStatus = 'completed'
400
+
401
+ campaignAPI.update(testCampaign.id, { status: newStatus }).then((response: any) => {
402
+ campaignAPI.validateSuccessResponse(response, 200)
403
+ expect(response.body.data.status).to.eq(newStatus)
404
+
405
+ cy.log(`Updated status to: ${newStatus}`)
406
+ })
407
+ })
408
+ })
409
+
410
+ it('CAMP_API_032: Should update campaign budget and revenue', () => {
411
+ campaignAPI.createTestRecord().then((testCampaign: any) => {
412
+ createdCampaigns.push(testCampaign)
413
+
414
+ const updateData = {
415
+ budget: 150000,
416
+ actualCost: 125000,
417
+ expectedRevenue: 750000
418
+ }
419
+
420
+ campaignAPI.update(testCampaign.id, updateData).then((response: any) => {
421
+ campaignAPI.validateSuccessResponse(response, 200)
422
+ const campaign = response.body.data
423
+
424
+ // Handle numeric fields that might be returned as strings or numbers
425
+ expect(campaign.budget).to.satisfy((val: any) =>
426
+ typeof val === 'number' ? val === updateData.budget : parseFloat(val) === updateData.budget
427
+ )
428
+
429
+ cy.log(`Updated budget and revenue`)
430
+ })
431
+ })
432
+ })
433
+
434
+ it('CAMP_API_033: Should update campaign dates', () => {
435
+ campaignAPI.createTestRecord().then((testCampaign: any) => {
436
+ createdCampaigns.push(testCampaign)
437
+
438
+ const updateData = {
439
+ startDate: '2025-02-01',
440
+ endDate: '2025-04-30'
441
+ }
442
+
443
+ campaignAPI.update(testCampaign.id, updateData).then((response: any) => {
444
+ campaignAPI.validateSuccessResponse(response, 200)
445
+ // Dates are returned as ISO strings, use include() for partial match
446
+ expect(response.body.data.startDate).to.include('2025-02-01')
447
+ expect(response.body.data.endDate).to.include('2025-04-30')
448
+
449
+ cy.log(`Updated campaign dates`)
450
+ })
451
+ })
452
+ })
453
+
454
+ it('CAMP_API_034: Should return 404 for non-existent campaign', () => {
455
+ const fakeId = 'non-existent-campaign-id-12345'
456
+
457
+ campaignAPI.update(fakeId, { name: 'New Name' }).then((response: any) => {
458
+ expect(response.status).to.eq(404)
459
+ expect(response.body).to.have.property('success', false)
460
+
461
+ cy.log('Update non-existent campaign returns 404')
462
+ })
463
+ })
464
+ })
465
+
466
+ // ============================================
467
+ // DELETE /api/v1/campaigns/{id} - Delete Campaign
468
+ // ============================================
469
+ describe('DELETE /api/v1/campaigns/{id} - Delete Campaign', () => {
470
+ it('CAMP_API_040: Should delete campaign by valid ID', () => {
471
+ // Create a campaign to delete
472
+ const campaignData = campaignAPI.generateRandomData()
473
+
474
+ campaignAPI.create(campaignData).then((createResponse: any) => {
475
+ expect(createResponse.status).to.eq(201)
476
+ const campaignId = createResponse.body.data.id
477
+
478
+ // Delete the campaign
479
+ campaignAPI.delete(campaignId).then((response: any) => {
480
+ campaignAPI.validateSuccessResponse(response, 200)
481
+ expect(response.body.data).to.have.property('success', true)
482
+ expect(response.body.data).to.have.property('id', campaignId)
483
+
484
+ cy.log(`Deleted campaign: ${campaignId}`)
485
+ })
486
+ })
487
+ })
488
+
489
+ it('CAMP_API_041: Should return 404 for non-existent campaign', () => {
490
+ const fakeId = 'non-existent-campaign-id-12345'
491
+
492
+ campaignAPI.delete(fakeId).then((response: any) => {
493
+ expect(response.status).to.eq(404)
494
+ expect(response.body).to.have.property('success', false)
495
+
496
+ cy.log('Delete non-existent campaign returns 404')
497
+ })
498
+ })
499
+
500
+ it('CAMP_API_042: Should verify deletion persists', () => {
501
+ // Create a campaign
502
+ const campaignData = campaignAPI.generateRandomData()
503
+
504
+ campaignAPI.create(campaignData).then((createResponse: any) => {
505
+ expect(createResponse.status).to.eq(201)
506
+ const campaignId = createResponse.body.data.id
507
+
508
+ // Delete it
509
+ campaignAPI.delete(campaignId).then((deleteResponse: any) => {
510
+ expect(deleteResponse.status).to.eq(200)
511
+
512
+ // Verify it's gone
513
+ campaignAPI.getById(campaignId).then((getResponse: any) => {
514
+ expect(getResponse.status).to.eq(404)
515
+ expect(getResponse.body).to.have.property('success', false)
516
+
517
+ cy.log('Deletion verified - campaign no longer exists')
518
+ })
519
+ })
520
+ })
521
+ })
522
+ })
523
+
524
+ // ============================================
525
+ // Integration - Complete CRUD Lifecycle
526
+ // ============================================
527
+ describe('Integration - Complete CRUD Lifecycle', () => {
528
+ it('CAMP_API_100: Should complete full lifecycle: Create -> Read -> Update -> Delete', () => {
529
+ // 1. CREATE
530
+ const campaignData = campaignAPI.generateRandomData({
531
+ name: 'Lifecycle Test Campaign',
532
+ type: 'email',
533
+ status: 'planned',
534
+ budget: 50000,
535
+ expectedRevenue: 250000,
536
+ description: 'Initial campaign for lifecycle testing'
537
+ })
538
+
539
+ campaignAPI.create(campaignData).then((createResponse: any) => {
540
+ campaignAPI.validateSuccessResponse(createResponse, 201)
541
+ const campaignId = createResponse.body.data.id
542
+
543
+ cy.log(`1. Created campaign: ${campaignId}`)
544
+
545
+ // 2. READ
546
+ campaignAPI.getById(campaignId).then((readResponse: any) => {
547
+ campaignAPI.validateSuccessResponse(readResponse, 200)
548
+ expect(readResponse.body.data.name).to.eq(campaignData.name)
549
+ expect(readResponse.body.data.type).to.eq(campaignData.type)
550
+
551
+ cy.log(`2. Read campaign: ${readResponse.body.data.name}`)
552
+
553
+ // 3. UPDATE
554
+ const updateData = {
555
+ name: 'Updated Lifecycle Campaign',
556
+ type: 'webinar',
557
+ status: 'active',
558
+ budget: 100000,
559
+ actualCost: 75000,
560
+ expectedRevenue: 500000,
561
+ description: 'Updated campaign description'
562
+ }
563
+
564
+ campaignAPI.update(campaignId, updateData).then((updateResponse: any) => {
565
+ campaignAPI.validateSuccessResponse(updateResponse, 200)
566
+ expect(updateResponse.body.data.name).to.eq(updateData.name)
567
+ expect(updateResponse.body.data.type).to.eq(updateData.type)
568
+ expect(updateResponse.body.data.status).to.eq(updateData.status)
569
+
570
+ cy.log(`3. Updated campaign: ${updateResponse.body.data.name}`)
571
+
572
+ // 4. DELETE
573
+ campaignAPI.delete(campaignId).then((deleteResponse: any) => {
574
+ campaignAPI.validateSuccessResponse(deleteResponse, 200)
575
+ expect(deleteResponse.body.data).to.have.property('success', true)
576
+
577
+ cy.log(`4. Deleted campaign: ${campaignId}`)
578
+
579
+ // 5. VERIFY DELETION
580
+ campaignAPI.getById(campaignId).then((verifyResponse: any) => {
581
+ expect(verifyResponse.status).to.eq(404)
582
+
583
+ cy.log('5. Verified deletion - campaign no longer exists')
584
+ cy.log('Full CRUD lifecycle completed successfully!')
585
+ })
586
+ })
587
+ })
588
+ })
589
+ })
590
+ })
591
+ })
592
+ })