diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..b48db440 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,23 @@ +[run] +omit = */tests/* + */factories/* + */migrations/* + */urls.py +branch = True + +include = + v1/* +; parallel = true +; concurrency = multiprocessing + +[report] +precision = 2 +ignore_errors = True +exclude_lines = + pragma: no cover + raise NotImplementedError + except ImportError + def __repr__ + def __str__ + if self\.logger\.debug + if __name__ == .__main__.: diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..dad43b71 --- /dev/null +++ b/.flake8 @@ -0,0 +1,16 @@ +[flake8] +ignore = D203,D101,D400,D401,D106,W503,F403,F405,E501,D403,D104 +exclude = + .git, + __pycache__, + migrations +filename = + *.py +max-complexity = 12 +import-order-style = google +; application-import-names = +max-line-length = 120 +max-linenumber = 500 +accept-encodings = utf-8,utf-16 +inline-quotes = single +multiline-quotes = double diff --git a/.gitignore b/.gitignore index 986a1f32..261c60bb 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,7 @@ sdist/ share/python-wheels/ var/ wheels/ +thenewboston.tar.gz # Unit test / coverage reports *.cover @@ -78,4 +79,4 @@ env.bak/ env/ venv venv.bak/ -venv/ \ No newline at end of file +venv/ diff --git a/Dockerfile b/Dockerfile index 5eb8a458..9eb07a3d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,9 +2,10 @@ FROM python:3.8 WORKDIR /opt/project -COPY requirements/local.txt . +COPY . . RUN set -x; \ python3 -m pip install pip-tools; \ - python3 -m pip install --no-cache-dir -r local.txt; \ + python3 -m pip install --no-cache-dir -r requirements/local.txt; \ + test -e thenewboston.tar.gz && python3 -m pip install thenewboston.tar.gz; \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /root/.cache diff --git a/README.md b/README.md index 7b27df9b..04ffc8b0 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,13 @@ docker-compose run app pytest docker-compose exec app pytest # if docker-compose run is running ``` +To run tests with coverage report: +``` +docker-compose run app pytest --cov=v1 +# or +docker-compose exec app pytest --cov=v1 # if docker-compose run is running +``` + To monitor Celery tasks: ``` docker-compose exec celery celery flower -A config.settings --address=127.0.0.1 --port=5555 diff --git a/pytest.ini b/pytest.ini index 0edc6f7c..fae32d03 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,4 @@ [pytest] DJANGO_SETTINGS_MODULE = config.settings.local python_files = tests/*.py +norecursedirs = .* .git *.egg build dist tmp* node_modules diff --git a/requirements/base.in b/requirements/base.in index 2be952c1..c859d701 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -1,9 +1,10 @@ celery==4.4.2 -django-debug-toolbar==2.2 +django-debug-toolbar==3.1.1 django-filter==2.3.0 django-redis==4.11.0 flower==0.9.4 redis==3.5.2 tblib==1.6.0 thenewboston==0.0.19 -wheel==0.34.2 +wheel==0.35.1 +requests-mock==1.8.0 diff --git a/requirements/base.txt b/requirements/base.txt index b7308afb..a6229091 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -22,7 +22,7 @@ constantly==15.1.0 # via twisted cryptography==3.1.1 # via autobahn, pyopenssl, service-identity daphne==2.5.0 # via channels django-cors-headers==3.4.0 # via thenewboston -django-debug-toolbar==2.2 # via -r requirements/base.in +django-debug-toolbar==3.1.1 # via -r requirements/base.in django-filter==2.3.0 # via -r requirements/base.in django-redis==4.11.0 # via -r requirements/base.in django==3.1 # via channels, django-cors-headers, django-debug-toolbar, django-filter, django-redis, djangorestframework, thenewboston @@ -34,7 +34,6 @@ hiredis==1.1.0 # via aioredis humanize==0.5.1 # via flower hyperlink==20.0.1 # via twisted idna==2.10 # via hyperlink, requests, twisted -importlib-metadata==2.0.0 # via kombu, pluggy, pytest incremental==17.5.0 # via twisted iniconfig==1.0.1 # via pytest kombu==4.6.11 # via celery @@ -58,9 +57,10 @@ pytest==6.0.1 # via pytest-asyncio, pytest-django, thenewboston python-dateutil==2.8.1 # via faker pytz==2020.1 # via celery, django, flower redis==3.5.2 # via -r requirements/base.in, django-redis -requests==2.23.0 # via thenewboston +requests-mock==1.8.0 # via -r requirements/base.in +requests==2.23.0 # via requests-mock, thenewboston service-identity==18.1.0 # via twisted -six==1.15.0 # via automat, cryptography, packaging, pynacl, pyopenssl, python-dateutil +six==1.15.0 # via automat, cryptography, packaging, pynacl, pyopenssl, python-dateutil, requests-mock sqlparse==0.3.1 # via django, django-debug-toolbar tblib==1.6.0 # via -r requirements/base.in text-unidecode==1.3 # via faker @@ -71,8 +71,7 @@ twisted[tls]==20.3.0 # via daphne txaio==20.4.1 # via autobahn urllib3==1.25.10 # via requests vine==1.3.0 # via amqp, celery -wheel==0.34.2 # via -r requirements/base.in -zipp==3.2.0 # via importlib-metadata +wheel==0.35.1 # via -r requirements/base.in zope.interface==5.1.0 # via twisted # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/development.txt b/requirements/development.txt index 2715a43f..c51c0d85 100644 --- a/requirements/development.txt +++ b/requirements/development.txt @@ -22,7 +22,7 @@ constantly==15.1.0 # via twisted cryptography==3.1.1 # via autobahn, pyopenssl, service-identity daphne==2.5.0 # via channels django-cors-headers==3.4.0 # via thenewboston -django-debug-toolbar==2.2 # via -r requirements/base.in +django-debug-toolbar==3.1.1 # via -r requirements/base.in django-filter==2.3.0 # via -r requirements/base.in django-redis==4.11.0 # via -r requirements/base.in django==3.1 # via channels, django-cors-headers, django-debug-toolbar, django-filter, django-redis, djangorestframework, thenewboston @@ -34,7 +34,6 @@ hiredis==1.1.0 # via aioredis humanize==0.5.1 # via flower hyperlink==20.0.1 # via twisted idna==2.10 # via hyperlink, requests, twisted -importlib-metadata==2.0.0 # via kombu, pluggy, pytest incremental==17.5.0 # via twisted iniconfig==1.0.1 # via pytest kombu==4.6.11 # via celery @@ -58,9 +57,10 @@ pytest==6.0.1 # via pytest-asyncio, pytest-django, thenewboston python-dateutil==2.8.1 # via faker pytz==2020.1 # via celery, django, flower redis==3.5.2 # via -r requirements/base.in, django-redis -requests==2.23.0 # via thenewboston +requests-mock==1.8.0 # via -r requirements/base.in +requests==2.23.0 # via requests-mock, thenewboston service-identity==18.1.0 # via twisted -six==1.15.0 # via automat, cryptography, packaging, pynacl, pyopenssl, python-dateutil +six==1.15.0 # via automat, cryptography, packaging, pynacl, pyopenssl, python-dateutil, requests-mock sqlparse==0.3.1 # via django, django-debug-toolbar tblib==1.6.0 # via -r requirements/base.in text-unidecode==1.3 # via faker @@ -71,8 +71,7 @@ twisted[tls]==20.3.0 # via daphne txaio==20.4.1 # via autobahn urllib3==1.25.10 # via requests vine==1.3.0 # via amqp, celery -wheel==0.34.2 # via -r requirements/base.in -zipp==3.2.0 # via importlib-metadata +wheel==0.35.1 # via -r requirements/base.in zope.interface==5.1.0 # via twisted # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/local.txt b/requirements/local.txt index 23aaaa8c..12438bd1 100644 --- a/requirements/local.txt +++ b/requirements/local.txt @@ -22,7 +22,7 @@ constantly==15.1.0 # via twisted cryptography==3.1.1 # via autobahn, pyopenssl, service-identity daphne==2.5.0 # via channels django-cors-headers==3.4.0 # via thenewboston -django-debug-toolbar==2.2 # via -r requirements/base.in +django-debug-toolbar==3.1.1 # via -r requirements/base.in django-filter==2.3.0 # via -r requirements/base.in django-redis==4.11.0 # via -r requirements/base.in django==3.1 # via channels, django-cors-headers, django-debug-toolbar, django-filter, django-redis, djangorestframework, thenewboston @@ -34,7 +34,6 @@ hiredis==1.1.0 # via aioredis humanize==0.5.1 # via flower hyperlink==20.0.1 # via twisted idna==2.10 # via hyperlink, requests, twisted -importlib-metadata==2.0.0 # via kombu, pluggy, pytest incremental==17.5.0 # via twisted iniconfig==1.0.1 # via pytest iptools==0.7.0 # via -r requirements/local.in @@ -59,9 +58,10 @@ pytest==6.0.1 # via pytest-asyncio, pytest-django, thenewboston python-dateutil==2.8.1 # via faker pytz==2020.1 # via celery, django, flower redis==3.5.2 # via -r requirements/base.in, django-redis -requests==2.23.0 # via thenewboston +requests-mock==1.8.0 # via -r requirements/base.in +requests==2.23.0 # via requests-mock, thenewboston service-identity==18.1.0 # via twisted -six==1.15.0 # via automat, cryptography, packaging, pynacl, pyopenssl, python-dateutil +six==1.15.0 # via automat, cryptography, packaging, pynacl, pyopenssl, python-dateutil, requests-mock sqlparse==0.3.1 # via django, django-debug-toolbar tblib==1.6.0 # via -r requirements/base.in text-unidecode==1.3 # via faker @@ -72,8 +72,7 @@ twisted[tls]==20.3.0 # via daphne txaio==20.4.1 # via autobahn urllib3==1.25.10 # via requests vine==1.3.0 # via amqp, celery -wheel==0.34.2 # via -r requirements/base.in -zipp==3.2.0 # via importlib-metadata +wheel==0.35.1 # via -r requirements/base.in zope.interface==5.1.0 # via twisted # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/production.txt b/requirements/production.txt index 984fba61..2af4caac 100644 --- a/requirements/production.txt +++ b/requirements/production.txt @@ -22,7 +22,7 @@ constantly==15.1.0 # via twisted cryptography==3.1.1 # via autobahn, pyopenssl, service-identity daphne==2.5.0 # via channels django-cors-headers==3.4.0 # via thenewboston -django-debug-toolbar==2.2 # via -r requirements/base.in +django-debug-toolbar==3.1.1 # via -r requirements/base.in django-filter==2.3.0 # via -r requirements/base.in django-redis==4.11.0 # via -r requirements/base.in django==3.1 # via channels, django-cors-headers, django-debug-toolbar, django-filter, django-redis, djangorestframework, thenewboston @@ -34,7 +34,6 @@ hiredis==1.1.0 # via aioredis humanize==0.5.1 # via flower hyperlink==20.0.1 # via twisted idna==2.10 # via hyperlink, requests, twisted -importlib-metadata==2.0.0 # via kombu, pluggy, pytest incremental==17.5.0 # via twisted iniconfig==1.0.1 # via pytest kombu==4.6.11 # via celery @@ -58,9 +57,10 @@ pytest==6.0.1 # via pytest-asyncio, pytest-django, thenewboston python-dateutil==2.8.1 # via faker pytz==2020.1 # via celery, django, flower redis==3.5.2 # via -r requirements/base.in, django-redis -requests==2.23.0 # via thenewboston +requests-mock==1.8.0 # via -r requirements/base.in +requests==2.23.0 # via requests-mock, thenewboston service-identity==18.1.0 # via twisted -six==1.15.0 # via automat, cryptography, packaging, pynacl, pyopenssl, python-dateutil +six==1.15.0 # via automat, cryptography, packaging, pynacl, pyopenssl, python-dateutil, requests-mock sqlparse==0.3.1 # via django, django-debug-toolbar tblib==1.6.0 # via -r requirements/base.in text-unidecode==1.3 # via faker @@ -71,8 +71,7 @@ twisted[tls]==20.3.0 # via daphne txaio==20.4.1 # via autobahn urllib3==1.25.10 # via requests vine==1.3.0 # via amqp, celery -wheel==0.34.2 # via -r requirements/base.in -zipp==3.2.0 # via importlib-metadata +wheel==0.35.1 # via -r requirements/base.in zope.interface==5.1.0 # via twisted # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/staging.txt b/requirements/staging.txt index ddec04bb..4f347587 100644 --- a/requirements/staging.txt +++ b/requirements/staging.txt @@ -22,7 +22,7 @@ constantly==15.1.0 # via twisted cryptography==3.1.1 # via autobahn, pyopenssl, service-identity daphne==2.5.0 # via channels django-cors-headers==3.4.0 # via thenewboston -django-debug-toolbar==2.2 # via -r requirements/base.in +django-debug-toolbar==3.1.1 # via -r requirements/base.in django-filter==2.3.0 # via -r requirements/base.in django-redis==4.11.0 # via -r requirements/base.in django==3.1 # via channels, django-cors-headers, django-debug-toolbar, django-filter, django-redis, djangorestframework, thenewboston @@ -34,7 +34,6 @@ hiredis==1.1.0 # via aioredis humanize==0.5.1 # via flower hyperlink==20.0.1 # via twisted idna==2.10 # via hyperlink, requests, twisted -importlib-metadata==2.0.0 # via kombu, pluggy, pytest incremental==17.5.0 # via twisted iniconfig==1.0.1 # via pytest kombu==4.6.11 # via celery @@ -58,9 +57,10 @@ pytest==6.0.1 # via pytest-asyncio, pytest-django, thenewboston python-dateutil==2.8.1 # via faker pytz==2020.1 # via celery, django, flower redis==3.5.2 # via -r requirements/base.in, django-redis -requests==2.23.0 # via thenewboston +requests-mock==1.8.0 # via -r requirements/base.in +requests==2.23.0 # via requests-mock, thenewboston service-identity==18.1.0 # via twisted -six==1.15.0 # via automat, cryptography, packaging, pynacl, pyopenssl, python-dateutil +six==1.15.0 # via automat, cryptography, packaging, pynacl, pyopenssl, python-dateutil, requests-mock sqlparse==0.3.1 # via django, django-debug-toolbar tblib==1.6.0 # via -r requirements/base.in text-unidecode==1.3 # via faker @@ -71,8 +71,7 @@ twisted[tls]==20.3.0 # via daphne txaio==20.4.1 # via autobahn urllib3==1.25.10 # via requests vine==1.3.0 # via amqp, celery -wheel==0.34.2 # via -r requirements/base.in -zipp==3.2.0 # via importlib-metadata +wheel==0.35.1 # via -r requirements/base.in zope.interface==5.1.0 # via twisted # The following packages are considered to be unsafe in a requirements file: diff --git a/scripts/compile_requirements.sh b/scripts/compile_requirements.sh old mode 100644 new mode 100755 diff --git a/v1/banks/tests/bank.py b/v1/banks/tests/bank.py index 6fb64e0e..1e593c95 100644 --- a/v1/banks/tests/bank.py +++ b/v1/banks/tests/bank.py @@ -1,5 +1,5 @@ +from rest_framework import status from rest_framework.reverse import reverse -from rest_framework.status import HTTP_200_OK from thenewboston.third_party.pytest.asserts import assert_objects_vs_dicts from thenewboston.utils.signed_requests import generate_signed_request @@ -11,12 +11,25 @@ def test_banks_list(client, banks, django_assert_max_num_queries): response = client.get_json( reverse('bank-list'), {'limit': 0}, - expected=HTTP_200_OK, + expected=status.HTTP_200_OK, ) assert_objects_vs_dicts(banks, response, key='node_identifier') assert response +def test_banks_post(client, bank_fake_data, self_configuration): + response = client.post_json( + reverse('bank-list'), + generate_signed_request( + data=bank_fake_data, + nid_signing_key=get_signing_key(), + ), + expected=status.HTTP_201_CREATED + ) + bank_fake_data['trust'] = f'{bank_fake_data["trust"]:.2f}' + assert response == bank_fake_data + + def test_banks_patch(client, bank, bank_fake_data, self_configuration): response = client.patch_json( @@ -28,6 +41,6 @@ def test_banks_patch(client, bank, bank_fake_data, self_configuration): data=bank_fake_data, nid_signing_key=get_signing_key(), ), - expected=HTTP_200_OK, + expected=status.HTTP_200_OK, ) - assert float(response['trust']) == bank_fake_data['trust'] + assert response['trust'] == f'{bank_fake_data["trust"]:.2f}' diff --git a/v1/banks/views/bank.py b/v1/banks/views/bank.py index 41fb92d7..aafb00ad 100644 --- a/v1/banks/views/bank.py +++ b/v1/banks/views/bank.py @@ -1,3 +1,4 @@ +from rest_framework import status from rest_framework.mixins import CreateModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet @@ -24,6 +25,8 @@ class BankViewSet( parameters: - name: trust type: number + create: + description: Create bank """ lookup_field = 'node_identifier' @@ -43,7 +46,8 @@ def create(self, request, *args, **kwargs): bank = serializer.save() return Response( - self.get_serializer(bank).data + self.get_serializer(bank).data, + status=status.HTTP_201_CREATED ) @is_self_signed_message diff --git a/v1/connection_requests/serializers/connection_request.py b/v1/connection_requests/serializers/connection_request.py index 7323f54b..e4609446 100644 --- a/v1/connection_requests/serializers/connection_request.py +++ b/v1/connection_requests/serializers/connection_request.py @@ -74,7 +74,7 @@ def get_node_config(data): raise serializers.ValidationError('Invalid node_type') except Exception as e: logger.exception(e) - raise serializers.ValidationError(e) + raise e if config_serializer.is_valid(): return config_data diff --git a/v1/connection_requests/tests/connection_request.py b/v1/connection_requests/tests/connection_request.py index acaa86b8..9553fb80 100644 --- a/v1/connection_requests/tests/connection_request.py +++ b/v1/connection_requests/tests/connection_request.py @@ -1,7 +1,10 @@ import pytest +from rest_framework import status from rest_framework.reverse import reverse from rest_framework.status import HTTP_400_BAD_REQUEST +from thenewboston.constants.network import PRIMARY_VALIDATOR from thenewboston.third_party.factory.utils import build_json +from thenewboston.utils.format import format_address from thenewboston.utils.signed_requests import generate_signed_request from ..factories.connection_request import ConnectionRequestFactory @@ -28,3 +31,26 @@ def test_banks_post_400_connect_to_self(client, connection_request_data, signing expected=HTTP_400_BAD_REQUEST, ) assert response.get('non_field_errors')[0] == 'Unable to connect to self' + + +def test_banks_post_400_primary_validator( + client, connection_request_data, signing_key, self_configuration, requests_mock +): + connection_request_data['node_type'] = PRIMARY_VALIDATOR + address = format_address( + ip_address=connection_request_data['ip_address'], + port=connection_request_data.get('port'), + protocol=connection_request_data['protocol'] + ) + requests_mock.get(f'{address}/config', json=connection_request_data) + + response = client.post_json( + reverse('connection_requests-list'), + generate_signed_request( + data=connection_request_data, + nid_signing_key=signing_key, + ), + expected=status.HTTP_400_BAD_REQUEST + ) + + assert response == {'non_field_errors': ['Unable to accept connection requests from primary validators']} diff --git a/v1/crawl/tests/crawl.py b/v1/crawl/tests/crawl.py index 9b8f926a..029cb2ff 100644 --- a/v1/crawl/tests/crawl.py +++ b/v1/crawl/tests/crawl.py @@ -74,7 +74,7 @@ def test_crawl_stop_200(client, self_configuration, celery_worker): assert response['crawl_last_completed'] is None assert response['crawl_status'] == CRAWL_STATUS_STOP_REQUESTED - time.sleep(1) + time.sleep(2) assert cache.get(CRAWL_STATUS) == CRAWL_STATUS_NOT_CRAWLING assert crawl_status(client)['crawl_status'] == CRAWL_STATUS_NOT_CRAWLING