Skip to content

Commit 5faccca

Browse files
committed
multitenant issue fix
1 parent 2864ebb commit 5faccca

5 files changed

Lines changed: 352 additions & 5 deletions

File tree

sdm/src/test/java/integration/com/sap/cds/sdm/Api.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,62 @@ public String createEntityDraft(
141141
return ("Could not create entity");
142142
}
143143

144+
public String createEntityDraftWithAuthor(
145+
String appUrl, String entityName, String entityName2, String srvpath, String authorName) {
146+
return createEntityDraftWithAuthor(appUrl, entityName, entityName2, srvpath, null, authorName);
147+
}
148+
149+
public String createEntityDraftWithAuthor(
150+
String appUrl,
151+
String entityName,
152+
String entityName2,
153+
String srvpath,
154+
String bookID,
155+
String authorName) {
156+
MediaType mediaType = MediaType.parse("application/json");
157+
158+
String jsonBody;
159+
if (bookID != null && !bookID.isEmpty()) {
160+
// Creating a Chapter within a Book — same as the default path
161+
jsonBody =
162+
"{\n \"title\": \"IntegrationTestEntity\",\n \"book_ID\": \"" + bookID + "\"\n}";
163+
} else {
164+
// Creating a Book with a caller-supplied author name so parallel matrix jobs
165+
// can use distinct fixture markers and their cleanups won't clobber each other.
166+
jsonBody =
167+
"{\n \"title\": \"IntegrationTestEntity\",\n \""
168+
+ entityName2
169+
+ "\": {\n \"ID\": \"41cf82fb-94bf-4d62-9e45-fa25f959b5b0\",\n \"name\": \""
170+
+ authorName
171+
+ "\"\n }\n}";
172+
}
173+
174+
RequestBody body = RequestBody.create(mediaType, jsonBody);
175+
Request request =
176+
new Request.Builder()
177+
.url("https://" + appUrl + "/odata/v4/" + serviceName + "/" + entityName)
178+
.method("POST", body)
179+
.addHeader("Content-Type", "application/json")
180+
.addHeader("Authorization", token)
181+
.build();
182+
183+
try (Response response = executeWithRetry(request)) {
184+
if (!response.isSuccessful()) {
185+
if (response.code() == 401) {
186+
System.out.println(
187+
"Create entity failed due to incorrect token. Please check the credentials");
188+
}
189+
System.out.println("Create entity failed. Error : " + response.body().string());
190+
throw new IOException("Could not create entity");
191+
}
192+
Map<String, Object> responseMap = objectMapper.readValue(response.body().string(), Map.class);
193+
return (String) responseMap.get("ID");
194+
} catch (IOException e) {
195+
System.out.println("Could not create entity : " + e);
196+
}
197+
return ("Could not create entity");
198+
}
199+
144200
public String editEntityDraft(String appUrl, String entityName, String srvpath, String entityID) {
145201
MediaType mediaType = MediaType.parse("application/json");
146202
Request request =

sdm/src/test/java/integration/com/sap/cds/sdm/ApiInterface.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,23 @@ public String createEntityDraft(
1313
public String createEntityDraft(
1414
String appUrl, String entityName, String entityName2, String srvpath, String bookID);
1515

16+
/**
17+
* Same as {@link #createEntityDraft(String, String, String, String)} but with a caller-supplied
18+
* author name so parallel matrix jobs can use distinct fixture markers and their cleanups won't
19+
* clobber each other.
20+
*/
21+
public String createEntityDraftWithAuthor(
22+
String appUrl, String entityName, String entityName2, String srvpath, String authorName);
23+
24+
/** Chapter-in-book variant of {@link #createEntityDraftWithAuthor}. */
25+
public String createEntityDraftWithAuthor(
26+
String appUrl,
27+
String entityName,
28+
String entityName2,
29+
String srvpath,
30+
String bookID,
31+
String authorName);
32+
1633
public String editEntityDraft(String appUrl, String entityName, String srvpath, String entityID);
1734

1835
public String saveEntityDraft(String appUrl, String entityName, String srvpath, String entityID);

sdm/src/test/java/integration/com/sap/cds/sdm/ApiMT.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,62 @@ public String createEntityDraft(
140140
return ("Could not create entity");
141141
}
142142

143+
public String createEntityDraftWithAuthor(
144+
String appUrl, String entityName, String entityName2, String srvpath, String authorName) {
145+
return createEntityDraftWithAuthor(appUrl, entityName, entityName2, srvpath, null, authorName);
146+
}
147+
148+
public String createEntityDraftWithAuthor(
149+
String appUrl,
150+
String entityName,
151+
String entityName2,
152+
String srvpath,
153+
String bookID,
154+
String authorName) {
155+
MediaType mediaType = MediaType.parse("application/json");
156+
157+
String jsonBody;
158+
if (bookID != null && !bookID.isEmpty()) {
159+
// Creating a Chapter within a Book — same as the default path
160+
jsonBody =
161+
"{\n \"title\": \"IntegrationTestEntity\",\n \"book_ID\": \"" + bookID + "\"\n}";
162+
} else {
163+
// Creating a Book with a caller-supplied author name so parallel matrix jobs
164+
// can use distinct fixture markers and their cleanups won't clobber each other.
165+
jsonBody =
166+
"{\n \"title\": \"IntegrationTestEntity\",\n \""
167+
+ entityName2
168+
+ "\": {\n \"ID\": \"41cf82fb-94bf-4d62-9e45-fa25f959b5b0\",\n \"name\": \""
169+
+ authorName
170+
+ "\"\n }\n}";
171+
}
172+
173+
RequestBody body = RequestBody.create(mediaType, jsonBody);
174+
Request request =
175+
new Request.Builder()
176+
.url("https://" + appUrl + "/api/admin/" + entityName)
177+
.method("POST", body)
178+
.addHeader("Content-Type", "application/json")
179+
.addHeader("Authorization", token)
180+
.build();
181+
182+
try (Response response = executeWithRetry(request)) {
183+
if (!response.isSuccessful()) {
184+
if (response.code() == 401) {
185+
System.out.println(
186+
"Create entity failed due to incorrect token. Please check the credentials");
187+
}
188+
System.out.println("Create entity failed. Error : " + response.body().string());
189+
throw new IOException("Could not create entity");
190+
}
191+
Map<String, Object> responseMap = objectMapper.readValue(response.body().string(), Map.class);
192+
return (String) responseMap.get("ID");
193+
} catch (IOException e) {
194+
System.out.println("Could not create entity : " + e);
195+
}
196+
return ("Could not create entity");
197+
}
198+
143199
public String editEntityDraft(String appUrl, String entityName, String srvpath, String entityID) {
144200
MediaType mediaType = MediaType.parse("application/json");
145201
Request request =

sdm/src/test/java/integration/com/sap/cds/sdm/IntegrationTest_ActiveEntity_Chapters_MultipleFacet.java

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)