Repository: Cornices/cornice.ext.swagger

Issue:
Reach and ensure 100% coverage

Discussion:

Patch:
diff --git a/cornice_swagger/converters/schema.py b/cornice_swagger/converters/schema.py
index 343b357..45686cb 100644
--- a/cornice_swagger/converters/schema.py
+++ b/cornice_swagger/converters/schema.py
@@ -62,7 +62,7 @@ def convert_regex_validator(validator):
         converted = {}
 
         if hasattr(colander, 'url') and validator is colander.url:
-            converted['format'] = 'uri'
+            converted['format'] = 'url'
         elif isinstance(validator, colander.Email):
             converted['format'] = 'email'
         else:
@@ -96,7 +96,7 @@ class ValidatorConversionDispatcher(object):
         if isinstance(validator, colander.All):
             converted = {}
             for v in validator.validators:
-                ret = self(v)
+                ret = self(None, v)
                 converted.update(ret)
             return converted
         else:
diff --git a/cornice_swagger/swagger.py b/cornice_swagger/swagger.py
index 4fff44e..035a3d9 100644
--- a/cornice_swagger/swagger.py
+++ b/cornice_swagger/swagger.py
@@ -1,5 +1,6 @@
 """Cornice Swagger 2.0 documentor"""
 import re
+import six
 
 import colander
 from cornice.validators import colander_validator, colander_body_validator
@@ -100,7 +101,7 @@ class CorniceSwagger(object):
 
                     if default_tag not in [t['name'] for t in tags]:
                         tag = {'name': default_tag}
-                        if cornice_swagger.util.is_string(view):
+                        if isinstance(view, six.string_types):
                             ob = args['klass']
                             desc = cornice_swagger.util.trim(ob.__doc__)
                             tag['description'] = desc
@@ -198,7 +199,7 @@ class CorniceSwagger(object):
                 op['parameters'] = parameters
 
         # Get summary from docstring
-        if cornice_swagger.util.is_string(view):
+        if isinstance(view, six.string_types):
             if 'klass' in args:
                 ob = args['klass']
                 view_ = getattr(ob, view.lower())
@@ -308,7 +309,7 @@ class ParameterHandler(object):
 
         params = []
 
-        if not cornice_swagger.util.is_object(schema_node):
+        if not isinstance(schema_node, colander.Schema):
             schema_node = schema_node()
 
         if colander_validator in validators:
@@ -364,9 +365,6 @@ class ParameterHandler(object):
 
         name = base_name or param.get('title', '') or param.get('name', '')
 
-        if not name:
-            raise CorniceSwaggerException('Parameter needs a name')
-
         pointer = self.json_pointer + name
         self.parameters[name] = param
 
diff --git a/cornice_swagger/util.py b/cornice_swagger/util.py
index 7ff9f54..c896bf6 100644
--- a/cornice_swagger/util.py
+++ b/cornice_swagger/util.py
@@ -1,24 +1,8 @@
 import sys
-import inspect
 
 
 PY3 = sys.version_info[0] == 3
 
-if PY3:
-    builtin_str = str
-    str = str
-    bytes = bytes
-    basestring = (str, bytes)
-else:
-    builtin_str = str
-    bytes = str
-    str = unicode  # noqa
-    basestring = basestring
-
-
-def is_string(s):
-    return isinstance(s, basestring)
-
 
 def trim(docstring):
     """
@@ -32,54 +16,9 @@ def trim(docstring):
     # Convert tabs to spaces (following the normal Python rules)
     # and split into a list of lines:
     lines = docstring.expandtabs().splitlines()
-    # Determine minimum indentation (first line doesn't count):
-    indent = sys.maxsize
-    for line in lines[1:]:
-        stripped = line.lstrip()
-        if stripped:
-            indent = min(indent, len(line) - len(stripped))
-    # Remove indentation (first line is special):
-    trimmed = [lines[0].strip()]
-    if indent < sys.maxsize:
-        for line in lines[1:]:
-            trimmed.append(line[indent:].rstrip())
-    # Strip off trailing and leading blank lines:
-    while trimmed and not trimmed[-1]:
-        trimmed.pop()
-    while trimmed and not trimmed[0]:
-        trimmed.pop(0)
-    # Return a single string:
-    res = '\n'.join(trimmed)
-    if not PY3 and not isinstance(res, str):
-        res = res.decode('utf8')
+    res = '\n'.join(lines)
     return res
 
 
 def get_class_that_defined_method(meth):
-    if hasattr(meth, "im_class"):
-        return meth.im_class
-    if PY3:
-        if inspect.ismethod(meth):
-            for cls in inspect.getmro(meth.__self__.__class__):
-                if cls.__dict__.get(meth.__name__) is meth:
-                    return cls
-            # fallback to __qualname__ parsing
-            meth = meth.__func__
-        if inspect.isfunction(meth):
-            cls = getattr(
-                inspect.getmodule(meth),
-                meth.__qualname__.split('.<locals>', 1)[0].rsplit('.', 1)[0])
-            if isinstance(cls, type):
-                return cls
-    return None
-
-
-def is_object(obj):
-    if not hasattr(obj, '__dict__'):
-        return False
-    if inspect.isroutine(obj):
-        return False
-    if inspect.isclass(obj):
-        return False
-    else:
-        return True
+    return getattr(meth, 'im_class', None)


Test patch:
diff --git a/tests/converters/test_parameters.py b/tests/converters/test_parameters.py
index 3dd4ee9..60c4bf0 100644
--- a/tests/converters/test_parameters.py
+++ b/tests/converters/test_parameters.py
@@ -3,6 +3,7 @@ import unittest
 import colander
 
 from cornice_swagger.converters import convert_parameter, convert_schema
+from cornice_swagger.converters.exceptions import NoSuchConverter
 
 
 class ParameterConversionTest(unittest.TestCase):
@@ -51,3 +52,7 @@ class ParameterConversionTest(unittest.TestCase):
             'required': True,
             'schema': convert_schema(MyBody(title='MyBody')),
         })
+
+    def test_raise_no_such_converter_on_invalid_location(self):
+        node = colander.SchemaNode(colander.String(), name='foo')
+        self.assertRaises(NoSuchConverter, convert_parameter, 'aaa', node)
diff --git a/tests/converters/test_schema.py b/tests/converters/test_schema.py
index d432759..6fd9504 100644
--- a/tests/converters/test_schema.py
+++ b/tests/converters/test_schema.py
@@ -2,6 +2,28 @@ import unittest
 import colander
 
 from cornice_swagger.converters import convert_schema as convert
+from cornice_swagger.converters.exceptions import NoSuchConverter
+
+
+class ConversionTest(unittest.TestCase):
+
+    def test_validate_all(self):
+        node = colander.SchemaNode(colander.String(),
+                                   validator=colander.All(
+                                       colander.Length(12, 42),
+                                       colander.Regex(r'foo*bar')
+                                   ))
+        ret = convert(node)
+        self.assertDictEqual(ret, {
+            'type': 'string',
+            'pattern': 'foo*bar',
+            'maxLength': 42,
+            'minLength': 12,
+        })
+
+    def test_raise_no_such_converter_on_invalid_type(self):
+        node = colander.SchemaNode(dict)
+        self.assertRaises(NoSuchConverter, convert, node)
 
 
 class StringConversionTest(unittest.TestCase):
@@ -49,6 +71,15 @@ class StringConversionTest(unittest.TestCase):
             'format': 'email',
         })
 
+    def test_validate_regex_url(self):
+        node = colander.SchemaNode(colander.String(),
+                                   validator=colander.url)
+        ret = convert(node)
+        self.assertDictEqual(ret, {
+            'type': 'string',
+            'format': 'url',
+        })
+
     def test_validate_oneof(self):
         node = colander.SchemaNode(colander.String(),
                                    validator=colander.OneOf(["one", "two"]))
diff --git a/tests/support.py b/tests/support.py
new file mode 100644
index 0000000..72a995d
--- /dev/null
+++ b/tests/support.py
@@ -0,0 +1,23 @@
+import colander
+
+
+class MyNestedSchema(colander.MappingSchema):
+    my_precious = colander.SchemaNode(colander.Boolean())
+
+
+class BodySchema(colander.MappingSchema):
+    id = colander.SchemaNode(colander.String())
+    timestamp = colander.SchemaNode(colander.Int())
+    obj = MyNestedSchema()
+
+
+class QuerySchema(colander.MappingSchema):
+    foo = colander.SchemaNode(colander.String(), missing=colander.drop)
+
+
+class HeaderSchema(colander.MappingSchema):
+    bar = colander.SchemaNode(colander.String())
+
+
+class PathSchema(colander.MappingSchema):
+    meh = colander.SchemaNode(colander.String(), default='default')
diff --git a/tests/test_cornice_swagger.py b/tests/test_generate_swagger_spec.py
similarity index 100%
rename from tests/test_cornice_swagger.py
rename to tests/test_generate_swagger_spec.py
diff --git a/tests/test_parameter_handler.py b/tests/test_parameter_handler.py
index 29a9f30..d44b999 100644
--- a/tests/test_parameter_handler.py
+++ b/tests/test_parameter_handler.py
@@ -6,28 +6,7 @@ from cornice.validators import colander_validator, colander_body_validator
 
 from cornice_swagger.swagger import ParameterHandler
 from cornice_swagger.converters import convert_schema
-
-
-class MyNestedSchema(colander.MappingSchema):
-    my_precious = colander.SchemaNode(colander.Boolean())
-
-
-class BodySchema(colander.MappingSchema):
-    id = colander.SchemaNode(colander.String())
-    timestamp = colander.SchemaNode(colander.Int())
-    obj = MyNestedSchema()
-
-
-class QuerySchema(colander.MappingSchema):
-    foo = colander.SchemaNode(colander.String(), missing=colander.drop)
-
-
-class HeaderSchema(colander.MappingSchema):
-    bar = colander.SchemaNode(colander.String())
-
-
-class PathSchema(colander.MappingSchema):
-    meh = colander.SchemaNode(colander.String(), default='default')
+from .support import BodySchema, PathSchema, QuerySchema, HeaderSchema
 
 
 class SchemaParamConversionTest(unittest.TestCase):
diff --git a/tests/test_swagger.py b/tests/test_swagger.py
new file mode 100644
index 0000000..be08c4e
--- /dev/null
+++ b/tests/test_swagger.py
@@ -0,0 +1,171 @@
+import unittest
+
+import colander
+from cornice.validators import colander_validator
+from cornice.service import Service
+
+from cornice_swagger.swagger import CorniceSwagger
+from .support import BodySchema, QuerySchema
+
+
+class GetRequestSchema(colander.MappingSchema):
+    querystring = QuerySchema()
+
+
+class PutRequestSchema(colander.MappingSchema):
+    querystring = QuerySchema()
+    body = BodySchema()
+
+
+class TestCorniceSwaggerGenerator(unittest.TestCase):
+
+    def setUp(self):
+
+        service = Service("IceCream", "/icecream/{flavour}")
+
+        class IceCream(object):
+            """
+            Ice cream service
+            """
+
+            @service.get(validators=(colander_validator, ), schema=GetRequestSchema())
+            def view_get(self, request):
+                """Serve icecream"""
+                return self.request.validated
+
+            @service.put(validators=(colander_validator, ), schema=PutRequestSchema())
+            def view_put(self, request):
+                """Add flavour"""
+                return self.request.validated
+
+        self.service = service
+        self.swagger = CorniceSwagger([self.service])
+        self.spec = self.swagger('IceCreamAPI', '4.2')
+
+    def test_path(self):
+        self.assertIn('/icecream/{flavour}', self.spec['paths'])
+
+    def test_path_methods(self):
+        path = self.spec['paths']['/icecream/{flavour}']
+        self.assertIn('get', path)
+        self.assertIn('put', path)
+
+    def test_path_parameters(self):
+        parameters = self.spec['paths']['/icecream/{flavour}']['parameters']
+        self.assertEquals(len(parameters), 1)
+        self.assertEquals(parameters[0]['name'], 'flavour')
+
+    def test_path_default_tags(self):
+        tags = self.spec['paths']['/icecream/{flavour}']['get']['tags']
+        self.assertEquals(tags, ['icecream'])
+
+    def test_with_schema_ref(self):
+        swagger = CorniceSwagger([self.service], def_ref_depth=1)
+        spec = swagger('IceCreamAPI', '4.2')
+        self.assertIn('definitions', spec)
+
+    def test_with_param_ref(self):
+        swagger = CorniceSwagger([self.service], param_ref=True)
+        spec = swagger('IceCreamAPI', '4.2')
+        self.assertIn('parameters', spec)
+
+
+class TestExtractContentTypes(unittest.TestCase):
+
+    def test_json_renderer(self):
+
+        service = Service("IceCream", "/icecream/{flavour}")
+
+        class IceCream(object):
+            """Ice cream service"""
+            @service.get(validators=(colander_validator, ), schema=GetRequestSchema(),
+                         renderer='json')
+            def view_get(self, request):
+                """Serve icecream"""
+                return self.request.validated
+
+        swagger = CorniceSwagger([service])
+        spec = swagger('IceCreamAPI', '4.2')
+        self.assertEquals(spec['paths']['/icecream/{flavour}']['get']['produces'],
+                          set(['application/json']))
+
+    def test_xml_renderer(self):
+
+        service = Service("IceCream", "/icecream/{flavour}")
+
+        class IceCream(object):
+            """Ice cream service"""
+            @service.get(validators=(colander_validator, ), schema=GetRequestSchema(),
+                         renderer='xml')
+            def view_get(self, request):
+                """Serve icecream"""
+                return self.request.validated
+
+        swagger = CorniceSwagger([service])
+        spec = swagger('IceCreamAPI', '4.2')
+        self.assertEquals(spec['paths']['/icecream/{flavour}']['get']['produces'],
+                          set(['text/xml']))
+
+    def test_unkown_renderer(self):
+
+        service = Service("IceCream", "/icecream/{flavour}")
+
+        class IceCream(object):
+            """Ice cream service"""
+            @service.get(validators=(colander_validator, ), schema=GetRequestSchema(),
+                         renderer='')
+            def view_get(self, request):
+                """Serve icecream"""
+                return self.request.validated
+
+        swagger = CorniceSwagger([service])
+        spec = swagger('IceCreamAPI', '4.2')
+        self.assertNotIn('produces', spec['paths']['/icecream/{flavour}']['get'])
+
+    def test_ctypes(self):
+
+        service = Service("IceCream", "/icecream/{flavour}")
+
+        class IceCream(object):
+            """Ice cream service"""
+            @service.put(validators=(colander_validator, ), schema=GetRequestSchema(),
+                         content_type=['application/json'])
+            def view_put(self, request):
+                """Serve icecream"""
+                return self.request.validated
+
+        swagger = CorniceSwagger([service])
+        spec = swagger('IceCreamAPI', '4.2')
+        self.assertEquals(spec['paths']['/icecream/{flavour}']['put']['consumes'],
+                          set(['application/json']))
+
+    def test_multiple_views_with_different_ctypes(self):
+
+        service = Service("IceCream", "/icecream/{flavour}")
+
+        class IceCream(object):
+            """Ice cream service"""
+
+            def view_put(self, request):
+                """Serve icecream"""
+                return "red"
+
+        service.add_view(
+            "put",
+            IceCream.view_put,
+            validators=(colander_validator, ),
+            schema=PutRequestSchema(),
+            content_type=['application/json'],
+        )
+        service.add_view(
+            "put",
+            IceCream.view_put,
+            validators=(colander_validator, ),
+            schema=PutRequestSchema(),
+            content_type=['text/xml'],
+        )
+
+        swagger = CorniceSwagger([service])
+        spec = swagger('IceCreamAPI', '4.2')
+        self.assertEquals(spec['paths']['/icecream/{flavour}']['put']['produces'],
+                          set(['application/json']))