25b408005ca873f47e020f43c5479a51cfbb7f99
[multicloud/azure.git] / azure / aria / aria-extension-cloudify / src / aria / tests / modeling / test_models.py
1 # Licensed to the Apache Software Foundation (ASF) under one or more
2 # contributor license agreements.  See the NOTICE file distributed with
3 # this work for additional information regarding copyright ownership.
4 # The ASF licenses this file to You under the Apache License, Version 2.0
5 # (the "License"); you may not use this file except in compliance with
6 # the License.  You may obtain a copy of the License at
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 from datetime import datetime
17 from contextlib import contextmanager
18
19 import pytest
20
21 from aria import application_model_storage
22 from aria.storage import (
23     sql_mapi,
24 )
25 from aria.storage.exceptions import StorageError
26 from aria.modeling.exceptions import ValueFormatException
27 from aria.modeling.models import (
28     ServiceTemplate,
29     Service,
30     ServiceUpdate,
31     ServiceUpdateStep,
32     ServiceModification,
33     Execution,
34     Task,
35     Plugin,
36     Relationship,
37     NodeTemplate,
38     Node,
39     Input,
40     Output,
41     Property,
42     Attribute,
43     Configuration,
44     Argument,
45     Type
46 )
47
48 from tests import mock
49 from tests.storage import release_sqlite_storage, init_inmemory_model_storage
50
51
52 @contextmanager
53 def sql_storage(storage_func):
54     storage = None
55     try:
56         storage = storage_func()
57         yield storage
58     finally:
59         if storage:
60             release_sqlite_storage(storage)
61
62
63 def _empty_storage():
64     return application_model_storage(sql_mapi.SQLAlchemyModelAPI,
65                                      initiator=init_inmemory_model_storage)
66
67
68 def _service_template_storage():
69     storage = _empty_storage()
70     service_template = mock.models.create_service_template()
71     storage.service_template.put(service_template)
72     return storage
73
74
75 def _service_storage():
76     storage = _service_template_storage()
77     service = mock.models.create_service(
78         storage.service_template.get_by_name(mock.models.SERVICE_TEMPLATE_NAME))
79     storage.service.put(service)
80     return storage
81
82
83 def _service_update_storage():
84     storage = _service_storage()
85     service_update = ServiceUpdate(
86         service=storage.service.list()[0],
87         created_at=now,
88         service_plan={},
89     )
90     storage.service_update.put(service_update)
91     return storage
92
93
94 def _node_template_storage():
95     storage = _service_storage()
96     service_template = storage.service_template.list()[0]
97     dependency_node_template = mock.models.create_dependency_node_template(service_template)
98     mock.models.create_dependent_node_template(service_template, dependency_node_template)
99     storage.service_template.update(service_template)
100     return storage
101
102
103 def _nodes_storage():
104     storage = _node_template_storage()
105     service = storage.service.get_by_name(mock.models.SERVICE_NAME)
106     dependency_node_template = storage.node_template.get_by_name(
107         mock.models.DEPENDENCY_NODE_TEMPLATE_NAME)
108     mock.models.create_node(dependency_node_template, service,
109                             name=mock.models.DEPENDENCY_NODE_NAME)
110
111     dependent_node_template = mock.models.create_dependent_node_template(service.service_template,
112                                                                          dependency_node_template)
113
114     mock.models.create_node(dependent_node_template, service, name=mock.models.DEPENDENT_NODE_NAME)
115     storage.service.update(service)
116     return storage
117
118
119 def _execution_storage():
120     storage = _service_storage()
121     execution = mock.models.create_execution(storage.service.list()[0])
122     plugin = mock.models.create_plugin()
123     storage.execution.put(execution)
124     storage.plugin.put(plugin)
125     return storage
126
127
128 @pytest.fixture
129 def empty_storage():
130     with sql_storage(_empty_storage) as storage:
131         yield storage
132
133
134 @pytest.fixture
135 def service_template_storage():
136     with sql_storage(_service_template_storage) as storage:
137         yield storage
138
139
140 @pytest.fixture
141 def service_storage():
142     with sql_storage(_service_storage) as storage:
143         yield storage
144
145
146 @pytest.fixture
147 def service_update_storage():
148     with sql_storage(_service_update_storage) as storage:
149         yield storage
150
151
152 @pytest.fixture
153 def node_template_storage():
154     with sql_storage(_node_template_storage) as storage:
155         yield storage
156
157
158 @pytest.fixture
159 def nodes_storage():
160     with sql_storage(_nodes_storage) as storage:
161         yield storage
162
163
164 @pytest.fixture
165 def execution_storage():
166     with sql_storage(_execution_storage) as storage:
167         yield storage
168
169
170 m_cls = type('MockClass')
171 now = datetime.utcnow()
172
173
174 def _test_model(is_valid, storage, model_cls, model_kwargs):
175     if is_valid:
176         model = model_cls(**model_kwargs)
177         getattr(storage, model_cls.__modelname__).put(model)
178         return model
179     else:
180         with pytest.raises((ValueFormatException, StorageError, TypeError),):
181             getattr(storage, model_cls.__modelname__).put(model_cls(**model_kwargs))
182
183
184 class TestServiceTemplate(object):
185
186     @pytest.mark.parametrize(
187         'is_valid, description, created_at, updated_at, main_file_name',
188         [
189             (False, [], now, now, '/path'),
190             (False, 'description', 'error', now, '/path'),
191             (False, 'description', now, 'error', '/path'),
192             (False, 'description', now, now, {}),
193
194             (True, 'description', now, now, '/path'),
195         ]
196     )
197
198     def test_service_template_model_creation(self, empty_storage, is_valid, description, created_at,
199                                              updated_at, main_file_name):
200         _test_model(is_valid=is_valid,
201                     storage=empty_storage,
202                     model_cls=ServiceTemplate,
203                     model_kwargs=dict(
204                         description=description,
205                         created_at=created_at,
206                         updated_at=updated_at,
207                         main_file_name=main_file_name)
208                    )
209
210
211 class TestService(object):
212
213     @pytest.mark.parametrize(
214         'is_valid, name, created_at, description, inputs, '
215         'outputs, updated_at',
216         [
217             (False, m_cls, now, 'desc', {}, {}, now),
218             (False, 'name', m_cls, 'desc', {}, {}, now),
219             (False, 'name', now, m_cls, {}, {}, now),
220             (False, 'name', now, 'desc', m_cls, {}, now),
221             (False, 'name', now, 'desc', {}, m_cls, now),
222             (False, 'name', now, 'desc', {}, {}, m_cls),
223
224             (True, 'name', now, 'desc', {}, {}, now),
225             (True, None, now, 'desc', {}, {}, now),
226             (True, 'name', now, None, {}, {}, now),
227             (True, 'name', now, 'desc', {}, {}, None),
228             (True, 'name', now, 'desc', {}, {}, now),
229         ]
230     )
231     def test_service_model_creation(self, service_storage, is_valid, name, created_at, description,
232                                     inputs, outputs, updated_at):
233         service = _test_model(
234             is_valid=is_valid,
235             storage=service_storage,
236             model_cls=Service,
237             model_kwargs=dict(
238                 name=name,
239                 service_template=service_storage.service_template.list()[0],
240                 created_at=created_at,
241                 description=description,
242                 inputs=inputs,
243                 outputs=outputs,
244                 updated_at=updated_at
245             ))
246         if is_valid:
247             assert service.service_template == \
248                    service_storage.service_template.list()[0]
249
250
251 class TestExecution(object):
252
253     @pytest.mark.parametrize(
254         'is_valid, created_at, started_at, ended_at, error, inputs, '
255         'status, workflow_name',
256         [
257             (False, m_cls, now, now, 'error', {}, Execution.STARTED, 'wf_name'),
258             (False, now, m_cls, now, 'error', {}, Execution.STARTED, 'wf_name'),
259             (False, now, now, m_cls, 'error', {}, Execution.STARTED, 'wf_name'),
260             (False, now, now, now, m_cls, {}, Execution.STARTED, 'wf_name'),
261             (False, now, now, now, 'error', m_cls, Execution.STARTED, 'wf_name'),
262             (False, now, now, now, 'error', {}, m_cls, 'wf_name'),
263             (False, now, now, now, 'error', {}, Execution.STARTED, m_cls),
264
265             (True, now, now, now, 'error', {}, Execution.STARTED, 'wf_name'),
266             (True, now, None, now, 'error', {}, Execution.STARTED, 'wf_name'),
267             (True, now, now, None, 'error', {}, Execution.STARTED, 'wf_name'),
268             (True, now, now, now, None, {}, Execution.STARTED, 'wf_name'),
269         ]
270     )
271     def test_execution_model_creation(self, service_storage, is_valid, created_at, started_at,
272                                       ended_at, error, inputs, status, workflow_name):
273         execution = _test_model(
274             is_valid=is_valid,
275             storage=service_storage,
276             model_cls=Execution,
277             model_kwargs=dict(
278                 service=service_storage.service.list()[0],
279                 created_at=created_at,
280                 started_at=started_at,
281                 ended_at=ended_at,
282                 error=error,
283                 inputs=inputs,
284                 status=status,
285                 workflow_name=workflow_name,
286             ))
287         if is_valid:
288             assert execution.service == service_storage.service.list()[0]
289             assert execution.service_template == service_storage.service_template.list()[0]
290
291     def test_execution_status_transition(self):
292         def create_execution(status):
293             execution = Execution(
294                 id='e_id',
295                 workflow_name='w_name',
296                 status=status,
297                 inputs={},
298                 created_at=now,
299             )
300             return execution
301
302         valid_transitions = {
303             Execution.PENDING: [Execution.STARTED,
304                                 Execution.CANCELLED,
305                                 Execution.PENDING],
306             Execution.STARTED: [Execution.FAILED,
307                                 Execution.SUCCEEDED,
308                                 Execution.CANCELLED,
309                                 Execution.CANCELLING,
310                                 Execution.STARTED],
311             Execution.CANCELLING: [Execution.FAILED,
312                                    Execution.SUCCEEDED,
313                                    Execution.CANCELLED,
314                                    Execution.CANCELLING],
315             Execution.FAILED: [Execution.FAILED],
316             Execution.SUCCEEDED: [Execution.SUCCEEDED],
317             Execution.CANCELLED: [Execution.CANCELLED, Execution.PENDING]
318         }
319
320         invalid_transitions = {
321             Execution.PENDING: [Execution.FAILED,
322                                 Execution.SUCCEEDED,
323                                 Execution.CANCELLING],
324             Execution.STARTED: [Execution.PENDING],
325             Execution.CANCELLING: [Execution.PENDING,
326                                    Execution.STARTED],
327             Execution.FAILED: [Execution.STARTED,
328                                Execution.SUCCEEDED,
329                                Execution.CANCELLED,
330                                Execution.CANCELLING],
331             Execution.SUCCEEDED: [Execution.PENDING,
332                                   Execution.STARTED,
333                                   Execution.FAILED,
334                                   Execution.CANCELLED,
335                                   Execution.CANCELLING],
336             Execution.CANCELLED: [Execution.STARTED,
337                                   Execution.FAILED,
338                                   Execution.SUCCEEDED,
339                                   Execution.CANCELLING],
340         }
341
342         for current_status, valid_transitioned_statues in valid_transitions.items():
343             for transitioned_status in valid_transitioned_statues:
344                 execution = create_execution(current_status)
345                 execution.status = transitioned_status
346
347         for current_status, invalid_transitioned_statues in invalid_transitions.items():
348             for transitioned_status in invalid_transitioned_statues:
349                 execution = create_execution(current_status)
350                 with pytest.raises(ValueError):
351                     execution.status = transitioned_status
352
353
354 class TestServiceUpdate(object):
355     @pytest.mark.parametrize(
356         'is_valid, created_at, service_plan, service_update_nodes, '
357         'service_update_service, service_update_node_templates, '
358         'modified_entity_ids, state',
359         [
360             (False, m_cls, {}, {}, {}, [], {}, 'state'),
361             (False, now, m_cls, {}, {}, [], {}, 'state'),
362             (False, now, {}, m_cls, {}, [], {}, 'state'),
363             (False, now, {}, {}, m_cls, [], {}, 'state'),
364             (False, now, {}, {}, {}, m_cls, {}, 'state'),
365             (False, now, {}, {}, {}, [], m_cls, 'state'),
366             (False, now, {}, {}, {}, [], {}, m_cls),
367
368             (True, now, {}, {}, {}, [], {}, 'state'),
369             (True, now, {}, None, {}, [], {}, 'state'),
370             (True, now, {}, {}, None, [], {}, 'state'),
371             (True, now, {}, {}, {}, None, {}, 'state'),
372             (True, now, {}, {}, {}, [], None, 'state'),
373             (True, now, {}, {}, {}, [], {}, None),
374         ]
375     )
376     def test_service_update_model_creation(self, service_storage, is_valid, created_at,
377                                            service_plan, service_update_nodes,
378                                            service_update_service, service_update_node_templates,
379                                            modified_entity_ids, state):
380         service_update = _test_model(
381             is_valid=is_valid,
382             storage=service_storage,
383             model_cls=ServiceUpdate,
384             model_kwargs=dict(
385                 service=service_storage.service.list()[0],
386                 created_at=created_at,
387                 service_plan=service_plan,
388                 service_update_nodes=service_update_nodes,
389                 service_update_service=service_update_service,
390                 service_update_node_templates=service_update_node_templates,
391                 modified_entity_ids=modified_entity_ids,
392                 state=state
393             ))
394         if is_valid:
395             assert service_update.service == \
396                    service_storage.service.list()[0]
397
398
399 class TestServiceUpdateStep(object):
400
401     @pytest.mark.parametrize(
402         'is_valid, action, entity_id, entity_type',
403         [
404             (False, m_cls, 'id', ServiceUpdateStep.ENTITY_TYPES.NODE),
405             (False, ServiceUpdateStep.ACTION_TYPES.ADD, m_cls,
406              ServiceUpdateStep.ENTITY_TYPES.NODE),
407             (False, ServiceUpdateStep.ACTION_TYPES.ADD, 'id', m_cls),
408
409             (True, ServiceUpdateStep.ACTION_TYPES.ADD, 'id',
410              ServiceUpdateStep.ENTITY_TYPES.NODE)
411         ]
412     )
413     def test_service_update_step_model_creation(self, service_update_storage, is_valid, action,
414                                                 entity_id, entity_type):
415         service_update_step = _test_model(
416             is_valid=is_valid,
417             storage=service_update_storage,
418             model_cls=ServiceUpdateStep,
419             model_kwargs=dict(
420                 service_update=
421                 service_update_storage.service_update.list()[0],
422                 action=action,
423                 entity_id=entity_id,
424                 entity_type=entity_type
425             ))
426         if is_valid:
427             assert service_update_step.service_update == \
428                    service_update_storage.service_update.list()[0]
429
430     def test_service_update_step_order(self):
431         add_node = ServiceUpdateStep(
432             id='add_step',
433             action='add',
434             entity_type='node',
435             entity_id='node_id')
436
437         modify_node = ServiceUpdateStep(
438             id='modify_step',
439             action='modify',
440             entity_type='node',
441             entity_id='node_id')
442
443         remove_node = ServiceUpdateStep(
444             id='remove_step',
445             action='remove',
446             entity_type='node',
447             entity_id='node_id')
448
449         for step in (add_node, modify_node, remove_node):
450             assert hash((step.id, step.entity_id)) == hash(step)
451
452         assert remove_node < modify_node < add_node
453         assert not remove_node > modify_node > add_node
454
455         add_rel = ServiceUpdateStep(
456             id='add_step',
457             action='add',
458             entity_type='relationship',
459             entity_id='relationship_id')
460
461         remove_rel = ServiceUpdateStep(
462             id='remove_step',
463             action='remove',
464             entity_type='relationship',
465             entity_id='relationship_id')
466
467         assert remove_rel < remove_node < add_node < add_rel
468         assert not add_node < None
469
470
471 class TestServiceModification(object):
472     @pytest.mark.parametrize(
473         'is_valid, context, created_at, ended_at, modified_node_templates, nodes, status',
474         [
475             (False, m_cls, now, now, {}, {}, ServiceModification.STARTED),
476             (False, {}, m_cls, now, {}, {}, ServiceModification.STARTED),
477             (False, {}, now, m_cls, {}, {}, ServiceModification.STARTED),
478             (False, {}, now, now, m_cls, {}, ServiceModification.STARTED),
479             (False, {}, now, now, {}, m_cls, ServiceModification.STARTED),
480             (False, {}, now, now, {}, {}, m_cls),
481
482             (True, {}, now, now, {}, {}, ServiceModification.STARTED),
483             (True, {}, now, None, {}, {}, ServiceModification.STARTED),
484             (True, {}, now, now, None, {}, ServiceModification.STARTED),
485             (True, {}, now, now, {}, None, ServiceModification.STARTED),
486         ]
487     )
488     def test_service_modification_model_creation(self, service_storage, is_valid, context,
489                                                  created_at, ended_at, modified_node_templates,
490                                                  nodes, status):
491         service_modification = _test_model(
492             is_valid=is_valid,
493             storage=service_storage,
494             model_cls=ServiceModification,
495             model_kwargs=dict(
496                 service=service_storage.service.list()[0],
497                 context=context,
498                 created_at=created_at,
499                 ended_at=ended_at,
500                 modified_node_templates=modified_node_templates,
501                 nodes=nodes,
502                 status=status,
503             ))
504         if is_valid:
505             assert service_modification.service == \
506                    service_storage.service.list()[0]
507
508
509 class TestNodeTemplate(object):
510     @pytest.mark.parametrize(
511         'is_valid, name, properties',
512         [
513             (False, m_cls, {}),
514             (False, 'name', m_cls),
515
516             (True, 'name', {}),
517         ]
518     )
519     def test_node_template_model_creation(self, service_storage, is_valid, name, properties):
520         node_template = _test_model(
521             is_valid=is_valid,
522             storage=service_storage,
523             model_cls=NodeTemplate,
524             model_kwargs=dict(
525                 name=name,
526                 type=service_storage.type.list()[0],
527                 properties=properties,
528                 service_template=service_storage.service_template.list()[0]
529             ))
530         if is_valid:
531             assert node_template.service_template == \
532                    service_storage.service_template.list()[0]
533
534
535 class TestNode(object):
536     @pytest.mark.parametrize(
537         'is_valid, name, state, version',
538         [
539             (False, m_cls, 'state', 1),
540             (False, 'name', 'state', 1),
541             (False, 'name', m_cls, 1),
542             (False, m_cls, 'state', m_cls),
543
544             (True, 'name', 'initial', 1),
545             (True, None, 'initial', 1),
546             (True, 'name', 'initial', 1),
547             (True, 'name', 'initial', None),
548         ]
549     )
550     def test_node_model_creation(self, node_template_storage, is_valid, name, state, version):
551         node = _test_model(
552             is_valid=is_valid,
553             storage=node_template_storage,
554             model_cls=Node,
555             model_kwargs=dict(
556                 node_template=node_template_storage.node_template.list()[0],
557                 type=node_template_storage.type.list()[0],
558                 name=name,
559                 state=state,
560                 version=version,
561                 service=node_template_storage.service.list()[0]
562             ))
563         if is_valid:
564             assert node.node_template == node_template_storage.node_template.list()[0]
565             assert node.service == \
566                    node_template_storage.service.list()[0]
567
568
569 class TestNodeHostAddress(object):
570
571     host_address = '1.1.1.1'
572
573     def test_host_address_on_none_hosted_node(self, service_storage):
574         node_template = self._node_template(service_storage, host_address='not considered')
575         node = self._node(service_storage,
576                           node_template,
577                           is_host=False,
578                           host_address='not considered')
579         assert node.host_address is None
580
581     def test_property_host_address_on_host_node(self, service_storage):
582         node_template = self._node_template(service_storage, host_address=self.host_address)
583         node = self._node(service_storage, node_template, is_host=True, host_address=None)
584         assert node.host_address == self.host_address
585
586     def test_runtime_property_host_address_on_host_node(self, service_storage):
587         node_template = self._node_template(service_storage, host_address='not considered')
588         node = self._node(service_storage, node_template, is_host=True,
589                           host_address=self.host_address)
590         assert node.host_address == self.host_address
591
592     def test_no_host_address_configured_on_host_node(self, service_storage):
593         node_template = self._node_template(service_storage, host_address=None)
594         node = self._node(service_storage, node_template, is_host=True, host_address=None)
595         assert node.host_address is None
596
597     def test_runtime_property_on_hosted_node(self, service_storage):
598         host_node_template = self._node_template(service_storage, host_address=None)
599         host_node = self._node(service_storage,
600                                host_node_template,
601                                is_host=True,
602                                host_address=self.host_address)
603         node_template = self._node_template(service_storage, host_address=None)
604         node = self._node(service_storage,
605                           node_template,
606                           is_host=False,
607                           host_address=None,
608                           host_fk=host_node.id)
609         assert node.host_address == self.host_address
610
611     def _node_template(self, storage, host_address):
612         kwargs = dict(
613             name='node_template',
614             type=storage.type.list()[0],
615             service_template=storage.service_template.list()[0]
616         )
617         if host_address:
618             kwargs['properties'] = {'host_address': Property.wrap('host_address', host_address)}
619         node = NodeTemplate(**kwargs)
620         storage.node_template.put(node)
621         return node
622
623     def _node(self, storage, node_template, is_host, host_address, host_fk=None):
624         kwargs = dict(
625             name='node',
626             node_template=node_template,
627             type=storage.type.list()[0],
628             state='initial',
629             service=storage.service.list()[0]
630         )
631         if is_host and (host_address is None):
632             host_address = node_template.properties.get('host_address')
633             if host_address is not None:
634                 host_address = host_address.value
635         if host_address:
636             kwargs.setdefault('attributes', {})['ip'] = Attribute.wrap('ip', host_address)
637         if is_host:
638             kwargs['host_fk'] = 1
639         elif host_fk:
640             kwargs['host_fk'] = host_fk
641         node = Node(**kwargs)
642         storage.node.put(node)
643         return node
644
645
646 class TestRelationship(object):
647     @pytest.mark.parametrize(
648         'is_valid, source_position, target_position',
649         [
650             (False, m_cls, 0),
651             (False, 0, m_cls),
652
653             (True, 0, 0),
654             (True, None, 0),
655             (True, 0, None),
656         ]
657     )
658     def test_relationship_model_creation(self, nodes_storage, is_valid, source_position,
659                                          target_position):
660         nodes = nodes_storage.node
661         source_node = nodes.get_by_name(mock.models.DEPENDENT_NODE_NAME)
662         target_node = nodes.get_by_name(mock.models.DEPENDENCY_NODE_NAME)
663         _test_model(is_valid=is_valid,
664                     storage=nodes_storage,
665                     model_cls=Relationship,
666                     model_kwargs=dict(
667                         source_node=source_node,
668                         target_node=target_node,
669                         source_position=source_position,
670                         target_position=target_position
671                     ))
672
673
674 class TestPlugin(object):
675     @pytest.mark.parametrize(
676         'is_valid, archive_name, distribution, distribution_release, '
677         'distribution_version, package_name, package_source, '
678         'package_version, supported_platform, supported_py_versions, uploaded_at, wheels',
679         [
680             (False, m_cls, 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src', 'pak_ver',
681              'sup_plat', [], now, []),
682             (False, 'arc_name', m_cls, 'dis_rel', 'dis_ver', 'pak_name', 'pak_src', 'pak_ver',
683              'sup_plat', [], now, []),
684             (False, 'arc_name', 'dis_name', m_cls, 'dis_ver', 'pak_name', 'pak_src', 'pak_ver',
685              'sup_plat', [], now, []),
686             (False, 'arc_name', 'dis_name', 'dis_rel', m_cls, 'pak_name', 'pak_src', 'pak_ver',
687              'sup_plat', [], now, []),
688             (False, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', m_cls, 'pak_src', 'pak_ver',
689              'sup_plat', [], now, []),
690             (False, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', m_cls, 'pak_ver',
691              'sup_plat', [], now, []),
692             (False, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src', m_cls,
693              'sup_plat', [], now, []),
694             (False, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src',
695              'pak_ver', m_cls, [], now, []),
696             (False, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src',
697              'pak_ver', 'sup_plat', m_cls, now, []),
698             (False, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src',
699              'pak_ver', 'sup_plat', [], m_cls, []),
700             (False, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src',
701              'pak_ver', 'sup_plat', [], now, m_cls),
702
703             (True, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src', 'pak_ver',
704              'sup_plat', [], now, []),
705             (True, 'arc_name', None, 'dis_rel', 'dis_ver', 'pak_name', 'pak_src', 'pak_ver',
706              'sup_plat', [], now, []),
707             (True, 'arc_name', 'dis_name', None, 'dis_ver', 'pak_name', 'pak_src', 'pak_ver',
708              'sup_plat', [], now, []),
709             (True, 'arc_name', 'dis_name', 'dis_rel', None, 'pak_name', 'pak_src', 'pak_ver',
710              'sup_plat', [], now, []),
711             (True, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src',
712              'pak_ver', 'sup_plat', [], now, []),
713             (True, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', None, 'pak_ver',
714              'sup_plat', [], now, []),
715             (True, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src', None,
716              'sup_plat', [], now, []),
717             (True, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src',
718              'pak_ver', None, [], now, []),
719             (True, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src',
720              'pak_ver', 'sup_plat', None, now, []),
721             (True, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src',
722              'pak_ver', 'sup_plat', [], now, []),
723         ]
724     )
725     def test_plugin_model_creation(self, empty_storage, is_valid, archive_name, distribution,
726                                    distribution_release, distribution_version, package_name,
727                                    package_source, package_version, supported_platform,
728                                    supported_py_versions, uploaded_at, wheels):
729         _test_model(is_valid=is_valid,
730                     storage=empty_storage,
731                     model_cls=Plugin,
732                     model_kwargs=dict(
733                         archive_name=archive_name,
734                         distribution=distribution,
735                         distribution_release=distribution_release,
736                         distribution_version=distribution_version,
737                         package_name=package_name,
738                         package_source=package_source,
739                         package_version=package_version,
740                         supported_platform=supported_platform,
741                         supported_py_versions=supported_py_versions,
742                         uploaded_at=uploaded_at,
743                         wheels=wheels,
744                     ))
745
746
747 class TestTask(object):
748
749     @pytest.mark.parametrize(
750         'is_valid, status, due_at, started_at, ended_at, max_attempts, attempts_count, '
751         'retry_interval, ignore_failure, name, operation_mapping, arguments, plugin_id',
752         [
753             (False, m_cls, now, now, now, 1, 1, 1, True, 'name', 'map', {}, '1'),
754             (False, Task.STARTED, m_cls, now, now, 1, 1, 1, True, 'name', 'map', {}, '1'),
755             (False, Task.STARTED, now, m_cls, now, 1, 1, 1, True, 'name', 'map', {}, '1'),
756             (False, Task.STARTED, now, now, m_cls, 1, 1, 1, True, 'name', 'map', {}, '1'),
757             (False, Task.STARTED, now, now, now, m_cls, 1, 1, True, 'name', 'map', {}, '1'),
758             (False, Task.STARTED, now, now, now, 1, m_cls, 1, True, 'name', 'map', {}, '1'),
759             (False, Task.STARTED, now, now, now, 1, 1, m_cls, True, 'name', 'map', {}, '1'),
760             (False, Task.STARTED, now, now, now, 1, 1, 1, True, m_cls, 'map', {}, '1'),
761             (False, Task.STARTED, now, now, now, 1, 1, 1, True, 'name', m_cls, {}, '1'),
762             (False, Task.STARTED, now, now, now, 1, 1, 1, True, 'name', 'map', m_cls, '1'),
763             (False, Task.STARTED, now, now, now, 1, 1, 1, True, 'name', 'map', {}, m_cls),
764             (False, Task.STARTED, now, now, now, 1, 1, 1, True, 'name', 'map', None, '1'),
765
766             (True, Task.STARTED, now, now, now, 1, 1, 1, True, 'name', 'map', {}, '1'),
767             (True, Task.STARTED, None, now, now, 1, 1, 1, True, 'name', 'map', {}, '1'),
768             (True, Task.STARTED, now, None, now, 1, 1, 1, True, 'name', 'map', {}, '1'),
769             (True, Task.STARTED, now, now, None, 1, 1, 1, True, 'name', 'map', {}, '1'),
770             (True, Task.STARTED, now, now, now, 1, None, 1, True, 'name', 'map', {}, '1'),
771             (True, Task.STARTED, now, now, now, 1, 1, None, True, 'name', 'map', {}, '1'),
772             (True, Task.STARTED, now, now, now, 1, 1, 1, None, 'name', 'map', {}, '1'),
773             (True, Task.STARTED, now, now, now, 1, 1, 1, True, None, 'map', {}, '1'),
774             (True, Task.STARTED, now, now, now, 1, 1, 1, True, 'name', None, {}, '1'),
775             (True, Task.STARTED, now, now, now, 1, 1, 1, True, 'name', 'map', {}, None),
776         ]
777     )
778     def test_task_model_creation(self, execution_storage, is_valid, status, due_at, started_at,
779                                  ended_at, max_attempts, attempts_count, retry_interval,
780                                  ignore_failure, name, operation_mapping, arguments, plugin_id):
781         task = _test_model(
782             is_valid=is_valid,
783             storage=execution_storage,
784             model_cls=Task,
785             model_kwargs=dict(
786                 status=status,
787                 execution=execution_storage.execution.list()[0],
788                 due_at=due_at,
789                 started_at=started_at,
790                 ended_at=ended_at,
791                 max_attempts=max_attempts,
792                 attempts_count=attempts_count,
793                 retry_interval=retry_interval,
794                 ignore_failure=ignore_failure,
795                 name=name,
796                 function=operation_mapping,
797                 arguments=arguments,
798                 plugin_fk=plugin_id,
799             ))
800         if is_valid:
801             assert task.execution == execution_storage.execution.list()[0]
802             if task.plugin:
803                 assert task.plugin == execution_storage.plugin.list()[0]
804
805     def test_task_max_attempts_validation(self):
806         def create_task(max_attempts):
807             Task(execution_fk='eid',
808                  name='name',
809                  function='',
810                  arguments={},
811                  max_attempts=max_attempts)
812         create_task(max_attempts=1)
813         create_task(max_attempts=2)
814         create_task(max_attempts=Task.INFINITE_RETRIES)
815         with pytest.raises(ValueError):
816             create_task(max_attempts=0)
817         with pytest.raises(ValueError):
818             create_task(max_attempts=-2)
819
820
821 class TestType(object):
822     def test_type_hierarchy(self):
823         super_type = Type(variant='variant', name='super')
824         sub_type = Type(variant='variant', parent=super_type, name='sub')
825         additional_type = Type(variant='variant', name='non_related')
826
827         assert super_type.hierarchy == [super_type]
828         assert sub_type.hierarchy == [sub_type, super_type]
829         assert additional_type.hierarchy == [additional_type]
830
831         super_type.parent = additional_type
832
833         assert super_type.hierarchy == [super_type, additional_type]
834         assert sub_type.hierarchy == [sub_type, super_type, additional_type]
835
836
837 class TestParameter(object):
838
839     MODELS_DERIVED_FROM_PARAMETER = (Input, Output, Property, Attribute, Configuration, Argument)
840
841     @pytest.mark.parametrize(
842         'is_valid, name, type_name, description',
843         [
844             (False, 'name', 'int', []),
845             (False, 'name', [], 'desc'),
846             (False, [], 'type_name', 'desc'),
847             (True, 'name', 'type_name', 'desc'),
848         ]
849     )
850     def test_derived_from_parameter_model_creation(self, empty_storage, is_valid, name, type_name,
851                                                    description):
852
853         for model_cls in self.MODELS_DERIVED_FROM_PARAMETER:
854             _test_model(is_valid=is_valid,
855                         storage=empty_storage,
856                         model_cls=model_cls,
857                         model_kwargs=dict(
858                             name=name,
859                             type_name=type_name,
860                             description=description,
861                             _value={})
862                        )
863
864     def test_as_argument(self):
865
866         for model_cls in self.MODELS_DERIVED_FROM_PARAMETER:
867             model = model_cls(name='name',
868                               type_name='type_name',
869                               description='description',
870                               _value={})
871             argument = model.as_argument()
872             assert isinstance(argument, Argument)