JAL-4199 Test URL settings and basic service discovery
[jalview.git] / test / jalview / ws2 / client / slivka / SlivkaWSDiscovererTest.java
1 package jalview.ws2.client.slivka;
2
3 import static org.hamcrest.MatcherAssert.assertThat;
4 import static org.hamcrest.Matchers.*;
5 import static org.mockito.Mockito.mock;
6 import static org.mockito.Mockito.when;
7
8 import java.io.IOException;
9 import java.net.MalformedURLException;
10 import java.net.URI;
11 import java.net.URL;
12 import java.util.ArrayList;
13 import java.util.Iterator;
14 import java.util.List;
15 import java.util.function.Function;
16
17 import org.testng.annotations.BeforeClass;
18 import org.testng.annotations.BeforeMethod;
19 import org.testng.annotations.DataProvider;
20 import org.testng.annotations.Test;
21
22 import jalview.bin.Cache;
23 import jalview.bin.Console;
24 import jalview.ws2.actions.alignment.AlignmentAction;
25 import jalview.ws2.actions.annotation.AnnotationAction;
26 import jalview.ws2.client.api.WebServiceDiscovererI;
27 import uk.ac.dundee.compbio.slivkaclient.SlivkaClient;
28 import uk.ac.dundee.compbio.slivkaclient.SlivkaService;
29
30 public class SlivkaWSDiscovererTest
31 {
32   private static final String URLS_PROPERTY_NAME = "SLIVKAHOSTURLS";
33
34   SlivkaClient clientMock;
35
36   Function<URL, SlivkaClient> factoryMock;
37
38   @BeforeClass(alwaysRun = true)
39   public void setupProperties()
40   {
41     Cache.loadProperties("test/jalview/ws2/client/slivka/default.jvprops");
42     Console.initLogger();
43   }
44
45   @BeforeMethod
46   public void setupDiscoverer() throws IOException
47   {
48     clientMock = mock(SlivkaClient.class);
49   }
50
51   @Test
52   public void getStatusForUrl_servicesReturned_statusIsOK() throws Exception
53   {
54     when(clientMock.getServices())
55             .thenReturn(List.of(mock(SlivkaService.class)));
56     var discoverer = new SlivkaWSDiscoverer(
57             url -> url.toString().equals("http://example.org") ? clientMock
58                     : null);
59     assertThat(discoverer.getStatusForUrl(new URL("http://example.org")),
60             is(WebServiceDiscovererI.STATUS_OK));
61   }
62
63   @Test
64   public void getStatusForUrl_noServicesReturned_statusIsNoServices()
65           throws Exception
66   {
67     when(clientMock.getServices()).thenReturn(List.of());
68     var discoverer = new SlivkaWSDiscoverer(
69             url -> url.toString().equals("http://example.org") ? clientMock
70                     : null);
71     assertThat(discoverer.getStatusForUrl(new URL("http://example.org")),
72             is(WebServiceDiscovererI.STATUS_NO_SERVICES));
73   }
74
75   @Test
76   public void getStatusForUrl_exceptionThrown_statusIsInvalid()
77           throws Exception
78   {
79     when(clientMock.getServices()).thenThrow(new IOException());
80     var discoverer = new SlivkaWSDiscoverer(
81             url -> url.toString().equals("http://example.org") ? clientMock
82                     : null);
83     assertThat(discoverer.getStatusForUrl(new URL("http://example.org")),
84             is(WebServiceDiscovererI.STATUS_INVALID));
85   }
86
87   @Test
88   public void testGetUrls_noPropEntry_defaultUrlReturned()
89           throws MalformedURLException
90   {
91     var discoverer = SlivkaWSDiscoverer.getInstance();
92     assertThat(discoverer.getUrls(),
93             contains(new URL("https://www.compbio.dundee.ac.uk/slivka/")));
94   }
95
96   @DataProvider
97   public Object[][] urlPropertyValues() throws MalformedURLException
98   {
99     return new Object[][] {
100         { "http://example.org/", List.of(new URL("http://example.org/")) },
101         { "https://example.org/slivka/",
102             List.of(new URL("https://example.org/slivka/")) },
103         { "https://www.compbio.dundee.ac.uk/,http://www.example.org/",
104             List.of(new URL("https://www.compbio.dundee.ac.uk/"),
105                     new URL("http://www.example.org/")) },
106         { "http://example.org/,", List.of(new URL("http://example.org/")) },
107         { ",http://example.org", List.of(new URL("http://example.org")) },
108         { "", List.of() },
109         { ",", List.of() },
110         { "example.org", List.of() },
111         { "example.org,http://example.org",
112             List.of(new URL("http://example.org")) } };
113   }
114
115   @Test(dataProvider = "urlPropertyValues")
116   public void testGetUrls_urlsProperlyParsed(String propValue,
117           List<URL> expected)
118   {
119     Cache.setProperty(URLS_PROPERTY_NAME, propValue);
120     var discoverer = SlivkaWSDiscoverer.getInstance();
121     assertThat(discoverer.getUrls(), equalTo(expected));
122   }
123
124   @Test
125   public void testSetUrls_emptyList_propertyReset()
126   {
127     Cache.setProperty(URLS_PROPERTY_NAME, "http://www.example.org");
128     var discoverer = SlivkaWSDiscoverer.getInstance();
129     discoverer.setUrls(List.of());
130     assertThat(Cache.getProperty(URLS_PROPERTY_NAME), is(nullValue()));
131   }
132
133   @Test
134   public void testSetUrls_null_propertyReset()
135   {
136     Cache.setProperty(URLS_PROPERTY_NAME, "http://www.example.org");
137     var discoverer = SlivkaWSDiscoverer.getInstance();
138     discoverer.setUrls(null);
139     assertThat(Cache.getProperty(URLS_PROPERTY_NAME), is(nullValue()));
140   }
141
142   @DataProvider
143   public Object[][] urlsList() throws MalformedURLException
144   {
145     return new Object[][] {
146         { List.of(new URL("http://example.org")), "http://example.org" },
147         { List.of(new URL("http://example.org/")), "http://example.org/" },
148         { List.of(new URL("http://example.org/slivka/")),
149             "http://example.org/slivka/" },
150         { List.of(new URL("https://www.compbio.dundee.ac.uk/slivka/"),
151                 new URL("http://example.org")),
152             "https://www.compbio.dundee.ac.uk/slivka/,http://example.org" }, };
153   }
154
155   @Test(dataProvider = "urlsList")
156   public void testSetUrls_urlsPropertySet(List<URL> urls, String expected)
157           throws MalformedURLException
158   {
159     var discoverer = SlivkaWSDiscoverer.getInstance();
160     discoverer.setUrls(urls);
161     assertThat(Cache.getProperty(URLS_PROPERTY_NAME), equalTo(expected));
162   }
163
164   @Test
165   public void testFetchServices_oneService_basicDataMatches()
166           throws IOException
167   {
168     var service = new SlivkaService(
169             URI.create("http://example.org/api/services/example"),
170             "example", "Example name", "Example service description",
171             "John Smith", "1.0", "MIT License",
172             List.of("operation::analysis::multiple sequence alignment"),
173             List.of(), List.of(), null);
174     when(clientMock.getServices()).thenReturn(List.of(service));
175     when(clientMock.getUrl()).thenReturn(URI.create("http://example.org/"));
176     var discoverer = new SlivkaWSDiscoverer(url -> clientMock);
177     var webServices = discoverer
178             .fetchServices(new URL("http://example.org/"));
179     assertThat(webServices, hasSize(1));
180     var webService = webServices.get(0);
181     assertThat(webService.getUrl(),
182             equalTo(new URL("http://example.org/")));
183     assertThat(webService.getClientName(), equalTo("slivka"));
184     assertThat(webService.getName(), equalTo("Example name"));
185     assertThat(webService.getDescription(),
186             equalTo("Example service description"));
187   }
188
189   @DataProvider
190   public String[] validMultipleSequenceAlignmentClassifiers()
191   {
192     return new String[] {
193         "Operation :: Analysis :: Multiple sequence alignment",
194         "operation :: analysis :: multiple sequence alignment",
195         "Operation\t::\tAnalysis\t::\tMultiple sequence alignment",
196         "Operation::Analysis::Multiple sequence alignment",
197         "Operation :: Analysis :: Multiple Sequence Alignment",
198         "OPERATION :: ANALYSIS :: MULTIPLE SEQUENCE ALIGNMENT",
199         "Operation :: Analysis :: Sequence alignment :: Multiple sequence alignment",
200         "Operation :: Analysis :: Sequence analysis :: Sequence alignment :: Multiple sequence alignment",
201         "Operation :: Alignment :: Multiple sequence alignment",
202         "Operation :: Alignment :: Sequence alignment :: Multiple sequence alignment",
203         "Operation :: Comparison :: Multiple sequence alignment",
204         "Operation :: Comparison :: Sequence comparison :: Sequence alignment :: Multiple sequence alignment" };
205
206   }
207
208   @Test(dataProvider = "validMultipleSequenceAlignmentClassifiers")
209   public void testFetchServices_multipleSequenceAlignmentClassifier_serviceTypeIsMSA(
210           String classifier) throws IOException
211   {
212     var service = new SlivkaService(URI.create("http://example.org/"),
213             "example", "name", "description", "author", "1.0", "MIT",
214             List.of(classifier), List.of(), List.of(), null);
215     when(clientMock.getServices()).thenReturn(List.of(service));
216     when(clientMock.getUrl()).thenReturn(URI.create("http://example.org/"));
217     var discoverer = new SlivkaWSDiscoverer(url -> clientMock);
218     var webServices = discoverer
219             .fetchServices(new URL("http://example.org/"));
220     assertThat(webServices, hasSize(1));
221     assertThat(webServices.get(0).getCategory(), equalTo("Alignment"));
222     assertThat(webServices.get(0).getActionClass(),
223             typeCompatibleWith(AlignmentAction.class));
224   }
225
226   @DataProvider
227   public SlivkaService[] multipleSequenceAlignmentService()
228   {
229     return new SlivkaService[] { new SlivkaService(
230             URI.create("http://example.org/"), "example", "Examaple name",
231             "Example description", "John Smith", "1.0", "MIT",
232             List.of("Operation :: Analysis :: Multiple sequence alignment"),
233             List.of(), List.of(), null),
234         new SlivkaService(
235                 URI.create("http://example.org/api/services/muscle"),
236                 "muscle", "MUSCLE",
237                 "MUltiple Sequence Comparison by Log- Expectation",
238                 "Robert C. Edgar", "3.8.31", "Public domain",
239                 List.of("Topic :: Computational biology :: Sequence analysis",
240                         "Operation :: Analysis :: Sequence analysis :: Sequence alignment :: Multiple sequence alignment"),
241                 List.of(), List.of(), null),
242         new SlivkaService(
243                 URI.create("http://example.org/api/services/tcoffee"),
244                 "tcoffee", "TCoffee",
245                 "Tree-based Consistency Objective Function for Alignment Evaluation",
246                 "Cedric Notredame", "13.41.0", "GNU GPL",
247                 List.of("Topic :: Computational biology :: Sequence analysis",
248                         "Operation :: Analysis :: Sequence analysis :: Sequence alignment :: Multiple sequence alignment"),
249                 List.of(), List.of(), null) };
250   }
251
252   @Test(dataProvider = "multipleSequenceAlignmentService")
253   public void testFetchServices_multipleSequenceAlignmentService_actionTypeIsAlignment(
254           SlivkaService service) throws IOException
255   {
256     when(clientMock.getServices()).thenReturn(List.of(service));
257     when(clientMock.getUrl()).thenReturn(URI.create("http://example.org/"));
258     var discoverer = new SlivkaWSDiscoverer(url -> clientMock);
259     var webServices = discoverer
260             .fetchServices(new URL("http://example.org/"));
261     assertThat(webServices.get(0).getCategory(), equalTo("Alignment"));
262     assertThat(webServices.get(0).getActionClass(),
263             typeCompatibleWith(AlignmentAction.class));
264   }
265
266   @Test(dataProvider = "multipleSequenceAlignmentService")
267   public void testFetchServices_multipleSequenceAlignmentService_serviceIsNonInteractive(
268           SlivkaService service) throws IOException
269   {
270     when(clientMock.getServices()).thenReturn(List.of(service));
271     when(clientMock.getUrl()).thenReturn(URI.create("http://example.org/"));
272     var discoverer = new SlivkaWSDiscoverer(url -> clientMock);
273     var webServices = discoverer
274             .fetchServices(new URL("http://example.org/"));
275     assertThat(webServices.get(0).isInteractive(), is(false));
276   }
277
278   @DataProvider
279   public SlivkaService[] clustalFamilyService()
280   {
281     return new SlivkaService[] { new SlivkaService(
282             URI.create("http://example.org/api/services/clustalo"),
283             "clustalo", "ClustalO",
284             "Clustal Omega is the latest addition to the Clustal family.",
285             "Fabian Sievers, et al.", "1.2.4", "GNU GPL ver. 2",
286             List.of("Topic :: Computational biology :: Sequence analysis",
287                     "Operation :: Analysis :: Sequence analysis :: Sequence alignment :: Multiple sequence alignment"),
288             List.of(), List.of(), null),
289         new SlivkaService(
290                 URI.create("http://example.org/api/services/clustalw"),
291                 "clustalw", "ClustalW",
292                 "ClustalW is a general purpose multiple alignment program.",
293                 "Larkin MA, et al.", "2.1", "GNU GPL ver. 3",
294                 List.of("Topic :: Computation biology :: Sequence analysis",
295                         "Operation :: Analysis :: Multiple sequence alignment"),
296                 List.of(), List.of(), null),
297         new SlivkaService(
298                 URI.create("http://example.org/api/services/clustalw2"),
299                 "clustalw2", "ClustalW2",
300                 "ClustalW is a general purpose multiple alignment program.",
301                 "Larkin MA, et al.", "2.1", "GNU GPL ver. 3",
302                 List.of("Topic :: Computation biology :: Sequence analysis",
303                         "Operation :: Analysis :: Multiple sequence alignment"),
304                 List.of(), List.of(), null), };
305   }
306
307   @Test(dataProvider = "clustalFamilyService")
308   public void testFetchService_clustalFamilyService_containsTwoActions(
309           SlivkaService service) throws IOException
310   {
311     when(clientMock.getServices()).thenReturn(List.of(service));
312     when(clientMock.getUrl()).thenReturn(URI.create("http://example.org"));
313     var discoverer = new SlivkaWSDiscoverer(url -> clientMock);
314     var webServices = discoverer
315             .fetchServices(new URL("http://example.org"));
316     var actions = webServices.get(0).getActions();
317     assertThat(actions, hasSize(2));
318     assertThat(actions.get(0), allOf(hasProperty("name", is("Alignment")),
319             hasProperty("subcategory", is("Align"))));
320     assertThat(actions.get(1),
321             allOf(hasProperty("name", is("Re-alignment")),
322                     hasProperty("subcategory", is("Realign"))));
323   }
324
325   @DataProvider
326   public String[] validRNASecondaryStructurePredictionClassifiers()
327   {
328     return new String[] {
329         "Operation :: Analysis :: RNA secondary structure prediction",
330         "operation :: analysis :: rna secondary structure prediction",
331         "OPERATION :: ANALYSIS :: RNA SECONDARY STRUCTURE PREDICTION",
332         "Operation\t::\tAnalysis\t::\tRNA secondary structure prediction",
333         "Operation::Analysis::RNA secondary structure prediction",
334         "Operation :: Analysis :: Structure analysis :: RNA secondary structure prediction",
335         "Operation :: Analysis :: Structure analysis :: Nucleic acid structure analysis :: RNA secondary structure analysis :: RNA secondary structure prediction",
336         "Operation :: Analysis :: Structure analysis :: Nucleic acid structure analysis :: Nucleic acid structure prediction :: RNA secondary structure prediction",
337         "Operation :: Analysis :: Sequence analysis :: Nucleic acid sequence analysis :: Nucleic acid feature detection :: RNA secondary structure prediction",
338         "Operation :: Prediction and recognition :: RNA secondary structure prediction",
339         "Operation :: Prediction and recognition :: Nucleic acid feature detection :: RNA secondary structure prediction",
340         "Operation :: Prediction and recignition :: Nucleic acid structure prediction :: RNA secondary structure prediction", };
341   }
342
343   @DataProvider
344   public Iterator<Object> RNASecondaryStructurePredictionService()
345   {
346     var services = new ArrayList<>();
347     for (var classifier : validRNASecondaryStructurePredictionClassifiers())
348     {
349       services.add(new SlivkaService(URI.create("http://example.org/"),
350               "example", "name", "description", "author", "1.0", "MIT",
351               List.of(classifier), List.of(), List.of(), null));
352     }
353     return services.iterator();
354   }
355
356   @Test(dataProvider = "RNASecondaryStructurePredictionService")
357   public void testFetchServices_RNASecStrPredClassifier_serviceTypeIsRNASecStrPred(
358           SlivkaService service) throws IOException
359   {
360     when(clientMock.getServices()).thenReturn(List.of(service));
361     when(clientMock.getUrl()).thenReturn(URI.create("http://example.org/"));
362     var discoverer = new SlivkaWSDiscoverer(url -> clientMock);
363     var webServices = discoverer
364             .fetchServices(new URL("http://example.org/"));
365     assertThat(webServices, hasSize(1));
366     assertThat(webServices.get(0).getCategory(),
367             equalTo("Secondary Structure Prediction"));
368     assertThat(webServices.get(0).getActionClass(),
369             typeCompatibleWith(AnnotationAction.class));
370   }
371
372   @DataProvider
373   public String[] validConservationAnalysisClassifiers()
374   {
375     return new String[] {
376         "Operation :: Analysis :: Sequence alignment analysis (conservation)",
377         "Operation::Analysis::Sequence alignment analysis (conservation)",
378         "Operation\t::\tAnalysis\t::\tSequence alignment analysis (conservation)",
379         "Operation :: Analysis :: Sequence analysis :: Sequence alignment analysis (conservation)",
380         "Operation :: Analysis :: Sequence analysis :: Sequence alignment analysis :: Sequence alignment analysis (conservation)", };
381   }
382
383   @DataProvider
384   public Iterator<Object> ConservationAnalysisService()
385   {
386     var services = new ArrayList<>();
387     for (var classifier : validConservationAnalysisClassifiers())
388     {
389       services.add(new SlivkaService(URI.create("http://example.org/"),
390               "example", "name", "description", "author", "1.0", "MIT",
391               List.of(classifier), List.of(), List.of(), null));
392     }
393     return services.iterator();
394   }
395
396   @Test(dataProvider = "validConservationAnalysisClassifiers")
397   public void testFetchServices_conservationAnalysisClassifier_serviceTypeIsConservation(
398           String classifier) throws IOException
399   {
400     var service = new SlivkaService(URI.create("http://example.org/"),
401             "example", "name", "description", "author", "1.0", "MIT",
402             List.of(classifier), List.of(), List.of(), null);
403     when(clientMock.getServices()).thenReturn(List.of(service));
404     when(clientMock.getUrl()).thenReturn(URI.create("http://example.org/"));
405     var discoverer = new SlivkaWSDiscoverer(url -> clientMock);
406     var webServices = discoverer
407             .fetchServices(new URL("http://example.org/"));
408     assertThat(webServices, hasSize(1));
409     assertThat(webServices.get(0).getCategory(), equalTo("Conservation"));
410     assertThat(webServices.get(0).getActionClass(),
411             typeCompatibleWith(AnnotationAction.class));
412   }
413
414   @DataProvider
415   public Object[] validProteinSequenceAnalysisClassifiers()
416   {
417     return new Object[] {
418         "Operation :: Analysis :: Sequence analysis :: Protein sequence analysis", };
419   }
420
421   @Test(dataProvider = "validProteinSequenceAnalysisClassifiers")
422   public void testFetchServices_proteinSequenceAnalysisClassifier_serviceTypeIsProtSeqAnalysis(
423           String classifier) throws IOException
424   {
425     var service = new SlivkaService(URI.create("http://example.org/"),
426             "example", "name", "description", "author", "1.0", "MIT",
427             List.of(classifier), List.of(), List.of(), null);
428     when(clientMock.getServices()).thenReturn(List.of(service));
429     when(clientMock.getUrl()).thenReturn(URI.create("http://example.org/"));
430     var discoverer = new SlivkaWSDiscoverer(url -> clientMock);
431     var webServices = discoverer
432             .fetchServices(new URL("http://example.org/"));
433     assertThat(webServices, hasSize(1));
434     assertThat(webServices.get(0).getCategory(),
435             equalTo("Protein Disorder"));
436     assertThat(webServices.get(0).getActionClass(),
437             typeCompatibleWith(AnnotationAction.class));
438   }
439
440   @DataProvider
441   public Object[] validProteinSecondaryStructurePredictionClassifiers()
442   {
443     return new Object[] {
444         "Operation ;: Analysis :: Protein secondary structure prediction",
445         "Operation :: Analysis :: Structure analysis :: Protein structure analysis :: Protein secondary structure analysis :: Protein secondary structure prediction",
446         "Operation :: Analysis :: Sequence analysis :: Protein sequence analysis :: Protein feature detection :: Protein secondary structure prediction",
447         "Operation :: Analysis :: Sequence analysis :: Protein sequence analysis :: Protein secondary structure prediction",
448         "Operation :: Prediction and recognition :: Protein secondary structure prediction",
449         "Operation :: Prediction and recognition :: Protein feature detection :: Protein secondary structure prediction", };
450   }
451
452   @Test(
453     enabled = false, // sec. str. pred. not implemented for slivka
454     dataProvider = "validProteinSecondaryStructurePredictionClassifiers")
455   public void testFetchServices_proteinSecStrPredClassifier_serviceTypeIsProtSecStrPred(
456           String classifier) throws IOException
457   {
458     var service = new SlivkaService(URI.create("http://example.org/"),
459             "example", "name", "description", "author", "1.0", "MIT",
460             List.of(classifier), List.of(), List.of(), null);
461     when(clientMock.getServices()).thenReturn(List.of(service));
462     when(clientMock.getUrl()).thenReturn(URI.create("http://example.org/"));
463     var discoverer = new SlivkaWSDiscoverer(url -> clientMock);
464     var webServices = discoverer
465             .fetchServices(new URL("http://example.org/"));
466     assertThat(webServices, hasSize(1));
467     assertThat(webServices.get(0).getCategory(),
468             equalTo("Protein Disorder"));
469     assertThat(webServices.get(0).getActionClass(),
470             typeCompatibleWith(AnnotationAction.class));
471   }
472 }