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