chore: depurar scripts obsoletos de migracion y pruebas legacy no requeridas
This commit is contained in:
@@ -1,539 +0,0 @@
|
||||
"""
|
||||
test_enrollment_and_commissions.py
|
||||
Tests for the enrollment management system:
|
||||
- StudentEnrollment model CRUD
|
||||
- Commission management routes (Bedelia/Admin)
|
||||
- /mis-materias routes per role (Docente, Alumno, Admin, Bedelia)
|
||||
- RBAC enrollment permissions
|
||||
- Bedelia role deduplication (no accented duplicate)
|
||||
"""
|
||||
import unittest
|
||||
from app import create_app, db
|
||||
from app.models.user import User
|
||||
from app.models.role import Role, SYSTEM_MODULES
|
||||
from app.models.subject import Subject, Commission
|
||||
from app.models.enrollment import StudentEnrollment
|
||||
|
||||
|
||||
def _login_as(client, user_id):
|
||||
"""Helper: inject Flask-Login session for the given user id."""
|
||||
with client.session_transaction() as sess:
|
||||
sess['_user_id'] = str(user_id)
|
||||
sess['_fresh'] = True
|
||||
|
||||
|
||||
class TestStudentEnrollmentModel(unittest.TestCase):
|
||||
"""Unit tests for the StudentEnrollment SQLAlchemy model."""
|
||||
|
||||
def setUp(self):
|
||||
self.app = create_app()
|
||||
self.app.config['TESTING'] = True
|
||||
self.app.config['WTF_CSRF_ENABLED'] = False
|
||||
self.ctx = self.app.app_context()
|
||||
self.ctx.push()
|
||||
|
||||
def tearDown(self):
|
||||
self.ctx.pop()
|
||||
|
||||
def test_table_exists(self):
|
||||
"""student_enrollments table must exist in the database."""
|
||||
result = db.session.execute(
|
||||
db.text("SELECT to_regclass('public.student_enrollments')")
|
||||
).scalar()
|
||||
self.assertIsNotNone(result, "Tabla student_enrollments no existe en la BD")
|
||||
|
||||
def test_model_columns(self):
|
||||
"""StudentEnrollment must expose all required columns."""
|
||||
cols = {c.name for c in StudentEnrollment.__table__.columns}
|
||||
for expected in ('id', 'student_id', 'commission_id', 'status', 'enrolled_at', 'notes'):
|
||||
self.assertIn(expected, cols, f"Columna faltante: {expected}")
|
||||
|
||||
def test_unique_constraint_name(self):
|
||||
"""Unique constraint uq_student_commission must exist."""
|
||||
constraint_names = {c.name for c in StudentEnrollment.__table__.constraints}
|
||||
self.assertIn('uq_student_commission', constraint_names)
|
||||
|
||||
def test_valid_statuses(self):
|
||||
"""to_dict must return a status in the accepted set."""
|
||||
alumno = User.query.filter(
|
||||
User.role.ilike('%ALUMNO%'), User.is_active == True
|
||||
).first()
|
||||
comm = Commission.query.filter_by(active=True).first()
|
||||
|
||||
if not alumno or not comm:
|
||||
self.skipTest("Se requiere al menos un alumno y una comision activa en la BD")
|
||||
|
||||
# Check if already enrolled to avoid integrity error
|
||||
existing = StudentEnrollment.query.filter_by(
|
||||
student_id=alumno.id, commission_id=comm.id
|
||||
).first()
|
||||
if not existing:
|
||||
enr = StudentEnrollment(
|
||||
student_id=alumno.id,
|
||||
commission_id=comm.id,
|
||||
status='activo',
|
||||
notes='Test enrollment'
|
||||
)
|
||||
db.session.add(enr)
|
||||
db.session.commit()
|
||||
enrolled = enr
|
||||
else:
|
||||
enrolled = existing
|
||||
|
||||
d = enrolled.to_dict()
|
||||
self.assertIn(d['status'], ['activo', 'retirado', 'condicional'])
|
||||
self.assertEqual(d['student_id'], alumno.id)
|
||||
self.assertEqual(d['commission_id'], comm.id)
|
||||
|
||||
# Cleanup (only if we created it)
|
||||
if not existing:
|
||||
db.session.delete(enrolled)
|
||||
# Restore current_students counter
|
||||
comm.current_students = max(0, (comm.current_students or 1) - 1)
|
||||
db.session.commit()
|
||||
|
||||
def test_relationships_accessible(self):
|
||||
"""student and commission relationships must resolve without error."""
|
||||
enr = StudentEnrollment.query.first()
|
||||
if enr:
|
||||
_ = enr.student
|
||||
_ = enr.commission
|
||||
|
||||
|
||||
class TestRBACEnrollmentModule(unittest.TestCase):
|
||||
"""Tests for the enrollment RBAC module and role permissions."""
|
||||
|
||||
def setUp(self):
|
||||
self.app = create_app()
|
||||
self.app.config['TESTING'] = True
|
||||
self.ctx = self.app.app_context()
|
||||
self.ctx.push()
|
||||
|
||||
def tearDown(self):
|
||||
self.ctx.pop()
|
||||
|
||||
def test_enrollment_module_in_system_modules(self):
|
||||
"""SYSTEM_MODULES must contain an 'enrollment' module."""
|
||||
module_ids = [m['id'] for m in SYSTEM_MODULES]
|
||||
self.assertIn('enrollment', module_ids,
|
||||
"Modulo 'enrollment' no encontrado en SYSTEM_MODULES")
|
||||
|
||||
def test_bedelia_has_enrollment_read_write(self):
|
||||
"""Bedelia role must have read_write on enrollment."""
|
||||
bedelia = Role.query.filter_by(name='Bedelia').first()
|
||||
self.assertIsNotNone(bedelia, "Rol 'Bedelia' no existe en la BD")
|
||||
self.assertEqual(bedelia.get_permission('enrollment'), 'read_write')
|
||||
self.assertTrue(bedelia.has_permission('enrollment', 'read_write'))
|
||||
|
||||
def test_admin_has_enrollment_read_write(self):
|
||||
"""Admin role must have read_write on enrollment."""
|
||||
admin = Role.query.filter_by(name='Admin').first()
|
||||
self.assertIsNotNone(admin)
|
||||
self.assertEqual(admin.get_permission('enrollment'), 'read_write')
|
||||
|
||||
def test_docente_has_enrollment_read(self):
|
||||
"""Docente role must have read on enrollment (not write)."""
|
||||
docente = Role.query.filter_by(name='Docente').first()
|
||||
self.assertIsNotNone(docente)
|
||||
self.assertTrue(docente.has_permission('enrollment', 'read'))
|
||||
self.assertFalse(docente.has_permission('enrollment', 'read_write'))
|
||||
|
||||
def test_alumno_has_enrollment_read(self):
|
||||
"""Alumno role must have read on enrollment."""
|
||||
alumno_role = Role.query.filter_by(name='Alumno').first()
|
||||
self.assertIsNotNone(alumno_role)
|
||||
self.assertTrue(alumno_role.has_permission('enrollment', 'read'))
|
||||
|
||||
def test_no_duplicate_bedelia_roles(self):
|
||||
"""There must be exactly one Bedelia role (no accented duplicate)."""
|
||||
bedelia_variants = Role.query.filter(
|
||||
Role.name.in_(['Bedelia', 'Bedelía', 'BEDELÍA'])
|
||||
).all()
|
||||
names = [r.name for r in bedelia_variants]
|
||||
self.assertNotIn('Bedelía', names,
|
||||
"Rol duplicado 'Bedelía' (con acento) todavía existe en la BD")
|
||||
self.assertEqual(len(bedelia_variants), 1,
|
||||
f"Se encontraron {len(bedelia_variants)} variantes de Bedelia: {names}")
|
||||
|
||||
|
||||
class TestCommissionsManagementRoutes(unittest.TestCase):
|
||||
"""Integration tests for /admin/commissions/ routes (Bedelia/Admin)."""
|
||||
|
||||
def setUp(self):
|
||||
self.app = create_app()
|
||||
self.app.config['TESTING'] = True
|
||||
self.app.config['WTF_CSRF_ENABLED'] = False
|
||||
self.client = self.app.test_client()
|
||||
self.ctx = self.app.app_context()
|
||||
self.ctx.push()
|
||||
|
||||
# Login as admin (first active user)
|
||||
self.admin = User.query.filter_by(email='admin@edu-space.com').first()
|
||||
self.bedelia = User.query.filter_by(email='bedelia@edu-space.com').first()
|
||||
self.docente = User.query.filter_by(email='docente@edu-space.com').first()
|
||||
self.alumno_user = User.query.filter_by(email='alumno@edu-space.com').first()
|
||||
|
||||
def tearDown(self):
|
||||
self.ctx.pop()
|
||||
|
||||
def _login(self, user):
|
||||
_login_as(self.client, user.id)
|
||||
|
||||
def test_commissions_list_accessible_by_admin(self):
|
||||
"""Admin can access /admin/commissions."""
|
||||
self._login(self.admin)
|
||||
res = self.client.get('/admin/commissions')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
html = res.get_data(as_text=True)
|
||||
self.assertIn('Gestión de Comisiones', html)
|
||||
|
||||
def test_commissions_list_accessible_by_bedelia(self):
|
||||
"""Bedelia can access /admin/commissions."""
|
||||
if not self.bedelia:
|
||||
self.skipTest("No bedelia user in DB")
|
||||
self._login(self.bedelia)
|
||||
res = self.client.get('/admin/commissions')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
def test_commissions_list_forbidden_for_docente(self):
|
||||
"""Docente without manage permissions should get 403 on /admin/commissions."""
|
||||
if not self.docente:
|
||||
self.skipTest("No docente user in DB")
|
||||
self._login(self.docente)
|
||||
res = self.client.get('/admin/commissions')
|
||||
# Docente has 'academic: read' → can_manage() is False → 403
|
||||
self.assertIn(res.status_code, [403, 302])
|
||||
|
||||
def test_commission_detail_accessible_by_admin(self):
|
||||
"""Commission detail page returns 200 for an existing commission."""
|
||||
self._login(self.admin)
|
||||
comm = Commission.query.filter_by(active=True).first()
|
||||
if not comm:
|
||||
self.skipTest("No active commission in DB")
|
||||
res = self.client.get(f'/admin/commissions/{comm.id}')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
html = res.get_data(as_text=True)
|
||||
self.assertIn('Alumnos Inscriptos', html)
|
||||
self.assertIn('Docente Asignado', html)
|
||||
|
||||
def test_commission_detail_accessible_by_bedelia(self):
|
||||
"""Bedelia can view commission detail."""
|
||||
if not self.bedelia:
|
||||
self.skipTest("No bedelia user in DB")
|
||||
self._login(self.bedelia)
|
||||
comm = Commission.query.filter_by(active=True).first()
|
||||
if not comm:
|
||||
self.skipTest("No active commission in DB")
|
||||
res = self.client.get(f'/admin/commissions/{comm.id}')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
def test_commission_detail_nonexistent_returns_404(self):
|
||||
"""Non-existent commission ID must return 404."""
|
||||
self._login(self.admin)
|
||||
res = self.client.get('/admin/commissions/999999')
|
||||
self.assertEqual(res.status_code, 404)
|
||||
|
||||
def test_assign_teacher_post(self):
|
||||
"""Bedelia can POST to assign a teacher to a commission."""
|
||||
if not self.bedelia or not self.docente:
|
||||
self.skipTest("Need bedelia and docente users")
|
||||
self._login(self.bedelia)
|
||||
comm = Commission.query.filter_by(active=True).first()
|
||||
if not comm:
|
||||
self.skipTest("No active commission in DB")
|
||||
|
||||
res = self.client.post(
|
||||
f'/admin/commissions/{comm.id}/assign-teacher',
|
||||
data={'teacher_id': self.docente.id},
|
||||
follow_redirects=True
|
||||
)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
# Verify assignment persisted
|
||||
db.session.refresh(comm)
|
||||
self.assertEqual(comm.teacher_id, self.docente.id)
|
||||
|
||||
def test_enroll_student_and_unenroll(self):
|
||||
"""Bedelia can enroll and unenroll an alumno in a commission."""
|
||||
if not self.bedelia or not self.alumno_user:
|
||||
self.skipTest("Need bedelia and alumno users")
|
||||
self._login(self.bedelia)
|
||||
comm = Commission.query.filter_by(active=True).first()
|
||||
if not comm:
|
||||
self.skipTest("No active commission in DB")
|
||||
|
||||
# Clean up any prior enrollment to ensure idempotent test
|
||||
prior = StudentEnrollment.query.filter_by(
|
||||
student_id=self.alumno_user.id, commission_id=comm.id
|
||||
).first()
|
||||
if prior:
|
||||
db.session.delete(prior)
|
||||
db.session.commit()
|
||||
|
||||
prev_count = comm.current_students or 0
|
||||
|
||||
# Enroll
|
||||
res = self.client.post(
|
||||
f'/admin/commissions/{comm.id}/enroll-student',
|
||||
data={'student_id': self.alumno_user.id, 'notes': 'Test'},
|
||||
follow_redirects=True
|
||||
)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
enr = StudentEnrollment.query.filter_by(
|
||||
student_id=self.alumno_user.id, commission_id=comm.id
|
||||
).first()
|
||||
self.assertIsNotNone(enr, "Inscripción no encontrada tras POST enroll")
|
||||
self.assertEqual(enr.status, 'activo')
|
||||
db.session.refresh(comm)
|
||||
self.assertEqual(comm.current_students, prev_count + 1)
|
||||
|
||||
# Unenroll
|
||||
res2 = self.client.post(
|
||||
f'/admin/commissions/{comm.id}/unenroll-student/{self.alumno_user.id}',
|
||||
follow_redirects=True
|
||||
)
|
||||
self.assertEqual(res2.status_code, 200)
|
||||
enr_after = StudentEnrollment.query.filter_by(
|
||||
student_id=self.alumno_user.id, commission_id=comm.id
|
||||
).first()
|
||||
self.assertIsNone(enr_after, "Inscripción debería haber sido eliminada")
|
||||
db.session.refresh(comm)
|
||||
self.assertEqual(comm.current_students, prev_count)
|
||||
|
||||
def test_update_enrollment_status(self):
|
||||
"""Bedelia can change enrollment status (activo→condicional→retirado)."""
|
||||
if not self.bedelia or not self.alumno_user:
|
||||
self.skipTest("Need bedelia and alumno users")
|
||||
self._login(self.bedelia)
|
||||
comm = Commission.query.filter_by(active=True).first()
|
||||
if not comm:
|
||||
self.skipTest("No active commission in DB")
|
||||
|
||||
# Ensure enrollment exists
|
||||
enr = StudentEnrollment.query.filter_by(
|
||||
student_id=self.alumno_user.id, commission_id=comm.id
|
||||
).first()
|
||||
if not enr:
|
||||
enr = StudentEnrollment(
|
||||
student_id=self.alumno_user.id,
|
||||
commission_id=comm.id,
|
||||
status='activo'
|
||||
)
|
||||
db.session.add(enr)
|
||||
db.session.commit()
|
||||
|
||||
for new_status in ['condicional', 'retirado', 'activo']:
|
||||
res = self.client.post(
|
||||
f'/admin/commissions/{comm.id}/update-enrollment/{self.alumno_user.id}',
|
||||
data={'status': new_status},
|
||||
follow_redirects=True
|
||||
)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
db.session.refresh(enr)
|
||||
self.assertEqual(enr.status, new_status)
|
||||
|
||||
# Cleanup
|
||||
db.session.delete(enr)
|
||||
db.session.commit()
|
||||
|
||||
def test_duplicate_enrollment_rejected(self):
|
||||
"""Enrolling the same student twice in the same commission is rejected."""
|
||||
if not self.bedelia or not self.alumno_user:
|
||||
self.skipTest("Need bedelia and alumno users")
|
||||
self._login(self.bedelia)
|
||||
comm = Commission.query.filter_by(active=True).first()
|
||||
if not comm:
|
||||
self.skipTest("No active commission in DB")
|
||||
|
||||
# Ensure a clean start
|
||||
prior = StudentEnrollment.query.filter_by(
|
||||
student_id=self.alumno_user.id, commission_id=comm.id
|
||||
).first()
|
||||
if not prior:
|
||||
enr = StudentEnrollment(
|
||||
student_id=self.alumno_user.id,
|
||||
commission_id=comm.id,
|
||||
status='activo'
|
||||
)
|
||||
db.session.add(enr)
|
||||
db.session.commit()
|
||||
|
||||
# Second enroll attempt
|
||||
res = self.client.post(
|
||||
f'/admin/commissions/{comm.id}/enroll-student',
|
||||
data={'student_id': self.alumno_user.id},
|
||||
follow_redirects=True
|
||||
)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
# Should see a warning message, and only one enrollment should exist
|
||||
count = StudentEnrollment.query.filter_by(
|
||||
student_id=self.alumno_user.id, commission_id=comm.id
|
||||
).count()
|
||||
self.assertEqual(count, 1)
|
||||
|
||||
# Cleanup
|
||||
StudentEnrollment.query.filter_by(
|
||||
student_id=self.alumno_user.id, commission_id=comm.id
|
||||
).delete()
|
||||
db.session.commit()
|
||||
|
||||
|
||||
class TestMySubjectsRoutes(unittest.TestCase):
|
||||
"""/mis-materias route tests per role."""
|
||||
|
||||
def setUp(self):
|
||||
self.app = create_app()
|
||||
self.app.config['TESTING'] = True
|
||||
self.app.config['WTF_CSRF_ENABLED'] = False
|
||||
self.client = self.app.test_client()
|
||||
self.ctx = self.app.app_context()
|
||||
self.ctx.push()
|
||||
|
||||
self.admin = User.query.filter_by(email='admin@edu-space.com').first()
|
||||
self.bedelia = User.query.filter_by(email='bedelia@edu-space.com').first()
|
||||
self.docente = User.query.filter_by(email='docente@edu-space.com').first()
|
||||
self.alumno_user = User.query.filter_by(email='alumno@edu-space.com').first()
|
||||
|
||||
def tearDown(self):
|
||||
self.ctx.pop()
|
||||
|
||||
def _login(self, user):
|
||||
_login_as(self.client, user.id)
|
||||
|
||||
def test_mis_materias_requires_login(self):
|
||||
"""Unauthenticated request to /mis-materias/ must redirect to login."""
|
||||
res = self.client.get('/mis-materias/')
|
||||
self.assertIn(res.status_code, [302, 401])
|
||||
|
||||
def test_mis_materias_admin_ok(self):
|
||||
"""Admin sees all commissions at /mis-materias/."""
|
||||
self._login(self.admin)
|
||||
res = self.client.get('/mis-materias/')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
html = res.get_data(as_text=True)
|
||||
self.assertIn('Todas las Comisiones', html)
|
||||
|
||||
def test_mis_materias_bedelia_ok(self):
|
||||
"""Bedelia sees all commissions at /mis-materias/."""
|
||||
if not self.bedelia:
|
||||
self.skipTest("No bedelia user")
|
||||
self._login(self.bedelia)
|
||||
res = self.client.get('/mis-materias/')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
def test_mis_materias_docente_ok(self):
|
||||
"""Docente gets 200 at /mis-materias/ (even with no assigned commissions)."""
|
||||
if not self.docente:
|
||||
self.skipTest("No docente user")
|
||||
self._login(self.docente)
|
||||
res = self.client.get('/mis-materias/')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
html = res.get_data(as_text=True)
|
||||
self.assertIn('Mis Materias', html)
|
||||
|
||||
def test_mis_materias_alumno_ok(self):
|
||||
"""Alumno gets 200 at /mis-materias/ (even with no enrollments)."""
|
||||
if not self.alumno_user:
|
||||
self.skipTest("No alumno user")
|
||||
self._login(self.alumno_user)
|
||||
res = self.client.get('/mis-materias/')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
html = res.get_data(as_text=True)
|
||||
self.assertIn('Mis Materias', html)
|
||||
|
||||
def test_mis_materias_detail_enrolled_alumno(self):
|
||||
"""Alumno enrolled in a commission can see its detail page."""
|
||||
if not self.alumno_user:
|
||||
self.skipTest("No alumno user")
|
||||
|
||||
comm = Commission.query.filter_by(active=True).first()
|
||||
if not comm:
|
||||
self.skipTest("No active commission in DB")
|
||||
|
||||
# Create temporary enrollment
|
||||
enr = StudentEnrollment.query.filter_by(
|
||||
student_id=self.alumno_user.id, commission_id=comm.id
|
||||
).first()
|
||||
cleanup_needed = False
|
||||
if not enr:
|
||||
enr = StudentEnrollment(
|
||||
student_id=self.alumno_user.id,
|
||||
commission_id=comm.id,
|
||||
status='activo'
|
||||
)
|
||||
db.session.add(enr)
|
||||
db.session.commit()
|
||||
cleanup_needed = True
|
||||
|
||||
self._login(self.alumno_user)
|
||||
res = self.client.get(f'/mis-materias/{comm.id}')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
html = res.get_data(as_text=True)
|
||||
self.assertIn('Tu inscripción', html)
|
||||
|
||||
if cleanup_needed:
|
||||
db.session.delete(enr)
|
||||
db.session.commit()
|
||||
|
||||
def test_mis_materias_detail_forbidden_for_unenrolled_alumno(self):
|
||||
"""Alumno NOT enrolled in a commission gets 403 on its detail page."""
|
||||
if not self.alumno_user:
|
||||
self.skipTest("No alumno user")
|
||||
|
||||
comm = Commission.query.filter_by(active=True).first()
|
||||
if not comm:
|
||||
self.skipTest("No active commission in DB")
|
||||
|
||||
# Ensure alumno is NOT enrolled
|
||||
StudentEnrollment.query.filter_by(
|
||||
student_id=self.alumno_user.id, commission_id=comm.id
|
||||
).delete()
|
||||
db.session.commit()
|
||||
|
||||
self._login(self.alumno_user)
|
||||
res = self.client.get(f'/mis-materias/{comm.id}')
|
||||
self.assertEqual(res.status_code, 403)
|
||||
|
||||
def test_mis_materias_detail_docente_own_commission(self):
|
||||
"""Docente can see detail of their own assigned commission."""
|
||||
if not self.docente:
|
||||
self.skipTest("No docente user")
|
||||
|
||||
comm = Commission.query.filter_by(active=True).first()
|
||||
if not comm:
|
||||
self.skipTest("No active commission in DB")
|
||||
|
||||
# Assign commission to docente
|
||||
prev_teacher = comm.teacher_id
|
||||
comm.teacher_id = self.docente.id
|
||||
db.session.commit()
|
||||
|
||||
self._login(self.docente)
|
||||
res = self.client.get(f'/mis-materias/{comm.id}')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
html = res.get_data(as_text=True)
|
||||
self.assertIn(comm.subject.name, html)
|
||||
|
||||
# Restore
|
||||
comm.teacher_id = prev_teacher
|
||||
db.session.commit()
|
||||
|
||||
def test_mis_materias_detail_docente_unauthorized_commission(self):
|
||||
"""Docente cannot see a commission they are not assigned to."""
|
||||
if not self.docente:
|
||||
self.skipTest("No docente user")
|
||||
|
||||
# Find a commission where docente is NOT the teacher
|
||||
comm = Commission.query.filter(
|
||||
Commission.active == True,
|
||||
Commission.teacher_id != self.docente.id
|
||||
).first()
|
||||
if not comm:
|
||||
self.skipTest("No commission without this docente in DB")
|
||||
|
||||
self._login(self.docente)
|
||||
res = self.client.get(f'/mis-materias/{comm.id}')
|
||||
self.assertEqual(res.status_code, 403)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
@@ -1,103 +0,0 @@
|
||||
import unittest
|
||||
from app import create_app, db
|
||||
from app.models.user import User
|
||||
|
||||
class TestRoleDashboardAndMenu(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.app = create_app()
|
||||
self.app.config['TESTING'] = True
|
||||
self.app.config['WTF_CSRF_ENABLED'] = False
|
||||
self.client = self.app.test_client()
|
||||
self.app_context = self.app.app_context()
|
||||
self.app_context.push()
|
||||
|
||||
# Usar usuario administrador para autenticación en sesión de pruebas
|
||||
self.user = User.query.filter_by(is_active=True).first()
|
||||
if self.user:
|
||||
with self.client.session_transaction() as sess:
|
||||
sess['_user_id'] = str(self.user.id)
|
||||
sess['_fresh'] = True
|
||||
|
||||
def tearDown(self):
|
||||
self.app_context.pop()
|
||||
|
||||
def test_user_role_methods(self):
|
||||
"""Verifica la normalización y detección de roles en el modelo User"""
|
||||
u = User(name="Test Teacher", email="prof@test.edu", role="Docente")
|
||||
self.assertEqual(u.get_institutional_role(), "docente")
|
||||
self.assertTrue(u.is_docente())
|
||||
self.assertFalse(u.is_admin())
|
||||
self.assertFalse(u.is_alumno())
|
||||
self.assertFalse(u.is_bedelia())
|
||||
|
||||
u_bedelia = User(name="Test Bedelia", email="bedelia@test.edu", role="Bedelia")
|
||||
self.assertEqual(u_bedelia.get_institutional_role(), "bedelia")
|
||||
self.assertTrue(u_bedelia.is_bedelia())
|
||||
|
||||
u_student = User(name="Test Student", email="student@test.edu", role="Alumno")
|
||||
self.assertEqual(u_student.get_institutional_role(), "alumno")
|
||||
self.assertTrue(u_student.is_alumno())
|
||||
|
||||
u_admin = User(name="Test Admin", email="admin@test.edu", role="ADMIN")
|
||||
self.assertEqual(u_admin.get_institutional_role(), "admin")
|
||||
self.assertTrue(u_admin.is_admin())
|
||||
|
||||
badge = u_admin.role_badge_display
|
||||
self.assertIn("badge-admin", badge["class"])
|
||||
self.assertIn("Administrador", badge["label"])
|
||||
|
||||
def test_admin_dashboard_rendered(self):
|
||||
"""Verifica que el dashboard administrativo renderiza KPIs y accesos de administración"""
|
||||
res = self.client.get('/?view_as=admin')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
html = res.get_data(as_text=True)
|
||||
self.assertIn('Aulas Totales', html)
|
||||
self.assertIn('Oferta de Materias', html)
|
||||
self.assertIn('Usuarios Activos', html)
|
||||
self.assertIn('Módulos de Gestión & Visualización de Menús', html)
|
||||
self.assertIn('Espacios & Sedes', html)
|
||||
self.assertIn('Auditoría & RBAC', html)
|
||||
|
||||
def test_bedelia_dashboard_rendered(self):
|
||||
"""Verifica que el dashboard de bedelía renderiza métricas operativas de campus"""
|
||||
res = self.client.get('/?view_as=bedelia')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
html = res.get_data(as_text=True)
|
||||
self.assertIn('Aulas Presenciales', html)
|
||||
self.assertIn('Salas Virtuales', html)
|
||||
self.assertIn('Acciones Rápidas & Menús Operativos de Bedelía', html)
|
||||
self.assertIn('Cartelera del Día', html)
|
||||
self.assertIn('Sincronizar Sheets', html)
|
||||
|
||||
def test_docente_dashboard_rendered(self):
|
||||
"""Verifica que el dashboard del docente renderiza sus clases y accesos directos"""
|
||||
res = self.client.get('/?view_as=docente')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
html = res.get_data(as_text=True)
|
||||
self.assertIn('Mis Clases Hoy', html)
|
||||
self.assertIn('Mis Reservas Activas', html)
|
||||
self.assertIn('Reservar Aula', html)
|
||||
self.assertIn('Accesos Directos de Docencia & Visualización de Menús', html)
|
||||
self.assertIn('Mis Clases Programadas para Hoy', html)
|
||||
|
||||
def test_alumno_dashboard_rendered(self):
|
||||
"""Verifica que el dashboard del alumno muestra la brújula diaria y herramientas de cursada"""
|
||||
res = self.client.get('/?view_as=alumno')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
html = res.get_data(as_text=True)
|
||||
self.assertIn('¿Dónde curso hoy? - Cartelera del Alumno', html)
|
||||
self.assertIn('Menú del Alumno & Herramientas de Cursada', html)
|
||||
self.assertIn('Cartelera de Hoy', html)
|
||||
self.assertIn('Cronograma Semanal', html)
|
||||
self.assertIn('Aulas Virtuales', html)
|
||||
|
||||
def test_audit_logs_view(self):
|
||||
"""Verifica que la vista de auditoría institucional responde correctamente"""
|
||||
res = self.client.get('/admin/audit-logs')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
html = res.get_data(as_text=True)
|
||||
self.assertIn('Registro de Auditoría Institucional', html)
|
||||
self.assertIn('Trazabilidad completa de operaciones', html)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -1,42 +0,0 @@
|
||||
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()
|
||||
@@ -1,60 +0,0 @@
|
||||
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()
|
||||
@@ -13,18 +13,23 @@ from app.models.genetic_algorithm import (
|
||||
Gene
|
||||
)
|
||||
from app.services.optimizer_service import OptimizerService, OptimizerJobStatus
|
||||
from app.services.jwt_service import JWTService
|
||||
from config.config import Config
|
||||
|
||||
|
||||
class TestConfig(Config):
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
|
||||
TESTING = True
|
||||
WTF_CSRF_ENABLED = False
|
||||
|
||||
|
||||
class Sprint4OptimizerAndConcurrencyTestCase(unittest.TestCase):
|
||||
"""Pruebas exhaustivas para el Sprint 4: Optimizador Heurístico, Concurrencia y Spring Boot."""
|
||||
|
||||
def setUp(self):
|
||||
self.app = create_app()
|
||||
self.app.config['TESTING'] = True
|
||||
self.app.config['WTF_CSRF_ENABLED'] = False
|
||||
self.app = create_app(TestConfig)
|
||||
self.app_context = self.app.app_context()
|
||||
self.app_context.push()
|
||||
db.create_all()
|
||||
self.client = self.app.test_client()
|
||||
|
||||
# Obtener usuario existente o crear
|
||||
@@ -38,6 +43,7 @@ class Sprint4OptimizerAndConcurrencyTestCase(unittest.TestCase):
|
||||
self.user = User(
|
||||
name='Sprint 4 Admin',
|
||||
email='admin_sprint4@edu-space.com',
|
||||
role='ADMIN',
|
||||
role_id=admin_role.id,
|
||||
is_active=True
|
||||
)
|
||||
@@ -50,9 +56,9 @@ class Sprint4OptimizerAndConcurrencyTestCase(unittest.TestCase):
|
||||
if not self.classrooms:
|
||||
for i in range(1, 11):
|
||||
room = Classroom(
|
||||
room_number=f"AULA-{100 + i}",
|
||||
code=f"AULA-{100 + i}",
|
||||
building="Edificio Central",
|
||||
capacity=30 + (i * 5),
|
||||
is_virtual=(i == 10),
|
||||
is_active=True
|
||||
)
|
||||
db.session.add(room)
|
||||
@@ -68,7 +74,8 @@ class Sprint4OptimizerAndConcurrencyTestCase(unittest.TestCase):
|
||||
}
|
||||
|
||||
def tearDown(self):
|
||||
db.session.rollback()
|
||||
db.session.remove()
|
||||
db.drop_all()
|
||||
self.app_context.pop()
|
||||
|
||||
def test_01_fast_fitness_and_conflict_detection(self):
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import unittest
|
||||
from app import create_app, db
|
||||
from app.models.user import User
|
||||
|
||||
class TestViewSwitcher(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.app = create_app()
|
||||
self.app.config['TESTING'] = True
|
||||
self.app.config['WTF_CSRF_ENABLED'] = False
|
||||
self.client = self.app.test_client()
|
||||
self.app_context = self.app.app_context()
|
||||
self.app_context.push()
|
||||
|
||||
# Login con usuario admin para acceder a las vistas protegidas
|
||||
self.user = User.query.filter_by(is_active=True).first()
|
||||
if self.user:
|
||||
with self.client.session_transaction() as sess:
|
||||
sess['_user_id'] = str(self.user.id)
|
||||
sess['_fresh'] = True
|
||||
|
||||
def tearDown(self):
|
||||
self.app_context.pop()
|
||||
|
||||
def test_classrooms_view_switcher_rendered(self):
|
||||
res = self.client.get('/classrooms/?view_mode=physical')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
html = res.get_data(as_text=True)
|
||||
# Verificar que el selector de vista está presente
|
||||
self.assertIn('view-switcher-group', html)
|
||||
self.assertIn('data-context="classrooms"', html)
|
||||
self.assertIn('data-view-container="cards"', html)
|
||||
self.assertIn('data-view-container="table"', html)
|
||||
|
||||
def test_reservations_view_switcher_rendered(self):
|
||||
res = self.client.get('/schedule/list')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
html = res.get_data(as_text=True)
|
||||
# Verificar que el selector de vista está presente
|
||||
self.assertIn('view-switcher-group', html)
|
||||
self.assertIn('data-context="reservations"', html)
|
||||
self.assertIn('data-view-container="table"', html)
|
||||
self.assertIn('data-view-container="cards"', html)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user