300eb6d409732e0357409cb633793082b77b5282
[jalview.git] / test / jalview / ws2 / actions / alignment / AlignmentActionTest.java
1 package jalview.ws2.actions.alignment;
2
3 import java.io.IOException;
4 import java.net.URL;
5 import java.util.List;
6 import java.util.concurrent.CountDownLatch;
7 import java.util.concurrent.TimeUnit;
8
9 import javax.help.UnsupportedOperationException;
10
11 import org.hamcrest.BaseMatcher;
12 import org.hamcrest.Description;
13 import org.hamcrest.Matcher;
14 import org.hamcrest.TypeSafeMatcher;
15 import org.mockito.ArgumentCaptor;
16 import org.testng.annotations.BeforeMethod;
17 import org.testng.annotations.DataProvider;
18 import org.testng.annotations.Test;
19
20 import jalview.datamodel.Alignment;
21 import jalview.datamodel.AlignmentI;
22 import jalview.datamodel.Sequence;
23 import jalview.datamodel.SequenceI;
24 import jalview.gui.AlignViewport;
25 import jalview.viewmodel.AlignmentViewport;
26 import jalview.ws.params.ParamDatastoreI;
27 import jalview.ws2.actions.api.JobI;
28 import jalview.ws2.actions.api.TaskEventListener;
29 import jalview.ws2.api.Credentials;
30 import jalview.ws2.api.JobStatus;
31 import jalview.ws2.api.WebService;
32 import jalview.ws2.api.WebServiceJobHandle;
33 import jalview.ws2.client.api.AlignmentWebServiceClientI;
34
35 import org.mockito.hamcrest.MockitoHamcrest;
36 import org.mockito.internal.hamcrest.HamcrestArgumentMatcher;
37
38 import static org.mockito.Mockito.*;
39 import static org.hamcrest.MatcherAssert.assertThat;
40 import static org.hamcrest.Matchers.*;
41
42 public class AlignmentActionTest
43 {
44   protected AlignmentWebServiceClientI mockClient;
45
46   protected AlignmentAction.Builder actionBuilder;
47
48   protected WebServiceJobHandle jobRef;
49
50   @BeforeMethod
51   public void setupMockClient() throws IOException
52   {
53     jobRef = new WebServiceJobHandle(
54         "mock", "mock", "http://example.org", "00000001");
55     mockClient = mock(AlignmentWebServiceClientI.class);
56     when(mockClient.getUrl()).thenReturn("http://example.org");
57     when(mockClient.getClientName()).thenReturn("mock");
58     when(mockClient.submit(anyList(), anyList(), any())).thenReturn(jobRef);
59     when(mockClient.getLog(jobRef)).thenReturn("");
60     when(mockClient.getErrorLog(jobRef)).thenReturn("");
61     doThrow(new UnsupportedOperationException()).when(mockClient).cancel(any());
62   }
63
64   @BeforeMethod(dependsOnMethods = { "setupMockClient" })
65   public void setupActionBuilder() throws IOException
66   {
67     actionBuilder = AlignmentAction.newBuilder(mockClient);
68     actionBuilder.name("mock");
69     actionBuilder.webService(
70         WebService.<AlignmentAction> newBuilder()
71             .url(new URL("http://example.org"))
72             .clientName("mock")
73             .category("Alignment")
74             .name("mock")
75             .paramDatastore(mock(ParamDatastoreI.class))
76             .actionClass(AlignmentAction.class)
77             .build());
78   }
79
80   @DataProvider
81   public Object[][] multipleSequencesUnalignedAndAligned()
82   {
83     return new Object[][] {
84         {
85             new Alignment(new SequenceI[]
86             {
87                 new Sequence("Seq 1", "----ASTVLITOPDCMMQEGGST-"),
88                 new Sequence("Seq 2", "-ASCGLITO------MMQEGGST-"),
89                 new Sequence("Seq 3", "AS--TVL--OPDTMMQEL------")
90             }),
91             new Alignment(new SequenceI[]
92             {
93                 new Sequence("Sequence0", "ASTV-LITOPDCMMQEGGST----"),
94                 new Sequence("Sequence1", "ASC-GLITO---MMQEGGST----"),
95                 new Sequence("Sequence2", "ASTV-L--OPDTMMQE--L-----")
96             })
97         }
98     };
99   }
100
101   @Test(
102     groups = { "Functional" },
103     dataProvider = "multipleSequencesUnalignedAndAligned")
104   public void submitSequences_verifySequenceNamesUniquified(
105       Alignment unaligned, Alignment aligned)
106       throws IOException
107   {
108     var viewport = new AlignViewport(unaligned);
109     when(mockClient.getAlignment(jobRef)).thenReturn(aligned);
110     when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED);
111     actionBuilder.submitGaps(false);
112     performAction(viewport, actionBuilder.build());
113     ArgumentCaptor<List<SequenceI>> argument = ArgumentCaptor.forClass(List.class);
114     verify(mockClient).submit(argument.capture(), eq(List.of()), eq(Credentials.empty()));
115     assertThat(argument.getValue(),
116         contains(hasProperty("name", is("Sequence0")),
117             hasProperty("name", is("Sequence1")),
118             hasProperty("name", is("Sequence2"))));
119   }
120
121   @Test(
122     groups = { "Functional" },
123     dataProvider = "multipleSequencesUnalignedAndAligned")
124   public void submitSequences_submitGapsOff_verifySequencesSubmittedWithoutGaps(Alignment unaligned, Alignment aligned)
125       throws IOException
126   {
127     var viewport = new AlignViewport(unaligned);
128     actionBuilder.submitGaps(false);
129     when(mockClient.getAlignment(jobRef)).thenReturn(aligned);
130     when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED);
131     performAction(viewport, actionBuilder.build());
132     ArgumentCaptor<List<SequenceI>> argument = ArgumentCaptor.forClass(List.class);
133     verify(mockClient).submit(argument.capture(), eq(List.of()), eq(Credentials.empty()));
134     assertThat(argument.getValue(),
135         contains(
136             matchesSequence("ASTVLITOPDCMMQEGGST"),
137             matchesSequence("ASCGLITOMMQEGGST"),
138             matchesSequence("ASTVLOPDTMMQEL")));
139   }
140
141   @Test(
142     groups = { "Functional" },
143     dataProvider = "multipleSequencesUnalignedAndAligned")
144   public void submitSequences_submitGapsOn_verifySequencesSubmittedWithGaps(
145       Alignment unaligned, Alignment aligned)
146       throws IOException
147   {
148     var viewport = new AlignViewport(unaligned);
149     actionBuilder.submitGaps(true);
150     when(mockClient.getAlignment(jobRef)).thenReturn(aligned);
151     when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED);
152     performAction(viewport, actionBuilder.build());
153     ArgumentCaptor<List<SequenceI>> argument = ArgumentCaptor.forClass(List.class);
154     verify(mockClient).submit(argument.capture(), eq(List.of()), eq(Credentials.empty()));
155     assertThat(argument.getValue(),
156         contains(
157             matchesSequence("----ASTVLITOPDCMMQEGGST-"),
158             matchesSequence("-ASCGLITO------MMQEGGST-"),
159             matchesSequence("AS--TVL--OPDTMMQEL------")));
160   }
161
162   @Test(
163     groups = { "Functional" },
164     dataProvider = "multipleSequencesUnalignedAndAligned")
165   public void retrieveResult_verifySequencesAligned(
166       Alignment unaligned, Alignment aligned)
167       throws IOException
168   {
169     var viewport = new AlignViewport(unaligned);
170     actionBuilder.submitGaps(false);
171     when(mockClient.getAlignment(jobRef)).thenReturn(aligned);
172     when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED);
173     var mockListener = performAction(viewport, actionBuilder.build());
174     var argument = ArgumentCaptor.forClass(AlignmentResult.class);
175     verify(mockListener).taskCompleted(any(), argument.capture());
176     var alignmentResult = argument.getValue().getAlignment();
177     assertThat(alignmentResult, hasProperty("sequences", contains(
178         matchesSequence("ASTV-LITOPDCMMQEGGST----"),
179         matchesSequence("ASC-GLITO---MMQEGGST----"),
180         matchesSequence("ASTV-L--OPDTMMQE--L-----"))));
181   }
182
183   protected static Matcher<SequenceI> matchesSequence(String sequence)
184   {
185     return new TypeSafeMatcher<SequenceI>()
186     {
187       @Override
188       public boolean matchesSafely(SequenceI obj)
189       {
190         if (!(obj instanceof SequenceI))
191           return false;
192         var seq = (SequenceI) obj;
193         return seq.getSequenceAsString().equals(sequence);
194       }
195
196       @Override
197       public void describeTo(Description description)
198       {
199         description.appendText("a sequence ").appendValue(sequence);
200       }
201
202       @Override
203       public void describeMismatchSafely(SequenceI item, Description description)
204       {
205         description.appendText("was ").appendValue(item.getSequenceAsString());
206       }
207     };
208   }
209
210   protected TaskEventListener<AlignmentResult> performAction(
211       AlignmentViewport viewport, AlignmentAction action)
212       throws IOException
213   {
214     TaskEventListener<AlignmentResult> listener = mock(TaskEventListener.class);
215     var latch = new CountDownLatch(1);
216     doAnswer(invocation -> {
217       latch.countDown();
218       return null;
219     })
220         .when(listener).taskCompleted(any(), any());
221     action.perform(viewport, List.of(), Credentials.empty(), listener);
222     try
223     {
224       latch.await(100, TimeUnit.MILLISECONDS);
225     } catch (InterruptedException e)
226     {
227     }
228     return listener;
229   }
230 }
231
232 class AlignmentActionListenerNotifiedTest extends AlignmentActionTest
233 {
234   private AlignViewport viewport;
235
236   @BeforeMethod
237   public void setupViewport()
238   {
239     viewport = new AlignViewport(new Alignment(new SequenceI[] {
240         new Sequence("Seq 1", "----ASTVLITOPDCMMQEGGST-"),
241         new Sequence("Seq 2", "-ASCGLITO------MMQEGGST-"),
242         new Sequence("Seq 3", "AS--TVL--OPDTMMQEL------")
243     }));
244   }
245
246   @DataProvider
247   public JobStatus[] jobStatuses()
248   {
249     // CREATED, INVALID and READY should not be returned by the server
250     return new JobStatus[] {
251         JobStatus.SUBMITTED,
252         JobStatus.QUEUED,
253         JobStatus.RUNNING,
254         JobStatus.COMPLETED,
255         JobStatus.FAILED,
256         JobStatus.CANCELLED,
257         JobStatus.SERVER_ERROR,
258         JobStatus.UNKNOWN
259     };
260   }
261
262   @Test(groups = { "Functional" })
263   public void allJobsStarted_taskStartedCalled()
264       throws IOException
265   {
266     when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED);
267     var mockListener = performAction(viewport, actionBuilder.build());
268     verify(mockListener).taskStarted(any(), anyList());
269   }
270
271   @Test(groups = { "Functional" })
272   public void allJobsStarted_taskStatusChangedCalledWithReadyThenSubmitted()
273       throws IOException
274   {
275     when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED);
276     var mockListener = performAction(viewport, actionBuilder.build());
277     var inOrder = inOrder(mockListener);
278     inOrder.verify(mockListener).taskStatusChanged(any(), eq(JobStatus.READY));
279     inOrder.verify(mockListener).taskStatusChanged(any(), eq(JobStatus.SUBMITTED));
280   }
281
282   @Test(groups = { "Functional" }, dataProvider = "jobStatuses")
283   public void jobStatusChanged_taskStatusChangedCalledWithJobStatus(JobStatus status)
284       throws IOException
285   {
286     when(mockClient.getStatus(jobRef))
287         .thenReturn(status)
288         .thenReturn(JobStatus.COMPLETED);
289     var mockListener = performAction(viewport, actionBuilder.build());
290     verify(mockListener).taskStatusChanged(any(), eq(status));
291   }
292
293   @Test(groups = { "Functional" }, dataProvider = "jobStatuses")
294   public void jobStatusChanged_subJobStatusChangedCalledWithJobStatus(JobStatus status)
295       throws IOException
296   {
297     when(mockClient.getStatus(jobRef))
298         .thenReturn(status)
299         .thenReturn(JobStatus.COMPLETED);
300     var mockListener = performAction(viewport, actionBuilder.build());
301     verify(mockListener).subJobStatusChanged(any(), any(), eq(status));
302   }
303 }