mirror of
https://github.com/ae-utbm/sith.git
synced 2024-11-22 14:13:21 +00:00
commit
79a6d9e771
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@ -21,6 +21,10 @@ jobs:
|
|||||||
tests:
|
tests:
|
||||||
name: Run tests and generate coverage report
|
name: Run tests and generate coverage report
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false # don't interrupt the other test processes
|
||||||
|
matrix:
|
||||||
|
pytest-mark: [slow, not slow]
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repository
|
- name: Check out repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@ -28,7 +32,7 @@ jobs:
|
|||||||
- uses: ./.github/actions/setup_xapian
|
- uses: ./.github/actions/setup_xapian
|
||||||
- uses: ./.github/actions/compile_messages
|
- uses: ./.github/actions/compile_messages
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: poetry run coverage run -m pytest
|
run: poetry run coverage run -m pytest -m "${{ matrix.pytest-mark }}"
|
||||||
- name: Generate coverage report
|
- name: Generate coverage report
|
||||||
run: |
|
run: |
|
||||||
poetry run coverage report
|
poetry run coverage report
|
||||||
|
@ -6,7 +6,7 @@ from django.utils.translation import activate
|
|||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def django_db_setup(django_db_setup, django_db_blocker):
|
def django_db_setup(django_db_setup, django_db_blocker):
|
||||||
with django_db_blocker.unblock():
|
with django_db_blocker.unblock():
|
||||||
call_command("setup")
|
call_command("populate")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
|
@ -1098,8 +1098,10 @@ Welcome to the wiki page!
|
|||||||
n = News(
|
n = News(
|
||||||
title="Repas barman",
|
title="Repas barman",
|
||||||
summary="Enjoy la fin du semestre!",
|
summary="Enjoy la fin du semestre!",
|
||||||
content="Viens donc t'enjailler avec les autres barmans aux "
|
content=(
|
||||||
"frais du BdF! \o/",
|
"Viens donc t'enjailler avec les autres barmans aux "
|
||||||
|
"frais du BdF! \\o/"
|
||||||
|
),
|
||||||
type="EVENT",
|
type="EVENT",
|
||||||
club=bar_club,
|
club=bar_club,
|
||||||
author=subscriber,
|
author=subscriber,
|
||||||
|
@ -19,7 +19,8 @@ import base64
|
|||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
from datetime import date, datetime, timedelta, timezone
|
from datetime import date, datetime, timedelta
|
||||||
|
from datetime import timezone as tz
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
from dict2xml import dict2xml
|
from dict2xml import dict2xml
|
||||||
@ -549,7 +550,7 @@ class Counter(models.Model):
|
|||||||
if since is None:
|
if since is None:
|
||||||
since = get_start_of_semester()
|
since = get_start_of_semester()
|
||||||
if isinstance(since, date):
|
if isinstance(since, date):
|
||||||
since = datetime(since.year, since.month, since.day, tzinfo=timezone.utc)
|
since = datetime(since.year, since.month, since.day, tzinfo=tz.utc)
|
||||||
return (
|
return (
|
||||||
self.sellings.filter(date__gte=since)
|
self.sellings.filter(date__gte=since)
|
||||||
.annotate(
|
.annotate(
|
||||||
@ -583,7 +584,7 @@ class Counter(models.Model):
|
|||||||
if since is None:
|
if since is None:
|
||||||
since = get_start_of_semester()
|
since = get_start_of_semester()
|
||||||
if isinstance(since, date):
|
if isinstance(since, date):
|
||||||
since = datetime(since.year, since.month, since.day, tzinfo=timezone.utc)
|
since = datetime(since.year, since.month, since.day, tzinfo=tz.utc)
|
||||||
total = self.sellings.filter(date__gte=since).aggregate(
|
total = self.sellings.filter(date__gte=since).aggregate(
|
||||||
total=Sum(F("quantity") * F("unit_price"), output_field=CurrencyField())
|
total=Sum(F("quantity") * F("unit_price"), output_field=CurrencyField())
|
||||||
)["total"]
|
)["total"]
|
||||||
|
@ -207,6 +207,29 @@ Pour lancer les tests il suffit d'utiliser la commande intégrée à django.
|
|||||||
# Lancer une méthode en particulier de cette même classe
|
# Lancer une méthode en particulier de cette même classe
|
||||||
pytest core.tests.UserRegistrationTest.test_register_user_form_ok
|
pytest core.tests.UserRegistrationTest.test_register_user_form_ok
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Certains tests sont un peu longs à tourner.
|
||||||
|
Pour ne faire tourner que les tests les plus rapides,
|
||||||
|
vous pouvez exécutez pytest ainsi :
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
pytest -m "not slow"
|
||||||
|
|
||||||
|
# vous pouvez toujours faire comme au-dessus
|
||||||
|
pytest core -m "not slow"
|
||||||
|
|
||||||
|
A l'inverse, vous pouvez ne faire tourner que les tests
|
||||||
|
lents en remplaçant `-m "not slow"` par `-m slow`.
|
||||||
|
|
||||||
|
De cette manière, votre processus de développement
|
||||||
|
devrait être un peu plus fluide.
|
||||||
|
Cependant, n'oubliez pas de bien faire tourner
|
||||||
|
tous les tests avant de push un commit.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Vérifier les dépendances Javascript
|
Vérifier les dépendances Javascript
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
|
@ -220,10 +220,11 @@ class EbouticTest(TestCase):
|
|||||||
self.client.get(reverse("eboutic:command"))
|
self.client.get(reverse("eboutic:command"))
|
||||||
response = self.client.get(et_answer_url)
|
response = self.client.get(et_answer_url)
|
||||||
assert response.status_code == 500
|
assert response.status_code == 500
|
||||||
assert (
|
msg = (
|
||||||
"Basket processing failed with error: SuspiciousOperation('Basket total and amount do not match'"
|
"Basket processing failed with error: "
|
||||||
in response.content.decode("utf-8"),
|
"SuspiciousOperation('Basket total and amount do not match'"
|
||||||
)
|
)
|
||||||
|
assert msg in response.content.decode("utf-8")
|
||||||
|
|
||||||
def test_buy_simple_product_with_credit_card(self):
|
def test_buy_simple_product_with_credit_card(self):
|
||||||
self.client.force_login(self.subscriber)
|
self.client.force_login(self.subscriber)
|
||||||
|
@ -128,7 +128,7 @@ class Command(BaseCommand):
|
|||||||
self.clubs.append(Club(unix_name=f"galaxy-club-{i}", name=f"club-{i}"))
|
self.clubs.append(Club(unix_name=f"galaxy-club-{i}", name=f"club-{i}"))
|
||||||
# We don't need to create corresponding groups here, as the Galaxy doesn't care about them
|
# We don't need to create corresponding groups here, as the Galaxy doesn't care about them
|
||||||
Club.objects.bulk_create(self.clubs)
|
Club.objects.bulk_create(self.clubs)
|
||||||
self.clubs = Club.objects.filter(unix_name__startswith="galaxy-").all()
|
self.clubs = list(Club.objects.filter(unix_name__startswith="galaxy-").all())
|
||||||
|
|
||||||
def make_users(self):
|
def make_users(self):
|
||||||
"""
|
"""
|
||||||
@ -147,20 +147,20 @@ class Command(BaseCommand):
|
|||||||
self.logger.info(f"Creating {u}")
|
self.logger.info(f"Creating {u}")
|
||||||
self.users.append(u)
|
self.users.append(u)
|
||||||
User.objects.bulk_create(self.users)
|
User.objects.bulk_create(self.users)
|
||||||
self.users = User.objects.filter(username__startswith="galaxy-").all()
|
self.users = list(User.objects.filter(username__startswith="galaxy-").all())
|
||||||
|
|
||||||
# now that users are created, create their subscription
|
# now that users are created, create their subscription
|
||||||
subs = []
|
subs = []
|
||||||
for i in range(self.NB_USERS):
|
end = Subscription.compute_end(duration=2)
|
||||||
u = self.users[i]
|
for i, user in enumerate(self.users):
|
||||||
self.logger.info(f"Registering {u}")
|
self.logger.info(f"Registering {user}")
|
||||||
subs.append(
|
subs.append(
|
||||||
Subscription(
|
Subscription(
|
||||||
member=u,
|
member=user,
|
||||||
subscription_start=Subscription.compute_start(
|
subscription_start=Subscription.compute_start(
|
||||||
self.now - timedelta(days=self.NB_USERS - i)
|
self.now - timedelta(days=self.NB_USERS - i)
|
||||||
),
|
),
|
||||||
subscription_end=Subscription.compute_end(duration=2),
|
subscription_end=end,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
Subscription.objects.bulk_create(subs)
|
Subscription.objects.bulk_create(subs)
|
||||||
@ -175,20 +175,22 @@ class Command(BaseCommand):
|
|||||||
Then it will take 14 other citizen among the previous 200
|
Then it will take 14 other citizen among the previous 200
|
||||||
(godfathers are usually older), and apply another
|
(godfathers are usually older), and apply another
|
||||||
heuristic to determine whether they should have a family link
|
heuristic to determine whether they should have a family link
|
||||||
|
It will result in approximately 40% of users having at least one godchild
|
||||||
|
and 70% having at least one godfather.
|
||||||
"""
|
"""
|
||||||
if self.users is None:
|
if self.users is None:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"The `make_users()` method must be called before `make_families()`"
|
"The `make_users()` method must be called before `make_families()`"
|
||||||
)
|
)
|
||||||
for i in range(200, self.NB_USERS):
|
|
||||||
godfathers = []
|
godfathers = []
|
||||||
|
for i in range(200, self.NB_USERS):
|
||||||
for j in range(i - 200, i, 14): # this will loop 14 times (14² = 196)
|
for j in range(i - 200, i, 14): # this will loop 14 times (14² = 196)
|
||||||
if (i / 10) % 10 == (i + j) % 10:
|
if (i // 10) % 10 == (i + j) % 10:
|
||||||
u1 = self.users[i]
|
u1 = self.users[i]
|
||||||
u2 = self.users[j]
|
u2 = self.users[j]
|
||||||
|
godfathers.append(User.godfathers.through(from_user=u1, to_user=u2))
|
||||||
self.logger.info(f"Making {u2} the godfather of {u1}")
|
self.logger.info(f"Making {u2} the godfather of {u1}")
|
||||||
godfathers.append(u2)
|
User.godfathers.through.objects.bulk_create(godfathers)
|
||||||
u1.godfathers.set(godfathers)
|
|
||||||
|
|
||||||
def make_club_memberships(self):
|
def make_club_memberships(self):
|
||||||
"""
|
"""
|
||||||
@ -295,7 +297,7 @@ class Command(BaseCommand):
|
|||||||
self.picts[i].compressed.name = self.picts[i].name
|
self.picts[i].compressed.name = self.picts[i].name
|
||||||
self.picts[i].thumbnail.name = self.picts[i].name
|
self.picts[i].thumbnail.name = self.picts[i].name
|
||||||
Picture.objects.bulk_create(self.picts)
|
Picture.objects.bulk_create(self.picts)
|
||||||
self.picts = Picture.objects.filter(name__startswith="galaxy-").all()
|
self.picts = list(Picture.objects.filter(name__startswith="galaxy-").all())
|
||||||
|
|
||||||
def make_pictures_memberships(self):
|
def make_pictures_memberships(self):
|
||||||
"""
|
"""
|
||||||
@ -377,32 +379,24 @@ class Command(BaseCommand):
|
|||||||
u1 = self.users[uid]
|
u1 = self.users[uid]
|
||||||
u2 = self.users[uid - 100]
|
u2 = self.users[uid - 100]
|
||||||
u3 = self.users[uid + 100]
|
u3 = self.users[uid + 100]
|
||||||
u1.godfathers.add(u2)
|
User.godfathers.through.objects.bulk_create(
|
||||||
u1.godchildren.add(u3)
|
[
|
||||||
|
User.godfathers.through(from_user=u1, to_user=u2),
|
||||||
|
User.godfathers.through(from_user=u3, to_user=u2),
|
||||||
|
],
|
||||||
|
ignore_conflicts=True, # in case a relationship has already been created
|
||||||
|
)
|
||||||
self.logger.info(f"{u1} will be important and close to {u2} and {u3}")
|
self.logger.info(f"{u1} will be important and close to {u2} and {u3}")
|
||||||
pictures_tags = []
|
pictures_tags = []
|
||||||
for p in range( # Mix them with other citizen for more chaos
|
for p in range(uid - 400, uid - 200):
|
||||||
uid - 400, uid - 200
|
# Mix them with other citizen for more chaos
|
||||||
):
|
pictures_tags += [
|
||||||
# users may already be on the pictures
|
PeoplePictureRelation(user=u1, picture=self.picts[p]),
|
||||||
if not self.picts[p].people.filter(user=u1).exists():
|
PeoplePictureRelation(user=u2, picture=self.picts[p]),
|
||||||
pictures_tags.append(
|
PeoplePictureRelation(user=u1, picture=self.picts[p + self.NB_USERS]),
|
||||||
PeoplePictureRelation(user=u1, picture=self.picts[p])
|
PeoplePictureRelation(user=u2, picture=self.picts[p + self.NB_USERS]),
|
||||||
)
|
]
|
||||||
if not self.picts[p].people.filter(user=u2).exists():
|
# users may already be on the pictures.
|
||||||
pictures_tags.append(
|
# In this case the conflict will just be ignored
|
||||||
PeoplePictureRelation(user=u2, picture=self.picts[p])
|
# and nothing will happen for this entry
|
||||||
)
|
PeoplePictureRelation.objects.bulk_create(pictures_tags, ignore_conflicts=True)
|
||||||
if not self.picts[p + self.NB_USERS].people.filter(user=u1).exists():
|
|
||||||
pictures_tags.append(
|
|
||||||
PeoplePictureRelation(
|
|
||||||
user=u1, picture=self.picts[p + self.NB_USERS]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if not self.picts[p + self.NB_USERS].people.filter(user=u2).exists():
|
|
||||||
pictures_tags.append(
|
|
||||||
PeoplePictureRelation(
|
|
||||||
user=u2, picture=self.picts[p + self.NB_USERS]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
PeoplePictureRelation.objects.bulk_create(pictures_tags)
|
|
||||||
|
File diff suppressed because one or more lines are too long
@ -25,6 +25,7 @@
|
|||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@ -147,6 +148,7 @@ class GalaxyTestModel(TestCase):
|
|||||||
galaxy.rule(0) # We want everybody here
|
galaxy.rule(0) # We want everybody here
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.slow
|
||||||
class GalaxyTestView(TestCase):
|
class GalaxyTestView(TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -196,6 +198,4 @@ class GalaxyTestView(TestCase):
|
|||||||
# Dump computed state, either for easier debugging, or to copy as new reference if changes are legit
|
# Dump computed state, either for easier debugging, or to copy as new reference if changes are legit
|
||||||
(galaxy_dir / "test_galaxy_state.json").write_text(json.dumps(state))
|
(galaxy_dir / "test_galaxy_state.json").write_text(json.dumps(state))
|
||||||
|
|
||||||
assert (
|
assert state == json.loads((galaxy_dir / "ref_galaxy_state.json").read_text())
|
||||||
state == json.loads((galaxy_dir / "ref_galaxy_state.json").read_text()),
|
|
||||||
)
|
|
||||||
|
10
poetry.lock
generated
10
poetry.lock
generated
@ -573,17 +573,17 @@ test = ["testfixtures"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "djangorestframework"
|
name = "djangorestframework"
|
||||||
version = "3.15.1"
|
version = "3.15.2"
|
||||||
description = "Web APIs for Django, made easy."
|
description = "Web APIs for Django, made easy."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "djangorestframework-3.15.1-py3-none-any.whl", hash = "sha256:3ccc0475bce968608cf30d07fb17d8e52d1d7fc8bfe779c905463200750cbca6"},
|
{file = "djangorestframework-3.15.2-py3-none-any.whl", hash = "sha256:2b8871b062ba1aefc2de01f773875441a961fefbf79f5eed1e32b2f096944b20"},
|
||||||
{file = "djangorestframework-3.15.1.tar.gz", hash = "sha256:f88fad74183dfc7144b2756d0d2ac716ea5b4c7c9840995ac3bfd8ec034333c1"},
|
{file = "djangorestframework-3.15.2.tar.gz", hash = "sha256:36fe88cd2d6c6bec23dca9804bab2ba5517a8bb9d8f47ebc68981b56840107ad"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
django = ">=3.0"
|
django = ">=4.2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "docutils"
|
name = "docutils"
|
||||||
|
@ -76,6 +76,7 @@ select = ["I", "F401"]
|
|||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
DJANGO_SETTINGS_MODULE = "sith.settings"
|
DJANGO_SETTINGS_MODULE = "sith.settings"
|
||||||
python_files = ["tests.py", "test_*.py", "*_tests.py"]
|
python_files = ["tests.py", "test_*.py", "*_tests.py"]
|
||||||
|
markers = ["slow"]
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=1.0.0"]
|
requires = ["poetry-core>=1.0.0"]
|
||||||
|
@ -72,7 +72,7 @@ class MergeUserTest(TestCase):
|
|||||||
assert "B'ian" == self.to_keep.nick_name
|
assert "B'ian" == self.to_keep.nick_name
|
||||||
assert "Jerusalem" == self.to_keep.address
|
assert "Jerusalem" == self.to_keep.address
|
||||||
assert "Rome" == self.to_keep.parent_address
|
assert "Rome" == self.to_keep.parent_address
|
||||||
assert (3, self.to_keep.groups.count())
|
assert self.to_keep.groups.count() == 3
|
||||||
groups = sorted(self.to_keep.groups.all(), key=lambda i: i.id)
|
groups = sorted(self.to_keep.groups.all(), key=lambda i: i.id)
|
||||||
expected = sorted([subscribers, mde_admin, sas_admin], key=lambda i: i.id)
|
expected = sorted([subscribers, mde_admin, sas_admin], key=lambda i: i.id)
|
||||||
assert groups == expected
|
assert groups == expected
|
||||||
|
@ -696,6 +696,17 @@ if DEBUG:
|
|||||||
|
|
||||||
if TESTING:
|
if TESTING:
|
||||||
CAPTCHA_TEST_MODE = True
|
CAPTCHA_TEST_MODE = True
|
||||||
|
PASSWORD_HASHERS = [ # not secure, but faster password hasher
|
||||||
|
"django.contrib.auth.hashers.MD5PasswordHasher",
|
||||||
|
]
|
||||||
|
STORAGES = { # store files in memory rather than using the hard drive
|
||||||
|
"default": {
|
||||||
|
"BACKEND": "django.core.files.storage.InMemoryStorage",
|
||||||
|
},
|
||||||
|
"staticfiles": {
|
||||||
|
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
if SENTRY_DSN:
|
if SENTRY_DSN:
|
||||||
# Connection to sentry
|
# Connection to sentry
|
||||||
|
Loading…
Reference in New Issue
Block a user