JAL-4199 Introduce AlignmentAction tests
authorMateusz Warowny <mmzwarowny@dundee.ac.uk>
Thu, 22 Jun 2023 13:41:19 +0000 (15:41 +0200)
committerMateusz Warowny <mmzwarowny@dundee.ac.uk>
Thu, 22 Jun 2023 13:41:19 +0000 (15:41 +0200)
test/jalview/ws2/actions/alignment/AlignmentActionTest.java [new file with mode: 0644]

diff --git a/test/jalview/ws2/actions/alignment/AlignmentActionTest.java b/test/jalview/ws2/actions/alignment/AlignmentActionTest.java
new file mode 100644 (file)
index 0000000..5586108
--- /dev/null
@@ -0,0 +1,295 @@
+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