From 4b148ffaa9a634adcae05f1db7b391d04db0d7e7 Mon Sep 17 00:00:00 2001
From: vkovtun <vkovtun@ethz.ch>
Date: Thu, 17 Aug 2023 17:13:19 +0200
Subject: [PATCH] SSDM-13926: Writing tests for import/export API. Added tests
 for import options.

---
 .../systemtest/asapi/v3/ImportTest.java       | 134 +++++++++++++++---
 .../test_files/xls/existing_vocabulary.xlsx   | Bin 0 -> 5491 bytes
 2 files changed, 118 insertions(+), 16 deletions(-)
 create mode 100644 server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/test_files/xls/existing_vocabulary.xlsx

diff --git a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/ImportTest.java b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/ImportTest.java
index c42172366a9..ef0b1eb208d 100644
--- a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/ImportTest.java
+++ b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/ImportTest.java
@@ -17,16 +17,20 @@
 package ch.ethz.sis.openbis.systemtest.asapi.v3;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
-import org.testng.annotations.BeforeClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeSuite;
 import org.testng.annotations.Test;
 
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult;
@@ -40,32 +44,31 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.Vocabulary;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.VocabularyTerm;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.fetchoptions.VocabularyFetchOptions;
 import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.search.VocabularySearchCriteria;
+import ch.systemsx.cisd.common.exceptions.UserFailureException;
 
 public class ImportTest extends AbstractTest
 {
 
+    private static final String VERSIONING_JSON = "./versioning.json";
+
+    private static final String XLS_VERSIONING_DIR = "xls-import.version-data-file";
+
     private final Map<String, String> IMPORT_SCRIPT_MAP = Map.of("Script 1", "Value 1", "Script 2", "Value 2");
 
     private final Collection<ImportScript> IMPORT_SCRIPTS = IMPORT_SCRIPT_MAP.entrySet().stream()
             .map(entry -> new ImportScript(entry.getKey(), entry.getValue())).collect(Collectors.toList());
 
-    private static byte[] fileContent;
 
-    @BeforeClass
-    public void setupClass()
+    @BeforeSuite
+    public void setupSuite()
     {
-        try (final InputStream is = ImportTest.class.getResourceAsStream("test_files/xls/import.xlsx"))
-        {
-            if (is == null)
-            {
-                throw new RuntimeException();
-            }
+        System.setProperty(XLS_VERSIONING_DIR, VERSIONING_JSON);
+    }
 
-            fileContent = is.readAllBytes();
-        } catch (final IOException e)
-        {
-            throw new RuntimeException(e);
-        }
+    @AfterMethod
+    public void afterTest()
+    {
+        new File(VERSIONING_JSON).delete();
     }
 
     @Test
@@ -73,7 +76,7 @@ public class ImportTest extends AbstractTest
     {
         final String sessionToken = v3api.login(TEST_USER, PASSWORD);
 
-        final ImportData importData = new UncompressedImportData(ImportFormat.XLS, fileContent, IMPORT_SCRIPTS);
+        final ImportData importData = new UncompressedImportData(ImportFormat.XLS, getFileContent("import.xlsx"), IMPORT_SCRIPTS);
         final ImportOptions importOptions = new ImportOptions(ImportMode.UPDATE_IF_EXISTS);
 
         v3api.executeImport(sessionToken, importData, importOptions);
@@ -92,6 +95,105 @@ public class ImportTest extends AbstractTest
         final List<VocabularyTerm> vocabularyTerms = vocabularySearchResult.getObjects().get(0).getTerms();
         assertEquals(2, vocabularyTerms.size());
         assertEquals(Set.of("HRP", "AAA"), vocabularyTerms.stream().map(VocabularyTerm::getCode).collect(Collectors.toSet()));
+
+        v3api.logout(sessionToken);
+    }
+
+    @Test
+    public void testImportOptionsUpdateIfExists()
+    {
+        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        final ImportData importData = new UncompressedImportData(ImportFormat.XLS, getFileContent("existing_vocabulary.xlsx"), IMPORT_SCRIPTS);
+        final ImportOptions importOptions = new ImportOptions(ImportMode.UPDATE_IF_EXISTS);
+
+        v3api.executeImport(sessionToken, importData, importOptions);
+
+        final VocabularySearchCriteria vocabularySearchCriteria = new VocabularySearchCriteria();
+        vocabularySearchCriteria.withCode().thatEquals("TEST_VOCABULARY");
+
+        final VocabularyFetchOptions vocabularyFetchOptions = new VocabularyFetchOptions();
+        vocabularyFetchOptions.withTerms();
+
+        final SearchResult<Vocabulary> vocabularySearchResult =
+                v3api.searchVocabularies(sessionToken, vocabularySearchCriteria, vocabularyFetchOptions);
+
+        assertEquals(1, vocabularySearchResult.getTotalCount());
+        assertEquals("Test vocabulary with modifications", vocabularySearchResult.getObjects().get(0).getDescription());
+
+        final List<VocabularyTerm> vocabularyTerms = vocabularySearchResult.getObjects().get(0).getTerms();
+        assertEquals(3, vocabularyTerms.size());
+        assertEquals(Set.of("TEST_TERM_A", "TEST_TERM_B", "TEST_TERM_C"), vocabularyTerms.stream().map(VocabularyTerm::getCode)
+                .collect(Collectors.toSet()));
+        assertEquals(Set.of("Test term A", "Test term B", "Test term C"), vocabularyTerms.stream().map(VocabularyTerm::getLabel)
+                .collect(Collectors.toSet()));
+
+        v3api.logout(sessionToken);
+    }
+
+    @Test
+    public void testImportOptionsIgnoreExisting()
+    {
+        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        final ImportData importData = new UncompressedImportData(ImportFormat.XLS, getFileContent("existing_vocabulary.xlsx"), IMPORT_SCRIPTS);
+        final ImportOptions importOptions = new ImportOptions(ImportMode.IGNORE_EXISTING);
+
+        v3api.executeImport(sessionToken, importData, importOptions);
+
+        final VocabularySearchCriteria vocabularySearchCriteria = new VocabularySearchCriteria();
+        vocabularySearchCriteria.withCode().thatEquals("TEST_VOCABULARY");
+
+        final VocabularyFetchOptions vocabularyFetchOptions = new VocabularyFetchOptions();
+        vocabularyFetchOptions.withTerms();
+
+        final SearchResult<Vocabulary> vocabularySearchResult =
+                v3api.searchVocabularies(sessionToken, vocabularySearchCriteria, vocabularyFetchOptions);
+
+        assertEquals(1, vocabularySearchResult.getTotalCount());
+        assertEquals("Test vocabulary", vocabularySearchResult.getObjects().get(0).getDescription());
+
+        final List<VocabularyTerm> vocabularyTerms = vocabularySearchResult.getObjects().get(0).getTerms();
+        assertEquals(3, vocabularyTerms.size());
+        assertEquals(Set.of("TEST_TERM_A", "TEST_TERM_B", "TEST_TERM_C"), vocabularyTerms.stream().map(VocabularyTerm::getCode)
+                .collect(Collectors.toSet()));
+        final List<String> descriptions = vocabularyTerms.stream().map(VocabularyTerm::getLabel).collect(Collectors.toList());
+        assertTrue(descriptions.containsAll(Arrays.asList(null, null, "Test term C")));
+
+        v3api.logout(sessionToken);
+    }
+
+    @Test(expectedExceptions = UserFailureException.class, expectedExceptionsMessageRegExp = ".*FAIL_IF_EXISTS.*")
+    public void testImportOptionsFailIfExists()
+    {
+        final String sessionToken = v3api.login(TEST_USER, PASSWORD);
+
+        final ImportData importData = new UncompressedImportData(ImportFormat.XLS, getFileContent("existing_vocabulary.xlsx"), IMPORT_SCRIPTS);
+        final ImportOptions importOptions = new ImportOptions(ImportMode.FAIL_IF_EXISTS);
+
+        try
+        {
+            v3api.executeImport(sessionToken, importData, importOptions);
+        } finally
+        {
+            v3api.logout(sessionToken);
+        }
+    }
+
+    private byte[] getFileContent(final String fileName)
+    {
+        try (final InputStream is = ImportTest.class.getResourceAsStream("test_files/xls/" + fileName))
+        {
+            if (is == null)
+            {
+                throw new RuntimeException();
+            }
+
+            return is.readAllBytes();
+        } catch (final IOException e)
+        {
+            throw new RuntimeException(e);
+        }
     }
 
 }
diff --git a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/test_files/xls/existing_vocabulary.xlsx b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/test_files/xls/existing_vocabulary.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..0b82cff49202e265e4f63977531911520d628eb1
GIT binary patch
literal 5491
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

literal 0
HcmV?d00001

-- 
GitLab