diff --git a/geoserver_acceptance_tests/tests/test_cascaded_stores.py b/geoserver_acceptance_tests/tests/test_cascaded_stores.py index 52165a2..88fa8da 100644 --- a/geoserver_acceptance_tests/tests/test_cascaded_stores.py +++ b/geoserver_acceptance_tests/tests/test_cascaded_stores.py @@ -120,3 +120,99 @@ def test_cascaded_wmts(geoserver_factory): ) assert content == "" assert status == 200 + + +def test_cascaded_wms_republish_keeps_the_layer(geoserver_factory): + """Publishing a cascaded WMS layer that already exists updates it instead of replacing it.""" + workspace = "test_cascaded_wms_republish" + wms_store = "test_cascaded_wms_republish_store" + wms_url = ( + "https://wms.geo.admin.ch/?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetCapabilities" + ) + wms_layer = "ch.swisstopo.swissboundaries3d-gemeinde-flaeche.fill" + title = "title set outside the publish payload" + geoserver: GeoServerCloud = geoserver_factory(workspace) + + # Create WMS store + content, status = geoserver.create_wms_store( + workspace_name=workspace, + wms_store_name=wms_store, + capabilities_url=wms_url, + ) + assert status == 201 + + # Publish layer + content, status = geoserver.create_wms_layer( + workspace_name=workspace, + wms_store_name=wms_store, + native_layer_name=wms_layer, + ) + assert status == 201 + + # Set a title, a setting the publish payload never sends + geoserver.rest_service.rest_client.put( + f"/rest/workspaces/{workspace}/wmsstores/{wms_store}/wmslayers/{wms_layer}.json", + json={"wmsLayer": {"title": title}}, + ) + + # Publish the same layer again + content, status = geoserver.create_wms_layer( + workspace_name=workspace, + wms_store_name=wms_store, + native_layer_name=wms_layer, + ) + assert status == 200 + + # The title survives, which a replaced layer would not have + content, status = geoserver.get_wms_layer( + workspace_name=workspace, wms_store_name=wms_store, wms_layer_name=wms_layer + ) + assert status == 200 + assert content["title"] == title + + +def test_cascaded_wmts_republish_keeps_the_layer(geoserver_factory): + """Publishing a cascaded WMTS layer that already exists updates it instead of replacing it.""" + workspace = "test_cascaded_wmts_republish" + wmts_store = "test_cascaded_wmts_republish_store" + wmts_url = "https://wmts.geo.admin.ch/EPSG/4326/1.0.0/WMTSCapabilities.xml" + wmts_layer = "ch.swisstopo.pixelkarte-grau" + title = "title set outside the publish payload" + geoserver: GeoServerCloud = geoserver_factory(workspace) + layer_path = ( + f"/rest/workspaces/{workspace}/wmtsstores/{wmts_store}/layers/{wmts_layer}.json" + ) + + # Create WMTS store + content, status = geoserver.create_wmts_store( + workspace_name=workspace, + name=wmts_store, + capabilities=wmts_url, + ) + assert status == 201 + + # Publish layer + content, status = geoserver.create_wmts_layer( + workspace_name=workspace, + wmts_store=wmts_store, + native_layer=wmts_layer, + ) + assert status == 201 + + # Set a title, a setting the publish payload never sends + geoserver.rest_service.rest_client.put( + layer_path, json={"wmtsLayer": {"title": title}} + ) + + # Publish the same layer again + content, status = geoserver.create_wmts_layer( + workspace_name=workspace, + wmts_store=wmts_store, + native_layer=wmts_layer, + ) + assert status == 200 + + # The title survives, which a replaced layer would not have + response = geoserver.rest_service.rest_client.get(layer_path) + assert response.status_code == 200 + assert response.json()["wmtsLayer"]["title"] == title diff --git a/geoservercloud/geoservercloud.py b/geoservercloud/geoservercloud.py index ab4d844..23914f9 100644 --- a/geoservercloud/geoservercloud.py +++ b/geoservercloud/geoservercloud.py @@ -801,7 +801,7 @@ def create_wms_layer( ) -> tuple[str, int]: """ Publish a remote WMS layer. - If it already exists, delete and recreate it (update is not supported by GeoServer) + If it already exists, update it in place, keeping the fields absent from the payload. :param workspace_name: Name of the workspace :type workspace_name: str @@ -1477,7 +1477,8 @@ def create_wmts_layer( international_abstract: dict[str, str] | None = None, ) -> tuple[str, int]: """ - Publish a remote WMTS layer (first delete it if it already exists) + Publish a remote WMTS layer. + If it already exists, update it in place, keeping the fields absent from the payload. :param workspace_name: Name of the workspace :type workspace_name: str diff --git a/geoservercloud/services/restservice.py b/geoservercloud/services/restservice.py index 1dafb88..d9c713f 100644 --- a/geoservercloud/services/restservice.py +++ b/geoservercloud/services/restservice.py @@ -194,14 +194,17 @@ def get_wms_layer( def create_wms_layer( self, workspace_name: str, wms_store_name: str, wms_layer: WmsLayer ) -> tuple[str, int]: - if self.resource_exists( - self.rest_endpoints.wmslayer(workspace_name, wms_store_name, wms_layer.name) - ): - self.delete_wms_layer(workspace_name, wms_store_name, wms_layer.name) - response: Response = self.rest_client.post( - self.rest_endpoints.wmslayers(workspace_name, wms_store_name), - json=wms_layer.post_payload(), + resource_path: str = self.rest_endpoints.wmslayer( + workspace_name, wms_store_name, wms_layer.name ) + response: Response + if not self.resource_exists(resource_path): + response = self.rest_client.post( + self.rest_endpoints.wmslayers(workspace_name, wms_store_name), + json=wms_layer.post_payload(), + ) + else: + response = self.rest_client.put(resource_path, json=wms_layer.put_payload()) return response.content.decode(), response.status_code def delete_wms_layer( @@ -252,16 +255,6 @@ def create_wmts_layer( international_title: dict[str, str] | None = None, international_abstract: dict[str, str] | None = None, ) -> tuple[str, int]: - resource_path: str = self.rest_endpoints.wmtslayer( - workspace_name, wmts_store, published_layer - ) - if self.resource_exists(resource_path): - self.rest_client.delete( - resource_path, - params={"recurse": "true"}, - ) - # Also delete the corresponding GWC layer (delete is not cascaded when using REST API) - self.delete_gwc_layer(workspace_name, published_layer) capabilities_url: str = ( self.rest_client.get( self.rest_endpoints.wmtsstore(workspace_name, wmts_store) @@ -283,9 +276,16 @@ def create_wmts_layer( international_abstract=international_abstract, ) - response: Response = self.rest_client.post( - self.rest_endpoints.wmtslayers(workspace_name, wmts_store), json=payload + resource_path: str = self.rest_endpoints.wmtslayer( + workspace_name, wmts_store, published_layer ) + response: Response + if not self.resource_exists(resource_path): + response = self.rest_client.post( + self.rest_endpoints.wmtslayers(workspace_name, wmts_store), json=payload + ) + else: + response = self.rest_client.put(resource_path, json=payload) return response.content.decode(), response.status_code def get_gwc_layer( diff --git a/tests/test_cascaded_wmts.py b/tests/test_cascaded_wmts.py index a83b563..b0e1afe 100644 --- a/tests/test_cascaded_wmts.py +++ b/tests/test_cascaded_wmts.py @@ -176,15 +176,6 @@ def test_create_wmts_layer_already_exists( f"{geoserver.url}/rest/workspaces/{WORKSPACE}/wmtsstores/{STORE}/layers/{LAYER}.json", status=200, ) - rsps.delete( - f"{geoserver.url}/rest/workspaces/{WORKSPACE}/wmtsstores/{STORE}/layers/{LAYER}.json", - status=200, - match=[responses.matchers.query_param_matcher({"recurse": "true"})], - ) - rsps.delete( - url=f"{geoserver.url}/gwc/rest/layers/{WORKSPACE}:{LAYER}.json", - status=200, - ) rsps.get( f"{geoserver.url}/rest/workspaces/{WORKSPACE}/wmtsstores/{STORE}.json", status=200, @@ -196,8 +187,8 @@ def test_create_wmts_layer_already_exists( body=CAPABILITIES, headers={"Content-Type": "application/xml"}, ) - rsps.post( - f"{geoserver.url}/rest/workspaces/{WORKSPACE}/wmtsstores/{STORE}/layers.json", + rsps.put( + f"{geoserver.url}/rest/workspaces/{WORKSPACE}/wmtsstores/{STORE}/layers/{LAYER}.json", match=[responses.matchers.json_params_matcher(wmts_layer_payload)], status=201, body=b"test_layer", diff --git a/tests/test_wms_layer.py b/tests/test_wms_layer.py index a30384f..60a0074 100644 --- a/tests/test_wms_layer.py +++ b/tests/test_wms_layer.py @@ -126,16 +126,10 @@ def test_update_wms_layer( f"{geoserver.url}/rest/workspaces/{WORKSPACE}/wmsstores/{STORE}/wmslayers/{LAYER}.json", status=200, ) - rsps.delete( + rsps.put( f"{geoserver.url}/rest/workspaces/{WORKSPACE}/wmsstores/{STORE}/wmslayers/{LAYER}.json", - status=200, - body=b"", - match=[responses.matchers.query_param_matcher({"recurse": "true"})], - ) - rsps.post( - f"{geoserver.url}/rest/workspaces/{WORKSPACE}/wmsstores/{STORE}/wmslayers.json", match=[responses.matchers.json_params_matcher(wms_layer_post_payload)], - status=201, + status=200, body=b"", ) content, code = geoserver.create_wms_layer( @@ -145,7 +139,7 @@ def test_update_wms_layer( ) assert content == "" - assert code == 201 + assert code == 200 def test_delete_wms_layer(geoserver: GeoServerCloud) -> None: