OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 Loading... | |
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() |
OLD | NEW |