| Index: appengine/config_service/validation_test.py
|
| diff --git a/appengine/config_service/validation_test.py b/appengine/config_service/validation_test.py
|
| index b27e2291bf7d38b1b203169af5a086fee791bed7..5cdce920c3da8ac671767d4e536a8c59adb0a342 100755
|
| --- a/appengine/config_service/validation_test.py
|
| +++ b/appengine/config_service/validation_test.py
|
| @@ -21,64 +21,16 @@ from components.config import validation_context
|
|
|
| from proto import project_config_pb2
|
| from proto import service_config_pb2
|
| +import services
|
| import storage
|
| import validation
|
|
|
|
|
| class ValidationTestCase(test_case.TestCase):
|
| - def test_validate_validation_cfg(self):
|
| - cfg = '''
|
| - rules {
|
| - config_set: "projects/foo"
|
| - path: "bar.cfg"
|
| - url: "https://foo.com/validate_config"
|
| - }
|
| - rules {
|
| - config_set: "regex:projects\/foo"
|
| - path: "regex:.+"
|
| - url: "https://foo.com/validate_config"
|
| - }
|
| - rules {
|
| - config_set: "bad config set name"
|
| - path: "regex:))bad regex"
|
| - # no url
|
| - }
|
| - rules {
|
| - config_set: "regex:)("
|
| - path: "/bar.cfg"
|
| - url: "http://not-https.com"
|
| - }
|
| - rules {
|
| - config_set: "projects/foo"
|
| - path: "a/../b.cfg"
|
| - url: "https://foo.com/validate_config"
|
| - }
|
| - rules {
|
| - config_set: "projects/foo"
|
| - path: "a/./b.cfg"
|
| - url: "/no/hostname"
|
| - }
|
| - '''
|
| - result = validation.validate_config(
|
| - config.self_config_set(), 'validation.cfg', cfg)
|
| -
|
| - self.assertEqual(
|
| - [m.text for m in result.messages],
|
| - [
|
| - 'Rule #3: config_set: invalid config set: bad config set name',
|
| - ('Rule #3: path: invalid regular expression "))bad regex": '
|
| - 'unbalanced parenthesis'),
|
| - 'Rule #3: url: not specified',
|
| - ('Rule #4: config_set: invalid regular expression ")(": '
|
| - 'unbalanced parenthesis'),
|
| - 'Rule #4: path: must not be absolute: /bar.cfg',
|
| - 'Rule #4: url: scheme must be "https"',
|
| - 'Rule #5: path: must not contain ".." or "." components: a/../b.cfg',
|
| - 'Rule #6: path: must not contain ".." or "." components: a/./b.cfg',
|
| - 'Rule #6: url: hostname not specified',
|
| - 'Rule #6: url: scheme must be "https"',
|
| - ]
|
| - )
|
| + def setUp(self):
|
| + super(ValidationTestCase, self).setUp()
|
| + self.services = []
|
| + self.mock(services, 'get_services_async', lambda: future(self.services))
|
|
|
| def test_validate_project_registry(self):
|
| cfg = '''
|
| @@ -119,10 +71,108 @@ class ValidationTestCase(test_case.TestCase):
|
| 'Project #4: id is not specified',
|
| ('Project #4: config_location: Invalid Gitiles repo url: '
|
| 'https://no-project.googlesource.com/bad_plus/+'),
|
| - 'Project list is not sorted by id. First offending id: a',
|
| + 'Projects are not sorted by id. First offending id: a',
|
| + ]
|
| + )
|
| +
|
| + def test_validate_services_registry(self):
|
| + cfg = '''
|
| + services {
|
| + id: "a"
|
| + }
|
| + services {
|
| + owners: "not an email"
|
| + config_location {
|
| + storage_type: GITILES
|
| + url: "../some"
|
| + }
|
| + metadata_url: "not an url"
|
| + }
|
| + services {
|
| + id: "b"
|
| + config_location {
|
| + storage_type: GITILES
|
| + url: "https://gitiles.host.com/project"
|
| + }
|
| + }
|
| + services {
|
| + id: "a-unsorted"
|
| + }
|
| + '''
|
| + result = validation.validate_config(
|
| + config.self_config_set(), 'services.cfg', cfg)
|
| +
|
| + self.assertEqual(
|
| + [m.text for m in result.messages],
|
| + [
|
| + 'Service #2: id is not specified',
|
| + ('Service #2: config_location: '
|
| + 'storage_type must not be set if relative url is used'),
|
| + 'Service #2: invalid email: "not an email"',
|
| + 'Service #2: metadata_url: hostname not specified',
|
| + 'Service #2: metadata_url: scheme must be "https"',
|
| + 'Services are not sorted by id. First offending id: a-unsorted',
|
| ]
|
| )
|
|
|
| +
|
| + def test_validate_service_dynamic_metadata_blob(self):
|
| + def expect_errors(blob, expected_messages):
|
| + ctx = config.validation.Context()
|
| + validation.validate_service_dynamic_metadata_blob(blob, ctx)
|
| + self.assertEqual(
|
| + [m.text for m in ctx.result().messages], expected_messages)
|
| +
|
| + expect_errors([], ['Service dynamic metadata must be an object'])
|
| + expect_errors({}, [])
|
| + expect_errors({'validation': 'bad'}, ['validation: must be an object'])
|
| + expect_errors(
|
| + {
|
| + 'validation': {
|
| + 'patterns': 'bad',
|
| + }
|
| + },
|
| + [
|
| + 'validation: url: not specified',
|
| + 'validation: patterns must be a list',
|
| + ])
|
| + expect_errors(
|
| + {
|
| + 'validation': {
|
| + 'url': 'bad url',
|
| + 'patterns': [
|
| + 'bad',
|
| + {
|
| + },
|
| + {
|
| + 'config_set': 'a:b',
|
| + 'path': '/foo',
|
| + },
|
| + {
|
| + 'config_set': 'regex:)(',
|
| + 'path': '../b',
|
| + },
|
| + {
|
| + 'config_set': 'projects/foo',
|
| + 'path': 'bar.cfg',
|
| + },
|
| + ]
|
| + }
|
| + },
|
| + [
|
| + 'validation: url: hostname not specified',
|
| + 'validation: url: scheme must be "https"',
|
| + 'validation: pattern #1: must be an object',
|
| + 'validation: pattern #2: config_set: Pattern must be a string',
|
| + 'validation: pattern #2: path: Pattern must be a string',
|
| + 'validation: pattern #3: config_set: Invalid pattern kind: a',
|
| + 'validation: pattern #3: path: must not be absolute: /foo',
|
| + 'validation: pattern #4: config_set: unbalanced parenthesis',
|
| + ('validation: pattern #4: path: '
|
| + 'must not contain ".." or "." components: ../b'),
|
| + ]
|
| + )
|
| +
|
| def test_validate_schemas(self):
|
| cfg = '''
|
| schemas {
|
| @@ -207,31 +257,49 @@ class ValidationTestCase(test_case.TestCase):
|
| ],
|
| )
|
|
|
| - def test_endpoint_validate_async(self):
|
| + def test_validation_by_service_async(self):
|
| cfg = '# a config'
|
| cfg_b64 = base64.b64encode(cfg)
|
|
|
| - self.mock(storage, 'get_self_config_async', mock.Mock())
|
| - storage.get_self_config_async.return_value = future(
|
| - service_config_pb2.ValidationCfg(
|
| - rules=[
|
| - service_config_pb2.ValidationCfg.Rule(
|
| - config_set='services/foo',
|
| - path='bar.cfg',
|
| + self.services = [
|
| + service_config_pb2.Service(id='a'),
|
| + service_config_pb2.Service(id='b'),
|
| + service_config_pb2.Service(id='c'),
|
| + ]
|
| +
|
| + @ndb.tasklet
|
| + def get_metadata_async(service_id):
|
| + if service_id == 'a':
|
| + raise ndb.Return(service_config_pb2.ServiceDynamicMetadata(
|
| + validation=service_config_pb2.Validator(
|
| + patterns=[service_config_pb2.ConfigPattern(
|
| + config_set='services/foo',
|
| + path='bar.cfg',
|
| + )],
|
| url='https://bar.verifier',
|
| - ),
|
| - service_config_pb2.ValidationCfg.Rule(
|
| - config_set='regex:projects/[^/]+',
|
| - path='regex:.+.\cfg',
|
| + )
|
| + ))
|
| + if service_id == 'b':
|
| + raise ndb.Return(service_config_pb2.ServiceDynamicMetadata(
|
| + validation=service_config_pb2.Validator(
|
| + patterns=[service_config_pb2.ConfigPattern(
|
| + config_set=r'regex:projects/[^/]+',
|
| + path=r'regex:.+\.cfg',
|
| + )],
|
| url='https://bar2.verifier',
|
| - ),
|
| - service_config_pb2.ValidationCfg.Rule(
|
| - config_set='regex:.+',
|
| - path='regex:.+',
|
| + )))
|
| + if service_id == 'c':
|
| + raise ndb.Return(service_config_pb2.ServiceDynamicMetadata(
|
| + validation=service_config_pb2.Validator(
|
| + patterns=[service_config_pb2.ConfigPattern(
|
| + config_set=r'regex:.+',
|
| + path=r'regex:.+',
|
| + )],
|
| url='https://ultimate.verifier',
|
| - ),
|
| - ]
|
| - ))
|
| + )))
|
| + return None
|
| + self.mock(services, 'get_metadata_async', mock.Mock())
|
| + services.get_metadata_async.side_effect = get_metadata_async
|
|
|
| @ndb.tasklet
|
| def json_request_async(url, **kwargs):
|
|
|