@@ -542,6 +542,149 @@ def test_token_authentication_functional(self):
542542 self .assertEqual (election .voter_set .get (voting_token = voter .voting_token ).id , voter .id )
543543 self .assertEqual (election .voter_set .get (voting_token = voter2 .voting_token ).id , voter2 .id )
544544
545+ def test_upload_token_voters (self ):
546+ """Test uploading token voters via CSV"""
547+ from unittest .mock import patch
548+
549+ # Create a new election
550+ election = models .Election .objects .create (
551+ admin = auth_models .User .objects .get (user_id = 'ben@adida.net' , user_type = 'google' ),
552+ uuid = str (uuid .uuid4 ()),
553+ short_name = 'token-upload-test' ,
554+ name = 'Token Upload Test Election' ,
555+ election_type = 'election' ,
556+ use_token_auth = False , # Start as False, should auto-enable
557+ cast_url = 'http://localhost:8000/helios'
558+ )
559+
560+ # Upload token voters via VoterFile
561+ csv_content = "token,alice@acme.com,Alice Smith\n token,bob@acme.com,Bob Jones"
562+ voter_file = models .VoterFile (election = election , voter_file_content = csv_content )
563+ voter_file .save ()
564+
565+ # Mock validate_email to avoid DNS issues
566+ with patch ('helios.models.validate_email' , return_value = True ):
567+ voter_file .process ()
568+
569+ # Verify use_token_auth was auto-enabled
570+ election .refresh_from_db ()
571+ self .assertTrue (election .use_token_auth )
572+
573+ # Verify voters were created with tokens
574+ voters = election .voter_set .all ()
575+ self .assertEqual (voters .count (), 2 )
576+
577+ alice = election .voter_set .get (voter_email = 'alice@acme.com' )
578+ self .assertEqual (alice .voter_login_id , 'alice@acme.com' )
579+ self .assertEqual (alice .voter_name , 'Alice Smith' )
580+ self .assertIsNotNone (alice .voting_token )
581+ self .assertEqual (len (alice .voting_token ), 20 )
582+
583+ bob = election .voter_set .get (voter_email = 'bob@acme.com' )
584+ self .assertEqual (bob .voter_login_id , 'bob@acme.com' )
585+ self .assertEqual (bob .voter_name , 'Bob Jones' )
586+ self .assertIsNotNone (bob .voting_token )
587+
588+ def test_upload_mixed_voters_in_same_csv (self ):
589+ """Test that mixing token and password voters in same CSV fails"""
590+ from unittest .mock import patch
591+
592+ election = models .Election .objects .create (
593+ admin = auth_models .User .objects .get (user_id = 'ben@adida.net' , user_type = 'google' ),
594+ uuid = str (uuid .uuid4 ()),
595+ short_name = 'mixed-test' ,
596+ name = 'Mixed Test Election' ,
597+ election_type = 'election' ,
598+ cast_url = 'http://localhost:8000/helios'
599+ )
600+
601+ # CSV with both token and password voters
602+ csv_content = "token,alice@acme.com,Alice Smith\n password,bob123,bob@acme.com,Bob Jones"
603+ voter_file = models .VoterFile (election = election , voter_file_content = csv_content )
604+ voter_file .save ()
605+
606+ # Should raise exception about mixing
607+ with patch ('helios.models.validate_email' , return_value = True ):
608+ with self .assertRaises (Exception ) as context :
609+ voter_file .process ()
610+ self .assertIn ("Cannot mix" , str (context .exception ))
611+
612+ def test_upload_token_voters_after_password_voters (self ):
613+ """Test that uploading token voters to election with password voters fails"""
614+ from unittest .mock import patch
615+
616+ election = models .Election .objects .create (
617+ admin = auth_models .User .objects .get (user_id = 'ben@adida.net' , user_type = 'google' ),
618+ uuid = str (uuid .uuid4 ()),
619+ short_name = 'password-first-test' ,
620+ name = 'Password First Test Election' ,
621+ election_type = 'election' ,
622+ use_token_auth = False ,
623+ cast_url = 'http://localhost:8000/helios'
624+ )
625+
626+ # First upload password voters
627+ csv_content1 = "password,alice123,alice@acme.com,Alice Smith"
628+ voter_file1 = models .VoterFile (election = election , voter_file_content = csv_content1 )
629+ voter_file1 .save ()
630+
631+ with patch ('helios.models.validate_email' , return_value = True ):
632+ voter_file1 .process ()
633+
634+ # Verify password voter was created
635+ self .assertEqual (election .voter_set .count (), 1 )
636+ alice = election .voter_set .first ()
637+ self .assertIsNotNone (alice .voter_password )
638+
639+ # Now try to upload token voters - should fail
640+ csv_content2 = "token,bob@acme.com,Bob Jones"
641+ voter_file2 = models .VoterFile (election = election , voter_file_content = csv_content2 )
642+ voter_file2 .save ()
643+
644+ with patch ('helios.models.validate_email' , return_value = True ):
645+ with self .assertRaises (Exception ) as context :
646+ voter_file2 .process ()
647+ self .assertIn ("already has password voters" , str (context .exception ))
648+
649+ def test_upload_password_voters_after_token_voters (self ):
650+ """Test that uploading password voters to election with token voters fails"""
651+ from unittest .mock import patch
652+
653+ election = models .Election .objects .create (
654+ admin = auth_models .User .objects .get (user_id = 'ben@adida.net' , user_type = 'google' ),
655+ uuid = str (uuid .uuid4 ()),
656+ short_name = 'token-first-test' ,
657+ name = 'Token First Test Election' ,
658+ election_type = 'election' ,
659+ use_token_auth = False ,
660+ cast_url = 'http://localhost:8000/helios'
661+ )
662+
663+ # First upload token voters
664+ csv_content1 = "token,alice@acme.com,Alice Smith"
665+ voter_file1 = models .VoterFile (election = election , voter_file_content = csv_content1 )
666+ voter_file1 .save ()
667+
668+ with patch ('helios.models.validate_email' , return_value = True ):
669+ voter_file1 .process ()
670+
671+ # Verify token voter was created and use_token_auth was enabled
672+ election .refresh_from_db ()
673+ self .assertTrue (election .use_token_auth )
674+ self .assertEqual (election .voter_set .count (), 1 )
675+ alice = election .voter_set .first ()
676+ self .assertIsNotNone (alice .voting_token )
677+
678+ # Now try to upload password voters - should fail
679+ csv_content2 = "password,bob123,bob@acme.com,Bob Jones"
680+ voter_file2 = models .VoterFile (election = election , voter_file_content = csv_content2 )
681+ voter_file2 .save ()
682+
683+ with patch ('helios.models.validate_email' , return_value = True ):
684+ with self .assertRaises (Exception ) as context :
685+ voter_file2 .process ()
686+ self .assertIn ("already has token voters" , str (context .exception ))
687+
545688
546689class CastVoteModelTests (TestCase ):
547690 fixtures = ['users.json' , 'election.json' ]
0 commit comments