CIA/e-voting-system/frontend/__tests__/auth-context.test.tsx
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

261 lines
6.5 KiB
TypeScript

/**
* Auth Context Tests
* Tests for the authentication context and has_voted state fix
*/
import React from "react"
import { render, screen, waitFor } from "@testing-library/react"
import { AuthProvider, useAuth } from "@/lib/auth-context"
import * as api from "@/lib/api"
// Mock the API module
jest.mock("@/lib/api", () => ({
authApi: {
login: jest.fn(),
register: jest.fn(),
getProfile: jest.fn(),
logout: jest.fn(),
},
getAuthToken: jest.fn(),
setAuthToken: jest.fn(),
clearAuthToken: jest.fn(),
}))
// Mock window.localStorage
const localStorageMock = {
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn(),
clear: jest.fn(),
}
global.localStorage = localStorageMock as any
describe("Auth Context - Bug #2: has_voted State Fix", () => {
beforeEach(() => {
jest.clearAllMocks()
localStorageMock.getItem.mockReturnValue(null)
})
test("login response includes has_voted field", async () => {
const mockLoginResponse = {
data: {
access_token: "test-token",
id: 1,
email: "test@example.com",
first_name: "Test",
last_name: "User",
has_voted: false,
expires_in: 1800,
},
status: 200,
}
;(api.authApi.login as jest.Mock).mockResolvedValue(mockLoginResponse)
;(api.setAuthToken as jest.Mock).mockImplementation(() => {})
let authContextValue: any
const TestComponent = () => {
authContextValue = useAuth()
return <div>{authContextValue.isLoading ? "Loading..." : "Ready"}</div>
}
const { rerender } = render(
<AuthProvider>
<TestComponent />
</AuthProvider>
)
// Simulate login
await waitFor(async () => {
await authContextValue.login("test@example.com", "password123")
})
expect(authContextValue.user).toBeDefined()
expect(authContextValue.user?.has_voted).toBeDefined()
expect(typeof authContextValue.user?.has_voted).toBe("boolean")
})
test("register response includes has_voted field", async () => {
const mockRegisterResponse = {
data: {
access_token: "test-token",
id: 2,
email: "newuser@example.com",
first_name: "New",
last_name: "User",
has_voted: false,
expires_in: 1800,
},
status: 200,
}
;(api.authApi.register as jest.Mock).mockResolvedValue(mockRegisterResponse)
;(api.setAuthToken as jest.Mock).mockImplementation(() => {})
let authContextValue: any
const TestComponent = () => {
authContextValue = useAuth()
return <div>{authContextValue.isLoading ? "Loading..." : "Ready"}</div>
}
render(
<AuthProvider>
<TestComponent />
</AuthProvider>
)
// Simulate registration
await waitFor(async () => {
await authContextValue.register(
"newuser@example.com",
"password123",
"New",
"User",
"ID123456"
)
})
expect(authContextValue.user?.has_voted).toBe(false)
})
test("has_voted is correctly set from server response, not hardcoded", async () => {
const mockLoginResponseVoted = {
data: {
access_token: "test-token",
id: 3,
email: "voted@example.com",
first_name: "Voted",
last_name: "User",
has_voted: true, // User has already voted
expires_in: 1800,
},
status: 200,
}
;(api.authApi.login as jest.Mock).mockResolvedValue(mockLoginResponseVoted)
;(api.setAuthToken as jest.Mock).mockImplementation(() => {})
let authContextValue: any
const TestComponent = () => {
authContextValue = useAuth()
return <div>{authContextValue.isLoading ? "Loading..." : "Ready"}</div>
}
render(
<AuthProvider>
<TestComponent />
</AuthProvider>
)
// Simulate login with user who has voted
await waitFor(async () => {
await authContextValue.login("voted@example.com", "password123")
})
// Verify has_voted is true (from server) not false (hardcoded)
expect(authContextValue.user?.has_voted).toBe(true)
})
test("has_voted defaults to false if not in response", async () => {
const mockLoginResponseNoField = {
data: {
access_token: "test-token",
id: 4,
email: "nofield@example.com",
first_name: "No",
last_name: "Field",
// has_voted missing from response
expires_in: 1800,
},
status: 200,
}
;(api.authApi.login as jest.Mock).mockResolvedValue(mockLoginResponseNoField)
;(api.setAuthToken as jest.Mock).mockImplementation(() => {})
let authContextValue: any
const TestComponent = () => {
authContextValue = useAuth()
return <div>{authContextValue.isLoading ? "Loading..." : "Ready"}</div>
}
render(
<AuthProvider>
<TestComponent />
</AuthProvider>
)
await waitFor(async () => {
await authContextValue.login("nofield@example.com", "password123")
})
// Should default to false if not present
expect(authContextValue.user?.has_voted).toBe(false)
})
test("profile refresh updates has_voted state", async () => {
const mockProfileResponse = {
data: {
id: 5,
email: "profile@example.com",
first_name: "Profile",
last_name: "User",
has_voted: true,
created_at: new Date().toISOString(),
},
status: 200,
}
;(api.authApi.getProfile as jest.Mock).mockResolvedValue(mockProfileResponse)
;(api.getAuthToken as jest.Mock).mockReturnValue("test-token")
let authContextValue: any
const TestComponent = () => {
authContextValue = useAuth()
return (
<div>
{authContextValue.user?.has_voted !== undefined
? `has_voted: ${authContextValue.user.has_voted}`
: "no user"}
</div>
)
}
render(
<AuthProvider>
<TestComponent />
</AuthProvider>
)
// Simulate profile refresh
await waitFor(async () => {
await authContextValue.refreshProfile()
})
expect(authContextValue.user?.has_voted).toBe(true)
})
})
describe("Auth Context - API Token Type Fix", () => {
test("AuthToken interface includes has_voted field", () => {
// This test ensures the TypeScript interface is correct
const token: api.AuthToken = {
access_token: "token",
expires_in: 1800,
id: 1,
email: "test@example.com",
first_name: "Test",
last_name: "User",
has_voted: false,
}
expect(token.has_voted).toBeDefined()
expect(typeof token.has_voted).toBe("boolean")
})
})