Merge branch 'mmw/JAL-4199-web-services-testing' into development/Release_2_12_Branch
[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(dataProvider = "multipleSequencesUnalignedAndAligned")
102   public void submitSequences_verifySequenceNamesUniquified(
103       Alignment unaligned, Alignment aligned)
104       throws IOException
105   {
106     var viewport = new AlignViewport(unaligned);
107     when(mockClient.getAlignment(jobRef)).thenReturn(aligned);
108     when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED);
109     actionBuilder.submitGaps(false);
110     performAction(viewport, actionBuilder.build());
111     ArgumentCaptor<List<SequenceI>> argument = ArgumentCaptor.forClass(List.class);
112     verify(mockClient).submit(argument.capture(), eq(List.of()), eq(Credentials.empty()));
113     assertThat(argument.getValue(),
114         contains(hasProperty("name", is("Sequence0")),
115             hasProperty("name", is("Sequence1")),
116             hasProperty("name", is("Sequence2"))));
117   }
118
119   @Test(dataProvider = "multipleSequencesUnalignedAndAligned")
120   public void submitSequences_submitGapsOff_verifySequencesSubmittedWithoutGaps(Alignment unaligned, Alignment aligned)
121       throws IOException
122   {
123     var viewport = new AlignViewport(unaligned);
124     actionBuilder.submitGaps(false);
125     when(mockClient.getAlignment(jobRef)).thenReturn(aligned);
126     when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED);
127     performAction(viewport, actionBuilder.build());
128     ArgumentCaptor<List<SequenceI>> argument = ArgumentCaptor.forClass(List.class);
129     verify(mockClient).submit(argument.capture(), eq(List.of()), eq(Credentials.empty()));
130     assertThat(argument.getValue(),
131         contains(
132             matchesSequence("ASTVLITOPDCMMQEGGST"),
133             matchesSequence("ASCGLITOMMQEGGST"),
134             matchesSequence("ASTVLOPDTMMQEL")));
135   }
136
137   @Test(dataProvider = "multipleSequencesUnalignedAndAligned")
138   public void submitSequences_submitGapsOn_verifySequencesSubmittedWithGaps(
139       Alignment unaligned, Alignment aligned)
140       throws IOException
141   {
142     var viewport = new AlignViewport(unaligned);
143     actionBuilder.submitGaps(true);
144     when(mockClient.getAlignment(jobRef)).thenReturn(aligned);
145     when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED);
146     performAction(viewport, actionBuilder.build());
147     ArgumentCaptor<List<SequenceI>> argument = ArgumentCaptor.forClass(List.class);
148     verify(mockClient).submit(argument.capture(), eq(List.of()), eq(Credentials.empty()));
149     assertThat(argument.getValue(),
150         contains(
151             matchesSequence("----ASTVLITOPDCMMQEGGST-"),
152             matchesSequence("-ASCGLITO------MMQEGGST-"),
153             matchesSequence("AS--TVL--OPDTMMQEL------")));
154   }
155
156   @Test(dataProvider = "multipleSequencesUnalignedAndAligned")
157   public void retrieveResult_verifySequencesAligned(
158       Alignment unaligned, Alignment aligned)
159       throws IOException
160   {
161     var viewport = new AlignViewport(unaligned);
162     actionBuilder.submitGaps(false);
163     when(mockClient.getAlignment(jobRef)).thenReturn(aligned);
164     when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED);
165     var mockListener = performAction(viewport, actionBuilder.build());
166     var argument = ArgumentCaptor.forClass(AlignmentResult.class);
167     verify(mockListener).taskCompleted(any(), argument.capture());
168     var alignmentResult = argument.getValue().getAlignment();
169     assertThat(alignmentResult, hasProperty("sequences", contains(
170         matchesSequence("ASTV-LITOPDCMMQEGGST----"),
171         matchesSequence("ASC-GLITO---MMQEGGST----"),
172         matchesSequence("ASTV-L--OPDTMMQE--L-----"))));
173   }
174
175   protected static Matcher<SequenceI> matchesSequence(String sequence)
176   {
177     return new TypeSafeMatcher<SequenceI>()
178     {
179       @Override
180       public boolean matchesSafely(SequenceI obj)
181       {
182         if (!(obj instanceof SequenceI))
183           return false;
184         var seq = (SequenceI) obj;
185         return seq.getSequenceAsString().equals(sequence);
186       }
187
188       @Override
189       public void describeTo(Description description)
190       {
191         description.appendText("a sequence ").appendValue(sequence);
192       }
193
194       @Override
195       public void describeMismatchSafely(SequenceI item, Description description)
196       {
197         description.appendText("was ").appendValue(item.getSequenceAsString());
198       }
199     };
200   }
201
202   protected TaskEventListener<AlignmentResult> performAction(
203       AlignmentViewport viewport, AlignmentAction action)
204       throws IOException
205   {
206     TaskEventListener<AlignmentResult> listener = mock(TaskEventListener.class);
207     var latch = new CountDownLatch(1);
208     doAnswer(invocation -> {
209       latch.countDown();
210       return null;
211     })
212         .when(listener).taskCompleted(any(), any());
213     action.perform(viewport, List.of(), Credentials.empty(), listener);
214     try
215     {
216       latch.await(100, TimeUnit.MILLISECONDS);
217     } catch (InterruptedException e)
218     {
219     }
220     return listener;
221   }
222 }
223
224 class AlignmentActionListenerNotifiedTest extends AlignmentActionTest
225 {
226   private AlignViewport viewport;
227
228   @BeforeMethod
229   public void setupViewport()
230   {
231     viewport = new AlignViewport(new Alignment(new SequenceI[] {
232         new Sequence("Seq 1", "----ASTVLITOPDCMMQEGGST-"),
233         new Sequence("Seq 2", "-ASCGLITO------MMQEGGST-"),
234         new Sequence("Seq 3", "AS--TVL--OPDTMMQEL------")
235     }));
236   }
237
238   @DataProvider
239   public JobStatus[] jobStatuses()
240   {
241     // CREATED, INVALID and READY should not be returned by the server
242     return new JobStatus[] {
243         JobStatus.SUBMITTED,
244         JobStatus.QUEUED,
245         JobStatus.RUNNING,
246         JobStatus.COMPLETED,
247         JobStatus.FAILED,
248         JobStatus.CANCELLED,
249         JobStatus.SERVER_ERROR,
250         JobStatus.UNKNOWN
251     };
252   }
253
254   @Test
255   public void allJobsStarted_taskStartedCalled()
256       throws IOException
257   {
258     when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED);
259     var mockListener = performAction(viewport, actionBuilder.build());
260     verify(mockListener).taskStarted(any(), anyList());
261   }
262
263   @Test
264   public void allJobsStarted_taskStatusChangedCalledWithReadyThenSubmitted()
265       throws IOException
266   {
267     when(mockClient.getStatus(jobRef)).thenReturn(JobStatus.COMPLETED);
268     var mockListener = performAction(viewport, actionBuilder.build());
269     var inOrder = inOrder(mockListener);
270     inOrder.verify(mockListener).taskStatusChanged(any(), eq(JobStatus.READY));
271     inOrder.verify(mockListener).taskStatusChanged(any(), eq(JobStatus.SUBMITTED));
272   }
273
274   @Test(dataProvider = "jobStatuses")
275   public void jobStatusChanged_taskStatusChangedCalledWithJobStatus(JobStatus status)
276       throws IOException
277   {
278     when(mockClient.getStatus(jobRef))
279         .thenReturn(status)
280         .thenReturn(JobStatus.COMPLETED);
281     var mockListener = performAction(viewport, actionBuilder.build());
282     verify(mockListener).taskStatusChanged(any(), eq(status));
283   }
284
285   @Test(dataProvider = "jobStatuses")
286   public void jobStatusChanged_subJobStatusChangedCalledWithJobStatus(JobStatus status)
287       throws IOException
288   {
289     when(mockClient.getStatus(jobRef))
290         .thenReturn(status)
291         .thenReturn(JobStatus.COMPLETED);
292     var mockListener = performAction(viewport, actionBuilder.build());
293     verify(mockListener).subJobStatusChanged(any(), any(), eq(status));
294   }
295 }