feat: allow TUs to change their votes on running proposals

In addition, this patch brings in display for the vote decision
you cast on it. This is only viewable by the request user; your
vote is not being shared with others.

Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
Kevin Morris 2022-03-07 23:13:08 -08:00
parent cf4ab696d0
commit 9e3c2e15ea
No known key found for this signature in database
GPG key ID: F7E46DED420788F3
5 changed files with 150 additions and 73 deletions

View file

@ -1,3 +1,4 @@
import html
import re
from http import HTTPStatus
@ -11,7 +12,7 @@ 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 YES_ID, TUVote
from aurweb.models.tu_vote import ABSTAIN_ID, YES_ID, TUVote
from aurweb.models.tu_voteinfo import TUVoteInfo
from aurweb.models.user import User
from aurweb.testing.requests import Request
@ -586,16 +587,21 @@ def test_tu_running_proposal(client: TestClient,
"/tu", params={"id": voteinfo.ID}, cookies=cookies)
assert response.status_code == int(HTTPStatus.OK)
# Check that we're told we've voted.
# Check that our vote decision is displayed.
content = html.unescape(response.text)
assert "You've already voted for this proposal." in content
expected = "You can change your vote while the proposal is still running."
assert expected in content
assert "Your vote:" in content
assert "<strong>Yes</strong>" in content
# Parse our new root.
root = parse_root(response.text)
# Check that we no longer have a voting form.
# Check that we still 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."
assert form is not None
def test_tu_ended_proposal(client, proposal):
@ -605,11 +611,11 @@ def test_tu_ended_proposal(client, proposal):
with db.begin():
voteinfo.End = ts - 5 # 5 seconds ago.
# Initiate an authenticated GET request to /tu/{proposal_id}.
proposal_id = voteinfo.ID
# Initiate an authenticated GET request to /tu/{voteinfo.ID}.
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
endpoint = f"/tu/{voteinfo.ID}"
with client as request:
response = request.get(f"/tu/{proposal_id}", cookies=cookies)
response = request.get(f"/tu/{voteinfo.ID}", cookies=cookies)
assert response.status_code == int(HTTPStatus.OK)
# Alright, now let's continue on to verifying some markup.
@ -627,7 +633,7 @@ def test_tu_ended_proposal(client, proposal):
result = result_node.xpath("./span/text()")[0]
assert result.strip() == "unknown"
# Check that voting has ended.
# Check that the form is gone; voting has ended.
form = root.xpath('//form[contains(@class, "action-form")]')
assert not form
@ -635,6 +641,19 @@ def test_tu_ended_proposal(client, proposal):
status = root.xpath('//span[contains(@class, "status")]/text()')[0]
assert status == "Voting is closed for this proposal."
# Perform a POST request and expect the same behavior.
data = {"decision": "Yes"}
with client as request:
resp = request.post(endpoint, data=data, cookies=cookies)
assert resp.status_code == HTTPStatus.BAD_REQUEST
# Repeat the same assertions as we did in the GET request.
root = parse_root(resp.text)
form = root.xpath('//form[contains(@class, "action-form")]')
assert not form
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. """
@ -667,11 +686,13 @@ def test_tu_proposal_vote(client, proposal):
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."
# Assert that we've gotten the message that we've voted.
content = html.unescape(response.text)
assert "You've already voted for this proposal." in content
expected = "You can change your vote while the proposal is still running."
assert expected in content
assert "Your vote:" in content
assert "<strong>Yes</strong>" in content
def test_tu_proposal_vote_unauthorized(
@ -732,36 +753,6 @@ def test_tu_proposal_vote_cant_self_vote(client, proposal):
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
@ -881,3 +872,32 @@ def test_tu_addvote_post_bylaws(client: TestClient, tu_user: User):
with client as request:
response = request.post("/addvote", cookies=cookies, data=data)
assert response.status_code == int(HTTPStatus.SEE_OTHER)
def test_tu_change_vote(client: TestClient, tu_user: User,
proposal: TUVoteInfo):
_, _, voteinfo = proposal
cookies = {"AURSID": tu_user.login(Request(), "testPassword")}
endpoint = f"/tu/{voteinfo.ID}"
# First, make an Abstain vote.
data = {"decision": "Abstain"}
with client as request:
resp = request.post(endpoint, data=data, cookies=cookies)
assert resp.status_code == HTTPStatus.OK
vote = db.query(TUVote).filter(TUVote.VoteID == voteinfo.ID).first()
assert vote is not None
assert vote.Decision == ABSTAIN_ID
assert voteinfo.Abstain == 1
# Changed our mind! Vote yes, instead.
data = {"decision": "Yes"}
with client as request:
resp = request.post(endpoint, data=data, cookies=cookies)
assert resp.status_code == HTTPStatus.OK
# Expect that the records got changed correctly.
assert vote.Decision == YES_ID
assert voteinfo.Abstain == 0
assert voteinfo.Yes == 1