33 *
44 * This file orchestrates all API test suites for the CMA JavaScript SDK.
55 *
6- * The test suite:
6+ * The test suite is FULLY SELF-CONTAINED and dynamically creates :
77 * 1. Logs in using EMAIL/PASSWORD to get authtoken
8- * 2. Uses existing test stack from API_KEY
9- * 3. Runs all API tests against the stack
10- * 4. Cleans up all created resources (keeps stack empty for next run)
11- * 5. Logs out
8+ * 2. Creates a NEW test stack (no pre-existing stack required)
9+ * 3. Creates a Management Token for the stack
10+ * 4. Creates a Personalize Project linked to the stack
11+ * 5. Runs all API tests against the stack
12+ * 6. Cleans up all created resources within the stack
13+ * 7. Conditionally deletes stack and personalize project (based on env flag)
14+ * 8. Logs out
1215 *
1316 * Environment Variables Required:
1417 * - EMAIL: User email for login
1518 * - PASSWORD: User password for login
1619 * - HOST: API host URL (e.g., api.contentstack.io, eu-api.contentstack.com)
17- * - API_KEY: Existing test stack API key
18- * - ORGANIZATION: Organization UID (for Teams tests)
20+ * - ORGANIZATION: Organization UID (for stack creation and personalize)
1921 *
2022 * Optional:
21- * - PERSONALIZE_PROJECT_UID: For Variants/Personalize tests
23+ * - PERSONALIZE_HOST: Personalize API host (default: personalize-api.contentstack.com)
24+ * - DELETE_DYNAMIC_RESOURCES: Toggle for deleting stack/personalize (default: true)
25+ * Set to 'false' to preserve resources for debugging
2226 * - MEMBER_EMAIL: For team member operations
2327 * - CLIENT_ID: OAuth client ID
2428 * - APP_ID: OAuth app ID
2529 * - REDIRECT_URI: OAuth redirect URI
2630 *
31+ * NO LONGER REQUIRED (dynamically created):
32+ * - API_KEY: Generated when test stack is created
33+ * - MANAGEMENT_TOKEN: Generated for the test stack
34+ * - PERSONALIZE_PROJECT_UID: Generated when personalize project is created
35+ *
2736 * Usage:
2837 * npm run test:sanity
2938 *
3039 * Or run individual test files:
3140 * npm run test -- --grep "Content Type API Tests"
41+ *
42+ * To preserve resources for debugging:
43+ * DELETE_DYNAMIC_RESOURCES=false npm run test:sanity
3244 */
3345
3446import dotenv from 'dotenv'
@@ -76,8 +88,12 @@ before(async function () {
7688 console . error ( ' EMAIL=your-email@example.com' )
7789 console . error ( ' PASSWORD=your-password' )
7890 console . error ( ' HOST=api.contentstack.io' )
79- console . error ( ' API_KEY=your-stack-api-key' )
8091 console . error ( ' ORGANIZATION=your-org-uid' )
92+ console . error ( '\nOptional settings:' )
93+ console . error ( ' PERSONALIZE_HOST=personalize-api.contentstack.com' )
94+ console . error ( ' DELETE_DYNAMIC_RESOURCES=true (set to false to preserve for debugging)' )
95+ console . error ( '\nNote: API_KEY, MANAGEMENT_TOKEN, and PERSONALIZE_PROJECT_UID' )
96+ console . error ( 'are now dynamically created and no longer required in .env' )
8197 throw error
8298 }
8399} )
@@ -88,6 +104,9 @@ before(async function () {
88104
89105// Clear request log and assertion tracker before each test
90106beforeEach ( function ( ) {
107+ // Clear SDK plugin request capture
108+ testSetup . clearCapturedRequests ( )
109+
91110 try {
92111 requestLogger . clearRequestLog ( )
93112 } catch ( e ) {
@@ -136,12 +155,14 @@ afterEach(function() {
136155 }
137156 }
138157
139- // For passed tests, try to get the last request from the request logger
140- let lastRequest = null
141- try {
142- lastRequest = requestLogger . getLastRequest ( )
143- } catch ( e ) {
144- // Request logger might not be active
158+ // Get the last request from SDK plugin capture or fallback to request logger
159+ let lastRequest = testSetup . getLastCapturedRequest ( )
160+ if ( ! lastRequest ) {
161+ try {
162+ lastRequest = requestLogger . getLastRequest ( )
163+ } catch ( e ) {
164+ // Request logger might not be active
165+ }
145166 }
146167
147168 // Add context to Mochawesome report
@@ -156,14 +177,26 @@ afterEach(function() {
156177 value : 'PASSED'
157178 } )
158179
159- // Add assertion details for passed tests (if any tracked via trackedExpect )
180+ // Add assertion details for passed tests (trackedExpect or API result )
160181 if ( trackedAssertions . length > 0 ) {
161182 addContext ( this , {
162183 title : '📊 Assertions Verified (Expected vs Actual)' ,
163184 value : trackedAssertions . map ( a =>
164185 `✓ ${ a . description } \n Expected: ${ a . expected } \n Actual: ${ a . actual } `
165186 ) . join ( '\n\n' )
166187 } )
188+ } else if ( lastRequest ) {
189+ // Fallback: show API result for tests that use expect() not trackedExpect
190+ addContext ( this , {
191+ title : '📊 Result (Expected vs Actual)' ,
192+ value : `Expected: Successful API response\nActual: ${ lastRequest . status || 'OK' } - ${ lastRequest . method } ${ lastRequest . url } `
193+ } )
194+ } else {
195+ // Final fallback: test passed but no request/assertion captured
196+ addContext ( this , {
197+ title : '📊 Result (Expected vs Actual)' ,
198+ value : 'Expected: Success\nActual: Test passed (no SDK request captured for this test)'
199+ } )
167200 }
168201
169202 // For passed tests, add the last request curl if available
@@ -204,7 +237,35 @@ afterEach(function() {
204237 value : 'FAILED'
205238 } )
206239
207- // Add assertion details for failed tests
240+ // Add Expected vs Actual for failed tests
241+ if ( error ) {
242+ if ( error . expected !== undefined || error . actual !== undefined ) {
243+ // Chai assertion error
244+ addContext ( this , {
245+ title : '❌ Expected vs Actual' ,
246+ value : `Expected: ${ JSON . stringify ( error . expected ) } \nActual: ${ JSON . stringify ( error . actual ) } `
247+ } )
248+ } else if ( error . status || error . errorMessage || apiInfo ) {
249+ // API/SDK error (e.g. 422 from API)
250+ const status = error . status ?? apiInfo ?. status ?? error . response ?. status
251+ const msg = error . errorMessage ?? apiInfo ?. errorMessage ?? error . message ?? 'Error'
252+ const errDetails = error . errors || apiInfo ?. errors || { }
253+ const detailsStr = Object . keys ( errDetails ) . length ? `\nDetails: ${ JSON . stringify ( errDetails ) } ` : ''
254+ addContext ( this , {
255+ title : '❌ Expected vs Actual' ,
256+ value : `Expected: Success\nActual: ${ status } - ${ msg } ${ detailsStr } `
257+ } )
258+ } else {
259+ // Fallback: any other error (e.g. thrown Error, assertion in test code)
260+ const msg = error . message || String ( error )
261+ addContext ( this , {
262+ title : '❌ Expected vs Actual' ,
263+ value : `Expected: Success\nActual: ${ msg } `
264+ } )
265+ }
266+ }
267+
268+ // Add assertion details for failed tests (from trackedExpect)
208269 if ( trackedAssertions . length > 0 ) {
209270 const passedAssertions = trackedAssertions . filter ( a => a . passed )
210271 const failedAssertion = trackedAssertions . find ( a => ! a . passed )
@@ -225,37 +286,43 @@ afterEach(function() {
225286 } )
226287 }
227288 }
289+
290+ // Add cURL from captured request (for ALL failed tests - from SDK plugin)
291+ if ( lastRequest && lastRequest . curl ) {
292+ addContext ( this , {
293+ title : '📋 cURL Command (copy-paste ready)' ,
294+ value : lastRequest . curl
295+ } )
296+ addContext ( this , {
297+ title : '📡 API Request' ,
298+ value : `${ lastRequest . method } ${ lastRequest . url } [${ lastRequest . status || 'N/A' } ]`
299+ } )
300+ if ( lastRequest . sdkMethod && ! lastRequest . sdkMethod . startsWith ( 'Unknown' ) ) {
301+ addContext ( this , {
302+ title : '📦 SDK Method Tested' ,
303+ value : lastRequest . sdkMethod
304+ } )
305+ }
306+ }
228307 }
229308
230- // Add API details if available (for failed tests)
309+ // Add API error details if available (for failed tests with API error in response )
231310 if ( apiInfo ) {
232311 const curl = errorToCurl ( apiInfo )
233312
234- // Try to get SDK method from the last request
235- const failedSdkMethod = lastRequest ?. sdkMethod
236-
237- // Store for final report
238313 testCurls . push ( {
239314 test : testTitle ,
240315 state : testState ,
241- curl : curl ,
242- sdkMethod : failedSdkMethod ,
316+ curl : curl || ( lastRequest ?. curl ) ,
317+ sdkMethod : lastRequest ?. sdkMethod ,
243318 details : {
244319 status : apiInfo . status ,
245320 message : apiInfo . errorMessage || apiInfo . message ,
246321 errors : apiInfo . errors
247322 }
248323 } )
249324
250- // Add SDK Method being tested (for failed tests)
251- if ( failedSdkMethod && ! failedSdkMethod . startsWith ( 'Unknown' ) ) {
252- addContext ( this , {
253- title : '📦 SDK Method Tested' ,
254- value : failedSdkMethod
255- } )
256- }
257-
258- // Add error/response details
325+ // Add error/response details (skip cURL if already added from lastRequest)
259326 addContext ( this , {
260327 title : '❌ API Error Details' ,
261328 value : {
@@ -267,13 +334,14 @@ afterEach(function() {
267334 }
268335 } )
269336
270- // Add cURL command
271- addContext ( this , {
272- title : '📋 cURL Command (copy-paste ready)' ,
273- value : curl
274- } )
337+ // Add cURL from apiInfo only if we didn't already add from lastRequest
338+ if ( ! lastRequest ?. curl && curl ) {
339+ addContext ( this , {
340+ title : '📋 cURL Command (copy-paste ready)' ,
341+ value : curl
342+ } )
343+ }
275344
276- // Add request URL for quick reference
277345 if ( apiInfo . request && apiInfo . request . url ) {
278346 addContext ( this , {
279347 title : '🔗 Request' ,
0 commit comments