blob: da62f928bde0b7aba82e11c4baf57b23193bd56f [file]
"""Shared fixtures.
Sessions are injected through the real Quart session cookie rather than by
monkeypatching, so the tests exercise the same code path asfquart uses in
production.
"""
from __future__ import annotations
import pathlib
import time
from collections.abc import AsyncIterator, Iterator
from typing import Any
import pytest
import pytest_asyncio
from asfquart.session import ClientSession
from asfcalendar.app import APP_ID, create_app
from asfcalendar.config import Config, from_dict
from asfcalendar.storage import Storage
# Personas used throughout the suite. The keys match asfquart's raw session
# dict, where committees arrive as "pmcs".
PERSONAS: dict[str, dict[str, Any]] = {
# A committer on httpd and tomcat, on no committee, not a member.
"alice": {"uid": "alice", "fullname": "Alice", "projects": ["httpd", "tomcat"], "pmcs": []},
# On the httpd committee (and therefore also a project member).
"bob": {"uid": "bob", "fullname": "Bob", "projects": ["httpd"], "pmcs": ["httpd"]},
# A foundation member who is also on the tomcat committee.
"carol": {
"uid": "carol",
"fullname": "Carol",
"projects": ["tomcat"],
"pmcs": ["tomcat"],
"isMember": True,
},
# A committer with no overlap with the others.
"dave": {"uid": "dave", "fullname": "Dave", "projects": ["maven"], "pmcs": []},
}
def session_for(name: str) -> ClientSession:
"""A ClientSession for one of the personas above."""
return ClientSession(dict(PERSONAS[name]))
def cookie_payload(name: str) -> dict[str, Any]:
"""The raw dict asfquart stores in the signed session cookie."""
now = time.time()
return {**PERSONAS[name], "cts": now, "uts": now}
@pytest.fixture
def config(tmp_path: pathlib.Path) -> Config:
return from_dict(
{
"database": {"path": "calendar.sqlite3"},
"app": {"frontend_dist": "dist", "title": "Test Calendar"},
"server": {"base_url": "https://calendar.example.org"},
},
root_dir=tmp_path,
)
@pytest_asyncio.fixture
async def store(config: Config) -> AsyncIterator[Storage]:
storage = Storage(config.database_path)
await storage.connect()
try:
yield storage
finally:
await storage.close()
@pytest.fixture
def app(config: Config) -> Any:
return create_app(config=config, testing=True, token_file=None)
@pytest_asyncio.fixture
async def client(app: Any) -> AsyncIterator[Any]:
async with app.test_app() as running:
yield running.test_client()
@pytest.fixture
def login(client: Any) -> Any:
"""Returns an async helper that puts a persona into the session cookie."""
async def _login(name: str | None) -> None:
async with client.session_transaction() as session:
if name is None:
session.pop(APP_ID, None)
else:
session[APP_ID] = cookie_payload(name)
return _login
@pytest.fixture
def dist_dir(config: Config) -> Iterator[pathlib.Path]:
"""Creates a minimal frontend build so the static routes have something
to serve."""
dist = config.frontend_dist_path
dist.mkdir(parents=True, exist_ok=True)
(dist / "index.html").write_text(
"<!doctype html><html><head><title>Calendar</title></head><body><div id=app></div></body></html>",
encoding="utf-8",
)
(dist / "assets").mkdir(exist_ok=True)
(dist / "assets" / "app.js").write_text("console.log('hi')", encoding="utf-8")
yield dist