mirror of
https://gitlab.archlinux.org/archlinux/aurweb.git
synced 2025-02-03 10:43:03 +01:00
feat(FastAPI): added /requests (get) route
Introduces `aurweb.defaults` and `aurweb.filters`. `aurweb.filters` is a location developers can put their additional Jinja2 filters and/or functions. We should slowly move all of our filters over here, where it makes sense. `aurweb.defaults` is a new module which hosts some default constants and utility functions, starting with offsets (O) and per page values (PP). As far as the new GET /requests is concerned, we match up here to PHP's implementation, with some minor improvements: Improvements: * PP on this page is now configurable: 50 (default), 100, or 250. * Example: `https://localhost:8444/requests?PP=250` Modifications: * The pagination is a bit different, but serves the exact same purpose. * "Last" no longer goes to an empty page. * Closes: https://gitlab.archlinux.org/archlinux/aurweb/-/issues/14 Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
parent
c164abe256
commit
99482f9962
11 changed files with 341 additions and 17 deletions
|
@ -31,8 +31,23 @@ class StubQuery:
|
||||||
|
|
||||||
|
|
||||||
class AnonymousUser:
|
class AnonymousUser:
|
||||||
|
""" A stubbed User class used when an unauthenticated User
|
||||||
|
makes a request against FastAPI. """
|
||||||
# Stub attributes used to mimic a real user.
|
# Stub attributes used to mimic a real user.
|
||||||
ID = 0
|
ID = 0
|
||||||
|
|
||||||
|
class AccountType:
|
||||||
|
""" A stubbed AccountType static class. In here, we use an ID
|
||||||
|
and AccountType which do not exist in our constant records.
|
||||||
|
All records primary keys (AccountType.ID) should be non-zero,
|
||||||
|
so using a zero here means that we'll never match against a
|
||||||
|
real AccountType. """
|
||||||
|
ID = 0
|
||||||
|
AccountType = "Anonymous"
|
||||||
|
|
||||||
|
# AccountTypeID == AccountType.ID; assign a stubbed column.
|
||||||
|
AccountTypeID = AccountType.ID
|
||||||
|
|
||||||
LangPreference = aurweb.config.get("options", "default_lang")
|
LangPreference = aurweb.config.get("options", "default_lang")
|
||||||
Timezone = aurweb.config.get("options", "default_timezone")
|
Timezone = aurweb.config.get("options", "default_timezone")
|
||||||
|
|
||||||
|
|
18
aurweb/defaults.py
Normal file
18
aurweb/defaults.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
""" Constant default values centralized in one place. """
|
||||||
|
|
||||||
|
# Default [O]ffset
|
||||||
|
O = 0
|
||||||
|
|
||||||
|
# Default [P]er [P]age
|
||||||
|
PP = 50
|
||||||
|
|
||||||
|
# A whitelist of valid PP values
|
||||||
|
PP_WHITELIST = {50, 100, 250}
|
||||||
|
|
||||||
|
|
||||||
|
def fallback_pp(per_page: int) -> int:
|
||||||
|
""" If `per_page` is a valid value in PP_WHITELIST, return it.
|
||||||
|
Otherwise, return defaults.PP. """
|
||||||
|
if per_page not in PP_WHITELIST:
|
||||||
|
return PP
|
||||||
|
return per_page
|
|
@ -4,8 +4,8 @@ import paginate
|
||||||
|
|
||||||
from jinja2 import pass_context
|
from jinja2 import pass_context
|
||||||
|
|
||||||
from aurweb import util
|
from aurweb import config, util
|
||||||
from aurweb.templates import register_filter
|
from aurweb.templates import register_filter, register_function
|
||||||
|
|
||||||
|
|
||||||
@register_filter("pager_nav")
|
@register_filter("pager_nav")
|
||||||
|
@ -48,3 +48,13 @@ def pager_nav(context: Dict[str, Any],
|
||||||
symbol_previous="‹ Previous",
|
symbol_previous="‹ Previous",
|
||||||
symbol_next="Next ›",
|
symbol_next="Next ›",
|
||||||
symbol_last="Last »")
|
symbol_last="Last »")
|
||||||
|
|
||||||
|
|
||||||
|
@register_function("config_getint")
|
||||||
|
def config_getint(section: str, key: str) -> int:
|
||||||
|
return config.getint(section, key)
|
||||||
|
|
||||||
|
|
||||||
|
@register_function("round")
|
||||||
|
def do_round(f: float) -> int:
|
||||||
|
return round(f)
|
||||||
|
|
|
@ -2,17 +2,18 @@ from datetime import datetime
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
from fastapi import APIRouter, Form, HTTPException, Request, Response
|
from fastapi import APIRouter, Form, HTTPException, Query, Request, Response
|
||||||
from fastapi.responses import JSONResponse, RedirectResponse
|
from fastapi.responses import JSONResponse, RedirectResponse
|
||||||
from sqlalchemy import and_
|
from sqlalchemy import and_, case
|
||||||
|
|
||||||
import aurweb.filters
|
import aurweb.filters
|
||||||
import aurweb.models.package_comment
|
import aurweb.models.package_comment
|
||||||
import aurweb.models.package_keyword
|
import aurweb.models.package_keyword
|
||||||
import aurweb.packages.util
|
import aurweb.packages.util
|
||||||
|
|
||||||
from aurweb import db, l10n
|
from aurweb import db, defaults, l10n
|
||||||
from aurweb.auth import auth_required
|
from aurweb.auth import account_type_required, auth_required
|
||||||
|
from aurweb.models.account_type import DEVELOPER, TRUSTED_USER, TRUSTED_USER_AND_DEV
|
||||||
from aurweb.models.license import License
|
from aurweb.models.license import License
|
||||||
from aurweb.models.package import Package
|
from aurweb.models.package import Package
|
||||||
from aurweb.models.package_base import PackageBase
|
from aurweb.models.package_base import PackageBase
|
||||||
|
@ -22,10 +23,11 @@ from aurweb.models.package_dependency import PackageDependency
|
||||||
from aurweb.models.package_license import PackageLicense
|
from aurweb.models.package_license import PackageLicense
|
||||||
from aurweb.models.package_notification import PackageNotification
|
from aurweb.models.package_notification import PackageNotification
|
||||||
from aurweb.models.package_relation import PackageRelation
|
from aurweb.models.package_relation import PackageRelation
|
||||||
from aurweb.models.package_request import PackageRequest
|
from aurweb.models.package_request import PENDING_ID, PackageRequest
|
||||||
from aurweb.models.package_source import PackageSource
|
from aurweb.models.package_source import PackageSource
|
||||||
from aurweb.models.package_vote import PackageVote
|
from aurweb.models.package_vote import PackageVote
|
||||||
from aurweb.models.relation_type import CONFLICTS_ID
|
from aurweb.models.relation_type import CONFLICTS_ID
|
||||||
|
from aurweb.models.request_type import RequestType
|
||||||
from aurweb.models.user import User
|
from aurweb.models.user import User
|
||||||
from aurweb.packages.search import PackageSearch
|
from aurweb.packages.search import PackageSearch
|
||||||
from aurweb.packages.util import get_pkg_or_base, get_pkgbase_comment, query_notified, query_voted
|
from aurweb.packages.util import get_pkg_or_base, get_pkgbase_comment, query_notified, query_voted
|
||||||
|
@ -535,3 +537,31 @@ async def package_base_comaintainers_post(
|
||||||
|
|
||||||
return RedirectResponse(f"/pkgbase/{pkgbase.Name}",
|
return RedirectResponse(f"/pkgbase/{pkgbase.Name}",
|
||||||
status_code=int(HTTPStatus.SEE_OTHER))
|
status_code=int(HTTPStatus.SEE_OTHER))
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/requests")
|
||||||
|
@account_type_required({TRUSTED_USER, DEVELOPER, TRUSTED_USER_AND_DEV})
|
||||||
|
@auth_required(True, redirect="/")
|
||||||
|
async def requests(request: Request,
|
||||||
|
O: int = Query(default=defaults.O),
|
||||||
|
PP: int = Query(default=defaults.PP)):
|
||||||
|
context = make_context(request, "Requests")
|
||||||
|
|
||||||
|
context["q"] = dict(request.query_params)
|
||||||
|
context["O"] = O
|
||||||
|
context["PP"] = PP
|
||||||
|
|
||||||
|
# A PackageRequest query, with left inner joined User and RequestType.
|
||||||
|
query = db.query(PackageRequest).join(
|
||||||
|
User, PackageRequest.UsersID == User.ID
|
||||||
|
).join(RequestType)
|
||||||
|
|
||||||
|
context["total"] = query.count()
|
||||||
|
context["results"] = query.order_by(
|
||||||
|
# Order primarily by the Status column being PENDING_ID,
|
||||||
|
# and secondarily by RequestTS; both in descending order.
|
||||||
|
case([(PackageRequest.Status == PENDING_ID, 1)], else_=0).desc(),
|
||||||
|
PackageRequest.RequestTS.desc()
|
||||||
|
).limit(PP).offset(O).all()
|
||||||
|
|
||||||
|
return render_template(request, "requests.html", context)
|
||||||
|
|
|
@ -71,6 +71,20 @@ def register_filter(name: str) -> Callable:
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def register_function(name: str) -> Callable:
|
||||||
|
""" A decorator that can be used to register a function.
|
||||||
|
"""
|
||||||
|
def decorator(func):
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
if name in _env.globals:
|
||||||
|
raise KeyError(f"Jinja already has a function named '{name}'")
|
||||||
|
_env.globals[name] = wrapper
|
||||||
|
return wrapper
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def make_context(request: Request, title: str, next: str = None):
|
def make_context(request: Request, title: str, next: str = None):
|
||||||
""" Create a context for a jinja2 TemplateResponse. """
|
""" Create a context for a jinja2 TemplateResponse. """
|
||||||
|
|
||||||
|
@ -83,6 +97,7 @@ def make_context(request: Request, title: str, next: str = None):
|
||||||
"timezones": time.SUPPORTED_TIMEZONES,
|
"timezones": time.SUPPORTED_TIMEZONES,
|
||||||
"title": title,
|
"title": title,
|
||||||
"now": datetime.now(tz=zoneinfo.ZoneInfo(timezone)),
|
"now": datetime.now(tz=zoneinfo.ZoneInfo(timezone)),
|
||||||
|
"utcnow": int(datetime.utcnow().timestamp()),
|
||||||
"config": aurweb.config,
|
"config": aurweb.config,
|
||||||
"next": next if next else request.url.path
|
"next": next if next else request.url.path
|
||||||
}
|
}
|
||||||
|
|
19
setup.cfg
19
setup.cfg
|
@ -6,17 +6,22 @@ ignore = E741, W503
|
||||||
max-line-length = 127
|
max-line-length = 127
|
||||||
max-complexity = 10
|
max-complexity = 10
|
||||||
|
|
||||||
# aurweb/routers/accounts.py
|
|
||||||
# Ignore some unavoidable flake8 warnings; we know this is against
|
# Ignore some unavoidable flake8 warnings; we know this is against
|
||||||
# pycodestyle, but some of the existing codebase uses `I` variables,
|
# PEP8, but some of the existing codebase uses `I` variables,
|
||||||
# so specifically silence warnings about it in pre-defined files.
|
# so specifically silence warnings about it in pre-defined files.
|
||||||
|
#
|
||||||
# In E741, the 'I', 'O', 'l' are ambiguous variable names.
|
# In E741, the 'I', 'O', 'l' are ambiguous variable names.
|
||||||
# Our current implementation uses these variables through HTTP
|
# Our current implementation uses these variables through HTTP
|
||||||
# and the FastAPI form specification wants them named as such.
|
# and the FastAPI form specification wants them named as such.
|
||||||
# In C901's case, our process_account_form function is way too
|
#
|
||||||
# complex for PEP (too many if statements). However, we need to
|
# With {W503,W504}, PEP8 does not want us to break lines before
|
||||||
# process these anyways, and making it any more complex would
|
# or after a binary operator. We have many scripts that already
|
||||||
# just add confusion to the implementation.
|
# do this, so we're ignoring it here.
|
||||||
|
ignore = E741, W503, W504
|
||||||
|
|
||||||
|
# aurweb/routers/accounts.py
|
||||||
|
# Ignore over-reaching complexity.
|
||||||
|
# TODO: This should actually be addressed so we do not ignore C901.
|
||||||
#
|
#
|
||||||
# test/test_ssh_pub_key.py
|
# test/test_ssh_pub_key.py
|
||||||
# E501 is detected due to our >127 width test constant. Ignore it.
|
# E501 is detected due to our >127 width test constant. Ignore it.
|
||||||
|
@ -24,7 +29,7 @@ max-complexity = 10
|
||||||
# Anything like this should be questioned.
|
# Anything like this should be questioned.
|
||||||
#
|
#
|
||||||
per-file-ignores =
|
per-file-ignores =
|
||||||
aurweb/routers/accounts.py:E741,C901
|
aurweb/routers/accounts.py:C901
|
||||||
test/test_ssh_pub_key.py:E501
|
test/test_ssh_pub_key.py:E501
|
||||||
aurweb/routers/packages.py:E741
|
aurweb/routers/packages.py:E741
|
||||||
|
|
||||||
|
|
115
templates/requests.html
Normal file
115
templates/requests.html
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
{% extends "partials/layout.html" %}
|
||||||
|
|
||||||
|
{% set singular = "%d package request found." %}
|
||||||
|
{% set plural = "%d package requests found." %}
|
||||||
|
|
||||||
|
{% block pageContent %}
|
||||||
|
<div id="pkglist-results" class="box">
|
||||||
|
{% if not total %}
|
||||||
|
<p>{{ "No requests matched your search criteria." | tr }}</p>
|
||||||
|
{% else %}
|
||||||
|
{% include "partials/widgets/pager.html" %}
|
||||||
|
<table class="results">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{ "Package" | tr }}</th>
|
||||||
|
<th>{{ "Type" | tr }}</th>
|
||||||
|
<th>{{ "Comments" | tr }}</th>
|
||||||
|
<th>{{ "Filed by" | tr }}</th>
|
||||||
|
<th>{{ "Date" | tr }}</th>
|
||||||
|
<th>{{ "Status" | tr }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for result in results %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{# Package #}
|
||||||
|
<a href="/pkgbase/{{ result.PackageBaseName }}">
|
||||||
|
{{ result.PackageBaseName }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
{# Type #}
|
||||||
|
<td>
|
||||||
|
{{ result.RequestType.name_display() }}
|
||||||
|
{# If the RequestType is a merge and request.MergeBaseName is valid... #}
|
||||||
|
{% if result.RequestType.ID == 3 and result.MergeBaseName %}
|
||||||
|
({{ result.MergeBaseName }})
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
{# Comments #}
|
||||||
|
<td class="wrap">{{ result.Comments }}</td>
|
||||||
|
<td>
|
||||||
|
{# Filed by #}
|
||||||
|
<a href="/account/{{ result.User.Username }}">
|
||||||
|
{{ result.User.Username }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
{% set idle_time = config_getint("options", "request_idle_time") %}
|
||||||
|
{% set time_delta = (utcnow - result.RequestTS) | int %}
|
||||||
|
|
||||||
|
{% set due = result.Status == 0 and time_delta > idle_time %}
|
||||||
|
<td
|
||||||
|
{% if due %}
|
||||||
|
class="flagged"
|
||||||
|
{% endif %}
|
||||||
|
>
|
||||||
|
{# Date #}
|
||||||
|
{% set date = result.RequestTS | dt | as_timezone(timezone) %}
|
||||||
|
{{ date.strftime("%Y-%m-%d %H:%M") }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{# Status #}
|
||||||
|
{% if result.Status == 0 %}
|
||||||
|
{% set temp_q = { "next": "/requests" } %}
|
||||||
|
|
||||||
|
{% if result.RequestType.ID == 1 %}
|
||||||
|
{% set action = "delete" %}
|
||||||
|
{% elif result.RequestType.ID == 2 %}
|
||||||
|
{% set action = "disown" %}
|
||||||
|
{% elif result.RequestType.ID == 3 %}
|
||||||
|
{% set action = "merge" %}
|
||||||
|
{# Add the 'via' url query parameter. #}
|
||||||
|
{% set temp_q = temp_q | extend_query(
|
||||||
|
["via", result.ID],
|
||||||
|
["into", result.MergeBaseName]
|
||||||
|
) %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if request.user.is_elevated() %}
|
||||||
|
{% if result.RequestType.ID == 2 and not due %}
|
||||||
|
{% set time_left = idle_time - time_delta %}
|
||||||
|
{% if time_left > 48 * 3600 %}
|
||||||
|
{% set n = round(time_left / (24 * 3600)) %}
|
||||||
|
{% set time_left_fmt = (n | tn("~%d day left", "~%d days left") | format(n)) %}
|
||||||
|
{% elif time_left > 3600 %}
|
||||||
|
{% set n = round(time_left / 3600) %}
|
||||||
|
{% set time_left_fmt = (n | tn("~%d hour left", "~%d hours left") | format(n)) %}
|
||||||
|
{% else %}
|
||||||
|
{% set time_left_fmt = ("<1 hour left" | tr) %}
|
||||||
|
{% endif %}
|
||||||
|
{{ "Locked" | tr }}
|
||||||
|
({{ time_left_fmt }})
|
||||||
|
{% else %}
|
||||||
|
{# Only elevated users (TU or Dev) are allowed to accept requests. #}
|
||||||
|
<a href="/pkgbase/{{ result.PackageBaseName }}/{{ action }}?{{ temp_q | urlencode }}">
|
||||||
|
{{ "Accept" | tr }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
<br />
|
||||||
|
{% endif %}
|
||||||
|
<a href="/requests/{{ result.ID }}/close">
|
||||||
|
{{ "Close" | tr }}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
{{ result.status_display() }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% include "partials/widgets/pager.html" %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
14
test/test_defaults.py
Normal file
14
test/test_defaults.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
from aurweb import defaults
|
||||||
|
|
||||||
|
|
||||||
|
def test_fallback_pp():
|
||||||
|
assert defaults.fallback_pp(75) == defaults.PP
|
||||||
|
assert defaults.fallback_pp(100) == 100
|
||||||
|
|
||||||
|
|
||||||
|
def test_pp():
|
||||||
|
assert defaults.PP == 50
|
||||||
|
|
||||||
|
|
||||||
|
def test_o():
|
||||||
|
assert defaults.O == 0
|
|
@ -8,7 +8,7 @@ import pytest
|
||||||
|
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
from aurweb import asgi, db
|
from aurweb import asgi, db, defaults
|
||||||
from aurweb.models.account_type import USER_ID, AccountType
|
from aurweb.models.account_type import USER_ID, AccountType
|
||||||
from aurweb.models.dependency_type import DependencyType
|
from aurweb.models.dependency_type import DependencyType
|
||||||
from aurweb.models.official_provider import OfficialProvider
|
from aurweb.models.official_provider import OfficialProvider
|
||||||
|
@ -74,6 +74,7 @@ def setup():
|
||||||
PackageNotification.__tablename__,
|
PackageNotification.__tablename__,
|
||||||
PackageComaintainer.__tablename__,
|
PackageComaintainer.__tablename__,
|
||||||
PackageComment.__tablename__,
|
PackageComment.__tablename__,
|
||||||
|
PackageRequest.__tablename__,
|
||||||
OfficialProvider.__tablename__
|
OfficialProvider.__tablename__
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -108,6 +109,18 @@ def maintainer() -> User:
|
||||||
yield maintainer
|
yield maintainer
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def tu_user():
|
||||||
|
tu_type = db.query(AccountType,
|
||||||
|
AccountType.AccountType == "Trusted User").first()
|
||||||
|
with db.begin():
|
||||||
|
tu_user = db.create(User, Username="test_tu",
|
||||||
|
Email="test_tu@example.org",
|
||||||
|
RealName="Test TU", Passwd="testPassword",
|
||||||
|
AccountType=tu_type)
|
||||||
|
yield tu_user
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def package(maintainer: User) -> Package:
|
def package(maintainer: User) -> Package:
|
||||||
""" Yield a Package created by user. """
|
""" Yield a Package created by user. """
|
||||||
|
@ -160,6 +173,25 @@ def packages(maintainer: User) -> List[Package]:
|
||||||
yield packages_
|
yield packages_
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def requests(user: User, packages: List[Package]) -> List[PackageRequest]:
|
||||||
|
pkgreqs = []
|
||||||
|
deletion_type = db.query(RequestType).filter(
|
||||||
|
RequestType.ID == DELETION_ID
|
||||||
|
).first()
|
||||||
|
with db.begin():
|
||||||
|
for i in range(55):
|
||||||
|
pkgreq = db.create(PackageRequest,
|
||||||
|
RequestType=deletion_type,
|
||||||
|
User=user,
|
||||||
|
PackageBase=packages[i].PackageBase,
|
||||||
|
PackageBaseName=packages[i].Name,
|
||||||
|
Comments=f"Deletion request for pkg_{i}",
|
||||||
|
ClosureComment=str())
|
||||||
|
pkgreqs.append(pkgreq)
|
||||||
|
yield pkgreqs
|
||||||
|
|
||||||
|
|
||||||
def test_package_not_found(client: TestClient):
|
def test_package_not_found(client: TestClient):
|
||||||
with client as request:
|
with client as request:
|
||||||
resp = request.get("/packages/not_found")
|
resp = request.get("/packages/not_found")
|
||||||
|
@ -1304,3 +1336,53 @@ def test_pkgbase_comaintainers(client: TestClient, user: User,
|
||||||
root = parse_root(resp.text)
|
root = parse_root(resp.text)
|
||||||
users = root.xpath('//textarea[@id="id_users"]')[0]
|
users = root.xpath('//textarea[@id="id_users"]')[0]
|
||||||
assert users is not None and users.text is None
|
assert users is not None and users.text is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_requests_unauthorized(client: TestClient,
|
||||||
|
maintainer: User,
|
||||||
|
tu_user: User,
|
||||||
|
packages: List[Package],
|
||||||
|
requests: List[PackageRequest]):
|
||||||
|
cookies = {"AURSID": maintainer.login(Request(), "testPassword")}
|
||||||
|
with client as request:
|
||||||
|
resp = request.get("/requests", cookies=cookies, allow_redirects=False)
|
||||||
|
assert resp.status_code == int(HTTPStatus.SEE_OTHER)
|
||||||
|
|
||||||
|
|
||||||
|
def test_requests(client: TestClient,
|
||||||
|
maintainer: User,
|
||||||
|
tu_user: User,
|
||||||
|
packages: List[Package],
|
||||||
|
requests: List[PackageRequest]):
|
||||||
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
||||||
|
with client as request:
|
||||||
|
resp = request.get("/requests", params={
|
||||||
|
# Pass in url query parameters O, SeB and SB to exercise
|
||||||
|
# their paths inside of the pager_nav used in this request.
|
||||||
|
"O": 0, # Page 1
|
||||||
|
"SeB": "nd",
|
||||||
|
"SB": "n"
|
||||||
|
}, cookies=cookies)
|
||||||
|
assert resp.status_code == int(HTTPStatus.OK)
|
||||||
|
|
||||||
|
assert "Next ›" in resp.text
|
||||||
|
assert "Last »" in resp.text
|
||||||
|
|
||||||
|
root = parse_root(resp.text)
|
||||||
|
# We have 55 requests, our defaults.PP is 50, so expect we have 50 rows.
|
||||||
|
rows = root.xpath('//table[@class="results"]/tbody/tr')
|
||||||
|
assert len(rows) == defaults.PP
|
||||||
|
|
||||||
|
# Request page 2 of the requests page.
|
||||||
|
with client as request:
|
||||||
|
resp = request.get("/requests", params={
|
||||||
|
"O": 50 # Page 2
|
||||||
|
}, cookies=cookies)
|
||||||
|
assert resp.status_code == int(HTTPStatus.OK)
|
||||||
|
|
||||||
|
assert "‹ Previous" in resp.text
|
||||||
|
assert "« First" in resp.text
|
||||||
|
|
||||||
|
root = parse_root(resp.text)
|
||||||
|
rows = root.xpath('//table[@class="results"]/tbody/tr')
|
||||||
|
assert len(rows) == 5 # There are five records left on the second page.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aurweb.templates import register_filter
|
from aurweb.templates import register_filter, register_function
|
||||||
|
|
||||||
|
|
||||||
@register_filter("func")
|
@register_filter("func")
|
||||||
|
@ -8,6 +8,11 @@ def func():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@register_function("function")
|
||||||
|
def function():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_register_filter_exists_key_error():
|
def test_register_filter_exists_key_error():
|
||||||
""" Most instances of register_filter are tested through module
|
""" Most instances of register_filter are tested through module
|
||||||
imports or template renders, so we only test failures here. """
|
imports or template renders, so we only test failures here. """
|
||||||
|
@ -15,3 +20,12 @@ def test_register_filter_exists_key_error():
|
||||||
@register_filter("func")
|
@register_filter("func")
|
||||||
def some_func():
|
def some_func():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_register_function_exists_key_error():
|
||||||
|
""" Most instances of register_filter are tested through module
|
||||||
|
imports or template renders, so we only test failures here. """
|
||||||
|
with pytest.raises(KeyError):
|
||||||
|
@register_function("function")
|
||||||
|
def some_func():
|
||||||
|
pass
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
from aurweb import util
|
from aurweb import filters, util
|
||||||
|
|
||||||
|
|
||||||
def test_timestamp_to_datetime():
|
def test_timestamp_to_datetime():
|
||||||
|
@ -34,3 +34,9 @@ def test_to_qs():
|
||||||
query = {"a": "b", "c": [1, 2, 3]}
|
query = {"a": "b", "c": [1, 2, 3]}
|
||||||
qs = util.to_qs(query)
|
qs = util.to_qs(query)
|
||||||
assert qs == "a=b&c=1&c=2&c=3"
|
assert qs == "a=b&c=1&c=2&c=3"
|
||||||
|
|
||||||
|
|
||||||
|
def test_round():
|
||||||
|
assert filters.do_round(1.3) == 1
|
||||||
|
assert filters.do_round(1.5) == 2
|
||||||
|
assert filters.do_round(2.0) == 2
|
||||||
|
|
Loading…
Add table
Reference in a new issue