mirror of
https://gitlab.archlinux.org/archlinux/aurweb.git
synced 2025-02-03 10:43:03 +01:00
add /tu/{proposal_id} (get, post) routes
This commit ports the `/tu/?id={proposal_id}` PHP routes to FastAPI into two individual GET and POST routes. With this port of the single proposal view and POST logic, several things have changed. - The only parameter used is now `decision`, which must contain `Yes`, `No`, or `Abstain` as a string. When an invalid value is given, a BAD_REQUEST response is returned in plaintext: Invalid 'decision' value. - The `doVote` parameter has been removed. - The details section has been rearranged into a set of divs with specific classes that can be used for testing. CSS has been added to persist the layout with the element changes. - Several errors that can be discovered in the POST path now trigger their own non-200 HTTPStatus codes. Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
parent
83c038a42a
commit
85ba4a33a8
7 changed files with 571 additions and 2 deletions
|
@ -1,7 +1,11 @@
|
|||
import typing
|
||||
|
||||
from datetime import datetime
|
||||
from http import HTTPStatus
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi import APIRouter, Form, HTTPException, Request
|
||||
from fastapi.responses import Response
|
||||
from sqlalchemy import and_, or_
|
||||
|
||||
from aurweb import db
|
||||
|
@ -10,7 +14,7 @@ from aurweb.models.account_type import DEVELOPER, TRUSTED_USER, TRUSTED_USER_AND
|
|||
from aurweb.models.tu_vote import TUVote
|
||||
from aurweb.models.tu_voteinfo import TUVoteInfo
|
||||
from aurweb.models.user import User
|
||||
from aurweb.templates import make_context, render_template
|
||||
from aurweb.templates import make_context, make_variable_context, render_template
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
@ -95,3 +99,122 @@ async def trusted_user(request: Request,
|
|||
])
|
||||
|
||||
return render_template(request, "tu/index.html", context)
|
||||
|
||||
|
||||
def render_proposal(request: Request,
|
||||
context: dict,
|
||||
proposal: int,
|
||||
voteinfo: TUVoteInfo,
|
||||
voters: typing.Iterable[User],
|
||||
vote: TUVote,
|
||||
status_code: HTTPStatus = HTTPStatus.OK):
|
||||
""" Render a single TU proposal. """
|
||||
context["proposal"] = proposal
|
||||
context["voteinfo"] = voteinfo
|
||||
context["voters"] = voters
|
||||
|
||||
participation = voteinfo.ActiveTUs / voteinfo.total_votes() \
|
||||
if voteinfo.total_votes() else 0
|
||||
context["participation"] = participation
|
||||
|
||||
accepted = (voteinfo.Yes > voteinfo.ActiveTUs / 2) or \
|
||||
(participation > voteinfo.Quorum and voteinfo.Yes > voteinfo.No)
|
||||
context["accepted"] = accepted
|
||||
|
||||
can_vote = voters.filter(TUVote.User == request.user).first() is None
|
||||
context["can_vote"] = can_vote
|
||||
|
||||
if not voteinfo.is_running():
|
||||
context["error"] = "Voting is closed for this proposal."
|
||||
|
||||
context["vote"] = vote
|
||||
context["has_voted"] = vote is not None
|
||||
|
||||
return render_template(request, "tu/show.html", context,
|
||||
status_code=status_code)
|
||||
|
||||
|
||||
@router.get("/tu/{proposal}")
|
||||
@auth_required(True, redirect="/")
|
||||
@account_type_required(REQUIRED_TYPES)
|
||||
async def trusted_user_proposal(request: Request, proposal: int):
|
||||
context = await make_variable_context(request, "Trusted User")
|
||||
proposal = int(proposal)
|
||||
|
||||
voteinfo = db.query(TUVoteInfo, TUVoteInfo.ID == proposal).first()
|
||||
if not voteinfo:
|
||||
raise HTTPException(status_code=int(HTTPStatus.NOT_FOUND))
|
||||
|
||||
voters = db.query(User).join(TUVote).filter(TUVote.VoteID == voteinfo.ID)
|
||||
vote = db.query(TUVote, and_(TUVote.UserID == request.user.ID,
|
||||
TUVote.VoteID == voteinfo.ID)).first()
|
||||
|
||||
if not request.user.is_trusted_user():
|
||||
context["error"] = "Only Trusted Users are allowed to vote."
|
||||
elif voteinfo.User == request.user.Username:
|
||||
context["error"] = "You cannot vote in an proposal about you."
|
||||
elif vote is not None:
|
||||
context["error"] = "You've already voted for this proposal."
|
||||
|
||||
context["vote"] = vote
|
||||
return render_proposal(request, context, proposal, voteinfo, voters, vote)
|
||||
|
||||
|
||||
@router.post("/tu/{proposal}")
|
||||
@auth_required(True, redirect="/")
|
||||
@account_type_required(REQUIRED_TYPES)
|
||||
async def trusted_user_proposal_post(request: Request,
|
||||
proposal: int,
|
||||
decision: str = Form(...)):
|
||||
context = await make_variable_context(request, "Trusted User")
|
||||
proposal = int(proposal) # Make sure it's an int.
|
||||
|
||||
voteinfo = db.query(TUVoteInfo, TUVoteInfo.ID == proposal).first()
|
||||
if not voteinfo:
|
||||
raise HTTPException(status_code=int(HTTPStatus.NOT_FOUND))
|
||||
|
||||
voters = db.query(User).join(TUVote).filter(TUVote.VoteID == voteinfo.ID)
|
||||
|
||||
# status_code we'll use for responses later.
|
||||
status_code = HTTPStatus.OK
|
||||
|
||||
if not request.user.is_trusted_user():
|
||||
# Test: Create a proposal and view it as a "Developer". It
|
||||
# should give us this error.
|
||||
context["error"] = "Only Trusted Users are allowed to vote."
|
||||
status_code = HTTPStatus.UNAUTHORIZED
|
||||
elif voteinfo.User == request.user.Username:
|
||||
context["error"] = "You cannot vote in an proposal about you."
|
||||
status_code = HTTPStatus.BAD_REQUEST
|
||||
|
||||
vote = db.query(TUVote, and_(TUVote.UserID == request.user.ID,
|
||||
TUVote.VoteID == voteinfo.ID)).first()
|
||||
|
||||
if status_code != HTTPStatus.OK:
|
||||
return render_proposal(request, context, proposal,
|
||||
voteinfo, voters, vote,
|
||||
status_code=status_code)
|
||||
|
||||
if vote is not None:
|
||||
context["error"] = "You've already voted for this proposal."
|
||||
status_code = HTTPStatus.BAD_REQUEST
|
||||
|
||||
if status_code != HTTPStatus.OK:
|
||||
return render_proposal(request, context, proposal,
|
||||
voteinfo, voters, vote,
|
||||
status_code=status_code)
|
||||
|
||||
if decision in {"Yes", "No", "Abstain"}:
|
||||
# Increment whichever decision was given to us.
|
||||
setattr(voteinfo, decision, getattr(voteinfo, decision) + 1)
|
||||
else:
|
||||
return Response("Invalid 'decision' value.",
|
||||
status_code=int(HTTPStatus.BAD_REQUEST))
|
||||
|
||||
vote = db.create(TUVote, User=request.user, VoteInfo=voteinfo,
|
||||
autocommit=False)
|
||||
voteinfo.ActiveTUs += 1
|
||||
db.commit()
|
||||
|
||||
context["error"] = "You've already voted for this proposal."
|
||||
return render_proposal(request, context, proposal, voteinfo, voters, vote)
|
||||
|
|
106
templates/partials/tu/proposal/details.html
Normal file
106
templates/partials/tu/proposal/details.html
Normal file
|
@ -0,0 +1,106 @@
|
|||
<h2>{% trans %}Proposal Details{% endtrans %}</h2>
|
||||
|
||||
{% if voteinfo.is_running() %}
|
||||
<p class="vote-running" style="font-weight: bold; color: red">
|
||||
{% trans %}This vote is still running.{% endtrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<!-- The margin style here mimics the margin on the old <p> element. -->
|
||||
<div class="proposal details">
|
||||
<div class="field user">
|
||||
{{ "User" | tr }}:
|
||||
<strong>
|
||||
{% if voteinfo.User %}
|
||||
<a href="{{ '/packages/?K=%s&SeB=m' | format(voteinfo.User)}}">
|
||||
{{ voteinfo.User }}
|
||||
</a>
|
||||
{% else %}
|
||||
N/A
|
||||
{% endif %}
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
{% set submitted = voteinfo.Submitted | dt | as_timezone(timezone) %}
|
||||
{% set end = voteinfo.End | dt | as_timezone(timezone) %}
|
||||
<div class="field submitted">
|
||||
{{
|
||||
"Submitted: %s by %s" | tr
|
||||
| format(submitted.strftime("%Y-%m-%d %H:%M"),
|
||||
voteinfo.Submitter.Username | e)
|
||||
}}
|
||||
</div>
|
||||
|
||||
<div class="field end">
|
||||
{{ "End" | tr }}:
|
||||
<strong>
|
||||
{{ end.strftime("%Y-%m-%d %H:%M") }}
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
{% if not voteinfo.is_running() %}
|
||||
<div class="field result">
|
||||
{{ "Result" | tr }}:
|
||||
{% if not voteinfo.ActiveTUs %}
|
||||
<span>{{ "unknown" | tr }}</span>
|
||||
{% elif accepted %}
|
||||
<span style="color: green; font-weight: bold">
|
||||
{{ "Accepted" | tr }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span style="color: red; font-weight: bold">
|
||||
{{ "Rejected" | tr }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="proposal agenda">
|
||||
<p class="field agenda">
|
||||
<!-- The `e` filter manually escapes content. -->
|
||||
{{ voteinfo.Agenda | replace("\n", "<br />\n") | safe | e }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<table class="vote-status">
|
||||
<tr>
|
||||
{% if not voteinfo.is_running() %}
|
||||
<th>{{ "Yes" | tr }}</th>
|
||||
<th>{{ "No" | tr }}</th>
|
||||
<th>{{ "Abstain" | tr }}</th>
|
||||
{% endif %}
|
||||
|
||||
<th>{{ "Total" | tr }}</th>
|
||||
<th>{{ "Voted" | tr }}</th>
|
||||
<th>{{ "Participation" | tr }}</th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
{% if not voteinfo.is_running() %}
|
||||
<td>{{ voteinfo.Yes }}</td>
|
||||
<td>{{ voteinfo.No }}</td>
|
||||
<td>{{ voteinfo.Abstain }}</td>
|
||||
{% endif %}
|
||||
|
||||
<td>{{ voteinfo.total_votes() }}</td>
|
||||
<td>
|
||||
{% if not has_voted %}
|
||||
<span style="color: red; font-weight: bold">
|
||||
{{ "No" | tr }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span style="color: green; font-weight: bold">
|
||||
{{ "Yes" | tr }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if voteinfo.ActiveTUs %}
|
||||
{{ (participation * 100) | number_format(2) }}%
|
||||
{% else %}
|
||||
{{ "unknown" | tr }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
14
templates/partials/tu/proposal/form.html
Normal file
14
templates/partials/tu/proposal/form.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
<form class="action-form" action="/tu/{{ proposal }}" method="POST">
|
||||
<!-- Translate each button's text but leave the value alone. -->
|
||||
<fieldset>
|
||||
<button type="submit" class="button" name="decision" value="Yes">
|
||||
{{ "Yes" | tr }}
|
||||
</button>
|
||||
<button type="submit" class="button" name="decision" value="No">
|
||||
{{ "No" | tr }}
|
||||
</button>
|
||||
<button type="submit" class="button" name="decision" value="Abstain">
|
||||
{{ "Abstain" | tr }}
|
||||
</button>
|
||||
</fieldset>
|
||||
</form>
|
10
templates/partials/tu/proposal/voters.html
Normal file
10
templates/partials/tu/proposal/voters.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
<h2>{{ "Voters" | tr }}</h2>
|
||||
<ul id="voters">
|
||||
{% for voter in voters %}
|
||||
<li>
|
||||
<a href="/account/{{ voter.Username | urlencode }}">
|
||||
{{ voter.Username | e }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
20
templates/tu/show.html
Normal file
20
templates/tu/show.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
{% extends "partials/layout.html" %}
|
||||
|
||||
{% block pageContent %}
|
||||
<div class="box">
|
||||
{% include "partials/tu/proposal/details.html" %}
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
{% include "partials/tu/proposal/voters.html" %}
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
{% if error %}
|
||||
<span class="status">{{ error | tr }}</span>
|
||||
{% else %}
|
||||
{% include "partials/tu/proposal/form.html" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -18,6 +18,7 @@ from aurweb.testing import setup_test_db
|
|||
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):
|
||||
|
@ -103,6 +104,26 @@ def user():
|
|||
AccountType=user_type)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def proposal(tu_user):
|
||||
ts = int(datetime.utcnow().timestamp())
|
||||
agenda = "Test proposal."
|
||||
start = ts - 5
|
||||
end = ts + 1000
|
||||
|
||||
user_type = db.query(AccountType,
|
||||
AccountType.AccountType == "User").first()
|
||||
user = db.create(User, Username="test", Email="test@example.org",
|
||||
RealName="Test User", Passwd="testPassword",
|
||||
AccountType=user_type)
|
||||
|
||||
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):
|
||||
with client as request:
|
||||
response = request.get("/tu", allow_redirects=False)
|
||||
|
@ -441,3 +462,270 @@ def test_tu_index_last_votes(client, tu_user, user):
|
|||
|
||||
assert user.text.strip() == tu_user.Username
|
||||
assert int(vote_id.text.strip()) == voteinfo.ID
|
||||
|
||||
|
||||
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_running_proposal(client, proposal):
|
||||
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
|
||||
|
||||
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.
|
||||
db.create(TUVote, VoteInfo=voteinfo, User=tu_user)
|
||||
voteinfo.ActiveTUs += 1
|
||||
voteinfo.Yes += 1
|
||||
db.commit()
|
||||
|
||||
# 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 = int(datetime.utcnow().timestamp())
|
||||
voteinfo.End = ts - 5 # 5 seconds ago.
|
||||
db.commit()
|
||||
|
||||
# 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, proposal):
|
||||
tu_user, user, voteinfo = proposal
|
||||
|
||||
dev_type = db.query(AccountType,
|
||||
AccountType.AccountType == "Developer").first()
|
||||
tu_user.AccountType = dev_type
|
||||
db.commit()
|
||||
|
||||
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.
|
||||
voteinfo.User = tu_user.Username
|
||||
db.commit()
|
||||
|
||||
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
|
||||
|
||||
db.create(TUVote, VoteInfo=voteinfo, User=tu_user)
|
||||
voteinfo.Yes += 1
|
||||
voteinfo.ActiveTUs += 1
|
||||
db.commit()
|
||||
|
||||
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."
|
||||
|
|
|
@ -204,3 +204,11 @@ label.confirmation,
|
|||
overflow: hidden;
|
||||
transition: height 1s;
|
||||
}
|
||||
|
||||
.proposal.details {
|
||||
margin: .33em 0 1em;
|
||||
}
|
||||
|
||||
button[type="submit"] {
|
||||
padding: 0 0.6em;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue