Exemple #1
0
def test_interpolated_name(config, client, mongo):
    juan = make_user('juan', 'Juan')
    name = 'Computes a name based on a Cow'

    res = client.post('/v1/execution', headers={**{
        'Content-Type': 'application/json',
    }, **make_auth(juan)}, data=json.dumps({
        'process_name': 'interpol',
        'form_array': [{
            'ref': 'form',
            'data': {
                'field': 'Cow',
            },
        }],
    }))

    # request succeeded
    assert res.status_code == 201

    # execution has name
    exc = next(Execution.q().filter(status='ongoing'))

    assert exc.name == name

    # execution collection has name
    reg2 = next(mongo[config["EXECUTION_COLLECTION"]].find())

    assert reg2['id'] == exc.id
    assert reg2['name'] == name

    # history has the name
    reg = next(mongo[config["POINTER_COLLECTION"]].find())

    assert reg['execution']['name'] == name
Exemple #2
0
def test_hidden_input(client, mocker, config, mongo):
    mocker.patch('pika.adapters.blocking_connection.'
                 'BlockingChannel.basic_publish')

    juan = make_user('juan', 'Juan')

    res = client.post('/v1/execution',
                      headers={
                          **{
                              'Content-Type': 'application/json',
                          },
                          **make_auth(juan)
                      },
                      data=json.dumps({
                          'process_name':
                          'input-hidden',
                          'form_array': [{
                              'ref': 'start_form',
                              'data': {
                                  'data': 'yes',
                              },
                          }],
                      }))

    assert res.status_code == 201

    exc = Execution.get_all()[0]
    ptr = exc.proxy.pointers.get()[0]

    pika.adapters.blocking_connection.BlockingChannel.\
        basic_publish.assert_called_once()

    args = pika.adapters.blocking_connection.\
        BlockingChannel.basic_publish.call_args[1]

    json_message = {
        'command':
        'step',
        'pointer_id':
        ptr.id,
        'user_identifier':
        'juan',
        'input': [
            Form.state_json('start_form', [
                {
                    'label': 'data',
                    'type': 'text',
                    'value': 'yes',
                    'value_caption': 'yes',
                    'name': 'data',
                    'state': 'valid',
                    'hidden': True,
                },
            ])
        ],
    }

    assert json.loads(args['body']) == json_message
Exemple #3
0
def test_hidden_input(client, mocker, config, mongo):
    mocker.patch('cacahuate.tasks.handle.delay')

    juan = make_user('juan', 'Juan')

    res = client.post('/v1/execution',
                      headers={
                          **{
                              'Content-Type': 'application/json',
                          },
                          **make_auth(juan)
                      },
                      data=json.dumps({
                          'process_name':
                          'input-hidden',
                          'form_array': [{
                              'ref': 'start_form',
                              'data': {
                                  'data': 'yes',
                              },
                          }],
                      }))

    assert res.status_code == 201

    exc = next(Execution.q().filter(status='ongoing'))
    ptr = next(exc.pointers.q().filter(status='ongoing'))

    handle.delay.assert_called_once()

    args = handle.delay.call_args[0][0]

    json_message = {
        'command':
        'step',
        'pointer_id':
        ptr.id,
        'user_identifier':
        'juan',
        'input': [
            Form.state_json('start_form', [
                {
                    'label': 'data',
                    'type': 'text',
                    'value': 'yes',
                    'value_caption': 'yes',
                    'name': 'data',
                    'state': 'valid',
                    'hidden': True,
                },
            ])
        ],
    }

    assert json.loads(args) == json_message
Exemple #4
0
    def patch(self, message, channel):
        execution = Execution.get_or_exception(message['execution_id'])
        xml = Xml.load(self.config, execution.process_name, direct=True)
        mongo = self.get_mongo()
        execution_collection = mongo[self.config['EXECUTION_COLLECTION']]
        pointer_collection = mongo[self.config['POINTER_COLLECTION']]

        # set nodes with pointers as unfilled, delete pointers
        updates = {}

        for pointer in execution.proxy.pointers.q():
            updates['state.items.{node}.state'.format(
                node=pointer.node_id, )] = 'unfilled'
            pointer.delete()
            pointer_collection.update_one({
                'id': pointer.id,
            }, {
                '$set': {
                    'state': 'cancelled',
                    'finished_at': datetime.now(),
                    'patch': {
                        'comment': message['comment'],
                        'inputs': message['inputs'],
                    },
                },
            })

        execution_collection.update_one({
            'id': execution.id,
        }, {
            '$set': updates,
        })

        # retrieve updated state
        state = next(execution_collection.find({'id': execution.id}))

        state_updates = cascade_invalidate(xml, state, message['inputs'],
                                           message['comment'])

        # update state
        collection = mongo[self.config['EXECUTION_COLLECTION']]
        collection.update_one({
            'id': state['id'],
        }, {
            '$set': state_updates,
        })

        # retrieve updated state
        state = next(execution_collection.find({'id': execution.id}))

        first_invalid_node = track_next_node(xml, state, self.get_mongo(),
                                             self.config)

        # wakeup and start execution from the found invalid node
        self.wakeup_and_notify(first_invalid_node, execution, channel, state)
Exemple #5
0
    def start(self, node, input, mongo, user_identifier):
        # the first set of values
        context = compact_values(input)

        # save the data
        execution = Execution(
            process_name=self.filename,
            name=self.get_name(context),
            name_template=self.name_template(),
            description=self.get_description(context),
            description_template=self.description_template(),
            started_at=datetime.now(),
            status='ongoing',
        ).save()

        pointer = Pointer(
            node_id=node.id,
            name=node.get_name(context),
            description=node.get_description(context),
            started_at=execution.started_at,
            status='ongoing',
        ).save()
        pointer.execution.set(execution)

        # log to mongo
        collection = mongo[self.config['POINTER_COLLECTION']]
        collection.insert_one(pointer_entry(
            node,
            pointer.name,
            pointer.description,
            execution,
            pointer
        ))

        collection = mongo[self.config['EXECUTION_COLLECTION']]
        collection.insert_one(execution_entry(
            execution,
            self.get_state(),
        ))

        # trigger rabbit
        from .tasks import handle

        handle.delay(json.dumps({
            'command': 'step',
            'pointer_id': pointer.id,
            'user_identifier': user_identifier,
            'input': input,
        }))

        return execution
Exemple #6
0
def execution_add_user(id):
    ''' adds the user as a candidate for solving the given node, only if the
    node has an active pointer. '''
    # TODO possible race condition introduced here. How does this code work in
    # case the handler is moving the pointer?

    # get execution
    execution = Execution.get_or_exception(id)

    # validate the members needed
    validate_json(request.json, ['identifier', 'node_id'])

    identifier = request.json['identifier']
    node_id = request.json['node_id']

    # get actual pointer
    try:
        pointer = next(execution.proxy.pointers.q().filter(node_id=node_id))
    except StopIteration:
        raise BadRequest([{
            'detail': f'{node_id} does not have a live pointer',
            'code': 'validation.no_live_pointer',
            'where': 'request.body.node_id',
        }])

    # get user
    user = User.get_by('identifier', identifier)
    if user is None:
        raise InvalidInputError('user_id', 'request.body.identifier')

    # update user
    user.proxy.tasks.add(pointer)

    # update pointer
    collection = mongo.db[app.config['POINTER_COLLECTION']]
    db_pointer = collection.find_one({'id': pointer.id})
    user_json = user.to_json()
    notified_users = db_pointer.get('notified_users', [])

    if user_json not in notified_users:
        notified_users.append(user.to_json())

    collection.update_one(
        {'id': pointer.id},
        {'$set': {
            'notified_users': notified_users
        }},
    )

    return jsonify(user_json), 200
Exemple #7
0
    def start(self, node, input, mongo, channel, user_identifier):
        # save the data
        execution = Execution(
            process_name=self.filename,
            name=self.get_name(input),
            description=self.get_description(input),
        ).save()
        pointer = Pointer(
            node_id=node.id,
            name=node.name,
            description=node.description,
        ).save()
        pointer.proxy.execution.set(execution)

        # log to mongo
        collection = mongo[self.config['POINTER_COLLECTION']]
        collection.insert_one(node.pointer_entry(execution, pointer))

        collection = mongo[self.config['EXECUTION_COLLECTION']]
        collection.insert_one({
            '_type': 'execution',
            'id': execution.id,
            'name': execution.name,
            'description': execution.description,
            'status': 'ongoing',
            'started_at': datetime.now(),
            'finished_at': None,
            'state': self.get_state(),
            'values': {},
            'actors': {},
        })

        # trigger rabbit
        channel.basic_publish(
            exchange='',
            routing_key=self.config['RABBIT_QUEUE'],
            body=json.dumps({
                'command': 'step',
                'pointer_id': pointer.id,
                'user_identifier': user_identifier,
                'input': input,
            }),
            properties=pika.BasicProperties(
                delivery_mode=2,
            ),
        )

        return execution
Exemple #8
0
def make_pointer(
    process_name,
    node_id,
    execution_status=None,
    pointer_status=None,
):
    exc = Execution(
        process_name=process_name,
        status=execution_status or 'ongoing',
    ).save()
    ptr = Pointer(
        node_id=node_id,
        status=pointer_status or 'ongoing',
    ).save()
    ptr.execution.set(exc)

    return ptr
Exemple #9
0
def delete_process(id):
    execution = Execution.get_or_exception(id)

    channel = get_channel()
    channel.basic_publish(
        exchange='',
        routing_key=app.config['RABBIT_QUEUE'],
        body=json.dumps({
            'command': 'cancel',
            'execution_id': execution.id,
        }),
        properties=pika.BasicProperties(delivery_mode=2, ),
    )

    return jsonify({
        'data': 'accepted',
    }), 202
Exemple #10
0
    def cancel_execution(self, message):
        execution = Execution.get_or_exception(message['execution_id'])

        for pointer in execution.proxy.pointers.get():
            pointer.delete()

        collection = self.get_mongo()[
            self.config['EXECUTION_COLLECTION']
        ]

        collection.update_one({
            'id': execution.id,
        }, {
            '$set': {
                'status': 'cancelled',
                'finished_at': datetime.now()
            }
        })

        execution.delete()
Exemple #11
0
    def cancel_execution(self, message):
        execution = Execution.get_or_exception(message['execution_id'])
        if execution.status != 'ongoing':
            raise ModelNotFoundError(
                'Specified execution never existed, and never will', )

        execution.status = 'cancelled'
        execution.finished_at = datetime.now()
        execution.save()

        for pointer in execution.pointers.q().filter(status='ongoing'):
            pointer.status = 'cancelled'
            pointer.finished_at = datetime.now()
            pointer.save()

        self.execution_collection().update_one({
            'id': execution.id,
        }, {
            '$set': {
                'status': execution.status,
                'finished_at': execution.finished_at
            }
        })

        self.pointer_collection().update_many(
            {
                'execution.id': execution.id,
                'state': 'ongoing',
            }, {
                '$set': {
                    'state': 'cancelled',
                    'finished_at': execution.finished_at
                }
            })

        self.pointer_collection().update_many({
            'execution.id': execution.id,
        }, {'$set': {
            'execution': execution.to_json(),
        }})
Exemple #12
0
    def cancel_execution(self, message):
        execution = Execution.get_or_exception(message['execution_id'])
        execution.status = 'cancelled'
        execution.finished_at = datetime.now()

        for pointer in execution.proxy.pointers.get():
            pointer.delete()

        exe_collection = self.get_mongo()[self.config['EXECUTION_COLLECTION']]

        exe_collection.update_one({
            'id': execution.id,
        }, {
            '$set': {
                'status': execution.status,
                'finished_at': execution.finished_at
            }
        })

        ptr_collection = self.get_mongo()[self.config['POINTER_COLLECTION']]

        ptr_collection.update_many(
            {
                'execution.id': execution.id,
                'state': 'ongoing',
            }, {
                '$set': {
                    'state': 'cancelled',
                    'finished_at': execution.finished_at
                }
            })

        ptr_collection.update_many({
            'execution.id': execution.id,
        }, {'$set': {
            'execution': execution.to_json(),
        }})

        execution.delete()
Exemple #13
0
def test_create_pointer(config):
    handler = Handler(config)

    xml = Xml.load(config, 'simple')
    xmliter = iter(xml)

    node = Action(next(xmliter), xmliter)

    exc = Execution.validate(
        process_name='simple.2018-02-19.xml',
        name='nombre',
        name_template='nombre',
        description='description',
        description_template='description',
        started_at=make_date(2020, 8, 21, 4, 5, 6),
        status='ongoing',
    ).save()
    pointer = handler.create_pointer(node, exc)
    execution = pointer.proxy.execution.get()

    assert pointer.node_id == 'start_node'

    assert execution.process_name == 'simple.2018-02-19.xml'
    assert execution.proxy.pointers.count() == 1
Exemple #14
0
def test_call_node_render(config, mongo, mocker):
    mocker.patch('cacahuate.tasks.handle.delay')

    handler = Handler(config)
    user = make_user('juan', 'Juan')
    ptr = make_pointer('call-render.2020-04-24.xml', 'start_node')
    execution = ptr.execution.get()
    value = random_string()

    mongo[config["EXECUTION_COLLECTION"]].insert_one({
        '_type':
        'execution',
        'id':
        execution.id,
        'state':
        Xml.load(config, execution.process_name).get_state(),
    })

    # teardown of first node and wakeup of call node
    handler.step({
        'command':
        'step',
        'pointer_id':
        ptr.id,
        'user_identifier':
        user.identifier,
        'input': [
            Form.state_json('start_form', [
                {
                    'name': 'data',
                    'name': 'data',
                    'value': value,
                    'value_caption': value,
                    'state': 'valid',
                },
            ])
        ],
    })
    assert Pointer.get(ptr.id).status == 'finished'
    ptr = next(execution.pointers.q().filter(status='ongoing'))
    assert ptr.node_id == 'call'

    new_ptr = next(Pointer.q().filter(node_id='start_node', status='ongoing'))

    # aditional rabbit call for new process
    args = handle.delay.call_args_list[0][0][0]

    assert json.loads(args) == {
        'command':
        'step',
        'pointer_id':
        new_ptr.id,
        'user_identifier':
        '__system__',
        'input': [
            Form.state_json('start_form', [
                {
                    'label': 'Info',
                    'name': 'data',
                    'state': 'valid',
                    'type': 'text',
                    'value': value,
                    'value_caption': value,
                    'hidden': False,
                },
            ])
        ],
    }

    # normal rabbit call
    args = handle.delay.call_args_list[1][0][0]

    json_res = json.loads(args)
    # check execution_id exists
    assert json_res['input'][0]['inputs']['items']['execution'].pop('value')
    assert json_res['input'][0]['inputs']['items']['execution'].pop(
        'value_caption')

    assert json_res == {
        'command':
        'step',
        'pointer_id':
        ptr.id,
        'user_identifier':
        '__system__',
        'input': [
            Form.state_json('call', [
                {
                    'name': 'execution',
                    'label': 'execution',
                    'type': 'text',
                    'hidden': False,
                    'state': 'valid',
                },
            ])
        ],
    }

    # mongo log registry created for new process
    reg = next(mongo[config["POINTER_COLLECTION"]].find({
        'id': new_ptr.id,
    }))
    assert reg['node']['id'] == 'start_node'

    # mongo execution registry created for new process
    reg = next(mongo[config["EXECUTION_COLLECTION"]].find({
        'id':
        new_ptr.execution.get().id,
    }))
    assert reg['name'] == 'Simplest process ever started with: ' + value

    # teardown of the call node and end of first execution
    handler.step({
        'command': 'step',
        'pointer_id': ptr.id,
        'user_identifier': '__system__',
        'input': [],
    })

    # old execution is gone, new is here
    assert Execution.get(execution.id).status == 'finished'
    assert Pointer.get(ptr.id).status == 'finished'
    execution = next(Execution.q().filter(status='ongoing'))
    assert execution.process_name == 'simple.2018-02-19.xml'
Exemple #15
0
def make_pointer(process_name, node_id):
    exc = Execution(process_name=process_name, ).save()
    ptr = Pointer(node_id=node_id, ).save()
    ptr.proxy.execution.set(exc)

    return ptr
Exemple #16
0
def test_call_handler_delete_process(config, mongo):
    handler = Handler(config)

    pointer = make_pointer('simple.2018-02-19.xml', 'requester')
    execution = pointer.execution.get()

    body = '{"command":"cancel", "execution_id":"%s", "pointer_id":"%s"}'\
        % (execution.id, pointer.id)

    mongo[config["EXECUTION_COLLECTION"]].insert_one({
        'started_at':
        datetime(2018, 4, 1, 21, 45),
        'finished_at':
        None,
        'status':
        'ongoing',
        'id':
        execution.id
    })

    ptr_id_1 = 'RANDOMPOINTERNAME'
    ptr_id_2 = 'MORERANDOMNAME'
    ptr_id_3 = 'NOTSORANDOMNAME'

    mongo[config["POINTER_COLLECTION"]].insert_many([
        {
            'execution': {
                'id': execution.id,
            },
            'state': 'finished',
            'id': ptr_id_1,
        },
        {
            'execution': {
                'id': execution.id,
            },
            'state': 'ongoing',
            'id': ptr_id_2,
        },
        {
            'execution': {
                'id': execution.id[::-1],
            },
            'state': 'ongoing',
            'id': ptr_id_3,
        },
    ])

    handler(body)

    reg = next(mongo[config["EXECUTION_COLLECTION"]].find())

    assert reg['id'] == execution.id
    assert reg['status'] == "cancelled"
    assert_near_date(reg['finished_at'])

    assert list(Execution.q().filter(status='ongoing')) == []
    assert list(Pointer.q().filter(status='ongoing')) == []

    assert mongo[config["POINTER_COLLECTION"]].find_one({
        'id': ptr_id_1,
    })['state'] == 'finished'
    assert mongo[config["POINTER_COLLECTION"]].find_one({
        'id': ptr_id_2,
    })['state'] == 'cancelled'
    assert mongo[config["POINTER_COLLECTION"]].find_one({
        'id': ptr_id_3,
    })['state'] == 'ongoing'
Exemple #17
0
    def patch(self, message):
        execution = Execution.get_or_exception(message['execution_id'])
        if execution.status != 'ongoing':
            raise ModelNotFoundError(
                'Specified execution never existed, and never will', )

        xml = Xml.load(self.config, execution.process_name, direct=True)

        # set nodes with pointers as unfilled, delete pointers
        updates = {}

        user = User.get_by(
            'identifier',
            message.get('user_identifier'),
        )

        if user is None:
            if message.get('user_identifier') == '__system__':
                user = User(identifier='__system__', fullname='System').save()
            else:
                raise InconsistentState('sent identifier of unexisten user')

        for pointer in execution.pointers.q().filter(status='ongoing'):
            updates['state.items.{node}.state'.format(
                node=pointer.node_id, )] = 'unfilled'
            pointer.status = 'cancelled'
            pointer.finished_at = datetime.now()
            pointer.save()

            self.pointer_collection().update_one({
                'id': pointer.id,
            }, {
                '$set': {
                    'state': 'cancelled',
                    'finished_at': pointer.finished_at,
                    'patch': {
                        'comment':
                        message['comment'],
                        'inputs':
                        message['inputs'],
                        'actor':
                        user.to_json(include=[
                            '_type',
                            'fullname',
                            'identifier',
                        ]),
                    },
                },
            })

        self.execution_collection().update_one({
            'id': execution.id,
        }, {
            '$set': updates,
        })

        # retrieve updated state
        state = next(self.execution_collection().find({'id': execution.id}))

        state_updates, array_filters = cascade_invalidate(
            xml, state, message['inputs'], message['comment'])

        # update state
        self.execution_collection().update_one(
            {'id': state['id']},
            {'$set': state_updates},
            array_filters=array_filters,
        )

        # retrieve updated state
        state = next(self.execution_collection().find({'id': execution.id}))

        first_invalid_node = track_next_node(xml, state, self.get_mongo(),
                                             self.config)

        # wakeup and start execution from the found invalid node
        self.wakeup_and_notify(first_invalid_node, execution, state)
Exemple #18
0
def test_call_handler_delete_process(config, mongo):
    handler = Handler(config)
    channel = MagicMock()
    method = {'delivery_tag': True}
    properties = ""
    pointer = make_pointer('simple.2018-02-19.xml', 'requester')
    execution_id = pointer.proxy.execution.get().id
    body = '{"command":"cancel", "execution_id":"%s", "pointer_id":"%s"}'\
        % (execution_id, pointer.id)

    mongo[config["EXECUTION_COLLECTION"]].insert_one({
        'started_at': datetime(2018, 4, 1, 21, 45),
        'finished_at': None,
        'status': 'ongoing',
        'id': execution_id
    })

    ptr_id_1 = 'RANDOMPOINTERNAME'
    ptr_id_2 = 'MORERANDOMNAME'
    ptr_id_3 = 'NOTSORANDOMNAME'

    mongo[config["POINTER_COLLECTION"]].insert_many([
        {
            'execution': {
                'id': execution_id,
            },
            'state': 'finished',
            'id': ptr_id_1,
        },
        {
            'execution': {
                'id': execution_id,
            },
            'state': 'ongoing',
            'id': ptr_id_2,
        },
        {
            'execution': {
                'id': execution_id[::-1],
            },
            'state': 'ongoing',
            'id': ptr_id_3,
        },
    ])

    handler(channel, method, properties, body)

    reg = next(mongo[config["EXECUTION_COLLECTION"]].find())

    assert reg['id'] == execution_id
    assert reg['status'] == "cancelled"
    assert_near_date(reg['finished_at'])

    assert Execution.count() == 0
    assert Pointer.count() == 0

    assert mongo[config["POINTER_COLLECTION"]].find_one({
        'id': ptr_id_1,
    })['state'] == 'finished'
    assert mongo[config["POINTER_COLLECTION"]].find_one({
        'id': ptr_id_2,
    })['state'] == 'cancelled'
    assert mongo[config["POINTER_COLLECTION"]].find_one({
        'id': ptr_id_3,
    })['state'] == 'ongoing'
Exemple #19
0
def continue_process():
    validate_json(request.json, ['execution_id', 'node_id'])

    execution_id = request.json['execution_id']
    node_id = request.json['node_id']

    try:
        execution = Execution.get_or_exception(execution_id)
    except ModelNotFoundError:
        raise BadRequest([{
            'detail': 'execution_id is not valid',
            'code': 'validation.invalid',
            'where': 'request.body.execution_id',
        }])

    xml = Xml.load(app.config, execution.process_name, direct=True)
    xmliter = iter(xml)

    try:
        continue_point = make_node(
            xmliter.find(lambda e: e.getAttribute('id') == node_id), xmliter)
    except ElementNotFound:
        raise BadRequest([{
            'detail': 'node_id is not a valid node',
            'code': 'validation.invalid_node',
            'where': 'request.body.node_id',
        }])

    try:
        pointer = next(execution.proxy.pointers.q().filter(node_id=node_id))
    except StopIteration:
        raise BadRequest([{
            'detail': 'node_id does not have a live pointer',
            'code': 'validation.no_live_pointer',
            'where': 'request.body.node_id',
        }])

    # Check for authorization
    if pointer not in g.user.proxy.tasks:
        raise Forbidden([{
            'detail': 'Provided user does not have this task assigned',
            'where': 'request.authorization',
        }])

    # Validate asociated forms
    collected_input = continue_point.validate_input(request.json)

    # trigger rabbit
    channel = get_channel()
    channel.basic_publish(
        exchange='',
        routing_key=app.config['RABBIT_QUEUE'],
        body=json.dumps({
            'command': 'step',
            'pointer_id': pointer.id,
            'user_identifier': g.user.identifier,
            'input': collected_input,
        }),
        properties=pika.BasicProperties(delivery_mode=2, ),
    )

    return {
        'data': 'accepted',
    }, 202
Exemple #20
0
def test_exit_interaction(config, mongo, mocker):
    mocker.patch('cacahuate.tasks.handle.delay')

    handler = Handler(config)
    user = make_user('juan', 'Juan')
    ptr = make_pointer('exit.2018-05-03.xml', 'start_node')
    execution = ptr.execution.get()
    execution.started_at = datetime(2018, 4, 1, 21, 45)
    execution.save()

    mongo[config["EXECUTION_COLLECTION"]].insert_one({
        '_type':
        'execution',
        'id':
        execution.id,
        'state':
        Xml.load(config, execution.process_name).get_state(),
        'values': [
            {
                '_type':
                'fgroup',
                'ref':
                '_execution',
                'forms': [{
                    'ref':
                    '_execution',
                    'fields': [
                        {
                            '_type': 'field',
                            'name': 'name',
                            'value': '',
                            'value_caption': '',
                            'state': 'valid',
                            'actor': {
                                'identifier': '__system__',
                            },
                            'set_at': execution.started_at,
                        },
                        {
                            '_type': 'field',
                            'name': 'description',
                            'value': '',
                            'value_caption': '',
                            'state': 'valid',
                            'actor': {
                                'identifier': '__system__',
                            },
                            'set_at': execution.started_at,
                        },
                    ],
                }],
            },
        ],
    })

    # first node
    handler.step({
        'command': 'step',
        'pointer_id': ptr.id,
        'user_identifier': user.identifier,
        'input': [],
    })
    ptr = next(Pointer.q().filter(status='ongoing'))
    assert ptr.node_id

    args = handle.delay.call_args[0][0]

    assert json.loads(args) == {
        'command': 'step',
        'pointer_id': ptr.id,
        'user_identifier': '__system__',
        'input': [],
    }

    # exit node
    handler.step({
        'command': 'step',
        'pointer_id': ptr.id,
        'user_identifier': '__system__',
        'input': [],
    })

    assert list(Pointer.q().filter(status='ongoing')) == []
    assert list(Execution.q().filter(status='ongoing')) == []

    # state is coherent
    state = next(mongo[config["EXECUTION_COLLECTION"]].find({
        'id': execution.id,
    }))

    del state['_id']
    del state['finished_at']

    values = state.pop('values')

    assert state == {
        '_type': 'execution',
        'id': execution.id,
        'name': '',
        'description': '',
        'state': {
            '_type': ':sorted_map',
            'items': {
                'start_node': {
                    '_type': 'node',
                    'type': 'action',
                    'id': 'start_node',
                    'state': 'valid',
                    'comment': '',
                    'actors': {
                        '_type': ':map',
                        'items': {
                            'juan': {
                                '_type': 'actor',
                                'forms': [],
                                'state': 'valid',
                                'user': {
                                    '_type': 'user',
                                    'identifier': 'juan',
                                    'fullname': 'Juan',
                                    'email': None,
                                },
                            },
                        },
                    },
                    'milestone': False,
                    'name': '',
                    'description': '',
                },
                'exit': {
                    '_type': 'node',
                    'type': 'exit',
                    'id': 'exit',
                    'state': 'valid',
                    'comment': '',
                    'actors': {
                        '_type': ':map',
                        'items': {
                            '__system__': {
                                '_type': 'actor',
                                'forms': [],
                                'state': 'valid',
                                'user': {
                                    '_type': 'user',
                                    'identifier': '__system__',
                                    'fullname': 'System',
                                    'email': None,
                                },
                            },
                        },
                    },
                    'milestone': False,
                    'name': 'Exit exit',
                    'description': 'Exit exit',
                },
                'final_node': {
                    '_type': 'node',
                    'type': 'action',
                    'id': 'final_node',
                    'state': 'unfilled',
                    'comment': '',
                    'actors': {
                        '_type': ':map',
                        'items': {},
                    },
                    'milestone': False,
                    'name': '',
                    'description': '',
                },
            },
            'item_order': ['start_node', 'exit', 'final_node'],
        },
        'status': 'finished',
        'actors': {
            'exit': '__system__',
            'start_node': 'juan',
        },
    }

    eval_context = make_context({'values': values}, {})
    eval_actor_map = make_actor_map({'values': values})

    expected_context = {
        '_env': [{}],
        '_execution': [{
            'name': '',
            'get_name_display': '',
            'description': '',
            'get_description_display': '',
        }],
    }

    assert {k: list(v.all())
            for k, v in eval_context.items()} == expected_context

    expected_actor_map = {
        '_execution': [{
            'name': {
                'actor': '__system__',
            },
            'description': {
                'actor': '__system__',
            },
        }],
    }

    for frms in eval_actor_map.values():
        for frm in frms:
            for fld in frm.values():
                assert fld.pop('set_at')

    assert eval_actor_map == expected_actor_map
Exemple #21
0
def test_call_node_render(config, mongo):
    handler = Handler(config)
    user = make_user('juan', 'Juan')
    ptr = make_pointer('call-render.2020-04-24.xml', 'start_node')
    channel = MagicMock()
    execution = ptr.proxy.execution.get()
    value = random_string()

    mongo[config["EXECUTION_COLLECTION"]].insert_one({
        '_type':
        'execution',
        'id':
        execution.id,
        'state':
        Xml.load(config, execution.process_name).get_state(),
    })

    # teardown of first node and wakeup of call node
    handler.call(
        {
            'command':
            'step',
            'pointer_id':
            ptr.id,
            'user_identifier':
            user.identifier,
            'input': [
                Form.state_json('start_form', [
                    {
                        'name': 'data',
                        'name': 'data',
                        'value': value,
                        'value_caption': value,
                    },
                ])
            ],
        }, channel)
    assert Pointer.get(ptr.id) is None
    ptr = execution.proxy.pointers.get()[0]
    assert ptr.node_id == 'call'

    new_ptr = next(Pointer.q().filter(node_id='start_node'))

    # aditional rabbit call for new process
    args = channel.basic_publish.call_args_list[0][1]

    assert args['exchange'] == ''
    assert args['routing_key'] == config['RABBIT_QUEUE']
    assert json.loads(args['body']) == {
        'command':
        'step',
        'pointer_id':
        new_ptr.id,
        'user_identifier':
        '__system__',
        'input': [
            Form.state_json('start_form', [
                {
                    'label': 'Info',
                    'name': 'data',
                    'state': 'valid',
                    'type': 'text',
                    'value': value,
                    'value_caption': value,
                    'hidden': False,
                },
            ])
        ],
    }

    # normal rabbit call
    args = channel.basic_publish.call_args_list[1][1]

    assert args['exchange'] == ''
    assert args['routing_key'] == config['RABBIT_QUEUE']
    assert json.loads(args['body']) == {
        'command': 'step',
        'pointer_id': ptr.id,
        'user_identifier': '__system__',
        'input': [],
    }

    # mongo log registry created for new process
    reg = next(mongo[config["POINTER_COLLECTION"]].find({
        'id': new_ptr.id,
    }))
    assert reg['node']['id'] == 'start_node'

    # mongo execution registry created for new process
    reg = next(mongo[config["EXECUTION_COLLECTION"]].find({
        'id':
        new_ptr.proxy.execution.get().id,
    }))
    assert reg['name'] == 'Simplest process ever started with: ' + value

    # teardown of the call node and end of first execution
    handler.call(
        {
            'command': 'step',
            'pointer_id': ptr.id,
            'user_identifier': '__system__',
            'input': [],
        }, channel)

    # old execution is gone, new is here
    assert Execution.get(execution.id) is None
    assert Pointer.get(ptr.id) is None
    execution = Execution.get_all()[0]
    assert execution.process_name == 'simple.2018-02-19.xml'
Exemple #22
0
def test_ifelifelse_else(config, mongo):
    ''' else will be executed if preceding condition is false'''
    # test setup
    handler = Handler(config)
    user = make_user('juan', 'Juan')
    ptr = make_pointer('else.2018-07-10.xml', 'start_node')
    channel = MagicMock()

    mongo[config["EXECUTION_COLLECTION"]].insert_one({
        '_type':
        'execution',
        'id':
        ptr.proxy.execution.get().id,
        'state':
        Xml.load(config, 'else').get_state(),
    })

    handler.call(
        {
            'command':
            'step',
            'pointer_id':
            ptr.id,
            'user_identifier':
            user.identifier,
            'input': [
                Form.state_json('secret01', [
                    {
                        'name': 'password',
                        'type': 'text',
                        'value': 'cuca',
                        'value_caption': 'cuca',
                    },
                ])
            ],
        }, channel)

    # pointer moved
    assert Pointer.get(ptr.id) is None
    ptr = Pointer.get_all()[0]
    assert ptr.node_id == 'condition01'

    # rabbit called
    channel.basic_publish.assert_called_once()
    args = channel.basic_publish.call_args[1]
    rabbit_call = {
        'command':
        'step',
        'pointer_id':
        ptr.id,
        'input': [
            Form.state_json('condition01', [
                {
                    'name': 'condition',
                    'name': 'condition',
                    'state': 'valid',
                    'type': 'bool',
                    'value': False,
                    'value_caption': 'False',
                },
            ])
        ],
        'user_identifier':
        '__system__',
    }
    assert json.loads(args['body']) == rabbit_call

    channel = MagicMock()
    handler.call(rabbit_call, channel)

    # pointer moved
    assert Pointer.get(ptr.id) is None
    ptr = Pointer.get_all()[0]
    assert ptr.node_id == 'elif01'

    # rabbit called
    channel.basic_publish.assert_called_once()
    args = channel.basic_publish.call_args[1]
    rabbit_call = {
        'command':
        'step',
        'pointer_id':
        ptr.id,
        'input': [
            Form.state_json('elif01', [
                {
                    'name': 'condition',
                    'state': 'valid',
                    'type': 'bool',
                    'value': False,
                    'value_caption': 'False',
                },
            ])
        ],
        'user_identifier':
        '__system__',
    }
    assert json.loads(args['body']) == rabbit_call

    channel = MagicMock()
    handler.call(rabbit_call, channel)

    # pointer moved
    assert Pointer.get(ptr.id) is None
    ptr = Pointer.get_all()[0]
    assert ptr.node_id == 'else01'

    # rabbit called
    channel.basic_publish.assert_called_once()
    args = channel.basic_publish.call_args[1]
    rabbit_call = {
        'command':
        'step',
        'pointer_id':
        ptr.id,
        'input': [
            Form.state_json('else01', [
                {
                    'name': 'condition',
                    'state': 'valid',
                    'type': 'bool',
                    'value': True,
                    'value_caption': 'True',
                },
            ])
        ],
        'user_identifier':
        '__system__',
    }
    assert json.loads(args['body']) == rabbit_call

    channel = MagicMock()
    handler.call(rabbit_call, channel)

    # pointer moved
    assert Pointer.get(ptr.id) is None
    ptr = Pointer.get_all()[0]
    assert ptr.node_id == 'action03'

    # rabbit called to notify the user
    channel.basic_publish.assert_called_once()
    args = channel.basic_publish.call_args[1]
    assert args['exchange'] == 'charpe_notify'

    channel = MagicMock()
    handler.call(
        {
            'command':
            'step',
            'pointer_id':
            ptr.id,
            'user_identifier':
            user.identifier,
            'input': [
                Form.state_json('form01', [
                    {
                        'name': 'answer',
                        'value': 'answer',
                        'value_caption': 'answer',
                    },
                ])
            ],
        }, channel)

    # execution finished
    assert len(Pointer.get_all()) == 0
    assert len(Execution.get_all()) == 0
Exemple #23
0
def execution_patch(id):
    execution = Execution.get_or_exception(id)
    collection = mongo.db[app.config['EXECUTION_COLLECTION']]
    execution_state = next(collection.find({'id': id}))

    validate_json(request.json, ['comment', 'inputs'])

    xml = Xml.load(app.config, execution.process_name, direct=True)
    dom = xml.get_dom()

    if type(request.json['inputs']) != list:
        raise RequiredListError('inputs', 'request.body.inputs')

    processed_inputs = []

    for i, field in enumerate(request.json['inputs']):
        if type(field) != dict:
            raise RequiredDictError(str(i), 'request.body.inputs.{}'.format(i))

        if 'ref' not in field:
            raise RequiredInputError('id',
                                     'request.body.inputs.{}.ref'.format(i))

        if type(field['ref']) != str:
            raise RequiredStrError('ref',
                                   'request.body.inputs.{}.ref'.format(i))

        # check down the state tree for existence of the requested ref
        processed_ref = []
        pieces = field['ref'].split('.')

        try:
            node_id = pieces.pop(0)
            node_state = execution_state['state']['items'][node_id]
        except IndexError:
            raise InputError('Missing segment in ref for node_id',
                             'request.body.inputs.{}.ref'.format(i),
                             'validation.invalid')
        except KeyError:
            raise InputError('node {} not found'.format(node_id),
                             'request.body.inputs.{}.ref'.format(i),
                             'validation.invalid')

        if node_state['type'] != 'action':
            raise InputError('only action nodes may be patched',
                             'request.body.inputs.{}.ref'.format(i),
                             'validation.invalid')

        processed_ref.append(node_id)

        # node xml element
        node = get_element_by(dom, 'action', 'id', node_id)

        if len(node_state['actors']['items']) == 1:
            only_key = list(node_state['actors']['items'].keys())[0]
            actor_state = node_state['actors']['items'][only_key]
        else:
            try:
                actor_username = pieces.pop(0)
                actor_state = node_state['actors']['items'][actor_username]
            except IndexError:
                raise InputError('Missing segment in ref for actor username',
                                 'request.body.inputs.{}.ref'.format(i),
                                 'validation.invalid')
            except KeyError:
                raise InputError('actor {} not found'.format(actor_username),
                                 'request.body.inputs.{}.ref'.format(i),
                                 'validation.invalid')

        processed_ref.append(actor_state['user']['identifier'])

        try:
            form_ref = pieces.pop(0)
        except IndexError:
            raise InputError('Missing segment in ref for form ref',
                             'request.body.inputs.{}.ref'.format(i),
                             'validation.invalid')

        if re.match(r'\d+', form_ref):
            try:
                form_index = int(form_ref)
                form_state = actor_state['forms'][form_index]
            except KeyError:
                raise InputError('form index {} not found'.format(form_ref),
                                 'request.body.inputs.{}.ref'.format(i),
                                 'validation.invalid')
        else:
            matching_forms = list(
                map(lambda f: f['ref'] == form_ref, actor_state['forms']))
            form_count = len(list(filter(lambda x: x, matching_forms)))

            if form_count == 1:
                form_index = matching_forms.index(True)
                form_state = actor_state['forms'][form_index]
            elif form_count == 0:
                raise InputError(
                    'No forms with ref {} in node'.format(form_ref),
                    'request.body.inputs.{}.ref'.format(i),
                    'validation.invalid')
            else:
                raise InputError(
                    'More than one form with ref {}'.format(form_ref),
                    'request.body.inputs.{}.ref'.format(i),
                    'validation.invalid')

        processed_ref.append(str(form_index) + ':' + form_state['ref'])

        # form xml element
        form = get_element_by(node, 'form', 'id', form_state['ref'])

        try:
            input_name = pieces.pop(0)
            form_state['inputs']['items'][input_name]
        except IndexError:
            raise InputError('Missing segment in ref for input name',
                             'request.body.inputs.{}.ref'.format(i),
                             'validation.invalid')
        except KeyError:
            raise InputError('input {} not found'.format(input_name),
                             'request.body.inputs.{}.ref'.format(i),
                             'validation.invalid')

        processed_ref.append(input_name)

        processed_inputs.append({
            'ref': '.'.join(processed_ref),
        })

        # input xml element
        input_el = get_element_by(form, 'input', 'name', input_name)

        if 'value' in field:
            try:
                input_obj = make_input(input_el)
                value = input_obj.validate(field['value'], 0)
                caption = input_obj.make_caption(value)

                processed_inputs[-1]['value'] = value
                processed_inputs[-1]['value_caption'] = caption
            except InputError as e:
                raise InputError('value invalid: {}'.format(str(e)),
                                 'request.body.inputs.{}.value'.format(i),
                                 'validation.invalid')

    channel = get_channel()
    channel.basic_publish(
        exchange='',
        routing_key=app.config['RABBIT_QUEUE'],
        body=json.dumps({
            'command': 'patch',
            'execution_id': execution.id,
            'comment': request.json['comment'],
            'inputs': processed_inputs,
        }),
        properties=pika.BasicProperties(delivery_mode=2, ),
    )

    return jsonify({
        'data': 'accepted',
    }), 202