def test_clear_skipped_votes( *, category_name: Optional[str], commit: bool, conference: Conference, ending_count: int, user: User, ) -> None: """Test that skipped votes can be cleared for a uesr.""" talk1 = Talk(title="", length=1, is_anonymized=True) talk2 = Talk(title="", length=1, is_anonymized=True) category1 = Category(conference=conference, name="First Category") category2 = Category(conference=conference, name="Second Category") vote1 = Vote(talk=talk1, user=user, skipped=True) vote2 = Vote(talk=talk2, user=user, skipped=True) category1.talks.append(talk1) category2.talks.append(talk2) db.session.add_all((category1, category2, talk1, talk2, vote1, vote2)) db.session.commit() # Choose the category to delete skipped votes from based on the # provided category name. if category_name is None: target_category = None else: target_category = Category.query.filter_by(name=category_name).first() # Clear the skipped votes as configured. Vote.clear_skipped(category=target_category, commit=commit, user=user) # Rollback the session to clear any uncommitted changes. db.session.rollback() # Check for the desired skipped vote count. assert Vote.query.count() == ending_count
def test_inviting_a_speaker_emails_the_speaker(client: Client, user: User, send_mail: Mock) -> None: talk = Talk(title="My Talk", length=25) talk.add_speaker(user, InvitationStatus.CONFIRMED) db.session.add(talk) db.session.commit() assert User.query.filter_by( email="*****@*****.**").one_or_none() is None # reload from DB to avoid "not attached to session" error talk = Talk.query.filter_by(title="My Talk").one() client.get("/test-login/{}".format(user.user_id)) resp = client.get("/talks/{}/speakers".format(talk.talk_id)) assert_html_response(resp) csrf_token = extract_csrf_from(resp) # this speaker doesn't exist, but we should still send the email postdata = {"email": "*****@*****.**", "csrf_token": csrf_token} resp = client.post("/talks/{}/speakers".format(talk.talk_id), data=postdata, follow_redirects=True) assert_html_response_contains(resp, "*****@*****.**") send_mail.assert_called_once_with(to=["*****@*****.**"], template="email/co-presenter-invite", talk=ANY) _, kwargs = send_mail.call_args assert kwargs["talk"].talk_id == talk.talk_id # this also implies a user with that email was created assert_talk_has_speakers(talk, ["*****@*****.**"])
def test_inviting_a_speaker_adds_the_speaker(client: Client, user: User, send_mail: Mock) -> None: talk = Talk(title="My Talk", length=25) talk.add_speaker(user, InvitationStatus.CONFIRMED) db.session.add(talk) alice = User(email="*****@*****.**", fullname="Alice Example") db.session.add(alice) db.session.commit() # reload from DB to avoid "not attached to session" error talk = Talk.query.filter_by(title="My Talk").one() client.get("/test-login/{}".format(user.user_id)) resp = client.get("/talks/{}/speakers".format(talk.talk_id)) assert_html_response(resp) csrf_token = extract_csrf_from(resp) postdata = {"email": "*****@*****.**", "csrf_token": csrf_token} resp = client.post("/talks/{}/speakers".format(talk.talk_id), data=postdata, follow_redirects=True) assert_html_response_contains(resp, "Alice Example") send_mail.assert_called_once_with(to=["*****@*****.**"], template="email/co-presenter-invite", talk=ANY) _, kwargs = send_mail.call_args assert kwargs["talk"].talk_id == talk.talk_id assert_talk_has_speakers(talk, ["*****@*****.**"])
def create_talk() -> Response: talk = Talk(accepted_recording_release=True) talk.add_speaker(g.user, InvitationStatus.CONFIRMED) form = TalkForm(conference=g.conference, obj=talk) if form.validate_on_submit(): form.populate_obj(talk) db.session.add(talk) db.session.commit() return redirect(url_for("views.preview_talk", talk_id=talk.talk_id)) return render_template("edit_talk.html", talk=talk, form=form)
def test_talk_anonymization(client: Client, user: User, send_mail: Mock) -> None: user.site_admin = True db.session.add(user) talk = Talk( title="Alice's Identifying Talk", description="This talk is by Alice", outline="Alice!", take_aways="Alice's point.", length=25, ) talk.add_speaker(user, InvitationStatus.CONFIRMED) db.session.add(talk) db.session.commit() db.session.refresh(talk) client.get("/test-login/{}".format(user.user_id), follow_redirects=True) resp = client.get(f"/manage/anonymize/{talk.talk_id}") assert_html_response_contains(resp, "Alice's Identifying Talk") postdata = { "title": "(Speaker name redacted)'s Identifying Talk", "description": "This talk is by (Speaker name redacted)", "outline": "(Speaker name redacted)!", "take_aways": "(The speaker's) point.", "csrf_token": extract_csrf_from(resp), } client.post(f"/manage/anonymize/{talk.talk_id}", data=postdata) talk = Talk.query.get(talk.talk_id) assert talk.is_anonymized is True assert talk.has_anonymization_changes is True assert talk.anonymized_title == "(Speaker name redacted)'s Identifying Talk" assert talk.anonymized_description == "This talk is by (Speaker name redacted)" assert talk.anonymized_outline == "(Speaker name redacted)!" assert talk.anonymized_take_aways == "(The speaker's) point." assert talk.title == "Alice's Identifying Talk" assert talk.description == "This talk is by Alice" assert talk.outline == "Alice!" assert talk.take_aways == "Alice's point." send_mail.assert_called_once_with( to=[user.email], template="email/talk-anonymized", talk_id=talk.talk_id, title=talk.title, # the original title )
def test_prompt_for_demographic_survey(client: Client, user: User) -> None: client.get("/test-login/{}".format(user.user_id)) resp = client.get("/talks") assert_html_response_doesnt_contain(resp, "demographic survey") talk = Talk(title="My Talk", length=25) talk.add_speaker(user, InvitationStatus.CONFIRMED) db.session.add(talk) db.session.commit() client.get("/test-login/{}".format(user.user_id)) resp = client.get("/talks") assert_html_response_contains(resp, "demographic_survey")
def test_vote_review_shows_your_vote_and_conduct_form( *, vote_value: Optional[int], skipped: bool, vote_note: str, authenticated_client: Client, user: User, ) -> None: """Show the user's vote results, if present, and the conduct report form.""" talk = Talk( title="", length=1, is_anonymized=True, anonymized_title="", anonymized_description="", anonymized_outline="", anonymized_take_aways="", ) conference = Conference.query.first() category = Category(name="", conference=conference) category.talks.append(talk) db.session.add_all((category, talk)) if vote_value is not None: vote = Vote(talk=talk, user=user, value=vote_value, skipped=False) db.session.add(vote) elif skipped: vote = Vote(talk=talk, user=user, value=None, skipped=True) db.session.add(vote) db.session.commit() resp = authenticated_client.get(f"/review/{talk.talk_id}") assert_html_response_contains(resp, vote_note) assert_html_response_contains( resp, '<form method="POST" action="/conduct-report">')
def test_speakers_button_shows_up_on_existing_talks(client: Client, user: User) -> None: talk = Talk(title="My Talk", length=25) talk.add_speaker(user, InvitationStatus.CONFIRMED) db.session.add(talk) db.session.commit() # reload from DB to avoid "not attached to session" error talk = Talk.query.filter_by(title="My Talk").one() client.get("/test-login/{}".format(user.user_id)) resp = client.get("/talks/{}".format(talk.talk_id)) assert_html_response_contains( resp, re.compile('<a href="/talks/{}/speakers".*class=".*btn.*">' "Manage Speakers</a>".format(talk.talk_id)), )
def test_vote_gating(*, authenticated_client: Client, conference: Conference, user: User) -> None: """Test that votes are gated correctly for a category.""" talk1 = Talk(title="", length=1, is_anonymized=True) talk2 = Talk(title="", length=1, is_anonymized=True) category = Category(conference=conference, name="") vote = Vote(talk=talk1, user=user) category.talks.append(talk1) category.talks.append(talk2) db.session.add_all((category, talk1, talk2, vote)) db.session.commit() public_id = str(vote.public_id) resp = authenticated_client.get(f"/vote/category/{category.category_id}") assert resp.status_code == 302 assert resp.headers["Location"].endswith(f"/vote/cast/{public_id}") assert Vote.query.count() == 1 assert Vote.query.filter_by(talk_id=2).count() == 0
def test_talks_list_page_doesnt_show_resubmit_after_proposal_window( client: Client, user: User ) -> None: conf = Conference.query.get(1) conf.proposals_begin = datetime.utcnow() - timedelta(days=3) conf.proposals_end = datetime.utcnow() - timedelta(days=1) withdrawn_talk = Talk(title="Withdrawn Talk", length=40, state=TalkStatus.WITHDRAWN) withdrawn_talk.add_speaker(user, InvitationStatus.CONFIRMED) db.session.add(withdrawn_talk) db.session.commit() client.get("/test-login/{}".format(user.user_id)) resp = client.get("/talks") assert_html_response_doesnt_contain(resp, "Re-Submit") # also make sure to prevent it server-side resp = client.get(f"/talks/1/resubmit") assert_html_response(resp, status=400)
def test_talk_editing_not_allowed_while_voting(user: User, client: Client) -> None: conf = Conference.query.get(1) conf.voting_begin = datetime.utcnow() - timedelta(days=1) conf.voting_end = datetime.utcnow() + timedelta(days=1) db.session.add(conf) talk = Talk(title="My Talk", length=25) talk.add_speaker(user, InvitationStatus.CONFIRMED) db.session.add(talk) db.session.commit() db.session.refresh(talk) client.get("/test-login/{}".format(user.user_id), follow_redirects=True) resp = client.get(f"/talks/{talk.talk_id}") assert_html_response(resp, status=200) postdata = {"csrf_token": extract_csrf_from(resp)} resp = client.post(f"/talks/{talk.talk_id}", data=postdata) assert_html_response(resp, status=400)
def test_choose_vote(*, authenticated_client: Client, conference: Conference, user: User) -> None: """Test that votes are created correctly for a category.""" talk1 = Talk(title="", length=1, is_anonymized=True) category1 = Category(conference=conference, name="1") category1.talks.append(talk1) talk2 = Talk(title="", length=1, is_anonymized=True) category2 = Category(conference=conference, name="2") category2.talks.append(talk2) db.session.add_all((talk1, talk2, category1, category2)) db.session.commit() resp = authenticated_client.get(f"/vote/category/{category2.category_id}") vote = Vote.query.filter_by(talk_id=2).first() assert resp.status_code == 302 assert resp.headers["Location"].endswith(f"/vote/cast/{vote.public_id}") assert Vote.query.filter_by(talk_id=1).count() == 0 assert Vote.query.filter_by(talk_id=2).count() == 1
def test_talk_anonymization_doesnt_set_is_anonymized_if_no_changes( client: Client, user: User, send_mail: Mock) -> None: user.site_admin = True db.session.add(user) talk = Talk( title="Alice's Identifying Talk", description="This talk is by Alice", outline="Alice!", take_aways="Alice's point.", length=25, ) talk.add_speaker(user, InvitationStatus.CONFIRMED) db.session.add(talk) db.session.commit() db.session.refresh(talk) client.get("/test-login/{}".format(user.user_id), follow_redirects=True) resp = client.get(f"/manage/anonymize/{talk.talk_id}") assert_html_response_contains(resp, "Alice's Identifying Talk") postdata = { "title": talk.title, "description": talk.description, "outline": talk.outline, "take_aways": talk.take_aways, "csrf_token": extract_csrf_from(resp), } client.post(f"/manage/anonymize/{talk.talk_id}", data=postdata) talk = Talk.query.get(talk.talk_id) assert talk.is_anonymized is True assert talk.has_anonymization_changes is False assert talk.anonymized_title == talk.title assert talk.anonymized_description == talk.anonymized_description assert talk.anonymized_outline == talk.outline assert talk.anonymized_take_aways == talk.take_aways assert not send_mail.called
def test_saving_a_talk_clears_categories( client: Client, conference: Conference, user: User ) -> None: category = Category(conference=conference, name="The Category") talk = Talk(title="Old Title", length=25) talk.categories.append(category) talk.add_speaker(user, InvitationStatus.CONFIRMED) db.session.add(talk) db.session.add(category) db.session.commit() client.get("/test-login/{}".format(user.user_id)) resp = client.get("/talks/1") csrf_token = extract_csrf_from(resp) postdata = {"title": "New Title", "csrf_token": csrf_token} resp = client.post("/talks/1", data=postdata, follow_redirects=True) assert_html_response(resp, status=200) talk = Talk.query.first() assert talk.title == "New Title" assert talk.categories == []
def test_manage_speakers_page_shows_other_speakers(client: Client, user: User) -> None: alice = User(email="*****@*****.**", fullname="Alice Example") bob = User(email="*****@*****.**", fullname="Bob Example") charlie = User(email="*****@*****.**", fullname="Charlie Example") db.session.add(alice) db.session.add(bob) db.session.add(charlie) talk = Talk(title="My Talk", length=25) talk.add_speaker(user, InvitationStatus.CONFIRMED) talk.add_speaker(alice, InvitationStatus.PENDING) talk.add_speaker(bob, InvitationStatus.REJECTED) talk.add_speaker(charlie, InvitationStatus.DELETED) db.session.add(talk) db.session.commit() # reload from DB to avoid "not attached to session" error talk = Talk.query.filter_by(title="My Talk").one() client.get("/test-login/{}".format(user.user_id)) resp = client.get("/talks/{}/speakers".format(talk.talk_id)) body = assert_html_response(resp) soup = bs4.BeautifulSoup(body, "html.parser") speakers = soup.find_all("div", class_="speaker") assert len(speakers) == 4 row_texts = [ re.sub(r"\s+", " ", row.get_text()).strip() for row in speakers ] assert sorted(row_texts) == sorted([ "{} ({}) Confirmed".format(user.fullname, user.email), "Alice Example ([email protected]) Pending Uninvite", "Bob Example ([email protected]) Rejected", "Charlie Example ([email protected]) Deleted Reinvite", ])
def test_manage_speakers_page_shows_primary_speaker(client: Client, user: User) -> None: talk = Talk(title="My Talk", length=25) talk.add_speaker(user, InvitationStatus.CONFIRMED) db.session.add(talk) db.session.commit() # reload from DB to avoid "not attached to session" error talk = Talk.query.filter_by(title="My Talk").one() client.get("/test-login/{}".format(user.user_id)) resp = client.get("/talks/{}/speakers".format(talk.talk_id)) body = assert_html_response(resp) soup = bs4.BeautifulSoup(body, "html.parser") speakers = soup.find_all("div", class_="speaker") assert len(speakers) == 1 row_texts = [ re.sub(r"\s+", " ", row.get_text()).strip() for row in speakers ] assert row_texts == ["{} ({}) Confirmed".format(user.fullname, user.email)]
def test_talks_list_page_shows_proposed_and_withdrawn_talks( client: Client, user: User ) -> None: in_talk = Talk(title="In Talk", length=25) in_talk.add_speaker(user, InvitationStatus.CONFIRMED) out_talk = Talk(title="Out Talk", length=40, state=TalkStatus.WITHDRAWN) out_talk.add_speaker(user, InvitationStatus.CONFIRMED) db.session.add(in_talk) db.session.add(out_talk) db.session.commit() client.get("/test-login/{}".format(user.user_id)) resp = client.get("/talks") body = assert_html_response(resp) soup = bs4.BeautifulSoup(body, "html.parser") talks = soup.find_all("div", class_="talk") assert len(talks) == 2 talk_row_texts = [ re.sub(r"\s+", " ", talk.parent.get_text()).strip() for talk in talks ] talk_row_texts.sort() assert re.match("In Talk.*Withdraw", talk_row_texts[0]) assert re.match("Out Talk.*Re-Submit", talk_row_texts[1]) # lazy: also test withdraw/resubmit here in the same test client.get(f"/talks/1/withdraw") client.get(f"/talks/2/resubmit") resp = client.get("/talks") body = assert_html_response(resp) soup = bs4.BeautifulSoup(body, "html.parser") talks = soup.find_all("div", class_="talk") assert len(talks) == 2 talk_row_texts = [ re.sub(r"\s+", " ", talk.parent.get_text()).strip() for talk in talks ] talk_row_texts.sort() assert re.match("In Talk.*Re-Submit", talk_row_texts[0]) assert re.match("Out Talk.*Withdraw", talk_row_texts[1])
def test_vote_save(*, authenticated_client: Client, user: User) -> None: """Test that a vote can be saved.""" talk = Talk(title="", length=1) vote = Vote(talk=talk, user=user) db.session.add_all((talk, vote)) db.session.commit() public_id = str(vote.public_id) resp = authenticated_client.post(f"/vote/cast/{vote.public_id}", data={ "action": "vote", "value": 1 }) assert resp.status_code == 302 assert str(Vote.query.filter_by(value=1).first().public_id) == public_id
def test_report_conduct_issue(*, authenticated_client: Client, send_mail: Mock, user: User) -> None: """Test that a user can successfully report a CoC issue.""" talk = Talk(title="", length=1) db.session.add(talk) db.session.commit() resp = authenticated_client.post( "/conduct-report", data={ "talk_id": talk.talk_id, "text": "Please review", "anonymous": 0 }, ) send_mail.assert_called_once() assert resp.status_code == 302 assert ConductReport.query.filter_by(user=user).count() == 1
def test_accept_button_accepts_the_talk(client: Client, user: User) -> None: alice = User(email="*****@*****.**", fullname="Alice Example") db.session.add(alice) talk = Talk(title="My Talk", length=25) talk.add_speaker(alice, InvitationStatus.CONFIRMED) # original speaker talk.add_speaker(user, InvitationStatus.PENDING) db.session.add(talk) db.session.commit() client.get("/test-login/{}".format(user.user_id)) client.get("/talks/1/speakers/accept") resp = client.get("/talks") assert_html_response_contains(resp, "My Talk", "(25 Minutes, Alice Example and You)") assert_html_response_doesnt_contain(resp, "Speaker Invitations:", "Reject", "Accept")
def test_talks_list_shows_invitations(client: Client, user: User) -> None: alice = User(email="*****@*****.**", fullname="Alice Example") db.session.add(alice) talk = Talk(title="My Talk", length=25) talk.add_speaker(alice, InvitationStatus.CONFIRMED) # original speaker talk.add_speaker(user, InvitationStatus.PENDING) db.session.add(talk) db.session.commit() client.get("/test-login/{}".format(user.user_id)) resp = client.get("/talks") assert_html_response_contains( resp, "Speaker Invitations:", "My Talk", "(25 Minutes, Alice Example and You)", '<a href="/talks/1/speakers/reject" class="btn btn-outline-danger btn-sm">Reject</a>', # NOQA: B950 '<a href="/talks/1/speakers/accept" class="btn btn-outline-primary btn-sm">Accept</a>', # NOQA: B950 )
def test_talks_list_page_lists_talks(client: Client, user: User) -> None: alice = User(email="*****@*****.**", fullname="Alice Example") bob = User(email="*****@*****.**", fullname="Bob Example") db.session.add(alice) db.session.add(bob) db.session.commit() one_talk = Talk(title="My Talk", length=25) one_talk.add_speaker(user, InvitationStatus.CONFIRMED) two_talk = Talk(title="Our Talk", length=40) two_talk.add_speaker(user, InvitationStatus.CONFIRMED) two_talk.add_speaker(alice, InvitationStatus.CONFIRMED) all_talk = Talk(title="All Our Talk", length=25) all_talk.add_speaker(user, InvitationStatus.CONFIRMED) all_talk.add_speaker(alice, InvitationStatus.CONFIRMED) all_talk.add_speaker(bob, InvitationStatus.CONFIRMED) db.session.add(one_talk) db.session.add(two_talk) db.session.add(all_talk) db.session.commit() client.get("/test-login/{}".format(user.user_id)) resp = client.get("/talks") body = assert_html_response(resp) soup = bs4.BeautifulSoup(body, "html.parser") talks = soup.find_all("div", class_="talk") assert len(talks) == 3 talk_row_texts = [re.sub(r"\s+", " ", talk.get_text()).strip() for talk in talks] assert sorted(talk_row_texts) == sorted( [ "My Talk (25 Minutes)", "Our Talk (40 Minutes, Alice Example and You)", "All Our Talk (25 Minutes, Alice Example, Bob Example, and You)", ] )