feat(roadmap): implementar Sprints 1, 2 y 3 (MVC, Stateless JWT API y Spring Security)
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import unittest
|
||||
import json
|
||||
from app import create_app, db
|
||||
from app.models.user import User
|
||||
from app.services.jwt_service import JWTService
|
||||
|
||||
class TestJWTAndAPI(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.app = create_app()
|
||||
self.app.config['TESTING'] = True
|
||||
self.client = self.app.test_client()
|
||||
self.app_context = self.app.app_context()
|
||||
self.app_context.push()
|
||||
|
||||
# Buscar usuario activo o crear de prueba
|
||||
self.test_user = User.query.filter_by(is_active=True).first()
|
||||
if not self.test_user:
|
||||
self.test_user = User(
|
||||
email="test_api_admin@unicaba.edu.ar",
|
||||
name="Test API Admin",
|
||||
role="ADMIN",
|
||||
is_active=True
|
||||
)
|
||||
self.test_user.set_password("Test1234!")
|
||||
db.session.add(self.test_user)
|
||||
db.session.commit()
|
||||
|
||||
def tearDown(self):
|
||||
db.session.rollback()
|
||||
self.app_context.pop()
|
||||
|
||||
def test_jwt_generation_and_decoding(self):
|
||||
tokens = JWTService.generate_tokens(self.test_user)
|
||||
self.assertIn('access_token', tokens)
|
||||
self.assertIn('refresh_token', tokens)
|
||||
|
||||
payload = JWTService.decode_token(tokens['access_token'], expected_type='access')
|
||||
self.assertEqual(payload['sub'], str(self.test_user.id))
|
||||
self.assertEqual(payload['email'], self.test_user.email)
|
||||
|
||||
def test_jwt_revocation(self):
|
||||
tokens = JWTService.generate_tokens(self.test_user)
|
||||
token = tokens['access_token']
|
||||
# Antes de revocar: válido
|
||||
payload = JWTService.decode_token(token, expected_type='access')
|
||||
self.assertIsNotNone(payload)
|
||||
|
||||
# Revocar
|
||||
revoked = JWTService.revoke_token(token)
|
||||
self.assertTrue(revoked)
|
||||
|
||||
# Después de revocar: debe lanzar InvalidTokenError
|
||||
with self.assertRaises(Exception):
|
||||
JWTService.decode_token(token, expected_type='access')
|
||||
|
||||
def test_protected_endpoints_require_token(self):
|
||||
# Sin token debe dar 401
|
||||
res = self.client.get('/api/v1/auth/me')
|
||||
self.assertEqual(res.status_code, 401)
|
||||
|
||||
res_classrooms = self.client.get('/api/v1/classrooms')
|
||||
self.assertEqual(res_classrooms.status_code, 401)
|
||||
|
||||
def test_authorized_endpoint_access(self):
|
||||
tokens = JWTService.generate_tokens(self.test_user)
|
||||
headers = {'Authorization': f"Bearer {tokens['access_token']}"}
|
||||
|
||||
res = self.client.get('/api/v1/auth/me', headers=headers)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
data = res.get_json()
|
||||
self.assertEqual(data['email'], self.test_user.email)
|
||||
|
||||
res_classrooms = self.client.get('/api/v1/classrooms', headers=headers)
|
||||
self.assertEqual(res_classrooms.status_code, 200)
|
||||
|
||||
def test_token_refresh_flow(self):
|
||||
tokens = JWTService.generate_tokens(self.test_user)
|
||||
res = self.client.post('/api/v1/auth/refresh', json={'refresh_token': tokens['refresh_token']})
|
||||
self.assertEqual(res.status_code, 200)
|
||||
data = res.get_json()
|
||||
self.assertIn('access_token', data)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,42 @@
|
||||
import unittest
|
||||
from app import create_app, db
|
||||
from app.models.audit_log import AuditLog
|
||||
from app.services.audit_service import AuditService
|
||||
|
||||
class TestSecurityChainAndAudit(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.app = create_app()
|
||||
self.app.config['TESTING'] = True
|
||||
self.client = self.app.test_client()
|
||||
self.app_context = self.app.app_context()
|
||||
self.app_context.push()
|
||||
|
||||
def tearDown(self):
|
||||
db.session.rollback()
|
||||
self.app_context.pop()
|
||||
|
||||
def test_security_headers_injected(self):
|
||||
res = self.client.get('/login')
|
||||
self.assertIn('X-Content-Type-Options', res.headers)
|
||||
self.assertEqual(res.headers['X-Content-Type-Options'], 'nosniff')
|
||||
self.assertIn('X-Frame-Options', res.headers)
|
||||
self.assertEqual(res.headers['X-Frame-Options'], 'SAMEORIGIN')
|
||||
self.assertIn('X-XSS-Protection', res.headers)
|
||||
self.assertEqual(res.headers['X-XSS-Protection'], '1; mode=block')
|
||||
|
||||
def test_audit_log_creation(self):
|
||||
entry = AuditService.log(
|
||||
action='TEST_ACTION',
|
||||
module='classrooms',
|
||||
entity_id=999,
|
||||
details={'test': True, 'desc': 'Prueba unitaria de auditoría'}
|
||||
)
|
||||
self.assertIsNotNone(entry.id)
|
||||
self.assertEqual(entry.action, 'TEST_ACTION')
|
||||
self.assertEqual(entry.module, 'classrooms')
|
||||
|
||||
recent = AuditService.get_recent_logs(limit=5, module='classrooms')
|
||||
self.assertTrue(any(e.id == entry.id for e in recent))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,60 @@
|
||||
import unittest
|
||||
from datetime import datetime, timedelta
|
||||
from app import create_app, db
|
||||
from app.models.user import User
|
||||
from app.models.classroom import Classroom
|
||||
from app.models.building import Building
|
||||
from app.models.subject import Commission
|
||||
from app.schemas.classroom_dto import ClassroomCreateDTO
|
||||
from app.schemas.reservation_dto import ReservationCreateDTO
|
||||
from app.services.classroom_service import ClassroomService
|
||||
from app.services.reservation_service import ReservationService
|
||||
from app.services.user_service import UserService
|
||||
|
||||
class TestServicesAndDTO(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.app = create_app()
|
||||
self.app.config['TESTING'] = True
|
||||
self.app.config['WTF_CSRF_ENABLED'] = False
|
||||
self.app_context = self.app.app_context()
|
||||
self.app_context.push()
|
||||
|
||||
self.classroom_service = ClassroomService()
|
||||
self.reservation_service = ReservationService()
|
||||
self.user_service = UserService()
|
||||
|
||||
def tearDown(self):
|
||||
db.session.rollback()
|
||||
self.app_context.pop()
|
||||
|
||||
def test_dto_validation(self):
|
||||
# Valid ClassroomCreateDTO
|
||||
dto = ClassroomCreateDTO(code="Aula Test 101", floor="1", capacity=40, building_name="Edificio Central")
|
||||
self.assertEqual(dto.code, "Aula Test 101")
|
||||
self.assertEqual(dto.capacity, 40)
|
||||
|
||||
# Invalid Reservation range (end before start)
|
||||
with self.assertRaises(Exception):
|
||||
ReservationCreateDTO(
|
||||
classroom_id=1,
|
||||
commission_id=1,
|
||||
user_id=1,
|
||||
start_time=datetime(2026, 9, 10, 10, 0),
|
||||
end_time=datetime(2026, 9, 10, 9, 0),
|
||||
purpose="Clase Test"
|
||||
)
|
||||
|
||||
def test_classroom_service_listing(self):
|
||||
classrooms = self.classroom_service.list_classrooms()
|
||||
self.assertIsInstance(classrooms, list)
|
||||
|
||||
def test_user_service_profile(self):
|
||||
user = User.query.first()
|
||||
if user:
|
||||
profile = self.user_service.get_profile_data(user)
|
||||
self.assertIn('email', profile)
|
||||
self.assertIn('role', profile)
|
||||
self.assertIn('permissions', profile)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user