From 0e683078491880264b429d32e57f6818c147ae7a Mon Sep 17 00:00:00 2001
From: Wannes Rombouts <wapiflapi@yahoo.fr>
Date: Mon, 3 Jun 2013 18:43:51 +0200
Subject: [PATCH] Added tests for layers, fixed some problems on the way. In
 particular changed some things in the way SLDs are added when uploaded, may
 break some things. We really need to figure out that part better.

---
 src/mapfile.py        |  40 +++++++--
 src/server.py         |  33 ++++---
 tests/files/style.sld |  21 +++++
 tests/testScenario.py | 195 ++++++++++++++++++++++++++++++++++++------
 tests/utils.py        |   6 +-
 5 files changed, 249 insertions(+), 46 deletions(-)
 create mode 100644 tests/files/style.sld

diff --git a/src/mapfile.py b/src/mapfile.py
index bc7a171..702f439 100644
--- a/src/mapfile.py
+++ b/src/mapfile.py
@@ -94,14 +94,16 @@ class Layer(MetadataMixin):
         extent = self.ms.getExtent()
         return stores.Extent(extent.minx, extent.miny, extent.maxx, extent.maxy)
 
-    def get_fields(self, mf=None):
+    def get_fields(self):
         fields = self.get_metadata("gml_include_items", "")
 
         if fields == "all":
             # TODO: Get fields from feature type
             raise NotImplemented()
+        elif not fields:
+            return []
         else:
-            fields = fields.split()
+            fields = fields.split(",")
         return fields
 
     def iter_fields(self):
@@ -126,12 +128,30 @@ class Layer(MetadataMixin):
         # we need to rename it to something we are sure is not used.
         sld_layer_name = "__mra_tmp_template"
 
+        print new_sld
+
         # This is realy ugly but etree has trouble with namespaces...
-        xmlsld = etree.fromstring(re.sub(' [a-zzA-Z]+:([a-zA-Z]+=")', ' \\1', new_sld))
-        xmlsld.find("NamedLayer/Name").text = sld_layer_name
-        new_sld = etree.tostring(xmlsld)
+        # TODO: Do this again in an other way.
+        fixedxml = re.sub(' [a-zzA-Z0-9]+:([a-zA-Z0-9]+=")', ' \\1', new_sld)
+        xmlsld = etree.fromstring(fixedxml)
+
+        # Still problems with namespaces...
+        for child in xmlsld.getchildren():
+            if child.tag.endswith("NamedLayer"):
+                for gchild in child.getchildren():
+                    if gchild.tag.endswith("Name"):
+                        gchild.text = sld_layer_name
+                        break
+                else:
+                    continue
+            break
+        else:
+            raise ValueError("Bad sld (No NamedLayer/Name)")
 
 
+        etree.register_namespace("", "http://www.opengis.net/sld")
+        new_sld = etree.tostring(xmlsld)
+
         ms_template_layer = self.ms.clone()
         ms_template_layer.name = sld_layer_name
         mf.ms.insertLayer(ms_template_layer)
@@ -307,7 +327,7 @@ class FeatureType(LayerModel):
         else:
             self.ms.connectiontype = mapscript.MS_SHAPEFILE
             url = urlparse.urlparse(cparam["url"])
-            self.ms.data = url.path
+            self.ms.data = tools.get_resource_path(url.path)
 
             # TODO: strip extention.
         #else:
@@ -324,6 +344,9 @@ class FeatureType(LayerModel):
 
     def configure_layer(self, layer, ws, enabled=True):
 
+        # TODO: We must also update all our personal attributes (type, ...)
+        # because we might not have been cloned.
+
         layer.set_metadatas({
                 "wfs_name": layer.get_metadata("wms_name"),
                 "wfs_title": layer.get_metadata("wms_title"),
@@ -401,7 +424,7 @@ class Coverage(LayerModel):
         #if cparam["dbtype"] in ["tif", "tiff"]:
         self.ms.connectiontype = mapscript.MS_RASTER
         url = urlparse.urlparse(cparam["url"])
-        self.ms.data = url.path
+        self.ms.data = tools.get_resource_path(url.path)
             # TODO: strip extention.
         #else:
         #    raise ValueError("Unhandled type '%s'" % cparam["dbtype"])
@@ -417,6 +440,9 @@ class Coverage(LayerModel):
 
     def configure_layer(self, layer, ws, enabled=True):
 
+        # TODO: We must also update all our personal attributes (type, ...)
+        # because we might not have been cloned.
+
         layer.set_metadatas({
                 "wfs_name": layer.get_metadata("wms_name"),
                 "wfs_title": layer.get_metadata("wms_title"),
diff --git a/src/server.py b/src/server.py
index 028c3bf..9fb0125 100755
--- a/src/server.py
+++ b/src/server.py
@@ -526,7 +526,7 @@ class styles(object):
         name = params.name
         if name == None:
             raise webapp.BadRequest(message="no parameter 'name' given.")
-        with webapp.mightConflict("style", mapfile=map_name):
+        with webapp.mightConflict(message="style {exception} already exists."):
             if name in tools.iter_styles(mf):
                 raise webapp.KeyExists(name)
 
@@ -623,7 +623,10 @@ class layers(object):
         else:
             raise webapp.BadRequest(message="Resource href is not handled by MRA.")
 
-        _, _, map_name, _, ws_name, st_type, st_name, r_type, r_name = path.split("/")
+        try:
+            _, map_name, _, ws_name, st_type, st_name, r_type, r_name = path.rsplit("/", 7)
+        except ValueError:
+            raise webapp.NotFound(message="ressource '%s' was not found." % path)
 
         r_name = r_name.rsplit(".", 1)[0]
 
@@ -656,7 +659,7 @@ class layer(object):
         return {"layer" : ({
                     "name": l_name,
                     "path": "/",
-                    "type": "VECTOR",
+                    "type": layer.get_type_name(),
                     "defaultStyle": {
                         "name": layer.ms.classgroup,
                         "href": "%s/maps/%s/styles/%s.%s" % (web.ctx.home, map_name, layer.ms.classgroup, format),
@@ -688,18 +691,23 @@ class layer(object):
         # This means we can have one mapfile for each workspace
         # and if eveything uses urls it should work *almost* as is.
         r_href = data["resource"]["href"]
-        _, _, map_name, _, ws_name, st_type, st_name, r_type, r_name = r_href.split("/")
+        try:
+            _, map_name, _, ws_name, st_type, st_name, r_type, r_name = r_href.rsplit("/", 7)
+        except ValueError:
+            raise webapp.NotFound(message="ressource '%s' was not found." % r_href)
+
         r_name = r_name.rsplit(".", 1)[0]
 
         ws = mf.get_workspace(ws_name)
         with webapp.mightNotFound(r_type, workspace=ws_name):
             try:
-                model = ws.get_model(r_name, st_type, st_name)
+                model = ws.get_model(r_name, r_type[:-1], st_name)
             except ValueError:
-                webapp.NotFound("Invalid layer model '%s'" % st_type)
+                raise webapp.NotFound("Invalid layer model '%s'" % st_type)
 
         with webapp.mightNotFound("layer", mapfile=map_name):
-            model.configure_layer(ws, mf, l_name, l_enabled)
+            layer = mf.get_layer(l_name)
+            model.configure_layer(layer, ws, l_enabled)
         mf.save()
 
 
@@ -735,7 +743,10 @@ class layerstyles(object):
         else:
             raise webapp.BadRequest(message="Resource href (%s) is not handled by MRA." % url.path)
 
-        _, _, map_name, _, s_name = path.split("/")
+        try:
+            _, map_name, _, s_name = path.rsplit("/", 3)
+        except ValueError:
+            raise webapp.NotFound(message="ressource '%s' was not found." % path)
 
         s_name = s_name.rsplit(".", 1)[0]
 
@@ -774,10 +785,10 @@ class layerfields(object):
         with webapp.mightNotFound("layer", mapfile=map_name):
             layer = mf.get_layer(l_name)
 
-        return {"fields": [{
+            return {"fields": [{
                     "name": layer.get_metadata("gml_%s_alias" % field, None),
                     "fieldType": layer.get_metadata("gml_%s_type" % field, None),
-                    } for field in layer.iter_fields(mf)]
+                    } for field in layer.iter_fields()]
                 }
 
 
@@ -922,5 +933,5 @@ app = web.application(urls, globals())
 
 if __name__ == "__main__":
     app.run()
-u
+
 application = app.wsgifunc()
diff --git a/tests/files/style.sld b/tests/files/style.sld
new file mode 100644
index 0000000..fd2f1bd
--- /dev/null
+++ b/tests/files/style.sld
@@ -0,0 +1,21 @@
+<StyledLayerDescriptor version="1.0.0" xmlns="http://www.opengis.net/sld" xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd">
+<NamedLayer>
+<Name>countries</Name>
+<UserStyle>
+<FeatureTypeStyle>
+<Rule>
+<Name>Class given in default</Name>
+<PolygonSymbolizer>
+<Fill>
+<CssParameter name="fill">#dcdcdc</CssParameter>
+</Fill>
+<Stroke>
+<CssParameter name="stroke">#606060</CssParameter>
+<CssParameter name="stroke-width">1.00</CssParameter>
+</Stroke>
+</PolygonSymbolizer>
+</Rule>
+</FeatureTypeStyle>
+</UserStyle>
+</NamedLayer>
+</StyledLayerDescriptor>
diff --git a/tests/testScenario.py b/tests/testScenario.py
index 7756ae1..ce64d9e 100644
--- a/tests/testScenario.py
+++ b/tests/testScenario.py
@@ -27,15 +27,9 @@
 from utils import APIRequest
 
 import sys
+import random
 
-
-def test_scenario():
-
-    target = "http://localhost:8080"
-    map_name = "tests"
-
-    # Clean the test file, now we are sure it is empty.
-    APIRequest("PUT", target + "/tests/" + map_name)
+def _test_workspaces(target, map_name, delete=True):
 
     # GET workspaces.
 
@@ -62,7 +56,7 @@ def test_scenario():
     name, title = "testDS1", "test datastore 1"
     _, r = APIRequest("POST", ws["dataStores"]["href"], {"dataStore":{"name":name, "title":title}},
                       get_response=True)
-    ds_link = r.getheader("Location").split(".", 1)[0]
+    ds_link = r.getheader("Location").rsplit(".", 1)[0]
 
     ds = APIRequest("GET", ds_link)["dataStore"]
     assert ds["name"] == name
@@ -96,7 +90,7 @@ def test_scenario():
     name, title = "timezones", "test feature type 1"
     _, r = APIRequest("POST", ds["href"], {"featureType":{"name":name, "title":title}},
                       get_response=True)
-    ft_link = r.getheader("Location").split(".", 1)[0]
+    ft_link = r.getheader("Location").rsplit(".", 1)[0]
 
     ft = APIRequest("GET", ft_link)["featureType"]
     assert ft["name"] == name
@@ -112,13 +106,15 @@ def test_scenario():
 
     # DELETE stuff
 
-    APIRequest("DELETE", ft_link)
-    fts = APIRequest("GET", ds_link + "/featuretypes")["featureTypes"]
-    assert len(fts) == 0
+    if delete:
 
-    APIRequest("DELETE", ds_link)
-    dss = APIRequest("GET", ws["dataStores"]["href"])["dataStores"]
-    assert len(dss) == 0
+        APIRequest("DELETE", ft_link)
+        fts = APIRequest("GET", ds_link + "/featuretypes")["featureTypes"]
+        assert len(fts) == 0
+
+        APIRequest("DELETE", ds_link)
+        dss = APIRequest("GET", ws["dataStores"]["href"])["dataStores"]
+        assert len(dss) == 0
 
 
 
@@ -136,7 +132,7 @@ def test_scenario():
     name, title = "testCS1", "test coverageStore 1"
     _, r = APIRequest("POST", ws["coverageStores"]["href"], {"coverageStore":{"name":name, "title":title}},
                       get_response=True)
-    cs_link = r.getheader("Location").split(".", 1)[0]
+    cs_link = r.getheader("Location").rsplit(".", 1)[0]
 
     cs = APIRequest("GET", cs_link)["coverageStore"]
     assert cs["name"] == name
@@ -170,27 +166,172 @@ def test_scenario():
     name, title = "HYP_LR", "test coverage 1"
     _, r = APIRequest("POST", cs["href"], {"coverage":{"name":name, "title":title}},
                       get_response=True)
-    ft_link = r.getheader("Location").split(".", 1)[0]
+    c_link = r.getheader("Location").rsplit(".", 1)[0]
 
-    ft = APIRequest("GET", ft_link)["coverage"]
+    ft = APIRequest("GET", c_link)["coverage"]
     assert ft["name"] == name
     assert ft["title"] == title
 
     # PUT a coverage
 
     ft["title"] = title.upper()
-    APIRequest("PUT", ft_link, {"coverage":ft})
+    APIRequest("PUT", c_link, {"coverage":ft})
 
-    ft = APIRequest("GET", ft_link)["coverage"]
+    ft = APIRequest("GET", c_link)["coverage"]
     assert ft["title"] == title.upper()
 
     # DELETE stuff
 
-    APIRequest("DELETE", ft_link)
-    fts = APIRequest("GET", cs_link + "/coverages")["coverages"]
-    assert len(fts) == 0
 
-    APIRequest("DELETE", cs_link)
-    css = APIRequest("GET", ws["coverageStores"]["href"])["coverageStores"]
-    assert len(css) == 0
+    if delete:
+
+        APIRequest("DELETE", c_link)
+        fts = APIRequest("GET", cs_link + "/coverages")["coverages"]
+        assert len(fts) == 0
+
+        APIRequest("DELETE", cs_link)
+        css = APIRequest("GET", ws["coverageStores"]["href"])["coverageStores"]
+        assert len(css) == 0
+
+
+    return ws
+
+def _test_styles(target, map_name):
+
+    # Lets DELETE all the styles.
+    styles = APIRequest("GET", target + "/maps/" + map_name + "/styles")["styles"]
+    for style in styles:
+        if style["name"].startswith("__test_"):
+            APIRequest("DELETE", style["href"])
+    styles = APIRequest("GET", target + "/maps/" + map_name + "/styles")["styles"]
+    assert len([style for style in styles if style["name"].startswith("__test_")]) == 0
+
+    # Lets POST a style and GET it.
+    name = "__test_awesome_style_name"
+    data = open("./files/style.sld").read()
+    noise = "".join(map(str, random.sample(xrange(10000000), 60)))
+    # We add some noise, so we can check PUT later.
+
+    styles = APIRequest("POST", target + "/maps/" + map_name + "/styles?name=" + name,
+                        encode=None, content_type="application/vnd.ogc.sld+xml", data=data+noise)
+
+    styles = APIRequest("GET", target + "/maps/" + map_name + "/styles")["styles"]
+    assert len(styles) == 1
+    assert styles[0]["name"] == name
+
+    st_link = styles[0]["href"].rsplit(".", 1)[0]
+    style = APIRequest("GET", st_link)
+
+    content = APIRequest("GET", style["href"], encode=None, decode=None)
+    assert content == data+noise
+
+    # Use PUT to remove the noise in the file.
+    styles = APIRequest("PUT", style["href"], encode=None, content_type="application/vnd.ogc.sld+xml", data=data)
+    content = APIRequest("GET", style["href"], encode=None, decode=None)
+    assert content == data
+
+
+def _test_layers(target, map_name):
+    # We need to setup something to work with first.
+
+    # GET workspaces.
+    wss = APIRequest("GET", target + "/maps/" + map_name + "/workspaces")["workspaces"]
+    ws = APIRequest("GET", wss[0]["href"])["workspace"]
+    # DataStores.
+    name, title = "testDS1", "test datastore 1"
+    _, r = APIRequest("POST", ws["dataStores"]["href"], {"dataStore":{"name":name, "title":title}},
+                      get_response=True)
+    ds_link = r.getheader("Location").rsplit(".", 1)[0]
+    # PUT file
+    APIRequest("PUT", ds_link + "/file.shp", open("./files/timezones_shp.zip", "rb"),
+               encode=None, content_type="application/zip")
+    ds = APIRequest("GET", ds_link)["dataStore"]
+    # POST a featuretype
+    name, title = "timezones", "test feature type 1"
+    _, r = APIRequest("POST", ds["href"], {"featureType":{"name":name, "title":title}},
+                      get_response=True)
+    ft_link = r.getheader("Location").rsplit(".", 1)[0]
+    # CoverageStores.
+    name, title = "testCS1", "test coverageStore 1"
+    _, r = APIRequest("POST", ws["coverageStores"]["href"], {"coverageStore":{"name":name, "title":title}},
+                      get_response=True)
+    cs_link = r.getheader("Location").rsplit(".", 1)[0]
+    # PUT file
+    APIRequest("PUT", cs_link + "/file.tif", open("./files/HYP_LR.zip", "rb"),
+               encode=None, content_type="application/zip")
+    cs = APIRequest("GET", cs_link)["coverageStore"]
+    # POST a coverage
+    name, title = "HYP_LR", "test coverage 1"
+    _, r = APIRequest("POST", cs["href"], {"coverage":{"name":name, "title":title}},
+                      get_response=True)
+    c_link = r.getheader("Location").rsplit(".", 1)[0]
+
+    #
+    # OK, now the actual layer tests.
+    #
+
+    layers = APIRequest("GET", target + "/maps/" + map_name + "/layers")["layers"]
+    assert len(layers) == 0
+
+    # A first layer for featuretype
+
+    name = "FTlayerTest"
+    _, r = APIRequest("POST", target + "/maps/" + map_name + "/layers",
+                      {"layer":{"name":name, "resource":{"href":ft_link}}},
+                      get_response=True)
+    ftl_link = r.getheader("Location").rsplit(".", 1)[0]
+
+    # Check GET.
+    ftl = APIRequest("GET", ftl_link)["layer"]
+    assert ftl["name"] == name
+    assert ftl["type"] == "POLYGON"
+
+    # check GET fields
+    fields = APIRequest("GET", ftl_link + "/fields")["fields"]
+    assert len(fields) == 15
+
+    # check GET layerstyles
+    styles = APIRequest("GET", ftl_link + "/styles")["styles"]
+    assert len(styles) == 1
+
+    APIRequest("POST", ftl_link + "/styles", {"style":{"resource":{"href":styles[0]["href"]}}})
+
+    # A second layer for coverage
+
+    name = "ClayerTest"
+    _, r = APIRequest("POST", target + "/maps/" + map_name + "/layers",
+                      {"layer":{"name":name, "resource":{"href":c_link}}},
+                      get_response=True)
+    ctl_link = r.getheader("Location").rsplit(".", 1)[0]
+
+    # Check GET.
+    ctl = APIRequest("GET", ctl_link)["layer"]
+    assert ctl["name"] == name
+    assert ctl["type"] == "RASTER"
+
+    # Check GET.
+    layers = APIRequest("GET", target + "/maps/" + map_name + "/layers")["layers"]
+    assert len(layers) == 2
+
+    # check GET fields
+    fields = APIRequest("GET", ctl_link + "/fields")["fields"]
+    assert len(fields) == 0
+
+    # check GET layerstyles
+    fields = APIRequest("GET", ctl_link + "/styles")["styles"]
+    assert len(fields) == 0
+
+
+def test_scenario():
+
+    target = "http://localhost:8080"
+    map_name = "tests"
+
+    # Clean the test file, now we are sure it is empty.
+    APIRequest("PUT", target + "/tests/" + map_name)
+
+
+    # _test_workspaces(target, map_name)
+    # _test_styles(target, map_name)
 
+    _test_layers(target, map_name)
diff --git a/tests/utils.py b/tests/utils.py
index bffdfbd..73e4491 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -38,7 +38,8 @@ def deduce_content_type(type):
         return "application/xml"
 
 
-def APIRequest(method, url, data=None, encode="json", decode="json", content_type=None, expected_type=None, get_response=False):
+def APIRequest(method, url, data=None, encode="json", decode="json", content_type=None, expected_type=None,
+               get_response=False):
 
     if encode == "json":
         data = json.dumps(data)
@@ -55,6 +56,9 @@ def APIRequest(method, url, data=None, encode="json", decode="json", content_typ
     else:
         url = surl.path
 
+    if surl.query:
+        url += "?" + surl.query
+
     print >>sys.stderr, method, surl.geturl().replace(surl.path, url)
     conn = httplib.HTTPConnection(surl.hostname, surl.port)
     conn.request(method, url, body=data, headers={"Content-Type":content_type})
-- 
GitLab