package jalview.ws2.actions.alignment; import static jalview.testutils.Matchers.matchesSequenceString; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.hasProperty; import static org.hamcrest.Matchers.is; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.IOException; import java.net.URL; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.mockito.ArgumentCaptor; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import jalview.datamodel.Alignment; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceI; import jalview.gui.AlignViewport; import jalview.viewmodel.AlignmentViewport; import jalview.ws.params.ParamDatastoreI; import jalview.ws2.actions.PollingTaskExecutor; import jalview.ws2.actions.api.TaskEventListener; import jalview.ws2.api.Credentials; import jalview.ws2.api.JobStatus; import jalview.ws2.api.WebService; import jalview.ws2.api.WebServiceJobHandle; import jalview.ws2.client.api.AlignmentWebServiceClientI; public class AlignmentActionTest { protected AlignmentWebServiceClientI mockClient; protected AlignmentAction.Builder actionBuilder; protected WebServiceJobHandle jobRef; @BeforeMethod(alwaysRun = true) public void setupMockClient() throws IOException { jobRef = new WebServiceJobHandle( "mock", "mock", "http://example.org", "00000001"); mockClient = mock(AlignmentWebServiceClientI.class); when(mockClient.getUrl()).thenReturn("http://example.org"); when(mockClient.getClientName()).thenReturn("mock"); when(mockClient.submit(anyList(), anyList(), any())).thenReturn(jobRef); when(mockClient.getLog(jobRef)).thenReturn(""); when(mockClient.getErrorLog(jobRef)).thenReturn(""); doThrow(new UnsupportedOperationException()).when(mockClient).cancel(any()); } @BeforeMethod(alwaysRun = true, dependsOnMethods = { "setupMockClient" }) public void setupActionBuilder() throws IOException { actionBuilder = AlignmentAction.newBuilder(mockClient); actionBuilder.name("mock"); actionBuilder.webService( WebService. newBuilder() .url(new URL("http://example.org")) .clientName("mock") .category("Alignment") .name("mock") .paramDatastore(mock(ParamDatastoreI.class)) .actionClass(AlignmentAction.class) .build()); } @DataProvider public Object[][] multipleSequencesUnalignedAndAligned() { return new Object[][] { { new Alignment(new SequenceI[] { new Sequence("Seq 1", "----ASTVLITOPDCMMQEGGST-"), new Sequence("Seq 2", "-ASCGLITO------MMQEGGST-"), new Sequence("Seq 3", "AS--TVL--OPDTMMQEL------") }), new Alignment(new SequenceI[] { new Sequence("Sequence0", "ASTV-LITOPDCMMQEGGST----"), new Sequence("Sequence1", "ASC-GLITO---MMQEGGST----"), new Sequence("Sequence2", "ASTV-L--OPDTMMQE--L-----") }) } }; } @Test( groups = { "Functional" }, dataProvider = "multipleSequencesUnalignedAndAligned") public void submitSequences_verifySequenceNamesUniquified( Alignment unaligned, Alignment aligned) throws IOException { var viewport = new AlignViewport(unaligned); when(mockClient.getAlignment(jobRef)).thenReturn(aligned); when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED); actionBuilder.submitGaps(false); performAction(viewport, actionBuilder.build()); ArgumentCaptor> argument = ArgumentCaptor.forClass(List.class); verify(mockClient).submit(argument.capture(), eq(List.of()), eq(Credentials.empty())); assertThat(argument.getValue(), contains(hasProperty("name", is("Sequence0")), hasProperty("name", is("Sequence1")), hasProperty("name", is("Sequence2")))); } @Test( groups = { "Functional" }, dataProvider = "multipleSequencesUnalignedAndAligned") public void submitSequences_submitGapsOff_verifySequencesSubmittedWithoutGaps(Alignment unaligned, Alignment aligned) throws IOException { var viewport = new AlignViewport(unaligned); actionBuilder.submitGaps(false); when(mockClient.getAlignment(jobRef)).thenReturn(aligned); when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED); performAction(viewport, actionBuilder.build()); ArgumentCaptor> argument = ArgumentCaptor.forClass(List.class); verify(mockClient).submit(argument.capture(), eq(List.of()), eq(Credentials.empty())); assertThat(argument.getValue(), contains( matchesSequenceString("ASTVLITOPDCMMQEGGST"), matchesSequenceString("ASCGLITOMMQEGGST"), matchesSequenceString("ASTVLOPDTMMQEL"))); } @Test( groups = { "Functional" }, dataProvider = "multipleSequencesUnalignedAndAligned") public void submitSequences_submitGapsOn_verifySequencesSubmittedWithGaps( Alignment unaligned, Alignment aligned) throws IOException { var viewport = new AlignViewport(unaligned); actionBuilder.submitGaps(true); when(mockClient.getAlignment(jobRef)).thenReturn(aligned); when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED); performAction(viewport, actionBuilder.build()); ArgumentCaptor> argument = ArgumentCaptor.forClass(List.class); verify(mockClient).submit(argument.capture(), eq(List.of()), eq(Credentials.empty())); assertThat(argument.getValue(), contains( matchesSequenceString("----ASTVLITOPDCMMQEGGST-"), matchesSequenceString("-ASCGLITO------MMQEGGST-"), matchesSequenceString("AS--TVL--OPDTMMQEL------"))); } @Test( groups = { "Functional" }, dataProvider = "multipleSequencesUnalignedAndAligned") public void retrieveResult_verifySequencesAligned( Alignment unaligned, Alignment aligned) throws IOException { var viewport = new AlignViewport(unaligned); actionBuilder.submitGaps(false); when(mockClient.getAlignment(jobRef)).thenReturn(aligned); when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED); var mockListener = performAction(viewport, actionBuilder.build()); var argument = ArgumentCaptor.forClass(AlignmentResult.class); verify(mockListener).taskCompleted(any(), argument.capture()); var alignmentResult = argument.getValue().getAlignment(); assertThat(alignmentResult, hasProperty("sequences", contains( matchesSequenceString("ASTV-LITOPDCMMQEGGST----"), matchesSequenceString("ASC-GLITO---MMQEGGST----"), matchesSequenceString("ASTV-L--OPDTMMQE--L-----")))); } protected TaskEventListener performAction( AlignmentViewport viewport, AlignmentAction action) throws IOException { TaskEventListener listener = mock(TaskEventListener.class); var latch = new CountDownLatch(1); doAnswer(invocation -> { latch.countDown(); return null; }) .when(listener).taskCompleted(any(), any()); var executor = PollingTaskExecutor.fromPool(viewport.getServiceExecutor()); var task = action.createTask(viewport, List.of(), Credentials.empty()); task.addTaskEventListener(listener); var cancellable = executor.submit(task); try { latch.await(100, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { cancellable.cancel(true); } return listener; } } class AlignmentActionListenerNotifiedTest extends AlignmentActionTest { private AlignViewport viewport; @BeforeMethod(alwaysRun = true) public void setupViewport() { viewport = new AlignViewport(new Alignment(new SequenceI[] { new Sequence("Seq 1", "----ASTVLITOPDCMMQEGGST-"), new Sequence("Seq 2", "-ASCGLITO------MMQEGGST-"), new Sequence("Seq 3", "AS--TVL--OPDTMMQEL------") })); } @DataProvider public JobStatus[] jobStatuses() { // CREATED, INVALID and READY should not be returned by the server return new JobStatus[] { JobStatus.SUBMITTED, JobStatus.QUEUED, JobStatus.RUNNING, JobStatus.COMPLETED, JobStatus.FAILED, JobStatus.CANCELLED, JobStatus.SERVER_ERROR, JobStatus.UNKNOWN }; } @Test(groups = { "Functional" }) public void allJobsStarted_taskStartedCalled() throws IOException { when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED); var mockListener = performAction(viewport, actionBuilder.build()); verify(mockListener).taskStarted(any(), anyList()); } @Test(groups = { "Functional" }) public void allJobsStarted_taskStatusChangedCalledWithReadyThenSubmitted() throws IOException { when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED); var mockListener = performAction(viewport, actionBuilder.build()); var inOrder = inOrder(mockListener); inOrder.verify(mockListener).taskStatusChanged(any(), eq(JobStatus.READY)); inOrder.verify(mockListener).taskStatusChanged(any(), eq(JobStatus.SUBMITTED)); } @Test(groups = { "Functional" }, dataProvider = "jobStatuses") public void jobStatusChanged_taskStatusChangedCalledWithJobStatus(JobStatus status) throws IOException { when(mockClient.getStatus(jobRef)) .thenReturn(status) .thenReturn(JobStatus.COMPLETED); var mockListener = performAction(viewport, actionBuilder.build()); verify(mockListener).taskStatusChanged(any(), eq(status)); } @Test(groups = { "Functional" }, dataProvider = "jobStatuses") public void jobStatusChanged_subJobStatusChangedCalledWithJobStatus(JobStatus status) throws IOException { when(mockClient.getStatus(jobRef)) .thenReturn(status) .thenReturn(JobStatus.COMPLETED); var mockListener = performAction(viewport, actionBuilder.build()); verify(mockListener).subJobStatusChanged(any(), any(), eq(status)); } }