Skip to content

Commit

Permalink
Improve decorators (#33)
Browse files Browse the repository at this point in the history
The original way I did it was just stupid, because I wanted to make it as little typing as possible.
  • Loading branch information
JasonGrace2282 authored May 27, 2024
1 parent 81485a8 commit 5c10d9d
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 105 deletions.
6 changes: 3 additions & 3 deletions tin/apps/assignments/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

from django.urls import reverse

from tin.tests import is_redirect, teacher
from tin.tests import is_redirect, login


@teacher
@login("teacher")
def test_create_folder(client, course) -> None:
response = client.post(
reverse("assignments:add_folder", args=[course.id]), {"name": "Fragment Shader"}
Expand All @@ -14,7 +14,7 @@ def test_create_folder(client, course) -> None:
assert course.folders.exists()


@teacher
@login("teacher")
def test_create_assignment(client, course) -> None:
data = {
"name": "Write a Vertex Shader",
Expand Down
6 changes: 3 additions & 3 deletions tin/apps/courses/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

from django.urls import reverse

from tin.tests import is_login_redirect, is_redirect, teacher
from tin.tests import is_login_redirect, is_redirect, login

from .models import Course


@teacher
@login("teacher")
def test_create_course(client, teacher) -> None:
course_name = "Foundations of CS"
response = client.post(
Expand All @@ -25,7 +25,7 @@ def test_create_course(client, teacher) -> None:
assert course.name == course_name


@teacher
@login("teacher")
def test_edit_course(client, course, teacher) -> None:
old_name = course.name
response = client.post(
Expand Down
116 changes: 17 additions & 99 deletions tin/tests/utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from __future__ import annotations

__all__ = ("admin", "teacher", "student")
__all__ = ("login",)

from typing import TYPE_CHECKING, Any, Callable, overload
from typing import TYPE_CHECKING, Any, Callable

import pytest

Expand All @@ -12,111 +12,29 @@
T = TypeVar("T", bound=object, default=None)
P = ParamSpec("P")

TestFunctionDecorator = Callable[[Callable[P, T]], Callable[P, T]]


# we need this function to take into account usage as
# @admin() or @admin
@overload
def apply_fixture(__f: Callable[P, T], prefix: str, **kwargs: Any) -> Callable[P, T]: ...


@overload
def apply_fixture(__f: None, prefix: str, **kwargs: Any) -> TestFunctionDecorator: ...


def apply_fixture(
__f: Callable[P, T] | None, /, prefix: str, **kwargs: Any
) -> Callable[P, T] | TestFunctionDecorator:
fixture = pytest.mark.usefixtures(prefix, **kwargs)
if __f is not None and callable(__f):
return fixture(__f)
return fixture


# We define overloads for each function
# because it looks nicer on hover


@overload
def admin(__f: Callable[P, T], /, **kwargs: Any) -> Callable[P, T]: ...


@overload
def admin(__f: None, /, **kwargs: Any) -> TestFunctionDecorator: ...


def admin(
__f: Callable[P, T] | None = None, /, **kwargs: Any
) -> Callable[P, T] | TestFunctionDecorator:
"""
Log in as an admin.
.. code-block:: python
@admin
def test_something(client):
# client is logged in as an admin
@admin(kwarg_for_usefixtures=xyz)
def test_something(client):
# client is logged in as an admin
def login(user: str, *args: Any) -> Callable[[Callable[P, T]], Callable[P, T]]:
"""
return apply_fixture(__f, "admin_login", **kwargs)

Login ``client`` as a tin user type.
@overload
def teacher(__f: Callable[P, T], /, **kwargs: Any) -> Callable[P, T]: ...
.. code-block::
@login("admin")
def test_no_redirect(client, course):
response = client.post(reverse("courses:index"), {})
assert not_login_redirect(response)
@overload
def teacher(__f: None, /, **kwargs: Any) -> TestFunctionDecorator: ...


def teacher(
__f: Callable[P, T] | None = None, /, **kwargs: Any
) -> Callable[P, T] | TestFunctionDecorator:
"""
Log in as a teacher
.. code-block:: python
@teacher
def test_something(client):
@login("teacher")
def test_teacher_thing(client):
# client is logged in as a teacher
@login("student")
def test_redirect(client):
# client is a student
@teacher(kwarg_for_usefixtures=xyz)
def test_something(client):
# client is logged in as a teacher
"""
return apply_fixture(__f, "teacher_login", **kwargs)


@overload
def student(__f: Callable[P, T], /, **kwargs: Any) -> Callable[P, T]: ...


@overload
def student(__f: None, /, **kwargs: Any) -> TestFunctionDecorator: ...


def student(
__f: Callable[P, T] | None = None, /, **kwargs: Any
) -> Callable[P, T] | TestFunctionDecorator:
response = client.post(reverse("courses:index"), {})
assert is_login_redirect(response)
"""
Log in as a student
.. code-block:: python

@student
def test_something(client):
# client is logged in as a student
@student(kwarg_for_usefixtures=xyz)
def test_something(client):
# client is logged in as a student
"""
return apply_fixture(__f, "student_login", **kwargs)
return pytest.mark.usefixtures(f"{user}_login", *args)

0 comments on commit 5c10d9d

Please sign in to comment.