Skip to content
Snippets Groups Projects
Select Git revision
  • 68cb994bfeaa7213c9bd3004b1ed0a41126517ea
  • test default protected
  • master protected
  • original
  • pirati-backup protected
  • beta-2
  • beta-1
  • v3.1.4
  • v3.1.3
  • v3.1.2
  • v3.1.1
  • v3.1.0
  • v3.0.16
  • v3.0.15
  • v3.0.14
  • v3.0.13
  • v3.0.12
  • v3.0.11
  • v3.0.10
  • v3.0.9
  • v3.0.8
  • v3.0.7
  • v3.0.6
  • v3.0.5
  • v3.0.4
25 results

tests.py

Blame
  • user avatar
    Ben Adida authored
    68cb994b
    History
    tests.py 25.26 KiB
    """
    Unit Tests for Helios
    """
    
    import unittest, datetime, re
    
    import models
    import datatypes
    
    from auth import models as auth_models
    from views import ELGAMAL_PARAMS
    import views
    import utils
    
    from django.db import IntegrityError, transaction
    from django.test.client import Client
    from django.test import TestCase
    from django.utils.html import escape as html_escape
    
    from django.core import mail
    from django.core.files import File
    from django.core.urlresolvers import reverse
    from django.conf import settings
    from django.core.exceptions import PermissionDenied
    
    import uuid
    
    class ElectionModelTests(TestCase):
        fixtures = ['users.json']
    
        def create_election(self):
            return models.Election.get_or_create(
                short_name='demo',
                name='Demo Election',
                description='Demo Election Description',
                admin=self.user)
    
        def setup_questions(self):
            QUESTIONS = [{"answer_urls": [None, None, None], "answers": ["a", "b", "c"], "choice_type": "approval", "max": 1, "min": 0, "question": "w?", "result_type": "absolute", "short_name": "w?", "tally_type": "homomorphic"}]
            self.election.questions = QUESTIONS
    
        def setup_trustee(self):
            self.election.generate_trustee(ELGAMAL_PARAMS)
    
        def setup_openreg(self):
            self.election.openreg=True
            self.election.save()
        
        def setUp(self):
            self.user = auth_models.User.objects.get(user_id='ben@adida.net', user_type='google')
            self.election, self.created_p = self.create_election()
    
        def test_create_election(self):
            # election should be created
            self.assertTrue(self.created_p)
    
            # should have a creation time
            self.assertNotEquals(self.election.created_at, None)
            self.assertTrue(self.election.created_at < datetime.datetime.utcnow())
    
        def test_find_election(self):
            election = models.Election.get_by_user_as_admin(self.user)[0]
            self.assertEquals(self.election, election)
    
            election = models.Election.get_by_uuid(self.election.uuid)
            self.assertEquals(self.election, election)
    
            election = models.Election.get_by_short_name(self.election.short_name)
            self.assertEquals(self.election, election)
            
        def test_setup_trustee(self):
            self.setup_trustee()
            self.assertEquals(self.election.num_trustees, 1)
    
        def test_add_voters_file(self):
            election = self.election
    
            FILE = "helios/fixtures/voter-file.csv"
            vf = models.VoterFile.objects.create(election = election, voter_file = File(open(FILE), "voter_file.css"))
            vf.process()
    
            # make sure that we stripped things correctly
            voter = election.voter_set.get(voter_login_id = 'benadida5')
            self.assertEquals(voter.voter_email, 'ben5@adida.net')
            self.assertEquals(voter.voter_name, 'Ben5 Adida')
    
        def test_check_issues_before_freeze(self):
            # should be three issues: no trustees, and no questions, and no voters
            issues = self.election.issues_before_freeze
            self.assertEquals(len(issues), 3)
    
            self.setup_questions()
    
            # should be two issues: no trustees, and no voters
            issues = self.election.issues_before_freeze
            self.assertEquals(len(issues), 2)
    
            self.election.questions = None
    
            self.setup_trustee()
    
            # should be two issues: no questions, and no voters
            issues = self.election.issues_before_freeze
            self.assertEquals(len(issues), 2)
            
            self.setup_questions()
    
            # move to open reg
            self.setup_openreg()
    
            issues = self.election.issues_before_freeze
            self.assertEquals(len(issues), 0)
            
        def test_helios_trustee(self):
            self.election.generate_trustee(ELGAMAL_PARAMS)
    
            self.assertTrue(self.election.has_helios_trustee())
    
            trustee = self.election.get_helios_trustee()
            self.assertNotEquals(trustee, None)
    
        def test_log(self):
            LOGS = ["testing 1", "testing 2", "testing 3"]
    
            for l in LOGS:
                self.election.append_log(l)
    
            pulled_logs = [l.log for l in self.election.get_log().all()]
            pulled_logs.reverse()
    
            self.assertEquals(LOGS,pulled_logs)
    
        def test_eligibility(self):
            self.election.eligibility = [{'auth_system': self.user.user_type}]
    
            # without openreg, this should be false
            self.assertFalse(self.election.user_eligible_p(self.user))
            
            # what about after saving?
            self.election.save()
            e = models.Election.objects.get(uuid = self.election.uuid)
            self.assertEquals(e.eligibility, [{'auth_system': self.user.user_type}])
    
            self.election.openreg = True
    
            # without openreg, and now true
            self.assertTrue(self.election.user_eligible_p(self.user))
    
        def test_freeze(self):
            # freezing without trustees and questions, no good
            def try_freeze():
                self.election.freeze()
            self.assertRaises(Exception, try_freeze)
            
            self.setup_questions()
            self.setup_trustee()
            self.setup_openreg()
    
            # this time it should work
            try_freeze()
            
            # make sure it logged something
            self.assertTrue(len(self.election.get_log().all()) > 0)
    
        def test_archive(self):
            self.election.archived_at = datetime.datetime.utcnow()
            self.assertTrue(self.election.is_archived)
    
            self.election.archived_at = None
            self.assertFalse(self.election.is_archived)
    
        def test_voter_registration(self):
            # before adding a voter
            voters = models.Voter.get_by_election(self.election)
            self.assertTrue(len(voters) == 0)
    
            # make sure no voter yet
            voter = models.Voter.get_by_election_and_user(self.election, self.user)
            self.assertTrue(voter == None)
    
            # make sure no voter at all across all elections
            voters = models.Voter.get_by_user(self.user)
            self.assertTrue(len(voters) == 0)
    
            # register the voter
            voter = models.Voter.register_user_in_election(self.user, self.election)
            
            # make sure voter is there now
            voter_2 = models.Voter.get_by_election_and_user(self.election, self.user)
    
            self.assertFalse(voter == None)
            self.assertFalse(voter_2 == None)
            self.assertEquals(voter, voter_2)
    
            # make sure voter is there in this call too
            voters = models.Voter.get_by_user(self.user)
            self.assertTrue(len(voters) == 1)
            self.assertEquals(voter, voters[0])
    
            voter_2 = models.Voter.get_by_election_and_uuid(self.election, voter.uuid)
            self.assertEquals(voter, voter_2)
    
            self.assertEquals(voter.user, self.user)
    
    
    
    class VoterModelTests(TestCase):
        fixtures = ['users.json', 'election.json']
    
        def setUp(self):
            self.election = models.Election.objects.get(short_name='test')
    
        def test_create_password_voter(self):
            v = models.Voter(uuid = str(uuid.uuid1()), election = self.election, voter_login_id = 'voter_test_1', voter_name = 'Voter Test 1', voter_email='foobar@acme.com')
    
            v.generate_password()
    
            v.save()
            
            # password has been generated!
            self.assertFalse(v.voter_password == None)
    
            # can't generate passwords twice
            self.assertRaises(Exception, lambda: v.generate_password())
            
            # check that you can get at the voter user structure
            self.assertEquals(v.user.user_id, v.voter_email)
    
    
    class CastVoteModelTests(TestCase):
        fixtures = ['users.json', 'election.json']
    
        def setUp(self):
            self.election = models.Election.objects.get(short_name='test')
            self.user = auth_models.User.objects.get(user_id='ben@adida.net', user_type='google')
    
            # register the voter
            self.voter = models.Voter.register_user_in_election(self.user, self.election)
    
        def test_cast_vote(self):
            pass
    
    class DatatypeTests(TestCase):
        fixtures = ['election.json']
    
        def setUp(self):
            self.election = models.Election.objects.all()[0]
            self.election.generate_trustee(ELGAMAL_PARAMS)
    
        def test_instantiate(self):
            ld_obj = datatypes.LDObject.instantiate(self.election.get_helios_trustee(), '2011/01/Trustee')
            foo = ld_obj.serialize()
    
        def test_from_dict(self):
            ld_obj = datatypes.LDObject.fromDict({
                    'y' : '1234',
                    'p' : '23434',
                    'g' : '2343243242',
                    'q' : '2343242343434'}, type_hint = 'pkc/elgamal/PublicKey')
    
        def test_dictobject_from_dict(self):
            original_dict = {
                'A' : '35423432',
                'B' : '234324243'}
            ld_obj = datatypes.LDObject.fromDict(original_dict, type_hint = 'legacy/EGZKProofCommitment')
    
            self.assertEquals(original_dict, ld_obj.toDict())
            
            
            
    
    ##
    ## Black box tests
    ##
    
    class DataFormatBlackboxTests(object):
        def setUp(self):
            self.election = models.Election.objects.all()[0]
    
        def assertEqualsToFile(self, response, file_path):
            expected = open(file_path)
            self.assertEquals(response.content, expected.read())
            expected.close()
    
        def test_election(self):
            response = self.client.get("/helios/elections/%s" % self.election.uuid, follow=False)
            self.assertEqualsToFile(response, self.EXPECTED_ELECTION_FILE)
    
        def test_voters_list(self):
            response = self.client.get("/helios/elections/%s/voters/" % self.election.uuid, follow=False)
            self.assertEqualsToFile(response, self.EXPECTED_VOTERS_FILE)
    
        def test_trustees_list(self):
            response = self.client.get("/helios/elections/%s/trustees/" % self.election.uuid, follow=False)
            self.assertEqualsToFile(response, self.EXPECTED_TRUSTEES_FILE)
    
        def test_ballots_list(self):
            response = self.client.get("/helios/elections/%s/ballots/" % self.election.uuid, follow=False)
            self.assertEqualsToFile(response, self.EXPECTED_BALLOTS_FILE)
    
    ## now we have a set of fixtures and expected results for various formats
    ## note how TestCase is used as a "mixin" here, so that the generic DataFormatBlackboxTests
    ## does not register as a set of test cases to run, but each concrete data format does.
    
    class LegacyElectionBlackboxTests(DataFormatBlackboxTests, TestCase):
        fixtures = ['legacy-data.json']
        EXPECTED_ELECTION_FILE = 'helios/fixtures/legacy-election-expected.json'
        EXPECTED_VOTERS_FILE = 'helios/fixtures/legacy-election-voters-expected.json'
        EXPECTED_TRUSTEES_FILE = 'helios/fixtures/legacy-trustees-expected.json'
        EXPECTED_BALLOTS_FILE = 'helios/fixtures/legacy-ballots-expected.json'
    
    #class V3_1_ElectionBlackboxTests(DataFormatBlackboxTests, TestCase):
    #    fixtures = ['v3.1-data.json']
    #    EXPECTED_ELECTION_FILE = 'helios/fixtures/v3.1-election-expected.json'
    #    EXPECTED_VOTERS_FILE = 'helios/fixtures/v3.1-election-voters-expected.json'
    #    EXPECTED_TRUSTEES_FILE = 'helios/fixtures/v3.1-trustees-expected.json'
    #    EXPECTED_BALLOTS_FILE = 'helios/fixtures/v3.1-ballots-expected.json'
    
    ##
    ## overall operation of the system
    ##
    
    class ElectionBlackboxTests(TestCase):
        fixtures = ['users.json', 'election.json']
    
        def setUp(self):
            self.election = models.Election.objects.all()[0]
            self.user = auth_models.User.objects.get(user_id='ben@adida.net', user_type='google')
    
        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()        
    
        def clear_login(self):
            session = self.client.session
            del session['user']
            session.save()        
    
        def test_election_params(self):
            response = self.client.get("/helios/elections/params")
            self.assertEquals(response.content, views.ELGAMAL_PARAMS_LD_OBJECT.serialize())
    
        def test_election_bad_trustee(self):
            response = self.client.get("/helios/t/%s/foobar@bar.com/badsecret" % self.election.short_name)
            self.assertEquals(response.status_code, 404)
    
        def test_get_election_shortcut(self):
            response = self.client.get("/helios/e/%s" % self.election.short_name, follow=True)
            self.assertContains(response, self.election.description)
            
        def test_get_election_raw(self):
            response = self.client.get("/helios/elections/%s" % self.election.uuid, follow=False)
            self.assertEquals(response.content, self.election.toJSON())
        
        def test_get_election(self):
            response = self.client.get("/helios/elections/%s/view" % self.election.uuid, follow=False)
            self.assertContains(response, self.election.description)
    
        def test_get_election_questions(self):
            response = self.client.get("/helios/elections/%s/questions" % self.election.uuid, follow=False)
            for q in self.election.questions:
                self.assertContains(response, q['question'])
        
        def test_get_election_trustees(self):
            response = self.client.get("/helios/elections/%s/trustees" % self.election.uuid, follow=False)
            for t in self.election.trustee_set.all():
                self.assertContains(response, t.name)
    
        def test_get_election_voters(self):
            response = self.client.get("/helios/elections/%s/voters/list" % self.election.uuid, follow=False)
            # check total count of voters
            if self.election.num_voters == 0:
                self.assertContains(response, "no voters")
            else:
                self.assertContains(response, "(of %s)" % self.election.num_voters)
    
        def test_get_election_voters_raw(self):
            response = self.client.get("/helios/elections/%s/voters/" % self.election.uuid, follow=False)
            self.assertEquals(len(utils.from_json(response.content)), self.election.num_voters)
            
        def test_election_creation_not_logged_in(self):
            response = self.client.post("/helios/elections/new", {
                    "short_name" : "test-complete",
                    "name" : "Test Complete",
                    "description" : "A complete election test",
                    "election_type" : "referendum",
                    "use_voter_aliases": "0",
                    "use_advanced_audit_features": "1",
                    "private_p" : "0"})
    
            self.assertRedirects(response, "/auth/?return_url=/helios/elections/new")
        
        def test_election_edit(self):
            # a bogus call to set up the session
            self.client.get("/")
    
            self.setup_login()
            response = self.client.get("/helios/elections/%s/edit" % self.election.uuid)
            response = self.client.post("/helios/elections/%s/edit" % self.election.uuid, {
                    "short_name" : self.election.short_name + "-2",
                    "name" : self.election.name,
                    "description" : self.election.description,
                    "election_type" : self.election.election_type,
                    "use_voter_aliases": self.election.use_voter_aliases,
                    'csrf_token': self.client.session['csrf_token']
                    })
    
            self.assertRedirects(response, "/helios/elections/%s/view" % self.election.uuid)
    
            new_election = models.Election.objects.get(uuid = self.election.uuid)
            self.assertEquals(new_election.short_name, self.election.short_name + "-2")
    
        def _setup_complete_election(self, election_params={}):
            "do the setup part of a whole election"
    
            # a bogus call to set up the session
            self.client.get("/")
    
            # REPLACE with params?
            self.setup_login()
    
            # create the election
            full_election_params = {
                "short_name" : "test-complete",
                "name" : "Test Complete",
                "description" : "A complete election test",
                "election_type" : "referendum",
                "use_voter_aliases": "0",
                "use_advanced_audit_features": "1",
                "private_p" : "0"}
    
            # override with the given
            full_election_params.update(election_params)
    
            response = self.client.post("/helios/elections/new", full_election_params)
    
            # we are redirected to the election, let's extract the ID out of the URL
            election_id = re.search('/elections/([^/]+)/', str(response['Location'])).group(1)
    
            # helios is automatically added as a trustee
    
            # check that helios is indeed a trustee
            response = self.client.get("/helios/elections/%s/trustees/view" % election_id)
            self.assertContains(response, "Trustee #1")
    
            # add a few voters, via file upload
            FILE = "helios/fixtures/voter-file.csv"
            voters_file = open(FILE)
            response = self.client.post("/helios/elections/%s/voters/upload" % election_id, {'voters_file': voters_file})
            voters_file.close()
            self.assertContains(response, "first few rows of this file")
    
            # now we confirm the upload
            response = self.client.post("/helios/elections/%s/voters/upload" % election_id, {'confirm_p': "1"})
            self.assertRedirects(response, "/helios/elections/%s/voters/list" % election_id)
    
            # and we want to check that there are now voters
            response = self.client.get("/helios/elections/%s/voters/" % election_id)
            NUM_VOTERS = 4
            self.assertEquals(len(utils.from_json(response.content)), NUM_VOTERS)
    
            # let's get a single voter
            single_voter = models.Election.objects.get(uuid = election_id).voter_set.all()[0]
            response = self.client.get("/helios/elections/%s/voters/%s" % (election_id, single_voter.uuid))
            self.assertContains(response, '"uuid": "%s"' % single_voter.uuid)
    
            response = self.client.get("/helios/elections/%s/voters/foobar" % election_id)
            self.assertEquals(response.status_code, 404)
            
            # add questions
            response = self.client.post("/helios/elections/%s/save_questions" % election_id, {
                    'questions_json': utils.to_json([{"answer_urls": [None,None], "answers": ["Alice", "Bob"], "choice_type": "approval", "max": 1, "min": 0, "question": "Who should be president?", "result_type": "absolute", "short_name": "Who should be president?", "tally_type": "homomorphic"}]),
                    'csrf_token': self.client.session['csrf_token']})
    
            self.assertContains(response, "SUCCESS")
    
            # freeze election
            response = self.client.post("/helios/elections/%s/freeze" % election_id, {
                    "csrf_token" : self.client.session['csrf_token']})
            self.assertRedirects(response, "/helios/elections/%s/view" % election_id)
    
            # email the voters
            num_messages_before = len(mail.outbox)
            response = self.client.post("/helios/elections/%s/voters/email" % election_id, {
                    "csrf_token" : self.client.session['csrf_token'],
                    "subject" : "your password",
                    "body" : "time to vote",
                    "suppress_election_links" : "0",
                    "send_to" : "all"
                    })
            self.assertRedirects(response, "/helios/elections/%s/view" % election_id)
            num_messages_after = len(mail.outbox)
            self.assertEquals(num_messages_after - num_messages_before, NUM_VOTERS)
    
            email_message = mail.outbox[num_messages_before]
            self.assertEquals(email_message.subject, "your password")
    
            # get the username and password
            username = re.search('voter ID: (.*)', email_message.body).group(1)
            password = re.search('password: (.*)', email_message.body).group(1)
    
            # now log out as administrator
            self.clear_login()
            self.assertEquals(self.client.session.has_key('user'), False)
    
            # return the voter username and password to vote
            return election_id, username, password
    
        def _cast_ballot(self, election_id, username, password, need_login=True, check_user_logged_in=False):
            """
            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, {
                    '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')
            encrypted_vote = ballot.serialize()
            
            # cast the ballot
            response = self.client.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))        
    
            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 ")
    
            # confirm the vote
            response = self.client.post("/helios/elections/%s/cast_confirm" % election_id, {
                    "csrf_token" : self.client.session['csrf_token'],
                    "status_update" : False})
            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
            # at position num_messages after, since that was the len() before we cast this ballot
            email_message = mail.outbox[len(mail.outbox) - 1]
            url = re.search('http://[^/]+(/[^ \n]*)', email_message.body).group(1)
    
            # check that we can get at that URL
            if not need_login:
                # confusing piece: if need_login is True, that means it was a public election
                # that required login before casting a ballot.
                # 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
                        })
                
            response = self.client.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')
    
        def _do_tally(self, election_id):
            # log back in as administrator
            self.setup_login()
    
            # encrypted tally
            response = self.client.post("/helios/elections/%s/compute_tally" % election_id, {
                    "csrf_token" : self.client.session['csrf_token']                
                    })
            self.assertRedirects(response, "/helios/elections/%s/view" % election_id)
    
            # should trigger helios decryption automatically
            self.assertNotEquals(models.Election.objects.get(uuid=election_id).get_helios_trustee().decryption_proofs, None)
    
            # combine decryptions
            response = self.client.post("/helios/elections/%s/combine_decryptions" % election_id, {
                    "csrf_token" : self.client.session['csrf_token'],
                    "subject" : "tally subject",
                    "body" : "tally body",
                    "send_to" : "all"
                    })
            self.assertRedirects(response, "/helios/elections/%s/view" % election_id)
    
            # check that tally matches
            response = self.client.get("/helios/elections/%s/result" % election_id)
            self.assertEquals(utils.from_json(response.content), [[0,1]])
            
        def test_do_complete_election(self):
            election_id, username, password = self._setup_complete_election()
            
            # cast a ballot while not logged in
            self._cast_ballot(election_id, username, password, check_user_logged_in=False)
    
            # cast a ballot while logged in as a user (not a voter)
            self.setup_login()
            self._cast_ballot(election_id, username, password, check_user_logged_in=True)
            self.clear_login()
    
            self._do_tally(election_id)
    
        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
                    })
    
            # FIXME: probably better to fetch password_voter_login as a get and post the form obtained
            # rather than assume return_url
            self.assertRedirects(response, "/helios/elections/%s/view" % election_id)
    
            self._cast_ballot(election_id, username, password, need_login = False)
            self._do_tally(election_id)
    
        def test_do_complete_election_with_trustees(self):
            """
            FIXME: do the this test
            """
            pass