1
0
mirror of https://gitlab.com/animath/si/plateforme-corres2math.git synced 2025-04-28 18:52:40 +00:00

Compare commits

...

2 Commits

Author SHA1 Message Date
Yohann D'ANELLO
1ddf39f296 Cover also settings files, keep 100% coverage by ignoring production files 2020-11-03 19:13:41 +01:00
Yohann D'ANELLO
fa368a399a More linting 2020-11-03 18:16:36 +01:00
12 changed files with 93 additions and 52 deletions

View File

@ -11,7 +11,7 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
Matrix.set_display_name("Bot des Correspondances") 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") stat_file = os.stat("corres2math/static/logo.png")
with open("corres2math/static/logo.png", "rb") as f: 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) 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: with open(".matrix_avatar", "w") as f:
f.write(avatar_uri) f.write(avatar_uri)
Matrix.set_avatar(avatar_uri) Matrix.set_avatar(avatar_uri)
else:
with open(".matrix_avatar", "r") as f: with open(".matrix_avatar", "r") as f:
avatar_uri = f.read().rstrip(" \t\r\n") avatar_uri = f.read().rstrip(" \t\r\n")
if not async_to_sync(Matrix.resolve_room_alias)("#faq:correspondances-maths.fr"): if not async_to_sync(Matrix.resolve_room_alias)("#faq:correspondances-maths.fr"):
Matrix.create_room( Matrix.create_room(

View File

@ -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. Drop all phases in order to unapply this migration.
""" """

View File

@ -1,3 +1,4 @@
import os
from datetime import timedelta from datetime import timedelta
from django.contrib.auth.models import User from django.contrib.auth.models import User
@ -562,10 +563,10 @@ class TestStudentParticipation(TestCase):
reverse("participation:add_question", args=(self.team.participation.pk,)), 302, 200) reverse("participation:add_question", args=(self.team.participation.pk,)), 302, 200)
response = self.client.get(reverse("participation:update_question", args=(self.question.pk,))) response = self.client.get(reverse("participation:update_question", args=(self.question.pk,)))
self.assertRedirects(response, reverse("login") + "?next=" + self.assertRedirects(response, reverse("login") + "?next=" +
reverse("participation:delete_question", args=(self.question.pk,)), 302, 200) reverse("participation:update_question", args=(self.question.pk,)), 302, 200)
response = self.client.get(reverse("participation:add_question", args=(self.question.pk,))) response = self.client.get(reverse("participation:delete_question", args=(self.question.pk,)))
self.assertRedirects(response, reverse("login") + "?next=" + self.assertRedirects(response, reverse("login") + "?next=" +
reverse("participation:add_question", args=(self.question.pk,)), 302, 200) reverse("participation:delete_question", args=(self.question.pk,)), 302, 200)
def test_current_phase(self): def test_current_phase(self):
""" """
@ -652,6 +653,14 @@ class TestStudentParticipation(TestCase):
)) ))
self.assertEqual(response.status_code, 200) 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): def test_forbidden_access(self):
""" """
Load personnal pages and ensure that these are protected. 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,))) resp = self.client.get(reverse("participation:delete_question", args=(question.pk,)))
self.assertEqual(resp.status_code, 403) 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): class TestAdmin(TestCase):
def setUp(self) -> None: def setUp(self) -> None:

View File

@ -58,11 +58,11 @@ class Registration(PolymorphicModel):
self.user.email_user(subject, message, html_message=html) self.user.email_user(subject, message, html_message=html)
@property @property
def type(self): def type(self): # pragma: no cover
raise NotImplementedError raise NotImplementedError
@property @property
def form_class(self): def form_class(self): # pragma: no cover
raise NotImplementedError raise NotImplementedError
@property @property

View File

@ -335,8 +335,3 @@ class TestRegistration(TestCase):
attr = CustomAuthUser(self.user.username).attributs() attr = CustomAuthUser(self.user.username).attributs()
self.assertEqual(attr["matrix_username"], self.user.registration.matrix_username) self.assertEqual(attr["matrix_username"], self.user.registration.matrix_username)
self.assertEqual(attr["display_name"], str(self.user.registration)) 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)

View File

@ -6,7 +6,7 @@ _client = None
def get_sympa_client(): def get_sympa_client():
global _client global _client
if _client is None: 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 from sympasoap import Client
_client = Client("https://" + os.getenv("SYMPA_URL")) _client = Client("https://" + os.getenv("SYMPA_URL"))
_client.login(os.getenv("SYMPA_EMAIL"), os.getenv("SYMPA_PASSWORD")) _client.login(os.getenv("SYMPA_EMAIL"), os.getenv("SYMPA_PASSWORD"))

View File

@ -1,8 +1,12 @@
import os import os
from typing import Tuple from typing import Any, Dict, Optional, Tuple, Union
from asgiref.sync import async_to_sync from asgiref.sync import async_to_sync
from nio import * from nio import AsyncClient, DataProvider, ProfileSetAvatarError, ProfileSetAvatarResponse, \
ProfileSetDisplayNameError, ProfileSetDisplayNameResponse, RoomCreateError, RoomCreateResponse, \
RoomInviteError, RoomInviteResponse, RoomKickError, RoomKickResponse, RoomPreset, \
RoomPutStateError, RoomPutStateResponse, RoomResolveAliasResponse, RoomVisibility, TransferMonitor, \
UploadError, UploadResponse
class Matrix: class Matrix:
@ -18,7 +22,7 @@ class Matrix:
_device_id: str = None _device_id: str = None
@classmethod @classmethod
async def _get_client(cls) -> Union[AsyncClient, "FakeMatrixClient"]: async def _get_client(cls) -> Union[AsyncClient, "FakeMatrixClient"]: # pragma: no cover
""" """
Retrieve the bot account. Retrieve the bot account.
If not logged, log in and store access token. If not logged, log in and store access token.
@ -129,7 +133,8 @@ class Matrix:
If left as ``None``, some servers might refuse the upload. If left as ``None``, some servers might refuse the upload.
""" """
client = await cls._get_client() 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 @classmethod
@async_to_sync @async_to_sync
@ -215,9 +220,7 @@ class Matrix:
""" """
client = await cls._get_client() client = await cls._get_client()
resp: RoomResolveAliasResponse = await client.room_resolve_alias(room_alias) resp: RoomResolveAliasResponse = await client.room_resolve_alias(room_alias)
if isinstance(resp, RoomResolveAliasResponse): return resp.room_id if isinstance(resp, RoomResolveAliasResponse) else None
return resp.room_id
return None
@classmethod @classmethod
@async_to_sync @async_to_sync
@ -263,7 +266,7 @@ class Matrix:
@classmethod @classmethod
@async_to_sync @async_to_sync
async def kick(cls, room_id: str, user_id: str, reason: str = None) -> Union[RoomKickResponse, RoomInviteError]: async def kick(cls, room_id: str, user_id: str, reason: str = None) -> Union[RoomKickResponse, RoomKickError]:
""" """
Kick a user from a room, or withdraw their invitation. Kick a user from a room, or withdraw their invitation.
@ -287,7 +290,7 @@ class Matrix:
@classmethod @classmethod
@async_to_sync @async_to_sync
async def set_room_power_level(cls, room_id: str, user_id: str, power_level: int)\ 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. Put a given power level to a user in a certain room.
@ -302,6 +305,9 @@ class Matrix:
power_level (int): The target power level to give. power_level (int): The target power level to give.
""" """
client = await cls._get_client() client = await cls._get_client()
if isinstance(client, FakeMatrixClient):
return RoomPutStateError("debug mode")
if room_id.startswith("#"): if room_id.startswith("#"):
room_id = await cls.resolve_room_alias(room_id) room_id = await cls.resolve_room_alias(room_id)
resp = await client.room_get_state_event(room_id, "m.room.power_levels") resp = await client.room_get_state_event(room_id, "m.room.power_levels")
@ -312,7 +318,7 @@ class Matrix:
@classmethod @classmethod
@async_to_sync @async_to_sync
async def set_room_power_level_event(cls, room_id: str, event: str, power_level: int)\ 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 Define the minimal power level to have to send a certain event type
in a given room. in a given room.
@ -328,6 +334,9 @@ class Matrix:
power_level (int): The target power level to give. power_level (int): The target power level to give.
""" """
client = await cls._get_client() client = await cls._get_client()
if isinstance(client, FakeMatrixClient):
return RoomPutStateError("debug mode")
if room_id.startswith("#"): if room_id.startswith("#"):
room_id = await cls.resolve_room_alias(room_id) room_id = await cls.resolve_room_alias(room_id)
resp = await client.room_get_state_event(room_id, "m.room.power_levels") resp = await client.room_get_state_event(room_id, "m.room.power_levels")
@ -370,4 +379,3 @@ class FakeMatrixClient:
async def func(*_, **_2): async def func(*_, **_2):
return None return None
return func return func

View File

@ -1,8 +1,7 @@
from django.conf import settings
from django.contrib.auth.models import AnonymousUser, User
from threading import local from threading import local
from django.conf import settings
from django.contrib.auth.models import AnonymousUser, User
from django.contrib.sessions.backends.db import SessionStore from django.contrib.sessions.backends.db import SessionStore
USER_ATTR_NAME = getattr(settings, 'LOCAL_USER_ATTR_NAME', '_current_user') USER_ATTR_NAME = getattr(settings, 'LOCAL_USER_ATTR_NAME', '_current_user')
@ -22,19 +21,13 @@ def get_current_user() -> User:
return getattr(_thread_locals, USER_ATTR_NAME, None) 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: def get_current_ip() -> str:
return getattr(_thread_locals, IP_ATTR_NAME, None) return getattr(_thread_locals, IP_ATTR_NAME, None)
def get_current_authenticated_user(): def get_current_authenticated_user():
current_user = get_current_user() current_user = get_current_user()
if isinstance(current_user, AnonymousUser): return None if isinstance(current_user, AnonymousUser) else current_user
return None
return current_user
class SessionMiddleware(object): class SessionMiddleware(object):
@ -50,10 +43,7 @@ class SessionMiddleware(object):
request.user = User.objects.get(pk=request.session["_fake_user_id"]) request.user = User.objects.get(pk=request.session["_fake_user_id"])
user = request.user user = request.user
if 'HTTP_X_REAL_IP' in request.META: ip = request.META.get('HTTP_X_REAL_IP' if 'HTTP_X_REAL_IP' in request.META else 'REMOTE_ADDR')
ip = request.META.get('HTTP_X_REAL_IP')
else:
ip = request.META.get('REMOTE_ADDR')
_set_current_user_and_ip(user, request.session, ip) _set_current_user_and_ip(user, request.session, ip)
response = self.get_response(request) response = self.get_response(request)
@ -62,7 +52,7 @@ class SessionMiddleware(object):
return response return response
class TurbolinksMiddleware(object): class TurbolinksMiddleware(object): # pragma: no cover
""" """
Send the `Turbolinks-Location` header in response to a visit that was redirected, Send the `Turbolinks-Location` header in response to a visit that was redirected,
and Turbolinks will replace the browser's topmost history entry. and Turbolinks will replace the browser's topmost history entry.

View File

@ -89,8 +89,7 @@ LOGIN_REDIRECT_URL = "index"
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'corres2math/templates')] 'DIRS': [os.path.join(BASE_DIR, 'corres2math/templates')],
,
'APP_DIRS': True, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [
@ -196,7 +195,7 @@ HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
_db_type = os.getenv('DJANGO_DB_TYPE', 'sqlite').lower() _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 = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.mysql' if _db_type == 'mysql' else 'django.db.backends.postgresql_psycopg2', 'ENGINE': 'django.db.backends.mysql' if _db_type == 'mysql' else 'django.db.backends.postgresql_psycopg2',
@ -215,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 * from .settings_prod import * # noqa: F401,F403
else: else:
from .settings_dev import * from .settings_dev import * # noqa: F401,F403

24
corres2math/tests.py Normal file
View File

@ -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)

View File

@ -14,8 +14,8 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from django.contrib import admin from django.contrib import admin
from django.urls import path, include from django.urls import include, path
from django.views.defaults import bad_request, permission_denied, page_not_found, server_error from django.views.defaults import bad_request, page_not_found, permission_denied, server_error
from django.views.generic import TemplateView from django.views.generic import TemplateView
from registration.views import PhotoAuthorizationView from registration.views import PhotoAuthorizationView

View File

@ -12,7 +12,7 @@ deps =
-r{toxinidir}/requirements.txt -r{toxinidir}/requirements.txt
coverage coverage
commands = commands =
coverage run --omit='*migrations*,apps/scripts*' --source=apps ./manage.py test apps/ coverage run --source=apps,corres2math ./manage.py test apps/ corres2math/
coverage report -m coverage report -m
[testenv:linters] [testenv:linters]
@ -25,7 +25,7 @@ deps =
pep8-naming pep8-naming
pyflakes pyflakes
commands = commands =
flake8 apps/ flake8 apps/ corres2math/
[flake8] [flake8]
exclude = exclude =