add tests

This commit is contained in:
thomas girod 2024-07-22 19:12:03 +02:00
parent b9d19be183
commit cb1aa8bef0
13 changed files with 332 additions and 160 deletions

32
core/baker_recipes.py Normal file
View File

@ -0,0 +1,32 @@
from datetime import timedelta
from django.utils.timezone import now
from model_bakery import seq
from model_bakery.recipe import Recipe, related
from core.models import User
from subscription.models import Subscription
active_subscription = Recipe(
Subscription,
subscription_start=now() - timedelta(days=30),
subscription_end=now() + timedelta(days=30),
)
ended_subscription = Recipe(
Subscription,
subscription_start=now() - timedelta(days=60),
subscription_end=now() - timedelta(days=30),
)
subscriber_user = Recipe(
User,
first_name="subscriber",
last_name=seq("user "),
subscriptions=related(active_subscription),
)
old_subscriber_user = Recipe(
User,
first_name="old subscriber",
last_name=seq("user "),
subscriptions=related(ended_subscription),
)

View File

@ -13,7 +13,7 @@ from pedagogy.schemas import SimpleUvSchema, UvFilterSchema, UvSchema
from pedagogy.utbm_api import find_uv from pedagogy.utbm_api import find_uv
@api_controller("/uv", permissions=[IsSubscriber]) @api_controller("/uv")
class UvController(ControllerBase): class UvController(ControllerBase):
@route.get( @route.get(
"/{year}/{code}", "/{year}/{code}",
@ -31,7 +31,10 @@ class UvController(ControllerBase):
return res return res
@route.get( @route.get(
"", response=PaginatedResponseSchema[SimpleUvSchema], url_name="fetch_uvs" "",
response=PaginatedResponseSchema[SimpleUvSchema],
url_name="fetch_uvs",
permissions=[IsSubscriber | IsInGroup(settings.SITH_GROUP_PEDAGOGY_ADMIN_ID)],
) )
@paginate(PageNumberPaginationExtra, page_size=100) @paginate(PageNumberPaginationExtra, page_size=100)
def fetch_uv_list(self, search: Query[UvFilterSchema]): def fetch_uv_list(self, search: Query[UvFilterSchema]):

View File

@ -123,7 +123,7 @@ class UvFilterSchema(FilterSchema):
def filter_semester(self, value: set[str] | None) -> Q: def filter_semester(self, value: set[str] | None) -> Q:
"""Special filter for the semester. """Special filter for the semester.
If both "SPRING" and "AUTUMN" are given, UV that are available If either "SPRING" or "AUTUMN" is given, UV that are available
during "AUTUMN_AND_SPRING" will be filtered. during "AUTUMN_AND_SPRING" will be filtered.
""" """
if not value: if not value:

View File

@ -125,8 +125,6 @@
function update_query_string(key, value) { function update_query_string(key, value) {
const url = new URL(window.location.href); const url = new URL(window.location.href);
console.log(value)
console.log(!!value)
if (!value) { if (!value) {
url.searchParams.delete(key) url.searchParams.delete(key)
} else if (Array.isArray(value)) { } else if (Array.isArray(value)) {

View File

165
pedagogy/tests/test_api.py Normal file
View File

@ -0,0 +1,165 @@
import json
from django.conf import settings
from django.test import TestCase
from django.urls import reverse
from model_bakery import baker
from model_bakery.recipe import Recipe
from core.baker_recipes import subscriber_user
from core.models import RealGroup, User
from pedagogy.models import UV
class UVSearchTest(TestCase):
"""Test UV guide rights for view and API."""
@classmethod
def setUpTestData(cls):
cls.root = User.objects.get(username="root")
cls.url = reverse("api:fetch_uvs")
uv_recipe = Recipe(UV, author=cls.root)
uvs = [
uv_recipe.prepare(
code="AP4A", credit_type="CS", semester="AUTUMN", department="GI"
),
uv_recipe.prepare(
code="MT01", credit_type="CS", semester="AUTUMN", department="TC"
),
uv_recipe.prepare(
code="PHYS11", credit_type="CS", semester="AUTUMN", department="TC"
),
uv_recipe.prepare(
code="TNEV", credit_type="TM", semester="SPRING", department="TC"
),
uv_recipe.prepare(
code="MT10", credit_type="TM", semester="AUTUMN", department="IMSI"
),
uv_recipe.prepare(
code="DA50",
credit_type="TM",
semester="AUTUMN_AND_SPRING",
department="GI",
),
]
UV.objects.bulk_create(uvs)
def test_permissions(self):
# Test with anonymous user
response = self.client.get(self.url)
assert response.status_code == 403
# Test with not subscribed user
self.client.force_login(baker.make(User))
response = self.client.get(self.url)
assert response.status_code == 403
for user in (
self.root,
subscriber_user.make(),
baker.make(
User,
groups=[
RealGroup.objects.get(pk=settings.SITH_GROUP_PEDAGOGY_ADMIN_ID)
],
),
):
# users that have right
with self.subTest():
self.client.force_login(user)
response = self.client.get(self.url)
assert response.status_code == 200
def test_format(self):
"""Test that the return data format is correct"""
self.client.force_login(self.root)
res = self.client.get(self.url + "?search=PA00")
uv = UV.objects.get(code="PA00")
assert res.status_code == 200
assert json.loads(res.content) == {
"count": 1,
"next": None,
"previous": None,
"results": [
{
"id": uv.id,
"title": uv.title,
"code": uv.code,
"credit_type": uv.credit_type,
"semester": uv.semester,
"department": uv.department,
}
],
}
def test_search_by_code(self):
self.client.force_login(self.root)
res = self.client.get(self.url + "?search=MT")
assert res.status_code == 200
assert {uv["code"] for uv in json.loads(res.content)["results"]} == {
"MT01",
"MT10",
}
def test_search_by_credit_type(self):
self.client.force_login(self.root)
res = self.client.get(self.url + "?credit_type=CS")
assert res.status_code == 200
codes = [uv["code"] for uv in json.loads(res.content)["results"]]
assert codes == ["AP4A", "MT01", "PHYS11"]
res = self.client.get(self.url + "?credit_type=CS&credit_type=OM")
assert res.status_code == 200
codes = {uv["code"] for uv in json.loads(res.content)["results"]}
assert codes == {"AP4A", "MT01", "PHYS11", "PA00"}
def test_search_by_semester(self):
self.client.force_login(self.root)
res = self.client.get(self.url + "?semester=SPRING")
assert res.status_code == 200
codes = {uv["code"] for uv in json.loads(res.content)["results"]}
assert codes == {"DA50", "TNEV", "PA00"}
def test_search_multiple_filters(self):
self.client.force_login(self.root)
res = self.client.get(
self.url + "?semester=AUTUMN&credit_type=CS&department=TC"
)
assert res.status_code == 200
codes = {uv["code"] for uv in json.loads(res.content)["results"]}
assert codes == {"MT01", "PHYS11"}
def test_search_fails(self):
self.client.force_login(self.root)
res = self.client.get(self.url + "?credit_type=CS&search=DA")
assert res.status_code == 200
assert json.loads(res.content)["results"] == []
def test_search_pa00_fail(self):
self.client.force_login(self.root)
# Search with UV code
response = self.client.get(reverse("pedagogy:guide"), {"search": "IFC"})
self.assertNotContains(response, text="PA00")
# Search with first letter of UV code
response = self.client.get(reverse("pedagogy:guide"), {"search": "I"})
self.assertNotContains(response, text="PA00")
# Search with UV manager
response = self.client.get(reverse("pedagogy:guide"), {"search": "GILLES"})
self.assertNotContains(response, text="PA00")
# Search with department
response = self.client.get(reverse("pedagogy:guide"), {"department": "TC"})
self.assertNotContains(response, text="PA00")
# Search with semester
response = self.client.get(reverse("pedagogy:guide"), {"semester": "CLOSED"})
self.assertNotContains(response, text="PA00")
# Search with language
response = self.client.get(reverse("pedagogy:guide"), {"language": "EN"})
self.assertNotContains(response, text="PA00")
# Search with credit type
response = self.client.get(reverse("pedagogy:guide"), {"credit_type": "TM"})
self.assertNotContains(response, text="PA00")

View File

@ -20,7 +20,6 @@
# Place - Suite 330, Boston, MA 02111-1307, USA. # Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #
import json
import pytest import pytest
from django.conf import settings from django.conf import settings
@ -555,157 +554,6 @@ class UVCommentUpdateTest(TestCase):
self.assertEqual(self.comment.author, self.krophil) self.assertEqual(self.comment.author, self.krophil)
class UVSearchTest(TestCase):
"""Test UV guide rights for view and API."""
@classmethod
def setUpTestData(cls):
cls.bibou = User.objects.get(username="root")
cls.tutu = User.objects.get(username="tutu")
cls.sli = User.objects.get(username="sli")
cls.guy = User.objects.get(username="guy")
cls.url = reverse("api:fetch_uvs")
uvs = [
UV(code="AP4A", credit_type="CS", semester="AUTUMN", department="GI"),
UV(code="MT01", credit_type="CS", semester="AUTUMN", department="TC"),
UV(code="PHYS11", credit_type="CS", semester="AUTUMN", department="TC"),
UV(code="TNEV", credit_type="TM", semester="SPRING", department="TC"),
UV(code="MT10", credit_type="TM", semester="AUTUMN", department="IMSI"),
UV(
code="DA50",
credit_type="TM",
semester="AUTUMN_AND_SPRING",
department="GI",
),
]
for uv in uvs:
uv.author = cls.bibou
uv.title = ""
uv.manager = ""
uv.language = "FR"
uv.objectives = ""
uv.program = ""
uv.skills = ""
uv.key_concepts = ""
uv.credits = 6
UV.objects.bulk_create(uvs)
def fetch_uvs(self, **kwargs):
params = "&".join(f"{key}={val}" for key, val in kwargs.items())
return json.loads(f"{self.url}?{params}")
def test_permissions(self):
# Test with anonymous user
response = self.client.get(self.url)
assert response.status_code == 403
# Test with not subscribed user
self.client.force_login(self.guy)
response = self.client.get(self.url)
assert response.status_code == 403
for user in self.bibou, self.tutu, self.sli:
# users that have right
with self.subTest():
self.client.force_login(user)
response = self.client.get(self.url)
assert response.status_code == 200
def test_format(self):
"""Test that the return data format is correct"""
self.client.force_login(self.bibou)
res = self.client.get(self.url + "?search=PA00")
uv = UV.objects.get(code="PA00")
assert res.status_code == 200
assert json.loads(res.content) == {
"count": 1,
"next": None,
"previous": None,
"results": [
{
"id": uv.id,
"title": uv.title,
"code": uv.code,
"credit_type": uv.credit_type,
"semester": uv.semester,
"department": uv.department,
}
],
}
def test_search_by_code(self):
self.client.force_login(self.bibou)
res = self.client.get(self.url + "?search=MT")
assert res.status_code == 200
assert {uv["code"] for uv in json.loads(res.content)["results"]} == {
"MT01",
"MT10",
}
def test_search_by_credit_type(self):
self.client.force_login(self.bibou)
res = self.client.get(self.url + "?credit_type=CS")
assert res.status_code == 200
codes = [uv["code"] for uv in json.loads(res.content)["results"]]
assert codes == ["AP4A", "MT01", "PHYS11"]
res = self.client.get(self.url + "?credit_type=CS&credit_type=OM")
assert res.status_code == 200
codes = {uv["code"] for uv in json.loads(res.content)["results"]}
assert codes == {"AP4A", "MT01", "PHYS11", "PA00"}
def test_search_by_semester(self):
self.client.force_login(self.bibou)
res = self.client.get(self.url + "?semester=SPRING")
assert res.status_code == 200
codes = {uv["code"] for uv in json.loads(res.content)["results"]}
assert codes == {"DA50", "TNEV", "PA00"}
def test_search_multiple_filters(self):
self.client.force_login(self.bibou)
res = self.client.get(
self.url + "?semester=AUTUMN&credit_type=CS&department=TC"
)
assert res.status_code == 200
codes = {uv["code"] for uv in json.loads(res.content)["results"]}
assert codes == {"MT01", "PHYS11"}
def test_search_fails(self):
self.client.force_login(self.bibou)
res = self.client.get(self.url + "?credit_type=CS&search=DA")
assert res.status_code == 200
assert json.loads(res.content)["results"] == []
def test_search_pa00_fail(self):
self.client.force_login(self.bibou)
# Search with UV code
response = self.client.get(reverse("pedagogy:guide"), {"search": "IFC"})
self.assertNotContains(response, text="PA00")
# Search with first letter of UV code
response = self.client.get(reverse("pedagogy:guide"), {"search": "I"})
self.assertNotContains(response, text="PA00")
# Search with UV manager
response = self.client.get(reverse("pedagogy:guide"), {"search": "GILLES"})
self.assertNotContains(response, text="PA00")
# Search with department
response = self.client.get(reverse("pedagogy:guide"), {"department": "TC"})
self.assertNotContains(response, text="PA00")
# Search with semester
response = self.client.get(reverse("pedagogy:guide"), {"semester": "CLOSED"})
self.assertNotContains(response, text="PA00")
# Search with language
response = self.client.get(reverse("pedagogy:guide"), {"language": "EN"})
self.assertNotContains(response, text="PA00")
# Search with credit type
response = self.client.get(reverse("pedagogy:guide"), {"credit_type": "TM"})
self.assertNotContains(response, text="PA00")
class UVModerationFormTest(TestCase): class UVModerationFormTest(TestCase):
"""Assert access rights and if the form works well.""" """Assert access rights and if the form works well."""

20
poetry.lock generated
View File

@ -1225,6 +1225,24 @@ files = [
griffe = ">=0.47" griffe = ">=0.47"
mkdocstrings = ">=0.25" mkdocstrings = ">=0.25"
[[package]]
name = "model-bakery"
version = "1.18.2"
description = "Smart object creation facility for Django."
optional = false
python-versions = ">=3.8"
files = [
{file = "model_bakery-1.18.2-py3-none-any.whl", hash = "sha256:fd13a251d20db78b790d80f75350a73af5d199e5151227b5dd35cb76f2f08fe8"},
{file = "model_bakery-1.18.2.tar.gz", hash = "sha256:8f8ab4ba26a206ed848da9b1740b5006b5eeca8a67389efb28dbff37b362e802"},
]
[package.dependencies]
django = ">=4.2"
[package.extras]
docs = ["myst-parser", "sphinx", "sphinx-rtd-theme"]
test = ["black", "coverage", "mypy", "pillow", "pytest", "pytest-django", "ruff"]
[[package]] [[package]]
name = "nodeenv" name = "nodeenv"
version = "1.9.1" version = "1.9.1"
@ -2454,4 +2472,4 @@ filelock = ">=3.4"
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.10" python-versions = "^3.10"
content-hash = "ee0b881719f6834880266d72272429708e781b3ccd34a0fbf3e8b4119dcb95fd" content-hash = "f8e48947d004d61d63a345d36d7b42777030e1ac3687bb27f97b2c51318fcc8d"

View File

@ -67,6 +67,7 @@ freezegun = "^1.2.2" # used to test time-dependent code
pytest = "^8.2.2" pytest = "^8.2.2"
pytest-cov = "^5.0.0" pytest-cov = "^5.0.0"
pytest-django = "^4.8.0" pytest-django = "^4.8.0"
model-bakery = "^1.18.2"
# deps used to work on the documentation # deps used to work on the documentation
[tool.poetry.group.docs.dependencies] [tool.poetry.group.docs.dependencies]

View File

@ -33,8 +33,12 @@ class SasController(ControllerBase):
# User can view any moderated picture if he/she is subscribed. # User can view any moderated picture if he/she is subscribed.
# If not, he/she can view only the one he/she has been identified on # If not, he/she can view only the one he/she has been identified on
raise PermissionDenied raise PermissionDenied
pictures = filters.filter( pictures = list(
Picture.objects.filter(is_moderated=True, asked_for_removal=False) filters.filter(
Picture.objects.filter(is_moderated=True, asked_for_removal=False)
)
.distinct()
.order_by("-date")
) )
for picture in pictures: for picture in pictures:
picture.full_size_url = picture.get_download_url() picture.full_size_url = picture.get_download_url()

0
sas/tests/__init__.py Normal file
View File

103
sas/tests/test_api.py Normal file
View File

@ -0,0 +1,103 @@
from django.test import TestCase
from django.urls import reverse
from model_bakery import baker
from model_bakery.recipe import Recipe
from core.baker_recipes import old_subscriber_user, subscriber_user
from core.models import User
from sas.models import Album, PeoplePictureRelation, Picture
class SasTest(TestCase):
@classmethod
def setUpTestData(cls):
Picture.objects.all().delete()
owner = User.objects.get(username="root")
cls.user_a = old_subscriber_user.make()
cls.user_b, cls.user_c = subscriber_user.make(_quantity=2)
picture_recipe = Recipe(
Picture, is_in_sas=True, is_folder=False, owner=owner, is_moderated=True
)
cls.album_a = baker.make(Album, is_in_sas=True)
cls.album_b = baker.make(Album, is_in_sas=True)
for album in cls.album_a, cls.album_b:
pictures = picture_recipe.make(parent=album, _quantity=5, _bulk_create=True)
baker.make(PeoplePictureRelation, picture=pictures[1], user=cls.user_a)
baker.make(PeoplePictureRelation, picture=pictures[2], user=cls.user_a)
baker.make(PeoplePictureRelation, picture=pictures[2], user=cls.user_b)
baker.make(PeoplePictureRelation, picture=pictures[3], user=cls.user_b)
baker.make(PeoplePictureRelation, picture=pictures[4], user=cls.user_a)
baker.make(PeoplePictureRelation, picture=pictures[4], user=cls.user_b)
baker.make(PeoplePictureRelation, picture=pictures[4], user=cls.user_c)
def test_anonymous_user_forbidden(self):
res = self.client.get(reverse("api:pictures"))
assert res.status_code == 403
def test_filter_by_album(self):
self.client.force_login(self.user_b)
res = self.client.get(reverse("api:pictures") + f"?album_id={self.album_a.id}")
assert res.status_code == 200
expected = list(
self.album_a.children_pictures.order_by("-date").values_list(
"id", flat=True
)
)
assert [i["id"] for i in res.json()] == expected
def test_filter_by_user(self):
self.client.force_login(self.user_b)
res = self.client.get(
reverse("api:pictures") + f"?users_identified={self.user_a.id}"
)
assert res.status_code == 200
expected = list(
self.user_a.pictures.order_by("-picture__date").values_list(
"picture_id", flat=True
)
)
assert [i["id"] for i in res.json()] == expected
def test_filter_by_multiple_user(self):
self.client.force_login(self.user_b)
res = self.client.get(
reverse("api:pictures")
+ f"?users_identified={self.user_a.id}&users_identified={self.user_b.id}"
)
assert res.status_code == 200
expected = list(
self.user_a.pictures.union(self.user_b.pictures.all())
.order_by("-picture__date")
.values_list("picture_id", flat=True)
)
assert [i["id"] for i in res.json()] == expected
def test_not_subscribed_user(self):
"""Test that a user that is not subscribed can only its own pictures."""
self.client.force_login(self.user_a)
res = self.client.get(
reverse("api:pictures") + f"?users_identified={self.user_a.id}"
)
assert res.status_code == 200
expected = list(
self.user_a.pictures.order_by("-picture__date").values_list(
"picture_id", flat=True
)
)
assert [i["id"] for i in res.json()] == expected
# trying to access the pictures of someone else
res = self.client.get(
reverse("api:pictures") + f"?users_identified={self.user_b.id}"
)
assert res.status_code == 403
# trying to access the pictures of someone else shouldn't success,
# even if mixed with owned pictures
res = self.client.get(
reverse("api:pictures")
+ f"?users_identified={self.user_a.id}&users_identified={self.user_b.id}"
)
assert res.status_code == 403