diff --git a/server/src/main/java/au/org/aodn/ogcapi/server/core/model/ogc/wfs/DownloadableFieldModel.java b/server/src/main/java/au/org/aodn/ogcapi/server/core/model/ogc/wfs/DownloadableFieldModel.java index a8cd6201..7db686d2 100644 --- a/server/src/main/java/au/org/aodn/ogcapi/server/core/model/ogc/wfs/DownloadableFieldModel.java +++ b/server/src/main/java/au/org/aodn/ogcapi/server/core/model/ogc/wfs/DownloadableFieldModel.java @@ -7,6 +7,11 @@ @Data @Builder public class DownloadableFieldModel { + public enum Dimension { + range, + single + } + @JsonProperty("label") private String label; @@ -15,4 +20,8 @@ public class DownloadableFieldModel { @JsonProperty("name") private String name; + // Indicate if this a single time point or range? + @Builder.Default + @JsonProperty("view_dimension") + private Dimension viewDimension = Dimension.range; } diff --git a/server/src/main/java/au/org/aodn/ogcapi/server/core/service/wms/WmsServer.java b/server/src/main/java/au/org/aodn/ogcapi/server/core/service/wms/WmsServer.java index 703f637b..1f143de2 100644 --- a/server/src/main/java/au/org/aodn/ogcapi/server/core/service/wms/WmsServer.java +++ b/server/src/main/java/au/org/aodn/ogcapi/server/core/service/wms/WmsServer.java @@ -29,6 +29,7 @@ import java.math.BigDecimal; import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -114,8 +115,8 @@ else if(wfsUrlComponents.getQueryParams().get("CQL_FILTER") != null) { if (range.size() == 2) { // Due to no standard name, we try our best to guess if 2 dateTime field, range mean we found start/end date String[] d = request.getDatetime().split("/"); - String guess1 = target.get(0).getName(); - String guess2 = target.get(1).getName(); + String guess1 = range.get(0).getName(); + String guess2 = range.get(1).getName(); if ((guess1.contains("start") || guess1.contains("min")) && (guess2.contains("end") || guess2.contains("max"))) { String timeCql = String.format("CQL_FILTER=%s >= %s AND %s <= %s", guess1, d[0], guess2, d[1]); @@ -174,6 +175,8 @@ protected List createMapQueryUrl(String url, String uuid, FeatureRequest param.putAll(wmsDefaultParam.getWms()); } else if (pathSegments.get(pathSegments.size() - 1).equalsIgnoreCase("ncwms")) { param.putAll(wmsDefaultParam.getNcwms()); + // Specific for ncWMS, check comment below related to CQL_FILTER + param.put("TIME", request.getDatetime()); } // Now we add the missing argument from the request @@ -215,12 +218,23 @@ protected List createMapQueryUrl(String url, String uuid, FeatureRequest builder.queryParam(key, value); } }); - // Cannot set cql in param as it contains value like "/" which is not allow in UriComponent checks - // but server must use "/" in param and cannot encode it to %2F, so to avoid exception in the - // build() call, we append the cql after the construction. - String target = String.join("&", builder.build().toUriString(), createCQLFilter(uuid, request)); - log.debug("Url to wms geoserver {}", target); - urls.add(target); + if (pathSegments.get(pathSegments.size() - 1).equalsIgnoreCase("wms")) { + // No, ncWMS (including GeoServer extension) does not support CQL_FILTER. + // It focuses on NetCDF gridded data with parameters like TIME, ELEVATION, COLORSCALERANGE, + // STYLES (palettes), NUMCOLORBANDS. CQL_FILTER is a GeoServer vendor parameter for vector + // filtering, not implemented in ncWMS. So we only add CQL if it is WMS + + // Cannot set cql in param as it contains value like "/" which is not allow in UriComponent checks + // but server must use "/" in param and cannot encode it to %2F, so to avoid exception in the + // build() call, we append the cql after the construction. + String target = String.join("&", builder.build().toUriString(), createCQLFilter(uuid, request)); + log.debug("Url to wms geoserver {}", target); + urls.add(target); + } else if (pathSegments.get(pathSegments.size() - 1).equalsIgnoreCase("ncwms")) { + String target = builder.build().toUriString(); + log.debug("Url to ncWms geoserver {}", target); + urls.add(target); + } return urls; } @@ -419,7 +433,14 @@ protected Optional getMapServerUrl(String collectionId, FeatureRequest r return Optional.empty(); } } - + /** + * Fetch the popup content when use click on the map, so it is kind of features associated with the map + * @param collectionId - The uuid + * @param request - The request object for the query + * @return - Usually an HTML or JSON from the server, not a good type of return type but this is from legacy system + * @throws JsonProcessingException - Not expected + * @throws URISyntaxException - Not expected + */ public FeatureInfoResponse getMapFeatures(String collectionId, FeatureRequest request) throws JsonProcessingException, URISyntaxException { Optional mapServerUrl = getMapServerUrl(collectionId, request); @@ -486,7 +507,6 @@ public DescribeLayerResponse describeLayer(String collectionId, FeatureRequest r @Cacheable(value = CACHE_WMS_MAP_TILE) public byte[] getMapTile(String collectionId, FeatureRequest request) throws URISyntaxException { Optional mapServerUrl = getMapServerUrl(collectionId, request); - log.debug("map tile request for uuid {} layername {}", collectionId, request.getLayerName()); if (mapServerUrl.isPresent()) { List urls = createMapQueryUrl(mapServerUrl.get(), collectionId, request); // Try one by one, we exit when any works @@ -494,7 +514,13 @@ public byte[] getMapTile(String collectionId, FeatureRequest request) throws URI log.debug("map tile request for layer name {} url {} ", request.getLayerName(), url); ResponseEntity response = restTemplateUtils.handleRedirect(url, restTemplate.exchange(url, HttpMethod.GET, pretendUserEntity, byte[].class), byte[].class, pretendUserEntity); if (response.getStatusCode().is2xxSuccessful()) { - return response.getBody(); + if (response.getHeaders().getContentType() != null && response.getHeaders().getContentType().getType().equals("image")) { + return response.getBody(); + } + else { + // Something wrong from the server likely syntax error + throw new URISyntaxException(response.getBody() != null ? new String(response.getBody(), StandardCharsets.UTF_8) : "", url); + } } } } @@ -509,15 +535,47 @@ public byte[] getMapTile(String collectionId, FeatureRequest request) throws URI public List getDownloadableFields(String collectionId, FeatureRequest request) { DescribeLayerResponse response = this.describeLayer(collectionId, request); + List result; if (response != null && response.getLayerDescription().getWfs() != null) { // If we are able to find the wfs server and real layer name based on wms layer, then use it FeatureRequest modified = FeatureRequest.builder().layerName(response.getLayerDescription().getQuery().getTypeName()).build(); - return wfsServer.getDownloadableFields(collectionId, modified, response.getLayerDescription().getWfs()); + result = wfsServer.getDownloadableFields(collectionId, modified, response.getLayerDescription().getWfs()); } else { // We trust what is found inside the elastic search metadata - return wfsServer.getDownloadableFields(collectionId, request, null); + result = wfsServer.getDownloadableFields(collectionId, request, null); } + + Optional wmsUrl = getMapServerUrl(collectionId, request); + if (wmsUrl.isPresent() && wmsUrl.get().contains("/ncwms")) { + // Special case for ncWMS where it is a gridded data and support TIME param, not CQL_FILTERS which only works + // for wms, we need to test if this TIME field is a single time=2012-01-01 .. or support range + // time=2012-01-01/2022-01-01, the only way to do it now is to issue a query and test it. + // This is likely the only case for imos custom ncwms server + FeatureRequest test = FeatureRequest.builder() + .layerName(request.getLayerName()) + .bbox(List.of(BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ONE, BigDecimal.ONE)) + .datetime("2020-01-01/2020-01-03") // Date value not important, with "/" indicate range + .build(); + try { + // Post this to ncwms, if this is not a range TIME, then it will + self.getMapTile(collectionId, test); + // Mark the time field dimension to RANGE + result.stream() + .filter(f -> f.getName().equalsIgnoreCase("time")) + .findFirst() + .ifPresent(a -> a.setViewDimension(DownloadableFieldModel.Dimension.range)); + } + catch(Exception uriSyntaxException) { + // Not support range + result.stream() + .filter(f -> f.getName().equalsIgnoreCase("time")) + .findFirst() + .ifPresent(a -> a.setViewDimension(DownloadableFieldModel.Dimension.single)); + } + } + + return result; } /** * Fetch raw layers from WMS GetCapabilities - cached by URL, that is query all layer supported by this WMS server. diff --git a/server/src/main/java/au/org/aodn/ogcapi/server/features/RestApi.java b/server/src/main/java/au/org/aodn/ogcapi/server/features/RestApi.java index e088ec01..0364c65f 100644 --- a/server/src/main/java/au/org/aodn/ogcapi/server/features/RestApi.java +++ b/server/src/main/java/au/org/aodn/ogcapi/server/features/RestApi.java @@ -129,7 +129,7 @@ public ResponseEntity getFeature( case wms_downloadable_fields -> { return (request.getLayerName() == null || request.getLayerName().isEmpty()) ? ResponseEntity.badRequest().build() : - featuresService.getWmsDownloadableFields(collectionId, request); + featuresService.getWmsFieldsProperties(collectionId, request); } case wms_map_tile -> { return featuresService.getWmsMapTile(collectionId, request); diff --git a/server/src/main/java/au/org/aodn/ogcapi/server/features/RestServices.java b/server/src/main/java/au/org/aodn/ogcapi/server/features/RestServices.java index 702e2626..e7fab86b 100644 --- a/server/src/main/java/au/org/aodn/ogcapi/server/features/RestServices.java +++ b/server/src/main/java/au/org/aodn/ogcapi/server/features/RestServices.java @@ -78,6 +78,7 @@ public ResponseEntity getWmsMapFeature(String collectionId, public ResponseEntity getWmsMapTile(String collectionId, FeatureRequest request) { try { return ResponseEntity.ok() + .contentType(MediaType.IMAGE_PNG) .body(wmsServer.getMapTile(collectionId, request)); } catch (Throwable e) { throw new RuntimeException(e); @@ -103,15 +104,14 @@ public ResponseEntity getWfsDownloadableFields(String collectionId, FeatureRe ResponseEntity.notFound().build() : ResponseEntity.ok(result); } - /** - * This is used to get the downloadable fields from wfs given a wms layer + * This is used to get the downloadable fields / viewable fields given a wms layer * * @param collectionId - The uuid of dataset * @param request - Request to get field given a WMS layer name * @return - The downloadable field name, or UNAUTHORIZED if it is not in white list */ - public ResponseEntity getWmsDownloadableFields(String collectionId, FeatureRequest request) { + public ResponseEntity getWmsFieldsProperties(String collectionId, FeatureRequest request) { if (request.getLayerName() == null || request.getLayerName().isEmpty()) { log.info("Layer name cannot be null or empty"); diff --git a/server/src/main/resources/application.yaml b/server/src/main/resources/application.yaml index 2f9563aa..5c894e9e 100644 --- a/server/src/main/resources/application.yaml +++ b/server/src/main/resources/application.yaml @@ -91,6 +91,7 @@ wms-default-param: STYLES: "" QUERYABLE: "true" CRS: "EPSG:3857" + NUMCOLORBANDS: "253" WIDTH: 256 HEIGHT: 256 allow-id: # Full list diff --git a/server/src/test/java/au/org/aodn/ogcapi/server/core/service/wms/WmsServerTest.java b/server/src/test/java/au/org/aodn/ogcapi/server/core/service/wms/WmsServerTest.java index 387cd458..3c0d4a82 100644 --- a/server/src/test/java/au/org/aodn/ogcapi/server/core/service/wms/WmsServerTest.java +++ b/server/src/test/java/au/org/aodn/ogcapi/server/core/service/wms/WmsServerTest.java @@ -6,6 +6,7 @@ import au.org.aodn.ogcapi.server.core.model.LinkModel; import au.org.aodn.ogcapi.server.core.model.StacCollectionModel; import au.org.aodn.ogcapi.server.core.model.ogc.FeatureRequest; +import au.org.aodn.ogcapi.server.core.model.ogc.wfs.DownloadableFieldModel; import au.org.aodn.ogcapi.server.core.model.ogc.wms.DescribeLayerResponse; import au.org.aodn.ogcapi.server.core.service.ElasticSearchBase; import au.org.aodn.ogcapi.server.core.service.Search; @@ -21,6 +22,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.HttpEntity; import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.web.client.RestTemplate; @@ -252,13 +254,14 @@ public void verifyHandleServiceExceptionReportCorrect() { .build() ); - String r = "\n" + - "\n" + - "\n" + - " \n" + - " srs_ghrsst_l4_gamssa_url/: no such layer on this server\n" + - "\n" + - ""; + String r = """ + + + + + srs_ghrsst_l4_gamssa_url/: no such layer on this server + + """; when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), eq(entity), eq(String.class))) .thenReturn(ResponseEntity.ok(r)); @@ -275,13 +278,14 @@ public void verifyHandleServiceExceptionReportCorrect() { @Test public void verifyParseCorrect() throws JsonProcessingException { DescribeLayerResponse value = wmsServer.xmlMapper.readValue( - "\n" + - "\n" + - "\n" + - " \n" + - " \n" + - " \n" + - "", DescribeLayerResponse.class); + """ + + + + + + + """, DescribeLayerResponse.class); assertEquals("imos:srs_ghrsst_l4_gamssa_url", value.getLayerDescription().getName()); assertEquals("https://geoserver-123.aodn.org.au/geoserver/wfs?", value.getLayerDescription().getWfs()); @@ -289,10 +293,9 @@ public void verifyParseCorrect() throws JsonProcessingException { } /** * Test with only one dateTime field in the describe layer - * @throws JsonProcessingException - Not expected */ @Test - public void verifyCreateCQLSingleDateTime() throws JsonProcessingException { + public void verifyCreateCQLSingleDateTime() { String uuid = "uuid1"; String layer = "imos:srs_ghrsst_l4_gamssa_url"; FeatureRequest request = FeatureRequest.builder() @@ -316,23 +319,24 @@ public void verifyCreateCQLSingleDateTime() throws JsonProcessingException { ); // This sample contains 1 dateTime field String value = - "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - ""; + """ + + + + + + + + + + + + + + + + + """; when(search.searchCollections(eq(uuid))) .thenReturn(stac); @@ -350,10 +354,9 @@ public void verifyCreateCQLSingleDateTime() throws JsonProcessingException { } /** * Test with only one dateTime field in the describe layer with predefined cql - * @throws JsonProcessingException - Not expected */ @Test - public void verifyCreateCQLSingleDateTimeWithCQL() throws JsonProcessingException { + public void verifyCreateCQLSingleDateTimeWithCQL() { String uuid = "uuid1"; String layer = "imos:srs_ghrsst_l4_gamssa_url"; FeatureRequest request = FeatureRequest.builder() @@ -377,23 +380,24 @@ public void verifyCreateCQLSingleDateTimeWithCQL() throws JsonProcessingExceptio ); // This sample contains 1 dateTime field String value = - "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - ""; + """ + + + + + + + + + + + + + + + + + """; when(search.searchCollections(eq(uuid))) .thenReturn(stac); @@ -411,10 +415,9 @@ public void verifyCreateCQLSingleDateTimeWithCQL() throws JsonProcessingExceptio } /** * Test with only two or more dateTime field in the describe layer with predefined cql - * @throws JsonProcessingException - Not expected */ @Test - public void verifyCreateCQLMultiDateTimeWithCQL() throws JsonProcessingException { + public void verifyCreateCQLMultiDateTimeWithCQL() { String uuid = "uuid1"; String layer = "aatams_sattag_qc_ctd_profile_map"; FeatureRequest request = FeatureRequest.builder() @@ -438,56 +441,57 @@ public void verifyCreateCQLMultiDateTimeWithCQL() throws JsonProcessingException ); // This sample contains 1 dateTime field String value = - "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - ""; + """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + """; when(search.searchCollections(eq(uuid))) .thenReturn(stac); @@ -505,10 +509,9 @@ public void verifyCreateCQLMultiDateTimeWithCQL() throws JsonProcessingException } /** * Test where dateTime is a range start_xxx end_xxx - * @throws JsonProcessingException - Not expected */ @Test - public void verifyCreateCQLRangeDateTimeWithCQL() throws JsonProcessingException { + public void verifyCreateCQLRangeDateTimeWithCQL() { String uuid = "uuid1"; String layer = "aatams_sattag_qc_ctd_profile_map"; FeatureRequest request = FeatureRequest.builder() @@ -532,56 +535,57 @@ public void verifyCreateCQLRangeDateTimeWithCQL() throws JsonProcessingException ); // This sample contains 1 dateTime field String value = - "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - ""; + """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + """; when(search.searchCollections(eq(uuid))) .thenReturn(stac); @@ -597,4 +601,181 @@ public void verifyCreateCQLRangeDateTimeWithCQL() throws JsonProcessingException assertEquals("CQL_FILTER=start_juld >= 2023-01-01 AND end_juld <= 2023-12-31 AND set_code=1234", result); } + + @Test + public void verifyNcWmsSupportRangeCheck() { + // Arrange + String uuid = "test-uuid"; + FeatureRequest request = FeatureRequest.builder() + .layerName("test/layer") + .build(); + + ElasticSearchBase.SearchResult stac = new ElasticSearchBase.SearchResult<>(); + stac.setCollections(new ArrayList<>()); + stac.getCollections().add( + StacCollectionModel + .builder() + .links(List.of( + LinkModel.builder() + .href("http://geoserver-123.aodn.org.au/geoserver/ncwms") + .title(request.getLayerName()) + .aiGroup(WMS_LINK_MARKER) + .build()) + ) + .build() + ); + + when(search.searchCollections(eq(uuid))) + .thenReturn(stac); + + // Simulate successful range query (no exception), content is not important in this test just need something parsable + String wmsReply = """ + + + + + + + """; + + when(restTemplate.exchange( + eq("http://geoserver-123.aodn.org.au/geoserver/ncwms?VERSION=1.1.1&SERVICE=WMS&REQUEST=DescribeLayer&LAYERS=test/layer"), + eq(HttpMethod.GET), + eq(entity), + eq(String.class)) + ).thenReturn(ResponseEntity.ok(wmsReply)); + + String wfsReply = """ + + + + + + + + + + + + + + + + + """; + + when(restTemplate.exchange( + eq("https://geoserver-123.aodn.org.au/geoserver/wfs?VERSION=2.0.0&SERVICE=WFS&TYPENAME=imos:srs_ghrsst_l3s_M_1d_ngt_url&REQUEST=DescribeFeatureType"), + eq(HttpMethod.GET), + eq(entity), + eq(String.class)) + ).thenReturn(ResponseEntity.ok(wfsReply)); + + // Make sure no exception throw, that means it support TIME=2020-01-01/2020-01-03 range + when(restTemplate.exchange( + eq("https://geoserver-123.aodn.org.au/geoserver/ncwms?FORMAT=image/png&CRS=EPSG:3857&STYLES=&QUERYABLE=true&WIDTH=256&TIME=2020-01-01/2020-01-03&HEIGHT=256&LAYERS=test/layer&EXCEPTIONS=application/vnd.ogc.se_xml&TILED=true&REQUEST=GetMap&BBOX=0,0,1,1&VERSION=1.3.0&SERVICE=ncwms&TRANSPARENT=TRUE&NUMCOLORBANDS=253"), + eq(HttpMethod.GET), + eq(entity), + eq(byte[].class)) + ).thenReturn( + ResponseEntity.ok() + .contentType(MediaType.IMAGE_PNG) + .body(new byte[10]) + ); + + // Act + List result = wmsServer.getDownloadableFields(uuid, request); + + // Assert + assertEquals(DownloadableFieldModel.Dimension.range, result.get(0).getViewDimension()); + } + + @Test + public void verifyNcWmsSupportSingleCheck() { + // Arrange + String uuid = "test-uuid"; + FeatureRequest request = FeatureRequest.builder() + .layerName("test/layer") + .build(); + + ElasticSearchBase.SearchResult stac = new ElasticSearchBase.SearchResult<>(); + stac.setCollections(new ArrayList<>()); + stac.getCollections().add( + StacCollectionModel + .builder() + .links(List.of( + LinkModel.builder() + .href("http://geoserver-123.aodn.org.au/geoserver/ncwms") + .title(request.getLayerName()) + .aiGroup(WMS_LINK_MARKER) + .build()) + ) + .build() + ); + + when(search.searchCollections(eq(uuid))) + .thenReturn(stac); + + // Simulate successful range query (no exception), content is not important in this test just need something parsable + String wmsReply = """ + + + + + + + """; + + when(restTemplate.exchange( + eq("http://geoserver-123.aodn.org.au/geoserver/ncwms?VERSION=1.1.1&SERVICE=WMS&REQUEST=DescribeLayer&LAYERS=test/layer"), + eq(HttpMethod.GET), + eq(entity), + eq(String.class)) + ).thenReturn(ResponseEntity.ok(wmsReply)); + + String wfsReply = """ + + + + + + + + + + + + + + + + + """; + + when(restTemplate.exchange( + eq("https://geoserver-123.aodn.org.au/geoserver/wfs?VERSION=2.0.0&SERVICE=WFS&TYPENAME=imos:srs_ghrsst_l3s_M_1d_ngt_url&REQUEST=DescribeFeatureType"), + eq(HttpMethod.GET), + eq(entity), + eq(String.class)) + ).thenReturn(ResponseEntity.ok(wfsReply)); + + // Make sure no exception throw, that means it support TIME=2020-01-01/2020-01-03 range + when(restTemplate.exchange( + eq("https://geoserver-123.aodn.org.au/geoserver/ncwms?FORMAT=image/png&CRS=EPSG:3857&STYLES=&QUERYABLE=true&WIDTH=256&TIME=2020-01-01/2020-01-03&HEIGHT=256&LAYERS=test/layer&EXCEPTIONS=application/vnd.ogc.se_xml&TILED=true&REQUEST=GetMap&BBOX=0,0,1,1&VERSION=1.3.0&SERVICE=ncwms&TRANSPARENT=TRUE&NUMCOLORBANDS=253"), + eq(HttpMethod.GET), + eq(entity), + eq(byte[].class)) + ).thenReturn( + // A non image type means the server return an XML of error, and signal not support range. + ResponseEntity.ok() + .contentType(MediaType.TEXT_XML) + .body(null) + ); + + // Act + List result = wmsServer.getDownloadableFields(uuid, request); + + // Assert + assertEquals(DownloadableFieldModel.Dimension.single, result.get(0).getViewDimension()); + } }