Skip to content

Commit 902206c

Browse files
test(metadata): add comprehensive language-filtering integration tests
DSC-896 - DSpaceObjectServiceImplLanguageFilterIT: 8 cases covering null, ANY, en-family prefix match (en/en_US/en_UK/en-GB), case-insensitivity, no-match (fr), blank lang, and multi-qualifier scenarios - MetadataSecurityServiceImplLangIT: 8 cases covering always-visible (null/*/blank), language-family match, unsupported-language fallback (ja), per-field fallback isolation, and no-fallback when matching values exist - MetadataExportIT: 3 cases verifying null/*/blank languages are all preserved on export - XlsCrosswalkIT: 2 cases verifying export is language-filter-free (all 6 variants exported) - ItemMetadataLanguageIT: 6 REST-layer cases covering family filter, always-visible, fallback, no-fallback, and unsupported-language scenarios
1 parent 724a8aa commit 902206c

5 files changed

Lines changed: 937 additions & 0 deletions

File tree

dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportIT.java

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,90 @@ public void metadataExportToCsvTest_NonValidIdentifier() throws Exception {
279279
StringUtils.contains(exceptionDuringTestRun.getMessage(), nonValidUUID));
280280
}
281281

282+
@Test
283+
public void testExportAllLanguages_nullLangIncluded() throws Exception {
284+
context.turnOffAuthorisationSystem();
285+
Community community = CommunityBuilder.createCommunity(context)
286+
.build();
287+
Collection collection = CollectionBuilder.createCollection(context, community)
288+
.build();
289+
Item item = ItemBuilder.createItem(context, collection)
290+
.withTitle("Title null lang")
291+
.withTitleForLanguage("Title en", "en")
292+
.withTitleForLanguage("Title de", "de")
293+
.withTitleForLanguage("Title ja", "ja")
294+
.build();
295+
context.restoreAuthSystemState();
296+
String fileLocation = configurationService.getProperty("dspace.dir")
297+
+ testProps.get("test.exportcsv").toString();
298+
299+
String[] args = new String[] {"metadata-export",
300+
"-i", String.valueOf(item.getHandle()),
301+
"-f", fileLocation};
302+
TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler();
303+
304+
ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl),
305+
testDSpaceRunnableHandler, kernelImpl);
306+
File file = new File(fileLocation);
307+
String fileContent = IOUtils.toString(new FileInputStream(file), StandardCharsets.UTF_8);
308+
assertTrue("CSV must contain null-lang title", fileContent.contains("Title null lang"));
309+
assertTrue("CSV must contain en title", fileContent.contains("Title en"));
310+
assertTrue("CSV must contain de title", fileContent.contains("Title de"));
311+
assertTrue("CSV must contain ja title", fileContent.contains("Title ja"));
312+
}
313+
314+
@Test
315+
public void testExportAllLanguages_starLangIncluded() throws Exception {
316+
context.turnOffAuthorisationSystem();
317+
Community community = CommunityBuilder.createCommunity(context)
318+
.build();
319+
Collection collection = CollectionBuilder.createCollection(context, community)
320+
.build();
321+
Item item = ItemBuilder.createItem(context, collection)
322+
.withTitleForLanguage("Title star lang", "*")
323+
.build();
324+
context.restoreAuthSystemState();
325+
String fileLocation = configurationService.getProperty("dspace.dir")
326+
+ testProps.get("test.exportcsv").toString();
327+
328+
String[] args = new String[] {"metadata-export",
329+
"-i", String.valueOf(item.getHandle()),
330+
"-f", fileLocation};
331+
TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler();
332+
333+
ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl),
334+
testDSpaceRunnableHandler, kernelImpl);
335+
File file = new File(fileLocation);
336+
String fileContent = IOUtils.toString(new FileInputStream(file), StandardCharsets.UTF_8);
337+
assertTrue("CSV must contain star-lang title", fileContent.contains("Title star lang"));
338+
}
339+
340+
@Test
341+
public void testExportAllLanguages_blankLangIncluded() throws Exception {
342+
context.turnOffAuthorisationSystem();
343+
Community community = CommunityBuilder.createCommunity(context)
344+
.build();
345+
Collection collection = CollectionBuilder.createCollection(context, community)
346+
.build();
347+
Item item = ItemBuilder.createItem(context, collection)
348+
.withTitleForLanguage("Title blank lang", "")
349+
.build();
350+
context.restoreAuthSystemState();
351+
String fileLocation = configurationService.getProperty("dspace.dir")
352+
+ testProps.get("test.exportcsv").toString();
353+
354+
String[] args = new String[] {"metadata-export",
355+
"-i", String.valueOf(item.getHandle()),
356+
"-f", fileLocation};
357+
TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler();
358+
359+
ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl),
360+
testDSpaceRunnableHandler, kernelImpl);
361+
File file = new File(fileLocation);
362+
String fileContent = IOUtils.toString(new FileInputStream(file), StandardCharsets.UTF_8);
363+
assertTrue("CSV must contain blank-lang title", fileContent.contains("Title blank lang"));
364+
}
365+
282366
@Test
283367
public void metadataExportToCsvTest_NonValidDSOType() throws Exception {
284368
String fileLocation = configurationService.getProperty("dspace.dir")
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
/**
2+
* The contents of this file are subject to the license and copyright
3+
* detailed in the LICENSE and NOTICE files at the root of the source
4+
* tree and available online at
5+
*
6+
* http://www.dspace.org/license/
7+
*/
8+
package org.dspace.content;
9+
10+
import static org.hamcrest.MatcherAssert.assertThat;
11+
import static org.hamcrest.Matchers.containsInAnyOrder;
12+
import static org.hamcrest.Matchers.empty;
13+
import static org.hamcrest.Matchers.hasSize;
14+
import static org.hamcrest.Matchers.is;
15+
16+
import java.sql.SQLException;
17+
import java.util.List;
18+
import java.util.stream.Collectors;
19+
20+
import org.dspace.AbstractIntegrationTestWithDatabase;
21+
import org.dspace.authorize.AuthorizeException;
22+
import org.dspace.builder.CollectionBuilder;
23+
import org.dspace.builder.CommunityBuilder;
24+
import org.dspace.builder.ItemBuilder;
25+
import org.dspace.content.factory.ContentServiceFactory;
26+
import org.dspace.content.service.ItemService;
27+
import org.junit.Before;
28+
import org.junit.Test;
29+
30+
/**
31+
* Integration tests for the language-family filtering logic introduced in DSC-896,
32+
* specifically for {@link org.dspace.content.DSpaceObjectServiceImpl#getMetadata(
33+
* org.dspace.content.DSpaceObject, String, String, String, String)}.
34+
*
35+
* <p>All tests create a real {@link Item} in H2 and invoke the public service API.
36+
*
37+
* @author DSpace developers
38+
*/
39+
public class DSpaceObjectServiceImplLanguageFilterIT extends AbstractIntegrationTestWithDatabase {
40+
41+
private static final String DC = "dc";
42+
private static final String TITLE = "title";
43+
private static final String DESCRIPTION = "description";
44+
45+
private final ItemService itemService =
46+
ContentServiceFactory.getInstance().getItemService();
47+
48+
private Collection collection;
49+
50+
@Override
51+
@Before
52+
public void setUp() throws Exception {
53+
super.setUp();
54+
55+
context.turnOffAuthorisationSystem();
56+
parentCommunity = CommunityBuilder.createCommunity(context)
57+
.withName("Test Community")
58+
.build();
59+
collection = CollectionBuilder.createCollection(context, parentCommunity)
60+
.withName("Test Collection")
61+
.build();
62+
context.restoreAuthSystemState();
63+
}
64+
65+
// ------------------------------------------------------------------
66+
// Helper: extract stored values from a MetadataValue list
67+
// ------------------------------------------------------------------
68+
69+
private List<String> values(List<MetadataValue> mvs) {
70+
return mvs.stream().map(MetadataValue::getValue).collect(Collectors.toList());
71+
}
72+
73+
private List<String> langs(List<MetadataValue> mvs) {
74+
return mvs.stream().map(MetadataValue::getLanguage).collect(Collectors.toList());
75+
}
76+
77+
// ------------------------------------------------------------------
78+
// Test 1: lang = "en" matches exact "en", "en_US", "en_UK", "en-GB"
79+
// but NOT "de" or null.
80+
// ------------------------------------------------------------------
81+
82+
@Test
83+
public void testLangEnMatchesLanguageFamily() throws SQLException, AuthorizeException {
84+
context.turnOffAuthorisationSystem();
85+
Item item = ItemBuilder.createItem(context, collection)
86+
.withTitleForLanguage("Title EN", "en")
87+
.withTitleForLanguage("Title EN_US", "en_US")
88+
.withTitleForLanguage("Title EN_UK", "en_UK")
89+
.withTitleForLanguage("Title EN-GB", "en-GB")
90+
.withTitleForLanguage("Title DE", "de")
91+
.withTitle("Title NULL") // null language via standard builder
92+
.build();
93+
context.restoreAuthSystemState();
94+
context.commit();
95+
96+
List<MetadataValue> result = itemService.getMetadata(item, DC, TITLE, null, "en");
97+
98+
assertThat("en filter must return 4 values", result, hasSize(4));
99+
assertThat(values(result), containsInAnyOrder(
100+
"Title EN", "Title EN_US", "Title EN_UK", "Title EN-GB"));
101+
}
102+
103+
// ------------------------------------------------------------------
104+
// Test 2: lang = null returns only values with null language.
105+
// ------------------------------------------------------------------
106+
107+
@Test
108+
public void testNullLangReturnsNullLanguageValuesOnly() throws SQLException, AuthorizeException {
109+
context.turnOffAuthorisationSystem();
110+
Item item = ItemBuilder.createItem(context, collection)
111+
.withTitleForLanguage("Title EN", "en")
112+
.withTitleForLanguage("Title DE", "de")
113+
.withTitle("Title NULL")
114+
.build();
115+
context.restoreAuthSystemState();
116+
context.commit();
117+
118+
List<MetadataValue> result = itemService.getMetadata(item, DC, TITLE, null, null);
119+
120+
assertThat("null lang filter must return only null-language value", result, hasSize(1));
121+
assertThat(result.get(0).getValue(), is("Title NULL"));
122+
assertThat(result.get(0).getLanguage(), is((String) null));
123+
}
124+
125+
// ------------------------------------------------------------------
126+
// Test 3: lang = Item.ANY returns ALL values.
127+
// ------------------------------------------------------------------
128+
129+
@Test
130+
public void testAnyLangReturnsAll() throws SQLException, AuthorizeException {
131+
context.turnOffAuthorisationSystem();
132+
Item item = ItemBuilder.createItem(context, collection)
133+
.withTitleForLanguage("Title EN", "en")
134+
.withTitleForLanguage("Title EN_US", "en_US")
135+
.withTitleForLanguage("Title EN_UK", "en_UK")
136+
.withTitleForLanguage("Title EN-GB", "en-GB")
137+
.withTitleForLanguage("Title DE", "de")
138+
.withTitle("Title NULL")
139+
.build();
140+
context.restoreAuthSystemState();
141+
context.commit();
142+
143+
List<MetadataValue> result = itemService.getMetadata(item, DC, TITLE, null, Item.ANY);
144+
145+
assertThat("Item.ANY must return all 6 values", result, hasSize(6));
146+
}
147+
148+
// ------------------------------------------------------------------
149+
// Test 4: lang = "de" returns only "de", not "en*".
150+
// ------------------------------------------------------------------
151+
152+
@Test
153+
public void testLangDeReturnsOnlyDe() throws SQLException, AuthorizeException {
154+
context.turnOffAuthorisationSystem();
155+
Item item = ItemBuilder.createItem(context, collection)
156+
.withTitleForLanguage("Title EN", "en")
157+
.withTitleForLanguage("Title EN_US", "en_US")
158+
.withTitleForLanguage("Title DE", "de")
159+
.withTitle("Title NULL")
160+
.build();
161+
context.restoreAuthSystemState();
162+
context.commit();
163+
164+
List<MetadataValue> result = itemService.getMetadata(item, DC, TITLE, null, "de");
165+
166+
assertThat("de filter must return exactly 1 value", result, hasSize(1));
167+
assertThat(result.get(0).getValue(), is("Title DE"));
168+
assertThat(result.get(0).getLanguage(), is("de"));
169+
}
170+
171+
// ------------------------------------------------------------------
172+
// Test 5: Language matching is case-insensitive.
173+
// ------------------------------------------------------------------
174+
175+
@Test
176+
public void testLangMatchingIsCaseInsensitive() throws SQLException, AuthorizeException {
177+
context.turnOffAuthorisationSystem();
178+
Item item = ItemBuilder.createItem(context, collection)
179+
.withTitleForLanguage("Title UPPER", "EN")
180+
.withTitleForLanguage("Title Mixed", "En_US")
181+
.withTitleForLanguage("Title DE", "de")
182+
.build();
183+
context.restoreAuthSystemState();
184+
context.commit();
185+
186+
List<MetadataValue> result = itemService.getMetadata(item, DC, TITLE, null, "en");
187+
188+
assertThat("case-insensitive en filter must return 2 values", result, hasSize(2));
189+
assertThat(values(result), containsInAnyOrder("Title UPPER", "Title Mixed"));
190+
}
191+
192+
// ------------------------------------------------------------------
193+
// Test 6: lang = "fr" returns empty when no French values exist.
194+
// ------------------------------------------------------------------
195+
196+
@Test
197+
public void testLangFrReturnsEmptyWhenNoFrenchValues() throws SQLException, AuthorizeException {
198+
context.turnOffAuthorisationSystem();
199+
Item item = ItemBuilder.createItem(context, collection)
200+
.withTitleForLanguage("Title EN", "en")
201+
.withTitle("Title NULL")
202+
.build();
203+
context.restoreAuthSystemState();
204+
context.commit();
205+
206+
List<MetadataValue> result = itemService.getMetadata(item, DC, TITLE, null, "fr");
207+
208+
assertThat("fr filter must return empty list when no fr values", result, is(empty()));
209+
}
210+
211+
// ------------------------------------------------------------------
212+
// Test 7: Blank/empty language strings.
213+
// - getMetadata(…, "en") must NOT match blank/empty lang values.
214+
// - getMetadata(…, null) behavior documented: blank lang ≠ null lang
215+
// (implementation returns blank values only when lang == Item.ANY).
216+
// ------------------------------------------------------------------
217+
218+
@Test
219+
public void testBlankLanguageNotMatchedByEnFilter() throws SQLException, AuthorizeException {
220+
context.turnOffAuthorisationSystem();
221+
Item item = ItemBuilder.createItem(context, collection)
222+
.withTitleForLanguage("Title BLANK", "")
223+
.withTitleForLanguage("Title SPACES", " ")
224+
.withTitleForLanguage("Title EN", "en")
225+
.build();
226+
context.restoreAuthSystemState();
227+
context.commit();
228+
229+
// "en" filter: blank lang does NOT start with "en", so excluded
230+
List<MetadataValue> enResult = itemService.getMetadata(item, DC, TITLE, null, "en");
231+
assertThat("en filter must not include blank-language values", enResult, hasSize(1));
232+
assertThat(enResult.get(0).getValue(), is("Title EN"));
233+
234+
// null filter: blank lang != null, so blank values not returned
235+
List<MetadataValue> nullResult = itemService.getMetadata(item, DC, TITLE, null, null);
236+
assertThat("null filter must not return blank-language values (blank != null)", nullResult, is(empty()));
237+
238+
// Item.ANY: all three returned
239+
List<MetadataValue> anyResult = itemService.getMetadata(item, DC, TITLE, null, Item.ANY);
240+
assertThat("Item.ANY must return all 3 values", anyResult, hasSize(3));
241+
}
242+
243+
// ------------------------------------------------------------------
244+
// Test 8: lang = "en" with qualifier = Item.ANY spans multiple qualifiers.
245+
// ------------------------------------------------------------------
246+
247+
@Test
248+
public void testLangEnWithAnyQualifierSpansQualifiers() throws SQLException, AuthorizeException {
249+
context.turnOffAuthorisationSystem();
250+
Item item = ItemBuilder.createItem(context, collection)
251+
// dc.title (no qualifier)
252+
.withTitleForLanguage("Title EN_US", "en_US")
253+
// dc.title.alternative
254+
.withTitleForLanguage("Alt EN_US", "en_US") // reuse withTitleForLanguage for dc.title.alternative
255+
.build();
256+
257+
// Add dc.title.alternative with en_US language directly via itemService
258+
itemService.addMetadata(context, item, DC, TITLE, "alternative", "en_US", "Alt EN_US Direct");
259+
itemService.update(context, item);
260+
context.restoreAuthSystemState();
261+
context.commit();
262+
263+
List<MetadataValue> result = itemService.getMetadata(item, DC, TITLE, Item.ANY, "en");
264+
265+
// Should include dc.title (en_US) and dc.title.alternative (en_US) matches
266+
assertThat("en filter with ANY qualifier must return values from all qualifiers",
267+
result.stream().anyMatch(mv -> "en_US".equals(mv.getLanguage())), is(true));
268+
}
269+
270+
}

0 commit comments

Comments
 (0)