Пример #1
0
    def test_execute_send_software_error(self, mock_post, mock_get):
        at = ActionTemplate.query.get('00000000-0000-0000-000a-000000000001')
        soft = Software(name='test', version=1, filename='test.zip')
        node1 = Server('nodeA', port=5000)
        ssa1 = SoftwareServerAssociation(software=soft, server=node1, path='/')
        db.session.add_all([soft, node1, ssa1])

        mock_post.return_value = Response(msg={'error': 'message'}, code=400)
        mock_get.return_value = Response(code=400)

        ro = NativeSoftwareSendOperation(code,
                                         expected_stdout=at.expected_stdout,
                                         expected_stderr=at.expected_stderr,
                                         expected_rc=at.expected_rc)

        cp = ro._execute(
            dict(input=dict(software=str(soft.id), server=str(node1.id))),
            timeout=10,
            context=self.context)

        mock_post.assert_called_once_with(node1,
                                          'api_1_0.send',
                                          json=dict(software_id=str(soft.id),
                                                    dest_server_id=str(
                                                        node1.id),
                                                    background=False,
                                                    include_transfer_data=True,
                                                    force=True),
                                          timeout=10,
                                          identity=ROOT)
        self.assertFalse(cp.success)
        self.assertEqual(flask.json.dumps(mock_post.return_value.msg),
                         cp.stdout)
Пример #2
0
    def test_execute_send_software(self, mock_post, mock_get):
        at = ActionTemplate.query.get('00000000-0000-0000-000a-000000000001')
        soft = Software(name='test', version=1, filename='test.zip')
        node1 = Server('nodeA', port=5000)
        node2 = Server('nodeB', port=5000)
        ssa1 = SoftwareServerAssociation(software=soft, server=node1, path='/')
        ssa2 = SoftwareServerAssociation(software=soft, server=node2, path='/')
        ssa3 = SoftwareServerAssociation(software=soft,
                                         server=self.s1,
                                         path='/')
        db.session.add_all([soft, node1, node2, ssa1, ssa2, ssa3])

        mock_post.return_value = Response(msg={'transfer_id': 1},
                                          code=at.expected_rc)
        mock_get.return_value = Response(msg={
            "route_list": [{
                "cost": 0,
                "destination_id": f"{node1.id}",
            }, {
                "cost": 1,
                "destination_id": f"{self.s1.id}",
            }],
        },
                                         code=200,
                                         server=node2)

        ro = NativeSoftwareSendOperation(code,
                                         expected_stdout=at.expected_stdout,
                                         expected_stderr=at.expected_stderr,
                                         expected_rc=at.expected_rc)

        cp = ro._execute(dict(input=dict(software=soft.id,
                                         server=node2.id,
                                         dest_path='dest',
                                         chunk_size=20,
                                         max_senders=2)),
                         timeout=None,
                         context=self.context)

        mock_post.assert_called_once_with(node1,
                                          'api_1_0.send',
                                          json=dict(software_id=str(soft.id),
                                                    dest_server_id=str(
                                                        node2.id),
                                                    background=False,
                                                    include_transfer_data=True,
                                                    force=True,
                                                    dest_path='dest',
                                                    chunk_size=20,
                                                    max_senders=2),
                                          timeout=None,
                                          identity=ROOT)
        self.assertTrue(cp.success)
        self.assertEqual(flask.json.dumps(mock_post.return_value.msg),
                         cp.stdout)
Пример #3
0
    def test_get_headers_url_response(self, m):
        msg = {'data': 'content'}
        status = 200

        def callback(request, **kwargs):
            if isinstance(request, PreparedRequest):
                self.assertEqual(str(self.server.id),
                                 request.headers.get('D-Destination'))
                self.assertEqual(str(self.server.id),
                                 request.headers.get('D-Source'))
                self.assertEqual("True", request.headers.get('D-Securizer'))
                self.assertEqual(f"Bearer {self.token}",
                                 request.headers.get('Authorization'))

                return status, {}, json.dumps(msg)
            else:
                self.assertEqual(str(self.server.id),
                                 kwargs['headers'].get('D-Destination'))
                self.assertEqual(str(self.server.id),
                                 kwargs['headers'].get('D-Source'))
                self.assertEqual("True", kwargs['headers'].get('D-Securizer'))
                self.assertEqual(f"Bearer {self.token}",
                                 kwargs['headers'].get('Authorization'))

                return CallbackResult(status=status, payload=msg)

        responses.add_callback(responses.GET, self.url, callback=callback)
        m.get(self.url, callback=callback)

        resp = get(self.server,
                   'home',
                   auth=self.auth,
                   headers={'D-Securizer': "True"})

        self.assertEqual(
            Response(msg=msg,
                     code=status,
                     exception=None,
                     server=self.server,
                     url=self.url), resp)

        resp = run(
            async_get(self.server,
                      'home',
                      auth=self.auth,
                      headers={'D-Securizer': "True"}))

        self.assertEqual(
            Response(msg=msg,
                     code=status,
                     exception=None,
                     server=self.server,
                     url=self.url), resp)
Пример #4
0
    def test_lock_unlock_lock_error_on_lock(self, m):
        m.post(self.n1.url('api_1_0.locker_prevent'),
               status=200,
               payload={'message': 'Preventing lock acquired'})
        m.post(self.n2.url('api_1_0.locker_prevent'),
               status=200,
               payload={'message': 'Preventing lock acquired'})
        m.post(self.n1.url('api_1_0.locker_lock'),
               status=200,
               payload={'message': 'Locked'})
        m.post(self.n2.url('api_1_0.locker_lock'),
               status=409,
               payload={'error': 'Unable to lock'})

        with self.assertRaises(errors.LockError) as e:
            ret = lock_unlock('L',
                              Scope.ORCHESTRATION, [self.n1, self.n2],
                              identity=ROOT)

        self.assertEqual(Scope.ORCHESTRATION, e.exception.scope)
        self.assertListEqual([
            Response(msg={'error': 'Unable to lock'},
                     code=409,
                     server=self.n2,
                     url=self.n2.url('api_1_0.locker_lock'))
        ], e.exception.responses)
Пример #5
0
    def test_send_file_background(self, mock_post, mock_executor_submit,
                                  mock_exists, mock_getsize, mock_md5):
        mock_post.return_value = Response(msg={'id': '1'}, code=200)
        mock_exists.return_value = True
        mock_getsize.return_value = self.size
        mock_md5.return_value = self.checksum

        resp = self.client.post(url_for('api_1_0.send'),
                                json=dict(file=os.path.join(
                                    self.source_path, self.filename),
                                          dest_server_id=str(self.node2.id),
                                          dest_path=self.dest_path),
                                headers=self.auth.header)

        mock_post.assert_called_once()
        server, view = mock_post.call_args[0]
        kwargs = mock_post.call_args[1]
        mock_executor_submit.assert_called_once()
        self.assertEqual(self.node2, db.session.merge(server))
        self.assertDictEqual(
            {
                'filename': self.filename,
                'num_chunks': 1,
                'dest_path': self.dest_path,
                'checksum': self.checksum,
                'size': self.size
            }, kwargs['json'])
        self.assertEqual(202, resp.status_code)
        self.assertDictEqual({'transfer_id': '1'}, resp.get_json())
Пример #6
0
    def test_lock_unreachable_network(self, m):
        def callback_prevent(url, **kwargs):
            return CallbackResult("{'message': 'Preventing lock acquired'}",
                                  status=200)

        def callback_unlock(url, **kwargs):
            return CallbackResult("{'message': 'UnLocked'}", status=200)

        m.post(self.n1.url('api_1_0.locker_prevent'),
               callback=callback_prevent)
        m.post(self.n2.url('api_1_0.locker_prevent'),
               callback=callback_prevent)
        m.post(self.n2.url('api_1_0.locker_unlock'), callback=callback_unlock)

        self.n1.route.cost = None
        self.n1.route.gate = None
        self.n1.route.proxy_server = None

        with self.assertRaises(errors.LockError) as e:
            lock(Scope.CATALOG, servers=[self.n1, self.n2], identity=ROOT)

        self.assertEqual(Scope.CATALOG, e.exception.scope)
        self.assertEqual(errors.LockError.action_map['P'], e.exception.action)
        self.assertDictEqual(
            errors.format_error_content(
                errors.LockError(
                    Scope.CATALOG,
                    action='P',
                    responses=[
                        Response(exception=errors.UnreachableDestination(
                            self.n1),
                                 server=self.n1)
                    ])), errors.format_error_content(e.exception))
        self.assertEqual(2, len(m.requests))
Пример #7
0
    def test_send_software_get_transfer_data(self, mock_post, mock_send_file,
                                             mock_exists, mock_get):
        mock_post.return_value = Response(msg={'id': '1'}, code=200)
        mock_exists.return_value = True
        mock_get.return_value = Response(msg={
            'transfer_id': '1',
            'status': 'COMPLETED'
        },
                                         code=200)

        resp = self.client.post(url_for('api_1_0.send'),
                                json=dict(software_id=str(self.soft.id),
                                          dest_server_id=str(self.node2.id),
                                          dest_path=self.dest_path,
                                          include_transfer_data=True),
                                headers=self.auth.header)

        mock_get.assert_called_once()
        server, view = mock_get.call_args[0]
        kwargs = mock_get.call_args[1]
        self.assertEqual(self.node2, db.session.merge(server))
        self.assertEqual('api_1_0.transferresource', view)
        self.assertDictEqual({'transfer_id': '1'}, kwargs['view_data'])

        self.assertEqual(202, resp.status_code)
        self.assertDictEqual({
            'transfer_id': '1',
            'status': 'COMPLETED'
        }, resp.get_json())

        mock_get.return_value = Response(
            msg={'error': {
                'message': 'some error content'
            }}, code=404)

        resp = self.client.post(url_for('api_1_0.send'),
                                json=dict(software_id=str(self.soft.id),
                                          dest_server_id=str(self.node2.id),
                                          dest_path=self.dest_path,
                                          include_transfer_data=True),
                                headers=self.auth.header)

        self.validate_error_response(resp,
                                     errors.HTTPError(mock_get.return_value))
Пример #8
0
class TestResourceCompleter(TestCase):
    default_response = Response(msg=[{
        "last_modified_at": "20200616.113822.505932+0200",
        "id": "00000000-1111-0000-0000-000000000000",
        "name": "dev1",
        "granules": []
    }, {
        "last_modified_at": "20200616.113955.580271+0200",
        "id": "00000000-2222-0000-0000-000000000000",
        "name": "dev2",
        "granules": []
    }, {
        "last_modified_at": "20200616.113955.580271+0200",
        "id": "00000000-3333-0000-0000-000000000000",
        "name": "node3",
        "granules": []
    }],
                                code=200)

    @mock.patch('dimensigon.dshell.completer.ntwrk.request')
    @mock.patch('dimensigon.dshell.completer.ntwrk.generate_url')
    def test_get_completions(self, mock_generate_url, mock_request):
        mock_request.return_value = self.default_response

        completer = ResourceCompleter('api_1_0.serverlist',
                                      'name',
                                      match_middle=False)
        # Static list on empty input.
        completions = completer.get_completions(Document(""), CompleteEvent())
        self.assertListEqual(["dev1", "dev2", "node3"],
                             [c.text for c in completions])

        completions = completer.get_completions(Document("d"), CompleteEvent())
        self.assertListEqual(["dev1", "dev2"], [c.text for c in completions])

    @mock.patch('dimensigon.dshell.completer.ntwrk.request')
    @mock.patch('dimensigon.dshell.completer.ntwrk.generate_url')
    def test_filter(self, mock_generate_url, mock_request):
        mock_request.return_value = self.default_response

        completer = ResourceCompleter('servers', 'name', match_middle=True)

        completions = completer.get_completions(Document("3"), CompleteEvent())
        self.assertListEqual(["node3"], [c.text for c in completions])

        completer = ResourceCompleter('servers',
                                      'name',
                                      ignore_case=True,
                                      match_middle=False)

        completions = completer.get_completions(Document("D"), CompleteEvent())
        self.assertListEqual(["dev1", "dev2"], [c.text for c in completions])
Пример #9
0
    def test_send_software_FileNotFound(self, mock_post, mock_send_file,
                                        mock_exists):
        mock_post.return_value = Response(msg={'transfer_id': '1'}, code=200)
        mock_exists.return_value = False

        resp = self.client.post(url_for('api_1_0.send'),
                                json=dict(software_id=str(self.soft.id),
                                          dest_server_id=str(self.node2.id),
                                          dest_path=self.dest_path),
                                headers=self.auth.header)

        self.validate_error_response(
            resp,
            errors.FileNotFound(os.path.join(self.source_path, self.filename)))
Пример #10
0
    def test_send_NoSoftwareServer(self, mock_post, mock_send_file,
                                   mock_exists):
        mock_post.return_value = Response(msg={'id': '1'}, code=200)
        mock_exists.return_value = True
        db.session.delete(self.ssa)

        resp = self.client.post(url_for('api_1_0.send'),
                                json=dict(software_id=str(self.soft.id),
                                          dest_server_id=str(self.node2.id),
                                          dest_path=self.dest_path),
                                headers=self.auth.header)

        self.validate_error_response(
            resp, errors.NoSoftwareServer(str(self.soft.id)))
Пример #11
0
    def test_send_file_FileNotFound(self, mock_post, mock_send_file,
                                    mock_exists, mock_getsize, mock_md5):
        mock_post.return_value = Response(msg={'id': '1'}, code=200)
        mock_exists.return_value = False
        mock_getsize.return_value = self.size
        mock_md5.return_value = self.checksum

        resp = self.client.post(url_for('api_1_0.send'),
                                json=dict(file=os.path.join(
                                    self.source_path, self.filename),
                                          dest_server_id=str(self.node2.id),
                                          dest_path=self.dest_path),
                                headers=self.auth.header)

        self.validate_error_response(
            resp,
            errors.FileNotFound(os.path.join(self.source_path, self.filename)))
Пример #12
0
    def test_execute_send_software_no_ssa(self, mock_get):
        at = ActionTemplate.query.get('00000000-0000-0000-000a-000000000001')
        soft = Software(name='test', version=1, filename='test.zip')
        node1 = Server('nodeA', port=5000)
        db.session.add_all([soft, node1])

        mock_get.return_value = Response(code=400)

        ro = NativeSoftwareSendOperation(code,
                                         expected_stdout=at.expected_stdout,
                                         expected_stderr=at.expected_stderr,
                                         expected_rc=at.expected_rc)

        cp = ro._execute(dict(input=dict(software=soft.id, server=self.s1.id)),
                         context=self.context)

        self.assertFalse(cp.success)
        self.assertEqual(f'{soft.id} has no server association', cp.stderr)
Пример #13
0
    def test_lock_unlock_lock_with_server_error_on_preventing(self, m):
        m.post(self.n1.url('api_1_0.locker_prevent'),
               status=200,
               payload={'message': 'Preventing lock acquired'})
        m.post(self.n2.url('api_1_0.locker_prevent'),
               status=500,
               body="Error message")

        with self.assertRaises(errors.LockError) as e:
            ret = lock_unlock('L',
                              Scope.ORCHESTRATION, [self.n1, self.n2],
                              identity=ROOT)

        self.assertEqual(Scope.ORCHESTRATION, e.exception.scope)
        self.assertListEqual([
            Response("Error message",
                     code=500,
                     server=self.n2,
                     url=self.n2.url('api_1_0.locker_prevent'))
        ], e.exception.responses)
Пример #14
0
    def test_lock_unlock_unlock_with_error(self, m):
        m.post(self.n2.url('api_1_0.locker_unlock'),
               status=200,
               payload={'message': 'UnLocked'})
        m.post(self.n1.url('api_1_0.locker_unlock'),
               status=409,
               payload={'error': 'Unable to unlock.'})

        with self.assertRaises(errors.LockError) as e:
            lock_unlock('U',
                        Scope.ORCHESTRATION, [self.n1, self.n2],
                        [str(self.n1.id), str(self.n2.id)],
                        identity=ROOT)

        self.assertEqual(Scope.ORCHESTRATION, e.exception.scope)
        self.assertListEqual([
            Response(msg={'error': 'Unable to unlock.'},
                     code=409,
                     server=self.n1,
                     url=self.n1.url('api_1_0.locker_unlock'))
        ], e.exception.responses)
Пример #15
0
    def test_send_software(self, mock_post, mock_send_file, mock_exists):
        mock_post.return_value = Response(msg={'id': '1'}, code=200)
        mock_exists.return_value = True

        resp = self.client.post(url_for('api_1_0.send'),
                                json=dict(software_id=str(self.soft.id),
                                          dest_server_id=str(self.node2.id),
                                          dest_path=self.dest_path),
                                headers=self.auth.header)

        mock_post.assert_called_once()
        server, view = mock_post.call_args[0]
        kwargs = mock_post.call_args[1]
        self.assertEqual(self.node2, db.session.merge(server))
        self.assertDictEqual(
            {
                'software_id': str(self.soft.id),
                'num_chunks': 1,
                'dest_path': self.dest_path
            }, kwargs['json'])
        self.assertEqual(202, resp.status_code)
        self.assertDictEqual({'transfer_id': '1'}, resp.get_json())
Пример #16
0
def request(method,
            url,
            session=None,
            token_refreshed=False,
            login=True,
            **kwargs) -> Response:
    exception = None
    content = None
    status = None
    json_data = None
    headers = {}

    if not session:
        _session = requests.session()
    else:
        _session = session

    raise_on_error = kwargs.pop('raise_on_error', False)

    func = getattr(_session, method.lower())

    if 'auth' not in kwargs:
        if env._access_token is None:
            try:
                refresh_access_token(login_=login)
            except requests.exceptions.ConnectionError as e:
                return Response(exception=ConnectionError(
                    f"Unable to contact with {env.get('SCHEME')}://"
                    f"{env.get('SERVER')}:{env.get('PORT')}/refresh"),
                                url=url)
            except Exception as e:
                return Response(exception=e, url=url)
            else:
                if env._access_token is None:
                    return Response(
                        exception=ValueError("No authentication set"), url=url)
        kwargs['auth'] = HTTPBearerAuth(env._access_token)

    if 'headers' not in kwargs:
        kwargs['headers'] = {}
    kwargs['headers'].update({'D-Securizer': 'plain'})

    kwargs['verify'] = env.get('SSL_VERIFY')

    logger.debug(f"{method.upper()} {url}\n{kwargs}")

    resp = None
    try:
        resp: requests.Response = func(url, **kwargs)
    except (requests.Timeout, ) as e:
        timeout = kwargs.get('timeout', None)
        if isinstance(timeout, tuple):
            timeout = timeout[0] + timeout[1]
        exception = TimeoutError(
            f"Socket timeout reached while trying to connect to {url} "
            f"for {timeout} seconds")
    except requests.ConnectionError as e:
        exception = ConnectionError(f"Unable to contact to {url}")
    except Exception as e:
        exception = e
    finally:
        if session is None and not (getattr(resp, 'status_code', None) == 401
                                    and resp.json().get('msg')
                                    == 'Token has expired'):
            _session.close()
    if exception is None:
        status = resp.status_code
        headers = resp.headers
        if status == 401 and not token_refreshed:
            json_data = resp.json()
            if json_data.get('msg') == 'Token has expired':
                try:
                    refresh_access_token()
                except requests.exceptions.ConnectionError as e:
                    return Response(exception=ConnectionError(
                        f"Unable to contact with {env.get('SCHEME')}://"
                        f"{env.get('SERVER')}:{env.get('PORT')}/refresh"),
                                    url=url)
                kwargs['auth'] = HTTPBearerAuth(env._access_token)
                resp = request(method,
                               url,
                               session=_session,
                               token_refreshed=True,
                               **kwargs)
                if not session:
                    _session.close()
                return resp
        try:
            json_data = resp.json()
        except (ValueError, ):
            content = resp.text

        if json_data is not None:
            # try:
            #     content = unpack_msg(json_data)
            # except NotValidMessage:
            #     content = json_data
            content = json_data
    else:
        if raise_on_error:
            raise exception

    return Response(msg=content,
                    code=status,
                    exception=exception,
                    url=url,
                    headers=headers)
Пример #17
0
    def to_json(self, add_step_exec=False, human=False, split_lines=False):
        data = {}
        if self.id:
            data.update(id=str(self.id))
        if self.start_time:
            data.update(
                start_time=self.start_time.strftime(defaults.DATETIME_FORMAT))
        if self.end_time:
            data.update(
                end_time=self.end_time.strftime(defaults.DATETIME_FORMAT))
        if human:
            # convert target ids to server names
            d = {}
            if isinstance(self.target, dict):
                for k, v in self.target.items():
                    if is_iterable_not_string(v):
                        d[k] = [str(Server.query.get(s) or s) for s in v]
                    else:
                        d[k] = str(Server.query.get(v) or v)
            elif isinstance(self.target, list):
                d = [str(Server.query.get(s) or s) for s in self.target]
            else:
                d = str(Server.query.get(self.target) or self.target)
            data.update(target=d)
            if self.executor:
                data.update(executor=str(self.executor))
            if self.service:
                data.update(service=str(self.service))
            if self.orchestration:
                data.update(
                    orchestration=dict(id=str(self.orchestration.id),
                                       name=self.orchestration.name,
                                       version=self.orchestration.version))
            else:
                data.update(orchestration=None)
            if self.server:
                data.update(
                    server=dict(id=str(self.server.id), name=self.server.name))
        else:
            data.update(target=self.target)
            if self.orchestration_id or getattr(self.orchestration, 'id',
                                                None):
                data.update(
                    orchestration_id=str(self.orchestration_id or getattr(
                        self.orchestration, 'id', None)))
            if self.executor_id or getattr(self.executor, 'id', None):
                data.update(executor_id=str(
                    self.executor_id or getattr(self.executor, 'id', None)))
            if self.service_id or getattr(self.service, 'id', None):
                data.update(service_id=str(
                    self.server_id or getattr(self.service, 'id', None)))
            if self.server_id or getattr(self.server, 'id', None):
                data.update(server_id=str(self.server_id
                                          or getattr(self.server, 'id', None)))
        data.update(params=self.params)
        data.update(success=self.success)
        data.update(undo_success=self.undo_success)
        data.update(message=self.message)

        if self.parent_step_execution_id and not add_step_exec:
            data.update(
                parent_step_execution_id=str(self.parent_step_execution_id))
        if add_step_exec:
            steps = []
            for se in self.step_executions:
                se: StepExecution

                se_json = se.to_json(human, split_lines=split_lines)
                if se.child_orch_execution:
                    se_json[
                        'orch_execution'] = se.child_orch_execution.to_json(
                            add_step_exec=add_step_exec,
                            split_lines=split_lines,
                            human=human)
                elif se.child_orch_execution_id:
                    from dimensigon.web.network import get, Response
                    from dimensigon.network.auth import HTTPBearerAuth
                    from flask_jwt_extended import create_access_token
                    params = ['steps']
                    if human:
                        params.append('human')

                    try:
                        resp = get(se.server,
                                   'api_1_0.orchexecutionresource',
                                   view_data=dict(
                                       execution_id=se.child_orch_execution_id,
                                       params=params))
                    except Exception as e:
                        current_app.logger.exception(
                            f"Exception while trying to acquire orch execution "
                            f"{se.child_orch_execution_id} from {se.server}")
                        resp = Response(exception=e)

                    if resp.ok:
                        se_json['orch_execution'] = resp.msg
                        se_json.pop('child_orch_execution_id', None)

                steps.append(se_json)
            # steps.sort(key=lambda x: x.start_time)
            data.update(steps=steps)
        return data
Пример #18
0
    def test_get_completions(self, mock_generate_url, mock_request):
        mock_request.return_value = Response(msg=[{
            "last_modified_at": "20200616.113822.505932+0200",
            "id": "01",
            "name": "dev1",
            "granules": []
        }, {
            "last_modified_at": "20200616.113955.580271+0200",
            "id": "02",
            "name": "dev2",
            "granules": []
        }, {
            "last_modified_at": "20200616.113955.580271+0200",
            "id": "13",
            "name": "node3",
            "granules": []
        }],
                                             code=200)

        data = {
            'status': [{
                'argument': 'node',
                'completer': ResourceCompleter('server', 'name')
            }],
            'server': {
                'list': [{
                    'argument': '--json',
                    'action': 'store_true',
                    'required': False
                },
                         [{
                             'argument': '--like'
                         }, {
                             'argument': '--name',
                             'completer': ResourceCompleter('servers', 'name')
                         }, {
                             'argument': '--id',
                             'completer': ResourceCompleter('servers', 'id')
                         }, {
                             'argument': '--last',
                             'action': 'store',
                             'type': int
                         }]],
                'show': [{
                    'argument': '--json',
                    'action': 'store_true',
                    'required': False
                }, {
                    'argument': 'node',
                    'nargs': '+',
                    'completer': ResourceCompleter('servers', 'name')
                }]
            },
            'nargs': [{
                'argument': 'compiler',
                'completer': WordCompleter(['python', 'ruby', 'c++']),
                'nargs': 2
            }, {
                'argument': 'so',
                'completer': WordCompleter(['windows', 'linux', 'mac'])
            }, {
                'argument': '--foo',
                'nargs': 1,
                'completer': WordCompleter(['aaa', 'bbb'])
            }, {
                'argument': '--bar',
                'nargs': '*',
                'completer': WordCompleter(['x1', 'x2', 'x3'])
            }, {
                'argument': '--xyz',
                'nargs': '+',
                'completer': WordCompleter(['x', 'y', 'z'])
            }]
        }

        completer = DshellCompleter.from_nested_dict(data)
        # Static list on empty input.
        completions = completer.get_completions(Document(""), CompleteEvent())
        self.assertListEqual(["status", "server", "nargs"],
                             [c.text for c in completions])

        # mid word
        completions = completer.get_completions(Document("st"),
                                                CompleteEvent())
        self.assertListEqual(["status"], [c.text for c in completions])

        # completed word
        completions = completer.get_completions(Document("status"),
                                                CompleteEvent())
        self.assertListEqual([], [c.text for c in completions])

        completions = completer.get_completions(Document("status "),
                                                CompleteEvent())
        self.assertListEqual(["dev1", "dev2", "node3"],
                             [c.text for c in completions])

        completions = completer.get_completions(Document("server "),
                                                CompleteEvent())
        self.assertListEqual(["list", "show"], [c.text for c in completions])

        completions = completer.get_completions(Document("server l"),
                                                CompleteEvent())
        self.assertListEqual(["list"], [c.text for c in completions])

        completions = completer.get_completions(Document("server list "),
                                                CompleteEvent())
        self.assertListEqual(["--json", "--like", "--name", "--id", "--last"],
                             [c.text for c in completions])

        completions = completer.get_completions(Document("server list --j"),
                                                CompleteEvent())
        self.assertListEqual(["--json"], [c.text for c in completions])

        completions = completer.get_completions(
            Document("server list --json "), CompleteEvent())
        self.assertListEqual(["--json", "--like", "--name", "--id", "--last"],
                             [c.text for c in completions])

        completions = completer.get_completions(
            Document("server list --json --id "), CompleteEvent())
        self.assertListEqual(["01", "02", "13"], [c.text for c in completions])

        completions = completer.get_completions(
            Document("server list --json --id 0"), CompleteEvent())
        self.assertListEqual(["01", "02"], [c.text for c in completions])

        completions = completer.get_completions(
            Document("server list --json --id 01 "), CompleteEvent())
        self.assertListEqual(["--json", "--like", "--name", "--id", "--last"],
                             [c.text for c in completions])

        completions = completer.get_completions(Document("server show "),
                                                CompleteEvent())
        self.assertListEqual(["--json", "dev1", "dev2", "node3"],
                             [c.text for c in completions])

        completions = completer.get_completions(Document("nargs "),
                                                CompleteEvent())
        self.assertListEqual(
            ['--foo', '--bar', '--xyz', 'python', 'ruby', 'c++'],
            [c.text for c in completions])

        completions = completer.get_completions(Document("nargs --foo "),
                                                CompleteEvent())
        self.assertListEqual(['aaa', 'bbb'], [c.text for c in completions])

        completions = completer.get_completions(Document("nargs --foo s "),
                                                CompleteEvent())
        self.assertListEqual(
            ['--foo', '--bar', '--xyz', 'python', 'ruby', 'c++'],
            [c.text for c in completions])

        completions = completer.get_completions(Document("nargs --bar "),
                                                CompleteEvent())
        self.assertListEqual(
            ['--foo', '--bar', '--xyz', '--', 'x1', 'x2', 'x3'],
            [c.text for c in completions])

        completions = completer.get_completions(Document("nargs --bar x"),
                                                CompleteEvent())
        self.assertListEqual(['x1', 'x2', 'x3'], [c.text for c in completions])

        completions = completer.get_completions(Document("nargs --bar x1 x2 "),
                                                CompleteEvent())
        self.assertListEqual(['--foo', '--bar', '--xyz', '--', 'x3'],
                             [c.text for c in completions])

        completions = completer.get_completions(
            Document("nargs --bar x1 x2 x3 "), CompleteEvent())
        self.assertListEqual(['--foo', '--bar', '--xyz', '--'],
                             [c.text for c in completions])

        completions = completer.get_completions(Document("nargs --xyz "),
                                                CompleteEvent())
        self.assertListEqual(['x', 'y', 'z'], [c.text for c in completions])

        completions = completer.get_completions(Document("nargs --xyz x "),
                                                CompleteEvent())
        self.assertListEqual(['--foo', '--bar', '--xyz', '--', 'y', 'z'],
                             [c.text for c in completions])

        completions = completer.get_completions(
            Document("nargs --bar x1 x2 x3 -- python ruby "), CompleteEvent())
        self.assertListEqual(['windows', 'linux', 'mac'],
                             [c.text for c in completions])