mirror of
https://gitlab.archlinux.org/archlinux/aurweb.git
synced 2025-02-03 10:43:03 +01:00
Merge branch 'pu_accounts' into pu
This commit is contained in:
commit
ae953ce19b
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
|
||||
}
|
||||
|
||||
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):
|
||||
return "<User(ID='%s', AccountType='%s', Username='%s')>" % (
|
||||
self.ID, str(self.AccountType), self.Username)
|
||||
|
|
|
@ -12,17 +12,18 @@ from sqlalchemy import and_, func, or_
|
|||
import aurweb.config
|
||||
|
||||
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.l10n import get_translator_for_request
|
||||
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.ssh_pub_key import SSHPubKey, get_fingerprint
|
||||
from aurweb.models.term import Term
|
||||
from aurweb.models.user import User
|
||||
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()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -591,6 +592,91 @@ async def account(request: Request, username: str):
|
|||
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,
|
||||
context: dict,
|
||||
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 tempfile
|
||||
|
||||
|
@ -14,7 +15,7 @@ from aurweb import captcha
|
|||
from aurweb.asgi import app
|
||||
from aurweb.db import commit, create, query
|
||||
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.session import Session
|
||||
from aurweb.models.ssh_pub_key import SSHPubKey, get_fingerprint
|
||||
|
@ -31,6 +32,8 @@ TEST_EMAIL = "test@example.org"
|
|||
client = TestClient(app)
|
||||
user = None
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def make_ssh_pubkey():
|
||||
# Create a public key with ssh-keygen (this adds ssh-keygen as a
|
||||
|
@ -55,8 +58,8 @@ def setup():
|
|||
account_type = query(AccountType,
|
||||
AccountType.AccountType == "User").first()
|
||||
user = create(User, Username=TEST_USERNAME, Email=TEST_EMAIL,
|
||||
RealName="Test User", Passwd="testPassword",
|
||||
AccountType=account_type)
|
||||
RealName="Test UserZ", Passwd="testPassword",
|
||||
IRCNick="testZ", AccountType=account_type)
|
||||
|
||||
yield user
|
||||
|
||||
|
@ -65,6 +68,14 @@ def setup():
|
|||
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():
|
||||
sid = user.login(Request(), "testPassword")
|
||||
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
|
||||
|
||||
|
||||
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():
|
||||
term = create(Term, Description="Test term.",
|
||||
URL="http://localhost", Revision=1)
|
||||
|
|
|
@ -209,6 +209,16 @@ label.confirmation,
|
|||
margin: .33em 0 1em;
|
||||
}
|
||||
|
||||
button[type="submit"] {
|
||||
button[type="submit"],
|
||||
button[type="reset"] {
|
||||
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