v0.166.0
 1from typing import TYPE_CHECKING, Any
 2
 3from plain.http.request import Request
 4from plain.runtime import settings
 5from plain.sessions import SessionStore
 6from plain.sessions.requests import get_request_session, set_request_session
 7
 8from .requests import set_request_user
 9from .sessions import get_user, login, logout
10
11if TYPE_CHECKING:
12    from plain.testing import Client
13
14__all__ = ["login_client", "logout_client"]
15
16
17def login_client(client: Client, user: Any) -> None:
18    """Log a user into a test client, without going through a login view.
19
20    Writes the session cookie to `client.cookies`, so every request the
21    client makes afterwards is that user's.
22    """
23    request = Request(method="GET", path="/")
24    set_request_session(request, _session_of(client))
25    login(request, user)
26    session = get_request_session(request)
27    session.save()
28    assert session.session_key is not None
29    session_cookie = settings.SESSION_COOKIE_NAME
30    client.cookies[session_cookie] = session.session_key
31    cookie_data: dict[str, Any] = {
32        "max-age": None,
33        "path": "/",
34        "domain": settings.SESSION_COOKIE_DOMAIN,
35        "secure": settings.SESSION_COOKIE_SECURE or None,
36        "expires": None,
37    }
38    # Morsel.update() is typed for str-only values, but these cookie
39    # attributes are legitimately None (unset). Set them one at a time
40    # through __setitem__, which is typed for Any and keeps Morsel's own
41    # reserved-key validation (unlike bypassing update() with dict.update()).
42    morsel = client.cookies[session_cookie]
43    for key, value in cookie_data.items():
44        morsel[key] = value
45
46
47def logout_client(client: Client) -> None:
48    """Log a test client out: end its session and drop its cookies."""
49    request = Request(method="GET", path="/")
50    set_request_session(request, _session_of(client))
51    set_request_user(request, get_user(request))
52    logout(request)
53    client.cookies.clear()
54
55
56def _session_of(client: Client) -> SessionStore:
57    """The session the client's cookie points to, or one that isn't written
58    yet.
59
60    Not `get_client_session()`, which writes a new session so that it can
61    hand the client its cookie. `login()` changes the session's key and
62    `logout()` ends it, so what that wrote would be written over or removed
63    by the next statement.
64    """
65    cookie = client.cookies.get(settings.SESSION_COOKIE_NAME)
66    if cookie:
67        return SessionStore(cookie.value)
68    return SessionStore()