diff --git a/apps/participation/management/commands/fix_matrix_channels.py b/apps/participation/management/commands/fix_matrix_channels.py index 427cc8b..bff9181 100644 --- a/apps/participation/management/commands/fix_matrix_channels.py +++ b/apps/participation/management/commands/fix_matrix_channels.py @@ -11,7 +11,7 @@ class Command(BaseCommand): def handle(self, *args, **options): Matrix.set_display_name("Bot des Correspondances") - if not os.path.isfile(".matrix_avatar"): + if not os.path.isfile(".matrix_avatar"): # pragma: no cover stat_file = os.stat("corres2math/static/logo.png") with open("corres2math/static/logo.png", "rb") as f: resp, _ = Matrix.upload(f, filename="logo.png", content_type="image/png", filesize=stat_file.st_size) @@ -21,9 +21,9 @@ class Command(BaseCommand): with open(".matrix_avatar", "w") as f: f.write(avatar_uri) Matrix.set_avatar(avatar_uri) - else: - with open(".matrix_avatar", "r") as f: - avatar_uri = f.read().rstrip(" \t\r\n") + + with open(".matrix_avatar", "r") as f: + avatar_uri = f.read().rstrip(" \t\r\n") if not async_to_sync(Matrix.resolve_room_alias)("#faq:correspondances-maths.fr"): Matrix.create_room( diff --git a/apps/participation/migrations/0006_phase.py b/apps/participation/migrations/0006_phase.py index 2653caa..e7b5212 100644 --- a/apps/participation/migrations/0006_phase.py +++ b/apps/participation/migrations/0006_phase.py @@ -27,7 +27,7 @@ def register_phases(apps, schema_editor): ) -def reverse_phase_registering(apps, schema_editor): +def reverse_phase_registering(apps, _): # pragma: no cover """ Drop all phases in order to unapply this migration. """ diff --git a/apps/participation/tests.py b/apps/participation/tests.py index 21a3c43..c8b7d4a 100644 --- a/apps/participation/tests.py +++ b/apps/participation/tests.py @@ -1,3 +1,4 @@ +import os from datetime import timedelta from django.contrib.auth.models import User @@ -652,6 +653,14 @@ class TestStudentParticipation(TestCase): )) self.assertEqual(response.status_code, 200) + # Unauthenticated user can't update the calendar + self.client.logout() + response = self.client.get(reverse("participation:calendar")) + self.assertEqual(response.status_code, 200) + response = self.client.get(reverse("participation:update_phase", args=(2,))) + self.assertRedirects(response, reverse("login") + "?next=" + + reverse("participation:update_phase", args=(2,)), 302, 200) + def test_forbidden_access(self): """ Load personnal pages and ensure that these are protected. @@ -682,6 +691,22 @@ class TestStudentParticipation(TestCase): resp = self.client.get(reverse("participation:delete_question", args=(question.pk,))) self.assertEqual(resp.status_code, 403) + def test_cover_matrix(self): + """ + Load matrix scripts, to cover them and ensure that they can run. + """ + self.user.registration.team = self.team + self.user.registration.save() + self.second_user.registration.team = self.second_team + self.second_user.registration.save() + self.team.participation.valid = True + self.team.participation.received_participation = self.second_team.participation + self.team.participation.save() + + call_command('fix_matrix_channels') + call_command('setup_third_phase') + os.remove(".matrix_avatar") + class TestAdmin(TestCase): def setUp(self) -> None: diff --git a/apps/registration/models.py b/apps/registration/models.py index 7fe7292..0a85dc6 100644 --- a/apps/registration/models.py +++ b/apps/registration/models.py @@ -58,11 +58,11 @@ class Registration(PolymorphicModel): self.user.email_user(subject, message, html_message=html) @property - def type(self): + def type(self): # pragma: no cover raise NotImplementedError @property - def form_class(self): + def form_class(self): # pragma: no cover raise NotImplementedError @property diff --git a/apps/registration/tests.py b/apps/registration/tests.py index 0bd1c2b..02170e9 100644 --- a/apps/registration/tests.py +++ b/apps/registration/tests.py @@ -335,8 +335,3 @@ class TestRegistration(TestCase): attr = CustomAuthUser(self.user.username).attributs() self.assertEqual(attr["matrix_username"], self.user.registration.matrix_username) self.assertEqual(attr["display_name"], str(self.user.registration)) - - def test_not_implemented_error(self): - # Only for coverage - self.assertRaises(NotImplementedError, lambda: Registration().type) - self.assertRaises(NotImplementedError, lambda: Registration().form_class) diff --git a/corres2math/lists.py b/corres2math/lists.py index a8cbac2..d224392 100644 --- a/corres2math/lists.py +++ b/corres2math/lists.py @@ -6,7 +6,7 @@ _client = None def get_sympa_client(): global _client if _client is None: - if os.getenv("SYMPA_PASSWORD", None) is not None: + if os.getenv("SYMPA_PASSWORD", None) is not None: # pragma: no cover from sympasoap import Client _client = Client("https://" + os.getenv("SYMPA_URL")) _client.login(os.getenv("SYMPA_EMAIL"), os.getenv("SYMPA_PASSWORD")) diff --git a/corres2math/matrix.py b/corres2math/matrix.py index 9434baa..00ae20c 100644 --- a/corres2math/matrix.py +++ b/corres2math/matrix.py @@ -22,7 +22,7 @@ class Matrix: _device_id: str = None @classmethod - async def _get_client(cls) -> Union[AsyncClient, "FakeMatrixClient"]: + async def _get_client(cls) -> Union[AsyncClient, "FakeMatrixClient"]: # pragma: no cover """ Retrieve the bot account. If not logged, log in and store access token. @@ -133,7 +133,8 @@ class Matrix: If left as ``None``, some servers might refuse the upload. """ client = await cls._get_client() - return await client.upload(data_provider, content_type, filename, encrypt, monitor, filesize) + return await client.upload(data_provider, content_type, filename, encrypt, monitor, filesize) \ + if isinstance(client, AsyncClient) else UploadResponse("debug mode"), None @classmethod @async_to_sync @@ -219,9 +220,7 @@ class Matrix: """ client = await cls._get_client() resp: RoomResolveAliasResponse = await client.room_resolve_alias(room_alias) - if isinstance(resp, RoomResolveAliasResponse): - return resp.room_id - return None + return resp.room_id if isinstance(resp, RoomResolveAliasResponse) else None @classmethod @async_to_sync @@ -291,7 +290,7 @@ class Matrix: @classmethod @async_to_sync async def set_room_power_level(cls, room_id: str, user_id: str, power_level: int)\ - -> Union[RoomPutStateResponse, RoomPutStateError]: + -> Union[RoomPutStateResponse, RoomPutStateError]: # pragma: no cover """ Put a given power level to a user in a certain room. @@ -306,6 +305,9 @@ class Matrix: power_level (int): The target power level to give. """ client = await cls._get_client() + if isinstance(client, FakeMatrixClient): + return RoomPutStateError("debug mode") + if room_id.startswith("#"): room_id = await cls.resolve_room_alias(room_id) resp = await client.room_get_state_event(room_id, "m.room.power_levels") @@ -316,7 +318,7 @@ class Matrix: @classmethod @async_to_sync async def set_room_power_level_event(cls, room_id: str, event: str, power_level: int)\ - -> Union[RoomPutStateResponse, RoomPutStateError]: + -> Union[RoomPutStateResponse, RoomPutStateError]: # pragma: no cover """ Define the minimal power level to have to send a certain event type in a given room. @@ -332,6 +334,9 @@ class Matrix: power_level (int): The target power level to give. """ client = await cls._get_client() + if isinstance(client, FakeMatrixClient): + return RoomPutStateError("debug mode") + if room_id.startswith("#"): room_id = await cls.resolve_room_alias(room_id) resp = await client.room_get_state_event(room_id, "m.room.power_levels") diff --git a/corres2math/middlewares.py b/corres2math/middlewares.py index fcd29ba..a8ccef6 100644 --- a/corres2math/middlewares.py +++ b/corres2math/middlewares.py @@ -21,19 +21,13 @@ def get_current_user() -> User: return getattr(_thread_locals, USER_ATTR_NAME, None) -def get_current_session() -> SessionStore: - return getattr(_thread_locals, SESSION_ATTR_NAME, None) - - def get_current_ip() -> str: return getattr(_thread_locals, IP_ATTR_NAME, None) def get_current_authenticated_user(): current_user = get_current_user() - if isinstance(current_user, AnonymousUser): - return None - return current_user + return None if isinstance(current_user, AnonymousUser) else current_user class SessionMiddleware(object): @@ -49,10 +43,7 @@ class SessionMiddleware(object): request.user = User.objects.get(pk=request.session["_fake_user_id"]) user = request.user - if 'HTTP_X_REAL_IP' in request.META: - ip = request.META.get('HTTP_X_REAL_IP') - else: - ip = request.META.get('REMOTE_ADDR') + ip = request.META.get('HTTP_X_REAL_IP' if 'HTTP_X_REAL_IP' in request.META else 'REMOTE_ADDR') _set_current_user_and_ip(user, request.session, ip) response = self.get_response(request) @@ -61,7 +52,7 @@ class SessionMiddleware(object): return response -class TurbolinksMiddleware(object): +class TurbolinksMiddleware(object): # pragma: no cover """ Send the `Turbolinks-Location` header in response to a visit that was redirected, and Turbolinks will replace the browser's topmost history entry. diff --git a/corres2math/settings.py b/corres2math/settings.py index 73c4a56..bae7440 100644 --- a/corres2math/settings.py +++ b/corres2math/settings.py @@ -195,7 +195,7 @@ HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' _db_type = os.getenv('DJANGO_DB_TYPE', 'sqlite').lower() -if _db_type == 'mysql' or _db_type.startswith('postgres') or _db_type == 'psql': +if _db_type == 'mysql' or _db_type.startswith('postgres') or _db_type == 'psql': # pragma: no cover DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql' if _db_type == 'mysql' else 'django.db.backends.postgresql_psycopg2', @@ -214,7 +214,7 @@ else: } } -if os.getenv("CORRES2MATH_STAGE", "dev") == "prod": +if os.getenv("CORRES2MATH_STAGE", "dev") == "prod": # pragma: no cover from .settings_prod import * # noqa: F401,F403 else: from .settings_dev import * # noqa: F401,F403 diff --git a/corres2math/tests.py b/corres2math/tests.py new file mode 100644 index 0000000..c4337de --- /dev/null +++ b/corres2math/tests.py @@ -0,0 +1,24 @@ +import os + +from django.core.handlers.asgi import ASGIHandler +from django.core.handlers.wsgi import WSGIHandler +from django.test import TestCase + + +class TestLoadModules(TestCase): + """ + Load modules that are not used in development mode in order to increase coverage. + """ + def test_asgi(self): + from corres2math import asgi + self.assertTrue(isinstance(asgi.application, ASGIHandler)) + + def test_wsgi(self): + from corres2math import wsgi + self.assertTrue(isinstance(wsgi.application, WSGIHandler)) + + def test_load_production_settings(self): + os.putenv("CORRES2MATH_STAGE", "prod") + os.putenv("DJANGO_DB_TYPE", "postgres") + from corres2math import settings_prod + self.assertFalse(settings_prod.DEBUG) diff --git a/tox.ini b/tox.ini index 27fbf8f..021529b 100644 --- a/tox.ini +++ b/tox.ini @@ -12,7 +12,7 @@ deps = -r{toxinidir}/requirements.txt coverage commands = - coverage run --omit='apps/scripts*' --source=apps,corres2math ./manage.py test apps/ + coverage run --source=apps,corres2math ./manage.py test apps/ corres2math/ coverage report -m [testenv:linters]