Merge branch 'fix_arbitrary_code_execution' into develop
This commit is contained in:
		
							
								
								
									
										64
									
								
								owrx/dsp.py
									
									
									
									
									
								
							
							
						
						
									
										64
									
								
								owrx/dsp.py
									
									
									
									
									
								
							| @@ -4,16 +4,26 @@ from owrx.js8 import Js8Parser | |||||||
| from owrx.aprs import AprsParser | from owrx.aprs import AprsParser | ||||||
| from owrx.pocsag import PocsagParser | from owrx.pocsag import PocsagParser | ||||||
| from owrx.source import SdrSource, SdrSourceEventClient | from owrx.source import SdrSource, SdrSourceEventClient | ||||||
| from owrx.property import PropertyStack, PropertyLayer | from owrx.property import PropertyStack, PropertyLayer, PropertyValidator | ||||||
|  | from owrx.property.validators import OrValidator, RegexValidator, BoolValidator | ||||||
| from owrx.modes import Modes | from owrx.modes import Modes | ||||||
| from csdr import csdr | from csdr import csdr | ||||||
| import threading | import threading | ||||||
|  | import re | ||||||
|  |  | ||||||
| import logging | import logging | ||||||
|  |  | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ModulationValidator(OrValidator): | ||||||
|  |     """ | ||||||
|  |     This validator only allows alphanumeric characters and numbers, but no spaces or special characters | ||||||
|  |     """ | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__(BoolValidator(), RegexValidator(re.compile("^[a-z0-9]+$"))) | ||||||
|  |  | ||||||
|  |  | ||||||
| class DspManager(csdr.output, SdrSourceEventClient): | class DspManager(csdr.output, SdrSourceEventClient): | ||||||
|     def __init__(self, handler, sdrSource): |     def __init__(self, handler, sdrSource): | ||||||
|         self.handler = handler |         self.handler = handler | ||||||
| @@ -27,22 +37,42 @@ class DspManager(csdr.output, SdrSourceEventClient): | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         self.props = PropertyStack() |         self.props = PropertyStack() | ||||||
|  |  | ||||||
|         # local demodulator properties not forwarded to the sdr |         # local demodulator properties not forwarded to the sdr | ||||||
|         self.props.addLayer( |         # ensure strict validation since these can be set from the client | ||||||
|             0, |         # and are used to build executable commands | ||||||
|             PropertyLayer().filter( |         validators = { | ||||||
|                 "output_rate", |             "output_rate": "int", | ||||||
|                 "hd_output_rate", |             "hd_output_rate": "int", | ||||||
|                 "squelch_level", |             "squelch_level": "num", | ||||||
|                 "secondary_mod", |             "secondary_mod": ModulationValidator(), | ||||||
|                 "low_cut", |             "low_cut": "num", | ||||||
|                 "high_cut", |             "high_cut": "num", | ||||||
|                 "offset_freq", |             "offset_freq": "int", | ||||||
|                 "mod", |             "mod": ModulationValidator(), | ||||||
|                 "secondary_offset_freq", |             "secondary_offset_freq": "int", | ||||||
|                 "dmr_filter", |             "dmr_filter": "int", | ||||||
|             ), |         } | ||||||
|         ) |         self.localProps = PropertyValidator(PropertyLayer().filter(*validators.keys()), validators) | ||||||
|  |  | ||||||
|  |         self.props.addLayer(0, self.localProps) | ||||||
|  |         # ensure strict validation since these can be set from the client | ||||||
|  |         # and are used to build executable commands | ||||||
|  |         validators = { | ||||||
|  |             "output_rate": "int", | ||||||
|  |             "hd_output_rate": "int", | ||||||
|  |             "squelch_level": "num", | ||||||
|  |             "secondary_mod": ModulationValidator(), | ||||||
|  |             "low_cut": "num", | ||||||
|  |             "high_cut": "num", | ||||||
|  |             "offset_freq": "int", | ||||||
|  |             "mod": ModulationValidator(), | ||||||
|  |             "secondary_offset_freq": "int", | ||||||
|  |             "dmr_filter": "int", | ||||||
|  |         } | ||||||
|  |         self.localProps = PropertyValidator(PropertyLayer().filter(*validators.keys()), validators) | ||||||
|  |  | ||||||
|  |         self.props.addLayer(0, self.localProps) | ||||||
|         # properties that we inherit from the sdr |         # properties that we inherit from the sdr | ||||||
|         self.props.addLayer( |         self.props.addLayer( | ||||||
|             1, |             1, | ||||||
| @@ -188,7 +218,7 @@ class DspManager(csdr.output, SdrSourceEventClient): | |||||||
|             self.setProperty(k, v) |             self.setProperty(k, v) | ||||||
|  |  | ||||||
|     def setProperty(self, prop, value): |     def setProperty(self, prop, value): | ||||||
|         self.props[prop] = value |         self.localProps[prop] = value | ||||||
|  |  | ||||||
|     def getClientClass(self): |     def getClientClass(self): | ||||||
|         return SdrSource.CLIENT_USER |         return SdrSource.CLIENT_USER | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| from abc import ABC, abstractmethod | from abc import ABC, abstractmethod | ||||||
|  | from owrx.property.validators import Validator | ||||||
| import logging | import logging | ||||||
|  |  | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
| @@ -154,6 +155,50 @@ class PropertyFilter(PropertyManager): | |||||||
|         return [k for k in self.pm.keys() if k in self.props] |         return [k for k in self.pm.keys() if k in self.props] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PropertyValidationError(Exception): | ||||||
|  |     def __init__(self, key, value): | ||||||
|  |         super().__init__('Invalid value for property "{key}": "{value}"'.format(key=key, value=str(value))) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PropertyValidator(PropertyManager): | ||||||
|  |     def __init__(self, pm: PropertyManager, validators=None): | ||||||
|  |         self.pm = pm | ||||||
|  |         self.pm.wire(self._fireCallbacks) | ||||||
|  |         if validators is None: | ||||||
|  |             self.validators = {} | ||||||
|  |         else: | ||||||
|  |             self.validators = {k: Validator.of(v) for k, v in validators.items()} | ||||||
|  |         super().__init__() | ||||||
|  |  | ||||||
|  |     def validate(self, key, value): | ||||||
|  |         if key not in self.validators: | ||||||
|  |             return | ||||||
|  |         if not self.validators[key].isValid(value): | ||||||
|  |             raise PropertyValidationError(key, value) | ||||||
|  |  | ||||||
|  |     def setValidator(self, key, validator): | ||||||
|  |         self.validators[key] = Validator.of(validator) | ||||||
|  |  | ||||||
|  |     def __getitem__(self, item): | ||||||
|  |         return self.pm.__getitem__(item) | ||||||
|  |  | ||||||
|  |     def __setitem__(self, key, value): | ||||||
|  |         self.validate(key, value) | ||||||
|  |         return self.pm.__setitem__(key, value) | ||||||
|  |  | ||||||
|  |     def __contains__(self, item): | ||||||
|  |         return self.pm.__contains__(item) | ||||||
|  |  | ||||||
|  |     def __dict__(self): | ||||||
|  |         return self.pm.__dict__() | ||||||
|  |  | ||||||
|  |     def __delitem__(self, key): | ||||||
|  |         return self.pm.__delitem__(key) | ||||||
|  |  | ||||||
|  |     def keys(self): | ||||||
|  |         return self.pm.keys() | ||||||
|  |  | ||||||
|  |  | ||||||
| class PropertyStack(PropertyManager): | class PropertyStack(PropertyManager): | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         super().__init__() |         super().__init__() | ||||||
|   | |||||||
							
								
								
									
										98
									
								
								owrx/property/validators.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								owrx/property/validators.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | |||||||
|  | from abc import ABC, abstractmethod | ||||||
|  | from functools import reduce | ||||||
|  | from operator import or_ | ||||||
|  | import re | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ValidatorException(Exception): | ||||||
|  |     pass | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Validator(ABC): | ||||||
|  |     @staticmethod | ||||||
|  |     def of(x): | ||||||
|  |         if isinstance(x, Validator): | ||||||
|  |             return x | ||||||
|  |         if callable(x): | ||||||
|  |             return LambdaValidator(x) | ||||||
|  |         if x in validator_types: | ||||||
|  |             return validator_types[x]() | ||||||
|  |         raise ValidatorException("Cannot create validator") | ||||||
|  |  | ||||||
|  |     @abstractmethod | ||||||
|  |     def isValid(self, value): | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class LambdaValidator(Validator): | ||||||
|  |     def __init__(self, c): | ||||||
|  |         self.callable = c | ||||||
|  |  | ||||||
|  |     def isValid(self, value): | ||||||
|  |         return self.callable(value) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TypeValidator(Validator): | ||||||
|  |     def __init__(self, type): | ||||||
|  |         self.type = type | ||||||
|  |         super().__init__() | ||||||
|  |  | ||||||
|  |     def isValid(self, value): | ||||||
|  |         return isinstance(value, self.type) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class IntegerValidator(TypeValidator): | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__(int) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FloatValidator(TypeValidator): | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__(float) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class StringValidator(TypeValidator): | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__(str) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class BoolValidator(TypeValidator): | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__(bool) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class OrValidator(Validator): | ||||||
|  |     def __init__(self, *validators): | ||||||
|  |         self.validators = validators | ||||||
|  |         super().__init__() | ||||||
|  |  | ||||||
|  |     def isValid(self, value): | ||||||
|  |         return reduce( | ||||||
|  |             or_, | ||||||
|  |             [v.isValid(value) for v in self.validators], | ||||||
|  |             False | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class NumberValidator(OrValidator): | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__(IntegerValidator(), FloatValidator()) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class RegexValidator(StringValidator): | ||||||
|  |     def __init__(self, regex: re.Pattern): | ||||||
|  |         self.regex = regex | ||||||
|  |         super().__init__() | ||||||
|  |  | ||||||
|  |     def isValid(self, value): | ||||||
|  |         return super().isValid(value) and self.regex.match(value) is not None | ||||||
|  |  | ||||||
|  |  | ||||||
|  | validator_types = { | ||||||
|  |     "string": StringValidator, | ||||||
|  |     "str": StringValidator, | ||||||
|  |     "integer": IntegerValidator, | ||||||
|  |     "int": IntegerValidator, | ||||||
|  |     "number": NumberValidator, | ||||||
|  |     "num": NumberValidator, | ||||||
|  | } | ||||||
| @@ -48,3 +48,11 @@ class PropertyFilterTest(TestCase): | |||||||
|         pf["testkey"] = "new value" |         pf["testkey"] = "new value" | ||||||
|         self.assertEqual(pm["testkey"], "new value") |         self.assertEqual(pm["testkey"], "new value") | ||||||
|         self.assertEqual(pf["testkey"], "new value") |         self.assertEqual(pf["testkey"], "new value") | ||||||
|  |  | ||||||
|  |     def testRejectsWrite(self): | ||||||
|  |         pm = PropertyLayer() | ||||||
|  |         pm["testkey"] = "old value" | ||||||
|  |         pf = PropertyFilter(pm, "otherkey") | ||||||
|  |         with self.assertRaises(KeyError): | ||||||
|  |             pf["testkey"] = "new value" | ||||||
|  |         self.assertEqual(pm["testkey"], "old value") | ||||||
|   | |||||||
							
								
								
									
										37
									
								
								test/property/test_property_validator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								test/property/test_property_validator.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | from unittest import TestCase | ||||||
|  | from owrx.property import PropertyLayer, PropertyValidator, PropertyValidationError | ||||||
|  | from owrx.property.validators import NumberValidator, StringValidator | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PropertyValidatorTest(TestCase): | ||||||
|  |     def testPassesUnvalidated(self): | ||||||
|  |         pm = PropertyLayer() | ||||||
|  |         pv = PropertyValidator(pm) | ||||||
|  |         pv["testkey"] = "testvalue" | ||||||
|  |         self.assertEqual(pv["testkey"], "testvalue") | ||||||
|  |         self.assertEqual(pm["testkey"], "testvalue") | ||||||
|  |  | ||||||
|  |     def testPassesValidValue(self): | ||||||
|  |         pv = PropertyValidator(PropertyLayer(), {"testkey": NumberValidator()}) | ||||||
|  |         pv["testkey"] = 42 | ||||||
|  |         self.assertEqual(pv["testkey"], 42) | ||||||
|  |  | ||||||
|  |     def testThrowsErrorOnInvalidValue(self): | ||||||
|  |         pv = PropertyValidator(PropertyLayer(), {"testkey": NumberValidator()}) | ||||||
|  |         with self.assertRaises(PropertyValidationError): | ||||||
|  |             pv["testkey"] = "text" | ||||||
|  |  | ||||||
|  |     def testSetValidator(self): | ||||||
|  |         pv = PropertyValidator(PropertyLayer()) | ||||||
|  |         pv.setValidator("testkey", NumberValidator()) | ||||||
|  |         with self.assertRaises(PropertyValidationError): | ||||||
|  |             pv["testkey"] = "text" | ||||||
|  |  | ||||||
|  |     def testUpdateValidator(self): | ||||||
|  |         pv = PropertyValidator(PropertyLayer(), {"testkey": StringValidator()}) | ||||||
|  |         # this should pass | ||||||
|  |         pv["testkey"] = "text" | ||||||
|  |         pv.setValidator("testkey", NumberValidator()) | ||||||
|  |         # this should raise | ||||||
|  |         with self.assertRaises(PropertyValidationError): | ||||||
|  |             pv["testkey"] = "text" | ||||||
							
								
								
									
										0
									
								
								test/property/validators/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								test/property/validators/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										17
									
								
								test/property/validators/test_bool_validator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								test/property/validators/test_bool_validator.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | from unittest import TestCase | ||||||
|  | from owrx.property.validators import BoolValidator | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class NumberValidatorTest(TestCase): | ||||||
|  |     def testPassesNumbers(self): | ||||||
|  |         validator = BoolValidator() | ||||||
|  |         self.assertTrue(validator.isValid(True)) | ||||||
|  |         self.assertTrue(validator.isValid(False)) | ||||||
|  |  | ||||||
|  |     def testDoesntPassOthers(self): | ||||||
|  |         validator = BoolValidator() | ||||||
|  |         self.assertFalse(validator.isValid(123)) | ||||||
|  |         self.assertFalse(validator.isValid(-2)) | ||||||
|  |         self.assertFalse(validator.isValid(.5)) | ||||||
|  |         self.assertFalse(validator.isValid("text")) | ||||||
|  |         self.assertFalse(validator.isValid(object())) | ||||||
							
								
								
									
										15
									
								
								test/property/validators/test_float_validator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								test/property/validators/test_float_validator.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | from unittest import TestCase | ||||||
|  | from owrx.property.validators import FloatValidator | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FloatValidatorTest(TestCase): | ||||||
|  |     def testPassesNumbers(self): | ||||||
|  |         validator = FloatValidator() | ||||||
|  |         self.assertTrue(validator.isValid(.5)) | ||||||
|  |  | ||||||
|  |     def testDoesntPassOthers(self): | ||||||
|  |         validator = FloatValidator() | ||||||
|  |         self.assertFalse(validator.isValid(123)) | ||||||
|  |         self.assertFalse(validator.isValid(-2)) | ||||||
|  |         self.assertFalse(validator.isValid("text")) | ||||||
|  |         self.assertFalse(validator.isValid(object())) | ||||||
							
								
								
									
										15
									
								
								test/property/validators/test_integer_validator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								test/property/validators/test_integer_validator.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | from unittest import TestCase | ||||||
|  | from owrx.property.validators import IntegerValidator | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class IntegerValidatorTest(TestCase): | ||||||
|  |     def testPassesIntegers(self): | ||||||
|  |         validator = IntegerValidator() | ||||||
|  |         self.assertTrue(validator.isValid(123)) | ||||||
|  |         self.assertTrue(validator.isValid(-2)) | ||||||
|  |  | ||||||
|  |     def testDoesntPassOthers(self): | ||||||
|  |         validator = IntegerValidator() | ||||||
|  |         self.assertFalse(validator.isValid(.5)) | ||||||
|  |         self.assertFalse(validator.isValid("text")) | ||||||
|  |         self.assertFalse(validator.isValid(object())) | ||||||
							
								
								
									
										21
									
								
								test/property/validators/test_lambda_validator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								test/property/validators/test_lambda_validator.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | from unittest import TestCase | ||||||
|  | from unittest.mock import Mock | ||||||
|  | from owrx.property.validators import LambdaValidator | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class LambdaValidatorTest(TestCase): | ||||||
|  |     def testPassesValue(self): | ||||||
|  |         mock = Mock() | ||||||
|  |         validator = LambdaValidator(mock.method) | ||||||
|  |         validator.isValid("test") | ||||||
|  |         mock.method.assert_called_once_with("test") | ||||||
|  |  | ||||||
|  |     def testReturnsTrue(self): | ||||||
|  |         validator = LambdaValidator(lambda x: True) | ||||||
|  |         self.assertTrue(validator.isValid("any value")) | ||||||
|  |         self.assertTrue(validator.isValid(3.1415926)) | ||||||
|  |  | ||||||
|  |     def testReturnsFalse(self): | ||||||
|  |         validator = LambdaValidator(lambda x: False) | ||||||
|  |         self.assertFalse(validator.isValid("any value")) | ||||||
|  |         self.assertFalse(validator.isValid(42)) | ||||||
							
								
								
									
										18
									
								
								test/property/validators/test_number_validator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								test/property/validators/test_number_validator.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | from unittest import TestCase | ||||||
|  | from owrx.property.validators import NumberValidator | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class NumberValidatorTest(TestCase): | ||||||
|  |     def testPassesNumbers(self): | ||||||
|  |         validator = NumberValidator() | ||||||
|  |         self.assertTrue(validator.isValid(123)) | ||||||
|  |         self.assertTrue(validator.isValid(-2)) | ||||||
|  |         self.assertTrue(validator.isValid(.5)) | ||||||
|  |  | ||||||
|  |     def testDoesntPassOthers(self): | ||||||
|  |         validator = NumberValidator() | ||||||
|  |         # bool is a subclass of int, so it passes this test. | ||||||
|  |         # not sure if we need to be more specific or if this is alright. | ||||||
|  |         # self.assertFalse(validator.isValid(True)) | ||||||
|  |         self.assertFalse(validator.isValid("text")) | ||||||
|  |         self.assertFalse(validator.isValid(object())) | ||||||
							
								
								
									
										17
									
								
								test/property/validators/test_or_validator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								test/property/validators/test_or_validator.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | from unittest import TestCase | ||||||
|  | from owrx.property.validators import OrValidator, IntegerValidator, StringValidator | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class OrValidatorTest(TestCase): | ||||||
|  |     def testPassesAnyValidators(self): | ||||||
|  |         validator = OrValidator(IntegerValidator(), StringValidator()) | ||||||
|  |         self.assertTrue(validator.isValid(42)) | ||||||
|  |         self.assertTrue(validator.isValid("text")) | ||||||
|  |  | ||||||
|  |     def testRejectsOtherTypes(self): | ||||||
|  |         validator = OrValidator(IntegerValidator(), StringValidator()) | ||||||
|  |         self.assertFalse(validator.isValid(.5)) | ||||||
|  |  | ||||||
|  |     def testRejectsIfNoValidator(self): | ||||||
|  |         validator = OrValidator() | ||||||
|  |         self.assertFalse(validator.isValid("any value")) | ||||||
							
								
								
									
										17
									
								
								test/property/validators/test_regex_validator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								test/property/validators/test_regex_validator.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | from unittest import TestCase | ||||||
|  | from owrx.property.validators import RegexValidator | ||||||
|  | import re | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class RegexValidatorTest(TestCase): | ||||||
|  |     def testMatchesRegex(self): | ||||||
|  |         validator = RegexValidator(re.compile("abc")) | ||||||
|  |         self.assertTrue(validator.isValid("abc")) | ||||||
|  |  | ||||||
|  |     def testDoesntMatchRegex(self): | ||||||
|  |         validator = RegexValidator(re.compile("abc")) | ||||||
|  |         self.assertFalse(validator.isValid("xyz")) | ||||||
|  |  | ||||||
|  |     def testFailsIfValueIsNoString(self): | ||||||
|  |         validator = RegexValidator(re.compile("abc")) | ||||||
|  |         self.assertFalse(validator.isValid(42)) | ||||||
							
								
								
									
										14
									
								
								test/property/validators/test_string_validator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								test/property/validators/test_string_validator.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | from unittest import TestCase | ||||||
|  | from owrx.property.validators import StringValidator | ||||||
|  |  | ||||||
|  | class StringValidatorTest(TestCase): | ||||||
|  |     def testPassesStrings(self): | ||||||
|  |         validator = StringValidator() | ||||||
|  |         self.assertTrue(validator.isValid("text")) | ||||||
|  |  | ||||||
|  |     def testDoesntPassOthers(self): | ||||||
|  |         validator = StringValidator() | ||||||
|  |         self.assertFalse(validator.isValid(123)) | ||||||
|  |         self.assertFalse(validator.isValid(-2)) | ||||||
|  |         self.assertFalse(validator.isValid(.5)) | ||||||
|  |         self.assertFalse(validator.isValid(object())) | ||||||
							
								
								
									
										20
									
								
								test/property/validators/test_validator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								test/property/validators/test_validator.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | from unittest import TestCase | ||||||
|  | from owrx.property.validators import Validator, NumberValidator, LambdaValidator, StringValidator | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ValidatorTest(TestCase): | ||||||
|  |  | ||||||
|  |     def testReturnsValidator(self): | ||||||
|  |         validator = NumberValidator() | ||||||
|  |         self.assertIs(validator, Validator.of(validator)) | ||||||
|  |  | ||||||
|  |     def testTransformsLambda(self): | ||||||
|  |         def my_callable(v): | ||||||
|  |             return True | ||||||
|  |         validator = Validator.of(my_callable) | ||||||
|  |         self.assertIsInstance(validator, LambdaValidator) | ||||||
|  |         self.assertTrue(validator.isValid("test")) | ||||||
|  |  | ||||||
|  |     def testGetsValidatorByKey(self): | ||||||
|  |         validator = Validator.of("str") | ||||||
|  |         self.assertIsInstance(validator, StringValidator) | ||||||
		Reference in New Issue
	
	Block a user
	 Jakob Ketterl
					Jakob Ketterl