Spaces:
Sleeping
Sleeping
| """ | |
| Comprehensive unit tests for the Election Assistant application — India 2026 | |
| Covers pages, API, security, Google services, rate limiting, and edge cases. | |
| """ | |
| import unittest | |
| import json | |
| import sys | |
| import os | |
| sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) | |
| from app import create_app, db | |
| from app.models import ElectionProcess, ProcessStep, FAQ, TimelineEvent | |
| class TestPageRoutes(unittest.TestCase): | |
| """Test all page routes return 200 with correct content.""" | |
| def setUp(self): | |
| self.app = create_app('testing') | |
| self.client = self.app.test_client() | |
| self.ctx = self.app.app_context() | |
| self.ctx.push() | |
| db.create_all() | |
| from app.models import seed_election_data | |
| seed_election_data() | |
| def tearDown(self): | |
| db.session.remove() | |
| db.drop_all() | |
| self.ctx.pop() | |
| def test_home_page(self): | |
| r = self.client.get('/') | |
| self.assertEqual(r.status_code, 200) | |
| self.assertIn(b'Election', r.data) | |
| def test_process_page(self): | |
| r = self.client.get('/process') | |
| self.assertEqual(r.status_code, 200) | |
| self.assertIn(b'Election Process', r.data) | |
| def test_timeline_page(self): | |
| r = self.client.get('/timeline') | |
| self.assertEqual(r.status_code, 200) | |
| self.assertIn(b'Timeline', r.data) | |
| def test_faq_page(self): | |
| r = self.client.get('/faq') | |
| self.assertEqual(r.status_code, 200) | |
| self.assertIn(b'FAQ', r.data) | |
| def test_resources_page(self): | |
| r = self.client.get('/resources') | |
| self.assertEqual(r.status_code, 200) | |
| self.assertIn(b'Resources', r.data) | |
| def test_404_page(self): | |
| r = self.client.get('/nonexistent-page') | |
| self.assertEqual(r.status_code, 404) | |
| class TestCoreAPI(unittest.TestCase): | |
| """Test core REST API endpoints.""" | |
| def setUp(self): | |
| self.app = create_app('testing') | |
| self.client = self.app.test_client() | |
| self.ctx = self.app.app_context() | |
| self.ctx.push() | |
| db.create_all() | |
| from app.models import seed_election_data | |
| seed_election_data() | |
| def tearDown(self): | |
| db.session.remove() | |
| db.drop_all() | |
| self.ctx.pop() | |
| def test_election_process(self): | |
| r = self.client.get('/api/election-process') | |
| self.assertEqual(r.status_code, 200) | |
| data = r.get_json() | |
| self.assertIn('India 2026', data['name']) | |
| def test_steps(self): | |
| r = self.client.get('/api/steps') | |
| self.assertEqual(r.status_code, 200) | |
| data = r.get_json() | |
| self.assertIsInstance(data, list) | |
| self.assertEqual(len(data), 5) | |
| def test_timeline(self): | |
| r = self.client.get('/api/timeline') | |
| self.assertEqual(r.status_code, 200) | |
| data = r.get_json() | |
| self.assertIsInstance(data, list) | |
| self.assertGreaterEqual(len(data), 20) | |
| def test_faq(self): | |
| r = self.client.get('/api/faq') | |
| self.assertEqual(r.status_code, 200) | |
| data = r.get_json() | |
| self.assertIsInstance(data, list) | |
| self.assertGreaterEqual(len(data), 10) | |
| def test_faq_categories(self): | |
| r = self.client.get('/api/faq/categories') | |
| self.assertEqual(r.status_code, 200) | |
| cats = r.get_json() | |
| self.assertIsInstance(cats, list) | |
| self.assertGreater(len(cats), 0) | |
| def test_faq_filter_by_category(self): | |
| r = self.client.get('/api/faq?category=Voting') | |
| self.assertEqual(r.status_code, 200) | |
| data = r.get_json() | |
| self.assertIsInstance(data, list) | |
| def test_health(self): | |
| r = self.client.get('/api/health') | |
| self.assertEqual(r.status_code, 200) | |
| self.assertEqual(r.get_json()['status'], 'healthy') | |
| def test_live_stats(self): | |
| r = self.client.get('/api/live/stats') | |
| self.assertEqual(r.status_code, 200) | |
| data = r.get_json() | |
| self.assertIn('total_events', data) | |
| def test_upcoming(self): | |
| r = self.client.get('/api/timeline/upcoming') | |
| self.assertEqual(r.status_code, 200) | |
| def test_events_by_status(self): | |
| r = self.client.get('/api/events/by-status/pending') | |
| self.assertEqual(r.status_code, 200) | |
| def test_invalid_status_filter(self): | |
| r = self.client.get('/api/events/by-status/invalid') | |
| self.assertEqual(r.status_code, 400) | |
| class TestGoogleServices(unittest.TestCase): | |
| """Test Google service integrations.""" | |
| def setUp(self): | |
| self.app = create_app('testing') | |
| self.client = self.app.test_client() | |
| self.ctx = self.app.app_context() | |
| self.ctx.push() | |
| db.create_all() | |
| from app.models import seed_election_data | |
| seed_election_data() | |
| def tearDown(self): | |
| db.session.remove() | |
| db.drop_all() | |
| self.ctx.pop() | |
| def test_google_config_endpoint(self): | |
| r = self.client.get('/api/google/config') | |
| self.assertEqual(r.status_code, 200) | |
| data = r.get_json() | |
| self.assertIn('translate_enabled', data) | |
| self.assertIn('maps_available', data) | |
| self.assertIn('calendar_supported', data) | |
| self.assertTrue(data['calendar_supported']) | |
| def test_calendar_link_valid_event(self): | |
| event = TimelineEvent.query.first() | |
| self.assertIsNotNone(event) | |
| r = self.client.get(f'/api/calendar-link/{event.id}') | |
| self.assertEqual(r.status_code, 200) | |
| data = r.get_json() | |
| self.assertIn('calendar_url', data) | |
| self.assertIn('calendar.google.com', data['calendar_url']) | |
| self.assertIn('event_title', data) | |
| def test_calendar_link_nonexistent_event(self): | |
| r = self.client.get('/api/calendar-link/99999') | |
| self.assertEqual(r.status_code, 404) | |
| def test_calendar_url_format(self): | |
| """Verify generated Calendar URL contains required params.""" | |
| from app.google_services import build_calendar_url | |
| from datetime import datetime | |
| url = build_calendar_url( | |
| title='Test Event', | |
| start_date=datetime(2026, 6, 7, 7, 0, 0), | |
| description='Test description', | |
| ) | |
| self.assertIn('action=TEMPLATE', url) | |
| self.assertIn('text=Test+Event', url) | |
| self.assertIn('calendar.google.com', url) | |
| def test_translate_enabled_by_default(self): | |
| from app.google_services import is_translate_enabled | |
| self.assertTrue(is_translate_enabled()) | |
| class TestSecurity(unittest.TestCase): | |
| """Test security headers and input sanitization.""" | |
| def setUp(self): | |
| self.app = create_app('testing') | |
| self.client = self.app.test_client() | |
| self.ctx = self.app.app_context() | |
| self.ctx.push() | |
| db.create_all() | |
| from app.models import seed_election_data | |
| seed_election_data() | |
| def tearDown(self): | |
| db.session.remove() | |
| db.drop_all() | |
| self.ctx.pop() | |
| def test_csp_header_present(self): | |
| r = self.client.get('/') | |
| self.assertIn('Content-Security-Policy', r.headers) | |
| csp = r.headers['Content-Security-Policy'] | |
| self.assertIn("default-src 'self'", csp) | |
| self.assertIn('translate.google.com', csp) | |
| def test_x_frame_options(self): | |
| r = self.client.get('/') | |
| self.assertEqual(r.headers.get('X-Frame-Options'), 'SAMEORIGIN') | |
| def test_x_content_type_options(self): | |
| r = self.client.get('/') | |
| self.assertEqual(r.headers.get('X-Content-Type-Options'), 'nosniff') | |
| def test_referrer_policy(self): | |
| r = self.client.get('/') | |
| self.assertEqual(r.headers.get('Referrer-Policy'), 'strict-origin-when-cross-origin') | |
| def test_input_sanitization(self): | |
| from app.security import sanitize_string | |
| self.assertEqual(sanitize_string('<script>alert(1)</script>'), | |
| '<script>alert(1)</script>') | |
| def test_sanitize_max_length(self): | |
| from app.security import sanitize_string | |
| result = sanitize_string('a' * 1000, max_length=100) | |
| self.assertEqual(len(result), 100) | |
| def test_sanitize_json_input(self): | |
| from app.security import sanitize_json_input | |
| raw = {'status': 'pending', 'evil': '<script>'} | |
| clean = sanitize_json_input(raw, allowed_keys={'status'}) | |
| self.assertIn('status', clean) | |
| self.assertNotIn('evil', clean) | |
| def test_cache_control_on_api(self): | |
| r = self.client.get('/api/steps') | |
| self.assertIn('Cache-Control', r.headers) | |
| self.assertIn('max-age', r.headers['Cache-Control']) | |
| class TestEdgeCases(unittest.TestCase): | |
| """Test edge cases and error handling.""" | |
| def setUp(self): | |
| self.app = create_app('testing') | |
| self.client = self.app.test_client() | |
| self.ctx = self.app.app_context() | |
| self.ctx.push() | |
| db.create_all() | |
| from app.models import seed_election_data | |
| seed_election_data() | |
| def tearDown(self): | |
| db.session.remove() | |
| db.drop_all() | |
| self.ctx.pop() | |
| def test_update_status_missing_body(self): | |
| event = TimelineEvent.query.first() | |
| r = self.client.put( | |
| f'/api/timeline/{event.id}/status', | |
| content_type='application/json', | |
| data=json.dumps({}), | |
| ) | |
| self.assertEqual(r.status_code, 400) | |
| def test_update_status_invalid_json(self): | |
| event = TimelineEvent.query.first() | |
| r = self.client.put( | |
| f'/api/timeline/{event.id}/status', | |
| content_type='application/json', | |
| data='not-json', | |
| ) | |
| self.assertIn(r.status_code, [400, 500]) | |
| def test_update_status_valid(self): | |
| event = TimelineEvent.query.first() | |
| r = self.client.put( | |
| f'/api/timeline/{event.id}/status', | |
| content_type='application/json', | |
| data=json.dumps({'status': 'completed'}), | |
| ) | |
| self.assertEqual(r.status_code, 200) | |
| self.assertEqual(r.get_json()['new_status'], 'completed') | |
| def test_update_nonexistent_event(self): | |
| r = self.client.put( | |
| '/api/timeline/99999/status', | |
| content_type='application/json', | |
| data=json.dumps({'status': 'completed'}), | |
| ) | |
| self.assertEqual(r.status_code, 404) | |
| def test_config_classes_exist(self): | |
| from app.config import DevelopmentConfig, TestingConfig, ProductionConfig | |
| self.assertTrue(TestingConfig.TESTING) | |
| self.assertFalse(ProductionConfig.DEBUG) | |
| self.assertTrue(DevelopmentConfig.DEBUG) | |
| if __name__ == '__main__': | |
| unittest.main() | |