diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..35bf78f5f --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[run] +omit = */test*,celerywyrm*,bookwyrm/migrations/* \ No newline at end of file diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..3bf9f2c5b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +__pycache__ +*.pyc +*.pyo +*.pyd +.git +.github +.pytest* \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7d53fd2f5..1384056f2 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ # BookWyrm .env /images/ + +# Testing +.coverage \ No newline at end of file diff --git a/README.md b/README.md index 4d67bd85e..5e16597c1 100644 --- a/README.md +++ b/README.md @@ -65,9 +65,8 @@ You'll have to install the Docker and docker-compose. When you're ready, run: ```bash docker-compose build -docker-compose up -docker-compose exec web python manage.py migrate -docker-compose exec web python manage.py shell -c 'import init_db' +docker-compose run --rm web python manage.py migrate +docker-compose run --rm web python manage.py initdb ``` ### Without Docker diff --git a/bookwyrm/management/commands/initdb.py b/bookwyrm/management/commands/initdb.py new file mode 100644 index 000000000..f29ed1027 --- /dev/null +++ b/bookwyrm/management/commands/initdb.py @@ -0,0 +1,100 @@ +from django.core.management.base import BaseCommand, CommandError +from django.contrib.auth.models import Group, Permission +from django.contrib.contenttypes.models import ContentType + +from bookwyrm.models import Connector, User +from bookwyrm.settings import DOMAIN + +def init_groups(): + groups = ['admin', 'moderator', 'editor'] + for group in groups: + Group.objects.create(name=group) + +def init_permissions(): + permissions = [{ + 'codename': 'edit_instance_settings', + 'name': 'change the instance info', + 'groups': ['admin',] + }, { + 'codename': 'set_user_group', + 'name': 'change what group a user is in', + 'groups': ['admin', 'moderator'] + }, { + 'codename': 'control_federation', + 'name': 'control who to federate with', + 'groups': ['admin', 'moderator'] + }, { + 'codename': 'create_invites', + 'name': 'issue invitations to join', + 'groups': ['admin', 'moderator'] + }, { + 'codename': 'moderate_user', + 'name': 'deactivate or silence a user', + 'groups': ['admin', 'moderator'] + }, { + 'codename': 'moderate_post', + 'name': 'delete other users\' posts', + 'groups': ['admin', 'moderator'] + }, { + 'codename': 'edit_book', + 'name': 'edit book info', + 'groups': ['admin', 'moderator', 'editor'] + }] + + content_type = ContentType.objects.get_for_model(User) + for permission in permissions: + permission_obj = Permission.objects.create( + codename=permission['codename'], + name=permission['name'], + content_type=content_type, + ) + # add the permission to the appropriate groups + for group_name in permission['groups']: + Group.objects.get(name=group_name).permissions.add(permission_obj) + + # while the groups and permissions shouldn't be changed because the code + # depends on them, what permissions go with what groups should be editable + + +def init_connectors(): + Connector.objects.create( + identifier=DOMAIN, + name='Local', + local=True, + connector_file='self_connector', + base_url='https://%s' % DOMAIN, + books_url='https://%s/book' % DOMAIN, + covers_url='https://%s/images/covers' % DOMAIN, + search_url='https://%s/search?q=' % DOMAIN, + priority=1, + ) + + Connector.objects.create( + identifier='bookwyrm.social', + name='BookWyrm dot Social', + connector_file='bookwyrm_connector', + base_url='https://bookwyrm.social' , + books_url='https://bookwyrm.social/book', + covers_url='https://bookwyrm.social/images/covers', + search_url='https://bookwyrm.social/search?q=', + priority=2, + ) + + Connector.objects.create( + identifier='openlibrary.org', + name='OpenLibrary', + connector_file='openlibrary', + base_url='https://openlibrary.org', + books_url='https://openlibrary.org', + covers_url='https://covers.openlibrary.org', + search_url='https://openlibrary.org/search?q=', + priority=3, + ) + +class Command(BaseCommand): + help = 'Initializes the database with starter data' + + def handle(self, *args, **options): + init_groups() + init_permissions() + init_connectors() \ No newline at end of file diff --git a/bookwyrm/tests/test_signing.py b/bookwyrm/tests/test_signing.py index 8653823d3..5170b4167 100644 --- a/bookwyrm/tests/test_signing.py +++ b/bookwyrm/tests/test_signing.py @@ -6,6 +6,8 @@ import pathlib import json import responses +import pytest + from django.test import TestCase, Client from django.utils.http import http_date @@ -167,6 +169,7 @@ class Signature(TestCase): response = self.send_test_request(sender=self.fake_remote) self.assertEqual(response.status_code, 401) + @pytest.mark.integration def test_changed_data(self): '''Message data must match the digest header.''' response = self.send_test_request( @@ -174,12 +177,14 @@ class Signature(TestCase): send_data=get_follow_data(self.mouse, self.cat)) self.assertEqual(response.status_code, 401) + @pytest.mark.integration def test_invalid_digest(self): response = self.send_test_request( self.mouse, digest='SHA-256=AAAAAAAAAAAAAAAAAA') self.assertEqual(response.status_code, 401) + @pytest.mark.integration def test_old_message(self): '''Old messages should be rejected to prevent replay attacks.''' response = self.send_test_request( diff --git a/docker-compose.yml b/docker-compose.yml index 18b58032c..08567ce33 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,6 +22,7 @@ services: - main web: build: . + env_file: .env command: python manage.py runserver 0.0.0.0:8000 volumes: - .:/app diff --git a/fr-dev b/fr-dev index 56494473b..c63a10a14 100755 --- a/fr-dev +++ b/fr-dev @@ -3,51 +3,86 @@ set -e set -x +function clean { + docker-compose stop + docker-compose rm -f +} + +function runweb { + docker-compose run --rm web "$@" + clean +} + +function execdb { + docker-compose exec db $@ +} + +function execweb { + docker-compose exec web "$@" +} + +function initdb { + execweb python manage.py migrate + execweb python manage.py initdb +} + case "$1" in up) docker-compose up --build ;; run) - docker-compose run --service-ports web + docker-compose run --rm --service-ports web ;; initdb) - docker-compose exec web python manage.py migrate - docker-compose exec web python manage.py shell -c 'import init_db' + initdb ;; resetdb) - docker-compose stop web - docker-compose exec db dropdb -U fedireads fedireads - docker-compose exec db createdb -U fedireads fedireads - docker-compose start web - docker-compose exec web python manage.py migrate - docker-compose exec web python manage.py shell -c 'import init_db' + clean + docker-compose up --build -d + execdb dropdb -U fedireads fedireads + execdb createdb -U fedireads fedireads + initdb + clean ;; makemigrations) - docker-compose exec web python manage.py makemigrations + execweb python manage.py makemigrations ;; migrate) - docker-compose exec web python manage.py migrate + execweb python manage.py migrate + ;; + bash) + execweb bash ;; shell) - docker-compose exec web python manage.py shell + execweb python manage.py shell ;; dbshell) - docker-compose exec db psql -U fedireads fedireads + execdb psql -U fedireads fedireads ;; restart_celery) docker-compose restart celery_worker ;; test) shift 1 - docker-compose exec web coverage run --source='.' --omit="*/test*,celerywyrm*,bookwyrm/migrations/*" manage.py test "$@" + execweb coverage run --source='.' --omit="*/test*,celerywyrm*,bookwyrm/migrations/*" manage.py test "$@" + ;; + pytest) + shift 1 + execweb pytest "$@" ;; test_report) - docker-compose exec web coverage report + execweb coverage report ;; collectstatic) - docker-compose exec web python manage.py collectstatic --no-input + execweb python manage.py collectstatic --no-input + ;; + build) + docker-compose build + ;; + clean) + clean ;; *) - echo "Unrecognised command. Try: up, initdb, resetdb, makemigrations, migrate, shell, dbshell, restart_celery, test, test_report" + echo "Unrecognised command. Try: build, clean, up, initdb, resetdb, makemigrations, migrate, bash, shell, dbshell, restart_celery, test, pytest, test_report" ;; esac diff --git a/init_db.py b/init_db.py deleted file mode 100644 index e2ade67bd..000000000 --- a/init_db.py +++ /dev/null @@ -1,91 +0,0 @@ -''' starter data ''' -from django.contrib.auth.models import Group, Permission -from django.contrib.contenttypes.models import ContentType - -from bookwyrm.models import Connector, User -from bookwyrm.settings import DOMAIN - - -groups = ['admin', 'moderator', 'editor'] -for group in groups: - Group.objects.create(name=group) - -permissions = [{ - 'codename': 'edit_instance_settings', - 'name': 'change the instance info', - 'groups': ['admin',] - }, { - 'codename': 'set_user_group', - 'name': 'change what group a user is in', - 'groups': ['admin', 'moderator'] - }, { - 'codename': 'control_federation', - 'name': 'control who to federate with', - 'groups': ['admin', 'moderator'] - }, { - 'codename': 'create_invites', - 'name': 'issue invitations to join', - 'groups': ['admin', 'moderator'] - }, { - 'codename': 'moderate_user', - 'name': 'deactivate or silence a user', - 'groups': ['admin', 'moderator'] - }, { - 'codename': 'moderate_post', - 'name': 'delete other users\' posts', - 'groups': ['admin', 'moderator'] - }, { - 'codename': 'edit_book', - 'name': 'edit book info', - 'groups': ['admin', 'moderator', 'editor'] - }] - -content_type = ContentType.objects.get_for_model(User) -for permission in permissions: - permission_obj = Permission.objects.create( - codename=permission['codename'], - name=permission['name'], - content_type=content_type, - ) - # add the permission to the appropriate groups - for group_name in permission['groups']: - Group.objects.get(name=group_name).permissions.add(permission_obj) - -# while the groups and permissions shouldn't be changed because the code -# depends on them, what permissions go with what groups should be editable - - - -Connector.objects.create( - identifier=DOMAIN, - name='Local', - local=True, - connector_file='self_connector', - base_url='https://%s' % DOMAIN, - books_url='https://%s/book' % DOMAIN, - covers_url='https://%s/images/covers' % DOMAIN, - search_url='https://%s/search?q=' % DOMAIN, - priority=1, -) - -Connector.objects.create( - identifier='bookwyrm.social', - name='BookWyrm dot Social', - connector_file='bookwyrm_connector', - base_url='https://bookwyrm.social' , - books_url='https://bookwyrm.social/book', - covers_url='https://bookwyrm.social/images/covers', - search_url='https://bookwyrm.social/search?q=', - priority=2, -) - -Connector.objects.create( - identifier='openlibrary.org', - name='OpenLibrary', - connector_file='openlibrary', - base_url='https://openlibrary.org', - books_url='https://openlibrary.org', - covers_url='https://covers.openlibrary.org', - search_url='https://openlibrary.org/search?q=', - priority=3, -) diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 000000000..fa9dbc59f --- /dev/null +++ b/pytest.ini @@ -0,0 +1,6 @@ +[pytest] +DJANGO_SETTINGS_MODULE = bookwyrm.settings +python_files = tests.py test_*.py *_tests.py +addopts = --cov=bookwyrm --cov-config=.coveragerc +markers = + integration: marks tests as requiring external resources (deselect with '-m "not integration"') diff --git a/rebuilddb.sh b/rebuilddb.sh index 99be0268e..d32e5dab4 100755 --- a/rebuilddb.sh +++ b/rebuilddb.sh @@ -21,5 +21,5 @@ fi python manage.py makemigrations fedireads python manage.py migrate -python manage.py shell < init_db.py +python manage.py initdb python manage.py runserver diff --git a/requirements.txt b/requirements.txt index e041ec25d..12a7b6e3f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,9 @@ flower==0.9.4 Pillow>=7.1.0 psycopg2==2.8.4 pycryptodome==3.9.4 +pytest-django==4.1.0 +pytest==6.1.2 +pytest-cov==2.10.1 python-dateutil==2.8.1 redis==3.4.1 requests==2.22.0