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