--- /dev/null
+package jalview.ws2.actions.alignment;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.help.UnsupportedOperationException;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+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.AlignmentI;
+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.api.JobI;
+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;
+
+import org.mockito.hamcrest.MockitoHamcrest;
+import org.mockito.internal.hamcrest.HamcrestArgumentMatcher;
+
+import static org.mockito.Mockito.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+
+public class AlignmentActionTest
+{
+ protected AlignmentWebServiceClientI mockClient;
+
+ protected AlignmentAction.Builder actionBuilder;
+
+ protected WebServiceJobHandle jobRef;
+
+ @BeforeMethod
+ 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(dependsOnMethods = { "setupMockClient" })
+ public void setupActionBuilder() throws IOException
+ {
+ actionBuilder = AlignmentAction.newBuilder(mockClient);
+ actionBuilder.name("mock");
+ actionBuilder.webService(
+ WebService.<AlignmentAction> 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(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<List<SequenceI>> 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(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<List<SequenceI>> argument = ArgumentCaptor.forClass(List.class);
+ verify(mockClient).submit(argument.capture(), eq(List.of()), eq(Credentials.empty()));
+ assertThat(argument.getValue(),
+ contains(
+ matchesSequence("ASTVLITOPDCMMQEGGST"),
+ matchesSequence("ASCGLITOMMQEGGST"),
+ matchesSequence("ASTVLOPDTMMQEL")));
+ }
+
+ @Test(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<List<SequenceI>> argument = ArgumentCaptor.forClass(List.class);
+ verify(mockClient).submit(argument.capture(), eq(List.of()), eq(Credentials.empty()));
+ assertThat(argument.getValue(),
+ contains(
+ matchesSequence("----ASTVLITOPDCMMQEGGST-"),
+ matchesSequence("-ASCGLITO------MMQEGGST-"),
+ matchesSequence("AS--TVL--OPDTMMQEL------")));
+ }
+
+ @Test(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(
+ matchesSequence("ASTV-LITOPDCMMQEGGST----"),
+ matchesSequence("ASC-GLITO---MMQEGGST----"),
+ matchesSequence("ASTV-L--OPDTMMQE--L-----"))));
+ }
+
+ protected static Matcher<SequenceI> matchesSequence(String sequence)
+ {
+ return new TypeSafeMatcher<SequenceI>()
+ {
+ @Override
+ public boolean matchesSafely(SequenceI obj)
+ {
+ if (!(obj instanceof SequenceI))
+ return false;
+ var seq = (SequenceI) obj;
+ return seq.getSequenceAsString().equals(sequence);
+ }
+
+ @Override
+ public void describeTo(Description description)
+ {
+ description.appendText("a sequence ").appendValue(sequence);
+ }
+
+ @Override
+ public void describeMismatchSafely(SequenceI item, Description description)
+ {
+ description.appendText("was ").appendValue(item.getSequenceAsString());
+ }
+ };
+ }
+
+ protected TaskEventListener<AlignmentResult> performAction(
+ AlignmentViewport viewport, AlignmentAction action)
+ throws IOException
+ {
+ TaskEventListener<AlignmentResult> listener = mock(TaskEventListener.class);
+ var latch = new CountDownLatch(1);
+ doAnswer(invocation -> {
+ latch.countDown();
+ return null;
+ })
+ .when(listener).taskCompleted(any(), any());
+ action.perform(viewport, List.of(), Credentials.empty(), listener);
+ try
+ {
+ latch.await(100, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e)
+ {
+ }
+ return listener;
+ }
+}
+
+class AlignmentActionListenerNotifiedTest extends AlignmentActionTest
+{
+ private AlignViewport viewport;
+
+ @BeforeMethod
+ 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
+ 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
+ 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(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(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));
+ }
+}
\ No newline at end of file