Browse Source

Sanitize data when git it to schema. Closes #22

Bastien Sevajol 6 years ago
parent
commit
7fecf1a1eb
3 changed files with 68 additions and 12 deletions
  1. 24 11
      hapic/processor.py
  2. 1 1
      setup.py
  3. 43 0
      tests/unit/test_processor.py

+ 24 - 11
hapic/processor.py View File

@@ -44,22 +44,33 @@ class ProcessorInterface(object):
44 44
         raise NotImplementedError
45 45
 
46 46
 
47
-class InputProcessor(ProcessorInterface):
47
+class Processor(ProcessorInterface):
48
+    @classmethod
49
+    def clean_data(cls, data: typing.Any) -> dict:
50
+        # Fixes #22: Schemas make not validation if None is given
51
+        if data is None:
52
+            return {}
53
+        return data
54
+
55
+
56
+class InputProcessor(Processor):
48 57
     pass
49 58
 
50 59
 
51
-class OutputProcessor(ProcessorInterface):
60
+class OutputProcessor(Processor):
52 61
     pass
53 62
 
54 63
 
55 64
 class MarshmallowOutputProcessor(OutputProcessor):
56 65
     def process(self, data: typing.Any):
57
-        data = self.schema.dump(data).data
58
-        self.validate(data)
59
-        return data
66
+        clean_data = self.clean_data(data)
67
+        dump_data = self.schema.dump(clean_data).data
68
+        self.validate(dump_data)
69
+        return dump_data
60 70
 
61 71
     def validate(self, data: typing.Any) -> None:
62
-        errors = self.schema.load(data).errors
72
+        clean_data = self.clean_data(data)
73
+        errors = self.schema.load(clean_data).errors
63 74
         if errors:
64 75
             raise OutputValidationException(
65 76
                 'Error when validate input: {}'.format(
@@ -68,8 +79,9 @@ class MarshmallowOutputProcessor(OutputProcessor):
68 79
             )
69 80
 
70 81
     def get_validation_error(self, data: dict) -> ProcessValidationError:
71
-        data = self.schema.dump(data).data
72
-        errors = self.schema.load(data).errors
82
+        clean_data = self.clean_data(data)
83
+        dump_data = self.schema.dump(clean_data).data
84
+        errors = self.schema.load(dump_data).errors
73 85
         return ProcessValidationError(
74 86
             message='Validation error of output data',
75 87
             details=errors,
@@ -78,7 +90,8 @@ class MarshmallowOutputProcessor(OutputProcessor):
78 90
 
79 91
 class MarshmallowInputProcessor(InputProcessor):
80 92
     def process(self, data: dict):
81
-        unmarshall = self.schema.load(data)
93
+        clean_data = self.clean_data(data)
94
+        unmarshall = self.schema.load(clean_data)
82 95
         if unmarshall.errors:
83 96
             raise OutputValidationException(
84 97
                 'Error when validate ouput: {}'.format(
@@ -89,9 +102,9 @@ class MarshmallowInputProcessor(InputProcessor):
89 102
         return unmarshall.data
90 103
 
91 104
     def get_validation_error(self, data: dict) -> ProcessValidationError:
92
-        marshmallow_errors = self.schema.load(data).errors
105
+        clean_data = self.clean_data(data)
106
+        marshmallow_errors = self.schema.load(clean_data).errors
93 107
         return ProcessValidationError(
94 108
             message='Validation error of input data',
95 109
             details=marshmallow_errors,
96 110
         )
97
-

+ 1 - 1
setup.py View File

@@ -23,7 +23,7 @@ setup(
23 23
     # Versions should comply with PEP440.  For a discussion on single-sourcing
24 24
     # the version across setup.py and the project code, see
25 25
     # https://packaging.python.org/en/latest/single_source_version.html
26
-    version='0.0.4.1',
26
+    version='0.4.2',
27 27
 
28 28
     description='HTTP api input/output manager',
29 29
     # long_description=long_description,

+ 43 - 0
tests/unit/test_processor.py View File

@@ -54,6 +54,7 @@ class TestProcessor(Base):
54 54
         processor.schema = MySchema()
55 55
 
56 56
         tested_data = {
57
+            # Missing 'first_name' key
57 58
             'last_name': 'Turing',
58 59
         }
59 60
 
@@ -64,6 +65,48 @@ class TestProcessor(Base):
64 65
         assert errors.details
65 66
         assert 'first_name' in errors.details
66 67
 
68
+    def test_unit__marshmallow_input_processor__error__validation_error_no_data(self):  # nopep8
69
+        processor = MarshmallowInputProcessor()
70
+        processor.schema = MySchema()
71
+
72
+        # Schema will not valid it because require first_name field
73
+        tested_data = {}
74
+
75
+        with pytest.raises(OutputValidationException):
76
+            processor.process(tested_data)
77
+
78
+        errors = processor.get_validation_error(tested_data)
79
+        assert errors.details
80
+        assert 'first_name' in errors.details
81
+
82
+    def test_unit__marshmallow_input_processor__error__validation_error_no_data_none(self):  # nopep8
83
+        processor = MarshmallowInputProcessor()
84
+        processor.schema = MySchema()
85
+
86
+        # Schema will not valid it because require first_name field
87
+        tested_data = None
88
+
89
+        with pytest.raises(OutputValidationException):
90
+            processor.process(tested_data)
91
+
92
+        errors = processor.get_validation_error(tested_data)
93
+        assert errors.details
94
+        assert 'first_name' in errors.details
95
+
96
+    def test_unit__marshmallow_input_processor__error__validation_error_no_data_empty_string(self):  # nopep8
97
+        processor = MarshmallowInputProcessor()
98
+        processor.schema = MySchema()
99
+
100
+        # Schema will not valid it because require first_name field
101
+        tested_data = ''
102
+
103
+        with pytest.raises(OutputValidationException):
104
+            processor.process(tested_data)
105
+
106
+        errors = processor.get_validation_error(tested_data)
107
+        assert errors.details
108
+        assert {'_schema': ['Invalid input type.']} == errors.details
109
+
67 110
     def test_unit__marshmallow_input_processor__ok__completed_data(self):
68 111
         processor = MarshmallowInputProcessor()
69 112
         processor.schema = MySchema()