From 62f0f38fd2c3c6862e8738a3a8bedb87331a3a28 Mon Sep 17 00:00:00 2001 From: Arved Solth Date: Thu, 8 Feb 2024 14:08:16 +0100 Subject: [PATCH 1/4] Fix 'addProcessFromCatalogTest' --- .../test/java/org/kitodo/MockDatabase.java | 8 +++--- .../services/data/ImportServiceIT.java | 3 +-- .../java/org/kitodo/selenium/AddingST.java | 21 ++++++++++++++-- .../pages/ProcessFromTemplatePage.java | 25 ++++++++++++------- .../testframework/pages/ProcessesPage.java | 13 ++++++++++ 5 files changed, 54 insertions(+), 16 deletions(-) diff --git a/Kitodo/src/test/java/org/kitodo/MockDatabase.java b/Kitodo/src/test/java/org/kitodo/MockDatabase.java index 0de1dce0efa..e9c6dd68859 100644 --- a/Kitodo/src/test/java/org/kitodo/MockDatabase.java +++ b/Kitodo/src/test/java/org/kitodo/MockDatabase.java @@ -136,6 +136,7 @@ public class MockDatabase { public static final String HIERARCHY_CHILD_TO_KEEP = "HierarchyChildToKeep"; public static final String HIERARCHY_CHILD_TO_REMOVE = "HierarchyChildToRemove"; public static final String HIERARCHY_CHILD_TO_ADD = "HierarchyChildToAdd"; + public static final int PORT = 8888; public static void startDatabaseServer() throws SQLException { tcpServer = Server.createTcpServer().start(); @@ -1717,7 +1718,7 @@ public static void insertImportConfigurations() throws DAOException, DataExcepti kalliopeConfiguration.setHost("localhost"); kalliopeConfiguration.setScheme("http"); kalliopeConfiguration.setPath("/sru"); - kalliopeConfiguration.setPort(8888); + kalliopeConfiguration.setPort(PORT); kalliopeConfiguration.setPrestructuredImport(false); kalliopeConfiguration.setReturnFormat(FileFormat.XML.name()); kalliopeConfiguration.setMetadataFormat(MetadataFormat.MODS.name()); @@ -1755,7 +1756,8 @@ public static void insertImportConfigurations() throws DAOException, DataExcepti k10plusConfiguration.setHost("localhost"); k10plusConfiguration.setScheme("http"); k10plusConfiguration.setPath("/sru"); - k10plusConfiguration.setPort(8888); + k10plusConfiguration.setPort(PORT); + k10plusConfiguration.setDefaultImportDepth(1); k10plusConfiguration.setPrestructuredImport(false); k10plusConfiguration.setReturnFormat(FileFormat.XML.name()); k10plusConfiguration.setMetadataFormat(MetadataFormat.PICA.name()); @@ -1807,7 +1809,7 @@ public static void insertImportconfigurationWithCustomUrlParameters() throws DAO customConfiguration.setHost("localhost"); customConfiguration.setScheme("http"); customConfiguration.setPath("/custom"); - customConfiguration.setPort(8888); + customConfiguration.setPort(PORT); customConfiguration.setPrestructuredImport(false); customConfiguration.setReturnFormat(FileFormat.XML.name()); customConfiguration.setMetadataFormat(MetadataFormat.PICA.name()); diff --git a/Kitodo/src/test/java/org/kitodo/production/services/data/ImportServiceIT.java b/Kitodo/src/test/java/org/kitodo/production/services/data/ImportServiceIT.java index 703d7691c3a..b65a12acdf1 100644 --- a/Kitodo/src/test/java/org/kitodo/production/services/data/ImportServiceIT.java +++ b/Kitodo/src/test/java/org/kitodo/production/services/data/ImportServiceIT.java @@ -104,7 +104,6 @@ public class ImportServiceIT { private static final List CHILD_RECORD_IDS = Arrays.asList("9991", "9992", "9993"); private static final String KALLIOPE_RECORD_ID = "999"; private static final String CUSTOM_INTERFACE_RECORD_ID = "12345"; - private static final int PORT = 8888; private static final int TEMPLATE_ID = 1; private static final int PROJECT_ID = 1; private static final int RULESET_ID = 1; @@ -136,7 +135,7 @@ public static void prepareDatabase() throws Exception { SecurityTestUtils.addUserDataToSecurityContext(userOne, 1); return !processService.findByTitle(firstProcess).isEmpty(); }); - server = new StubServer(PORT).run(); + server = new StubServer(MockDatabase.PORT).run(); setupServer(); } diff --git a/Kitodo/src/test/java/org/kitodo/selenium/AddingST.java b/Kitodo/src/test/java/org/kitodo/selenium/AddingST.java index 13eebcd9927..2ea27fee53c 100644 --- a/Kitodo/src/test/java/org/kitodo/selenium/AddingST.java +++ b/Kitodo/src/test/java/org/kitodo/selenium/AddingST.java @@ -18,9 +18,11 @@ import static org.junit.Assume.assumeTrue; import java.io.File; +import java.io.IOException; import java.util.List; import java.util.concurrent.TimeUnit; +import com.xebialabs.restito.server.StubServer; import org.apache.commons.lang3.SystemUtils; import org.junit.After; import org.junit.AfterClass; @@ -60,6 +62,7 @@ public class AddingST extends BaseTestSelenium { + private static StubServer server; private static ProcessesPage processesPage; private static ProjectsPage projectsPage; private static UsersPage usersPage; @@ -68,6 +71,9 @@ public class AddingST extends BaseTestSelenium { private static ImportConfigurationEditPage importConfigurationEditPage; private static final String TEST_METADATA_FILE = "testMetadataFileServiceTest.xml"; private static int secondProcessId = -1; + private static final String PICA_PPN = "pica.ppn"; + private static final String PICA_XML = "picaxml"; + private static final String TEST_FILE_PATH = "src/test/resources/sruTestRecord.xml"; @BeforeClass public static void setup() throws Exception { @@ -88,6 +94,13 @@ public static void setup() throws Exception { } assertTrue("Should find exactly one second process!", secondProcessId > 0); ProcessTestUtils.copyTestMetadataFile(secondProcessId, TEST_METADATA_FILE); + server = new StubServer(MockDatabase.PORT).run(); + setupServer(); + } + + private static void setupServer() throws IOException { + // REST endpoint for testing metadata import + MockDatabase.addRestEndPointForSru(server, PICA_PPN + "=test", TEST_FILE_PATH, PICA_XML, 1); } /** @@ -97,6 +110,7 @@ public static void setup() throws Exception { @AfterClass public static void removeUnsuitableParentTestProcess() throws DAOException { ProcessTestUtils.removeTestProcess(secondProcessId); + server.stop(); } @Before @@ -189,10 +203,9 @@ public void addProcessAsChildNotPossible() throws Exception { Pages.getProcessFromTemplatePage().cancel(); } - @Ignore @Test public void addProcessFromCatalogTest() throws Exception { - assumeTrue(!SystemUtils.IS_OS_WINDOWS && !SystemUtils.IS_OS_MAC); + assumeTrue(!SystemUtils.IS_OS_WINDOWS); projectsPage.createNewProcess(); assertEquals("Header for create new process is incorrect", "Einen neuen Vorgang anlegen (Produktionsvorlage: 'First template')", @@ -201,6 +214,10 @@ public void addProcessFromCatalogTest() throws Exception { String generatedTitle = Pages.getProcessFromTemplatePage().createProcessFromCatalog(); boolean processAvailable = processesPage.getProcessTitles().contains(generatedTitle); assertTrue("Created Process was not listed at processes table!", processAvailable); + int index = processesPage.getProcessTitles().indexOf(generatedTitle); + assertTrue("Process table does not contain ID or new process", index >= 0); + int processId = Integer.parseInt(processesPage.getProcessIds().get(index)); + ProcessTestUtils.removeTestProcess(processId); } @Test diff --git a/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/ProcessFromTemplatePage.java b/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/ProcessFromTemplatePage.java index 0874a92241e..f27b52ef6f5 100644 --- a/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/ProcessFromTemplatePage.java +++ b/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/ProcessFromTemplatePage.java @@ -90,7 +90,7 @@ public class ProcessFromTemplatePage extends EditPage { private WebElement searchTermInput; @SuppressWarnings("unused") - @FindBy(id = TAB_VIEW + ":performCatalogSearch") + @FindBy(id = OPAC_SEARCH_FORM + ":performCatalogSearch") private WebElement performCatalogSearchButton; @SuppressWarnings("unused") @@ -262,21 +262,28 @@ public boolean createProcessAsChildNotPossible() throws Exception { } } + /** + * Tests importing metadata from a simulated OPAC and saving it as a process in Kitodo.Production. + * @return title of the created process as String. + * @throws Exception when saving the imported process fails. + */ public String createProcessFromCatalog() throws Exception { clickElement(catalogSelect.findElement(By.cssSelector(CSS_SELECTOR_DROPDOWN_TRIGGER))); - clickElement(Browser.getDriver().findElement(By.id(catalogSelect.getAttribute("id") + "_2"))); - + clickElement(Browser.getDriver().findElement(By.cssSelector("li[data-label='K10Plus']"))); clickElement(fieldSelect.findElement(By.cssSelector(CSS_SELECTOR_DROPDOWN_TRIGGER))); - clickElement(Browser.getDriver().findElement(By.id(fieldSelect.getAttribute("id") + "_1"))); - + clickElement(Browser.getDriver().findElement(By.cssSelector("li[data-label='PPN']"))); + await("Wait for 'searchInput' field to become active").pollDelay(100, TimeUnit.MILLISECONDS) + .atMost(3, TimeUnit.SECONDS).ignoreExceptions().until(() -> searchTermInput.isEnabled()); searchTermInput.sendKeys("test"); + await("Wait for 'performSearch' button to become active").pollDelay(100, TimeUnit.MILLISECONDS) + .atMost(3, TimeUnit.SECONDS).ignoreExceptions().until(() -> performCatalogSearchButton.isEnabled()); performCatalogSearchButton.click(); - selectRecord.click(); - - Thread.sleep(Browser.getDelayAfterPickListClick()); + await("Wait for popup dialog and loading screen to disappear").pollDelay(1, TimeUnit.SECONDS) + .atMost(5, TimeUnit.SECONDS).ignoreExceptions().until(() -> !Browser.getDriver() + .findElement(By.id("loadingScreen")).isDisplayed()); titleSortInput.sendKeys("Test"); ppnAnalogInput.sendKeys("12345"); - + ppnDigitalInput.sendKeys("67890"); guessImagesInput.sendKeys("299"); generateTitleButton.click(); await("Wait for title generation").pollDelay(3, TimeUnit.SECONDS).atMost(10, TimeUnit.SECONDS) diff --git a/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/ProcessesPage.java b/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/ProcessesPage.java index 666c2709934..7f41ea815cd 100644 --- a/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/ProcessesPage.java +++ b/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/ProcessesPage.java @@ -234,6 +234,19 @@ public List getProcessTitles() throws Exception { return getTableDataByColumn(processesTable, 3); } + /** + * Returns a list of all process IDs which were displayed on process page. + * + * @return list of process IDs + * @throws Exception when navigating to processes page fails + */ + public List getProcessIds() throws Exception { + if (isNotAt()) { + goTo(); + } + return getTableDataByColumn(processesTable, 2); + } + public void createNewBatch() throws Exception { switchToTabByIndex(TabIndex.BATCHES.getIndex()); From 54fb63966d7bea00bbb746c305aaa25d781972aa Mon Sep 17 00:00:00 2001 From: Arved Solth Date: Fri, 9 Feb 2024 10:35:43 +0100 Subject: [PATCH 2/4] Fix 'addProcessesTest' --- .../java/org/kitodo/selenium/AddingST.java | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Kitodo/src/test/java/org/kitodo/selenium/AddingST.java b/Kitodo/src/test/java/org/kitodo/selenium/AddingST.java index 2ea27fee53c..34e04dc89af 100644 --- a/Kitodo/src/test/java/org/kitodo/selenium/AddingST.java +++ b/Kitodo/src/test/java/org/kitodo/selenium/AddingST.java @@ -20,6 +20,7 @@ import java.io.File; import java.io.IOException; import java.util.List; +import java.util.Optional; import java.util.concurrent.TimeUnit; import com.xebialabs.restito.server.StubServer; @@ -172,12 +173,10 @@ public void addProcessesTest() throws Exception { assertTrue("Created Process was not listed at processes table!", processAvailable); ProcessService processService = ServiceManager.getProcessService(); - // TODO: make processService.findByTitle(generatedTitle) work - int recordNumber = 1; - Process generatedProcess; - do { - generatedProcess = processService.getById(recordNumber++); - } while (!generatedTitle.equals(generatedProcess.getTitle())); + Optional optionalProcess = processService.getAll().stream().filter(process -> generatedTitle + .equals(process.getTitle())).findAny(); + assertTrue("Generated process not found in database", optionalProcess.isPresent()); + Process generatedProcess = optionalProcess.get(); assertNull("Created Process unexpectedly got a parent!", generatedProcess.getParent()); projectsPage.createNewProcess(); @@ -187,12 +186,12 @@ public void addProcessesTest() throws Exception { boolean childProcessAvailable = processesPage.getProcessTitles().contains(generatedChildTitle); assertTrue("Created Process was not listed at processes table!", childProcessAvailable); - // TODO: make processService.findByTitle(generatedChildTitle) work - Process generatedChildProcess; - do { - generatedChildProcess = processService.getById(recordNumber++); - } while (!generatedChildTitle.equals(generatedChildProcess.getTitle())); + Optional optionalChildProcess = processService.getAll().stream().filter(process -> generatedChildTitle + .equals(process.getTitle())).findAny(); + assertTrue("Generated child process not found in database", optionalChildProcess.isPresent()); + Process generatedChildProcess = optionalChildProcess.get(); assertEquals("Created Process has a wrong parent!", generatedProcess, generatedChildProcess.getParent()); + ProcessTestUtils.removeTestProcess(generatedProcess.getId()); } @Test From f8710680dfc0b090a1971016b2254f09b38f8bf9 Mon Sep 17 00:00:00 2001 From: Arved Solth Date: Tue, 13 Feb 2024 16:21:25 +0100 Subject: [PATCH 3/4] Add Selenium test for automatic child record import --- .../test/java/org/kitodo/MockDatabase.java | 28 ++++++-- .../catalogimport/CatalogImportIT.java | 10 ++- .../catalogimport/ImportServiceTest.java | 5 +- .../services/data/ImportServiceIT.java | 7 +- .../kitodo/selenium/ConfigConversionST.java | 8 ++- .../java/org/kitodo/selenium/ImportingST.java | 69 +++++++++++++++---- .../selenium/testframework/pages/Page.java | 4 +- .../pages/ProcessFromTemplatePage.java | 69 ++++++++++++++++--- .../testframework/pages/ProcessesPage.java | 17 +++++ .../org/kitodo/test/utils/TestConstants.java | 34 +++++++++ .../importMultipleChildRecords.xml | 18 ++++- .../importRecords/importParentRecord.xml | 5 +- .../importRecords/sruNumberOfChildRecords.xml | 22 ++++++ .../test/resources/rulesets/ruleset_test.xml | 3 + .../src/test/resources/xslt/mods2kitodo.xsl | 2 +- 15 files changed, 255 insertions(+), 46 deletions(-) create mode 100644 Kitodo/src/test/java/org/kitodo/test/utils/TestConstants.java create mode 100644 Kitodo/src/test/resources/importRecords/sruNumberOfChildRecords.xml diff --git a/Kitodo/src/test/java/org/kitodo/MockDatabase.java b/Kitodo/src/test/java/org/kitodo/MockDatabase.java index e9c6dd68859..bffb046d806 100644 --- a/Kitodo/src/test/java/org/kitodo/MockDatabase.java +++ b/Kitodo/src/test/java/org/kitodo/MockDatabase.java @@ -105,6 +105,7 @@ import org.kitodo.production.services.ServiceManager; import org.kitodo.production.workflow.model.Converter; import org.kitodo.test.utils.ProcessTestUtils; +import org.kitodo.test.utils.TestConstants; import static com.xebialabs.restito.builder.stub.StubHttp.whenHttp; import static com.xebialabs.restito.semantics.Condition.get; @@ -1686,7 +1687,7 @@ public static void insertImportConfigurations() throws DAOException, DataExcepti // add GBV import configuration, including id and default search fields ImportConfiguration gbvConfiguration = new ImportConfiguration(); - gbvConfiguration.setTitle("GBV"); + gbvConfiguration.setTitle(TestConstants.GBV); gbvConfiguration.setConfigurationType(ImportConfigurationType.OPAC_SEARCH.name()); gbvConfiguration.setInterfaceType(SearchInterfaceType.SRU.name()); gbvConfiguration.setSruVersion("1.2"); @@ -1710,7 +1711,7 @@ public static void insertImportConfigurations() throws DAOException, DataExcepti // add Kalliope import configuration, including id search field ImportConfiguration kalliopeConfiguration = new ImportConfiguration(); - kalliopeConfiguration.setTitle("Kalliope"); + kalliopeConfiguration.setTitle(TestConstants.KALLIOPE); kalliopeConfiguration.setConfigurationType(ImportConfigurationType.OPAC_SEARCH.name()); kalliopeConfiguration.setInterfaceType(SearchInterfaceType.SRU.name()); kalliopeConfiguration.setSruVersion("1.2"); @@ -1726,12 +1727,12 @@ public static void insertImportConfigurations() throws DAOException, DataExcepti .getById(1))); SearchField idSearchFieldKalliope = new SearchField(); - idSearchFieldKalliope.setValue("ead.id"); + idSearchFieldKalliope.setValue(TestConstants.EAD_ID); idSearchFieldKalliope.setLabel("Identifier"); idSearchFieldKalliope.setImportConfiguration(kalliopeConfiguration); SearchField parentIdSearchFieldKalliope = new SearchField(); - parentIdSearchFieldKalliope.setValue("context.ead.id"); + parentIdSearchFieldKalliope.setValue(TestConstants.EAD_PARENT_ID); parentIdSearchFieldKalliope.setLabel("Parent ID"); parentIdSearchFieldKalliope.setImportConfiguration(kalliopeConfiguration); @@ -1748,7 +1749,7 @@ public static void insertImportConfigurations() throws DAOException, DataExcepti // add K10Plus import configuration, including id search field ImportConfiguration k10plusConfiguration = new ImportConfiguration(); - k10plusConfiguration.setTitle("K10Plus"); + k10plusConfiguration.setTitle(TestConstants.K10PLUS); k10plusConfiguration.setConfigurationType(ImportConfigurationType.OPAC_SEARCH.name()); k10plusConfiguration.setInterfaceType(SearchInterfaceType.SRU.name()); k10plusConfiguration.setSruVersion("1.1"); @@ -2167,6 +2168,23 @@ public static Process addProcess(String processTitle, int projectId, int templat * @param numberOfRecords URL parameter containing maximum number of records associated with this endpoint * @throws IOException when reading the response file fails */ + public static void addRestEndPointForSru(StubServer server, String query, String filePath, String format, + int startRecord, int numberOfRecords) + throws IOException { + try (InputStream inputStream = Files.newInputStream(Paths.get(filePath))) { + String serverResponse = IOUtils.toString(inputStream, StandardCharsets.UTF_8); + whenHttp(server) + .match(get("/sru"), + parameter("operation", "searchRetrieve"), + parameter("recordSchema", format), + parameter("startRecord", String.valueOf(startRecord)), + parameter("maximumRecords", String.valueOf(numberOfRecords)), + parameter("query", query)) + .then(Action.ok(), Action.contentType("text/xml"), Action.stringContent(serverResponse)); + } + + } + public static void addRestEndPointForSru(StubServer server, String query, String filePath, String format, int numberOfRecords) throws IOException { diff --git a/Kitodo/src/test/java/org/kitodo/production/services/catalogimport/CatalogImportIT.java b/Kitodo/src/test/java/org/kitodo/production/services/catalogimport/CatalogImportIT.java index 425f66c7319..8f34cc09e24 100644 --- a/Kitodo/src/test/java/org/kitodo/production/services/catalogimport/CatalogImportIT.java +++ b/Kitodo/src/test/java/org/kitodo/production/services/catalogimport/CatalogImportIT.java @@ -25,14 +25,12 @@ import org.kitodo.SecurityTestUtils; import org.kitodo.production.helper.TempProcess; import org.kitodo.production.services.ServiceManager; +import org.kitodo.test.utils.TestConstants; public class CatalogImportIT { private static StubServer server; private static final int PORT = 8888; - private static final String HITLIST_RECORD_PATH = "src/test/resources/importRecords/importHitlist.xml"; - private static final String CHILD_RECORD_PATH = "src/test/resources/importRecords/importChildRecord.xml"; - private static final String PARENT_RECORD_PATH = "src/test/resources/importRecords/importParentRecord.xml"; private static final String CHILD_RECORD_ID = "1"; private static final String PARENT_RECORD_ID = "2"; private static final int PROJECT_ID = 1; @@ -67,8 +65,8 @@ public void shouldImportProcessHierarchy() throws Exception { private static void setupServer() throws IOException { server = new StubServer(PORT).run(); - MockDatabase.addRestEndPointForSru(server, "ead.id=" + CHILD_RECORD_ID, CHILD_RECORD_PATH, "mods", 1); - MockDatabase.addRestEndPointForSru(server, "ead.id=" + PARENT_RECORD_ID, PARENT_RECORD_PATH, "mods", 1); - MockDatabase.addRestEndPointForSru(server, "ead.title=test", HITLIST_RECORD_PATH, "mods",10); + MockDatabase.addRestEndPointForSru(server, "ead.id=" + CHILD_RECORD_ID, TestConstants.CHILD_RECORD_PATH, "mods", 1); + MockDatabase.addRestEndPointForSru(server, "ead.id=" + PARENT_RECORD_ID, TestConstants.PARENT_RECORD_PATH, "mods", 1); + MockDatabase.addRestEndPointForSru(server, "ead.title=test", TestConstants.HITLIST_RECORD_PATH, "mods",10); } } diff --git a/Kitodo/src/test/java/org/kitodo/production/services/catalogimport/ImportServiceTest.java b/Kitodo/src/test/java/org/kitodo/production/services/catalogimport/ImportServiceTest.java index 5668bd6b642..f78bfc8c83b 100644 --- a/Kitodo/src/test/java/org/kitodo/production/services/catalogimport/ImportServiceTest.java +++ b/Kitodo/src/test/java/org/kitodo/production/services/catalogimport/ImportServiceTest.java @@ -28,6 +28,7 @@ import org.kitodo.production.helper.Helper; import org.kitodo.production.services.ServiceManager; import org.kitodo.production.services.data.ImportService; +import org.kitodo.test.utils.TestConstants; public class ImportServiceTest { @@ -181,8 +182,8 @@ private ImportConfiguration createImportConfiguration(SearchInterfaceType interf private List createSearchFields(ImportConfiguration importConfiguration) { List SearchFields = new ArrayList<>(); SearchFields.add(addSearchField(TITLE, "ead.tit", importConfiguration, true)); - SearchFields.add(addSearchField(RECORD_ID, "ead.id", importConfiguration, true)); - SearchFields.add(addSearchField(PARENT_ID, "context.ead.id", importConfiguration, false)); + SearchFields.add(addSearchField(RECORD_ID, TestConstants.EAD_ID, importConfiguration, true)); + SearchFields.add(addSearchField(PARENT_ID, TestConstants.EAD_PARENT_ID, importConfiguration, false)); importConfiguration.setSearchFields(SearchFields); return SearchFields; } diff --git a/Kitodo/src/test/java/org/kitodo/production/services/data/ImportServiceIT.java b/Kitodo/src/test/java/org/kitodo/production/services/data/ImportServiceIT.java index b65a12acdf1..137b2916234 100644 --- a/Kitodo/src/test/java/org/kitodo/production/services/data/ImportServiceIT.java +++ b/Kitodo/src/test/java/org/kitodo/production/services/data/ImportServiceIT.java @@ -73,6 +73,7 @@ import org.kitodo.production.helper.XMLUtils; import org.kitodo.production.services.ServiceManager; import org.kitodo.test.utils.ProcessTestUtils; +import org.kitodo.test.utils.TestConstants; import org.w3c.dom.Document; import org.xml.sax.SAXException; @@ -109,10 +110,8 @@ public class ImportServiceIT { private static final int RULESET_ID = 1; private static final int EXPECTED_NR_OF_CHILDREN = 23; private static final String PICA_XML = "picaxml"; - private static final String MODS = "mods"; private static final String PICA_PPN = "pica.ppn"; private static final String PICA_PARENT_ID = "pica.parentId"; - private static final String EAD_PARENT_ID = "context.ead.id"; private static final String firstProcess = "First process"; private static final String TEST_PROCESS_TITLE = "Testtitel"; private static final String KITODO = "kitodo"; @@ -526,9 +525,9 @@ private static void setupServer() throws IOException { // REST endpoint for testing failed import of child records MockDatabase.addRestEndPointForSru(server, PICA_PARENT_ID + "=" + RECORD_ID, TEST_FILE_PATH_NUMBER_OF_HITS, PICA_XML, 1); // REST endpoint for testing successful import of child records - MockDatabase.addRestEndPointForSru(server, EAD_PARENT_ID + "=" + KALLIOPE_RECORD_ID, CHILD_RECORDS_PATH, MODS, 3); + MockDatabase.addRestEndPointForSru(server, TestConstants.EAD_PARENT_ID + "=" + KALLIOPE_RECORD_ID, CHILD_RECORDS_PATH, TestConstants.MODS, 3); // REST endpoint for testing retrieval of child records given existing parent process - MockDatabase.addRestEndPointForSru(server, EAD_PARENT_ID + "=" + PARENT_RECORD_CATALOG_ID, CHILD_RECORDS_PATH, MODS,3); + MockDatabase.addRestEndPointForSru(server, TestConstants.EAD_PARENT_ID + "=" + PARENT_RECORD_CATALOG_ID, CHILD_RECORDS_PATH, TestConstants.MODS,3); // REST endpoint for successful import from custom search interface MockDatabase.addRestEndPointForCustom(server, TEST_FILE_SUCCESS_RESPONSE_PATH, CUSTOM_INTERFACE_RECORD_ID, "firstValue"); diff --git a/Kitodo/src/test/java/org/kitodo/selenium/ConfigConversionST.java b/Kitodo/src/test/java/org/kitodo/selenium/ConfigConversionST.java index ead8a416d16..3a1e16142a1 100644 --- a/Kitodo/src/test/java/org/kitodo/selenium/ConfigConversionST.java +++ b/Kitodo/src/test/java/org/kitodo/selenium/ConfigConversionST.java @@ -29,6 +29,7 @@ import org.kitodo.selenium.testframework.Browser; import org.kitodo.selenium.testframework.Pages; import org.kitodo.selenium.testframework.pages.ProjectsPage; +import org.openqa.selenium.By; public class ConfigConversionST extends BaseTestSelenium { @@ -61,7 +62,12 @@ public void shouldImportCatalogConfigurations() throws Exception { assertEquals(MODS_2_KITODO, importConfigurationsTab.getMappingFileTitle()); importConfigurationsTab.selectInputFormatMods(); importConfigurationsTab.selectOutputFormatKitodo(); - importConfigurationsTab.clickMappingFileOkButton(); + await("Wait for 'confirm mapping' button to be enabled") + .pollDelay(1, TimeUnit.SECONDS) + .pollInterval(1, TimeUnit.SECONDS) + .atMost(5, TimeUnit.SECONDS).ignoreExceptions() + .until(() -> Browser.getDriver().findElement(By.id("mappingFileFormatsForm:ok")).isEnabled()); + Browser.getDriver().findElement(By.id("mappingFileFormatsForm:ok")).click(); await("Wait for 'Results' dialog to be displayed") .atMost(5, TimeUnit.SECONDS) diff --git a/Kitodo/src/test/java/org/kitodo/selenium/ImportingST.java b/Kitodo/src/test/java/org/kitodo/selenium/ImportingST.java index 7b2f6f3aa67..8cdc38f789b 100644 --- a/Kitodo/src/test/java/org/kitodo/selenium/ImportingST.java +++ b/Kitodo/src/test/java/org/kitodo/selenium/ImportingST.java @@ -20,16 +20,15 @@ import java.util.List; import java.util.concurrent.TimeUnit; +import com.xebialabs.restito.server.StubServer; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.kitodo.MockDatabase; -import org.kitodo.data.database.beans.Process; import org.kitodo.data.database.exceptions.DAOException; import org.kitodo.data.exceptions.DataException; -import org.kitodo.production.services.ServiceManager; import org.kitodo.production.services.data.ProcessService; import org.kitodo.selenium.testframework.BaseTestSelenium; import org.kitodo.selenium.testframework.Browser; @@ -38,14 +37,12 @@ import org.kitodo.selenium.testframework.pages.ProcessesPage; import org.kitodo.selenium.testframework.pages.ProjectsPage; import org.kitodo.test.utils.ProcessTestUtils; +import org.kitodo.test.utils.TestConstants; import org.openqa.selenium.support.ui.Select; public class ImportingST extends BaseTestSelenium { - private static final String PPN = "PPN"; - private static final String GBV = "GBV"; - private static final String K10PLUS = "K10Plus"; - private static final String KALLIOPE = "Kalliope"; + private static StubServer server; private static final String TEST_VOLUME = "Test volume"; private static final String TEST_MULTI_VOLUME_WORK_FILE = "testMultiVolumeWorkMeta.xml"; private static ProcessFromTemplatePage importPage; @@ -63,6 +60,8 @@ public static void setup() throws Exception { MockDatabase.insertMappingFiles(); MockDatabase.insertImportConfigurations(); MockDatabase.addDefaultChildProcessImportConfigurationToFirstProject(); + server = new StubServer(MockDatabase.PORT).run(); + setupServer(); } @Before @@ -81,18 +80,31 @@ public void logout() throws Exception { @AfterClass public static void cleanup() throws DAOException, DataException, IOException { ProcessService.deleteProcess(multiVolumeWorkId); + server.stop(); + } + + private static void setupServer() throws IOException { + // endpoint for retrieving number of child records of process with given ID + MockDatabase.addRestEndPointForSru(server, TestConstants.EAD_PARENT_ID + "=" + TestConstants.KALLIOPE_PARENT_ID, + TestConstants.NUMBER_OF_CHILD_RECORDS_PATH, TestConstants.MODS, 0); + // endpoint for retrieving parent process with given ID + MockDatabase.addRestEndPointForSru(server, TestConstants.EAD_ID + "=" + TestConstants.KALLIOPE_PARENT_ID, + TestConstants.PARENT_RECORD_PATH, TestConstants.MODS, 1); + // endpoint for retrieving child records of process with given ID + MockDatabase.addRestEndPointForSru(server, TestConstants.EAD_PARENT_ID + "=" + TestConstants.KALLIOPE_PARENT_ID, + TestConstants.MULTIPLE_CHILD_RECORDS_PATH, TestConstants.MODS, 1, 3); } @Test public void checkDefaultValuesTest() throws Exception { projectsPage.createNewProcess(); Select catalogSelectMenu = new Select(importPage.getCatalogMenu()); - assertEquals("Wrong default catalog selected", K10PLUS, + assertEquals("Wrong default catalog selected", TestConstants.K10PLUS, catalogSelectMenu.getFirstSelectedOption().getAttribute("label")); importPage.selectGBV(); Select searchFieldSelectMenu = new Select(importPage.getSearchFieldMenu()); - assertEquals("Wrong default search field selected", PPN, + assertEquals("Wrong default search field selected", TestConstants.PPN, searchFieldSelectMenu.getFirstSelectedOption().getAttribute("label")); } @@ -106,7 +118,7 @@ public void checkSearchButtonActivatedText() throws Exception { projectsPage.createNewProcess(); assertFalse("'Search' button should be deactivated until import configuration, search field and " + "search term have been selected", importPage.getSearchButton().isEnabled()); - importPage.enterTestSearchValue(); + importPage.enterTestSearchValue("12345"); await("Wait for 'Search' button to be enabled").pollDelay(700, TimeUnit.MILLISECONDS) .atMost(30, TimeUnit.SECONDS).ignoreExceptions() .until(() -> importPage.getSearchButton().isEnabled()); @@ -120,7 +132,6 @@ public void checkSearchButtonActivatedText() throws Exception { */ @Test public void checkDefaultChildProcessImportConfiguration() throws Exception { - Process process = ServiceManager.getProcessService().getById(multiVolumeWorkId); ProcessTestUtils.copyTestMetadataFile(multiVolumeWorkId, TEST_MULTI_VOLUME_WORK_FILE); processesPage.goTo(); processesPage.applyFilter("id:" + multiVolumeWorkId); @@ -139,8 +150,40 @@ public void checkDefaultChildProcessImportConfiguration() throws Exception { public void checkOrderOfImportConfigurations() throws Exception { projectsPage.createNewProcess(); List importConfigurationNames = importPage.getImportConfigurationsTitles(); - assertEquals("Wrong first import configuration title", GBV, importConfigurationNames.get(1)); - assertEquals("Wrong first import configuration title", K10PLUS, importConfigurationNames.get(2)); - assertEquals("Wrong first import configuration title", KALLIOPE, importConfigurationNames.get(3)); + assertEquals("Wrong title of first import configuration", TestConstants.GBV, importConfigurationNames.get(1)); + assertEquals("Wrong title of second import configuration", TestConstants.K10PLUS, importConfigurationNames.get(2)); + assertEquals("Wrong title of third import configuration", TestConstants.KALLIOPE, importConfigurationNames.get(3)); + } + + /** + * Tests whether import process hierarchies works correctly or not. + */ + @Test + public void checkHierarchyImport() throws Exception { + projectsPage.createNewProcess(); + Select catalogSelectMenu = new Select(importPage.getCatalogMenu()); + assertEquals("Wrong default catalog selected", TestConstants.K10PLUS, + catalogSelectMenu.getFirstSelectedOption().getAttribute("label")); + importPage.selectKalliope(); + Select searchFieldSelectMenu = new Select(importPage.getSearchFieldMenu()); + assertEquals("Wrong default search field selected", TestConstants.IDENTIFIER, + searchFieldSelectMenu.getFirstSelectedOption().getAttribute("label")); + importPage.enterTestSearchValue(TestConstants.KALLIOPE_PARENT_ID); + importPage.activateChildProcessImport(); + importPage.decreaseImportDepth(); + importPage.getSearchButton().click(); + assertTrue("Hierarchy panel should be visible", importPage.isHierarchyPanelVisible()); + String parentTitle = importPage.getProcessTitle(); + Pages.getProcessFromTemplatePage().save(); + processesPage.applyFilter(parentTitle); + assertEquals("Exactly one imported parent process should be displayed", 1, + processesPage.countListedProcesses()); + List processIds = processesPage.getProcessIds(); + assertEquals("Exactly one process ID should be visible", 1, processIds.size()); + int processId = Integer.parseInt(processIds.get(0)); + processesPage.filterByChildren(); + List childProcessIds = processesPage.getProcessIds(); + assertEquals("Wrong number of child processes", 3, childProcessIds.size()); + ProcessTestUtils.removeTestProcess(processId); } } diff --git a/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/Page.java b/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/Page.java index f29aa10188e..f729b38f3eb 100644 --- a/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/Page.java +++ b/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/Page.java @@ -192,7 +192,7 @@ protected void clickButtonAndWaitForRedirect(WebElement button, String url) { WebDriverWait webDriverWait = new WebDriverWait(Browser.getDriver(), 60); for (int attempt = 1; attempt < 4; attempt++) { try { - await("Wait for button clicked").pollDelay(700, TimeUnit.MILLISECONDS).atMost(30, TimeUnit.SECONDS) + await("Wait for button clicked").pollDelay(700, TimeUnit.MILLISECONDS).atMost(10, TimeUnit.SECONDS) .ignoreExceptions().until(() -> isButtonClicked.test(button)); if (Browser.isAlertPresent() && url.contains("login")) { Browser.getDriver().switchTo().alert().accept(); @@ -208,7 +208,7 @@ protected void clickButtonAndWaitForRedirect(WebElement button, String url) { } protected void clickElement(WebElement element) { - await("Wait for element clicked").pollDelay(500, TimeUnit.MILLISECONDS).atMost(20, TimeUnit.SECONDS) + await("Wait for element " + element.getText() + ", " + element.getTagName() + " to be clicked").pollDelay(500, TimeUnit.MILLISECONDS).atMost(20, TimeUnit.SECONDS) .ignoreExceptions().until(() -> isButtonClicked.test(element)); } diff --git a/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/ProcessFromTemplatePage.java b/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/ProcessFromTemplatePage.java index f27b52ef6f5..7a309d4a65e 100644 --- a/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/ProcessFromTemplatePage.java +++ b/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/ProcessFromTemplatePage.java @@ -20,6 +20,7 @@ import org.awaitility.core.ConditionTimeoutException; import org.kitodo.selenium.testframework.Browser; import org.kitodo.selenium.testframework.Pages; +import org.kitodo.test.utils.TestConstants; import org.openqa.selenium.By; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; @@ -28,6 +29,8 @@ public class ProcessFromTemplatePage extends EditPage { private static final String TAB_VIEW = EDIT_FORM + ":processFromTemplateTabView"; private static final String OPAC_SEARCH_FORM = "catalogSearchForm"; + private static final String HIERARCHY_PANEL = "editForm:processFromTemplateTabView:processHierarchyContent"; + private static final String IMPORT_CHILD_PROCESSES_SWITCH = "#catalogSearchForm\\:importChildren .ui-chkbox-box"; @SuppressWarnings("unused") @FindBy(id = TAB_VIEW) @@ -137,14 +140,26 @@ public WebElement getTemplateProcessMenu() { return Browser.getDriver().findElementById("searchEditForm:processSelect_input"); } + private void selectCatalog(String catalogName) throws InterruptedException { + clickElement(catalogSelect.findElement(By.cssSelector(CSS_SELECTOR_DROPDOWN_TRIGGER))); + clickElement(Browser.getDriver().findElement(By.cssSelector("li[data-label='" + catalogName + "']"))); + Thread.sleep(Browser.getDelayAfterCatalogSelection()); + } + /** * Select GBV catalog. * @throws InterruptedException when thread is interrupted */ public void selectGBV() throws InterruptedException { - clickElement(catalogSelect.findElement(By.cssSelector(CSS_SELECTOR_DROPDOWN_TRIGGER))); - clickElement(Browser.getDriver().findElement(By.id(catalogSelect.getAttribute("id") + "_1"))); - Thread.sleep(Browser.getDelayAfterCatalogSelection()); + selectCatalog(TestConstants.GBV); + } + + /** + * Select Kalliope catalog. + * @throws InterruptedException when thread is interrupted + */ + public void selectKalliope() throws InterruptedException { + selectCatalog(TestConstants.KALLIOPE); } /** @@ -177,7 +192,7 @@ public String createProcess() throws Exception { generateTitleButton.click(); await("Wait for title generation").pollDelay(3, TimeUnit.SECONDS).atMost(10, TimeUnit.SECONDS) .ignoreExceptions().until(() -> isInputValueNotEmpty.test(processTitleInput)); - String generatedTitle = processTitleInput.getAttribute("value"); + String generatedTitle = processTitleInput.getAttribute(TestConstants.VALUE); save(); return generatedTitle; } @@ -207,7 +222,7 @@ public String createProcessAsChild(String parentProcessTitle) throws Exception { generateTitleButton.click(); await("Wait for title generation").pollDelay(3, TimeUnit.SECONDS).atMost(10, TimeUnit.SECONDS) .ignoreExceptions().until(() -> isInputValueNotEmpty.test(processTitleInput)); - final String generatedTitle = processTitleInput.getAttribute("value"); + final String generatedTitle = processTitleInput.getAttribute(TestConstants.VALUE); switchToTabByIndex(1); searchForParentInput.sendKeys(parentProcessTitle); @@ -288,16 +303,54 @@ public String createProcessFromCatalog() throws Exception { generateTitleButton.click(); await("Wait for title generation").pollDelay(3, TimeUnit.SECONDS).atMost(10, TimeUnit.SECONDS) .ignoreExceptions().until(() -> isInputValueNotEmpty.test(processTitleInput)); - String generatedTitle = processTitleInput.getAttribute("value"); + String generatedTitle = processTitleInput.getAttribute(TestConstants.VALUE); save(); return generatedTitle; } + /** + * Get process title from corresponding "CreateProcessForm" input field. + * @return process title input field value + */ + public String getProcessTitle() { + return processTitleInput.getAttribute(TestConstants.VALUE); + } + + /** + * Check and return whether hierarchy panel is visible or not after triggering catalog import. + * @return whether hierarchy panel is visible + */ + public boolean isHierarchyPanelVisible() { + WebElement hierarchyPanel = Browser.getDriver().findElement(By.id(HIERARCHY_PANEL)); + return hierarchyPanel.isDisplayed(); + } + + /** + * Activate automatic import of child records by clicking on the switch labeled "Import child processes" + */ + public void activateChildProcessImport() { + // activate child process search + WebElement childProcessSwitch = Browser.getDriver().findElement(By.cssSelector(IMPORT_CHILD_PROCESSES_SWITCH)); + await("Wait for 'childProcessImport' switch to become active").pollDelay(100, TimeUnit.MILLISECONDS) + .atMost(3, TimeUnit.SECONDS).ignoreExceptions().until(childProcessSwitch::isEnabled); + clickElement(childProcessSwitch); + } + + /** + * Decrease the import depth for the catalog by clicking on the chevron-down arrow in the corresponding input field. + * @throws InterruptedException when putting the thread to sleep fails + */ + public void decreaseImportDepth() throws InterruptedException { + WebElement spinnerDown = Browser.getDriver().findElement(By.cssSelector("#catalogSearchForm .ui-spinner-down")); + spinnerDown.click(); + Thread.sleep(Browser.getDelayAfterCatalogSelection()); + } + /** * Enter test value into search term field. */ - public void enterTestSearchValue() { - searchTermInput.sendKeys("12345"); + public void enterTestSearchValue(String searchTerm) { + searchTermInput.sendKeys(searchTerm); } public ProcessesPage save() throws IllegalAccessException, InstantiationException { diff --git a/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/ProcessesPage.java b/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/ProcessesPage.java index 7f41ea815cd..7d3476513d7 100644 --- a/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/ProcessesPage.java +++ b/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/ProcessesPage.java @@ -572,4 +572,21 @@ public void createChildProcess() throws InstantiationException, IllegalAccessExc + MULTI_VOLUME_WORK_PROCESS_TITLE + "'"); } } + + /** + * Toggles first row expansion in process list. + */ + public void filterByChildren() { + WebElement rowToggler = processesTable.findElement(By.className("ui-row-toggler")); + rowToggler.click(); + await("Wait for row expansion to become visible").pollDelay(1, TimeUnit.SECONDS) + .pollInterval(500, TimeUnit.MILLISECONDS).atMost(3, TimeUnit.SECONDS) + .until(() -> Browser.getDriver().findElement(By.className("row-expansion-wrapper")).isDisplayed()); + WebElement rowExpansion = Browser.getDriver().findElement(By.className("row-expansion-wrapper")); + WebElement childFilterLink = rowExpansion.findElement(By.cssSelector(".value a")); + childFilterLink.click(); + await("Wait for execution of link click").pollDelay(1, TimeUnit.SECONDS) + .atMost(Browser.getDelayMaxAfterLinkClick(), TimeUnit.MILLISECONDS).ignoreExceptions() + .until(this::isAt); + } } diff --git a/Kitodo/src/test/java/org/kitodo/test/utils/TestConstants.java b/Kitodo/src/test/java/org/kitodo/test/utils/TestConstants.java new file mode 100644 index 00000000000..e16d51476de --- /dev/null +++ b/Kitodo/src/test/java/org/kitodo/test/utils/TestConstants.java @@ -0,0 +1,34 @@ +/* + * (c) Kitodo. Key to digital objects e. V. + * + * This file is part of the Kitodo project. + * + * It is licensed under GNU General Public License version 3 or later. + * + * For the full copyright and license information, please read the + * GPL3-License.txt file that was distributed with this source code. + */ + +package org.kitodo.test.utils; + +/** + * This class is used to group constants used in various tests in one place from which they can be retrieved. + */ +public class TestConstants { + + public static final String MODS = "mods"; + public static final String GBV = "GBV"; + public static final String K10PLUS = "K10Plus"; + public static final String KALLIOPE = "Kalliope"; + public static final String PPN = "PPN"; + public static final String PARENT_RECORD_PATH = "src/test/resources/importRecords/importParentRecord.xml"; + public static final String NUMBER_OF_CHILD_RECORDS_PATH = "src/test/resources/importRecords/sruNumberOfChildRecords.xml"; + public static final String MULTIPLE_CHILD_RECORDS_PATH = "src/test/resources/importRecords/importMultipleChildRecords.xml"; + public static final String CHILD_RECORD_PATH = "src/test/resources/importRecords/importChildRecord.xml"; + public static final String HITLIST_RECORD_PATH = "src/test/resources/importRecords/importHitlist.xml"; + public static final String EAD_ID = "ead.id"; + public static final String EAD_PARENT_ID = "context.ead.id"; + public static final String KALLIOPE_PARENT_ID = "DE-1234-BE-56-7890"; + public static final String IDENTIFIER = "Identifier"; + public static final String VALUE = "value"; +} diff --git a/Kitodo/src/test/resources/importRecords/importMultipleChildRecords.xml b/Kitodo/src/test/resources/importRecords/importMultipleChildRecords.xml index c61c35a2d3d..d7cf64a7486 100644 --- a/Kitodo/src/test/resources/importRecords/importMultipleChildRecords.xml +++ b/Kitodo/src/test/resources/importRecords/importMultipleChildRecords.xml @@ -25,11 +25,15 @@ 9991 - 123123 + DE-1234-BE-56-7890 Child process 1 + Volume + + single unit + @@ -45,11 +49,15 @@ 9992 - 123123 + DE-1234-BE-56-7890 Child process 2 + Volume + + single unit + @@ -65,11 +73,15 @@ 9993 - 123123 + DE-1234-BE-56-7890 Child process 3 + Volume + + single unit + diff --git a/Kitodo/src/test/resources/importRecords/importParentRecord.xml b/Kitodo/src/test/resources/importRecords/importParentRecord.xml index 479023e92ec..0a4c3027003 100644 --- a/Kitodo/src/test/resources/importRecords/importParentRecord.xml +++ b/Kitodo/src/test/resources/importRecords/importParentRecord.xml @@ -23,13 +23,16 @@ xsi:schemaLocation="http://www.loc.gov/mods/v3 http://www.loc.gov/standards/mods/v3/mods-3-4.xsd"> http://www.test.id - 2 + DE-1234-BE-56-7890 2019-11-09 2019-11-09 Elternvorgang + + multipart monograph + diff --git a/Kitodo/src/test/resources/importRecords/sruNumberOfChildRecords.xml b/Kitodo/src/test/resources/importRecords/sruNumberOfChildRecords.xml new file mode 100644 index 00000000000..b09398419fb --- /dev/null +++ b/Kitodo/src/test/resources/importRecords/sruNumberOfChildRecords.xml @@ -0,0 +1,22 @@ + + + + 1.2 + 3 + + 1.2 + pica.ppn=11111 + 0 + xml + mods + + diff --git a/Kitodo/src/test/resources/rulesets/ruleset_test.xml b/Kitodo/src/test/resources/rulesets/ruleset_test.xml index 19ec16d3e95..ab70eded5ec 100644 --- a/Kitodo/src/test/resources/rulesets/ruleset_test.xml +++ b/Kitodo/src/test/resources/rulesets/ruleset_test.xml @@ -46,6 +46,9 @@ + + + diff --git a/Kitodo/src/test/resources/xslt/mods2kitodo.xsl b/Kitodo/src/test/resources/xslt/mods2kitodo.xsl index 45a18373d77..b3ef2deac8b 100644 --- a/Kitodo/src/test/resources/xslt/mods2kitodo.xsl +++ b/Kitodo/src/test/resources/xslt/mods2kitodo.xsl @@ -27,7 +27,7 @@ - +