@@ -65,6 +65,19 @@ public final class TestStackContext {
6565 private static String managementToken ;
6666 private static String managementTokenUid ;
6767
68+ // Optional: second stack inside the Asset-Management org (AM_ORG_UID) for
69+ // DAM / Contentstack Assets scan tests - mirrors the JS suite's Part 2.
70+ private static final String AM_ORG_UID = getEnv ("AM_ORG_UID" , "amOrgUid" , null );
71+ private static String amStackApiKey ;
72+ private static String amStackName ;
73+ private static boolean amStackCreated = false ;
74+
75+ // Optional: Personalize project linked to the test stack (JS suite parity)
76+ private static final String PERSONALIZE_HOST =
77+ getEnv ("PERSONALIZE_HOST" , "personalizeHost" , "personalize-api.contentstack.com" ).trim ();
78+ private static String personalizeProjectUid ;
79+ private static boolean personalizeCreated = false ;
80+
6881 private static boolean setupAttempted = false ;
6982 private static boolean stackCreated = false ;
7083 private static boolean tornDown = false ;
@@ -131,10 +144,16 @@ public static synchronized void ensureSetup() {
131144 new FixtureSeeder (HOST , stackApiKey , authtoken ).seedAll ();
132145 }
133146
147+ // Optional extras (both non-fatal; skipped when env not configured)
148+ createAmStack ();
149+ createPersonalizeProject ();
150+
134151 System .out .println ("============================================================" );
135152 System .out .println ("[TestStackContext] Dynamic setup complete" );
136153 System .out .println ("[TestStackContext] Stack: " + stackName + " (" + stackApiKey + ")" );
137154 System .out .println ("[TestStackContext] Management token: " + (managementToken != null ? "created" : "NOT created (tests fall back to authtoken)" ));
155+ System .out .println ("[TestStackContext] AM stack: " + (amStackCreated ? amStackName + " (" + amStackApiKey + ")" : "not created (AM_ORG_UID unset or failed)" ));
156+ System .out .println ("[TestStackContext] Personalize project: " + (personalizeCreated ? personalizeProjectUid : "not created" ));
138157 System .out .println ("============================================================" );
139158 } catch (Exception e ) {
140159 System .err .println ("[TestStackContext] Dynamic setup FAILED: " + e .getMessage ());
@@ -159,11 +178,21 @@ public static synchronized void teardown() {
159178 System .out .println ("[TestStackContext] Stack: " + stackName );
160179 System .out .println ("[TestStackContext] API key: " + stackApiKey );
161180 System .out .println ("[TestStackContext] Management token: " + managementToken );
162- System .out .println ("[TestStackContext] Remember to delete it manually when done!" );
181+ if (amStackCreated ) {
182+ System .out .println ("[TestStackContext] AM stack: " + amStackName + " (" + amStackApiKey + ")" );
183+ }
184+ if (personalizeCreated ) {
185+ System .out .println ("[TestStackContext] Personalize project: " + personalizeProjectUid );
186+ }
187+ System .out .println ("[TestStackContext] Remember to delete them manually when done!" );
163188 writeStatusFile ("preserved " + stackName + " " + stackApiKey );
164189 return ;
165190 }
166191
192+ // Linked/secondary resources first, then the main stack
193+ deletePersonalizeProject ();
194+ deleteAmStack ();
195+
167196 try {
168197 Request request = new Request .Builder ()
169198 .url ("https://" + HOST + "/v3/stacks" )
@@ -232,6 +261,21 @@ public static boolean isStackCreated() {
232261 return stackCreated ;
233262 }
234263
264+ /** @return api key of the AM-org test stack, or null when not created */
265+ public static String getAmStackApiKey () {
266+ return amStackApiKey ;
267+ }
268+
269+ /** @return true when the optional AM-org stack exists (AM_ORG_UID configured) */
270+ public static boolean isAmStackCreated () {
271+ return amStackCreated ;
272+ }
273+
274+ /** @return uid of the Personalize project linked to the test stack, or null */
275+ public static String getPersonalizeProjectUid () {
276+ return personalizeProjectUid ;
277+ }
278+
235279 // =============================================================================
236280 // Setup steps
237281 // =============================================================================
@@ -376,6 +420,150 @@ private static boolean createManagementTokenWithScope(String tokenName, String[]
376420 }
377421 }
378422
423+ /**
424+ * Creates a second stack inside the Asset-Management org (AM_ORG_UID) for
425+ * DAM / Contentstack Assets scan tests - the JS suite's "Part 2" pattern.
426+ * Skipped silently when AM_ORG_UID is not configured; failures are non-fatal
427+ * (AM tests skip themselves when the stack is absent).
428+ */
429+ @ SuppressWarnings ("unchecked" )
430+ private static void createAmStack () {
431+ if (AM_ORG_UID == null || AM_ORG_UID .trim ().isEmpty ()) {
432+ return ;
433+ }
434+ amStackName = "SDK_Test_Java_AM_" + shortId ();
435+ System .out .println ("[TestStackContext] Creating AM-org test stack: " + amStackName + " ..." );
436+ try {
437+ JSONObject stack = new JSONObject ();
438+ stack .put ("name" , amStackName );
439+ stack .put ("description" , "Automated Java CMA SDK AM-org test stack" );
440+ stack .put ("master_locale" , "en-us" );
441+ JSONObject body = new JSONObject ();
442+ body .put ("stack" , stack );
443+ Request request = new Request .Builder ()
444+ .url ("https://" + HOST + "/v3/stacks" )
445+ .header ("authtoken" , authtoken )
446+ .header ("organization_uid" , AM_ORG_UID .trim ())
447+ .post (RequestBody .create (body .toJSONString (), JSON_MEDIA ))
448+ .build ();
449+ try (Response response = http .newCall (request ).execute ()) {
450+ String responseBody = response .body () != null ? response .body ().string () : "" ;
451+ if (!response .isSuccessful ()) {
452+ System .err .println ("[TestStackContext] AM stack creation failed (" + response .code () + "): "
453+ + responseBody + " - AM scan tests will be skipped" );
454+ return ;
455+ }
456+ JSONObject json = (JSONObject ) parser .parse (responseBody );
457+ JSONObject stackObj = (JSONObject ) json .get ("stack" );
458+ amStackApiKey = (String ) stackObj .get ("api_key" );
459+ amStackCreated = true ;
460+ }
461+ System .out .println ("[TestStackContext] Created AM stack " + amStackName + " (api_key: " + amStackApiKey + ")" );
462+ Thread .sleep (5000 ); // same provisioning wait as the main stack
463+ } catch (Exception e ) {
464+ System .err .println ("[TestStackContext] AM stack creation error: " + e .getMessage ()
465+ + " - AM scan tests will be skipped" );
466+ }
467+ }
468+
469+ /**
470+ * Creates a Personalize project linked to the test stack (JS suite parity).
471+ * Non-fatal: personalize-dependent tests skip when the project is absent.
472+ */
473+ @ SuppressWarnings ("unchecked" )
474+ private static void createPersonalizeProject () {
475+ if (PERSONALIZE_HOST == null || PERSONALIZE_HOST .isEmpty ()) {
476+ return ;
477+ }
478+ String projectName = "SDK_Test_Java_Proj_" + shortId ();
479+ System .out .println ("[TestStackContext] Creating personalize project: " + projectName + " ..." );
480+ try {
481+ JSONObject body = new JSONObject ();
482+ body .put ("name" , projectName );
483+ body .put ("description" , "Automated Java CMA SDK test project" );
484+ body .put ("connectedStackApiKey" , stackApiKey );
485+ Request request = new Request .Builder ()
486+ .url ("https://" + PERSONALIZE_HOST + "/projects" )
487+ .header ("authtoken" , authtoken )
488+ .header ("organization_uid" , ORGANIZATION )
489+ .post (RequestBody .create (body .toJSONString (), JSON_MEDIA ))
490+ .build ();
491+ try (Response response = http .newCall (request ).execute ()) {
492+ String responseBody = response .body () != null ? response .body ().string () : "" ;
493+ if (!response .isSuccessful ()) {
494+ System .err .println ("[TestStackContext] Personalize project creation failed ("
495+ + response .code () + "): " + truncate (responseBody ) + " - personalize tests will be skipped" );
496+ return ;
497+ }
498+ JSONObject json = (JSONObject ) parser .parse (responseBody );
499+ Object uid = json .get ("uid" ) != null ? json .get ("uid" )
500+ : json .get ("project_uid" ) != null ? json .get ("project_uid" ) : json .get ("_id" );
501+ personalizeProjectUid = uid != null ? uid .toString () : null ;
502+ personalizeCreated = personalizeProjectUid != null ;
503+ }
504+ if (personalizeCreated ) {
505+ System .out .println ("[TestStackContext] Created personalize project: " + personalizeProjectUid );
506+ }
507+ } catch (Exception e ) {
508+ System .err .println ("[TestStackContext] Personalize project creation error: " + e .getMessage ());
509+ }
510+ }
511+
512+ private static void deleteAmStack () {
513+ if (!amStackCreated ) {
514+ return ;
515+ }
516+ try {
517+ Request request = new Request .Builder ()
518+ .url ("https://" + HOST + "/v3/stacks" )
519+ .header ("api_key" , amStackApiKey )
520+ .header ("authtoken" , authtoken )
521+ .delete ()
522+ .build ();
523+ try (Response response = http .newCall (request ).execute ()) {
524+ if (response .isSuccessful ()) {
525+ System .out .println ("[TestStackContext] Deleted AM test stack: " + amStackName );
526+ amStackCreated = false ;
527+ } else {
528+ System .err .println ("[TestStackContext] AM stack deletion failed with " + response .code ()
529+ + " - delete manually: " + amStackName + " (" + amStackApiKey + ")" );
530+ }
531+ }
532+ } catch (Exception e ) {
533+ System .err .println ("[TestStackContext] AM stack deletion error: " + e .getMessage ()
534+ + " - delete manually: " + amStackName + " (" + amStackApiKey + ")" );
535+ }
536+ }
537+
538+ private static void deletePersonalizeProject () {
539+ if (!personalizeCreated ) {
540+ return ;
541+ }
542+ try {
543+ Request request = new Request .Builder ()
544+ .url ("https://" + PERSONALIZE_HOST + "/projects/" + personalizeProjectUid )
545+ .header ("authtoken" , authtoken )
546+ .header ("organization_uid" , ORGANIZATION )
547+ .delete ()
548+ .build ();
549+ try (Response response = http .newCall (request ).execute ()) {
550+ if (response .isSuccessful ()) {
551+ System .out .println ("[TestStackContext] Deleted personalize project: " + personalizeProjectUid );
552+ personalizeCreated = false ;
553+ } else {
554+ System .err .println ("[TestStackContext] Personalize project deletion failed with " + response .code ()
555+ + " - delete manually: " + personalizeProjectUid );
556+ }
557+ }
558+ } catch (Exception e ) {
559+ System .err .println ("[TestStackContext] Personalize project deletion error: " + e .getMessage ());
560+ }
561+ }
562+
563+ private static String truncate (String s ) {
564+ return s != null && s .length () > 160 ? s .substring (0 , 160 ) + "..." : String .valueOf (s );
565+ }
566+
379567 /**
380568 * Freshly created management tokens are propagated asynchronously on some
381569 * environments (observed on dev11: intermittent 412s when the token is used
0 commit comments