mirror of
https://gitlab.archlinux.org/archlinux/aurweb.git
synced 2025-02-03 10:43:03 +01:00
add /accounts/ (get, post) routes
Slight markup changes, same style overall and same form parameters as the PHP implementation. In addition, we've disabled the "left" and "right" navigation buttons when we're at the border of the table. CSS Changes: - Added similar styling to submit `<buttons>` that submit `<input>` had. - Added .results tr td[align="{left,right}"] styling to align the result table's `More -->` button to the right of the table. Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
parent
bdc913d088
commit
021a1c8fb6
7 changed files with 760 additions and 7 deletions
|
@ -168,6 +168,15 @@ class User(Base):
|
||||||
aurweb.models.account_type.TRUSTED_USER_AND_DEV_ID
|
aurweb.models.account_type.TRUSTED_USER_AND_DEV_ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def can_edit_user(self, user):
|
||||||
|
""" Can this account record edit the target user? It must either
|
||||||
|
be the target user or a user with enough permissions to do so.
|
||||||
|
|
||||||
|
:param user: Target user
|
||||||
|
:return: Boolean indicating whether this instance can edit `user`
|
||||||
|
"""
|
||||||
|
return self == user or self.is_trusted_user() or self.is_developer()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<User(ID='%s', AccountType='%s', Username='%s')>" % (
|
return "<User(ID='%s', AccountType='%s', Username='%s')>" % (
|
||||||
self.ID, str(self.AccountType), self.Username)
|
self.ID, str(self.AccountType), self.Username)
|
||||||
|
|
|
@ -12,17 +12,18 @@ from sqlalchemy import and_, func, or_
|
||||||
import aurweb.config
|
import aurweb.config
|
||||||
|
|
||||||
from aurweb import db, l10n, time, util
|
from aurweb import db, l10n, time, util
|
||||||
from aurweb.auth import auth_required
|
from aurweb.auth import account_type_required, auth_required
|
||||||
from aurweb.captcha import get_captcha_answer, get_captcha_salts, get_captcha_token
|
from aurweb.captcha import get_captcha_answer, get_captcha_salts, get_captcha_token
|
||||||
from aurweb.l10n import get_translator_for_request
|
from aurweb.l10n import get_translator_for_request
|
||||||
from aurweb.models.accepted_term import AcceptedTerm
|
from aurweb.models.accepted_term import AcceptedTerm
|
||||||
from aurweb.models.account_type import AccountType
|
from aurweb.models.account_type import (DEVELOPER, DEVELOPER_ID, TRUSTED_USER, TRUSTED_USER_AND_DEV, TRUSTED_USER_AND_DEV_ID,
|
||||||
|
TRUSTED_USER_ID, USER_ID, AccountType)
|
||||||
from aurweb.models.ban import Ban
|
from aurweb.models.ban import Ban
|
||||||
from aurweb.models.ssh_pub_key import SSHPubKey, get_fingerprint
|
from aurweb.models.ssh_pub_key import SSHPubKey, get_fingerprint
|
||||||
from aurweb.models.term import Term
|
from aurweb.models.term import Term
|
||||||
from aurweb.models.user import User
|
from aurweb.models.user import User
|
||||||
from aurweb.scripts.notify import ResetKeyNotification
|
from aurweb.scripts.notify import ResetKeyNotification
|
||||||
from aurweb.templates import make_variable_context, render_template
|
from aurweb.templates import make_context, make_variable_context, render_template
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -591,6 +592,91 @@ async def account(request: Request, username: str):
|
||||||
return render_template(request, "account/show.html", context)
|
return render_template(request, "account/show.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/accounts/")
|
||||||
|
@auth_required(True)
|
||||||
|
@account_type_required({TRUSTED_USER, DEVELOPER, TRUSTED_USER_AND_DEV})
|
||||||
|
async def accounts(request: Request):
|
||||||
|
context = make_context(request, "Accounts")
|
||||||
|
return render_template(request, "account/search.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/accounts/")
|
||||||
|
@auth_required(True)
|
||||||
|
@account_type_required({TRUSTED_USER, DEVELOPER, TRUSTED_USER_AND_DEV})
|
||||||
|
async def accounts_post(request: Request,
|
||||||
|
O: int = Form(default=0), # Offset
|
||||||
|
SB: str = Form(default=str()), # Search By
|
||||||
|
U: str = Form(default=str()), # Username
|
||||||
|
T: str = Form(default=str()), # Account Type
|
||||||
|
S: bool = Form(default=False), # Suspended
|
||||||
|
E: str = Form(default=str()), # Email
|
||||||
|
R: str = Form(default=str()), # Real Name
|
||||||
|
I: str = Form(default=str()), # IRC Nick
|
||||||
|
K: str = Form(default=str())): # PGP Key
|
||||||
|
context = await make_variable_context(request, "Accounts")
|
||||||
|
context["pp"] = pp = 50 # Hits per page.
|
||||||
|
|
||||||
|
offset = max(O, 0) # Minimize offset at 0.
|
||||||
|
context["offset"] = offset # Offset.
|
||||||
|
|
||||||
|
context["params"] = dict(await request.form())
|
||||||
|
if "O" in context["params"]:
|
||||||
|
context["params"].pop("O")
|
||||||
|
|
||||||
|
# Setup order by criteria based on SB.
|
||||||
|
order_by_columns = {
|
||||||
|
"t": (AccountType.ID.asc(), User.Username.asc()),
|
||||||
|
"r": (User.RealName.asc(), AccountType.ID.asc()),
|
||||||
|
"i": (User.IRCNick.asc(), AccountType.ID.asc()),
|
||||||
|
}
|
||||||
|
default_order = (User.Username.asc(), AccountType.ID.asc())
|
||||||
|
order_by = order_by_columns.get(SB, default_order)
|
||||||
|
|
||||||
|
# Convert parameter T to an AccountType ID.
|
||||||
|
account_types = {
|
||||||
|
"u": USER_ID,
|
||||||
|
"t": TRUSTED_USER_ID,
|
||||||
|
"d": DEVELOPER_ID,
|
||||||
|
"td": TRUSTED_USER_AND_DEV_ID
|
||||||
|
}
|
||||||
|
account_type_id = account_types.get(T, None)
|
||||||
|
|
||||||
|
# Get a query handle to users, populate the total user
|
||||||
|
# count into a jinja2 context variable.
|
||||||
|
query = db.query(User).join(AccountType)
|
||||||
|
context["total_users"] = query.count()
|
||||||
|
|
||||||
|
# Populate this list with any additional statements to
|
||||||
|
# be ANDed together.
|
||||||
|
statements = []
|
||||||
|
if account_type_id is not None:
|
||||||
|
statements.append(AccountType.ID == account_type_id)
|
||||||
|
if U:
|
||||||
|
statements.append(User.Username.like(f"%{U}%"))
|
||||||
|
if S:
|
||||||
|
statements.append(User.Suspended == S)
|
||||||
|
if E:
|
||||||
|
statements.append(User.Email.like(f"%{E}%"))
|
||||||
|
if R:
|
||||||
|
statements.append(User.RealName.like(f"%{R}%"))
|
||||||
|
if I:
|
||||||
|
statements.append(User.IRCNick.like(f"%{I}%"))
|
||||||
|
if K:
|
||||||
|
statements.append(User.PGPKey.like(f"%{K}%"))
|
||||||
|
|
||||||
|
# Filter the query by combining all statements added above into
|
||||||
|
# an AND statement, unless there's just one statement, which
|
||||||
|
# we pass on to filter() as args.
|
||||||
|
if statements:
|
||||||
|
query = query.filter(and_(*statements))
|
||||||
|
|
||||||
|
# Finally, order and truncate our users for the current page.
|
||||||
|
users = query.order_by(*order_by).limit(pp).offset(offset)
|
||||||
|
context["users"] = users
|
||||||
|
|
||||||
|
return render_template(request, "account/index.html", context)
|
||||||
|
|
||||||
|
|
||||||
def render_terms_of_service(request: Request,
|
def render_terms_of_service(request: Request,
|
||||||
context: dict,
|
context: dict,
|
||||||
terms: typing.Iterable):
|
terms: typing.Iterable):
|
||||||
|
|
13
templates/account/index.html
Normal file
13
templates/account/index.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{% extends "partials/layout.html" %}
|
||||||
|
|
||||||
|
{% block pageContent %}
|
||||||
|
<div class="box">
|
||||||
|
<h2>{{ "Accounts" | tr }}</h2>
|
||||||
|
|
||||||
|
{% if not users %}
|
||||||
|
{{ "No results matched your search criteria." | tr }}
|
||||||
|
{% else %}
|
||||||
|
{% include "partials/account/results.html" %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
69
templates/account/search.html
Normal file
69
templates/account/search.html
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
{% extends "partials/layout.html" %}
|
||||||
|
|
||||||
|
{% block pageContent %}
|
||||||
|
<div class="box">
|
||||||
|
<h2>{{ "Accounts" | tr }}</h2>
|
||||||
|
{{ "Use this form to search existing accounts." | tr }}
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<form class="account-search-form" action="/accounts/" method="post">
|
||||||
|
<fieldset>
|
||||||
|
<p>
|
||||||
|
<label for="id_username">{{ "Username" | tr }}:</label>
|
||||||
|
<input type="text" size="30" maxlength="64" name="U"
|
||||||
|
id="id_username" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<label for="id_type">{{ "Account Type" | tr }}:</label>
|
||||||
|
<select name="T" id="id_type">
|
||||||
|
<option value="">{{ "Any type" | tr }}</option>
|
||||||
|
<option value="u">{{ "Normal user" | tr }}</option>
|
||||||
|
<option value="t">{{ "Trusted user" | tr }}</option>
|
||||||
|
<option value="d">{{ "Developer" | tr }}</option>
|
||||||
|
<option value="td">{{ "Trusted User & Developer" | tr }}</option>
|
||||||
|
</select>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label for="id_suspended">{{ "Account Suspended" | tr }}:</label>
|
||||||
|
<input type="checkbox" name="S" id="id_suspended" />
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label for="id_email">{{ "Email Address" | tr }}:</label>
|
||||||
|
<input type="text" size="30" maxlength="64" name="E"
|
||||||
|
id="id_email" />
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label for="id_realname">{{ "Real Name" | tr }}:</label>
|
||||||
|
<input type="text" size="30" maxlength="32" name="R"
|
||||||
|
id="id_realname" />
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label for="id_irc">{{ "IRC Nick" | tr }}:</label>
|
||||||
|
<input type="text" size="30" maxlength="32" name="I"
|
||||||
|
id="id_irc" />
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label for="id_sortby">{{ "Sort by" | tr }}:</label>
|
||||||
|
<select name="SB" id="id_sortby">
|
||||||
|
<option value="u">{{ "Username" | tr }}</option>
|
||||||
|
<option value="t">{{ "Account Type" | tr }}</option>
|
||||||
|
<option value="r">{{ "Real Name" | tr }}</option>
|
||||||
|
<option value="i">{{ "IRC Nick" | tr }}</option>
|
||||||
|
</select>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label></label>
|
||||||
|
<button type="submit" class="button">
|
||||||
|
{{ "Search" | tr }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="reset" class="button">
|
||||||
|
{{ "Reset" | tr }}
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
82
templates/partials/account/results.html
Normal file
82
templates/partials/account/results.html
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
<table class="results users">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{ "Username" | tr }}</th>
|
||||||
|
<th>{{ "Type" | tr }}</th>
|
||||||
|
<th>{{ "Status" | tr }}</th>
|
||||||
|
<th>{{ "Real Name" | tr }}</th>
|
||||||
|
<th>{{ "IRC Nick" | tr }}</th>
|
||||||
|
<th>{{ "PGP Key Fingerprint" | tr }}</th>
|
||||||
|
<th>{{ "Edit Account" | tr }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{% for user in users %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="/packages/?K={{ user.Username }}&SeB=m">
|
||||||
|
{{ user.Username }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>{{ user.AccountType.AccountType }}</td>
|
||||||
|
<td>{{ "Suspended" if user.Suspended else "Active" }}</td>
|
||||||
|
<td>{{ user.RealName | e }}</td>
|
||||||
|
<td>{{ user.IRCNick | e }}</td>
|
||||||
|
<td>{{ user.PGPKey or '' | e }}</td>
|
||||||
|
<td>
|
||||||
|
{% if request.user.can_edit_user(user) %}
|
||||||
|
<a href="/account/{{ user.Username }}/edit">
|
||||||
|
{{ "Edit" | tr }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table class="results">
|
||||||
|
<tr>
|
||||||
|
<td align="left">
|
||||||
|
<form action="/accounts/" method="post">
|
||||||
|
<fieldset>
|
||||||
|
<input type="hidden" name="O"
|
||||||
|
value="{{ offset - pp }}" />
|
||||||
|
{% for k, v in params.items() %}
|
||||||
|
<input type="hidden" name="{{ k }}"
|
||||||
|
value="{{ v }}" />
|
||||||
|
{% endfor %}
|
||||||
|
<button type="submit" class="button page-prev"
|
||||||
|
{% if offset <= 0 %}
|
||||||
|
disabled
|
||||||
|
{% endif %}
|
||||||
|
>
|
||||||
|
<-- {{ "Less" | tr }}
|
||||||
|
</button>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
<td align="right">
|
||||||
|
<form action="/accounts/" method="post">
|
||||||
|
<fieldset>
|
||||||
|
<input type="hidden" name="O"
|
||||||
|
value="{{ offset + pp }}" />
|
||||||
|
{% for k, v in params.items() %}
|
||||||
|
<input type="hidden" name="{{ k }}"
|
||||||
|
value="{{ v }}" />
|
||||||
|
{% endfor %}
|
||||||
|
<button type="submit" class="button page-next"
|
||||||
|
{% if offset + pp >= total_users %}
|
||||||
|
disabled
|
||||||
|
{% endif %}
|
||||||
|
>
|
||||||
|
{{ "More" | tr }}-->
|
||||||
|
</button>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@ from aurweb import captcha
|
||||||
from aurweb.asgi import app
|
from aurweb.asgi import app
|
||||||
from aurweb.db import commit, create, query
|
from aurweb.db import commit, create, query
|
||||||
from aurweb.models.accepted_term import AcceptedTerm
|
from aurweb.models.accepted_term import AcceptedTerm
|
||||||
from aurweb.models.account_type import AccountType
|
from aurweb.models.account_type import DEVELOPER_ID, TRUSTED_USER_AND_DEV_ID, TRUSTED_USER_ID, AccountType
|
||||||
from aurweb.models.ban import Ban
|
from aurweb.models.ban import Ban
|
||||||
from aurweb.models.session import Session
|
from aurweb.models.session import Session
|
||||||
from aurweb.models.ssh_pub_key import SSHPubKey, get_fingerprint
|
from aurweb.models.ssh_pub_key import SSHPubKey, get_fingerprint
|
||||||
|
@ -31,6 +32,8 @@ TEST_EMAIL = "test@example.org"
|
||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
user = None
|
user = None
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def make_ssh_pubkey():
|
def make_ssh_pubkey():
|
||||||
# Create a public key with ssh-keygen (this adds ssh-keygen as a
|
# Create a public key with ssh-keygen (this adds ssh-keygen as a
|
||||||
|
@ -55,8 +58,8 @@ def setup():
|
||||||
account_type = query(AccountType,
|
account_type = query(AccountType,
|
||||||
AccountType.AccountType == "User").first()
|
AccountType.AccountType == "User").first()
|
||||||
user = create(User, Username=TEST_USERNAME, Email=TEST_EMAIL,
|
user = create(User, Username=TEST_USERNAME, Email=TEST_EMAIL,
|
||||||
RealName="Test User", Passwd="testPassword",
|
RealName="Test UserZ", Passwd="testPassword",
|
||||||
AccountType=account_type)
|
IRCNick="testZ", AccountType=account_type)
|
||||||
|
|
||||||
yield user
|
yield user
|
||||||
|
|
||||||
|
@ -65,6 +68,14 @@ def setup():
|
||||||
setup_test_db("Terms", "AcceptedTerms")
|
setup_test_db("Terms", "AcceptedTerms")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def tu_user():
|
||||||
|
user.AccountType = query(AccountType,
|
||||||
|
AccountType.ID == TRUSTED_USER_AND_DEV_ID).first()
|
||||||
|
commit()
|
||||||
|
yield user
|
||||||
|
|
||||||
|
|
||||||
def test_get_passreset_authed_redirects():
|
def test_get_passreset_authed_redirects():
|
||||||
sid = user.login(Request(), "testPassword")
|
sid = user.login(Request(), "testPassword")
|
||||||
assert sid is not None
|
assert sid is not None
|
||||||
|
@ -929,6 +940,479 @@ def test_get_account_unauthenticated():
|
||||||
assert "You must log in to view user information." in content
|
assert "You must log in to view user information." in content
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_accounts(tu_user):
|
||||||
|
""" Test that we can GET request /accounts/ and receive
|
||||||
|
a form which can be used to POST /accounts/. """
|
||||||
|
sid = user.login(Request(), "testPassword")
|
||||||
|
cookies = {"AURSID": sid}
|
||||||
|
|
||||||
|
with client as request:
|
||||||
|
response = request.get("/accounts/", cookies=cookies)
|
||||||
|
assert response.status_code == int(HTTPStatus.OK)
|
||||||
|
|
||||||
|
parser = lxml.etree.HTMLParser()
|
||||||
|
root = lxml.etree.fromstring(response.text, parser=parser)
|
||||||
|
|
||||||
|
# Get the form.
|
||||||
|
form = root.xpath('//form[contains(@class, "account-search-form")]')
|
||||||
|
|
||||||
|
# Make sure there's only one form and it goes where it should.
|
||||||
|
assert len(form) == 1
|
||||||
|
form = next(iter(form))
|
||||||
|
assert form.attrib.get("method") == "post"
|
||||||
|
assert form.attrib.get("action") == "/accounts/"
|
||||||
|
|
||||||
|
def field(element):
|
||||||
|
""" Return the given element string as a valid
|
||||||
|
selector in the form. """
|
||||||
|
return f"./fieldset/p/{element}"
|
||||||
|
|
||||||
|
username = form.xpath(field('input[@id="id_username"]'))
|
||||||
|
assert bool(username)
|
||||||
|
|
||||||
|
account_type = form.xpath(field('select[@id="id_type"]'))
|
||||||
|
assert bool(account_type)
|
||||||
|
|
||||||
|
suspended = form.xpath(field('input[@id="id_suspended"]'))
|
||||||
|
assert bool(suspended)
|
||||||
|
|
||||||
|
email = form.xpath(field('input[@id="id_email"]'))
|
||||||
|
assert bool(email)
|
||||||
|
|
||||||
|
realname = form.xpath(field('input[@id="id_realname"]'))
|
||||||
|
assert bool(realname)
|
||||||
|
|
||||||
|
irc = form.xpath(field('input[@id="id_irc"]'))
|
||||||
|
assert bool(irc)
|
||||||
|
|
||||||
|
sortby = form.xpath(field('select[@id="id_sortby"]'))
|
||||||
|
assert bool(sortby)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_root(html):
|
||||||
|
parser = lxml.etree.HTMLParser()
|
||||||
|
return lxml.etree.fromstring(html, parser=parser)
|
||||||
|
|
||||||
|
|
||||||
|
def get_rows(html):
|
||||||
|
root = parse_root(html)
|
||||||
|
return root.xpath('//table[contains(@class, "users")]/tbody/tr')
|
||||||
|
|
||||||
|
|
||||||
|
def test_post_accounts(tu_user):
|
||||||
|
# Set a PGPKey.
|
||||||
|
user.PGPKey = "5F18B20346188419750745D7335F2CB41F253D30"
|
||||||
|
|
||||||
|
# Create a few more users.
|
||||||
|
users = [user]
|
||||||
|
for i in range(10):
|
||||||
|
_user = create(User, Username=f"test_{i}",
|
||||||
|
Email=f"test_{i}@example.org",
|
||||||
|
RealName=f"Test #{i}",
|
||||||
|
Passwd="testPassword",
|
||||||
|
IRCNick=f"test_#{i}",
|
||||||
|
autocommit=False)
|
||||||
|
users.append(_user)
|
||||||
|
|
||||||
|
# Commit everything to the database.
|
||||||
|
commit()
|
||||||
|
|
||||||
|
sid = user.login(Request(), "testPassword")
|
||||||
|
cookies = {"AURSID": sid}
|
||||||
|
|
||||||
|
with client as request:
|
||||||
|
response = request.post("/accounts/", cookies=cookies)
|
||||||
|
assert response.status_code == int(HTTPStatus.OK)
|
||||||
|
|
||||||
|
rows = get_rows(response.text)
|
||||||
|
assert len(rows) == 11
|
||||||
|
|
||||||
|
# Simulate default ascending ORDER_BY.
|
||||||
|
sorted_users = sorted(users, key=lambda u: u.Username)
|
||||||
|
for i, _user in enumerate(sorted_users):
|
||||||
|
columns = rows[i].xpath("./td")
|
||||||
|
assert len(columns) == 7
|
||||||
|
|
||||||
|
username, atype, suspended, real_name, \
|
||||||
|
irc_nick, pgp_key, edit = columns
|
||||||
|
|
||||||
|
username = next(iter(username.xpath("./a")))
|
||||||
|
assert username.text.strip() == _user.Username
|
||||||
|
|
||||||
|
assert atype.text.strip() == str(_user.AccountType)
|
||||||
|
assert suspended.text.strip() == "Active"
|
||||||
|
assert real_name.text.strip() == _user.RealName
|
||||||
|
assert irc_nick.text == _user.IRCNick
|
||||||
|
assert pgp_key.text == (_user.PGPKey or None)
|
||||||
|
|
||||||
|
edit = edit.xpath("./a")
|
||||||
|
if user.can_edit_user(_user):
|
||||||
|
edit = next(iter(edit))
|
||||||
|
assert edit.text.strip() == "Edit"
|
||||||
|
else:
|
||||||
|
assert not edit
|
||||||
|
|
||||||
|
logger.debug('Checked user row {"id": %s, "username": "%s"}.'
|
||||||
|
% (_user.ID, _user.Username))
|
||||||
|
|
||||||
|
|
||||||
|
def test_post_accounts_username(tu_user):
|
||||||
|
# Test the U parameter path.
|
||||||
|
sid = user.login(Request(), "testPassword")
|
||||||
|
cookies = {"AURSID": sid}
|
||||||
|
|
||||||
|
with client as request:
|
||||||
|
response = request.post("/accounts/", cookies=cookies,
|
||||||
|
data={"U": user.Username})
|
||||||
|
assert response.status_code == int(HTTPStatus.OK)
|
||||||
|
|
||||||
|
rows = get_rows(response.text)
|
||||||
|
assert len(rows) == 1
|
||||||
|
|
||||||
|
row = next(iter(rows))
|
||||||
|
username, type, status, realname, irc, pgp_key, edit = row
|
||||||
|
|
||||||
|
username = next(iter(username.xpath("./a")))
|
||||||
|
assert username.text.strip() == user.Username
|
||||||
|
|
||||||
|
|
||||||
|
def test_post_accounts_account_type(tu_user):
|
||||||
|
# Check the different account type options.
|
||||||
|
sid = user.login(Request(), "testPassword")
|
||||||
|
cookies = {"AURSID": sid}
|
||||||
|
|
||||||
|
# Make a user with the "User" role here so we can
|
||||||
|
# test the `u` parameter.
|
||||||
|
account_type = query(AccountType,
|
||||||
|
AccountType.AccountType == "User").first()
|
||||||
|
create(User, Username="test_2",
|
||||||
|
Email="test_2@example.org",
|
||||||
|
RealName="Test User 2",
|
||||||
|
Passwd="testPassword",
|
||||||
|
AccountType=account_type)
|
||||||
|
|
||||||
|
# Expect no entries; we marked our only user as a User type.
|
||||||
|
with client as request:
|
||||||
|
response = request.post("/accounts/", cookies=cookies,
|
||||||
|
data={"T": "t"})
|
||||||
|
assert response.status_code == int(HTTPStatus.OK)
|
||||||
|
assert len(get_rows(response.text)) == 0
|
||||||
|
|
||||||
|
# So, let's also ensure that specifying "u" returns our user.
|
||||||
|
with client as request:
|
||||||
|
response = request.post("/accounts/", cookies=cookies,
|
||||||
|
data={"T": "u"})
|
||||||
|
assert response.status_code == int(HTTPStatus.OK)
|
||||||
|
|
||||||
|
rows = get_rows(response.text)
|
||||||
|
assert len(rows) == 1
|
||||||
|
|
||||||
|
row = next(iter(rows))
|
||||||
|
username, type, status, realname, irc, pgp_key, edit = row
|
||||||
|
|
||||||
|
assert type.text.strip() == "User"
|
||||||
|
|
||||||
|
# Set our only user to a Trusted User.
|
||||||
|
user.AccountType = query(AccountType,
|
||||||
|
AccountType.ID == TRUSTED_USER_ID).first()
|
||||||
|
commit()
|
||||||
|
|
||||||
|
with client as request:
|
||||||
|
response = request.post("/accounts/", cookies=cookies,
|
||||||
|
data={"T": "t"})
|
||||||
|
assert response.status_code == int(HTTPStatus.OK)
|
||||||
|
|
||||||
|
rows = get_rows(response.text)
|
||||||
|
assert len(rows) == 1
|
||||||
|
|
||||||
|
row = next(iter(rows))
|
||||||
|
username, type, status, realname, irc, pgp_key, edit = row
|
||||||
|
|
||||||
|
assert type.text.strip() == "Trusted User"
|
||||||
|
|
||||||
|
user.AccountType = query(AccountType,
|
||||||
|
AccountType.ID == DEVELOPER_ID).first()
|
||||||
|
commit()
|
||||||
|
|
||||||
|
with client as request:
|
||||||
|
response = request.post("/accounts/", cookies=cookies,
|
||||||
|
data={"T": "d"})
|
||||||
|
assert response.status_code == int(HTTPStatus.OK)
|
||||||
|
|
||||||
|
rows = get_rows(response.text)
|
||||||
|
assert len(rows) == 1
|
||||||
|
|
||||||
|
row = next(iter(rows))
|
||||||
|
username, type, status, realname, irc, pgp_key, edit = row
|
||||||
|
|
||||||
|
assert type.text.strip() == "Developer"
|
||||||
|
|
||||||
|
user.AccountType = query(AccountType,
|
||||||
|
AccountType.ID == TRUSTED_USER_AND_DEV_ID
|
||||||
|
).first()
|
||||||
|
commit()
|
||||||
|
|
||||||
|
with client as request:
|
||||||
|
response = request.post("/accounts/", cookies=cookies,
|
||||||
|
data={"T": "td"})
|
||||||
|
assert response.status_code == int(HTTPStatus.OK)
|
||||||
|
|
||||||
|
rows = get_rows(response.text)
|
||||||
|
assert len(rows) == 1
|
||||||
|
|
||||||
|
row = next(iter(rows))
|
||||||
|
username, type, status, realname, irc, pgp_key, edit = row
|
||||||
|
|
||||||
|
assert type.text.strip() == "Trusted User & Developer"
|
||||||
|
|
||||||
|
|
||||||
|
def test_post_accounts_status(tu_user):
|
||||||
|
# Test the functionality of Suspended.
|
||||||
|
sid = user.login(Request(), "testPassword")
|
||||||
|
cookies = {"AURSID": sid}
|
||||||
|
|
||||||
|
with client as request:
|
||||||
|
response = request.post("/accounts/", cookies=cookies)
|
||||||
|
assert response.status_code == int(HTTPStatus.OK)
|
||||||
|
|
||||||
|
rows = get_rows(response.text)
|
||||||
|
assert len(rows) == 1
|
||||||
|
|
||||||
|
row = next(iter(rows))
|
||||||
|
username, type, status, realname, irc, pgp_key, edit = row
|
||||||
|
assert status.text.strip() == "Active"
|
||||||
|
|
||||||
|
user.Suspended = True
|
||||||
|
commit()
|
||||||
|
|
||||||
|
with client as request:
|
||||||
|
response = request.post("/accounts/", cookies=cookies,
|
||||||
|
data={"S": True})
|
||||||
|
assert response.status_code == int(HTTPStatus.OK)
|
||||||
|
|
||||||
|
rows = get_rows(response.text)
|
||||||
|
assert len(rows) == 1
|
||||||
|
|
||||||
|
row = next(iter(rows))
|
||||||
|
username, type, status, realname, irc, pgp_key, edit = row
|
||||||
|
assert status.text.strip() == "Suspended"
|
||||||
|
|
||||||
|
|
||||||
|
def test_post_accounts_email(tu_user):
|
||||||
|
sid = user.login(Request(), "testPassword")
|
||||||
|
cookies = {"AURSID": sid}
|
||||||
|
|
||||||
|
# Search via email.
|
||||||
|
with client as request:
|
||||||
|
response = request.post("/accounts/", cookies=cookies,
|
||||||
|
data={"E": user.Email})
|
||||||
|
assert response.status_code == int(HTTPStatus.OK)
|
||||||
|
|
||||||
|
rows = get_rows(response.text)
|
||||||
|
assert len(rows) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_post_accounts_realname(tu_user):
|
||||||
|
# Test the R parameter path.
|
||||||
|
sid = user.login(Request(), "testPassword")
|
||||||
|
cookies = {"AURSID": sid}
|
||||||
|
|
||||||
|
with client as request:
|
||||||
|
response = request.post("/accounts/", cookies=cookies,
|
||||||
|
data={"R": user.RealName})
|
||||||
|
assert response.status_code == int(HTTPStatus.OK)
|
||||||
|
|
||||||
|
rows = get_rows(response.text)
|
||||||
|
assert len(rows) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_post_accounts_irc(tu_user):
|
||||||
|
# Test the I parameter path.
|
||||||
|
sid = user.login(Request(), "testPassword")
|
||||||
|
cookies = {"AURSID": sid}
|
||||||
|
|
||||||
|
with client as request:
|
||||||
|
response = request.post("/accounts/", cookies=cookies,
|
||||||
|
data={"I": user.IRCNick})
|
||||||
|
assert response.status_code == int(HTTPStatus.OK)
|
||||||
|
|
||||||
|
rows = get_rows(response.text)
|
||||||
|
assert len(rows) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_post_accounts_sortby(tu_user):
|
||||||
|
# Create a second user so we can compare sorts.
|
||||||
|
account_type = query(AccountType,
|
||||||
|
AccountType.ID == DEVELOPER_ID).first()
|
||||||
|
create(User, Username="test2",
|
||||||
|
Email="test2@example.org",
|
||||||
|
RealName="Test User 2",
|
||||||
|
Passwd="testPassword",
|
||||||
|
IRCNick="test2",
|
||||||
|
AccountType=account_type)
|
||||||
|
|
||||||
|
sid = user.login(Request(), "testPassword")
|
||||||
|
cookies = {"AURSID": sid}
|
||||||
|
|
||||||
|
# Show that "u" is the default search order, by username.
|
||||||
|
with client as request:
|
||||||
|
response = request.post("/accounts/", cookies=cookies)
|
||||||
|
assert response.status_code == int(HTTPStatus.OK)
|
||||||
|
rows = get_rows(response.text)
|
||||||
|
assert len(rows) == 2
|
||||||
|
first_rows = rows
|
||||||
|
|
||||||
|
with client as request:
|
||||||
|
response = request.post("/accounts/", cookies=cookies,
|
||||||
|
data={"SB": "u"})
|
||||||
|
assert response.status_code == int(HTTPStatus.OK)
|
||||||
|
rows = get_rows(response.text)
|
||||||
|
assert len(rows) == 2
|
||||||
|
|
||||||
|
def compare_text_values(column, lhs, rhs):
|
||||||
|
return [row[column].text.strip() for row in lhs] \
|
||||||
|
== [row[column].text.strip() for row in rhs]
|
||||||
|
|
||||||
|
# Test the username rows are ordered the same.
|
||||||
|
assert compare_text_values(0, first_rows, rows) is True
|
||||||
|
|
||||||
|
with client as request:
|
||||||
|
response = request.post("/accounts/", cookies=cookies,
|
||||||
|
data={"SB": "i"})
|
||||||
|
assert response.status_code == int(HTTPStatus.OK)
|
||||||
|
rows = get_rows(response.text)
|
||||||
|
assert len(rows) == 2
|
||||||
|
|
||||||
|
# Test the rows are reversed when ordering by IRCNick.
|
||||||
|
assert compare_text_values(4, first_rows, reversed(rows)) is True
|
||||||
|
|
||||||
|
# Sort by "i" -> RealName.
|
||||||
|
with client as request:
|
||||||
|
response = request.post("/accounts/", cookies=cookies,
|
||||||
|
data={"SB": "r"})
|
||||||
|
assert response.status_code == int(HTTPStatus.OK)
|
||||||
|
rows = get_rows(response.text)
|
||||||
|
assert len(rows) == 2
|
||||||
|
|
||||||
|
# Test the rows are reversed when ordering by RealName.
|
||||||
|
assert compare_text_values(4, first_rows, reversed(rows)) is True
|
||||||
|
|
||||||
|
user.AccountType = query(AccountType,
|
||||||
|
AccountType.ID == TRUSTED_USER_AND_DEV_ID).first()
|
||||||
|
commit()
|
||||||
|
|
||||||
|
# Fetch first_rows again with our new AccountType ordering.
|
||||||
|
with client as request:
|
||||||
|
response = request.post("/accounts/", cookies=cookies)
|
||||||
|
assert response.status_code == int(HTTPStatus.OK)
|
||||||
|
rows = get_rows(response.text)
|
||||||
|
assert len(rows) == 2
|
||||||
|
first_rows = rows
|
||||||
|
|
||||||
|
# Sort by "t" -> AccountType.
|
||||||
|
with client as request:
|
||||||
|
response = request.post("/accounts/", cookies=cookies,
|
||||||
|
data={"SB": "t"})
|
||||||
|
assert response.status_code == int(HTTPStatus.OK)
|
||||||
|
rows = get_rows(response.text)
|
||||||
|
assert len(rows) == 2
|
||||||
|
|
||||||
|
# Test that rows again got reversed.
|
||||||
|
assert compare_text_values(1, first_rows, reversed(rows))
|
||||||
|
|
||||||
|
|
||||||
|
def test_post_accounts_pgp_key(tu_user):
|
||||||
|
user.PGPKey = "5F18B20346188419750745D7335F2CB41F253D30"
|
||||||
|
commit()
|
||||||
|
|
||||||
|
sid = user.login(Request(), "testPassword")
|
||||||
|
cookies = {"AURSID": sid}
|
||||||
|
|
||||||
|
# Search via PGPKey.
|
||||||
|
with client as request:
|
||||||
|
response = request.post("/accounts/", cookies=cookies,
|
||||||
|
data={"K": user.PGPKey})
|
||||||
|
assert response.status_code == int(HTTPStatus.OK)
|
||||||
|
|
||||||
|
rows = get_rows(response.text)
|
||||||
|
assert len(rows) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_post_accounts_paged(tu_user):
|
||||||
|
# Create 150 users.
|
||||||
|
users = [user]
|
||||||
|
account_type = query(AccountType,
|
||||||
|
AccountType.AccountType == "User").first()
|
||||||
|
for i in range(150):
|
||||||
|
_user = create(User, Username=f"test_#{i}",
|
||||||
|
Email=f"test_#{i}@example.org",
|
||||||
|
RealName=f"Test User #{i}",
|
||||||
|
Passwd="testPassword",
|
||||||
|
AccountType=account_type,
|
||||||
|
autocommit=False)
|
||||||
|
users.append(_user)
|
||||||
|
commit()
|
||||||
|
|
||||||
|
sid = user.login(Request(), "testPassword")
|
||||||
|
cookies = {"AURSID": sid}
|
||||||
|
|
||||||
|
with client as request:
|
||||||
|
response = request.post("/accounts/", cookies=cookies)
|
||||||
|
assert response.status_code == int(HTTPStatus.OK)
|
||||||
|
|
||||||
|
rows = get_rows(response.text)
|
||||||
|
assert len(rows) == 50 # `pp`, or hits per page is defined at 50.
|
||||||
|
|
||||||
|
# Sort users in ascending default sort by order.
|
||||||
|
sorted_users = sorted(users, key=lambda u: u.Username)
|
||||||
|
|
||||||
|
# Get the first fifty sorted users and assert that's what
|
||||||
|
# we got in the first search result page.
|
||||||
|
first_fifty = sorted_users[:50]
|
||||||
|
|
||||||
|
for i, _user in enumerate(first_fifty):
|
||||||
|
row = rows[i]
|
||||||
|
username = row[0].xpath("./a")[0] # First column
|
||||||
|
assert username.text.strip() == _user.Username
|
||||||
|
|
||||||
|
root = parse_root(response.text)
|
||||||
|
page_prev = root.xpath('//button[contains(@class, "page-prev")]')[0]
|
||||||
|
page_next = root.xpath('//button[contains(@class, "page-next")]')[0]
|
||||||
|
|
||||||
|
assert page_prev.attrib["disabled"] == "disabled"
|
||||||
|
assert "disabled" not in page_next.attrib
|
||||||
|
|
||||||
|
with client as request:
|
||||||
|
response = request.post("/accounts/", cookies=cookies,
|
||||||
|
data={"O": 50}) # +50 offset.
|
||||||
|
assert response.status_code == int(HTTPStatus.OK)
|
||||||
|
|
||||||
|
rows = get_rows(response.text)
|
||||||
|
assert len(rows) == 50
|
||||||
|
|
||||||
|
second_fifty = sorted_users[50:100]
|
||||||
|
|
||||||
|
for i, _user in enumerate(second_fifty):
|
||||||
|
row = rows[i]
|
||||||
|
username = row[0].xpath("./a")[0] # First column
|
||||||
|
assert username.text.strip() == _user.Username
|
||||||
|
|
||||||
|
with client as request:
|
||||||
|
response = request.post("/accounts/", cookies=cookies,
|
||||||
|
data={"O": 101}) # Last page.
|
||||||
|
assert response.status_code == int(HTTPStatus.OK)
|
||||||
|
|
||||||
|
rows = get_rows(response.text)
|
||||||
|
assert len(rows) == 50
|
||||||
|
|
||||||
|
root = parse_root(response.text)
|
||||||
|
page_prev = root.xpath('//button[contains(@class, "page-prev")]')[0]
|
||||||
|
page_next = root.xpath('//button[contains(@class, "page-next")]')[0]
|
||||||
|
|
||||||
|
assert "disabled" not in page_prev.attrib
|
||||||
|
assert page_next.attrib["disabled"] == "disabled"
|
||||||
|
|
||||||
|
|
||||||
def test_get_terms_of_service():
|
def test_get_terms_of_service():
|
||||||
term = create(Term, Description="Test term.",
|
term = create(Term, Description="Test term.",
|
||||||
URL="http://localhost", Revision=1)
|
URL="http://localhost", Revision=1)
|
||||||
|
|
|
@ -209,6 +209,16 @@ label.confirmation,
|
||||||
margin: .33em 0 1em;
|
margin: .33em 0 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
button[type="submit"] {
|
button[type="submit"],
|
||||||
|
button[type="reset"] {
|
||||||
padding: 0 0.6em;
|
padding: 0 0.6em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.results tr td[align="left"] fieldset {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results tr td[align="right"] fieldset {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue