[Git][debian-gis-team/mapproxy][master] 5 commits: New upstream version 3.0.0+dfsg

Bas Couwenberg (@sebastic) gitlab at salsa.debian.org
Thu Aug 29 17:15:40 BST 2024



Bas Couwenberg pushed to branch master at Debian GIS Project / mapproxy


Commits:
c970fb93 by Bas Couwenberg at 2024-08-29T15:00:35+02:00
New upstream version 3.0.0+dfsg
- - - - -
76a521cc by Bas Couwenberg at 2024-08-29T15:00:54+02:00
New upstream version 3.0.1+dfsg
- - - - -
2736443b by Bas Couwenberg at 2024-08-29T15:00:55+02:00
Update upstream source from tag 'upstream/3.0.1+dfsg'

Update to upstream version '3.0.1+dfsg'
with Debian dir 30a55e25c1061f2f5dcce1460212fad85e9f1d67
- - - - -
649d99d8 by Bas Couwenberg at 2024-08-29T15:01:50+02:00
New upstream release.

- - - - -
6c99f6b6 by Bas Couwenberg at 2024-08-29T18:09:35+02:00
Set distribution to unstable.

- - - - -


28 changed files:

- .github/workflows/dockerbuild.yml
- .github/workflows/ghpages.yml
- .github/workflows/test.yml
- CHANGES.txt
- debian/changelog
- doc/caches.rst
- doc/configuration.rst
- mapproxy/config/loader.py
- mapproxy/config_template/base_config/mapproxy.yaml
- mapproxy/exception.py
- mapproxy/service/demo.py
- mapproxy/service/ows.py
- mapproxy/service/templates/demo/demo.html
- + mapproxy/service/templates/ows_exception.xml
- mapproxy/test/system/fixture/seed.yaml
- mapproxy/test/system/test_demo.py
- mapproxy/test/system/test_demo_with_extra_service.py
- mapproxy/test/system/test_legendgraphic.py
- mapproxy/test/system/test_renderd_client.py
- mapproxy/test/system/test_response_headers.py
- mapproxy/test/system/test_seed.py
- mapproxy/test/system/test_source_errors.py
- mapproxy/test/system/test_wms.py
- mapproxy/test/system/test_wmsc.py
- mapproxy/test/unit/test_client.py
- mapproxy/test/unit/test_exceptions.py
- requirements-tests.txt
- setup.py


Changes:

=====================================
.github/workflows/dockerbuild.yml
=====================================
@@ -128,9 +128,9 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Run trivy
-        uses: aquasecurity/trivy-action at master
+        uses: aquasecurity/trivy-action at v0.24.0
         with:
-          format: 'table'
+          format: 'sarif'
           ignore-unfixed: true
           image-ref: ${{ needs.get-version.outputs.version }}
           output: 'trivy-results.sarif'


=====================================
.github/workflows/ghpages.yml
=====================================
@@ -43,7 +43,7 @@ jobs:
         | sort -V \
         | uniq \
         | jq -R -s -c 'split("\n")[:-1]' \
-        > docs/config/versions.json
+        > config/versions.json
 
     - name: Deploy config folder to GitHub Pages
       uses: JamesIves/github-pages-deploy-action at v4.5.0


=====================================
.github/workflows/test.yml
=====================================
@@ -60,7 +60,7 @@ jobs:
         python-version: ${{ matrix.python-version }}
 
     - name: Cache python deps 💾
-      uses: actions/cache at v3
+      uses: actions/cache at v4
       with:
         path: ~/.cache/pip
         key: ${{ runner.OS }}-python-${{ hashFiles('**/requirements-tests.txt') }}


=====================================
CHANGES.txt
=====================================
@@ -1,3 +1,35 @@
+3.0.1 2024-08-27
+~~~~~~~~~~~~~~~~
+
+Fixes:
+
+  - Fix capabilities links in demo page and config template
+  - Trivy report format
+
+
+3.0.0 2024-08-27
+~~~~~~~~~~~~~~~~
+
+Breaking:
+
+  - Add proper XML Exception when service parameter disabled / invalid or missing
+    In the past MapProxy returned WMS Capabilities when no explicit service parameter was given in
+    GetCapabilities requests. With this change, an OGC exception will be returned instead.
+    Invalid service types now also return a proper exception instead of an `internal error`.
+    Besides, the status code of exception responses now have a 400 or 500 code instead of 200
+    for better conformity.
+
+Maintenance:
+
+  - Add test for seeding sqlite cache
+  - Documentation updates and clarifications
+  - Dependency updates
+
+Fixes:
+
+  - Fix gh-pages workflow
+
+
 2.2.0 2024-07-24
 ~~~~~~~~~~~~~~~~
 


=====================================
debian/changelog
=====================================
@@ -1,8 +1,9 @@
-mapproxy (2.2.0+dfsg-2) UNRELEASED; urgency=medium
+mapproxy (3.0.1+dfsg-1) unstable; urgency=medium
 
+  * New upstream release.
   * Bump Standards-Version to 4.7.0, no changes.
 
- -- Bas Couwenberg <sebastic at debian.org>  Sun, 28 Jul 2024 19:44:37 +0200
+ -- Bas Couwenberg <sebastic at debian.org>  Thu, 29 Aug 2024 18:09:25 +0200
 
 mapproxy (2.2.0+dfsg-1) unstable; urgency=medium
 


=====================================
doc/caches.rst
=====================================
@@ -513,7 +513,7 @@ Available options:
   When set to ``true``, requests to S3 ``GetObject`` will be fetched via urllib2 instead of boto, which decreases response times. Defaults to ``false``.
 
 .. note::
-  The hierarchical ``directory_layouts`` can hit limitations of S3 *"if you are routinely processing 100 or more requests per second"*. ``directory_layout: reverse_tms`` can work around this limitation. Please read `S3 Request Rate and Performance Considerations <http://docs.aws.amazon.com/AmazonS3/latest/dev/request-rate-perf-considerations.html>`_ for more information on this issue.
+  The hierarchical ``directory_layouts`` can hit limitations of AWS S3 if you are routinely processing 3500 or more requests per second. ``directory_layout: reverse_tms`` can work around this limitation. Please read `S3 Request Rate and Performance Considerations <http://docs.aws.amazon.com/AmazonS3/latest/dev/request-rate-perf-considerations.html>`_ for more information on this issue.
 
 Example
 -------


=====================================
doc/configuration.rst
=====================================
@@ -664,13 +664,13 @@ Example:
 
 .. code-block:: yaml
 
-   caches:
-     osm_cache:
-     grids: ['osm_grid']
-     sources: [OSM]
-     disable_storage: false
-     refresh_before:
-       days: 1
+  caches:
+    osm_cache:
+      grids: ['osm_grid']
+      sources: [OSM]
+      disable_storage: false
+      refresh_before:
+        days: 1
 
 
 ``disable_storage``
@@ -685,7 +685,7 @@ even if the there are matching tiles in the cache. See :ref:`seed_only <wms_seed
 """""""""""""
 
 Directory where MapProxy should store tiles for this cache. Uses the value of ``globals.cache.base_dir`` by default. MapProxy will store each cache in a subdirectory named after the cache and the grid SRS (e.g. ``cachename_EPSG1234``).
-See :ref:`directory option<cache_file_directory>` on how configure a complete path.
+See :ref:`directory option<cache_file_directory>` on how to configure a complete path.
 
 ``cache``
 """""""""
@@ -1004,7 +1004,7 @@ The following options define how tiles are created and stored. Most options can
 .. _meta_size:
 
 ``meta_size``
-  MapProxy does not make a single request for every tile it needs, but it will request a large meta-tile that consist of multiple tiles. ``meta_size`` defines how large a meta-tile is. A ``meta_size`` of ``[4, 4]`` will request 16 tiles in one pass. With a tile size of 256x256 this will result in 1024x1024 requests to the source. Tiled sources are still requested tile by tile, but you can configure MapProxy to load multiple tiles in bulk with ``bulk_meta_tiles``.
+  MapProxy does not make a single request for every tile it needs, but it will request a large meta-tile that consist of multiple tiles. ``meta_size`` defines how large a meta-tile is. A ``meta_size`` of ``[4, 4]`` will request 16 tiles in one pass. With a tile size of 256x256 and 0 ``meta_buffer``, this will result in 1024x1024 requests to the source. (Note that the default value for ``meta_buffer`` is 80.) Tiled sources are still requested tile by tile, but you can configure MapProxy to load multiple tiles in bulk with ``bulk_meta_tiles``.
 
 
 .. _bulk_meta_tiles:
@@ -1017,7 +1017,7 @@ The following options define how tiles are created and stored. Most options can
 ``meta_buffer``
   MapProxy will increase the size of each meta-tile request by this number of
   pixels in each direction. This can solve cases where labels are cut-off at
-  the edge of tiles.
+  the edge of tiles. Defaults to 80.
 
 ``base_dir``
   The base directory where all cached tiles will be stored. The path can


=====================================
mapproxy/config/loader.py
=====================================
@@ -24,6 +24,7 @@ from mapproxy.util.py import memoize
 from mapproxy.config.spec import validate_options, add_source_to_mapproxy_yaml_spec, add_service_to_mapproxy_yaml_spec
 from mapproxy.config.validator import validate
 from mapproxy.config import load_default_config, finish_base_config, defaults
+from mapproxy.service.ows import OWSServer
 
 import os
 import sys
@@ -2063,9 +2064,7 @@ class ServiceConfiguration(ConfigurationBase):
                 else:
                     services.append(new_service)
 
-        if ows_services:
-            from mapproxy.service.ows import OWSServer
-            services.append(OWSServer(ows_services))
+        services.append(OWSServer(ows_services))
         return services
 
     def tile_layers(self, conf, use_grid_names=False):


=====================================
mapproxy/config_template/base_config/mapproxy.yaml
=====================================
@@ -10,7 +10,7 @@
 # Demo:
 #     http://localhost:8080/demo
 # WMS:
-#     capabilities: http://localhost:8080/service?REQUEST=GetCapabilities
+#     capabilities: http://localhost:8080/service?REQUEST=GetCapabilities&SERVICE=WMS
 # WMTS:
 #     capabilities: http://localhost:8080/wmts/1.0.0/WMTSCapabilities.xml
 #     first tile: http://localhost:8080/wmts/osm/webmercator/0/0/0.png


=====================================
mapproxy/exception.py
=====================================
@@ -19,6 +19,9 @@ Service exception handling (WMS exceptions, XML, in_image, etc.).
 from html import escape
 
 from mapproxy.response import Response
+from mapproxy.template import template_loader
+import mapproxy.service
+get_template = template_loader(mapproxy.service.__package__, 'templates')
 
 
 class RequestError(Exception):
@@ -29,13 +32,14 @@ class RequestError(Exception):
                     was valid (e.g. the source server is unreachable
     """
 
-    def __init__(self, message, code=None, request=None, internal=False, status=None):
+    def __init__(self, message, code=None, request=None, internal=False, status=None, locator=None):
         Exception.__init__(self, message)
         self.msg = message
         self.code = code
         self.request = request
         self.internal = internal
         self.status = status
+        self.locator = locator
 
     def render(self):
         """
@@ -93,7 +97,7 @@ class XMLExceptionHandler(ExceptionHandler):
     The content_type is sent as defined here.
     """
 
-    status_code = 200
+    status_code = 500
     """
     The HTTP status code.
     """
@@ -122,7 +126,11 @@ class XMLExceptionHandler(ExceptionHandler):
 
         :type request_error: `RequestError`
         """
-        status_code = self.status_codes.get(request_error.code, self.status_code)
+        if request_error.status is not None:
+            status_code = request_error.status
+        else:
+            status_code = self.status_codes.get(request_error.code, self.status_code)
+
         # escape &<> in error message (e.g. URL params)
         msg = escape(request_error.msg)
         result = self.template.substitute(exception=msg,
@@ -138,6 +146,34 @@ class XMLExceptionHandler(ExceptionHandler):
         return self.template_func(self.template_file)
 
 
+class OWSExceptionHandler(XMLExceptionHandler):
+    """
+    Exception handler for generic OWS ServiceExceptionReports
+    """
+    template_file = 'ows_exception.xml'
+    template_func = get_template
+    mimetype = 'text/xml'
+
+    def render(self, request_error):
+        """
+        Render the template of this exception handler. Passes the
+        ``request_error.msg``, ``request_error.locator`` and ``request_error.code`` to the template.
+
+        :type request_error: `RequestError`
+        """
+        if request_error.status is not None:
+            status_code = request_error.status
+        else:
+            status_code = self.status_codes.get(request_error.code, self.status_code)
+
+        # escape &<> in error message (e.g. URL params)
+        msg = escape(request_error.msg)
+        result = self.template.substitute(exception=msg,
+                                          code=request_error.code, locator=request_error.locator)
+        return Response(result, mimetype=self.mimetype, content_type=self.content_type,
+                        status=status_code)
+
+
 class PlainExceptionHandler(ExceptionHandler):
     mimetype = 'text/plain'
     status_code = 404


=====================================
mapproxy/service/demo.py
=====================================
@@ -138,7 +138,7 @@ class DemoServer(Server):
         elif 'wmts_layer' in req.args:
             demo = self._render_wmts_template('demo/wmts_demo.html', req)
         elif 'wms_capabilities' in req.args:
-            internal_url = '%s/service?REQUEST=GetCapabilities' % (req.server_script_url)
+            internal_url = '%s/service?REQUEST=GetCapabilities&SERVICE=WMS' % (req.server_script_url)
             if 'type' in req.args and req.args['type'] == 'external':
                 url = internal_url.replace(req.server_script_url, req.script_url)
             else:
@@ -146,7 +146,7 @@ class DemoServer(Server):
             capabilities = urllib2.urlopen(url)
             demo = self._render_capabilities_template('demo/capabilities_demo.html', capabilities, 'WMS', url)
         elif 'wmsc_capabilities' in req.args:
-            internal_url = '%s/service?REQUEST=GetCapabilities&tiled=true' % (req.server_script_url)
+            internal_url = '%s/service?REQUEST=GetCapabilities&SERVICE=WMS&tiled=true' % (req.server_script_url)
             if 'type' in req.args and req.args['type'] == 'external':
                 url = internal_url.replace(req.server_script_url, req.script_url)
             else:


=====================================
mapproxy/service/ows.py
=====================================
@@ -16,6 +16,7 @@
 """
 Wrapper service handler for all OWS services (/service?).
 """
+from mapproxy.exception import OWSExceptionHandler, RequestError
 
 
 class OWSServer(object):
@@ -33,7 +34,24 @@ class OWSServer(object):
             self.services[service.service] = service
 
     def handle(self, req):
-        service = req.args.get('service', 'wms').lower()
-        assert service in self.services
+        service = req.args.get('service')
+        wmtver = req.args.get('wmtver')
+        if not service:
+            if wmtver == '1.0.0':
+                # WMS version 1.0.0 did not have a mandatory service parameter
+                service = 'wms'
+            else:
+                req.exception_handler = OWSExceptionHandler()
+
+                error = RequestError('The service parameter is missing',
+                        code='MissingParameterValue', request=req, locator='service', status=400)
+                return req.exception_handler.render(error)
+
+        service = service.lower()
+        if service not in self.services:
+            req.exception_handler = OWSExceptionHandler()
+            error = RequestError('The value of the service parameter "' + str(service) + '" is invalid',
+                        code='InvalidParameterValue', request=req, locator='service', status=400)
+            return error.render()
 
         return self.services[service].handle(req)


=====================================
mapproxy/service/templates/demo/demo.html
=====================================
@@ -35,7 +35,7 @@ jscript_openlayers=None
             {{if 'wms' in services}}
             <div class="capabilities">
                 <span>Capabilities document</span>
-                <span><a href="../service?REQUEST=GetCapabilities">(download as xml)</a></span>
+                <span><a href="../service?REQUEST=GetCapabilities&SERVICE=WMS">(download as xml)</a></span>
                 <span><a href="../demo/?wms_capabilities">(view as html, internal)</a></span>
                 <span><a href="../demo/?wms_capabilities&type=external">(view as html, external)</a></span>
             </div>
@@ -83,7 +83,7 @@ jscript_openlayers=None
             {{if 'wms' in services}}
             <div class="capabilities">
                 <span>Capabilities document</span>
-                <span><a href="../service?REQUEST=GetCapabilities&tiled=true">(download as xml)</a></span>
+                <span><a href="../service?REQUEST=GetCapabilities&SERVICE=WMS&tiled=true">(download as xml)</a></span>
                 <span><a href="../demo/?wmsc_capabilities">(view as html, internal)</a></span>
                 <span><a href="../demo/?wmsc_capabilities&type=external">(view as html, external)</a></span>
             </div>


=====================================
mapproxy/service/templates/ows_exception.xml
=====================================
@@ -0,0 +1,10 @@
+<?xml version="1.0"?>
+<ows:ExceptionReport xmlns:ows="http://www.opengis.net/ows/1.1"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://www.opengis.net/ows/1.1 http://schemas.opengis.net/ows/1.1.0/owsExceptionReport.xsd"
+  version="1.0.0" xml:lang="en">
+  <ows:Exception{{if code is not None}} exceptionCode="{{code}}"{{endif}}
+     {{if locator is not None}} locator="{{locator}}"{{endif}}>
+    <ows:ExceptionText>{{exception}}</ows:ExceptionText>
+  </ows:Exception>
+</ows:ExceptionReport>
\ No newline at end of file


=====================================
mapproxy/test/system/fixture/seed.yaml
=====================================
@@ -29,6 +29,11 @@ seeds:
     refresh_before:
       days: 1
 
+  sqlite_cache:
+    caches: [sqlite_cache]
+    grids: [GLOBAL_GEODETIC]
+    levels: [0]
+
   with_empty_coverage:
     caches: [mbtile_cache]
     grids: [GLOBAL_GEODETIC]
@@ -58,7 +63,7 @@ cleanups:
     levels: [1]
     remove_all: true
 
-  sqlite_cache:
+  cleanup_sqlite_cache:
     caches: [sqlite_cache]
     grids: [GLOBAL_GEODETIC]
     levels: [3]
@@ -91,4 +96,4 @@ cleanups:
     grids: [GLOBAL_GEODETIC]
     levels: [0]
     remove_before:
-      mtime: 'seed.yaml'
\ No newline at end of file
+      mtime: 'seed.yaml'


=====================================
mapproxy/test/system/test_demo.py
=====================================
@@ -29,8 +29,8 @@ class TestDemo(SysTest):
     def test_basic(self, app):
         resp = app.get("/demo/", status=200)
         assert resp.content_type == "text/html"
-        assert 'href="../service?REQUEST=GetCapabilities"' in resp
-        assert 'href="../service?REQUEST=GetCapabilities&tiled=true"' in resp
+        assert 'href="../service?REQUEST=GetCapabilities&SERVICE=WMS"' in resp
+        assert 'href="../service?REQUEST=GetCapabilities&SERVICE=WMS&tiled=true"' in resp
         assert 'href="../demo/?wmts_layer=wms_cache&format=jpeg&srs=EPSG%3A900913"' in resp
         assert 'href="../demo/?tms_layer=wms_cache&format=jpeg&srs=EPSG%3A900913"' in resp
 


=====================================
mapproxy/test/system/test_demo_with_extra_service.py
=====================================
@@ -45,8 +45,8 @@ class TestDemoWithExtraService(SysTest):
     def test_basic(self, app):
         resp = app.get("/demo/", status=200)
         assert resp.content_type == "text/html"
-        assert 'href="../service?REQUEST=GetCapabilities"' in resp
-        assert 'href="../service?REQUEST=GetCapabilities&tiled=true"' in resp
+        assert 'href="../service?REQUEST=GetCapabilities&SERVICE=WMS"' in resp
+        assert 'href="../service?REQUEST=GetCapabilities&SERVICE=WMS&tiled=true"' in resp
         assert 'href="../demo/?wmts_layer=wms_cache&format=jpeg&srs=EPSG%3A900913"' in resp
         assert 'href="../demo/?tms_layer=wms_cache&format=jpeg&srs=EPSG%3A900913"' in resp
         assert '<h2>My extra service</h2>' in resp


=====================================
mapproxy/test/system/test_legendgraphic.py
=====================================
@@ -212,7 +212,7 @@ class TestWMSLegendgraphic(SysTest):
 
     def test_get_legendgraphic_no_legend_111(self, app):
         self.common_lg_req_111.params["layer"] = "wms_no_legend"
-        resp = app.get(self.common_lg_req_111)
+        resp = app.get(self.common_lg_req_111, expect_errors=True)
         assert resp.content_type == "application/vnd.ogc.se_xml"
         xml = resp.lxml
         assert (
@@ -227,7 +227,7 @@ class TestWMSLegendgraphic(SysTest):
             .replace("sld_version", "invalid")
             .replace("format", "invalid")
         )
-        resp = app.get(req)
+        resp = app.get(req, expect_errors=True)
         assert resp.content_type == "application/vnd.ogc.se_xml"
         xml = resp.lxml
         assert "missing parameters" in xml.xpath("//ServiceException/text()")[0]
@@ -237,7 +237,7 @@ class TestWMSLegendgraphic(SysTest):
         req = str(self.common_lg_req_111).replace(
             "sld_version=1.1.0", "sld_version=1.0.0"
         )
-        resp = app.get(req)
+        resp = app.get(req, expect_errors=True)
         assert resp.content_type == "application/vnd.ogc.se_xml"
         xml = resp.lxml
         assert "invalid sld_version" in xml.xpath("//ServiceException/text()")[0]
@@ -245,7 +245,7 @@ class TestWMSLegendgraphic(SysTest):
 
     def test_get_legendgraphic_no_legend_130(self, app):
         self.common_lg_req_130.params["layer"] = "wms_no_legend"
-        resp = app.get(self.common_lg_req_130)
+        resp = app.get(self.common_lg_req_130, expect_errors=True)
         assert resp.content_type == "text/xml"
         xml = resp.lxml
         assert_xpath_wms130(xml, "/ogc:ServiceExceptionReport/@version", "1.3.0")
@@ -258,7 +258,7 @@ class TestWMSLegendgraphic(SysTest):
 
     def test_get_legendgraphic_missing_params_130(self, app):
         req = str(self.common_lg_req_130).replace("format", "invalid")
-        resp = app.get(req)
+        resp = app.get(req, expect_errors=True)
         assert resp.content_type == "text/xml"
         xml = resp.lxml
         assert_xpath_wms130(xml, "/ogc:ServiceExceptionReport/@version", "1.3.0")
@@ -271,7 +271,7 @@ class TestWMSLegendgraphic(SysTest):
         req = str(self.common_lg_req_130).replace(
             "sld_version=1.1.0", "sld_version=1.0.0"
         )
-        resp = app.get(req)
+        resp = app.get(req, expect_errors=True)
         assert resp.content_type == "text/xml"
         xml = resp.lxml
         assert_xpath_wms130(xml, "/ogc:ServiceExceptionReport/@version", "1.3.0")


=====================================
mapproxy/test/system/test_renderd_client.py
=====================================
@@ -179,14 +179,14 @@ class TestWMS111(SysTest):
 
         with mock_single_req_httpd(("localhost", 42423), req_handler):
             self.common_map_req.params["bbox"] = "0,0,9,9"
-            resp = app.get(self.common_map_req)
+            resp = app.get(self.common_map_req, expect_errors=True)
 
             assert resp.content_type == "application/vnd.ogc.se_xml"
             is_111_exception(resp.lxml, re_msg="Error from renderd: barf")
 
     def test_get_map_connection_error(self, app):
         self.common_map_req.params["bbox"] = "0,0,9,9"
-        resp = app.get(self.common_map_req)
+        resp = app.get(self.common_map_req, expect_errors=True)
 
         assert resp.content_type == "application/vnd.ogc.se_xml"
         is_111_exception(resp.lxml, re_msg="Error while communicating with renderd:")
@@ -210,7 +210,7 @@ class TestWMS111(SysTest):
 
         with mock_single_req_httpd(("localhost", 42423), req_handler):
             self.common_map_req.params["bbox"] = "0,0,9,9"
-            resp = app.get(self.common_map_req)
+            resp = app.get(self.common_map_req, expect_errors=True)
 
         assert resp.content_type == "application/vnd.ogc.se_xml"
         is_111_exception(


=====================================
mapproxy/test/system/test_response_headers.py
=====================================
@@ -46,7 +46,7 @@ class TestResponseHeaders(SysTest):
         assert resp.vary == ('X-Script-Name', 'X-Forwarded-Host', 'X-Forwarded-Proto')
 
     def test_no_endpoint(self, app):
-        resp = app.get('http://localhost/service?')
+        resp = app.get('http://localhost/service?', expect_errors=True)
         assert resp.vary == ('X-Script-Name', 'X-Forwarded-Host', 'X-Forwarded-Proto')
 
     def test_image_response(self, app):


=====================================
mapproxy/test/system/test_seed.py
=====================================
@@ -249,6 +249,19 @@ class TestSeed(SeedTestBase):
                 seed(tasks, dry_run=False)
                 cleanup(cleanup_tasks, verbose=False, dry_run=False)
 
+    def test_seed_sqlite(self):
+        with tmp_image((256, 256), format='png') as img:
+            img_data = img.read()
+            expected_req = ({'path': r'/service?LAYERS=baz&SERVICE=WMS&FORMAT=image%2Fpng'
+                                     '&REQUEST=GetMap&VERSION=1.1.1&bbox=-180.0,-90.0,180.0,90.0'
+                                     '&width=256&height=128&srs=EPSG:4326'},
+                            {'body': img_data, 'headers': {'content-type': 'image/png'}})
+            with mock_httpd(('localhost', 42423), [expected_req]):
+                seed_conf = load_seed_tasks_conf(self.seed_conf_file, self.mapproxy_conf)
+                tasks, cleanup_tasks = seed_conf.seeds(['sqlite_cache']), seed_conf.cleanups(['cleanup_sqlite_cache'])
+                seed(tasks, dry_run=False)
+                cleanup(cleanup_tasks, verbose=False, dry_run=False)
+
     def create_tile(self, coord=(0, 0, 0)):
         return Tile(coord,
                     ImageSource(tile_image_buf,
@@ -290,7 +303,7 @@ class TestSeed(SeedTestBase):
 
     def test_cleanup_sqlite(self):
         seed_conf = load_seed_tasks_conf(self.seed_conf_file, self.mapproxy_conf)
-        cleanup_tasks = seed_conf.cleanups(['sqlite_cache'])
+        cleanup_tasks = seed_conf.cleanups(['cleanup_sqlite_cache'])
 
         cache = cleanup_tasks[0].tile_manager.cache
         cache.store_tile(self.create_tile((0, 0, 2)))
@@ -337,8 +350,8 @@ class TestSeed(SeedTestBase):
     def test_active_seed_tasks(self):
         with local_base_config(self.mapproxy_conf.base_config):
             seed_conf = load_seed_tasks_conf(self.seed_conf_file, self.mapproxy_conf)
-            assert len(seed_conf.seed_tasks_names()) == 5
-            assert len(seed_conf.seeds()) == 5
+            assert len(seed_conf.seed_tasks_names()) == 6
+            assert len(seed_conf.seeds()) == 6
 
     def test_seed_refresh_remove_before_from_file(self):
         # tile already there but old


=====================================
mapproxy/test/system/test_source_errors.py
=====================================
@@ -129,7 +129,7 @@ class TestWMS(SysTest):
 
     def test_all_offline(self, app):
         self.common_map_req.params.layers = "all_offline"
-        resp = app.get(self.common_map_req)
+        resp = app.get(self.common_map_req, expect_errors=True)
         assert resp.content_type == "application/vnd.ogc.se_xml"
         is_111_exception(resp.lxml, re_msg="no response from url")
 
@@ -175,12 +175,12 @@ class TestWMSRaise(SysTest):
 
         with mock_httpd(("localhost", 42423), expected_req):
             self.common_map_req.params.layers = "mixed"
-            resp = app.get(self.common_map_req)
+            resp = app.get(self.common_map_req, expect_errors=True)
             is_111_exception(resp.lxml, re_msg="no response from url")
 
     def test_all_offline(self, app):
         self.common_map_req.params.layers = "all_offline"
-        resp = app.get(self.common_map_req)
+        resp = app.get(self.common_map_req, expect_errors=True)
         assert resp.content_type == "application/vnd.ogc.se_xml"
         is_111_exception(resp.lxml, re_msg="no response from url")
 
@@ -268,7 +268,7 @@ class TestTileErrors(SysTest):
         ]
 
         with mock_httpd(("localhost", 42423), expected_req):
-            resp = app.get(self.common_map_req)
+            resp = app.get(self.common_map_req, expect_errors=True)
             assert_no_cache(resp)
             assert resp.content_type == "application/vnd.ogc.se_xml"
             assert b"500" in resp.body


=====================================
mapproxy/test/system/test_wms.py
=====================================
@@ -141,7 +141,7 @@ class TestWMS111(SysTest):
 
     def test_invalid_request_type(self, app):
         req = str(self.common_map_req).replace("GetMap", "invalid")
-        resp = app.get(req)
+        resp = app.get(req, expect_errors=True)
         is_111_exception(resp.lxml, "unknown WMS request type 'invalid'")
 
     def test_endpoints(self, app):
@@ -220,20 +220,20 @@ class TestWMS111(SysTest):
 
     def test_invalid_layer(self, app):
         self.common_map_req.params["layers"] = "invalid"
-        resp = app.get(self.common_map_req)
+        resp = app.get(self.common_map_req, expect_errors=True)
         assert resp.content_type == "application/vnd.ogc.se_xml"
         is_111_exception(resp.lxml, "unknown layer: invalid", "LayerNotDefined")
 
     def test_invalid_layer_img_exception(self, app):
         self.common_map_req.params["layers"] = "invalid"
         self.common_map_req.params["exceptions"] = "application/vnd.ogc.se_inimage"
-        resp = app.get(self.common_map_req)
+        resp = app.get(self.common_map_req, expect_errors=True)
         assert resp.content_type == "image/png"
         assert is_png(BytesIO(resp.body))
 
     def test_invalid_format(self, app):
         self.common_map_req.params["format"] = "image/ascii"
-        resp = app.get(self.common_map_req)
+        resp = app.get(self.common_map_req, expect_errors=True)
         assert resp.content_type == "application/vnd.ogc.se_xml"
         is_111_exception(
             resp.lxml, "unsupported image format: image/ascii", "InvalidFormat"
@@ -262,20 +262,20 @@ class TestWMS111(SysTest):
 
     def test_invalid_srs(self, app):
         self.common_map_req.params["srs"] = "EPSG:1234"
-        resp = app.get(self.common_map_req)
+        resp = app.get(self.common_map_req, expect_errors=True)
         assert resp.content_type == "application/vnd.ogc.se_xml"
         is_111_exception(resp.lxml, "unsupported srs: EPSG:1234", "InvalidSRS")
 
     def test_get_map_unknown_style(self, app):
         self.common_map_req.params["styles"] = "unknown"
-        resp = app.get(self.common_map_req)
+        resp = app.get(self.common_map_req, expect_errors=True)
         assert resp.content_type == "application/vnd.ogc.se_xml"
         is_111_exception(resp.lxml, "unsupported styles: unknown", "StyleNotDefined")
 
     def test_get_map_too_large(self, app):
         self.common_map_req.params.size = (5000, 5000)
         self.common_map_req.params["exceptions"] = "application/vnd.ogc.se_inimage"
-        resp = app.get(self.common_map_req)
+        resp = app.get(self.common_map_req, expect_errors=True)
         # is xml, even if inimage was requested
         assert resp.content_type == "application/vnd.ogc.se_xml"
         is_111_exception(resp.lxml, "image size too large")
@@ -371,7 +371,7 @@ class TestWMS111(SysTest):
 
     def test_get_map_xml_exception(self, app):
         self.common_map_req.params["bbox"] = "0,0,90,90"
-        resp = app.get(self.common_map_req)
+        resp = app.get(self.common_map_req, expect_errors=True)
         assert resp.content_type == "application/vnd.ogc.se_xml"
         xml = resp.lxml
         assert xml.xpath("/ServiceExceptionReport/ServiceException/@code") == []
@@ -380,7 +380,7 @@ class TestWMS111(SysTest):
 
     def test_direct_layer_error(self, app):
         self.common_map_req.params["layers"] = "direct"
-        resp = app.get(self.common_map_req)
+        resp = app.get(self.common_map_req, expect_errors=True)
         assert resp.content_type == "application/vnd.ogc.se_xml"
         xml = resp.lxml
         assert xml.xpath("/ServiceExceptionReport/ServiceException/@code") == []
@@ -403,7 +403,7 @@ class TestWMS111(SysTest):
             {"body": b"notanimage", "headers": {"content-type": "image/jpeg"}},
         )
         with mock_httpd(("localhost", 42423), [expected_req]):
-            resp = app.get(self.common_map_req)
+            resp = app.get(self.common_map_req, expect_errors=True)
             assert resp.content_type == "application/vnd.ogc.se_xml"
             xml = resp.lxml
             assert xml.xpath("/ServiceExceptionReport/ServiceException/@code") == []
@@ -428,7 +428,7 @@ class TestWMS111(SysTest):
             ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True
         ):
             self.common_map_req.params["bbox"] = "0,0,180,90"
-            resp = app.get(self.common_map_req)
+            resp = app.get(self.common_map_req, expect_errors=True)
         assert resp.content_type == "application/vnd.ogc.se_xml"
 
         xml = resp.lxml
@@ -545,7 +545,7 @@ class TestWMS111(SysTest):
         url = (
             """/service?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&BBOX=7,2,-9,10&SRS=EPSG:4326&WIDTH=164&HEIGHT=388&LAYERS=wms_cache&STYLES=&FORMAT=image/png&TRANSPARENT=TRUE"""  # noqa
         )
-        resp = app.get(url)
+        resp = app.get(url, expect_errors=True)
         is_111_exception(resp.lxml, "invalid bbox 7,2,-9,10")
 
     def test_get_map_invalid_bbox2(self, app):
@@ -553,7 +553,7 @@ class TestWMS111(SysTest):
         url = (
             """/service?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&BBOX=-72988843.697212,-255661507.634227,142741550.188860,255661507.634227&SRS=EPSG:25833&WIDTH=164&HEIGHT=388&LAYERS=wms_cache_100&STYLES=&FORMAT=image/png&TRANSPARENT=TRUE"""  # noqa
         )
-        resp = app.get(url)
+        resp = app.get(url, expect_errors=True)
         # result depends on proj version
         is_111_exception(
             resp.lxml,
@@ -562,9 +562,9 @@ class TestWMS111(SysTest):
 
     def test_get_map_broken_bbox(self, app):
         url = (
-            """/service?VERSION=1.1.11&REQUEST=GetMap&SRS=EPSG:31468&BBOX=-20000855.0573254,2847125.18913603,-19329367.42767611,4239924.78564583&WIDTH=130&HEIGHT=62&LAYERS=wms_cache&STYLES=&FORMAT=image/png&TRANSPARENT=TRUE"""  # noqa
+            """/service?SERVICE=WMS&VERSION=1.1.11&REQUEST=GetMap&SRS=EPSG:31468&BBOX=-20000855.0573254,2847125.18913603,-19329367.42767611,4239924.78564583&WIDTH=130&HEIGHT=62&LAYERS=wms_cache&STYLES=&FORMAT=image/png&TRANSPARENT=TRUE"""  # noqa
         )
-        resp = app.get(url)
+        resp = app.get(url, expect_errors=True)
         is_111_exception(resp.lxml, "Could not transform BBOX: Invalid result.")
 
     def test_get_map100(self, app, base_dir, cache_dir):
@@ -808,7 +808,7 @@ class TestWMS111(SysTest):
 
             del self.common_fi_req.params["format"]
             del self.common_fi_req.params["styles"]
-            resp = app.get(self.common_fi_req)
+            resp = app.get(self.common_fi_req, expect_errors=True)
             xml = resp.lxml
             assert "missing parameters" in xml.xpath("//ServiceException/text()")[0]
             assert validate_with_dtd(xml, "wms/1.1.1/exception_1_1_1.dtd")
@@ -819,7 +819,7 @@ class TestWMS111(SysTest):
     def test_get_featureinfo_not_queryable(self, app):
         self.common_fi_req.params["query_layers"] = "tms_cache"
         self.common_fi_req.params["exceptions"] = "application/vnd.ogc.se_xml"
-        resp = app.get(self.common_fi_req)
+        resp = app.get(self.common_fi_req, expect_errors=True)
         assert resp.content_type == "application/vnd.ogc.se_xml"
         xml = resp.lxml
         assert xml.xpath("/ServiceExceptionReport/ServiceException/@code") == []
@@ -912,7 +912,7 @@ class TestWMS110(SysTest):
 
     def test_invalid_layer(self, app):
         self.common_map_req.params["layers"] = "invalid"
-        resp = app.get(self.common_map_req)
+        resp = app.get(self.common_map_req, expect_errors=True)
         assert resp.content_type == "application/vnd.ogc.se_xml"
         xml = resp.lxml
         assert xml.xpath("/ServiceExceptionReport/@version")[0] == "1.1.0"
@@ -925,7 +925,7 @@ class TestWMS110(SysTest):
 
     def test_invalid_format(self, app):
         self.common_map_req.params["format"] = "image/ascii"
-        resp = app.get(self.common_map_req)
+        resp = app.get(self.common_map_req, expect_errors=True)
         assert resp.content_type == "application/vnd.ogc.se_xml"
         xml = resp.lxml
         assert xml.xpath("/ServiceExceptionReport/@version")[0] == "1.1.0"
@@ -955,7 +955,7 @@ class TestWMS110(SysTest):
 
     def test_invalid_srs(self, app):
         self.common_map_req.params["srs"] = "EPSG:1234"
-        resp = app.get(self.common_map_req)
+        resp = app.get(self.common_map_req, expect_errors=True)
         assert resp.content_type == "application/vnd.ogc.se_xml"
         xml = resp.lxml
         assert xml.xpath("/ServiceExceptionReport/@version")[0] == "1.1.0"
@@ -980,7 +980,7 @@ class TestWMS110(SysTest):
 
     def test_get_map_xml_exception(self, app):
         self.common_map_req.params["bbox"] = "0,0,90,90"
-        resp = app.get(self.common_map_req)
+        resp = app.get(self.common_map_req, expect_errors=True)
         assert resp.content_type == "application/vnd.ogc.se_xml"
         xml = resp.lxml
         assert xml.xpath("/ServiceExceptionReport/ServiceException/@code") == []
@@ -1053,7 +1053,7 @@ class TestWMS110(SysTest):
     def test_get_featureinfo_not_queryable(self, app):
         self.common_fi_req.params["query_layers"] = "tms_cache"
         self.common_fi_req.params["exceptions"] = "application/vnd.ogc.se_xml"
-        resp = app.get(self.common_fi_req)
+        resp = app.get(self.common_fi_req, expect_errors=True)
         assert resp.content_type == "application/vnd.ogc.se_xml"
         xml = resp.lxml
         assert xml.xpath("/ServiceExceptionReport/ServiceException/@code") == []
@@ -1167,7 +1167,7 @@ class TestWMS100(SysTest):
 
     def test_invalid_layer(self, app):
         self.common_map_req.params["layers"] = "invalid"
-        resp = app.get(self.common_map_req)
+        resp = app.get(self.common_map_req, expect_errors=True)
         assert resp.content_type == "text/xml"
         xml = resp.lxml
         assert xml.xpath("/WMTException/@version")[0] == "1.0.0"
@@ -1175,7 +1175,7 @@ class TestWMS100(SysTest):
 
     def test_invalid_format(self, app):
         self.common_map_req.params["format"] = "image/ascii"
-        resp = app.get(self.common_map_req)
+        resp = app.get(self.common_map_req, expect_errors=True)
         assert resp.content_type == "text/xml"
         xml = resp.lxml
         assert xml.xpath("/WMTException/@version")[0] == "1.0.0"
@@ -1201,7 +1201,7 @@ class TestWMS100(SysTest):
     def test_invalid_srs(self, app):
         self.common_map_req.params["srs"] = "EPSG:1234"
         print(self.common_map_req.complete_url)
-        resp = app.get(self.common_map_req.complete_url)
+        resp = app.get(self.common_map_req.complete_url, expect_errors=True)
         xml = resp.lxml
         assert xml.xpath("//WMTException/text()")[0].strip() == "unsupported srs: EPSG:1234"
 
@@ -1234,7 +1234,7 @@ class TestWMS100(SysTest):
 
     def test_get_map_xml_exception(self, app):
         self.common_map_req.params["bbox"] = "0,0,90,90"
-        resp = app.get(self.common_map_req)
+        resp = app.get(self.common_map_req, expect_errors=True)
         xml = resp.lxml
         assert "No response from URL" in xml.xpath("//WMTException/text()")[0]
 
@@ -1278,7 +1278,7 @@ class TestWMS100(SysTest):
     def test_get_featureinfo_not_queryable(self, app):
         self.common_fi_req.params["query_layers"] = "tms_cache"
         self.common_fi_req.params["exceptions"] = "application/vnd.ogc.se_xml"
-        resp = app.get(self.common_fi_req)
+        resp = app.get(self.common_fi_req, expect_errors=True)
         assert resp.content_type == "text/xml"
         xml = resp.lxml
         assert "tms_cache is not queryable" in xml.xpath("//WMTException/text()")[0]
@@ -1383,7 +1383,7 @@ class TestWMS130(SysTest):
 
     def test_invalid_layer(self, app):
         self.common_map_req.params["layers"] = "invalid"
-        resp = app.get(self.common_map_req)
+        resp = app.get(self.common_map_req, expect_errors=True)
         assert resp.content_type == "text/xml"
         xml = resp.lxml
         assert_xpath_wms130(xml, "/ogc:ServiceExceptionReport/@version", "1.3.0")
@@ -1397,7 +1397,7 @@ class TestWMS130(SysTest):
 
     def test_invalid_format(self, app):
         self.common_map_req.params["format"] = "image/ascii"
-        resp = app.get(self.common_map_req)
+        resp = app.get(self.common_map_req, expect_errors=True)
         assert resp.content_type == "text/xml"
         xml = resp.lxml
         assert_xpath_wms130(xml, "/ogc:ServiceExceptionReport/@version", "1.3.0")
@@ -1431,7 +1431,7 @@ class TestWMS130(SysTest):
         self.common_map_req.params["srs"] = "EPSG:1234"
         self.common_map_req.params["exceptions"] = "text/xml"
 
-        resp = app.get(self.common_map_req)
+        resp = app.get(self.common_map_req, expect_errors=True)
         assert resp.content_type == "text/xml"
         xml = resp.lxml
         assert_xpath_wms130(
@@ -1457,7 +1457,7 @@ class TestWMS130(SysTest):
 
     def test_get_map_xml_exception(self, app):
         self.common_map_req.params["bbox"] = "0,0,90,90"
-        resp = app.get(self.common_map_req)
+        resp = app.get(self.common_map_req, expect_errors=True)
         assert resp.content_type == "text/xml"
         xml = resp.lxml
         assert (


=====================================
mapproxy/test/system/test_wmsc.py
=====================================
@@ -99,18 +99,18 @@ class TestWMSC(SysTest):
 
     def test_get_tile_wrong_bbox(self, app):
         self.common_map_req.params.bbox = "-20037508,0.0,200000.0,20037508"
-        resp = app.get(str(self.common_map_req) + "&tiled=true")
+        resp = app.get(str(self.common_map_req) + "&tiled=true", expect_errors=True)
         assert_no_cache(resp)
         is_111_exception(resp.lxml, re_msg=".*invalid bbox")
 
     def test_get_tile_wrong_fromat(self, app):
         self.common_map_req.params.format = "image/png"
-        resp = app.get(str(self.common_map_req) + "&tiled=true")
+        resp = app.get(str(self.common_map_req) + "&tiled=true", expect_errors=True)
         assert_no_cache(resp)
         is_111_exception(resp.lxml, re_msg="Invalid request: invalid.*format.*jpeg")
 
     def test_get_tile_wrong_size(self, app):
         self.common_map_req.params.size = (256, 255)
-        resp = app.get(str(self.common_map_req) + "&tiled=true")
+        resp = app.get(str(self.common_map_req) + "&tiled=true", expect_errors=True)
         assert_no_cache(resp)
         is_111_exception(resp.lxml, re_msg="Invalid request: invalid.*size.*256x256")


=====================================
mapproxy/test/unit/test_client.py
=====================================
@@ -108,6 +108,7 @@ class TestHTTPClient(object):
             assert False, 'expected HTTPClientError'
 
     @pytest.mark.online
+    @pytest.mark.flaky(reruns=5, reruns_delay=2)
     def test_https_untrusted_root(self):
         self.client = HTTPClient('https://untrusted-root.badssl.com/')
         try:
@@ -116,12 +117,14 @@ class TestHTTPClient(object):
             assert_re(e.args[0], r'Could not verify connection to URL')
 
     @pytest.mark.online
+    @pytest.mark.flaky(reruns=5, reruns_delay=2)
     def test_https_insecure(self):
         self.client = HTTPClient(
             'https://untrusted-root.badssl.com/', insecure=True)
         self.client.open('https://untrusted-root.badssl.com/')
 
     @pytest.mark.online
+    @pytest.mark.flaky(reruns=5, reruns_delay=2)
     def test_https_valid_ca_cert_file(self):
         # verify with fixed ca_certs file
         cert_file = '/etc/ssl/certs/ca-certificates.crt'
@@ -136,11 +139,13 @@ class TestHTTPClient(object):
                 self.client.open('https://www.google.com/')
 
     @pytest.mark.online
+    @pytest.mark.flaky(reruns=5, reruns_delay=2)
     def test_https_valid_default_cert(self):
         self.client = HTTPClient('https://www.google.com/')
         self.client.open('https://www.google.com/')
 
     @pytest.mark.online
+    @pytest.mark.flaky(reruns=5, reruns_delay=2)
     def test_https_invalid_cert(self):
         # load 'wrong' root cert
         with TempFile() as tmp:


=====================================
mapproxy/test/unit/test_exceptions.py
=====================================
@@ -18,6 +18,7 @@ from io import BytesIO
 from mapproxy.compat.image import Image
 from mapproxy.exception import RequestError
 from mapproxy.request import url_decode
+from mapproxy.request.base import Request
 from mapproxy.request.wms import WMSMapRequest
 from mapproxy.request.wms.exception import (
     WMS100ExceptionHandler,
@@ -25,6 +26,7 @@ from mapproxy.request.wms.exception import (
     WMS130ExceptionHandler,
     WMS110ExceptionHandler,
 )
+from mapproxy.service.ows import OWSServer
 from mapproxy.test.helper import Mocker, validate_with_dtd, validate_with_xsd
 from mapproxy.test.image import is_png
 
@@ -166,6 +168,90 @@ http://schemas.opengis.net/wms/1.3.0/exceptions_1_3_0.xsd">
         assert expected_resp.strip() == response.data
         assert validate_with_xsd(response.data, 'wms/1.3.0/exceptions_1_3_0.xsd')
 
+    def test_missing_service_request(self):
+        reqString = "REQUEST=GetCapabilities"
+        conf = {
+            'QUERY_STRING': reqString,
+            'wsgi.url_scheme': 'http',
+            'HTTP_HOST': 'localhost',
+        }
+        req = Request(conf)
+        ows_services = []
+        server = OWSServer(ows_services)
+        response = server.handle(req)
+
+        expected_resp = """
+<?xml version="1.0"?>
+<ows:ExceptionReport xmlns:ows="http://www.opengis.net/ows/1.1"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://www.opengis.net/ows/1.1 http://schemas.opengis.net/ows/1.1.0/owsExceptionReport.xsd"
+  version="1.0.0" xml:lang="en">
+  <ows:Exception exceptionCode="MissingParameterValue"
+      locator="service">
+    <ows:ExceptionText>The service parameter is missing</ows:ExceptionText>
+  </ows:Exception>
+</ows:ExceptionReport>
+"""
+
+        assert expected_resp.strip() == response.response.strip()
+        assert response.content_type == 'text/xml; charset=utf-8'
+        assert response.status == '400 Bad Request'
+        assert validate_with_xsd(response.response, 'ows/1.1.0/owsExceptionReport.xsd')
+
+    def test_invalid_service_request(self):
+        reqString = "REQUEST=GetCapabilities&SERVICE=wms"
+        conf = {
+            'QUERY_STRING': reqString,
+            'wsgi.url_scheme': 'http',
+            'HTTP_HOST': 'localhost',
+        }
+        req = Request(conf)
+        ows_services = []
+        server = OWSServer(ows_services)
+        response = server.handle(req)
+
+        expected_resp = """
+<?xml version="1.0"?>
+<ows:ExceptionReport xmlns:ows="http://www.opengis.net/ows/1.1"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://www.opengis.net/ows/1.1 http://schemas.opengis.net/ows/1.1.0/owsExceptionReport.xsd"
+  version="1.0.0" xml:lang="en">
+  <ows:Exception exceptionCode="InvalidParameterValue"
+      locator="service">
+    <ows:ExceptionText>The value of the service parameter "wms" is invalid</ows:ExceptionText>
+  </ows:Exception>
+</ows:ExceptionReport>
+"""
+
+        assert expected_resp.strip() == response.response
+        assert response.content_type == 'text/xml; charset=utf-8'
+        assert response.status == '400 Bad Request'
+        assert validate_with_xsd(response.response, 'ows/1.1.0/owsExceptionReport.xsd')
+
+    def test_valid_service_request(self):
+        reqString = "REQUEST=GetCapabilities&SERVICE=wms"
+        conf = {
+            'QUERY_STRING': reqString,
+            'wsgi.url_scheme': 'http',
+            'HTTP_HOST': 'localhost',
+        }
+        req = Request(conf)
+
+        class Service(object):
+
+            def __init__(self, service):
+                self.service = service
+
+            def handle(self, req):
+                return 'all good'
+
+        ows_services = [Service('wms')]
+        server = OWSServer(ows_services)
+        response = server.handle(req)
+
+        expected_resp = 'all good'
+        assert expected_resp == response
+
 
 class TestWMS100ExceptionHandler(Mocker):
     def test_render(self):


=====================================
requirements-tests.txt
=====================================
@@ -1,23 +1,23 @@
 azure-storage-blob>=12.9.0
 Jinja2==2.11.3
 MarkupSafe==1.1.1
-Pillow==10.2.0
-WebOb==1.8.7
+Pillow==10.3.0
+WebOb==1.8.8
 Shapely==2.0.1
 WebTest==3.0.0
 attrs==19.3.0;python_version<"3.10"
 attrs==23.2.0;python_version>="3.10"
-aws-sam-translator==1.82.0
+aws-sam-translator==1.91.0
 aws-xray-sdk==2.12.1
 beautifulsoup4==4.12.2
-boto3==1.34.4
+boto3==1.35.6
 boto==2.49.0
-botocore==1.34.17
-certifi==2023.11.17
+botocore==1.35.6
+certifi==2024.7.4
 cffi==1.16.0;
 cfn-lint==0.80.3
 chardet==5.2.0
-cryptography==41.0.7
+cryptography==43.0.0
 decorator==5.1.1
 docker==7.0.0
 docutils==0.20.1
@@ -33,15 +33,15 @@ jsonpointer==2.4
 jsonschema==4.17;python_version<"3.10"
 jsonschema==4.20;python_version>="3.10"
 junit-xml==1.9
-lxml==4.9.4
+lxml==5.3.0
 mock==5.1.0
 more-itertools==10.1.0
-moto==4.2.12
+moto==5.0.13
 networkx==3.1
 numpy==1.26.0;python_version>="3.9"
 numpy==1.24.0;python_version=="3.8"
 packaging==23.2
-pluggy==0.13.1
+pluggy==1.5.0
 py==1.11.0
 pyasn1==0.5.1
 pycparser==2.21
@@ -49,24 +49,24 @@ pyparsing==3.1.1
 pyproj==2.6.1.post1;python_version=="3.8"
 pyproj==3.6.1;python_version>"3.8"
 pyrsistent==0.20.0
-pytest==7.4.3
+pytest==8.3.2
 pytest-rerunfailures==13.0
 python-dateutil==2.8.2
 python-jose==3.3.0
 pytz==2023.3.post1
 redis==5.0.1
-requests==2.31.0
+requests==2.32.2
 responses==0.24.1
 riak==2.7.0
 rsa==4.9
-s3transfer==0.9.0
+s3transfer==0.10.2
 six==1.16.0
 soupsieve==2.0.1
 sshpubkeys==3.3.1
 toml==0.10.2
-urllib3==1.26.18
+urllib3==1.26.19
 waitress==2.1.2
 websocket-client==1.7.0
 wrapt==1.16.0
 xmltodict==0.13.0
-zipp==3.17.0
+zipp==3.19.1


=====================================
setup.py
=====================================
@@ -63,7 +63,7 @@ def long_description(changelog_releases=10):
 
 setup(
     name='MapProxy',
-    version="2.2.0",
+    version="3.0.1",
     description='An accelerating proxy for tile and web map services',
     long_description=long_description(7),
     author='Oliver Tonnhofer',



View it on GitLab: https://salsa.debian.org/debian-gis-team/mapproxy/-/compare/8471507c4e0e6787bddaa53ed7308c8d77b0af80...6c99f6b6aecc0d6584e55fb2b6043847ec388839

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/mapproxy/-/compare/8471507c4e0e6787bddaa53ed7308c8d77b0af80...6c99f6b6aecc0d6584e55fb2b6043847ec388839
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20240829/9b99ce08/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list