@nextsparkjs/theme-crm 0.1.0-beta.18 → 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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextsparkjs/theme-crm",
3
- "version": "0.1.0-beta.18",
3
+ "version": "0.1.0-beta.20",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./config/theme.config.ts",
@@ -13,7 +13,7 @@
13
13
  "react": "^19.0.0",
14
14
  "react-dom": "^19.0.0",
15
15
  "zod": "^4.0.0",
16
- "@nextsparkjs/core": "0.1.0-beta.18"
16
+ "@nextsparkjs/core": "0.1.0-beta.23"
17
17
  },
18
18
  "nextspark": {
19
19
  "type": "theme",
@@ -0,0 +1,686 @@
1
+ /**
2
+ * Activities API - CRUD Tests
3
+ *
4
+ * Comprehensive test suite for Activity API endpoints.
5
+ * Tests GET, POST, PATCH, DELETE operations.
6
+ *
7
+ * Entity characteristics:
8
+ * - Required fields: type, subject
9
+ * - Optional fields: description, dueDate, contactId, companyId, opportunityId, completedAt, assignedTo
10
+ * - Types: call, email, meeting, task, demo, follow-up
11
+ * - Special actions: Complete (set completedAt), Reschedule (update dueDate)
12
+ * - Access: shared within team (all team members see all activities)
13
+ * - Team context: required (x-team-id header)
14
+ */
15
+
16
+ /// <reference types="cypress" />
17
+
18
+ import { ActivityAPIController } from '../../../src/controllers/ActivityAPIController'
19
+
20
+ describe('Activities API - CRUD Operations', () => {
21
+ // Test constants
22
+ const SUPERADMIN_API_KEY = 'test_api_key_for_testing_purposes_only_not_a_real_secret_key_abc123'
23
+ const TEAM_ID = 'team-tmt-001'
24
+ const BASE_URL = Cypress.config('baseUrl') || 'http://localhost:5173'
25
+
26
+ // Controller instance
27
+ let activityAPI: InstanceType<typeof ActivityAPIController>
28
+
29
+ // Track created activities for cleanup
30
+ let createdActivities: any[] = []
31
+
32
+ before(() => {
33
+ // Initialize controller with superadmin credentials
34
+ activityAPI = new ActivityAPIController(BASE_URL, SUPERADMIN_API_KEY, TEAM_ID)
35
+ })
36
+
37
+ afterEach(() => {
38
+ // Cleanup created activities after each test
39
+ createdActivities.forEach((activity) => {
40
+ if (activity?.id) {
41
+ activityAPI.delete(activity.id)
42
+ }
43
+ })
44
+ createdActivities = []
45
+ })
46
+
47
+ // ============================================
48
+ // GET /api/v1/activities - List Activities
49
+ // ============================================
50
+ describe('GET /api/v1/activities - List Activities', () => {
51
+ it('ACTV_API_001: Should list activities with valid API key', () => {
52
+ activityAPI.getAll().then((response: any) => {
53
+ activityAPI.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} activities`)
61
+ })
62
+ })
63
+
64
+ it('ACTV_API_002: Should list activities with pagination', () => {
65
+ activityAPI.getAll({ page: 1, limit: 5 }).then((response: any) => {
66
+ activityAPI.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} activities`)
72
+ })
73
+ })
74
+
75
+ it('ACTV_API_003: Should filter activities by type', () => {
76
+ // First create an activity with a specific type
77
+ const testType = 'meeting'
78
+ const activityData = activityAPI.generateRandomData({ type: testType })
79
+
80
+ activityAPI.create(activityData).then((createResponse: any) => {
81
+ expect(createResponse.status).to.eq(201)
82
+ createdActivities.push(createResponse.body.data)
83
+
84
+ // Now filter by that type
85
+ activityAPI.getAll({ type: testType }).then((response: any) => {
86
+ activityAPI.validateSuccessResponse(response, 200)
87
+ expect(response.body.data).to.be.an('array')
88
+
89
+ // All returned activities should have the specified type
90
+ response.body.data.forEach((activity: any) => {
91
+ expect(activity.type).to.eq(testType)
92
+ })
93
+
94
+ cy.log(`Found ${response.body.data.length} activities with type '${testType}'`)
95
+ })
96
+ })
97
+ })
98
+
99
+ it('ACTV_API_004: Should filter activities by completed status', () => {
100
+ // Create an activity first, then mark it as complete via status
101
+ const activityData = {
102
+ type: 'task',
103
+ subject: `Completed Test Activity ${Date.now()}`,
104
+ status: 'scheduled'
105
+ }
106
+
107
+ activityAPI.create(activityData).then((createResponse: any) => {
108
+ expect(createResponse.status).to.eq(201)
109
+ createdActivities.push(createResponse.body.data)
110
+
111
+ // Mark it as complete by updating status
112
+ activityAPI.update(createResponse.body.data.id, { status: 'completed' }).then((completeResponse: any) => {
113
+ expect(completeResponse.status).to.eq(200)
114
+ expect(completeResponse.body.data.status).to.eq('completed')
115
+
116
+ // Filter for completed activities by status
117
+ activityAPI.getAll({ status: 'completed' }).then((response: any) => {
118
+ activityAPI.validateSuccessResponse(response, 200)
119
+ expect(response.body.data).to.be.an('array')
120
+
121
+ // All returned activities should have status 'completed'
122
+ response.body.data.forEach((activity: any) => {
123
+ expect(activity.status).to.eq('completed')
124
+ })
125
+
126
+ cy.log(`Found ${response.body.data.length} completed activities`)
127
+ })
128
+ })
129
+ })
130
+ })
131
+
132
+ it('ACTV_API_005: Should search activities by subject', () => {
133
+ // Create an activity with a unique searchable term
134
+ const uniqueTerm = `SearchActivity${Date.now()}`
135
+ const activityData = activityAPI.generateRandomData({
136
+ subject: `Test ${uniqueTerm} Activity`
137
+ })
138
+
139
+ activityAPI.create(activityData).then((createResponse: any) => {
140
+ expect(createResponse.status).to.eq(201)
141
+ createdActivities.push(createResponse.body.data)
142
+
143
+ // Search for the unique term
144
+ activityAPI.getAll({ search: uniqueTerm }).then((response: any) => {
145
+ activityAPI.validateSuccessResponse(response, 200)
146
+ expect(response.body.data).to.be.an('array')
147
+ expect(response.body.data.length).to.be.greaterThan(0)
148
+
149
+ // Verify the found activity contains our search term
150
+ const foundActivity = response.body.data.find(
151
+ (a: any) => a.id === createResponse.body.data.id
152
+ )
153
+ expect(foundActivity).to.exist
154
+ expect(foundActivity.subject).to.include(uniqueTerm)
155
+
156
+ cy.log(`Search found ${response.body.data.length} activities matching '${uniqueTerm}'`)
157
+ })
158
+ })
159
+ })
160
+
161
+ it('ACTV_API_006: Should return empty array for non-matching search', () => {
162
+ const nonExistentTerm = 'NonExistentActivitySearchTerm123456789'
163
+
164
+ activityAPI.getAll({ search: nonExistentTerm }).then((response: any) => {
165
+ activityAPI.validateSuccessResponse(response, 200)
166
+ expect(response.body.data).to.be.an('array')
167
+ expect(response.body.data.length).to.eq(0)
168
+
169
+ cy.log('Search with non-matching term returns empty array')
170
+ })
171
+ })
172
+
173
+ it('ACTV_API_007: Should reject request without API key', () => {
174
+ const noAuthAPI = new ActivityAPIController(BASE_URL, null, TEAM_ID)
175
+
176
+ noAuthAPI.getAll().then((response: any) => {
177
+ expect(response.status).to.eq(401)
178
+ expect(response.body).to.have.property('success', false)
179
+
180
+ cy.log('Request without API key rejected with 401')
181
+ })
182
+ })
183
+
184
+ it('ACTV_API_008: Should reject request without x-team-id', () => {
185
+ const noTeamAPI = new ActivityAPIController(BASE_URL, SUPERADMIN_API_KEY, null)
186
+
187
+ noTeamAPI.getAll().then((response: any) => {
188
+ expect(response.status).to.eq(400)
189
+ expect(response.body).to.have.property('success', false)
190
+ expect(response.body).to.have.property('code', 'TEAM_CONTEXT_REQUIRED')
191
+
192
+ cy.log('Request without x-team-id rejected with TEAM_CONTEXT_REQUIRED')
193
+ })
194
+ })
195
+ })
196
+
197
+ // ============================================
198
+ // POST /api/v1/activities - Create Activity
199
+ // ============================================
200
+ describe('POST /api/v1/activities - Create Activity', () => {
201
+ it('ACTV_API_010: Should create activity with valid data', () => {
202
+ // Only send non-relation fields to avoid FK constraint issues
203
+ const activityData = {
204
+ type: 'call',
205
+ subject: `Initial Discovery Call ${Date.now()}`,
206
+ description: 'Discuss project requirements and timeline',
207
+ priority: 'medium',
208
+ status: 'scheduled'
209
+ }
210
+
211
+ activityAPI.create(activityData).then((response: any) => {
212
+ activityAPI.validateSuccessResponse(response, 201)
213
+ createdActivities.push(response.body.data)
214
+
215
+ const activity = response.body.data
216
+ activityAPI.validateObject(activity)
217
+
218
+ // Verify provided data
219
+ expect(activity.type).to.eq(activityData.type)
220
+ expect(activity.subject).to.eq(activityData.subject)
221
+ expect(activity.description).to.eq(activityData.description)
222
+
223
+ cy.log(`Created activity: ${activity.subject} (ID: ${activity.id})`)
224
+ })
225
+ })
226
+
227
+ it('ACTV_API_011: Should create activity with minimal data (type, subject only)', () => {
228
+ const minimalData = {
229
+ type: 'task',
230
+ subject: `MinimalActivity${Date.now()}`
231
+ }
232
+
233
+ activityAPI.create(minimalData).then((response: any) => {
234
+ activityAPI.validateSuccessResponse(response, 201)
235
+ createdActivities.push(response.body.data)
236
+
237
+ const activity = response.body.data
238
+ activityAPI.validateObject(activity)
239
+
240
+ // Verify required fields
241
+ expect(activity.type).to.eq(minimalData.type)
242
+ expect(activity.subject).to.eq(minimalData.subject)
243
+
244
+ // Verify optional fields are null or undefined
245
+ const optionalFields = ['description', 'dueDate', 'contactId', 'companyId', 'opportunityId', 'completedAt', 'assignedTo']
246
+ optionalFields.forEach(field => {
247
+ if (activity[field] !== null && activity[field] !== undefined) {
248
+ expect(activity[field]).to.be.a('string')
249
+ }
250
+ })
251
+
252
+ cy.log(`Created activity with minimal data: ${activity.id}`)
253
+ })
254
+ })
255
+
256
+ it('ACTV_API_012: Should create activity with all optional fields (non-relation)', () => {
257
+ const now = new Date()
258
+ const futureDate = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000) // 7 days from now
259
+
260
+ // Only include non-relation optional fields to avoid FK constraint issues
261
+ const activityData = {
262
+ type: 'meeting',
263
+ subject: `Comprehensive Activity ${Date.now()}`,
264
+ description: 'Activity with complete data including all optional fields',
265
+ dueDate: futureDate.toISOString(),
266
+ priority: 'high',
267
+ status: 'scheduled',
268
+ duration: 60,
269
+ location: 'Conference Room A',
270
+ outcome: 'Pending'
271
+ }
272
+
273
+ activityAPI.create(activityData).then((response: any) => {
274
+ activityAPI.validateSuccessResponse(response, 201)
275
+ createdActivities.push(response.body.data)
276
+
277
+ const activity = response.body.data
278
+ expect(activity.type).to.eq(activityData.type)
279
+ expect(activity.subject).to.eq(activityData.subject)
280
+ expect(activity.description).to.eq(activityData.description)
281
+ // dueDate is returned as ISO string
282
+ expect(activity.dueDate).to.include(futureDate.toISOString().split('T')[0])
283
+ expect(activity.priority).to.eq(activityData.priority)
284
+ expect(activity.status).to.eq(activityData.status)
285
+
286
+ cy.log(`Created activity with all optional fields: ${activity.id}`)
287
+ })
288
+ })
289
+
290
+ it('ACTV_API_013: Should reject creation without type', () => {
291
+ const invalidData = {
292
+ subject: 'Activity without type'
293
+ // Missing: type
294
+ }
295
+
296
+ activityAPI.create(invalidData).then((response: any) => {
297
+ activityAPI.validateErrorResponse(response, 400, 'VALIDATION_ERROR')
298
+
299
+ cy.log('Creation without type rejected with VALIDATION_ERROR')
300
+ })
301
+ })
302
+
303
+ it('ACTV_API_014: Should reject creation without subject', () => {
304
+ const invalidData = {
305
+ type: 'call'
306
+ // Missing: subject
307
+ }
308
+
309
+ activityAPI.create(invalidData).then((response: any) => {
310
+ activityAPI.validateErrorResponse(response, 400, 'VALIDATION_ERROR')
311
+
312
+ cy.log('Creation without subject rejected with VALIDATION_ERROR')
313
+ })
314
+ })
315
+
316
+ it('ACTV_API_015: Should reject creation with invalid type', () => {
317
+ const invalidData = {
318
+ type: 'invalid-type-123',
319
+ subject: 'Activity with invalid type'
320
+ }
321
+
322
+ activityAPI.create(invalidData).then((response: any) => {
323
+ // Should fail validation (expecting specific types only)
324
+ activityAPI.validateErrorResponse(response, 400, 'VALIDATION_ERROR')
325
+
326
+ cy.log('Creation with invalid type rejected with VALIDATION_ERROR')
327
+ })
328
+ })
329
+
330
+ it('ACTV_API_016: Should create activity with priority', () => {
331
+ // Test creating activity with priority instead of relation fields
332
+ const activityData = {
333
+ type: 'email',
334
+ subject: `Priority Email Activity ${Date.now()}`,
335
+ priority: 'urgent',
336
+ status: 'scheduled'
337
+ }
338
+
339
+ activityAPI.create(activityData).then((response: any) => {
340
+ activityAPI.validateSuccessResponse(response, 201)
341
+ createdActivities.push(response.body.data)
342
+
343
+ const activity = response.body.data
344
+ expect(activity.priority).to.eq('urgent')
345
+
346
+ cy.log(`Created activity with priority: ${activity.priority}`)
347
+ })
348
+ })
349
+
350
+ it('ACTV_API_017: Should reject creation without x-team-id', () => {
351
+ const noTeamAPI = new ActivityAPIController(BASE_URL, SUPERADMIN_API_KEY, null)
352
+ const activityData = noTeamAPI.generateRandomData()
353
+
354
+ noTeamAPI.create(activityData).then((response: any) => {
355
+ expect(response.status).to.eq(400)
356
+ expect(response.body).to.have.property('success', false)
357
+ expect(response.body).to.have.property('code', 'TEAM_CONTEXT_REQUIRED')
358
+
359
+ cy.log('Creation without x-team-id rejected with TEAM_CONTEXT_REQUIRED')
360
+ })
361
+ })
362
+ })
363
+
364
+ // ============================================
365
+ // GET /api/v1/activities/{id} - Get Activity by ID
366
+ // ============================================
367
+ describe('GET /api/v1/activities/{id} - Get Activity by ID', () => {
368
+ it('ACTV_API_020: Should get activity by valid ID', () => {
369
+ // First create an activity with simple fields only
370
+ const activityData = {
371
+ type: 'call',
372
+ subject: `Get Test Activity ${Date.now()}`,
373
+ description: 'Test activity for get by ID'
374
+ }
375
+
376
+ activityAPI.create(activityData).then((createResponse: any) => {
377
+ expect(createResponse.status).to.eq(201)
378
+ createdActivities.push(createResponse.body.data)
379
+
380
+ const activityId = createResponse.body.data.id
381
+
382
+ // Get the activity by ID
383
+ activityAPI.getById(activityId).then((response: any) => {
384
+ activityAPI.validateSuccessResponse(response, 200)
385
+
386
+ const activity = response.body.data
387
+ activityAPI.validateObject(activity)
388
+ expect(activity.id).to.eq(activityId)
389
+ expect(activity.type).to.eq(activityData.type)
390
+ expect(activity.subject).to.eq(activityData.subject)
391
+
392
+ cy.log(`Retrieved activity: ${activity.subject}`)
393
+ })
394
+ })
395
+ })
396
+
397
+ it('ACTV_API_021: Should return 404 for non-existent activity', () => {
398
+ const fakeId = 'non-existent-activity-id-12345'
399
+
400
+ activityAPI.getById(fakeId).then((response: any) => {
401
+ expect(response.status).to.eq(404)
402
+ expect(response.body).to.have.property('success', false)
403
+
404
+ cy.log('Non-existent activity returns 404')
405
+ })
406
+ })
407
+ })
408
+
409
+ // ============================================
410
+ // PATCH /api/v1/activities/{id} - Update Activity
411
+ // ============================================
412
+ describe('PATCH /api/v1/activities/{id} - Update Activity', () => {
413
+ it('ACTV_API_030: Should update activity with multiple fields', () => {
414
+ // First create an activity with simple fields
415
+ const createData = {
416
+ type: 'call',
417
+ subject: `Update Test Activity ${Date.now()}`,
418
+ description: 'Initial description'
419
+ }
420
+
421
+ activityAPI.create(createData).then((createResponse: any) => {
422
+ expect(createResponse.status).to.eq(201)
423
+ const testActivity = createResponse.body.data
424
+ createdActivities.push(testActivity)
425
+
426
+ const updateData = {
427
+ subject: 'Updated Activity Subject',
428
+ description: 'Updated description with new information',
429
+ type: 'meeting',
430
+ priority: 'high'
431
+ }
432
+
433
+ activityAPI.update(testActivity.id, updateData).then((response: any) => {
434
+ activityAPI.validateSuccessResponse(response, 200)
435
+
436
+ const activity = response.body.data
437
+ expect(activity.subject).to.eq(updateData.subject)
438
+ expect(activity.description).to.eq(updateData.description)
439
+ expect(activity.type).to.eq(updateData.type)
440
+ expect(activity.priority).to.eq(updateData.priority)
441
+
442
+ cy.log(`Updated activity: ${activity.subject}`)
443
+ })
444
+ })
445
+ })
446
+
447
+ it('ACTV_API_031: Should update activity subject', () => {
448
+ activityAPI.createTestRecord().then((testActivity: any) => {
449
+ createdActivities.push(testActivity)
450
+
451
+ const newSubject = 'Completely New Subject Line'
452
+
453
+ activityAPI.update(testActivity.id, { subject: newSubject }).then((response: any) => {
454
+ activityAPI.validateSuccessResponse(response, 200)
455
+ expect(response.body.data.subject).to.eq(newSubject)
456
+
457
+ cy.log(`Updated subject to: ${newSubject}`)
458
+ })
459
+ })
460
+ })
461
+
462
+ it('ACTV_API_032: Should mark activity as complete (set status to completed)', () => {
463
+ // Create activity with simple fields
464
+ const createData = {
465
+ type: 'task',
466
+ subject: `Complete Test Activity ${Date.now()}`,
467
+ status: 'scheduled'
468
+ }
469
+
470
+ activityAPI.create(createData).then((createResponse: any) => {
471
+ expect(createResponse.status).to.eq(201)
472
+ const testActivity = createResponse.body.data
473
+ createdActivities.push(testActivity)
474
+
475
+ // Update status to completed
476
+ activityAPI.update(testActivity.id, { status: 'completed' }).then((response: any) => {
477
+ activityAPI.validateSuccessResponse(response, 200)
478
+
479
+ const activity = response.body.data
480
+ expect(activity.status).to.eq('completed')
481
+
482
+ cy.log(`Marked activity as complete: ${activity.id}`)
483
+ })
484
+ })
485
+ })
486
+
487
+ it('ACTV_API_033: Should reschedule activity (update dueDate)', () => {
488
+ activityAPI.createTestRecord().then((testActivity: any) => {
489
+ createdActivities.push(testActivity)
490
+
491
+ // Set new due date to 14 days from now
492
+ const newDueDate = new Date()
493
+ newDueDate.setDate(newDueDate.getDate() + 14)
494
+ const newDueDateISO = newDueDate.toISOString()
495
+
496
+ // Use the reschedule() helper method
497
+ activityAPI.reschedule(testActivity.id, newDueDateISO).then((response: any) => {
498
+ activityAPI.validateSuccessResponse(response, 200)
499
+
500
+ const activity = response.body.data
501
+ expect(activity.dueDate).to.eq(newDueDateISO)
502
+
503
+ cy.log(`Rescheduled activity to: ${activity.dueDate}`)
504
+ })
505
+ })
506
+ })
507
+
508
+ it('ACTV_API_034: Should update activity type', () => {
509
+ activityAPI.createTestRecord({ type: 'call' }).then((testActivity: any) => {
510
+ createdActivities.push(testActivity)
511
+
512
+ const newType = 'meeting'
513
+
514
+ activityAPI.update(testActivity.id, { type: newType }).then((response: any) => {
515
+ activityAPI.validateSuccessResponse(response, 200)
516
+ expect(response.body.data.type).to.eq(newType)
517
+
518
+ cy.log(`Updated type from 'call' to '${newType}'`)
519
+ })
520
+ })
521
+ })
522
+
523
+ it('ACTV_API_035: Should return 404 for non-existent activity', () => {
524
+ const fakeId = 'non-existent-activity-id-12345'
525
+
526
+ activityAPI.update(fakeId, { subject: 'New Subject' }).then((response: any) => {
527
+ expect(response.status).to.eq(404)
528
+ expect(response.body).to.have.property('success', false)
529
+
530
+ cy.log('Update non-existent activity returns 404')
531
+ })
532
+ })
533
+
534
+ it('ACTV_API_036: Should reject empty update body', () => {
535
+ activityAPI.createTestRecord().then((testActivity: any) => {
536
+ createdActivities.push(testActivity)
537
+
538
+ activityAPI.update(testActivity.id, {}).then((response: any) => {
539
+ expect(response.status).to.eq(400)
540
+ expect(response.body).to.have.property('success', false)
541
+ // Error code can be NO_FIELDS or HTTP_400 depending on validation layer
542
+ expect(response.body.code).to.be.oneOf(['NO_FIELDS', 'HTTP_400'])
543
+
544
+ cy.log('Empty update body rejected')
545
+ })
546
+ })
547
+ })
548
+ })
549
+
550
+ // ============================================
551
+ // DELETE /api/v1/activities/{id} - Delete Activity
552
+ // ============================================
553
+ describe('DELETE /api/v1/activities/{id} - Delete Activity', () => {
554
+ it('ACTV_API_040: Should delete activity by valid ID', () => {
555
+ // Create an activity to delete
556
+ const activityData = activityAPI.generateRandomData()
557
+
558
+ activityAPI.create(activityData).then((createResponse: any) => {
559
+ expect(createResponse.status).to.eq(201)
560
+ const activityId = createResponse.body.data.id
561
+
562
+ // Delete the activity
563
+ activityAPI.delete(activityId).then((response: any) => {
564
+ activityAPI.validateSuccessResponse(response, 200)
565
+ expect(response.body.data).to.have.property('success', true)
566
+ expect(response.body.data).to.have.property('id', activityId)
567
+
568
+ cy.log(`Deleted activity: ${activityId}`)
569
+ })
570
+ })
571
+ })
572
+
573
+ it('ACTV_API_041: Should return 404 for non-existent activity', () => {
574
+ const fakeId = 'non-existent-activity-id-12345'
575
+
576
+ activityAPI.delete(fakeId).then((response: any) => {
577
+ expect(response.status).to.eq(404)
578
+ expect(response.body).to.have.property('success', false)
579
+
580
+ cy.log('Delete non-existent activity returns 404')
581
+ })
582
+ })
583
+
584
+ it('ACTV_API_042: Should verify deletion persists', () => {
585
+ // Create an activity
586
+ const activityData = activityAPI.generateRandomData()
587
+
588
+ activityAPI.create(activityData).then((createResponse: any) => {
589
+ expect(createResponse.status).to.eq(201)
590
+ const activityId = createResponse.body.data.id
591
+
592
+ // Delete it
593
+ activityAPI.delete(activityId).then((deleteResponse: any) => {
594
+ expect(deleteResponse.status).to.eq(200)
595
+
596
+ // Verify it's gone
597
+ activityAPI.getById(activityId).then((getResponse: any) => {
598
+ expect(getResponse.status).to.eq(404)
599
+ expect(getResponse.body).to.have.property('success', false)
600
+
601
+ cy.log('Deletion verified - activity no longer exists')
602
+ })
603
+ })
604
+ })
605
+ })
606
+ })
607
+
608
+ // ============================================
609
+ // Integration - Complete CRUD Lifecycle with Complete Action
610
+ // ============================================
611
+ describe('Integration - Complete CRUD Lifecycle', () => {
612
+ it('ACTV_API_100: Should complete full lifecycle: Create -> Read -> Update -> Complete -> Delete', () => {
613
+ // 1. CREATE - Use simple fields only (no FK relations)
614
+ const futureDate = new Date()
615
+ futureDate.setDate(futureDate.getDate() + 7)
616
+
617
+ const activityData = {
618
+ type: 'call',
619
+ subject: `Lifecycle Test Activity ${Date.now()}`,
620
+ description: 'Testing complete CRUD lifecycle',
621
+ dueDate: futureDate.toISOString(),
622
+ priority: 'medium',
623
+ status: 'scheduled'
624
+ }
625
+
626
+ activityAPI.create(activityData).then((createResponse: any) => {
627
+ activityAPI.validateSuccessResponse(createResponse, 201)
628
+ const activityId = createResponse.body.data.id
629
+
630
+ cy.log(`1. Created activity: ${activityId}`)
631
+
632
+ // 2. READ
633
+ activityAPI.getById(activityId).then((readResponse: any) => {
634
+ activityAPI.validateSuccessResponse(readResponse, 200)
635
+ expect(readResponse.body.data.type).to.eq(activityData.type)
636
+ expect(readResponse.body.data.subject).to.eq(activityData.subject)
637
+
638
+ cy.log(`2. Read activity: ${readResponse.body.data.subject}`)
639
+
640
+ // 3. UPDATE
641
+ const newDueDate = new Date()
642
+ newDueDate.setDate(newDueDate.getDate() + 14)
643
+
644
+ const updateData = {
645
+ subject: 'Updated Lifecycle Activity',
646
+ description: 'Updated description for lifecycle test',
647
+ type: 'meeting',
648
+ dueDate: newDueDate.toISOString()
649
+ }
650
+
651
+ activityAPI.update(activityId, updateData).then((updateResponse: any) => {
652
+ activityAPI.validateSuccessResponse(updateResponse, 200)
653
+ expect(updateResponse.body.data.subject).to.eq(updateData.subject)
654
+ expect(updateResponse.body.data.type).to.eq(updateData.type)
655
+
656
+ cy.log(`3. Updated activity: ${updateResponse.body.data.subject}`)
657
+
658
+ // 4. COMPLETE (mark as done via status)
659
+ activityAPI.update(activityId, { status: 'completed' }).then((completeResponse: any) => {
660
+ activityAPI.validateSuccessResponse(completeResponse, 200)
661
+ expect(completeResponse.body.data.status).to.eq('completed')
662
+
663
+ cy.log(`4. Completed activity: ${completeResponse.body.data.status}`)
664
+
665
+ // 5. DELETE
666
+ activityAPI.delete(activityId).then((deleteResponse: any) => {
667
+ activityAPI.validateSuccessResponse(deleteResponse, 200)
668
+ expect(deleteResponse.body.data).to.have.property('success', true)
669
+
670
+ cy.log(`5. Deleted activity: ${activityId}`)
671
+
672
+ // 6. VERIFY DELETION
673
+ activityAPI.getById(activityId).then((verifyResponse: any) => {
674
+ expect(verifyResponse.status).to.eq(404)
675
+
676
+ cy.log('6. Verified deletion - activity no longer exists')
677
+ cy.log('Full CRUD lifecycle with complete action completed successfully!')
678
+ })
679
+ })
680
+ })
681
+ })
682
+ })
683
+ })
684
+ })
685
+ })
686
+ })