From 74021d704329584ad064abd069cab8dea9eac37a Mon Sep 17 00:00:00 2001 From: Ben Adida Date: Sun, 5 Jun 2011 15:34:01 -0700 Subject: [PATCH] added more true integration tests with dependencey django-webtest and webtest --- README.txt | 4 ++ helios/security.py | 37 +++++++++++++-- helios/tests.py | 113 ++++++++++++++++++++++++++++++--------------- helios/views.py | 24 +++------- 4 files changed, 122 insertions(+), 56 deletions(-) diff --git a/README.txt b/README.txt index c89c4faf1..caf075689 100644 --- a/README.txt +++ b/README.txt @@ -21,6 +21,10 @@ NEEDS: - South for schema migration -- easy_install South +- django-webtest for testing +-- http://pypi.python.org/pypi/django-webtest +-- easy_install webtest +-- easy_install django-webtest GETTING SOUTH WORKING ON EXISTING INSTALL - as of Helios v3.0.4, we're using South to migrate data models diff --git a/helios/security.py b/helios/security.py index 50e4a03d5..05e99b4e2 100644 --- a/helios/security.py +++ b/helios/security.py @@ -19,6 +19,23 @@ import helios +# current voter +def get_voter(request, user, election): + """ + return the current voter + """ + voter = None + if request.session.has_key('CURRENT_VOTER'): + voter = request.session['CURRENT_VOTER'] + if voter.election != election: + voter = None + + if not voter: + if user: + voter = Voter.get_by_election_and_user(election, user) + + return voter + # a function to check if the current user is a trustee HELIOS_TRUSTEE_UUID = 'helios_trustee_uuid' def get_logged_in_trustee(request): @@ -80,9 +97,8 @@ def election_view_wrapper(request, election_uuid=None, *args, **kw): # if private election, only logged in voters if election.private_p and not checks.get('allow_logins',False): - from views import get_voter, get_user, password_voter_login - user = get_user(request) - if not user_can_admin_election(user, election) and not get_voter(request, user, election): + from views import password_voter_login + if not user_can_see_election(request, election): return_url = request.get_full_path() return HttpResponseRedirect("%s?%s" % (reverse(password_voter_login, args=[election.uuid]), urllib.urlencode({ 'return_url' : return_url @@ -101,6 +117,21 @@ def user_can_admin_election(user, election): # election or site administrator return election.admin == user or user.admin_p +def user_can_see_election(request, election): + user = get_user(request) + + if not election.private_p: + return True + + # election is private + + # but maybe this user is the administrator? + if user_can_admin_election(user, election): + return True + + # then this user has to be a voter + return (get_voter(request, user, election) != None) + def api_client_can_admin_election(api_client, election): return election.api_client == api_client and api_client != None diff --git a/helios/tests.py b/helios/tests.py index ad026a5ec..91a414f29 100644 --- a/helios/tests.py +++ b/helios/tests.py @@ -3,6 +3,7 @@ """ import unittest, datetime, re +import django_webtest import models import datatypes @@ -316,11 +317,30 @@ class LegacyElectionBlackboxTests(DataFormatBlackboxTests, TestCase): # EXPECTED_TRUSTEES_FILE = 'helios/fixtures/v3.1-trustees-expected.json' # EXPECTED_BALLOTS_FILE = 'helios/fixtures/v3.1-ballots-expected.json' +class WebTest(django_webtest.WebTest): + def assertRedirects(self, response, url): + """ + reimplement this in case it's a WebOp response + """ + if hasattr(response, 'status_code'): + return super(django_webtest.WebTest, self).assertRedirects(response, url) + + assert response.status_int == 302 + assert url in response.location, "redirected to %s instead of %s" % (response.location, url) + + def assertContains(self, response, text): + if hasattr(response, 'status_code'): + return super(django_webtest.WebTest, self).assertContains(response, text) + + assert response.status_int == 200 + assert text in response.testbody, "missing text %s" % text + + ## ## overall operation of the system ## -class ElectionBlackboxTests(TestCase): +class ElectionBlackboxTests(WebTest): fixtures = ['users.json', 'election.json'] def setUp(self): @@ -331,7 +351,10 @@ def setup_login(self): # set up the session session = self.client.session session['user'] = {'type': self.user.user_type, 'user_id': self.user.user_id} - session.save() + session.save() + + # set up the app, too + self.app.cookies['sessionid'] = self.client.cookies.get('sessionid').value def clear_login(self): session = self.client.session @@ -513,38 +536,45 @@ def _cast_ballot(self, election_id, username, password, need_login=True, check_u check_user_logged_in looks for the "you're already logged" message """ # vote by preparing a ballot via the server-side encryption - response = self.client.post("/helios/elections/%s/encrypt-ballot" % election_id, { + response = self.app.post("/helios/elections/%s/encrypt-ballot" % election_id, { 'answers_json': utils.to_json([[1]])}) self.assertContains(response, "answers") # parse it as an encrypted vote, and re-serialize it - ballot = datatypes.LDObject.fromDict(utils.from_json(response.content), type_hint='legacy/EncryptedVote') + ballot = datatypes.LDObject.fromDict(utils.from_json(response.testbody), type_hint='legacy/EncryptedVote') encrypted_vote = ballot.serialize() # cast the ballot - response = self.client.post("/helios/elections/%s/cast" % election_id, { + response = self.app.post("/helios/elections/%s/cast" % election_id, { 'encrypted_vote': encrypted_vote}) self.assertRedirects(response, "%s/helios/elections/%s/cast_confirm" % (settings.SECURE_URL_HOST, election_id)) + cast_confirm_page = response.follow() + if need_login: if check_user_logged_in: - response = self.client.get("/helios/elections/%s/cast_confirm" % election_id) - self.assertContains(response, "You are logged in as") - self.assertContains(response, "requires election-specific credentials") - - response = self.client.post("/helios/elections/%s/password_voter_login" % election_id, { - 'voter_id' : username, - 'password' : password - }) - self.assertRedirects(response, "/helios/elections/%s/cast_confirm" % election_id) - else: - response = self.client.get("/helios/elections/%s/cast_confirm" % election_id) - self.assertContains(response, "I am ") + self.assertContains(cast_confirm_page, "You are logged in as") + self.assertContains(cast_confirm_page, "requires election-specific credentials") - # confirm the vote - response = self.client.post("/helios/elections/%s/cast_confirm" % election_id, { - "csrf_token" : self.client.session['csrf_token'], - "status_update" : False}) + # set the form + login_form = cast_confirm_page.form + login_form['voter_id'] = username + login_form['password'] = password + + cast_confirm_page = login_form.submit() + + self.assertRedirects(cast_confirm_page, "/helios/elections/%s/cast_confirm" % election_id) + cast_confirm_page = cast_confirm_page.follow() + + # here we should be at the cast-confirm page and logged in + self.assertContains(cast_confirm_page, "I am ") + + # confirm the vote, now with the actual form + cast_form = cast_confirm_page.form + + if 'status_update' in cast_form.fields.keys(): + cast_form['status_update'] = False + response = cast_form.submit() self.assertRedirects(response, "%s/helios/elections/%s/cast_done" % (settings.URL_HOST, election_id)) # at this point an email should have gone out to the user @@ -559,18 +589,25 @@ def _cast_ballot(self, election_id, username, password, need_login=True, check_u # so if need_login is False, it was a private election, and we do need to re-login here # we need to re-login if it's a private election, because all data, including ballots # is otherwise private - response = self.client.post("/helios/elections/%s/password_voter_login" % election_id, { - 'voter_id' : username, - 'password' : password - }) + login_page = self.app.get("/helios/elections/%s/password_voter_login" % election_id) + + # if we redirected, that's because we can see the page, I think + if login_page.status_int != 302: + login_form = login_page.form + + login_form['voter_id'] = username + login_form['password'] = password + login_form.submit() - response = self.client.get(url) + response = self.app.get(url) self.assertContains(response, ballot.hash) self.assertContains(response, html_escape(encrypted_vote)) # if we request the redirect to cast_done, the voter should be logged out, but not the user - response = self.client.get("/helios/elections/%s/cast_done" % election_id) - assert not self.client.session.has_key('CURRENT_VOTER') + response = self.app.get("/helios/elections/%s/cast_done" % election_id) + + # FIXME: how to check this? We can't do it by checking session that we're doign webtes + # assert not self.client.session.has_key('CURRENT_VOTER') def _do_tally(self, election_id): # log back in as administrator @@ -615,15 +652,19 @@ def test_do_complete_election_private(self): # private election election_id, username, password = self._setup_complete_election({'private_p' : "1"}) - # log in - response = self.client.post("/helios/elections/%s/password_voter_login" % election_id, { - 'voter_id' : username, - 'password' : password, - 'return_url' : "/helios/elections/%s/view" % election_id - }) + # get the password_voter_login_form via the front page + # (which will test that redirects are doing the right thing) + response = self.app.get("/helios/elections/%s/view" % election_id) + + # ensure it redirects + self.assertRedirects(response, "/helios/elections/%s/password_voter_login" % election_id) + + login_form = response.follow().form + + login_form['voter_id'] = username + login_form['password'] = password - # FIXME: probably better to fetch password_voter_login as a get and post the form obtained - # rather than assume return_url + response = login_form.submit() self.assertRedirects(response, "/helios/elections/%s/view" % election_id) self._cast_ballot(election_id, username, password, need_login = False) diff --git a/helios/views.py b/helios/views.py index 119575baf..33efb07fb 100644 --- a/helios/views.py +++ b/helios/views.py @@ -100,22 +100,6 @@ def stats(request): 'limit' : limit}) -def get_voter(request, user, election): - """ - return the current voter - """ - voter = None - if request.session.has_key('CURRENT_VOTER'): - voter = request.session['CURRENT_VOTER'] - if voter.election != election: - voter = None - - if not voter: - if user: - voter = Voter.get_by_election_and_user(election, user) - - return voter - ## ## simple admin for development ## @@ -546,10 +530,15 @@ def password_voter_login(request, election): """ This is used to log in as a voter for a particular election """ - + # the URL to send the user to after they've logged in return_url = request.REQUEST.get('return_url', reverse(one_election_cast_confirm, args=[election.uuid])) if request.method == "GET": + # if user logged in somehow in the interim, e.g. using the login link for administration, + # then go! + if user_can_see_election(request, election): + return HttpResponseRedirect(reverse(one_election_view, args = [election.uuid])) + password_login_form = forms.VoterPasswordForm() return render_template(request, 'password_voter_login', {'election': election, 'return_url' : return_url, @@ -663,6 +652,7 @@ def one_election_cast_confirm(request, election): return render_template(request, 'election_cast_confirm', { 'login_box': login_box, 'election' : election, 'vote_fingerprint': vote_fingerprint, 'past_votes': past_votes, 'issues': issues, 'voter' : voter, + 'return_url': return_url, 'status_update_label': status_update_label, 'status_update_message': status_update_message, 'show_password': show_password, 'password_only': password_only, 'password_login_form': password_login_form, 'bad_voter_login': bad_voter_login})