@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.
- package/package.json +2 -2
- package/tests/cypress/e2e/api/activities/activities-crud.cy.ts +686 -0
- package/tests/cypress/e2e/api/campaigns/campaigns-crud.cy.ts +592 -0
- package/tests/cypress/e2e/api/companies/companies-crud.cy.ts +682 -0
- package/tests/cypress/e2e/api/contacts/contacts-crud.cy.ts +668 -0
- package/tests/cypress/e2e/api/leads/leads-crud.cy.ts +648 -0
- package/tests/cypress/e2e/api/notes/notes-crud.cy.ts +424 -0
- package/tests/cypress/e2e/api/opportunities/opportunities-crud.cy.ts +865 -0
- package/tests/cypress/e2e/api/pipelines/pipelines-crud.cy.ts +545 -0
- package/tests/cypress/e2e/api/products/products-crud.cy.ts +447 -0
- package/tests/cypress/e2e/ui/activities/activities-admin.cy.ts +268 -0
- package/tests/cypress/e2e/ui/activities/activities-member.cy.ts +257 -0
- package/tests/cypress/e2e/ui/activities/activities-owner.cy.ts +268 -0
- package/tests/cypress/e2e/ui/companies/companies-admin.cy.ts +188 -0
- package/tests/cypress/e2e/ui/companies/companies-member.cy.ts +166 -0
- package/tests/cypress/e2e/ui/companies/companies-owner.cy.ts +189 -0
- package/tests/cypress/e2e/ui/contacts/contacts-admin.cy.ts +252 -0
- package/tests/cypress/e2e/ui/contacts/contacts-member.cy.ts +224 -0
- package/tests/cypress/e2e/ui/contacts/contacts-owner.cy.ts +236 -0
- package/tests/cypress/e2e/ui/leads/leads-admin.cy.ts +286 -0
- package/tests/cypress/e2e/ui/leads/leads-member.cy.ts +193 -0
- package/tests/cypress/e2e/ui/leads/leads-owner.cy.ts +210 -0
- package/tests/cypress/e2e/ui/opportunities/opportunities-admin.cy.ts +197 -0
- package/tests/cypress/e2e/ui/opportunities/opportunities-member.cy.ts +229 -0
- package/tests/cypress/e2e/ui/opportunities/opportunities-owner.cy.ts +196 -0
- package/tests/cypress/e2e/ui/pipelines/pipelines-admin.cy.ts +320 -0
- package/tests/cypress/e2e/ui/pipelines/pipelines-member.cy.ts +262 -0
- package/tests/cypress/e2e/ui/pipelines/pipelines-owner.cy.ts +282 -0
- package/tests/cypress/fixtures/blocks.json +9 -0
- package/tests/cypress/fixtures/entities.json +240 -0
- package/tests/cypress/src/components/CRMDataTable.js +223 -0
- package/tests/cypress/src/components/CRMMobileNav.js +138 -0
- package/tests/cypress/src/components/CRMSidebar.js +145 -0
- package/tests/cypress/src/components/CRMTopBar.js +194 -0
- package/tests/cypress/src/components/DealCard.js +197 -0
- package/tests/cypress/src/components/EntityDetail.ts +290 -0
- package/tests/cypress/src/components/EntityForm.ts +357 -0
- package/tests/cypress/src/components/EntityList.ts +360 -0
- package/tests/cypress/src/components/PipelineKanban.js +204 -0
- package/tests/cypress/src/components/StageColumn.js +196 -0
- package/tests/cypress/src/components/index.js +13 -0
- package/tests/cypress/src/components/index.ts +22 -0
- package/tests/cypress/src/controllers/ActivityAPIController.ts +113 -0
- package/tests/cypress/src/controllers/BaseAPIController.ts +307 -0
- package/tests/cypress/src/controllers/CampaignAPIController.ts +114 -0
- package/tests/cypress/src/controllers/CompanyAPIController.ts +112 -0
- package/tests/cypress/src/controllers/ContactAPIController.ts +104 -0
- package/tests/cypress/src/controllers/LeadAPIController.ts +96 -0
- package/tests/cypress/src/controllers/NoteAPIController.ts +130 -0
- package/tests/cypress/src/controllers/OpportunityAPIController.ts +134 -0
- package/tests/cypress/src/controllers/PipelineAPIController.ts +116 -0
- package/tests/cypress/src/controllers/ProductAPIController.ts +113 -0
- package/tests/cypress/src/controllers/index.ts +35 -0
- package/tests/cypress/src/entities/ActivitiesPOM.ts +130 -0
- package/tests/cypress/src/entities/CompaniesPOM.ts +117 -0
- package/tests/cypress/src/entities/ContactsPOM.ts +117 -0
- package/tests/cypress/src/entities/LeadsPOM.ts +129 -0
- package/tests/cypress/src/entities/OpportunitiesPOM.ts +178 -0
- package/tests/cypress/src/entities/PipelinesPOM.ts +341 -0
- package/tests/cypress/src/entities/index.ts +31 -0
- package/tests/cypress/src/forms/OpportunityForm.js +316 -0
- package/tests/cypress/src/forms/PipelineForm.js +243 -0
- package/tests/cypress/src/forms/index.js +8 -0
- package/tests/cypress/src/index.js +22 -0
- package/tests/cypress/src/index.ts +68 -0
- package/tests/cypress/src/selectors.ts +50 -0
- package/tests/cypress/src/session-helpers.ts +94 -0
- package/tests/cypress/support/e2e.ts +89 -0
- package/tests/cypress.config.ts +165 -0
- 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.
|
|
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.
|
|
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
|
+
})
|