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