ajout d'un toggle pour permission d'upload des documents dans admin pannel
CI - Security, Lint & Tests / validate (push) Failing after 1m3s

ajout de compteur d'heure disponible
ajout du calendrier de dispo dans les profils publics
This commit is contained in:
cedrick2711
2026-08-26 22:33:24 -04:00
parent 820cccb25e
commit bfb25f4d16
16 changed files with 1966 additions and 74 deletions
+135
View File
@@ -0,0 +1,135 @@
"""Admin document-upload toggle — enable/disable gate and enforcement
on contract upload routes.
ADMIN-006 style. The document_uploads_enabled setting gates every
contract upload route for non-admins. Admins always bypass the lock.
"""
import pytest
from app.extensions import db
from app.models import AppSettings, Contract, User
NON_ADMIN_ROLES = ['manager', 'coach']
def _redirected(response):
return response.status_code in (301, 302)
class TestDocumentUploadToggle:
"""Toggle logic — on/off round-trip and audit log."""
def test_toggle_from_enabled_to_disabled(self, client, as_role):
as_role('admin')
# Default is enabled
client.post('/admin/toggle-document-uploads', data={}, follow_redirects=False)
with client.application.app_context():
assert not AppSettings.get_bool('document_uploads_enabled', default=True)
def test_toggle_from_disabled_to_enabled(self, client, as_role):
as_role('admin')
client.post('/admin/toggle-document-uploads', data={}, follow_redirects=False)
client.post('/admin/toggle-document-uploads', data={}, follow_redirects=False)
with client.application.app_context():
assert AppSettings.get_bool('document_uploads_enabled', default=True)
def test_toggle_creates_audit_log(self, client, as_role):
from app.models import AuditLog
as_role('admin')
client.post('/admin/toggle-document-uploads', data={}, follow_redirects=False)
with client.application.app_context():
entry = AuditLog.query.filter_by(action='document_uploads_toggled').first()
assert entry is not None
def test_default_is_enabled(self, app):
with app.app_context():
assert AppSettings.get_bool('document_uploads_enabled', default=True)
class TestDocumentUploadGateEnforcement:
"""Non-admin roles are blocked when the toggle is off."""
def _contract_for(self, app, player_id):
"""Create a minimal contract for enforcement tests."""
from datetime import datetime
with app.app_context():
admin = User.query.filter_by(role='admin').first()
contract = Contract(
player_id=player_id,
uploaded_by_id=admin.id,
original_filename='test.pdf',
stored_filename='test.pdf',
file_path='contrats signes/test.pdf',
status='pending_player_signing',
uploaded_at=datetime.utcnow(),
)
db.session.add(contract)
db.session.commit()
return contract.id
@pytest.mark.parametrize('role', NON_ADMIN_ROLES)
def test_non_admin_cannot_upload_contract_when_disabled(
self, app, client, as_role, make_user, role
):
as_role('admin')
client.post('/admin/toggle-document-uploads', data={}) # disable
as_role(role)
response = client.post(
'/users/contracts/upload',
data={'player_id': '', 'contract_file': ''},
follow_redirects=True,
)
html = response.data.decode()
# Default locale is French — the flash message says "désactivés"
assert 'désactivés' in html or 'disabled' in html.lower()
@pytest.mark.parametrize('role', NON_ADMIN_ROLES)
def test_admin_can_always_upload_contract(self, client, as_role, role):
as_role('admin')
# Pre-create a player for the contract
as_role('player')
client.post('/admin/toggle-document-uploads', data={}) # disable
# Admin should still be able to reach the upload page
response = client.get('/users/contracts/upload', follow_redirects=False)
assert _redirected(response) or response.status_code == 200
def test_player_cannot_upload_signed_when_disabled(self, app, client, as_role, make_user):
as_role('admin')
client.post('/admin/toggle-document-uploads', data={}) # disable while logged in as admin
player_id = make_user('player')
contract_id = self._contract_for(app, player_id)
# Log in as the player
with app.app_context():
username = db.session.get(User, player_id).username
client.post('/auth/login', data={'username': username, 'password': 'Password123'})
response = client.post(
f'/users/contracts/{contract_id}/upload_signed',
data={'signed_file': ''},
follow_redirects=True,
)
html = response.data.decode()
assert 'désactivés' in html or 'disabled' in html.lower()
def test_admin_can_always_upload(self, app, client, as_role):
as_role('admin')
client.post('/admin/toggle-document-uploads', data={}) # disable
# Admin GET the upload page — should be allowed (not redirected away)
response = client.get('/users/contracts/upload', follow_redirects=False)
# The admin bypasses the lock so they see the upload form (200)
# or get redirected (if no players exist, etc). Either way is fine
# as long as it's not the flash about uploads being disabled.
body = response.data.decode() if response.status_code == 200 else ''
# The toggle flash ("désactivés") could still be present, but the
# upload-disabled message ("désactivés") must not appear in the body.
# We check for the French lock message specifically.
assert 'actuellement désactivés' not in body
+1
View File
@@ -12,6 +12,7 @@ ADMIN_ROUTES = [
'/admin',
'/admin/backup/create',
'/admin/toggle-tryouts',
'/admin/toggle-document-uploads',
'/admin/season/start',
'/admin/season/end',
'/admin/teams/wipe',
+116
View File
@@ -0,0 +1,116 @@
"""Tests for the hours column in the user list and the read-only
availability grid on public profiles."""
from datetime import time
from app.extensions import db
from app.models import (
CoachAvailability,
PlayerDisponibility,
)
class TestHoursColumn:
"""The manage-users table shows available hours for players/coaches."""
def test_admin_sees_hours_header(self, app, client, as_role):
as_role('admin')
body = client.get('/users').get_data(as_text=True)
# Default locale is French, so _("Hours") renders as "Heures"
assert 'Heures' in body
def test_player_without_slots_shows_0(self, app, client, as_role, make_user):
as_role('admin')
make_user('player') # create a player but stay logged in as admin
body = client.get('/users').get_data(as_text=True)
assert '0.0' in body
def test_player_with_slots_shows_hours(self, app, client, as_role, make_user):
as_role('admin')
player_id = make_user('player')
with app.app_context():
db.session.add(
PlayerDisponibility(
player_id=player_id,
day_of_week=0,
start_time=time(17, 0),
end_time=time(19, 0),
)
)
db.session.add(
PlayerDisponibility(
player_id=player_id,
day_of_week=1,
start_time=time(18, 0),
end_time=time(20, 0),
)
)
db.session.commit()
body = client.get('/users').get_data(as_text=True)
# 2h + 2h = 4.0
assert '4.0' in body
def test_coach_without_slots_shows_0(self, app, client, as_role, make_user):
as_role('admin')
make_user('coach')
body = client.get('/users').get_data(as_text=True)
assert '0.0' in body
def test_admin_roles_show_dash(self, app, client, as_role, make_user):
as_role('admin')
make_user('manager')
make_user('scout')
body = client.get('/users').get_data(as_text=True)
# managers and scouts should show mdash
assert '\u2014' in body
class TestAvailabilityOnPublicProfile:
"""view_user renders a weekly availability grid for players/coaches."""
def test_player_without_availability_shows_no_grid(self, app, client, as_role):
as_role('admin')
player_id = as_role('player')
body = client.get(f'/users/{player_id}/view').get_data(as_text=True)
# Default locale is French
assert 'Disponibilit\u00e9s' not in body
def test_player_with_slots_shows_grid(self, app, client, as_role, make_user):
as_role('admin')
player_id = make_user('player')
with app.app_context():
db.session.add(
PlayerDisponibility(
player_id=player_id,
day_of_week=2,
start_time=time(18, 0),
end_time=time(18, 30),
)
)
db.session.commit()
body = client.get(f'/users/{player_id}/view').get_data(as_text=True)
assert 'Disponibilit\u00e9s' in body
assert '18:00' in body
def test_coach_with_slots_shows_grid(self, app, client, as_role, make_user):
as_role('admin')
coach_id = make_user('coach')
with app.app_context():
db.session.add(
CoachAvailability(
coach_id=coach_id,
day_of_week=3,
start_time=time(10, 0),
end_time=time(10, 30),
)
)
db.session.commit()
body = client.get(f'/users/{coach_id}/view').get_data(as_text=True)
assert 'Disponibilit\u00e9s' in body
assert '10:00' in body
def test_admin_profile_shows_no_grid(self, app, client, as_role):
admin_id = as_role('admin')
body = client.get(f'/users/{admin_id}/view').get_data(as_text=True)
assert 'Disponibilit\u00e9s' not in body