diff --git a/data-prepper-plugins/saas-source-plugins/jira-source/src/main/java/org/opensearch/dataprepper/plugins/source/jira/JiraClient.java b/data-prepper-plugins/saas-source-plugins/jira-source/src/main/java/org/opensearch/dataprepper/plugins/source/jira/JiraClient.java index 3c1529b274..6af72715ff 100644 --- a/data-prepper-plugins/saas-source-plugins/jira-source/src/main/java/org/opensearch/dataprepper/plugins/source/jira/JiraClient.java +++ b/data-prepper-plugins/saas-source-plugins/jira-source/src/main/java/org/opensearch/dataprepper/plugins/source/jira/JiraClient.java @@ -55,7 +55,6 @@ public JiraClient(JiraService service, this.configuration = sourceConfig; } - @Override public Iterator listItems() { jiraIterator.initialize(lastPollTime); diff --git a/data-prepper-plugins/saas-source-plugins/jira-source/src/main/java/org/opensearch/dataprepper/plugins/source/jira/JiraIterator.java b/data-prepper-plugins/saas-source-plugins/jira-source/src/main/java/org/opensearch/dataprepper/plugins/source/jira/JiraIterator.java index bf9546acdf..e650277abd 100644 --- a/data-prepper-plugins/saas-source-plugins/jira-source/src/main/java/org/opensearch/dataprepper/plugins/source/jira/JiraIterator.java +++ b/data-prepper-plugins/saas-source-plugins/jira-source/src/main/java/org/opensearch/dataprepper/plugins/source/jira/JiraIterator.java @@ -11,7 +11,6 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import java.util.Objects; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; @@ -64,7 +63,7 @@ public boolean hasNext() { private boolean isCrawlerRunning() { boolean isRunning = Boolean.FALSE; - if (Objects.nonNull(futureList)) { + if (!futureList.isEmpty()) { for (Future future : futureList) { if (!future.isDone()) { isRunning = Boolean.TRUE; diff --git a/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/JiraClientTest.java b/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/JiraClientTest.java new file mode 100644 index 0000000000..4560faf24d --- /dev/null +++ b/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/JiraClientTest.java @@ -0,0 +1,110 @@ +package org.opensearch.dataprepper.plugins.source.jira; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.opensearch.dataprepper.model.buffer.Buffer; +import org.opensearch.dataprepper.model.event.Event; +import org.opensearch.dataprepper.model.record.Record; +import org.opensearch.dataprepper.plugins.source.source_crawler.base.CrawlerSourceConfig; +import org.opensearch.dataprepper.plugins.source.source_crawler.base.PluginExecutorServiceProvider; +import org.opensearch.dataprepper.plugins.source.source_crawler.coordination.state.SaasWorkerProgressState; + +import java.time.Instant; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class JiraClientTest { + + @Mock + private Buffer> buffer; + + @Mock + private SaasWorkerProgressState saasWorkerProgressState; + + @Mock + private CrawlerSourceConfig crawlerSourceConfig; + + @Mock + private JiraSourceConfig jiraSourceConfig; + + @Mock + private JiraService jiraService; + + @Mock + private JiraIterator jiraIterator; + + private PluginExecutorServiceProvider executorServiceProvider = new PluginExecutorServiceProvider(); + + @Test + void testConstructor() { + JiraClient jiraClient = new JiraClient(jiraService, jiraIterator, executorServiceProvider, jiraSourceConfig); + jiraClient.setLastPollTime(Instant.ofEpochSecond(1234L)); + assertNotNull(jiraClient); + } + + @Test + void testListItems() { + JiraClient jiraClient = new JiraClient(jiraService, jiraIterator, executorServiceProvider, jiraSourceConfig); + assertNotNull(jiraClient.listItems()); + } + + + @Test + void testExecutePartition() throws Exception { + JiraClient jiraClient = new JiraClient(jiraService, jiraIterator, executorServiceProvider, jiraSourceConfig); + Map keyAttributes = new HashMap<>(); + keyAttributes.put("project", "test"); + when(saasWorkerProgressState.getKeyAttributes()).thenReturn(keyAttributes); + List itemIds = List.of("ID1", "ID2", "ID3", "ID4"); + when(saasWorkerProgressState.getItemIds()).thenReturn(itemIds); + Instant exportStartTime = Instant.now(); + when(saasWorkerProgressState.getExportStartTime()).thenReturn(Instant.ofEpochSecond(exportStartTime.toEpochMilli())); + + when(jiraService.getIssue(anyString())).thenReturn("{\"id\":\"ID1\",\"key\":\"TEST-1\"}"); + + ArgumentCaptor>> recordsCaptor = ArgumentCaptor.forClass((Class) Collection.class); + + jiraClient.executePartition(saasWorkerProgressState, buffer, crawlerSourceConfig); + + verify(buffer).writeAll(recordsCaptor.capture(), anyInt()); + Collection> capturedRecords = recordsCaptor.getValue(); + assertFalse(capturedRecords.isEmpty()); + for (Record record : capturedRecords) { + assertNotNull(record.getData()); + } + } + + @Test + void bufferWriteRuntimeTest() throws Exception { + JiraClient jiraClient = new JiraClient(jiraService, jiraIterator, executorServiceProvider, jiraSourceConfig); + Map keyAttributes = new HashMap<>(); + keyAttributes.put("project", "test"); + when(saasWorkerProgressState.getKeyAttributes()).thenReturn(keyAttributes); + List itemIds = List.of("ID1", "ID2", "ID3", "ID4"); + when(saasWorkerProgressState.getItemIds()).thenReturn(itemIds); + Instant exportStartTime = Instant.now(); + when(saasWorkerProgressState.getExportStartTime()).thenReturn(Instant.ofEpochSecond(exportStartTime.toEpochMilli())); + + when(jiraService.getIssue(anyString())).thenReturn("{\"id\":\"ID1\",\"key\":\"TEST-1\"}"); + + ArgumentCaptor>> recordsCaptor = ArgumentCaptor.forClass((Class) Collection.class); + + doThrow(new RuntimeException()).when(buffer).writeAll(recordsCaptor.capture(), anyInt()); + assertThrows(RuntimeException.class, () -> jiraClient.executePartition(saasWorkerProgressState, buffer, crawlerSourceConfig)); + } +} \ No newline at end of file diff --git a/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/JiraConfigHelperTest.java b/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/JiraConfigHelperTest.java new file mode 100644 index 0000000000..6db4684efc --- /dev/null +++ b/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/JiraConfigHelperTest.java @@ -0,0 +1,112 @@ +package org.opensearch.dataprepper.plugins.source.jira; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.opensearch.dataprepper.plugins.source.jira.utils.JiraConfigHelper; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; +import static org.opensearch.dataprepper.plugins.source.jira.utils.Constants.BASIC; +import static org.opensearch.dataprepper.plugins.source.jira.utils.Constants.OAUTH2; +import static org.opensearch.dataprepper.plugins.source.jira.utils.JiraConfigHelper.ISSUE_STATUS_FILTER; +import static org.opensearch.dataprepper.plugins.source.jira.utils.JiraConfigHelper.ISSUE_TYPE_FILTER; + +@ExtendWith(MockitoExtension.class) +public class JiraConfigHelperTest { + + @Mock + JiraSourceConfig jiraSourceConfig; + + @Test + void testInitialization() { + JiraConfigHelper jiraConfigHelper = new JiraConfigHelper(); + assertNotNull(jiraConfigHelper); + } + + @Test + void testIssueTypeFilter() { + testGetIssue(ISSUE_TYPE_FILTER); + } + + @Test + void testIssueStatusFilter() { + testGetIssue(ISSUE_STATUS_FILTER); + } + + private void testGetIssue(String filter) { + List issueTypeFilter = List.of("Bug", "Task"); + when(jiraSourceConfig.getAdditionalProperties()).thenReturn( + Map.of(filter, issueTypeFilter) + ); + List result = null; + if (filter.equals(ISSUE_TYPE_FILTER)) { + result = JiraConfigHelper.getIssueTypeFilter(jiraSourceConfig); + } else if (filter.equals(ISSUE_STATUS_FILTER)) { + result = JiraConfigHelper.getIssueStatusFilter(jiraSourceConfig); + } + assertEquals(issueTypeFilter, result); + } + + + @Test + void testGetProjectKeyFilter() { + assertTrue(JiraConfigHelper.getProjectKeyFilter(jiraSourceConfig).isEmpty()); + List projectKeyFilter = List.of("TEST", "TEST2"); + when(jiraSourceConfig.getProject()).thenReturn(projectKeyFilter); + assertEquals(projectKeyFilter, JiraConfigHelper.getProjectKeyFilter(jiraSourceConfig)); + } + + @Test + void testValidateConfig() { + assertThrows(RuntimeException.class, () -> JiraConfigHelper.validateConfig(jiraSourceConfig)); + + when(jiraSourceConfig.getAccountUrl()).thenReturn("https://test.com"); + assertThrows(RuntimeException.class, () -> JiraConfigHelper.validateConfig(jiraSourceConfig)); + + when(jiraSourceConfig.getAuthType()).thenReturn("fakeType"); + assertThrows(RuntimeException.class, () -> JiraConfigHelper.validateConfig(jiraSourceConfig)); + } + + @Test + void testValidateConfigBasic() { + when(jiraSourceConfig.getAccountUrl()).thenReturn("https://test.com"); + when(jiraSourceConfig.getAuthType()).thenReturn(BASIC); + assertThrows(RuntimeException.class, () -> JiraConfigHelper.validateConfig(jiraSourceConfig)); + + when(jiraSourceConfig.getJiraId()).thenReturn("id"); + assertThrows(RuntimeException.class, () -> JiraConfigHelper.validateConfig(jiraSourceConfig)); + + when(jiraSourceConfig.getJiraCredential()).thenReturn("credential"); + when(jiraSourceConfig.getJiraId()).thenReturn(null); + assertThrows(RuntimeException.class, () -> JiraConfigHelper.validateConfig(jiraSourceConfig)); + + when(jiraSourceConfig.getJiraId()).thenReturn("id"); + assertDoesNotThrow(() -> JiraConfigHelper.validateConfig(jiraSourceConfig)); + } + + @Test + void testValidateConfigOauth2() { + when(jiraSourceConfig.getAccountUrl()).thenReturn("https://test.com"); + when(jiraSourceConfig.getAuthType()).thenReturn(OAUTH2); + assertThrows(RuntimeException.class, () -> JiraConfigHelper.validateConfig(jiraSourceConfig)); + + when(jiraSourceConfig.getAccessToken()).thenReturn("id"); + assertThrows(RuntimeException.class, () -> JiraConfigHelper.validateConfig(jiraSourceConfig)); + + when(jiraSourceConfig.getRefreshToken()).thenReturn("credential"); + when(jiraSourceConfig.getAccessToken()).thenReturn(null); + assertThrows(RuntimeException.class, () -> JiraConfigHelper.validateConfig(jiraSourceConfig)); + + when(jiraSourceConfig.getAccessToken()).thenReturn("id"); + assertDoesNotThrow(() -> JiraConfigHelper.validateConfig(jiraSourceConfig)); + } +} diff --git a/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/JiraItemInfoTest.java b/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/JiraItemInfoTest.java new file mode 100644 index 0000000000..ba42065397 --- /dev/null +++ b/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/JiraItemInfoTest.java @@ -0,0 +1,97 @@ +package org.opensearch.dataprepper.plugins.source.jira; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.Instant; +import java.util.Map; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; +import static org.opensearch.dataprepper.plugins.source.jira.JiraItemInfo.PROJECT; + +@ExtendWith(MockitoExtension.class) +public class JiraItemInfoTest { + private String project; + private String issueType; + private String id; + private String itemId; + private Instant eventTime; + + @Mock + private Map metadata; + + @Mock + private Map newMetadata; + + @Mock + private JiraItemInfo jiraItemInfo; + + @BeforeEach + void setUP() { + issueType = "TestIssue"; + id = UUID.randomUUID().toString(); + project = "TestProject"; + itemId = UUID.randomUUID().toString(); + eventTime = Instant.ofEpochSecond(0); + jiraItemInfo = new JiraItemInfo(id, itemId, project, issueType, metadata, eventTime); + } + + @Test + void testGetters() { + assertEquals(jiraItemInfo.getItemId(), itemId); + assertEquals(jiraItemInfo.getId(), id); + assertEquals(jiraItemInfo.getProject(), project); + assertEquals(jiraItemInfo.getIssueType(), issueType); + assertEquals(jiraItemInfo.getMetadata(), metadata); + assertEquals(jiraItemInfo.getEventTime(), eventTime); + } + + @Test + void testSetter() { + jiraItemInfo.setEventTime(Instant.now()); + assertNotEquals(jiraItemInfo.getEventTime(), eventTime); + jiraItemInfo.setItemId("newItemID"); + assertNotEquals(jiraItemInfo.getItemId(), itemId); + jiraItemInfo.setId("newID"); + assertNotEquals(jiraItemInfo.getId(), id); + jiraItemInfo.setProject("newProject"); + assertNotEquals(jiraItemInfo.getProject(), project); + jiraItemInfo.setMetadata(newMetadata); + assertNotEquals(jiraItemInfo.getMetadata(), metadata); + jiraItemInfo.setIssueType("newIssueType"); + assertNotEquals(jiraItemInfo.getIssueType(), issueType); + + } + + @Test + void testGetPartitionKey() { + String partitionKey = jiraItemInfo.getPartitionKey(); + assertTrue(partitionKey.contains(project)); + assertTrue(partitionKey.contains(issueType)); + } + + @Test + void testGetKeyAttributes() { + assertNotNull(jiraItemInfo.getKeyAttributes().get(PROJECT)); + } + + @Test + void testGetLastModifiedAt() { + when(metadata.getOrDefault("updated", "0")).thenReturn("5"); + when(metadata.getOrDefault("created", "0")).thenReturn("0"); + assertEquals(Instant.ofEpochMilli(5), jiraItemInfo.getLastModifiedAt()); + + when(metadata.getOrDefault("updated", "0")).thenReturn("5"); + when(metadata.getOrDefault("created", "0")).thenReturn("7"); + assertEquals(Instant.ofEpochMilli(7), jiraItemInfo.getLastModifiedAt()); + } + +} diff --git a/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/JiraIteratorTest.java b/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/JiraIteratorTest.java new file mode 100644 index 0000000000..78cd32f0cd --- /dev/null +++ b/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/JiraIteratorTest.java @@ -0,0 +1,154 @@ +package org.opensearch.dataprepper.plugins.source.jira; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.opensearch.dataprepper.plugins.source.jira.models.IssueBean; +import org.opensearch.dataprepper.plugins.source.jira.models.SearchResults; +import org.opensearch.dataprepper.plugins.source.jira.rest.auth.JiraAuthConfig; +import org.opensearch.dataprepper.plugins.source.source_crawler.base.PluginExecutorServiceProvider; +import org.springframework.web.client.RestTemplate; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.opensearch.dataprepper.plugins.source.jira.utils.Constants.CREATED; +import static org.opensearch.dataprepper.plugins.source.jira.utils.Constants.KEY; +import static org.opensearch.dataprepper.plugins.source.jira.utils.Constants.UPDATED; + +@ExtendWith(MockitoExtension.class) +public class JiraIteratorTest { + + @Mock + private RestTemplate restTemplate; + + @Mock + private SearchResults mockSearchResults; + + @Mock + private JiraAuthConfig authConfig; + + private JiraService jiraService; + + private PluginExecutorServiceProvider executorServiceProvider = new PluginExecutorServiceProvider(); + + @Mock + private JiraSourceConfig jiraSourceConfig; + + private JiraIterator jiraIterator; + + @BeforeEach + void setUp() { + jiraService = spy(new JiraService(restTemplate, jiraSourceConfig, authConfig)); + } + + public JiraIterator createObjectUnderTest() { + return new JiraIterator(jiraService, executorServiceProvider, jiraSourceConfig); + } + + @Test + void testInitialization() { + jiraIterator = createObjectUnderTest(); + assertNotNull(jiraIterator); + jiraIterator.initialize(Instant.ofEpochSecond(0)); + assertFalse(jiraIterator.hasNext()); + assertFalse(jiraIterator.hasNext()); + } + + @Test + void sleepInterruptionTest() { + jiraIterator = createObjectUnderTest(); + jiraIterator.initialize(Instant.ofEpochSecond(0)); + + Thread testThread = new Thread(() -> { + assertThrows(InterruptedException.class, () -> { + try { + jiraIterator.hasNext(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + }); + + testThread.start(); + testThread.interrupt(); + } + + @Test + void testItemInfoQueueNotEmpty() { + jiraIterator = createObjectUnderTest(); + + List mockIssues = new ArrayList<>(); + for (int i = 0; i < 50; i++) { + IssueBean issue1 = createIssueBean(false); + mockIssues.add(issue1); + } + + when(mockSearchResults.getIssues()).thenReturn(mockIssues); + when(mockSearchResults.getTotal()).thenReturn(100); + + doReturn(mockSearchResults).when(jiraService).getAllIssues(any(StringBuilder.class), anyInt(), any(JiraSourceConfig.class)); + + jiraIterator.initialize(Instant.ofEpochSecond(0)); + assertTrue(jiraIterator.hasNext()); + assertNotNull(jiraIterator.next()); + } + + + private IssueBean createIssueBean(boolean nullFields) { + IssueBean issue1 = new IssueBean(); + issue1.setId(UUID.randomUUID().toString()); + issue1.setKey("issue_1_key"); + issue1.setSelf("https://example.com/rest/api/2/issue/123"); + issue1.setExpand("operations,versionedRepresentations,editmeta"); + + Map fieldMap = new HashMap<>(); + if (!nullFields) { + fieldMap.put(CREATED, Instant.now()); + fieldMap.put(UPDATED, Instant.now()); + } else { + fieldMap.put(CREATED, 0); + fieldMap.put(UPDATED, 0); + } + + Map issueTypeMap = new HashMap<>(); + issueTypeMap.put("name", "Task"); + issueTypeMap.put("self", "https://example.com/rest/api/2/issuetype/1"); + issueTypeMap.put("id", "1"); + fieldMap.put("issuetype", issueTypeMap); + + Map projectMap = new HashMap<>(); + if (!nullFields) { + projectMap.put("name", "project name test"); + projectMap.put(KEY, "TEST"); + } + fieldMap.put("project", projectMap); + + Map priorityMap = new HashMap<>(); + priorityMap.put("name", "Medium"); + fieldMap.put("priority", priorityMap); + + Map statusMap = new HashMap<>(); + statusMap.put("name", "In Progress"); + fieldMap.put("status", statusMap); + + issue1.setFields(fieldMap); + + return issue1; + } +} diff --git a/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/JiraServiceTest.java b/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/JiraServiceTest.java index 7ba507f844..e36f9a90a1 100644 --- a/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/JiraServiceTest.java +++ b/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/JiraServiceTest.java @@ -1,39 +1,251 @@ package org.opensearch.dataprepper.plugins.source.jira; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.opensearch.dataprepper.plugins.source.jira.models.IssueBean; +import org.opensearch.dataprepper.plugins.source.jira.models.SearchResults; import org.opensearch.dataprepper.plugins.source.jira.rest.auth.JiraAuthConfig; import org.opensearch.dataprepper.plugins.source.jira.rest.auth.JiraAuthFactory; +import org.opensearch.dataprepper.plugins.source.source_crawler.base.PluginExecutorServiceProvider; +import org.opensearch.dataprepper.plugins.source.source_crawler.model.ItemInfo; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestTemplate; import java.io.IOException; import java.io.InputStream; +import java.net.URI; +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import static org.opensearch.dataprepper.plugins.source.jira.utils.Constants.ACCESSIBLE_RESOURCES; +import static org.opensearch.dataprepper.plugins.source.jira.utils.Constants.BASIC; +import static org.opensearch.dataprepper.plugins.source.jira.utils.Constants.CREATED; +import static org.opensearch.dataprepper.plugins.source.jira.utils.Constants.KEY; +import static org.opensearch.dataprepper.plugins.source.jira.utils.Constants.UPDATED; /** * The type Jira service. */ - @ExtendWith(MockitoExtension.class) public class JiraServiceTest { - Logger log = LoggerFactory.getLogger(JiraServiceTest.class); + @Mock + private RestTemplate restTemplate; + + @Mock + private JiraAuthConfig authConfig; @Mock - RestTemplate restTemplate; + private SearchResults mockSearchResults; + + @Mock + private StringBuilder jql; + + private static final Logger log = org.slf4j.LoggerFactory.getLogger(JiraServiceTest.class); + private PluginExecutorServiceProvider executorServiceProvider = new PluginExecutorServiceProvider(); + + @AfterEach + void tearDown() { + executorServiceProvider.terminateExecutor(); + } + + @Test + void testJiraServiceInitialization() throws JsonProcessingException { + List issueType = new ArrayList<>(); + JiraSourceConfig jiraSourceConfig = createJiraConfiguration(BASIC, issueType); + JiraService jiraService = new JiraService(restTemplate, jiraSourceConfig, authConfig); + assertNotNull(jiraService); + } + + @Test + public void testGetJiraEntities() throws JsonProcessingException, InterruptedException, TimeoutException { + List issueType = new ArrayList<>(); + issueType.add("Task"); + JiraSourceConfig jiraSourceConfig = createJiraConfiguration(BASIC, issueType); + JiraService jiraService = spy(new JiraService(restTemplate, jiraSourceConfig, authConfig)); + List mockIssues = new ArrayList<>(); + IssueBean issue1 = createIssueBean(false); + mockIssues.add(issue1); + IssueBean issue2 = createIssueBean(true); + mockIssues.add(issue2); + + when(mockSearchResults.getIssues()).thenReturn(mockIssues); + when(mockSearchResults.getTotal()).thenReturn(mockIssues.size()); + + doReturn(mockSearchResults).when(jiraService).getAllIssues(any(StringBuilder.class), anyInt(), any(JiraSourceConfig.class)); + + Instant timestamp = Instant.ofEpochSecond(0); + List> futureList = new ArrayList<>(); + Queue itemInfoQueue = new ConcurrentLinkedQueue<>(); + ExecutorService crawlerTaskExecutor = executorServiceProvider.get(); + jiraService.getJiraEntities(jiraSourceConfig, timestamp, itemInfoQueue, futureList, crawlerTaskExecutor); + + waitForFutures(futureList); + + assertEquals(mockIssues.size(), itemInfoQueue.size()); + } + + @Test + public void buildIssueItemInfoMultipleFutureThreads() throws JsonProcessingException, InterruptedException, TimeoutException { + List issueType = new ArrayList<>(); + issueType.add("Task"); + JiraSourceConfig jiraSourceConfig = createJiraConfiguration(BASIC, issueType); + JiraService jiraService = spy(new JiraService(restTemplate, jiraSourceConfig, authConfig)); + List mockIssues = new ArrayList<>(); + for (int i = 0; i < 50; i++) { + IssueBean issue1 = createIssueBean(false); + mockIssues.add(issue1); + } + + when(mockSearchResults.getIssues()).thenReturn(mockIssues); + when(mockSearchResults.getTotal()).thenReturn(100); + + doReturn(mockSearchResults).when(jiraService).getAllIssues(any(StringBuilder.class), anyInt(), any(JiraSourceConfig.class)); + + Instant timestamp = Instant.ofEpochSecond(0); + List> futureList = new ArrayList<>(); + Queue itemInfoQueue = new ConcurrentLinkedQueue<>(); + ExecutorService crawlerTaskExecutor = executorServiceProvider.get(); + jiraService.getJiraEntities(jiraSourceConfig, timestamp, itemInfoQueue, futureList, crawlerTaskExecutor); + + waitForFutures(futureList); + + assertEquals(100, itemInfoQueue.size()); + } + + @Test + public void testGetJiraEntitiesException() throws JsonProcessingException { + List issueType = new ArrayList<>(); + issueType.add("Task"); + JiraSourceConfig jiraSourceConfig = createJiraConfiguration(BASIC, issueType); + JiraService jiraService = spy(new JiraService(restTemplate, jiraSourceConfig, authConfig)); + + doThrow(RuntimeException.class).when(jiraService).getAllIssues(any(StringBuilder.class), anyInt(), any(JiraSourceConfig.class)); + + Instant timestamp = Instant.ofEpochSecond(0); + List> futureList = new ArrayList<>(); + Queue itemInfoQueue = new ConcurrentLinkedQueue<>(); + ExecutorService crawlerTaskExecutor = executorServiceProvider.get(); + assertThrows(RuntimeException.class, () -> { + jiraService.getJiraEntities(jiraSourceConfig, timestamp, itemInfoQueue, futureList, crawlerTaskExecutor); + }); + } + + @Test + public void testGetAllIssuesBasic() throws JsonProcessingException { + List issueType = new ArrayList<>(); + issueType.add("Task"); + JiraSourceConfig jiraSourceConfig = createJiraConfiguration(BASIC, issueType); + JiraService jiraService = new JiraService(restTemplate, jiraSourceConfig, authConfig); + doReturn(new ResponseEntity<>(mockSearchResults, HttpStatus.OK)).when(restTemplate).getForEntity(any(URI.class), any(Class.class)); + SearchResults results = jiraService.getAllIssues(jql, 0, jiraSourceConfig); + assertNotNull(results); + } + + + private JiraSourceConfig createJiraConfiguration(String auth_type, List issueType) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + Map connectorCredentialsMap = new HashMap<>(); + connectorCredentialsMap.put("auth_type", auth_type); + + Map jiraSourceConfigMap = new HashMap<>(); + jiraSourceConfigMap.put("account_url", ACCESSIBLE_RESOURCES); + jiraSourceConfigMap.put("connector_credentials", connectorCredentialsMap); + jiraSourceConfigMap.put("issue_type", issueType); + + + String jiraSourceConfigJsonString = objectMapper.writeValueAsString(jiraSourceConfigMap); + return objectMapper.readValue(jiraSourceConfigJsonString, JiraSourceConfig.class); + } + + private IssueBean createIssueBean(boolean nullFields) { + IssueBean issue1 = new IssueBean(); + issue1.setId(UUID.randomUUID().toString()); + issue1.setKey("issue_1_key"); + issue1.setSelf("https://example.com/rest/api/2/issue/123"); + issue1.setExpand("operations,versionedRepresentations,editmeta"); + + Map fieldMap = new HashMap<>(); + if (!nullFields) { + fieldMap.put(CREATED, Instant.now()); + fieldMap.put(UPDATED, Instant.now()); + } else { + fieldMap.put(CREATED, 0); + fieldMap.put(UPDATED, 0); + } + + Map issueTypeMap = new HashMap<>(); + issueTypeMap.put("name", "Task"); + issueTypeMap.put("self", "https://example.com/rest/api/2/issuetype/1"); + issueTypeMap.put("id", "1"); + fieldMap.put("issuetype", issueTypeMap); + + Map projectMap = new HashMap<>(); + if (!nullFields) { + projectMap.put("name", "project name test"); + projectMap.put(KEY, "TEST"); + } + fieldMap.put("project", projectMap); + + Map priorityMap = new HashMap<>(); + priorityMap.put("name", "Medium"); + fieldMap.put("priority", priorityMap); + + Map statusMap = new HashMap<>(); + statusMap.put("name", "In Progress"); + fieldMap.put("status", statusMap); + + issue1.setFields(fieldMap); + + return issue1; + } + + private void waitForFutures(List> futureList) throws InterruptedException, TimeoutException { + for (Future future : futureList) { + try { + future.get(1000, TimeUnit.MILLISECONDS); + } catch (InterruptedException ie) { + log.error("Thread interrupted.", ie); + throw new InterruptedException(ie.getMessage()); + } catch (ExecutionException xe) { + log.error("The task aborted when attempting to retrieve its result.", xe); + } catch (TimeoutException te) { + log.error("Future is not done when timeout.", te); + throw new TimeoutException(te.getMessage()); + } + } + } private static InputStream getResourceAsStream(String resourceName) { InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourceName); @@ -46,7 +258,7 @@ private static InputStream getResourceAsStream(String resourceName) { @ParameterizedTest @ValueSource(strings = {"basic-auth-jira-pipeline.yaml"}) public void testFetchingJiraIssue(String configFileName) { - when(restTemplate.getForEntity(any(String.class), any(Class.class))).thenReturn(new ResponseEntity<>("", HttpStatus.OK)); + doReturn(new ResponseEntity<>("", HttpStatus.OK)).when(restTemplate).getForEntity(any(URI.class), any(Class.class)); JiraSourceConfig jiraSourceConfig = createJiraConfigurationFromYaml(configFileName); JiraAuthConfig authConfig = new JiraAuthFactory(jiraSourceConfig).getObject(); JiraService jiraService = new JiraService(restTemplate, jiraSourceConfig, authConfig); @@ -54,6 +266,7 @@ public void testFetchingJiraIssue(String configFileName) { assertNotNull(ticketDetails); } + private JiraSourceConfig createJiraConfigurationFromYaml(String fileName) { ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()); try (InputStream inputStream = getResourceAsStream(fileName)) { @@ -64,5 +277,4 @@ private JiraSourceConfig createJiraConfigurationFromYaml(String fileName) { return null; } - } diff --git a/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/JiraSourceConfigTest.java b/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/JiraSourceConfigTest.java new file mode 100644 index 0000000000..b98235e27d --- /dev/null +++ b/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/JiraSourceConfigTest.java @@ -0,0 +1,103 @@ +package org.opensearch.dataprepper.plugins.source.jira; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.opensearch.dataprepper.plugins.source.jira.utils.Constants.BASIC; +import static org.opensearch.dataprepper.plugins.source.jira.utils.Constants.OAUTH2; + +public class JiraSourceConfigTest { + private JiraSourceConfig jiraSourceConfig; + + private String accessToken = "access token test"; + private String refreshToken = "refresh token test"; + private String clientId = "client id test"; + private String clientSecret = "client secret test"; + + + private JiraSourceConfig createJiraSourceConfig(String authtype, boolean hasToken) throws JsonProcessingException { + Map configMap = new HashMap<>(); + configMap.put("account_url", "https://example.atlassian.net"); + + Map connectorCredentialMap = new HashMap<>(); + connectorCredentialMap.put("auth_type", authtype); + if (hasToken) { + connectorCredentialMap.put("access_token", accessToken); + connectorCredentialMap.put("refresh_token", refreshToken); + } else { + connectorCredentialMap.put("refresh_token", ""); + } + connectorCredentialMap.put("client_id", clientId); + connectorCredentialMap.put("client_secret", clientSecret); + + configMap.put("connector_credentials", connectorCredentialMap); + + List projectList = Arrays.asList("project1", "project2"); + configMap.put("project", projectList); + + List issueTypeList = Arrays.asList("issue type 1", "issue type 2"); + configMap.put("issue_type", issueTypeList); + + List inclusionPatternList = Arrays.asList("pattern 1", "pattern 2"); + configMap.put("inclusion_patterns", inclusionPatternList); + + List exclusionPatternList = Arrays.asList("pattern 3", "pattern 4"); + configMap.put("exclusion_patterns", exclusionPatternList); + + configMap.put("status", "Test Status"); + + ObjectMapper objectMapper = new ObjectMapper(); + String jsonConfig = objectMapper.writeValueAsString(configMap); + JiraSourceConfig config = objectMapper.readValue(jsonConfig, JiraSourceConfig.class); + return config; + } + + @Test + void testGetters() throws JsonProcessingException { + jiraSourceConfig = createJiraSourceConfig(BASIC, false); + assertNotNull(jiraSourceConfig.getInclusionPatterns()); + assertNotNull(jiraSourceConfig.getIssueType()); + assertNotNull(jiraSourceConfig.getExclusionPatterns()); + assertNotNull(jiraSourceConfig.getNumWorkers()); + assertNotNull(jiraSourceConfig.getIssueType()); + assertNotNull(jiraSourceConfig.getProject()); + assertNotNull(jiraSourceConfig.getStatus()); + assertNotNull(jiraSourceConfig.getConnectorCredentials()); + assertNotNull(jiraSourceConfig.getAccountUrl()); + assertNotNull(jiraSourceConfig.getBackOff()); + } + + @Test + void testFetchGivenOauthAttributeWrongAuthType() throws JsonProcessingException { + jiraSourceConfig = createJiraSourceConfig(BASIC, true); + assertThrows(RuntimeException.class, () -> jiraSourceConfig.getAccessToken()); + } + + @Test + void testFetchGivenOauthAtrribute() throws JsonProcessingException { + jiraSourceConfig = createJiraSourceConfig(OAUTH2, true); +// assertThrows(RuntimeException.class, () -> jiraSourceConfig.getAccessToken("unknown attribute")); + assertEquals(accessToken, jiraSourceConfig.getAccessToken()); + assertEquals(refreshToken, jiraSourceConfig.getRefreshToken()); + assertEquals(clientId, jiraSourceConfig.getClientId()); + assertEquals(clientSecret, jiraSourceConfig.getClientSecret()); + } + + @Test + void testFetchGivenOauthAtrributeMissing() throws JsonProcessingException { + jiraSourceConfig = createJiraSourceConfig(OAUTH2, false); + assertThrows(RuntimeException.class, () -> jiraSourceConfig.getAccessToken()); + assertThrows(RuntimeException.class, () -> jiraSourceConfig.getRefreshToken()); + + } + +} diff --git a/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/JiraSourceTest.java b/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/JiraSourceTest.java new file mode 100644 index 0000000000..d95235e3ee --- /dev/null +++ b/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/JiraSourceTest.java @@ -0,0 +1,45 @@ +package org.opensearch.dataprepper.plugins.source.jira; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.opensearch.dataprepper.metrics.PluginMetrics; +import org.opensearch.dataprepper.model.acknowledgements.AcknowledgementSetManager; +import org.opensearch.dataprepper.model.plugin.PluginFactory; +import org.opensearch.dataprepper.plugins.source.jira.rest.auth.JiraAuthConfig; +import org.opensearch.dataprepper.plugins.source.source_crawler.base.Crawler; +import org.opensearch.dataprepper.plugins.source.source_crawler.base.PluginExecutorServiceProvider; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@ExtendWith(MockitoExtension.class) +public class JiraSourceTest { + + @Mock + private PluginMetrics pluginMetrics; + + @Mock + private JiraSourceConfig jiraSourceConfig; + + @Mock + private JiraAuthConfig jiraOauthConfig; + + @Mock + private PluginFactory pluginFactory; + + @Mock + private AcknowledgementSetManager acknowledgementSetManager; + + + @Mock + private Crawler crawler; + + private PluginExecutorServiceProvider executorServiceProvider = new PluginExecutorServiceProvider(); + + @Test + void initialization() { + JiraSource source = new JiraSource(pluginMetrics, jiraSourceConfig, jiraOauthConfig, pluginFactory, acknowledgementSetManager, crawler, executorServiceProvider); + assertNotNull(source); + } +} diff --git a/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/models/SearchResultsTest.java b/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/models/SearchResultsTest.java index 24d42eabe3..3a569ca39d 100644 --- a/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/models/SearchResultsTest.java +++ b/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/models/SearchResultsTest.java @@ -5,11 +5,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -17,16 +15,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; @ExtendWith(MockitoExtension.class) public class SearchResultsTest { private final ObjectMapper objectMapper = new ObjectMapper(); - @Mock - private Map names; - private SearchResults searchResults; @BeforeEach @@ -59,18 +53,16 @@ public void testGetters() throws JsonProcessingException { issue2.setId("issue 2"); testIssues.add(issue1); testIssues.add(issue2); - List testWarnings = Arrays.asList("Warning1", "Warning2"); - Map map = new HashMap<>(); - map.put("expand", expand); - map.put("startAt", startAt); - map.put("maxResults", maxResults); - map.put("total", total); - map.put("issues", testIssues); - map.put("warningMessages", testWarnings); - map.put("names", names); - String jsonString = objectMapper.writeValueAsString(map); + Map searchResultsMap = new HashMap<>(); + searchResultsMap.put("expand", expand); + searchResultsMap.put("startAt", startAt); + searchResultsMap.put("maxResults", maxResults); + searchResultsMap.put("total", total); + searchResultsMap.put("issues", testIssues); + + String jsonString = objectMapper.writeValueAsString(searchResultsMap); searchResults = objectMapper.readValue(jsonString, SearchResults.class); @@ -79,12 +71,10 @@ public void testGetters() throws JsonProcessingException { assertEquals(searchResults.getMaxResults(), maxResults); assertEquals(searchResults.getTotal(), total); - List returnedIssues = searchResults.getIssues(); assertNotNull(returnedIssues); assertEquals(testIssues.size(), returnedIssues.size()); - // Compare each issue's properties for (int i = 0; i < testIssues.size(); i++) { IssueBean originalIssue = testIssues.get(i); IssueBean returnedIssue = returnedIssues.get(i); @@ -93,20 +83,5 @@ public void testGetters() throws JsonProcessingException { } } - @Test - public void testToString() throws JsonProcessingException { - String state = "{\"expand\": \"same\"}"; - searchResults = objectMapper.readValue(state, SearchResults.class); - String jsonString = searchResults.toString(); - System.out.print(jsonString); - assertTrue(jsonString.contains("expand: same")); - assertTrue(jsonString.contains("startAt: null")); - assertTrue(jsonString.contains("maxResults: null")); - assertTrue(jsonString.contains("total: null")); - assertTrue(jsonString.contains("ISSUE")); - assertTrue(jsonString.contains("warningMessages")); - assertTrue(jsonString.contains("name")); - assertTrue(jsonString.contains("schema")); - } } diff --git a/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/rest/auth/JiraBasicAuthConfigTest.java b/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/rest/auth/JiraBasicAuthConfigTest.java new file mode 100644 index 0000000000..0028525127 --- /dev/null +++ b/data-prepper-plugins/saas-source-plugins/jira-source/src/test/java/org/opensearch/dataprepper/plugins/source/jira/rest/auth/JiraBasicAuthConfigTest.java @@ -0,0 +1,35 @@ +package org.opensearch.dataprepper.plugins.source.jira.rest.auth; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.opensearch.dataprepper.plugins.source.jira.JiraSourceConfig; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@ExtendWith(MockitoExtension.class) +public class JiraBasicAuthConfigTest { + + @Mock + private JiraSourceConfig jiraSourceConfig; + + private JiraBasicAuthConfig jiraBasicAuthConfig; + + @BeforeEach + void setUp() { + jiraBasicAuthConfig = new JiraBasicAuthConfig(jiraSourceConfig); + } + + @Test + void testGetUrl() { + assertEquals(jiraBasicAuthConfig.getUrl(), jiraSourceConfig.getAccountUrl()); + } + + @Test + void DoNothingForBasicAuthentication() { + jiraBasicAuthConfig.initCredentials(); + jiraBasicAuthConfig.renewCredentials(); + } +} \ No newline at end of file