diff --git a/letsencrypt/client/acme.py b/letsencrypt/client/acme.py index 52cbceb71..f49cddbb8 100644 --- a/letsencrypt/client/acme.py +++ b/letsencrypt/client/acme.py @@ -5,9 +5,9 @@ import pkg_resources import jsonschema -SCHEMATA = { - schema: json.load(open(pkg_resources.resource_filename( - __name__, "schemata/%s.json" % schema))) for schema in [ +SCHEMATA = dict([ + (schema, json.load(open(pkg_resources.resource_filename( + __name__, "schemata/%s.json" % schema)))) for schema in [ "authorization", "authorizationRequest", "certificate", @@ -18,32 +18,42 @@ SCHEMATA = { "error", "revocation", "revocationRequest", - "statusRequest" + "statusRequest", ] -} +]) -def acme_object_validate(j): - """Validate a JSON object against the ACME protocol using JSON Schema. +def acme_object_validate(json_string, schemata=None): + """Validate a JSON string against the ACME protocol using JSON Schema. + + :param json_string: Well-formed input JSON string. + :type json_string: str + + :param schemata: Mapping from type name to JSON Schema definition. + Useful for testing. + :type schemata: dict + + :returns: None if validation was successful. + :raises: jsonschema.ValidationError if validation was unsuccessful + ValueError if the object cannot even be parsed as valid JSON - Success will return None; failure to validate will raise a - jsonschema.ValidationError exception describing the reason that the - object could not be validated successfully, or a ValueError exception - if the object cannot even be parsed as valid JSON. """ - j = json.loads(j) - if not isinstance(j, dict): + schemata = SCHEMATA if schemata is None else schemata + json_object = json.loads(json_string) + if not isinstance(json_object, dict): raise jsonschema.ValidationError("this is not a dictionary object") - if "type" not in j: + if "type" not in json_object: raise jsonschema.ValidationError("missing type field") - if j["type"] not in SCHEMATA: - raise jsonschema.ValidationError("unknown type %s" % j["type"]) - jsonschema.validate(j, SCHEMATA[j["type"]]) + if json_object["type"] not in schemata: + raise jsonschema.ValidationError( + "unknown type %s" % json_object["type"]) + jsonschema.validate(json_object, schemata[json_object["type"]]) def pretty(json_string): """Return a pretty-printed version of any JSON string. Useful when printing out protocol messages for debugging purposes. + """ return json.dumps(json.loads(json_string), indent=4) diff --git a/letsencrypt/client/acme_test.py b/letsencrypt/client/acme_test.py new file mode 100644 index 000000000..e5eae6c9a --- /dev/null +++ b/letsencrypt/client/acme_test.py @@ -0,0 +1,58 @@ +"""Tests for letsencrypt.client.acme.""" +import unittest + +import jsonschema + + +class ACMEObjectValidateTest(unittest.TestCase): + """Tests for letsencrypt.client.acme.acme_object_validate.""" + + def setUp(self): + self.schemata = { + 'foo': { + 'type' : 'object', + 'properties' : { + 'price' : {'type' : 'number'}, + 'name' : {'type' : 'string'}, + }, + }, + } + + def _call(self, json_string): + from letsencrypt.client.acme import acme_object_validate + return acme_object_validate(json_string, self.schemata) + + def _test_fails(self, json_string): + self.assertRaises(jsonschema.ValidationError, self._call, json_string) + + def test_non_dictionary_fails(self): + self._test_fails('[]') + + def test_dict_without_type_fails(self): + self._test_fails('{}') + + def test_unknown_type_fails(self): + self._test_fails('{"type": "bar"}') + + def test_valid_returns_none(self): + self.assertTrue(self._call('{"type": "foo"}') is None) + + def test_invalid_fails(self): + self._test_fails('{"type": "foo", "price": "asd"}') + + +class PrettyTest(unittest.TestCase): + """Tests for letsencrypt.client.acme.pretty.""" + + def _call(self, json_string): + from letsencrypt.client.acme import pretty + return pretty(json_string) + + def test_it(self): + self.assertEqual( + self._call('{"foo": "bar", "foo2": "bar2"}'), + '{\n "foo2": "bar2", \n "foo": "bar"\n}') + + +if __name__ == '__main__': + unittest.main()