CIA/e-voting-system/frontend/__tests__/elections-api.test.ts
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

195 lines
5.5 KiB
TypeScript

/**
* Elections API Tests
* Tests for Bug #1: Missing /api/elections/upcoming and /completed endpoints
*/
import * as api from "@/lib/api"
// Mock fetch
global.fetch = jest.fn()
describe("Elections API - Bug #1: Missing Endpoints Fix", () => {
beforeEach(() => {
jest.clearAllMocks()
localStorage.getItem = jest.fn().mockReturnValue("test-token")
})
test("getActive elections endpoint works", async () => {
const mockElections = [
{
id: 1,
name: "Active Election",
description: "Currently active",
start_date: new Date().toISOString(),
end_date: new Date(Date.now() + 86400000).toISOString(),
is_active: true,
results_published: false,
candidates: [],
},
]
;(global.fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => mockElections,
})
const response = await api.electionsApi.getActive()
expect(response.status).toBe(200)
expect(response.data).toEqual(mockElections)
expect(global.fetch).toHaveBeenCalledWith(
expect.stringContaining("/api/elections/active"),
expect.any(Object)
)
})
test("getUpcoming elections endpoint works", async () => {
const mockUpcomingElections = [
{
id: 2,
name: "Upcoming Election",
description: "Starting soon",
start_date: new Date(Date.now() + 864000000).toISOString(),
end_date: new Date(Date.now() + 950400000).toISOString(),
is_active: true,
results_published: false,
candidates: [],
},
]
;(global.fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => mockUpcomingElections,
})
const response = await api.electionsApi.getUpcoming()
expect(response.status).toBe(200)
expect(response.data).toEqual(mockUpcomingElections)
expect(global.fetch).toHaveBeenCalledWith(
expect.stringContaining("/api/elections/upcoming"),
expect.any(Object)
)
})
test("getCompleted elections endpoint works", async () => {
const mockCompletedElections = [
{
id: 3,
name: "Completed Election",
description: "Already finished",
start_date: new Date(Date.now() - 864000000).toISOString(),
end_date: new Date(Date.now() - 777600000).toISOString(),
is_active: true,
results_published: true,
candidates: [],
},
]
;(global.fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => mockCompletedElections,
})
const response = await api.electionsApi.getCompleted()
expect(response.status).toBe(200)
expect(response.data).toEqual(mockCompletedElections)
expect(global.fetch).toHaveBeenCalledWith(
expect.stringContaining("/api/elections/completed"),
expect.any(Object)
)
})
test("all election endpoints accept authentication token", async () => {
;(global.fetch as jest.Mock).mockResolvedValue({
ok: true,
status: 200,
json: async () => [],
})
const token = "test-auth-token"
;(localStorage.getItem as jest.Mock).mockReturnValue(token)
await api.electionsApi.getActive()
const callArgs = (global.fetch as jest.Mock).mock.calls[0][1]
expect(callArgs.headers.Authorization).toBe(`Bearer ${token}`)
})
test("election endpoints handle errors gracefully", async () => {
const errorMessage = "Server error"
;(global.fetch as jest.Mock).mockResolvedValueOnce({
ok: false,
status: 500,
json: async () => ({ detail: errorMessage }),
})
const response = await api.electionsApi.getUpcoming()
expect(response.error).toBeDefined()
expect(response.status).toBe(500)
})
test("election endpoints return array of elections", async () => {
const mockElections = [
{ id: 1, name: "Election 1" },
{ id: 2, name: "Election 2" },
]
;(global.fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => mockElections,
})
const response = await api.electionsApi.getActive()
expect(Array.isArray(response.data)).toBe(true)
expect(response.data).toHaveLength(2)
})
})
describe("Elections API - Response Format Consistency", () => {
test("all election endpoints return consistent response format", async () => {
const mockData = []
;(global.fetch as jest.Mock).mockResolvedValue({
ok: true,
status: 200,
json: async () => mockData,
})
const activeResp = await api.electionsApi.getActive()
const upcomingResp = await api.electionsApi.getUpcoming()
const completedResp = await api.electionsApi.getCompleted()
// All should have same structure
expect(activeResp).toHaveProperty("data")
expect(activeResp).toHaveProperty("status")
expect(upcomingResp).toHaveProperty("data")
expect(upcomingResp).toHaveProperty("status")
expect(completedResp).toHaveProperty("data")
expect(completedResp).toHaveProperty("status")
})
test("election endpoints return array directly, not wrapped in object", async () => {
const mockElections = [{ id: 1, name: "Test" }]
;(global.fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => mockElections,
})
const response = await api.electionsApi.getActive()
// Should be array, not { elections: [...] }
expect(Array.isArray(response.data)).toBe(true)
expect(response.data[0].name).toBe("Test")
})
})