@@ -37,6 +37,9 @@ class IntegrationTest_ActiveEntity_Chapters_MultipleFacet {
3737 private static String entityName2 = "author" ;
3838 private static String srvpath = "AdminService" ;
3939 private static String [] facet = {"attachments" , "references" , "footnotes" };
40+ // Distinct author-name marker for this suite so its cleanup only touches its own books and
41+ // doesn't collide with sibling ActiveEntity matrix jobs.
42+ private static final String AUTHOR_NAME = "author-active-chapters-multifacet" ;
4043 private static ApiInterface api ;
4144 private static ApiInterface apiNoRoles ;
4245
@@ -171,14 +174,117 @@ static void setup() throws IOException {
171174 } else {
172175 throw new IllegalArgumentException ("Invalid tenancy model specified: " + tenancyModel );
173176 }
177+
178+ // Pre-test cleanup: remove any stale test-fixture books (author == "author") from prior runs.
179+ // Failures here are logged but never block the tests so we can still see the real test outcome.
180+ cleanupStaleTestData (client );
181+ }
182+
183+ /**
184+ * Deletes any leftover {@code Books} rows whose {@code author} field equals the test-fixture
185+ * literal {@code "author"} — both active and draft. Deleting a Book cascades to its Chapters, so
186+ * this also clears stale chapter fixtures.
187+ */
188+ private static void cleanupStaleTestData (OkHttpClient client ) {
189+ String tenancyModel = System .getProperty ("tenancyModel" );
190+ String baseUrl =
191+ "multi" .equals (tenancyModel )
192+ ? "https://" + appUrl + "/api/admin/" + bookEntityName
193+ : "https://" + appUrl + "/odata/v4/" + srvpath + "/" + bookEntityName ;
194+ int activeDeleted =
195+ cleanupBookSet (client , baseUrl + "?$filter=author/name eq '" + AUTHOR_NAME + "'" , true );
196+ int draftDeleted =
197+ cleanupBookSet (
198+ client ,
199+ baseUrl
200+ + "?$filter=author/name eq '"
201+ + AUTHOR_NAME
202+ + "' and IsActiveEntity eq false" ,
203+ false );
204+ System .out .println (
205+ "🧹 Pre-test cleanup: deleted "
206+ + activeDeleted
207+ + " active and "
208+ + draftDeleted
209+ + " draft fixture book(s) (author='author')" );
210+ }
211+
212+ /** See sibling test file — same best-effort cleanup helper. */
213+ private static int cleanupBookSet (OkHttpClient client , String filterUrl , boolean active ) {
214+ int deleted = 0 ;
215+ try {
216+ Request listReq =
217+ new Request .Builder ()
218+ .url (filterUrl )
219+ .get ()
220+ .addHeader ("Authorization" , "Bearer " + token )
221+ .build ();
222+ try (Response listRes = client .newCall (listReq ).execute ()) {
223+ if (listRes .code () != 200 ) {
224+ System .out .println (
225+ "⚠️ Cleanup list ("
226+ + (active ? "active" : "draft" )
227+ + ") returned HTTP "
228+ + listRes .code ()
229+ + " — skipping" );
230+ return 0 ;
231+ }
232+ ObjectMapper mapper = new ObjectMapper ();
233+ com .fasterxml .jackson .databind .JsonNode root = mapper .readTree (listRes .body ().string ());
234+ com .fasterxml .jackson .databind .JsonNode values = root .path ("value" );
235+ if (!values .isArray ()) {
236+ return 0 ;
237+ }
238+ for (com .fasterxml .jackson .databind .JsonNode book : values ) {
239+ String id = book .path ("ID" ).asText ("" );
240+ if (id .isEmpty ()) {
241+ continue ;
242+ }
243+ try {
244+ String resp =
245+ active
246+ ? api .deleteEntity (appUrl , bookEntityName , id )
247+ : api .deleteEntityDraft (appUrl , bookEntityName , id );
248+ if ("Entity Deleted" .equals (resp ) || "Deleted" .equals (resp )) {
249+ deleted ++;
250+ } else {
251+ System .out .println (
252+ "⚠️ Cleanup could not delete "
253+ + (active ? "active" : "draft" )
254+ + " book "
255+ + id
256+ + " (response: "
257+ + resp
258+ + ")" );
259+ }
260+ } catch (Exception innerEx ) {
261+ System .out .println (
262+ "⚠️ Cleanup delete failed for "
263+ + (active ? "active" : "draft" )
264+ + " book "
265+ + id
266+ + ": "
267+ + innerEx .getMessage ());
268+ }
269+ }
270+ }
271+ } catch (Exception e ) {
272+ System .out .println (
273+ "⚠️ Cleanup "
274+ + (active ? "active" : "draft" )
275+ + " list-fetch failed (continuing): "
276+ + e .getMessage ());
277+ }
278+ return deleted ;
174279 }
175280
176281 /**
177282 * Creates a Book + nested Chapter and saves the draft so both become active. Returns an {@code
178283 * [bookID, chapterID]} array, or {@code null} if any step fails.
179284 */
180285 private static String [] createAndActivateBookWithChapter () {
181- String bookResponse = api .createEntityDraft (appUrl , bookEntityName , entityName2 , srvpath );
286+ String bookResponse =
287+ api .createEntityDraftWithAuthor (appUrl , bookEntityName , entityName2 , srvpath , AUTHOR_NAME );
182288 if (bookResponse .equals ("Could not create entity" )) {
183289 return null ;
184290 }
@@ -353,7 +459,8 @@ void testCreateAttachmentInActiveCoexistsWithDraftUploadsOnChapterAllFacets() th
353459 + " all facets" );
354460 boolean testStatus = false ;
355461 // Create a fresh book+chapter draft (kept in draft mode so we can upload draft attachments)
356- String bookResponse = api .createEntityDraft (appUrl , bookEntityName , entityName2 , srvpath );
462+ String bookResponse =
463+ api .createEntityDraftWithAuthor (appUrl , bookEntityName , entityName2 , srvpath , AUTHOR_NAME );
357464 if (!bookResponse .equals ("Could not create entity" )) {
358465 bookID3 = bookResponse ;
359466 String chapterResponse =
0 commit comments