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