Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(654)

Side by Side Diff: appengine/config_service/validation_test.py

Issue 1221643020: config services: services.cfg and validation (Closed) Base URL: git@github.com:luci/luci-py.git@master
Patch Set: Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright 2015 The Swarming Authors. All rights reserved. 2 # Copyright 2015 The Swarming Authors. All rights reserved.
3 # Use of this source code is governed by the Apache v2.0 license that can be 3 # Use of this source code is governed by the Apache v2.0 license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 import base64 6 import base64
7 import logging 7 import logging
8 8
9 from test_env import future 9 from test_env import future
10 import test_env 10 import test_env
11 test_env.setup_test_env() 11 test_env.setup_test_env()
12 12
13 from google.appengine.ext import ndb 13 from google.appengine.ext import ndb
14 14
15 from test_support import test_case 15 from test_support import test_case
16 import mock 16 import mock
17 17
18 from components import config 18 from components import config
19 from components import net 19 from components import net
20 from components.config import validation_context 20 from components.config import validation_context
21 21
22 from proto import project_config_pb2 22 from proto import project_config_pb2
23 from proto import service_config_pb2 23 from proto import service_config_pb2
24 import services
24 import storage 25 import storage
25 import validation 26 import validation
26 27
27 28
28 class ValidationTestCase(test_case.TestCase): 29 class ValidationTestCase(test_case.TestCase):
29 def test_validate_validation_cfg(self): 30 def setUp(self):
30 cfg = ''' 31 super(ValidationTestCase, self).setUp()
31 rules { 32 self.services = []
32 config_set: "projects/foo" 33 self.mock(services, 'get_services_async', lambda: future(self.services))
33 path: "bar.cfg"
34 url: "https://foo.com/validate_config"
35 }
36 rules {
37 config_set: "regex:projects\/foo"
38 path: "regex:.+"
39 url: "https://foo.com/validate_config"
40 }
41 rules {
42 config_set: "bad config set name"
43 path: "regex:))bad regex"
44 # no url
45 }
46 rules {
47 config_set: "regex:)("
48 path: "/bar.cfg"
49 url: "http://not-https.com"
50 }
51 rules {
52 config_set: "projects/foo"
53 path: "a/../b.cfg"
54 url: "https://foo.com/validate_config"
55 }
56 rules {
57 config_set: "projects/foo"
58 path: "a/./b.cfg"
59 url: "/no/hostname"
60 }
61 '''
62 result = validation.validate_config(
63 config.self_config_set(), 'validation.cfg', cfg)
64
65 self.assertEqual(
66 [m.text for m in result.messages],
67 [
68 'Rule #3: config_set: invalid config set: bad config set name',
69 ('Rule #3: path: invalid regular expression "))bad regex": '
70 'unbalanced parenthesis'),
71 'Rule #3: url: not specified',
72 ('Rule #4: config_set: invalid regular expression ")(": '
73 'unbalanced parenthesis'),
74 'Rule #4: path: must not be absolute: /bar.cfg',
75 'Rule #4: url: scheme must be "https"',
76 'Rule #5: path: must not contain ".." or "." components: a/../b.cfg',
77 'Rule #6: path: must not contain ".." or "." components: a/./b.cfg',
78 'Rule #6: url: hostname not specified',
79 'Rule #6: url: scheme must be "https"',
80 ]
81 )
82 34
83 def test_validate_project_registry(self): 35 def test_validate_project_registry(self):
84 cfg = ''' 36 cfg = '''
85 projects { 37 projects {
86 id: "a" 38 id: "a"
87 config_location { 39 config_location {
88 storage_type: GITILES 40 storage_type: GITILES
89 url: "https://a.googlesource.com/project" 41 url: "https://a.googlesource.com/project"
90 } 42 }
91 } 43 }
(...skipping 20 matching lines...) Expand all
112 self.assertEqual( 64 self.assertEqual(
113 [m.text for m in result.messages], 65 [m.text for m in result.messages],
114 [ 66 [
115 'Project b: config_location: storage_type is not set', 67 'Project b: config_location: storage_type is not set',
116 'Project a: id is not unique', 68 'Project a: id is not unique',
117 ('Project a: config_location: Invalid Gitiles repo url: ' 69 ('Project a: config_location: Invalid Gitiles repo url: '
118 'https://no-project.googlesource.com'), 70 'https://no-project.googlesource.com'),
119 'Project #4: id is not specified', 71 'Project #4: id is not specified',
120 ('Project #4: config_location: Invalid Gitiles repo url: ' 72 ('Project #4: config_location: Invalid Gitiles repo url: '
121 'https://no-project.googlesource.com/bad_plus/+'), 73 'https://no-project.googlesource.com/bad_plus/+'),
122 'Project list is not sorted by id. First offending id: a', 74 'Projects are not sorted by id. First offending id: a',
123 ] 75 ]
124 ) 76 )
125 77
78 def test_validate_services_registry(self):
79 cfg = '''
80 services {
81 id: "a"
82 }
83 services {
84 owners: "not an email"
85 config_location {
86 storage_type: GITILES
87 url: "../some"
88 }
89 metadata_url: "not an url"
90 }
91 services {
92 id: "b"
93 config_location {
94 storage_type: GITILES
95 url: "https://gitiles.host.com/project"
96 }
97 }
98 services {
99 id: "a-unsorted"
100 }
101 '''
102 result = validation.validate_config(
103 config.self_config_set(), 'services.cfg', cfg)
104
105 self.assertEqual(
106 [m.text for m in result.messages],
107 [
108 'Service #2: id is not specified',
109 ('Service #2: config_location: '
110 'storage_type must not be set if relative url is used'),
111 'Service #2: invalid email: "not an email"',
112 'Service #2: metadata_url: hostname not specified',
113 'Service #2: metadata_url: scheme must be "https"',
114 'Services are not sorted by id. First offending id: a-unsorted',
115 ]
116 )
117
118
119 def test_validate_service_dynamic_metadata_blob(self):
120 def expect_errors(blob, expected_messages):
121 ctx = config.validation.Context()
122 validation.validate_service_dynamic_metadata_blob(blob, ctx)
123 self.assertEqual(
124 [m.text for m in ctx.result().messages], expected_messages)
125
126 expect_errors([], ['Service dynamic metadata must be an object'])
127 expect_errors({}, [])
128 expect_errors({'validation': 'bad'}, ['validation: must be an object'])
129 expect_errors(
130 {
131 'validation': {
132 'patterns': 'bad',
133 }
134 },
135 [
136 'validation: url: not specified',
137 'validation: patterns must be a list',
138 ])
139 expect_errors(
140 {
141 'validation': {
142 'url': 'bad url',
143 'patterns': [
144 'bad',
145 {
146 },
147 {
148 'config_set': 'a:b',
149 'path': '/foo',
150 },
151 {
152 'config_set': 'regex:)(',
153 'path': '../b',
154 },
155 {
156 'config_set': 'projects/foo',
157 'path': 'bar.cfg',
158 },
159 ]
160 }
161 },
162 [
163 'validation: url: hostname not specified',
164 'validation: url: scheme must be "https"',
165 'validation: pattern #1: must be an object',
166 'validation: pattern #2: config_set: pattern must be a string: None',
167 'validation: pattern #2: path: pattern must be a string: None',
168 'validation: pattern #3: config_set: unknown pattern kind: a',
169 'validation: pattern #3: path: must not be absolute: /foo',
170 ('validation: pattern #4: config_set: '
171 'invalid regular expression ")(": unbalanced parenthesis'),
172 ('validation: pattern #4: path: '
173 'must not contain ".." or "." components: ../b'),
174 ]
175 )
176
126 def test_validate_schemas(self): 177 def test_validate_schemas(self):
127 cfg = ''' 178 cfg = '''
128 schemas { 179 schemas {
129 name: "services/config:foo" 180 name: "services/config:foo"
130 url: "https://foo" 181 url: "https://foo"
131 } 182 }
132 schemas { 183 schemas {
133 name: "projects:foo" 184 name: "projects:foo"
134 url: "https://foo" 185 url: "https://foo"
135 } 186 }
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
200 self.assertEqual( 251 self.assertEqual(
201 [m.text for m in result.messages], 252 [m.text for m in result.messages],
202 [ 253 [
203 'Ref #2: name is not specified', 254 'Ref #2: name is not specified',
204 'Ref #3: duplicate ref: refs/heads/master', 255 'Ref #3: duplicate ref: refs/heads/master',
205 'Ref #4: name does not start with "refs/": does_not_start_with_ref', 256 'Ref #4: name does not start with "refs/": does_not_start_with_ref',
206 'Ref #4: must not contain ".." or "." components: ../bad/path' 257 'Ref #4: must not contain ".." or "." components: ../bad/path'
207 ], 258 ],
208 ) 259 )
209 260
210 def test_endpoint_validate_async(self): 261 def test_validation_by_service_async(self):
211 cfg = '# a config' 262 cfg = '# a config'
212 cfg_b64 = base64.b64encode(cfg) 263 cfg_b64 = base64.b64encode(cfg)
213 264
214 self.mock(storage, 'get_self_config_async', mock.Mock()) 265 self.services = [
215 storage.get_self_config_async.return_value = future( 266 service_config_pb2.Service(id='a'),
216 service_config_pb2.ValidationCfg( 267 service_config_pb2.Service(id='b'),
217 rules=[ 268 service_config_pb2.Service(id='c'),
218 service_config_pb2.ValidationCfg.Rule( 269 ]
219 config_set='services/foo', 270
220 path='bar.cfg', 271 @ndb.tasklet
272 def get_metadata_async(service_id):
273 if service_id == 'a':
274 raise ndb.Return(service_config_pb2.ServiceDynamicMetadata(
275 validation=service_config_pb2.Validator(
276 patterns=[service_config_pb2.ConfigPattern(
277 config_set='services/foo',
278 path='bar.cfg',
279 )],
221 url='https://bar.verifier', 280 url='https://bar.verifier',
222 ), 281 )
223 service_config_pb2.ValidationCfg.Rule( 282 ))
224 config_set='regex:projects/[^/]+', 283 if service_id == 'b':
225 path='regex:.+.\cfg', 284 raise ndb.Return(service_config_pb2.ServiceDynamicMetadata(
285 validation=service_config_pb2.Validator(
286 patterns=[service_config_pb2.ConfigPattern(
287 config_set='regex:projects/[^/]+',
288 path='regex:.+.\cfg',
Sergiy Byelozyorov 2015/07/07 09:10:47 regex is invalid, please correct: r'regex:.+\.cfg'
289 )],
226 url='https://bar2.verifier', 290 url='https://bar2.verifier',
227 ), 291 )))
228 service_config_pb2.ValidationCfg.Rule( 292 if service_id == 'c':
229 config_set='regex:.+', 293 raise ndb.Return(service_config_pb2.ServiceDynamicMetadata(
230 path='regex:.+', 294 validation=service_config_pb2.Validator(
295 patterns=[service_config_pb2.ConfigPattern(
296 config_set='regex:.+',
297 path='regex:.+',
Sergiy Byelozyorov 2015/07/07 09:10:47 please also use raw string: r'regex:.+'
298 )],
231 url='https://ultimate.verifier', 299 url='https://ultimate.verifier',
232 ), 300 )))
233 ] 301 return None
234 )) 302 self.mock(services, 'get_metadata_async', mock.Mock())
303 services.get_metadata_async.side_effect = get_metadata_async
235 304
236 @ndb.tasklet 305 @ndb.tasklet
237 def json_request_async(url, **kwargs): 306 def json_request_async(url, **kwargs):
238 raise ndb.Return({ 307 raise ndb.Return({
239 'messages': [{ 308 'messages': [{
240 'text': 'OK from %s' % url, 309 'text': 'OK from %s' % url,
241 # default severity 310 # default severity
242 }], 311 }],
243 }) 312 })
244 313
(...skipping 120 matching lines...) Expand 10 before | Expand all | Expand 10 after
365 'config_set: projects/baz/refs/x\n' 434 'config_set: projects/baz/refs/x\n'
366 'path: qux.cfg\n' 435 'path: qux.cfg\n'
367 'response: %r' % res), 436 'response: %r' % res),
368 ), 437 ),
369 ], 438 ],
370 ) 439 )
371 440
372 441
373 if __name__ == '__main__': 442 if __name__ == '__main__':
374 test_env.main() 443 test_env.main()
OLDNEW
« appengine/config_service/validation.py ('K') | « appengine/config_service/validation.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698