mirror of
https://gitlab.archlinux.org/archlinux/aurweb.git
synced 2025-02-03 10:43:03 +01:00
By implicitly joining, sqlalchemy joined on `TUVote.UsersID = TUVoteInfo.SubmitterID`. This should be joining on `TUVote.VoteID = TUVoteInfo.ID` instead to include all TUVote instances found in the database. Closes #266 Signed-off-by: Kevin Morris <kevr@0cost.org>
884 lines
30 KiB
Python
884 lines
30 KiB
Python
import re
|
|
|
|
from http import HTTPStatus
|
|
from io import StringIO
|
|
from typing import Tuple
|
|
|
|
import lxml.etree
|
|
import pytest
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
from aurweb import config, db, filters, time
|
|
from aurweb.models.account_type import DEVELOPER_ID, TRUSTED_USER_ID, AccountType
|
|
from aurweb.models.tu_vote import TUVote
|
|
from aurweb.models.tu_voteinfo import TUVoteInfo
|
|
from aurweb.models.user import User
|
|
from aurweb.testing.requests import Request
|
|
|
|
DATETIME_REGEX = r'^[0-9]{4}-[0-9]{2}-[0-9]{2} \(.+\)$'
|
|
PARTICIPATION_REGEX = r'^1?[0-9]{2}[%]$' # 0% - 100%
|
|
|
|
|
|
def parse_root(html):
|
|
parser = lxml.etree.HTMLParser(recover=True)
|
|
tree = lxml.etree.parse(StringIO(html), parser)
|
|
return tree.getroot()
|
|
|
|
|
|
def get_table(root, class_name):
|
|
table = root.xpath(f'//table[contains(@class, "{class_name}")]')[0]
|
|
return table
|
|
|
|
|
|
def get_table_rows(table):
|
|
tbody = table.xpath("./tbody")[0]
|
|
return tbody.xpath("./tr")
|
|
|
|
|
|
def get_pkglist_directions(table):
|
|
stats = table.getparent().xpath("./div[@class='pkglist-stats']")[0]
|
|
nav = stats.xpath("./p[@class='pkglist-nav']")[0]
|
|
return nav.xpath("./a")
|
|
|
|
|
|
def get_a(node):
|
|
return node.xpath('./a')[0].text.strip()
|
|
|
|
|
|
def get_span(node):
|
|
return node.xpath('./span')[0].text.strip()
|
|
|
|
|
|
def assert_current_vote_html(row, expected):
|
|
columns = row.xpath("./td")
|
|
proposal, start, end, user, voted = columns
|
|
p, s, e, u, v = expected # Column expectations.
|
|
assert re.match(p, get_a(proposal)) is not None
|
|
assert re.match(s, start.text) is not None
|
|
assert re.match(e, end.text) is not None
|
|
assert re.match(u, get_a(user)) is not None
|
|
assert re.match(v, get_span(voted)) is not None
|
|
|
|
|
|
def assert_past_vote_html(row, expected):
|
|
columns = row.xpath("./td")
|
|
proposal, start, end, user, yes, no, voted = columns # Real columns.
|
|
p, s, e, u, y, n, v = expected # Column expectations.
|
|
assert re.match(p, get_a(proposal)) is not None
|
|
assert re.match(s, start.text) is not None
|
|
assert re.match(e, end.text) is not None
|
|
assert re.match(u, get_a(user)) is not None
|
|
assert re.match(y, yes.text) is not None
|
|
assert re.match(n, no.text) is not None
|
|
assert re.match(v, get_span(voted)) is not None
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup(db_test):
|
|
return
|
|
|
|
|
|
@pytest.fixture
|
|
def client():
|
|
from aurweb.asgi import app
|
|
yield TestClient(app=app)
|
|
|
|
|
|
@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
|
|
def tu_user2():
|
|
with db.begin():
|
|
tu_user2 = db.create(User, Username="test_tu2",
|
|
Email="test_tu2@example.org",
|
|
RealName="Test TU 2", Passwd="testPassword",
|
|
AccountTypeID=TRUSTED_USER_ID)
|
|
yield tu_user2
|
|
|
|
|
|
@pytest.fixture
|
|
def user():
|
|
user_type = db.query(AccountType,
|
|
AccountType.AccountType == "User").first()
|
|
with db.begin():
|
|
user = db.create(User, Username="test", Email="test@example.org",
|
|
RealName="Test User", Passwd="testPassword",
|
|
AccountType=user_type)
|
|
yield user
|
|
|
|
|
|
@pytest.fixture
|
|
def proposal(user, tu_user):
|
|
ts = time.utcnow()
|
|
agenda = "Test proposal."
|
|
start = ts - 5
|
|
end = ts + 1000
|
|
|
|
with db.begin():
|
|
voteinfo = db.create(TUVoteInfo,
|
|
Agenda=agenda, Quorum=0.0,
|
|
User=user.Username, Submitter=tu_user,
|
|
Submitted=start, End=end)
|
|
yield (tu_user, user, voteinfo)
|
|
|
|
|
|
def test_tu_index_guest(client):
|
|
headers = {"referer": config.get("options", "aur_location") + "/tu"}
|
|
with client as request:
|
|
response = request.get("/tu", allow_redirects=False, headers=headers)
|
|
assert response.status_code == int(HTTPStatus.SEE_OTHER)
|
|
|
|
params = filters.urlencode({"next": "/tu"})
|
|
assert response.headers.get("location") == f"/login?{params}"
|
|
|
|
|
|
def test_tu_index_unauthorized(client: TestClient, user: User):
|
|
cookies = {"AURSID": user.login(Request(), "testPassword")}
|
|
with client as request:
|
|
# Login as a normal user, not a TU.
|
|
response = request.get("/tu", cookies=cookies, allow_redirects=False)
|
|
assert response.status_code == int(HTTPStatus.SEE_OTHER)
|
|
assert response.headers.get("location") == "/"
|
|
|
|
|
|
def test_tu_empty_index(client, tu_user):
|
|
""" Check an empty index when we don't create any records. """
|
|
|
|
# Make a default get request to /tu.
|
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
|
with client as request:
|
|
response = request.get("/tu", cookies=cookies, allow_redirects=False)
|
|
assert response.status_code == int(HTTPStatus.OK)
|
|
|
|
# Parse lxml root.
|
|
root = parse_root(response.text)
|
|
|
|
# Check that .current-votes does not exist.
|
|
tables = root.xpath('//table[contains(@class, "current-votes")]')
|
|
assert len(tables) == 0
|
|
|
|
# Check that .past-votes has does not exist.
|
|
tables = root.xpath('//table[contains(@class, "current-votes")]')
|
|
assert len(tables) == 0
|
|
|
|
|
|
def test_tu_index(client, tu_user):
|
|
ts = time.utcnow()
|
|
|
|
# Create some test votes: (Agenda, Start, End).
|
|
votes = [
|
|
("Test agenda 1", ts - 5, ts + 1000), # Still running.
|
|
("Test agenda 2", ts - 1000, ts - 5) # Not running anymore.
|
|
]
|
|
vote_records = []
|
|
with db.begin():
|
|
for vote in votes:
|
|
agenda, start, end = vote
|
|
vote_records.append(
|
|
db.create(TUVoteInfo, Agenda=agenda,
|
|
User=tu_user.Username,
|
|
Submitted=start, End=end,
|
|
Quorum=0.0,
|
|
Submitter=tu_user))
|
|
|
|
with db.begin():
|
|
# Vote on an ended proposal.
|
|
vote_record = vote_records[1]
|
|
vote_record.Yes += 1
|
|
vote_record.ActiveTUs += 1
|
|
db.create(TUVote, VoteInfo=vote_record, User=tu_user)
|
|
|
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
|
with client as request:
|
|
# Pass an invalid cby and pby; let them default to "desc".
|
|
response = request.get("/tu", cookies=cookies, params={
|
|
"cby": "BAD!",
|
|
"pby": "blah"
|
|
}, allow_redirects=False)
|
|
|
|
assert response.status_code == int(HTTPStatus.OK)
|
|
|
|
# Rows we expect to exist in HTML produced by /tu for current votes.
|
|
expected_rows = [
|
|
(
|
|
r'Test agenda 1',
|
|
DATETIME_REGEX,
|
|
DATETIME_REGEX,
|
|
tu_user.Username,
|
|
r'^(Yes|No)$'
|
|
)
|
|
]
|
|
|
|
# Assert that we are matching the number of current votes.
|
|
current_votes = [c for c in votes if c[2] > ts]
|
|
assert len(current_votes) == len(expected_rows)
|
|
|
|
# Parse lxml.etree root.
|
|
root = parse_root(response.text)
|
|
|
|
table = get_table(root, "current-votes")
|
|
rows = get_table_rows(table)
|
|
for i, row in enumerate(rows):
|
|
assert_current_vote_html(row, expected_rows[i])
|
|
|
|
# Assert that we are matching the number of past votes.
|
|
past_votes = [c for c in votes if c[2] <= ts]
|
|
assert len(past_votes) == len(expected_rows)
|
|
|
|
# Rows we expect to exist in HTML produced by /tu for past votes.
|
|
expected_rows = [
|
|
(
|
|
r'Test agenda 2',
|
|
DATETIME_REGEX,
|
|
DATETIME_REGEX,
|
|
tu_user.Username,
|
|
r'^\d+$',
|
|
r'^\d+$',
|
|
r'^(Yes|No)$'
|
|
)
|
|
]
|
|
|
|
table = get_table(root, "past-votes")
|
|
rows = get_table_rows(table)
|
|
for i, row in enumerate(rows):
|
|
assert_past_vote_html(row, expected_rows[i])
|
|
|
|
# Get the .last-votes table and check that our vote shows up.
|
|
table = get_table(root, "last-votes")
|
|
rows = get_table_rows(table)
|
|
assert len(rows) == 1
|
|
|
|
# Check to see the rows match up to our user and related vote.
|
|
username, vote_id = rows[0]
|
|
username = username.xpath("./a")[0]
|
|
vote_id = vote_id.xpath("./a")[0]
|
|
assert username.text.strip() == tu_user.Username
|
|
assert int(vote_id.text.strip()) == vote_records[1].ID
|
|
|
|
|
|
def test_tu_index_table_paging(client, tu_user):
|
|
ts = time.utcnow()
|
|
|
|
with db.begin():
|
|
for i in range(25):
|
|
# Create 25 current votes.
|
|
db.create(TUVoteInfo, Agenda=f"Agenda #{i}",
|
|
User=tu_user.Username,
|
|
Submitted=(ts - 5), End=(ts + 1000),
|
|
Quorum=0.0,
|
|
Submitter=tu_user)
|
|
|
|
for i in range(25):
|
|
# Create 25 past votes.
|
|
db.create(TUVoteInfo, Agenda=f"Agenda #{25 + i}",
|
|
User=tu_user.Username,
|
|
Submitted=(ts - 1000), End=(ts - 5),
|
|
Quorum=0.0,
|
|
Submitter=tu_user)
|
|
|
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
|
with client as request:
|
|
response = request.get("/tu", cookies=cookies, allow_redirects=False)
|
|
assert response.status_code == int(HTTPStatus.OK)
|
|
|
|
# Parse lxml.etree root.
|
|
root = parse_root(response.text)
|
|
|
|
table = get_table(root, "current-votes")
|
|
rows = get_table_rows(table)
|
|
assert len(rows) == 10
|
|
|
|
def make_expectation(offset, i):
|
|
return [
|
|
f"Agenda #{offset + i}",
|
|
DATETIME_REGEX,
|
|
DATETIME_REGEX,
|
|
tu_user.Username,
|
|
r'^(Yes|No)$'
|
|
]
|
|
|
|
for i, row in enumerate(rows):
|
|
assert_current_vote_html(row, make_expectation(0, i))
|
|
|
|
# Parse out Back/Next buttons.
|
|
directions = get_pkglist_directions(table)
|
|
assert len(directions) == 1
|
|
assert "Next" in directions[0].text
|
|
|
|
# Now, get the next page of current votes.
|
|
offset = 10 # Specify coff=10
|
|
with client as request:
|
|
response = request.get("/tu", cookies=cookies, params={
|
|
"coff": offset
|
|
}, allow_redirects=False)
|
|
assert response.status_code == int(HTTPStatus.OK)
|
|
|
|
old_rows = rows
|
|
root = parse_root(response.text)
|
|
|
|
table = get_table(root, "current-votes")
|
|
rows = get_table_rows(table)
|
|
assert rows != old_rows
|
|
|
|
for i, row in enumerate(rows):
|
|
assert_current_vote_html(row, make_expectation(offset, i))
|
|
|
|
# Parse out Back/Next buttons.
|
|
directions = get_pkglist_directions(table)
|
|
assert len(directions) == 2
|
|
assert "Back" in directions[0].text
|
|
assert "Next" in directions[1].text
|
|
|
|
# Make sure past-votes' Back/Next were not affected.
|
|
past_votes = get_table(root, "past-votes")
|
|
past_directions = get_pkglist_directions(past_votes)
|
|
assert len(past_directions) == 1
|
|
assert "Next" in past_directions[0].text
|
|
|
|
offset = 20 # Specify coff=10
|
|
with client as request:
|
|
response = request.get("/tu", cookies=cookies, params={
|
|
"coff": offset
|
|
}, allow_redirects=False)
|
|
assert response.status_code == int(HTTPStatus.OK)
|
|
|
|
# Do it again, we only have five left.
|
|
old_rows = rows
|
|
root = parse_root(response.text)
|
|
|
|
table = get_table(root, "current-votes")
|
|
rows = get_table_rows(table)
|
|
assert rows != old_rows
|
|
for i, row in enumerate(rows):
|
|
assert_current_vote_html(row, make_expectation(offset, i))
|
|
|
|
# Parse out Back/Next buttons.
|
|
directions = get_pkglist_directions(table)
|
|
assert len(directions) == 1
|
|
assert "Back" in directions[0].text
|
|
|
|
# Make sure past-votes' Back/Next were not affected.
|
|
past_votes = get_table(root, "past-votes")
|
|
past_directions = get_pkglist_directions(past_votes)
|
|
assert len(past_directions) == 1
|
|
assert "Next" in past_directions[0].text
|
|
|
|
|
|
def test_tu_index_sorting(client, tu_user):
|
|
ts = time.utcnow()
|
|
|
|
with db.begin():
|
|
for i in range(2):
|
|
# Create 'Agenda #1' and 'Agenda #2'.
|
|
db.create(TUVoteInfo, Agenda=f"Agenda #{i + 1}",
|
|
User=tu_user.Username,
|
|
Submitted=(ts + 5), End=(ts + 1000),
|
|
Quorum=0.0,
|
|
Submitter=tu_user)
|
|
|
|
# Let's order each vote one day after the other.
|
|
# This will allow us to test the sorting nature
|
|
# of the tables.
|
|
ts += 86405
|
|
|
|
# Make a default request to /tu.
|
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
|
with client as request:
|
|
response = request.get("/tu", cookies=cookies, allow_redirects=False)
|
|
assert response.status_code == int(HTTPStatus.OK)
|
|
|
|
# Get lxml handles of the document.
|
|
root = parse_root(response.text)
|
|
table = get_table(root, "current-votes")
|
|
rows = get_table_rows(table)
|
|
|
|
# The latest Agenda is at the top by default.
|
|
expected = [
|
|
"Agenda #2",
|
|
"Agenda #1"
|
|
]
|
|
|
|
assert len(rows) == len(expected)
|
|
for i, row in enumerate(rows):
|
|
assert_current_vote_html(row, [
|
|
expected[i],
|
|
DATETIME_REGEX,
|
|
DATETIME_REGEX,
|
|
tu_user.Username,
|
|
r'^(Yes|No)$'
|
|
])
|
|
|
|
# Make another request; one that sorts the current votes
|
|
# in ascending order instead of the default descending order.
|
|
with client as request:
|
|
response = request.get("/tu", cookies=cookies, params={
|
|
"cby": "asc"
|
|
}, allow_redirects=False)
|
|
assert response.status_code == int(HTTPStatus.OK)
|
|
|
|
# Get lxml handles of the document.
|
|
root = parse_root(response.text)
|
|
table = get_table(root, "current-votes")
|
|
rows = get_table_rows(table)
|
|
|
|
# Reverse our expectations and assert that the proposals got flipped.
|
|
rev_expected = list(reversed(expected))
|
|
assert len(rows) == len(rev_expected)
|
|
for i, row in enumerate(rows):
|
|
assert_current_vote_html(row, [
|
|
rev_expected[i],
|
|
DATETIME_REGEX,
|
|
DATETIME_REGEX,
|
|
tu_user.Username,
|
|
r'^(Yes|No)$'
|
|
])
|
|
|
|
|
|
def test_tu_index_last_votes(client: TestClient, tu_user: User, tu_user2: User,
|
|
user: User):
|
|
ts = time.utcnow()
|
|
|
|
with db.begin():
|
|
# Create a proposal which has ended.
|
|
voteinfo = db.create(TUVoteInfo, Agenda="Test agenda",
|
|
User=user.Username,
|
|
Submitted=(ts - 1000),
|
|
End=(ts - 5),
|
|
Yes=1,
|
|
No=1,
|
|
ActiveTUs=1,
|
|
Quorum=0.0,
|
|
Submitter=tu_user)
|
|
|
|
# Create a vote on it from tu_user.
|
|
db.create(TUVote, VoteInfo=voteinfo, User=tu_user)
|
|
db.create(TUVote, VoteInfo=voteinfo, User=tu_user2)
|
|
|
|
# Now, check that tu_user got populated in the .last-votes table.
|
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
|
with client as request:
|
|
response = request.get("/tu", cookies=cookies)
|
|
assert response.status_code == int(HTTPStatus.OK)
|
|
|
|
root = parse_root(response.text)
|
|
table = get_table(root, "last-votes")
|
|
rows = get_table_rows(table)
|
|
assert len(rows) == 2
|
|
|
|
last_vote = rows[0]
|
|
user, vote_id = last_vote.xpath("./td/a")
|
|
assert user.text.strip() == tu_user.Username
|
|
assert int(vote_id.text.strip()) == voteinfo.ID
|
|
|
|
last_vote = rows[1]
|
|
user, vote_id = last_vote.xpath("./td/a")
|
|
assert int(vote_id.text.strip()) == voteinfo.ID
|
|
assert user.text.strip() == tu_user2.Username
|
|
|
|
|
|
def test_tu_proposal_not_found(client, tu_user):
|
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
|
with client as request:
|
|
response = request.get("/tu", params={"id": 1}, cookies=cookies)
|
|
assert response.status_code == int(HTTPStatus.NOT_FOUND)
|
|
|
|
|
|
def test_tu_proposal_unauthorized(client: TestClient, user: User,
|
|
proposal: Tuple[User, User, TUVoteInfo]):
|
|
cookies = {"AURSID": user.login(Request(), "testPassword")}
|
|
endpoint = f"/tu/{proposal[2].ID}"
|
|
with client as request:
|
|
response = request.get(endpoint, cookies=cookies,
|
|
allow_redirects=False)
|
|
assert response.status_code == int(HTTPStatus.SEE_OTHER)
|
|
assert response.headers.get("location") == "/tu"
|
|
|
|
with client as request:
|
|
response = request.post(endpoint, cookies=cookies,
|
|
data={"decision": False},
|
|
allow_redirects=False)
|
|
assert response.status_code == int(HTTPStatus.SEE_OTHER)
|
|
assert response.headers.get("location") == "/tu"
|
|
|
|
|
|
def test_tu_running_proposal(client: TestClient,
|
|
proposal: Tuple[User, User, TUVoteInfo]):
|
|
tu_user, user, voteinfo = proposal
|
|
|
|
# Initiate an authenticated GET request to /tu/{proposal_id}.
|
|
proposal_id = voteinfo.ID
|
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
|
with client as request:
|
|
response = request.get(f"/tu/{proposal_id}", cookies=cookies)
|
|
assert response.status_code == int(HTTPStatus.OK)
|
|
|
|
# Alright, now let's continue on to verifying some markup.
|
|
# First, let's verify that the proposal details match.
|
|
root = parse_root(response.text)
|
|
details = root.xpath('//div[@class="proposal details"]')[0]
|
|
|
|
vote_running = root.xpath('//p[contains(@class, "vote-running")]')[0]
|
|
assert vote_running.text.strip() == "This vote is still running."
|
|
|
|
# Verify User field.
|
|
username = details.xpath(
|
|
'./div[contains(@class, "user")]/strong/a/text()')[0]
|
|
assert username.strip() == user.Username
|
|
|
|
submitted = details.xpath(
|
|
'./div[contains(@class, "submitted")]/text()')[0]
|
|
assert re.match(r'^Submitted: \d{4}-\d{2}-\d{2} \d{2}:\d{2} \(.+\) by$',
|
|
submitted.strip()) is not None
|
|
submitter = details.xpath('./div[contains(@class, "submitted")]/a')[0]
|
|
assert submitter.text.strip() == tu_user.Username
|
|
assert submitter.attrib["href"] == f"/account/{tu_user.Username}"
|
|
|
|
end = details.xpath('./div[contains(@class, "end")]')[0]
|
|
end_label = end.xpath("./text()")[0]
|
|
assert end_label.strip() == "End:"
|
|
|
|
end_datetime = end.xpath("./strong/text()")[0]
|
|
assert re.match(r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2} \(.+\)$',
|
|
end_datetime.strip()) is not None
|
|
|
|
# We have not voted yet. Assert that our voting form is shown.
|
|
form = root.xpath('//form[contains(@class, "action-form")]')[0]
|
|
fields = form.xpath("./fieldset")[0]
|
|
buttons = fields.xpath('./button[@name="decision"]')
|
|
assert len(buttons) == 3
|
|
|
|
# Check the button names and values.
|
|
yes, no, abstain = buttons
|
|
|
|
# Yes
|
|
assert yes.attrib["name"] == "decision"
|
|
assert yes.attrib["value"] == "Yes"
|
|
|
|
# No
|
|
assert no.attrib["name"] == "decision"
|
|
assert no.attrib["value"] == "No"
|
|
|
|
# Abstain
|
|
assert abstain.attrib["name"] == "decision"
|
|
assert abstain.attrib["value"] == "Abstain"
|
|
|
|
# Create a vote.
|
|
with db.begin():
|
|
db.create(TUVote, VoteInfo=voteinfo, User=tu_user)
|
|
voteinfo.ActiveTUs += 1
|
|
voteinfo.Yes += 1
|
|
|
|
# Make another request now that we've voted.
|
|
with client as request:
|
|
response = request.get(
|
|
"/tu", params={"id": voteinfo.ID}, cookies=cookies)
|
|
assert response.status_code == int(HTTPStatus.OK)
|
|
|
|
# Parse our new root.
|
|
root = parse_root(response.text)
|
|
|
|
# Check that we no longer have a voting form.
|
|
form = root.xpath('//form[contains(@class, "action-form")]')
|
|
assert not form
|
|
|
|
# Check that we're told we've voted.
|
|
status = root.xpath('//span[contains(@class, "status")]/text()')[0]
|
|
assert status == "You've already voted for this proposal."
|
|
|
|
|
|
def test_tu_ended_proposal(client, proposal):
|
|
tu_user, user, voteinfo = proposal
|
|
|
|
ts = time.utcnow()
|
|
with db.begin():
|
|
voteinfo.End = ts - 5 # 5 seconds ago.
|
|
|
|
# Initiate an authenticated GET request to /tu/{proposal_id}.
|
|
proposal_id = voteinfo.ID
|
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
|
with client as request:
|
|
response = request.get(f"/tu/{proposal_id}", cookies=cookies)
|
|
assert response.status_code == int(HTTPStatus.OK)
|
|
|
|
# Alright, now let's continue on to verifying some markup.
|
|
# First, let's verify that the proposal details match.
|
|
root = parse_root(response.text)
|
|
details = root.xpath('//div[@class="proposal details"]')[0]
|
|
|
|
vote_running = root.xpath('//p[contains(@class, "vote-running")]')
|
|
assert not vote_running
|
|
|
|
result_node = details.xpath('./div[contains(@class, "result")]')[0]
|
|
result_label = result_node.xpath("./text()")[0]
|
|
assert result_label.strip() == "Result:"
|
|
|
|
result = result_node.xpath("./span/text()")[0]
|
|
assert result.strip() == "unknown"
|
|
|
|
# Check that voting has ended.
|
|
form = root.xpath('//form[contains(@class, "action-form")]')
|
|
assert not form
|
|
|
|
# We should see a status about it.
|
|
status = root.xpath('//span[contains(@class, "status")]/text()')[0]
|
|
assert status == "Voting is closed for this proposal."
|
|
|
|
|
|
def test_tu_proposal_vote_not_found(client, tu_user):
|
|
""" Test POST request to a missing vote. """
|
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
|
with client as request:
|
|
data = {"decision": "Yes"}
|
|
response = request.post("/tu/1", cookies=cookies,
|
|
data=data, allow_redirects=False)
|
|
assert response.status_code == int(HTTPStatus.NOT_FOUND)
|
|
|
|
|
|
def test_tu_proposal_vote(client, proposal):
|
|
tu_user, user, voteinfo = proposal
|
|
|
|
# Store the current related values.
|
|
yes = voteinfo.Yes
|
|
active_tus = voteinfo.ActiveTUs
|
|
|
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
|
with client as request:
|
|
data = {"decision": "Yes"}
|
|
response = request.post(f"/tu/{voteinfo.ID}", cookies=cookies,
|
|
data=data)
|
|
assert response.status_code == int(HTTPStatus.OK)
|
|
|
|
# Check that the proposal record got updated.
|
|
assert voteinfo.Yes == yes + 1
|
|
assert voteinfo.ActiveTUs == active_tus + 1
|
|
|
|
# Check that the new TUVote exists.
|
|
vote = db.query(TUVote, TUVote.VoteInfo == voteinfo,
|
|
TUVote.User == tu_user).first()
|
|
assert vote is not None
|
|
|
|
root = parse_root(response.text)
|
|
|
|
# Check that we're told we've voted.
|
|
status = root.xpath('//span[contains(@class, "status")]/text()')[0]
|
|
assert status == "You've already voted for this proposal."
|
|
|
|
|
|
def test_tu_proposal_vote_unauthorized(
|
|
client: TestClient, proposal: Tuple[User, User, TUVoteInfo]):
|
|
tu_user, user, voteinfo = proposal
|
|
|
|
with db.begin():
|
|
tu_user.AccountTypeID = DEVELOPER_ID
|
|
|
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
|
with client as request:
|
|
data = {"decision": "Yes"}
|
|
response = request.post(f"/tu/{voteinfo.ID}", cookies=cookies,
|
|
data=data, allow_redirects=False)
|
|
assert response.status_code == int(HTTPStatus.UNAUTHORIZED)
|
|
|
|
root = parse_root(response.text)
|
|
status = root.xpath('//span[contains(@class, "status")]/text()')[0]
|
|
assert status == "Only Trusted Users are allowed to vote."
|
|
|
|
with client as request:
|
|
data = {"decision": "Yes"}
|
|
response = request.get(f"/tu/{voteinfo.ID}", cookies=cookies,
|
|
data=data, allow_redirects=False)
|
|
assert response.status_code == int(HTTPStatus.OK)
|
|
|
|
root = parse_root(response.text)
|
|
status = root.xpath('//span[contains(@class, "status")]/text()')[0]
|
|
assert status == "Only Trusted Users are allowed to vote."
|
|
|
|
|
|
def test_tu_proposal_vote_cant_self_vote(client, proposal):
|
|
tu_user, user, voteinfo = proposal
|
|
|
|
# Update voteinfo.User.
|
|
with db.begin():
|
|
voteinfo.User = tu_user.Username
|
|
|
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
|
with client as request:
|
|
data = {"decision": "Yes"}
|
|
response = request.post(f"/tu/{voteinfo.ID}", cookies=cookies,
|
|
data=data, allow_redirects=False)
|
|
assert response.status_code == int(HTTPStatus.BAD_REQUEST)
|
|
|
|
root = parse_root(response.text)
|
|
status = root.xpath('//span[contains(@class, "status")]/text()')[0]
|
|
assert status == "You cannot vote in an proposal about you."
|
|
|
|
with client as request:
|
|
data = {"decision": "Yes"}
|
|
response = request.get(f"/tu/{voteinfo.ID}", cookies=cookies,
|
|
data=data, allow_redirects=False)
|
|
assert response.status_code == int(HTTPStatus.OK)
|
|
|
|
root = parse_root(response.text)
|
|
status = root.xpath('//span[contains(@class, "status")]/text()')[0]
|
|
assert status == "You cannot vote in an proposal about you."
|
|
|
|
|
|
def test_tu_proposal_vote_already_voted(client, proposal):
|
|
tu_user, user, voteinfo = proposal
|
|
|
|
with db.begin():
|
|
db.create(TUVote, VoteInfo=voteinfo, User=tu_user)
|
|
voteinfo.Yes += 1
|
|
voteinfo.ActiveTUs += 1
|
|
|
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
|
with client as request:
|
|
data = {"decision": "Yes"}
|
|
response = request.post(f"/tu/{voteinfo.ID}", cookies=cookies,
|
|
data=data, allow_redirects=False)
|
|
assert response.status_code == int(HTTPStatus.BAD_REQUEST)
|
|
|
|
root = parse_root(response.text)
|
|
status = root.xpath('//span[contains(@class, "status")]/text()')[0]
|
|
assert status == "You've already voted for this proposal."
|
|
|
|
with client as request:
|
|
data = {"decision": "Yes"}
|
|
response = request.get(f"/tu/{voteinfo.ID}", cookies=cookies,
|
|
data=data, allow_redirects=False)
|
|
assert response.status_code == int(HTTPStatus.OK)
|
|
|
|
root = parse_root(response.text)
|
|
status = root.xpath('//span[contains(@class, "status")]/text()')[0]
|
|
assert status == "You've already voted for this proposal."
|
|
|
|
|
|
def test_tu_proposal_vote_invalid_decision(client, proposal):
|
|
tu_user, user, voteinfo = proposal
|
|
|
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
|
with client as request:
|
|
data = {"decision": "EVIL"}
|
|
response = request.post(f"/tu/{voteinfo.ID}", cookies=cookies,
|
|
data=data)
|
|
assert response.status_code == int(HTTPStatus.BAD_REQUEST)
|
|
assert response.text == "Invalid 'decision' value."
|
|
|
|
|
|
def test_tu_addvote(client: TestClient, tu_user: User):
|
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
|
with client as request:
|
|
response = request.get("/addvote", cookies=cookies)
|
|
assert response.status_code == int(HTTPStatus.OK)
|
|
|
|
|
|
def test_tu_addvote_unauthorized(client: TestClient, user: User,
|
|
proposal: Tuple[User, User, TUVoteInfo]):
|
|
cookies = {"AURSID": user.login(Request(), "testPassword")}
|
|
with client as request:
|
|
response = request.get("/addvote", cookies=cookies,
|
|
allow_redirects=False)
|
|
assert response.status_code == int(HTTPStatus.SEE_OTHER)
|
|
assert response.headers.get("location") == "/tu"
|
|
|
|
with client as request:
|
|
response = request.post("/addvote", cookies=cookies,
|
|
allow_redirects=False)
|
|
assert response.status_code == int(HTTPStatus.SEE_OTHER)
|
|
assert response.headers.get("location") == "/tu"
|
|
|
|
|
|
def test_tu_addvote_invalid_type(client: TestClient, tu_user: User):
|
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
|
with client as request:
|
|
response = request.get("/addvote", params={"type": "faketype"},
|
|
cookies=cookies)
|
|
assert response.status_code == int(HTTPStatus.OK)
|
|
|
|
root = parse_root(response.text)
|
|
error = root.xpath('//*[contains(@class, "error")]/text()')[0]
|
|
assert error.strip() == "Invalid type."
|
|
|
|
|
|
def test_tu_addvote_post(client: TestClient, tu_user: User, user: User):
|
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
|
|
|
data = {
|
|
"user": user.Username,
|
|
"type": "add_tu",
|
|
"agenda": "Blah"
|
|
}
|
|
|
|
with client as request:
|
|
response = request.post("/addvote", cookies=cookies, data=data)
|
|
assert response.status_code == int(HTTPStatus.SEE_OTHER)
|
|
|
|
voteinfo = db.query(TUVoteInfo, TUVoteInfo.Agenda == "Blah").first()
|
|
assert voteinfo is not None
|
|
|
|
|
|
def test_tu_addvote_post_cant_duplicate_username(client: TestClient,
|
|
tu_user: User, user: User):
|
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
|
|
|
data = {
|
|
"user": user.Username,
|
|
"type": "add_tu",
|
|
"agenda": "Blah"
|
|
}
|
|
|
|
with client as request:
|
|
response = request.post("/addvote", cookies=cookies, data=data)
|
|
assert response.status_code == int(HTTPStatus.SEE_OTHER)
|
|
|
|
voteinfo = db.query(TUVoteInfo, TUVoteInfo.Agenda == "Blah").first()
|
|
assert voteinfo is not None
|
|
|
|
with client as request:
|
|
response = request.post("/addvote", cookies=cookies, data=data)
|
|
assert response.status_code == int(HTTPStatus.BAD_REQUEST)
|
|
|
|
|
|
def test_tu_addvote_post_invalid_username(client: TestClient, tu_user: User):
|
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
|
data = {"user": "fakeusername"}
|
|
with client as request:
|
|
response = request.post("/addvote", cookies=cookies, data=data)
|
|
assert response.status_code == int(HTTPStatus.NOT_FOUND)
|
|
|
|
|
|
def test_tu_addvote_post_invalid_type(client: TestClient, tu_user: User,
|
|
user: User):
|
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
|
data = {"user": user.Username}
|
|
with client as request:
|
|
response = request.post("/addvote", cookies=cookies, data=data)
|
|
assert response.status_code == int(HTTPStatus.BAD_REQUEST)
|
|
|
|
|
|
def test_tu_addvote_post_invalid_agenda(client: TestClient,
|
|
tu_user: User, user: User):
|
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
|
data = {"user": user.Username, "type": "add_tu"}
|
|
with client as request:
|
|
response = request.post("/addvote", cookies=cookies, data=data)
|
|
assert response.status_code == int(HTTPStatus.BAD_REQUEST)
|
|
|
|
|
|
def test_tu_addvote_post_bylaws(client: TestClient, tu_user: User):
|
|
# Bylaws votes do not need a user specified.
|
|
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
|
|
data = {"type": "bylaws", "agenda": "Blah blah!"}
|
|
with client as request:
|
|
response = request.post("/addvote", cookies=cookies, data=data)
|
|
assert response.status_code == int(HTTPStatus.SEE_OTHER)
|