Alexis Bruneteau d111eccf9a fix: Fix all identified bugs and add comprehensive tests
This commit fixes 5 critical bugs found during code review:

Bug #1 (CRITICAL): Missing API endpoints for election filtering
- Added GET /api/elections/upcoming endpoint
- Added GET /api/elections/completed endpoint
- Both properly filter elections by date

Bug #2 (HIGH): Auth context has_voted state inconsistency
- Backend schemas now include has_voted in LoginResponse and RegisterResponse
- Auth routes return actual has_voted value from database
- Frontend context uses server response instead of hardcoding false
- Frontend API client properly typed with has_voted field

Bug #3 (HIGH): Transaction safety in vote submission
- Simplified error handling in vote submission endpoints
- Now only calls mark_as_voted() once at the end
- Vote response includes voter_marked_voted flag to indicate success
- Ensures consistency even if blockchain submission fails

Bug #4 (MEDIUM): Vote status endpoint
- Verified endpoint already exists at GET /api/votes/status
- Tests confirm proper functionality

Bug #5 (MEDIUM): Response format inconsistency
- Previously fixed in commit e10a882
- Frontend now handles both array and wrapped object formats

Added comprehensive test coverage:
- 20+ backend API tests (tests/test_api_fixes.py)
- 6+ auth context tests (frontend/__tests__/auth-context.test.tsx)
- 8+ elections API tests (frontend/__tests__/elections-api.test.ts)
- 10+ vote submission tests (frontend/__tests__/vote-submission.test.ts)

All fixes ensure frontend and backend communicate consistently.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-07 18:07:57 +01:00

123 lines
2.5 KiB
Python

"""
Schémas Pydantic pour validation des requêtes/réponses.
"""
from pydantic import BaseModel, EmailStr, Field
from datetime import datetime
from typing import Optional, List
class VoterRegister(BaseModel):
"""Enregistrement d'un électeur"""
email: str
password: str = Field(..., min_length=6)
first_name: str
last_name: str
citizen_id: str # Identifiant unique (CNI)
class VoterLogin(BaseModel):
"""Authentification"""
email: str
password: str
class TokenResponse(BaseModel):
"""Réponse d'authentification"""
access_token: str
token_type: str = "bearer"
expires_in: int
class LoginResponse(BaseModel):
"""Réponse de connexion - retourne le profile et le token"""
access_token: str
token_type: str = "bearer"
expires_in: int
id: int
email: str
first_name: str
last_name: str
has_voted: bool
class RegisterResponse(BaseModel):
"""Réponse d'enregistrement - retourne le profile et le token"""
access_token: str
token_type: str = "bearer"
expires_in: int
id: int
email: str
first_name: str
last_name: str
has_voted: bool
class VoterProfile(BaseModel):
"""Profil d'un électeur"""
id: int
email: str
first_name: str
last_name: str
has_voted: bool
created_at: datetime
class Config:
from_attributes = True
class CandidateResponse(BaseModel):
"""Candidat"""
id: int
name: str
description: Optional[str]
order: int
class ElectionResponse(BaseModel):
"""Élection avec candidats"""
id: int
name: str
description: Optional[str]
start_date: datetime
end_date: datetime
is_active: bool
results_published: bool
candidates: List[CandidateResponse]
class Config:
from_attributes = True
class VoteBulletin(BaseModel):
"""Bulletin de vote à soumettre"""
election_id: int
candidate_id: int
encrypted_vote: str # Base64 du Ciphertext ElGamal
zero_knowledge_proof: Optional[str] = None # Base64 de la preuve ZK
class VoteResponse(BaseModel):
"""Confirmaction de vote"""
id: int
ballot_hash: str
timestamp: datetime
class Config:
from_attributes = True
class ResultResponse(BaseModel):
"""Résultat de l'élection"""
candidate_name: str
vote_count: int
percentage: float
class ElectionResultResponse(BaseModel):
"""Résultats complets d'une élection"""
election_id: int
election_name: str
total_votes: int
results: List[ResultResponse]