Skip to content

Commit a0ac9bd

Browse files
fix: delete cateogory by slug
1 parent eb92413 commit a0ac9bd

File tree

3 files changed

+359
-0
lines changed

3 files changed

+359
-0
lines changed

src/integration-test/java/com/commercetools/sync/integration/commons/utils/CategoryITUtils.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,45 @@ public static void deleteAllCategories(@Nonnull final ProjectApiRoot ctpClient)
311311
});
312312
}
313313

314+
/**
315+
* Deletes categories from CTP projects defined by the {@code ctpClient} that match any of the
316+
* supplied slugs in the specified locale. This method is useful for cleaning up categories that
317+
* may not have keys set (which prevents them from being properly tracked by {@link
318+
* #deleteAllCategories(ProjectApiRoot)}).
319+
*
320+
* @param ctpClient defines the CTP project to delete the categories from.
321+
* @param locale the locale to use when matching slugs.
322+
* @param slugs the list of slugs to match for deletion.
323+
*/
324+
public static void deleteCategoriesBySlug(
325+
@Nonnull final ProjectApiRoot ctpClient,
326+
@Nonnull final Locale locale,
327+
@Nonnull final List<String> slugs) {
328+
slugs.forEach(
329+
slug -> {
330+
ctpClient
331+
.categories()
332+
.get()
333+
.addWhere("slug(" + locale.getLanguage() + "=:slug)")
334+
.addPredicateVar("slug", slug)
335+
.execute()
336+
.toCompletableFuture()
337+
.join()
338+
.getBody()
339+
.getResults()
340+
.forEach(
341+
category ->
342+
ctpClient
343+
.categories()
344+
.withId(category.getId())
345+
.delete()
346+
.withVersion(category.getVersion())
347+
.execute()
348+
.toCompletableFuture()
349+
.join());
350+
});
351+
}
352+
314353
private static List<Category> sortCategoriesByLeastAncestors(
315354
@Nonnull final List<Category> categories) {
316355
categories.sort(Comparator.comparingInt(category -> category.getAncestors().size()));
Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
package com.commercetools.sync.integration.commons.utils;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import com.commercetools.api.models.category.Category;
6+
import com.commercetools.api.models.category.CategoryDraft;
7+
import com.commercetools.api.models.category.CategoryDraftBuilder;
8+
import com.commercetools.api.models.common.LocalizedString;
9+
import io.vrap.rmf.base.client.ApiHttpResponse;
10+
import java.util.List;
11+
import java.util.Locale;
12+
import java.util.concurrent.CompletableFuture;
13+
import java.util.stream.Collectors;
14+
import org.junit.jupiter.api.AfterAll;
15+
import org.junit.jupiter.api.BeforeAll;
16+
import org.junit.jupiter.api.BeforeEach;
17+
import org.junit.jupiter.api.Test;
18+
19+
/**
20+
* Integration tests for {@link CategoryITUtils} utility methods that require actual CTP API
21+
* interactions.
22+
*/
23+
class CategoryITUtilsIT {
24+
25+
/**
26+
* Delete all categories and types from target project before running tests.
27+
*/
28+
@BeforeAll
29+
static void setup() {
30+
CategoryITUtils.deleteAllCategories(TestClientUtils.CTP_TARGET_CLIENT);
31+
ITUtils.deleteTypes(TestClientUtils.CTP_TARGET_CLIENT);
32+
}
33+
34+
/** Clean up before each test to ensure a fresh state. */
35+
@BeforeEach
36+
void setupTest() {
37+
CategoryITUtils.deleteAllCategories(TestClientUtils.CTP_TARGET_CLIENT);
38+
}
39+
40+
/** Cleans up the target test data that were built in this test class. */
41+
@AfterAll
42+
static void tearDown() {
43+
CategoryITUtils.deleteAllCategories(TestClientUtils.CTP_TARGET_CLIENT);
44+
ITUtils.deleteTypes(TestClientUtils.CTP_TARGET_CLIENT);
45+
}
46+
47+
@Test
48+
void deleteCategoriesBySlug_WithExistingCategories_ShouldDeleteOnlyMatchingSlugs() {
49+
// preparation - create 4 categories with different slugs
50+
final CategoryDraft category1 =
51+
CategoryDraftBuilder.of()
52+
.name(LocalizedString.of(Locale.ENGLISH, "Category 1"))
53+
.slug(LocalizedString.of(Locale.ENGLISH, "test-slug-1"))
54+
.key("key1")
55+
.build();
56+
57+
final CategoryDraft category2 =
58+
CategoryDraftBuilder.of()
59+
.name(LocalizedString.of(Locale.ENGLISH, "Category 2"))
60+
.slug(LocalizedString.of(Locale.ENGLISH, "test-slug-2"))
61+
.key("key2")
62+
.build();
63+
64+
final CategoryDraft category3 =
65+
CategoryDraftBuilder.of()
66+
.name(LocalizedString.of(Locale.ENGLISH, "Category 3"))
67+
.slug(LocalizedString.of(Locale.ENGLISH, "test-slug-3"))
68+
.key("key3")
69+
.build();
70+
71+
final CategoryDraft category4 =
72+
CategoryDraftBuilder.of()
73+
.name(LocalizedString.of(Locale.ENGLISH, "Category 4"))
74+
.slug(LocalizedString.of(Locale.ENGLISH, "other-slug"))
75+
.key("key4")
76+
.build();
77+
78+
TestClientUtils.CTP_TARGET_CLIENT.categories().create(category1).executeBlocking();
79+
TestClientUtils.CTP_TARGET_CLIENT.categories().create(category2).executeBlocking();
80+
TestClientUtils.CTP_TARGET_CLIENT.categories().create(category3).executeBlocking();
81+
TestClientUtils.CTP_TARGET_CLIENT.categories().create(category4).executeBlocking();
82+
83+
// test - delete categories with slugs test-slug-1 and test-slug-2
84+
CategoryITUtils.deleteCategoriesBySlug(
85+
TestClientUtils.CTP_TARGET_CLIENT,
86+
Locale.ENGLISH,
87+
List.of("test-slug-1", "test-slug-2"));
88+
89+
// assertion - verify only 2 categories remain (test-slug-3 and other-slug)
90+
final List<Category> remainingCategories =
91+
TestClientUtils.CTP_TARGET_CLIENT
92+
.categories()
93+
.get()
94+
.execute()
95+
.toCompletableFuture()
96+
.join()
97+
.getBody()
98+
.getResults();
99+
100+
assertThat(remainingCategories).hasSize(2);
101+
assertThat(remainingCategories)
102+
.extracting(category -> category.getSlug().get(Locale.ENGLISH))
103+
.containsExactlyInAnyOrder("test-slug-3", "other-slug");
104+
assertThat(remainingCategories)
105+
.extracting(Category::getKey)
106+
.containsExactlyInAnyOrder("key3", "key4");
107+
}
108+
109+
@Test
110+
void deleteCategoriesBySlug_WithCategoriesWithoutKeys_ShouldDeleteSuccessfully() {
111+
// preparation - create categories without keys
112+
final CategoryDraft categoryWithoutKey1 =
113+
CategoryDraftBuilder.of()
114+
.name(LocalizedString.of(Locale.ENGLISH, "Category Without Key 1"))
115+
.slug(LocalizedString.of(Locale.ENGLISH, "no-key-slug-1"))
116+
.build();
117+
118+
final CategoryDraft categoryWithoutKey2 =
119+
CategoryDraftBuilder.of()
120+
.name(LocalizedString.of(Locale.ENGLISH, "Category Without Key 2"))
121+
.slug(LocalizedString.of(Locale.ENGLISH, "no-key-slug-2"))
122+
.build();
123+
124+
final CategoryDraft categoryWithKey =
125+
CategoryDraftBuilder.of()
126+
.name(LocalizedString.of(Locale.ENGLISH, "Category With Key"))
127+
.slug(LocalizedString.of(Locale.ENGLISH, "with-key-slug"))
128+
.key("with-key")
129+
.build();
130+
131+
TestClientUtils.CTP_TARGET_CLIENT.categories().create(categoryWithoutKey1).executeBlocking();
132+
TestClientUtils.CTP_TARGET_CLIENT.categories().create(categoryWithoutKey2).executeBlocking();
133+
TestClientUtils.CTP_TARGET_CLIENT.categories().create(categoryWithKey).executeBlocking();
134+
135+
// test - delete categories without keys by their slugs
136+
CategoryITUtils.deleteCategoriesBySlug(
137+
TestClientUtils.CTP_TARGET_CLIENT,
138+
Locale.ENGLISH,
139+
List.of("no-key-slug-1", "no-key-slug-2"));
140+
141+
// assertion - verify only the category with key remains
142+
final List<Category> remainingCategories =
143+
TestClientUtils.CTP_TARGET_CLIENT
144+
.categories()
145+
.get()
146+
.execute()
147+
.toCompletableFuture()
148+
.join()
149+
.getBody()
150+
.getResults();
151+
152+
assertThat(remainingCategories).hasSize(1);
153+
assertThat(remainingCategories.get(0).getSlug().get(Locale.ENGLISH))
154+
.isEqualTo("with-key-slug");
155+
assertThat(remainingCategories.get(0).getKey()).isEqualTo("with-key");
156+
}
157+
158+
@Test
159+
void deleteCategoriesBySlug_WithNonExistingSlugs_ShouldNotThrowException() {
160+
// preparation - create one category
161+
final CategoryDraft category =
162+
CategoryDraftBuilder.of()
163+
.name(LocalizedString.of(Locale.ENGLISH, "Category"))
164+
.slug(LocalizedString.of(Locale.ENGLISH, "existing-slug"))
165+
.key("existing-key")
166+
.build();
167+
168+
TestClientUtils.CTP_TARGET_CLIENT.categories().create(category).executeBlocking();
169+
170+
// test - try to delete categories with non-existing slugs
171+
CategoryITUtils.deleteCategoriesBySlug(
172+
TestClientUtils.CTP_TARGET_CLIENT,
173+
Locale.ENGLISH,
174+
List.of("non-existing-slug-1", "non-existing-slug-2"));
175+
176+
// assertion - verify the existing category was not affected
177+
final List<Category> remainingCategories =
178+
TestClientUtils.CTP_TARGET_CLIENT
179+
.categories()
180+
.get()
181+
.execute()
182+
.toCompletableFuture()
183+
.join()
184+
.getBody()
185+
.getResults();
186+
187+
assertThat(remainingCategories).hasSize(1);
188+
assertThat(remainingCategories.get(0).getSlug().get(Locale.ENGLISH))
189+
.isEqualTo("existing-slug");
190+
}
191+
192+
@Test
193+
void deleteCategoriesBySlug_WithEmptySlugList_ShouldNotDeleteAnything() {
194+
// preparation - create categories
195+
final CategoryDraft category1 =
196+
CategoryDraftBuilder.of()
197+
.name(LocalizedString.of(Locale.ENGLISH, "Category 1"))
198+
.slug(LocalizedString.of(Locale.ENGLISH, "slug-1"))
199+
.key("key1")
200+
.build();
201+
202+
final CategoryDraft category2 =
203+
CategoryDraftBuilder.of()
204+
.name(LocalizedString.of(Locale.ENGLISH, "Category 2"))
205+
.slug(LocalizedString.of(Locale.ENGLISH, "slug-2"))
206+
.key("key2")
207+
.build();
208+
209+
TestClientUtils.CTP_TARGET_CLIENT.categories().create(category1).executeBlocking();
210+
TestClientUtils.CTP_TARGET_CLIENT.categories().create(category2).executeBlocking();
211+
212+
// test - call with empty list
213+
CategoryITUtils.deleteCategoriesBySlug(
214+
TestClientUtils.CTP_TARGET_CLIENT, Locale.ENGLISH, List.of());
215+
216+
// assertion - verify both categories still exist
217+
final List<Category> remainingCategories =
218+
TestClientUtils.CTP_TARGET_CLIENT
219+
.categories()
220+
.get()
221+
.execute()
222+
.toCompletableFuture()
223+
.join()
224+
.getBody()
225+
.getResults();
226+
227+
assertThat(remainingCategories).hasSize(2);
228+
assertThat(remainingCategories)
229+
.extracting(category -> category.getSlug().get(Locale.ENGLISH))
230+
.containsExactlyInAnyOrder("slug-1", "slug-2");
231+
}
232+
233+
@Test
234+
void deleteCategoriesBySlug_WithDifferentLocale_ShouldDeleteMatchingCategories() {
235+
// preparation - create categories with German slugs
236+
final CategoryDraft categoryDe1 =
237+
CategoryDraftBuilder.of()
238+
.name(LocalizedString.of(Locale.GERMAN, "Kategorie 1"))
239+
.slug(LocalizedString.of(Locale.GERMAN, "deutsche-slug-1"))
240+
.key("de-key1")
241+
.build();
242+
243+
final CategoryDraft categoryDe2 =
244+
CategoryDraftBuilder.of()
245+
.name(LocalizedString.of(Locale.GERMAN, "Kategorie 2"))
246+
.slug(LocalizedString.of(Locale.GERMAN, "deutsche-slug-2"))
247+
.key("de-key2")
248+
.build();
249+
250+
final CategoryDraft categoryDe3 =
251+
CategoryDraftBuilder.of()
252+
.name(LocalizedString.of(Locale.GERMAN, "Kategorie 3"))
253+
.slug(LocalizedString.of(Locale.GERMAN, "andere-slug"))
254+
.key("de-key3")
255+
.build();
256+
257+
TestClientUtils.CTP_TARGET_CLIENT.categories().create(categoryDe1).executeBlocking();
258+
TestClientUtils.CTP_TARGET_CLIENT.categories().create(categoryDe2).executeBlocking();
259+
TestClientUtils.CTP_TARGET_CLIENT.categories().create(categoryDe3).executeBlocking();
260+
261+
// test - delete categories with German slugs
262+
CategoryITUtils.deleteCategoriesBySlug(
263+
TestClientUtils.CTP_TARGET_CLIENT, Locale.GERMAN, List.of("deutsche-slug-1"));
264+
265+
// assertion - verify only 2 categories remain
266+
final List<Category> remainingCategories =
267+
TestClientUtils.CTP_TARGET_CLIENT
268+
.categories()
269+
.get()
270+
.execute()
271+
.toCompletableFuture()
272+
.join()
273+
.getBody()
274+
.getResults();
275+
276+
assertThat(remainingCategories).hasSize(2);
277+
assertThat(remainingCategories)
278+
.extracting(category -> category.getSlug().get(Locale.GERMAN))
279+
.containsExactlyInAnyOrder("deutsche-slug-2", "andere-slug");
280+
}
281+
282+
@Test
283+
void deleteCategoriesBySlug_WithDuplicateSlugsInList_ShouldHandleGracefully() {
284+
// preparation - create category
285+
final CategoryDraft category =
286+
CategoryDraftBuilder.of()
287+
.name(LocalizedString.of(Locale.ENGLISH, "Category"))
288+
.slug(LocalizedString.of(Locale.ENGLISH, "duplicate-slug"))
289+
.key("dup-key")
290+
.build();
291+
292+
TestClientUtils.CTP_TARGET_CLIENT.categories().create(category).executeBlocking();
293+
294+
// test - try to delete with duplicate slugs in the list
295+
CategoryITUtils.deleteCategoriesBySlug(
296+
TestClientUtils.CTP_TARGET_CLIENT,
297+
Locale.ENGLISH,
298+
List.of("duplicate-slug", "duplicate-slug", "duplicate-slug"));
299+
300+
// assertion - verify category was deleted (no error thrown)
301+
final List<Category> remainingCategories =
302+
TestClientUtils.CTP_TARGET_CLIENT
303+
.categories()
304+
.get()
305+
.execute()
306+
.toCompletableFuture()
307+
.join()
308+
.getBody()
309+
.getResults();
310+
311+
assertThat(remainingCategories).isEmpty();
312+
}
313+
}
314+

src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/categories/CategorySyncIT.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,12 @@ void syncDrafts_fromCategoriesWithoutKeys_ShouldNotUpdateCategories() {
486486
CompletableFuture.allOf(futureCreations.toArray(new CompletableFuture[futureCreations.size()]))
487487
.join();
488488

489+
// Delete any existing categories in TARGET with the same slugs to avoid conflicts
490+
CategoryITUtils.deleteCategoriesBySlug(
491+
TestClientUtils.CTP_TARGET_CLIENT,
492+
Locale.ENGLISH,
493+
List.of("furniture1-project-source", "furniture2-project-source"));
494+
489495
// Create two categories in the target without Keys.
490496
futureCreations = new ArrayList<>();
491497
final CategoryDraft newCategoryDraft1 =

0 commit comments

Comments
 (0)