From 219f5131e11b538925a084846f8423a4fc527485 Mon Sep 17 00:00:00 2001
From: Ben Adida <ben@adida.net>
Date: Wed, 29 Dec 2010 11:59:14 -0800
Subject: [PATCH] beginning of modularization and additional tests

---
 auth/tests.py                     | 13 +++--
 helios/crypto/electionalgs.py     |  1 +
 helios/datatypes/__init__.py      | 90 +++++++++++++++++++++++++++++++
 helios/datatypes/legacy.py        |  5 ++
 helios/fixtures/election.json     |  2 +-
 helios/fixtures/voter-file.csv    |  4 ++
 helios/forms.py                   |  2 +-
 helios/models.py                  | 18 ++++++-
 helios/templates/voters_list.html |  2 +-
 helios/tests.py                   | 47 +++++++++++++++-
 helios/views.py                   |  2 +
 11 files changed, 175 insertions(+), 11 deletions(-)
 create mode 100644 helios/datatypes/__init__.py
 create mode 100644 helios/datatypes/legacy.py
 create mode 100644 helios/fixtures/voter-file.csv

diff --git a/auth/tests.py b/auth/tests.py
index b88220f..b989d48 100644
--- a/auth/tests.py
+++ b/auth/tests.py
@@ -90,17 +90,20 @@ class UserBlackboxTests(TestCase):
 
     def setUp(self):
         # create a bogus user
-        self.test_user = models.User.objects.create(user_type='password',user_id='foobar_user',name="Foobar User", info={'password':'foobaz', 'email':'foobar-test@adida.net'})
+        self.test_user = models.User.objects.create(user_type='password',user_id='foobar-test@adida.net',name="Foobar User", info={'password':'foobaz'})
 
     def test_password_login(self):
+        ## we can't test this anymore until it's election specific
+        pass
+
         # get to the login page
-        login_page_response = self.client.get(reverse(views.start, kwargs={'system_name':'password'}), follow=True)
+        # login_page_response = self.client.get(reverse(views.start, kwargs={'system_name':'password'}), follow=True)
 
         # log in and follow all redirects
-        response = self.client.post(reverse(password_views.password_login_view), {'username' : 'foobar_user', 'password': 'foobaz'}, follow=True)
+        # response = self.client.post(reverse(password_views.password_login_view), {'username' : 'foobar_user', 'password': 'foobaz'}, follow=True)
 
-        self.assertContains(response, "logged in as")
-        self.assertContains(response, "Foobar User")
+        # self.assertContains(response, "logged in as")
+        # self.assertContains(response, "Foobar User")
 
     def test_logout(self):
         response = self.client.post(reverse(views.logout), follow=True)
diff --git a/helios/crypto/electionalgs.py b/helios/crypto/electionalgs.py
index 609b5ad..2532504 100644
--- a/helios/crypto/electionalgs.py
+++ b/helios/crypto/electionalgs.py
@@ -21,6 +21,7 @@ class HeliosObject(object):
   JSON_FIELDS = None
 
   def __init__(self, **kwargs):
+    import pdb; pdb.set_trace()
     self.set_from_args(**kwargs)
     
     # generate uuid if need be
diff --git a/helios/datatypes/__init__.py b/helios/datatypes/__init__.py
new file mode 100644
index 0000000..9618483
--- /dev/null
+++ b/helios/datatypes/__init__.py
@@ -0,0 +1,90 @@
+"""
+The Helios datatypes are RDF datatypes that map to JSON-LD
+"""
+
+from helios import utils
+
+class LDObject(object):
+    """
+    A linked-data object wraps another object and serializes it according to a particular
+    data format. For example, a legacy election LDObject instance will wrap an Election object
+    and serialize its fields according to the specs for that version.
+
+    To accomodate old JSON types, we allow  classes to override default JSON-LD fields.
+    """
+
+    # whether or not to add JSON-LD things
+    USE_JSON_LD = True
+
+    # fields to serialize
+    FIELDS = []
+
+    def set_from_args(self, **kwargs):
+        for f in self.FIELDS:
+            if kwargs.has_key(f):
+                new_val = self.process_value_in(f, kwargs[f])
+                setattr(self, f, new_val)
+            else:
+                setattr(self, f, None)
+        
+    def serialize(self):
+        return utils.to_json(self.toDict())
+    
+    def toDict(self, alternate_fields=None):
+        val = {}
+        for f in (alternate_fields or self.FIELDS):
+            val[f] = self.process_value_out(f, getattr(self, f))
+        return val
+    
+    @classmethod
+    def fromDict(cls, d):
+        # go through the keys and fix them
+        new_d = {}
+        for k in d.keys():
+            new_d[str(k)] = d[k]
+      
+        return cls(**new_d)    
+
+    @property
+    def hash(self):
+        s = self.serialize()
+        return utils.hash_b64(s)
+    
+    def process_value_in(self, field_name, field_value):
+        """
+        process some fields on the way into the object
+        """
+        if field_value == None:
+            return None
+      
+        val = self._process_value_in(field_name, field_value)
+        if val != None:
+            return val
+        else:
+            return field_value
+    
+    def _process_value_in(self, field_name, field_value):
+        return None
+
+    def process_value_out(self, field_name, field_value):
+        """
+        process some fields on the way out of the object
+        """
+        if field_value == None:
+            return None
+      
+        val = self._process_value_out(field_name, field_value)
+        if val != None:
+            return val
+        else:
+            return field_value
+  
+    def _process_value_out(self, field_name, field_value):
+        return None
+    
+    def __eq__(self, other):
+        if not hasattr(self, 'uuid'):
+            return super(LDObject,self) == other
+    
+        return other != None and self.uuid == other.uuid
+  
diff --git a/helios/datatypes/legacy.py b/helios/datatypes/legacy.py
new file mode 100644
index 0000000..941f15f
--- /dev/null
+++ b/helios/datatypes/legacy.py
@@ -0,0 +1,5 @@
+"""
+Legacy datatypes for Helios (v3.0)
+"""
+
+class 
diff --git a/helios/fixtures/election.json b/helios/fixtures/election.json
index 6159a99..9833051 100644
--- a/helios/fixtures/election.json
+++ b/helios/fixtures/election.json
@@ -7,7 +7,7 @@
             "short_name" : "test",
             "name" : "Test Election",
             "election_type" : "election",
-            "advanced_audit_features" : true,
+            "use_advanced_audit_features" : true,
             "private_p" : false,
             "description" : "test descriptoin",
             "public_key" : null,
diff --git a/helios/fixtures/voter-file.csv b/helios/fixtures/voter-file.csv
new file mode 100644
index 0000000..7a6a77d
--- /dev/null
+++ b/helios/fixtures/voter-file.csv
@@ -0,0 +1,4 @@
+benadida5,ben5@adida.net,Ben5 Adida
+benadida6,ben6@adida.net,Ben6 Adida
+benadida7,ben7@adida.net,Ben7 Adida
+benadida8,ben8@adida.net,Ben8 Adida
diff --git a/helios/forms.py b/helios/forms.py
index 3ac8951..e7973d6 100644
--- a/helios/forms.py
+++ b/helios/forms.py
@@ -13,7 +13,7 @@ class ElectionForm(forms.Form):
   description = forms.CharField(max_length=2000, widget=forms.Textarea(attrs={'cols': 70, 'wrap': 'soft'}))
   election_type = forms.ChoiceField(label="type", choices = Election.ELECTION_TYPES)
   use_voter_aliases = forms.BooleanField(required=False, initial=False, help_text='if selected, voter identities will be replaced with aliases, e.g. "V12", in the ballot tracking center')
-  advanced_audit_features = forms.BooleanField(required=False, initial=True, help_text='disable this only if you want a simple election with reduced security but a simpler user interface')
+  use_advanced_audit_features = forms.BooleanField(required=False, initial=True, help_text='disable this only if you want a simple election with reduced security but a simpler user interface')
   private_p = forms.BooleanField(required=False, initial=False, label="Private?", help_text='a private election is only visible to registered/eligible voters')
   
 
diff --git a/helios/models.py b/helios/models.py
index 57fa376..2952312 100644
--- a/helios/models.py
+++ b/helios/models.py
@@ -32,6 +32,12 @@ class Election(models.Model, electionalgs.Election):
   admin = models.ForeignKey(User)
   
   uuid = models.CharField(max_length=50, null=False)
+
+  # keep track of the type and version of election, which will help dispatch to the right
+  # code, both for crypto and serialization
+  # v3 and prior have a datatype of "legacy/election"
+  # v3.1 and above have a specific datatype of "2011/01/election"
+  datatype = models.CharField(max_length=250, null=False, default="2011/01/election")
   
   short_name = models.CharField(max_length=100)
   name = models.CharField(max_length=250)
@@ -42,7 +48,6 @@ class Election(models.Model, electionalgs.Election):
     )
 
   election_type = models.CharField(max_length=250, null=False, default='election', choices = ELECTION_TYPES)
-  advanced_audit_features = models.BooleanField(default=True, null=False)
   private_p = models.BooleanField(default=False, null=False)
 
   description = models.TextField()
@@ -64,6 +69,7 @@ class Election(models.Model, electionalgs.Election):
     
   # voter aliases?
   use_voter_aliases = models.BooleanField(default=False)
+  use_advanced_audit_features = models.BooleanField(default=True, null=False)
   
   # where votes should be cast
   cast_url = models.CharField(max_length = 500)
@@ -653,6 +659,16 @@ class Voter(models.Model, electionalgs.Voter):
   def get_by_user(cls, user):
     return cls.objects.select_related().filter(user = user).order_by('-cast_at')
 
+  @property
+  def vote_tinyhash(self):
+    """
+    get the tinyhash of the latest castvote
+    """
+    if not self.vote_hash:
+      return None
+    
+    return CastVote.objects.get(vote_hash = self.vote_hash).vote_tinyhash
+
   @property
   def election_uuid(self):
     return self.election.uuid
diff --git a/helios/templates/voters_list.html b/helios/templates/voters_list.html
index 043c2fc..6ec12c1 100644
--- a/helios/templates/voters_list.html
+++ b/helios/templates/voters_list.html
@@ -107,7 +107,7 @@ Voters {{voters_page.start_index}} - {{voters_page.end_index}} (of {{total_voter
 {% if election.use_voter_aliases %}
 <td>{{voter.alias}}</td>
 {% endif %}
-<td><tt style="font-size: 1.4em;;">{% if voter.vote_hash %}{{voter.vote_hash}} <span style="font-size:0.8em;">[<a href="{% url helios.views.voter_last_vote election_uuid=election.uuid,voter_uuid=voter.uuid %}">view</a>]</span>{% else %}&mdash;{% endif %}</tt></td>
+<td><tt style="font-size: 1.4em;;">{% if voter.vote_hash %}{{voter.vote_hash}} <span style="font-size:0.8em;">[<a href="{% url helios.views.castvote_shortcut vote_tinyhash=voter.vote_tinyhash %}">view</a>]</span>{% else %}&mdash;{% endif %}</tt></td>
 </tr>
 {% endfor %}
 </table>
diff --git a/helios/tests.py b/helios/tests.py
index e5a3222..fd9aaff 100644
--- a/helios/tests.py
+++ b/helios/tests.py
@@ -7,6 +7,7 @@ import unittest, datetime
 import models
 from auth import models as auth_models
 from views import ELGAMAL_PARAMS
+import views
 
 from django.db import IntegrityError, transaction
 
@@ -14,6 +15,8 @@ from django.test.client import Client
 from django.test import TestCase
 
 from django.core import mail
+from django.core.files import File
+from django.core.urlresolvers import reverse
 
 import uuid
 
@@ -57,7 +60,11 @@ class ElectionModelTests(TestCase):
         self.assertEquals(self.election, election)
         
     def test_add_voters_file(self):
-        pass
+        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()
 
     def test_check_issues_before_freeze(self):
         # should be two issues: no trustees, and no questions
@@ -176,7 +183,8 @@ class VoterModelTests(TestCase):
         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')
+        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()
@@ -187,3 +195,38 @@ class VoterModelTests(TestCase):
         # 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)
+
+##
+## Black box tests
+##
+
+class ElectionBlackboxTests(TestCase):
+    fixtures = ['users.json', 'election.json']
+
+    def setUp(self):
+        self.election = models.Election.objects.all()[0]        
+
+    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):
+        assert False
+    
+    def test_get_election_trustees(self):
+        assert False
+
+    def test_get_election_voters(self):
+        assert False
+        
+        
diff --git a/helios/views.py b/helios/views.py
index 713fffb..947d7fe 100644
--- a/helios/views.py
+++ b/helios/views.py
@@ -215,6 +215,8 @@ def one_election_schedule(request, election):
 @election_view()
 @json
 def one_election(request, election):
+  if not election:
+    raise Http404
   return election.toJSONDict()
 
 @election_view()
-- 
GitLab