Pull JSON schemas at build/test not run time
[dcaegen2/platform.git] / mod / onboardingapi / dcae_cli / catalog / mock / tests / test_schema.py
1 # ============LICENSE_START=======================================================
2 # org.onap.dcae
3 # ================================================================================
4 # Copyright (c) 2017-2020 AT&T Intellectual Property. All rights reserved.
5 # ================================================================================
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 #      http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 # ============LICENSE_END=========================================================
18 #
19 # ECOMP is a trademark and service mark of AT&T Intellectual Property.
20
21 # -*- coding: utf-8 -*-
22 '''
23 Tests the mock catalog
24 '''
25 import pytest
26 import json, copy
27
28 from dcae_cli.catalog.mock.schema import validate_component, validate_format,apply_defaults_docker_config, apply_defaults
29 from dcae_cli.catalog.mock import schema
30 from dcae_cli.util.exc import DcaeException
31
32
33 format_test = r'''
34 {
35   "self": {
36     "name": "asimov.format.integerClassification",
37     "version": "1.0.0",
38     "description": "Represents a single classification from a machine learning model - just a test version"
39   },
40   "dataformatversion": "1.0.0",
41   "jsonschema": {
42       "$schema": "http://json-schema.org/draft-04/schema#",
43       "type": "object",
44       "properties": {
45           "classification": {
46               "type": "string"
47           }
48       },
49       "additionalProperties": false
50    }
51 }
52 '''
53
54
55 component_test = r'''
56 {
57   "self": {
58     "version": "1.0.0",
59     "name": "asimov.component.kpi_anomaly",
60     "description": "Classifies VNF KPI data as anomalous",
61     "component_type": "docker"
62   },
63   "streams": {
64     "subscribes": [
65       {
66         "format": "dcae.vnf.kpi",
67         "version": "1.0.0",
68         "route": "/data",
69         "type": "http"
70       },
71       {
72         "format":"std.format_one",
73         "version":"1.0.0",
74         "config_key":"sub2",
75         "type": "message router"
76       }
77     ],
78     "publishes": [
79       {
80         "format": "asimov.format.integerClassification",
81         "version": "1.0.0",
82         "config_key": "prediction",
83         "type": "http"
84       },
85       {
86         "format":"std.format_one",
87         "version":"1.0.0",
88         "config_key":"pub2",
89         "type": "message router"
90       }
91     ]
92   },
93   "services": {
94     "calls": [],
95     "provides": [
96       {
97         "route": "/score-vnf",
98         "request": {
99           "format": "dcae.vnf.kpi",
100           "version": "1.0.0"
101         },
102         "response": {
103           "format": "asimov.format.integerClassification",
104           "version": "1.0.0"
105         }
106       }
107     ]
108   },
109   "parameters": [
110     {
111       "name": "threshold",
112       "value": 0.75,
113       "description": "Probability threshold to exceed to be anomalous",
114       "designer_editable": false,
115       "sourced_at_deployment": false,
116       "policy_editable": false
117     }
118   ],
119   "artifacts": [
120     {
121       "uri": "somedockercontainerpath",
122       "type": "docker image"
123     }
124     ],
125   "auxilary": {
126     "healthcheck": {
127       "type": "http",
128       "endpoint": "/health"
129     }
130   }
131 }
132 '''
133
134 cdap_component_test = r'''
135 {  
136    "self":{  
137       "name":"std.cdap_comp",
138       "version":"0.0.0",
139       "description":"cdap test component",
140       "component_type":"cdap"
141    },
142    "streams":{  
143       "publishes":[  
144          {  
145             "format":"std.format_one",
146             "version":"1.0.0",
147             "config_key":"pub1",
148             "type": "http"
149          },
150          {  
151             "format":"std.format_one",
152             "version":"1.0.0",
153             "config_key":"pub2",
154             "type": "message router"
155          }
156       ],
157       "subscribes":[  
158          {  
159             "format":"std.format_two",
160             "version":"1.5.0",
161             "route":"/sub1",
162             "type": "http"
163          },
164          {  
165             "format":"std.format_one",
166             "version":"1.0.0",
167             "config_key":"sub2",
168             "type": "message router"
169          }
170       ]
171    },
172    "services":{  
173       "calls":[  
174
175       ],
176       "provides":[  
177          {  
178             "request":{  
179                "format":"std.format_one",
180                "version":"1.0.0"
181             },
182             "response":{  
183                "format":"std.format_two",
184                "version":"1.5.0"
185             },
186             "service_name":"baphomet",
187             "service_endpoint":"rises",
188             "verb":"GET"
189          }
190       ]
191    },
192    "parameters":[  
193
194    ],
195    "artifacts": [
196      {
197        "uri": "somecdapjarurl",
198        "type": "jar"
199      }
200      ],
201    "auxilary": {
202      "streamname":"who",
203      "artifact_name" : "HelloWorld",
204      "artifact_version" : "3.4.3",
205      "programs" : [
206                     {"program_type" : "flows", "program_id" : "WhoFlow"},
207                     {"program_type" : "services", "program_id" : "Greeting"}
208                   ],
209      "namespace" : "hw"
210    }
211 }
212 '''
213
214
215 def test_basic(mock_cli_config):
216     validate_component(json.loads(component_test))
217     validate_format(json.loads(format_test))
218     validate_component(json.loads(cdap_component_test))
219
220     # Test with DR publishes for cdap
221     dr_publishes = { "format":"std.format_one", "version":"1.0.0",
222             "config_key":"pub3", "type": "data router" }
223     cdap_valid = json.loads(cdap_component_test)
224     cdap_valid["streams"]["publishes"].append(dr_publishes)
225
226     # Test with DR subscribes for cdap
227     cdap_invalid = json.loads(cdap_component_test)
228     ss = cdap_invalid["streams"]["subscribes"][0]
229     ss["type"] = "data_router"
230     ss["config_key"] = "nada"
231     cdap_invalid["streams"]["subscribes"][0] = ss
232
233     with pytest.raises(DcaeException):
234         validate_component(cdap_invalid)
235
236
237
238 def test_validate_docker_config(mock_cli_config):
239
240     def compose_spec(config):
241         spec = json.loads(component_test)
242         spec["auxilary"] = config
243         return spec
244
245     good_docker_configs = [
246             {
247                 "healthcheck": {
248                     "type": "http",
249                     "endpoint": "/health",
250                     "interval": "15s",
251                     "timeout": "1s"
252                 }
253             },
254             {
255                 "healthcheck": {
256                     "type": "script",
257                     "script": "curl something"
258                     }
259             }]
260
261     for good_config in good_docker_configs:
262         spec = compose_spec(good_config)
263         assert validate_component(spec) == None
264
265     bad_docker_configs = [
266             #{},
267             {
268                 "healthcheck": {}
269             },
270             {
271                 "healthcheck": {
272                     "type": "http"
273                 }
274             },
275             {
276                 "healthcheck": {
277                     "type": "http",
278                     "script": "huh"
279                 }
280             }]
281
282     for bad_config in bad_docker_configs:
283         with pytest.raises(DcaeException):
284             spec = compose_spec(bad_config)
285             validate_component(spec)
286
287
288 def test_validate_cdap_config(mock_cli_config):
289
290     def compose_spec(config):
291         spec = json.loads(cdap_component_test)
292         spec["auxilary"] = config
293         return spec
294
295     good_cdap_configs = [
296        {
297            "streamname":"streamname",
298            "artifact_version":"6.6.6",
299            "artifact_name" : "testname",
300            "programs" : [],
301        },
302        {
303            "streamname":"streamname",
304            "artifact_version":"6.6.6",
305            "artifact_name" : "testname",
306            "programs" : [{"program_type" : "flows", "program_id" : "flow_id"}],
307            "program_preferences" : [{"program_type" : "flows", "program_id" : "flow_id", "program_pref" : {"he" : "shall rise"}}],
308            "namespace" : "this should be an optional field",
309            "app_preferences" : {"he" : "shall rise"}
310        }
311     ]
312
313     for good_config in good_cdap_configs:
314         spec = compose_spec(good_config)
315         assert validate_component(spec) == None
316
317     bad_cdap_configs = [
318             {},
319             {"YOU HAVE" : "ALWAYS FAILED ME"}
320             ]
321
322     for bad_config in bad_cdap_configs:
323         with pytest.raises(DcaeException):
324             spec = compose_spec(bad_config)
325             validate_component(bad_config)
326
327
328 def test_apply_defaults():
329     definition = { "length": { "default": 10 }, "duration": { "default": "10s" } }
330
331     # Test: Add expected properties
332     properties = {}
333     actual = apply_defaults(definition, properties)
334     assert actual == { "length": 10, "duration": "10s" }
335
336     # Test: Don't mess with existing values
337     properties = { "length": 100, "duration": "100s" }
338     actual = apply_defaults(definition, properties)
339     assert actual == properties
340
341     # Test: No defaults to apply
342     definition = { "length": {}, "duration": {} }
343     properties = { "width": 100 }
344     actual = apply_defaults(definition, properties)
345     assert actual == properties
346
347     # Test: Nested object
348     definition = { "length": { "default": 10 }, "duration": { "default": "10s" },
349             "location": { "properties": { "lat": { "default": "40" },
350                 "long": { "default": "75" }, "alt": {} } } }
351     actual = apply_defaults(definition, {})
352     assert actual == {'duration': '10s', 'length': 10,
353             'location': {'lat': '40', 'long': '75'}}
354
355
356 def test_apply_defaults_docker_config(mock_cli_config):
357     # Test: Adding of missing expected properties for http
358     dc = { "healthcheck": { "type": "http", "endpoint": "/foo" } }
359     actual = apply_defaults_docker_config(dc)
360
361     assert "interval" in actual["healthcheck"]
362     assert "timeout" in actual["healthcheck"]
363
364     # Test: Adding of missing expected properties for script
365     dc = { "healthcheck": { "type": "script", "script": "/bin/do-something" } }
366     actual = apply_defaults_docker_config(dc)
367
368     assert "interval" in actual["healthcheck"]
369     assert "timeout" in actual["healthcheck"]
370
371     # Test: Expected properties already exist
372     dc = { "healthcheck": { "type": "http", "endpoint": "/foo",
373         "interval": "10000s", "timeout": "100000s" } }
374     actual = apply_defaults_docker_config(dc)
375     assert dc == actual
376
377     # Test: Never should happen
378     dc = { "healthcheck": { "type": "bogus" } }
379     actual = apply_defaults_docker_config(dc)
380     assert dc == actual
381
382
383 def test_validate():
384     fake_schema = {
385             "$schema": "http://json-schema.org/draft-04/schema#",
386             "title": "Test schema",
387             "type": "object",
388             "properties": {
389                 "foo": { "type": "string" },
390                 "bar": { "type": "integer" }
391                 },
392             "required": ["foo", "bar"]
393             }
394
395     good_path = "/correct_path"
396
397     goodschema = schema._Schema(good_path)
398     goodschema.ret = fake_schema
399     def fetch_schema(path):
400         if path == good_path:
401             return fake_schema
402         else:
403             raise schema.FetchSchemaError("Schema not found")
404
405     # Success case
406
407     good_instance = { "foo": "hello", "bar": 1776 }
408
409     schema._validate(goodschema, good_instance)
410
411     # Error from validating
412
413     bad_instance = {}
414
415     with pytest.raises(DcaeException):
416         schema._validate(goodschema, bad_instance)
417
418     # Error from fetching
419
420     bad_path = "/wrong_path"
421
422     badschema = schema._Schema(bad_path)
423
424     with pytest.raises(DcaeException):
425         schema._validate(badschema, good_instance)