169 lines
9.2 KiB
Python
169 lines
9.2 KiB
Python
"""Couverture métier du checkpoint B : profils sans création implicite."""
|
||
from app_new import db
|
||
from app_new.core.models.college import Building, Zone, Room, RoomProfile, RoomProfileItem
|
||
from app_new.core.models.equipment import Equipment
|
||
from app_new.core.services.equipment_creation import create_equipment
|
||
from app_new.core.models.equipment import EquipmentCategory
|
||
from app_new.core.models.planning import WorkSchedule
|
||
from time import perf_counter
|
||
import re
|
||
|
||
|
||
def test_room_profile_is_editable_and_application_uses_shared_service(authenticated_client, app):
|
||
with app.app_context():
|
||
building = Building(name="TEST_UI_020_B_BUILDING")
|
||
db.session.add(building)
|
||
db.session.flush()
|
||
zone = Zone(name="TEST_UI_020_B_ZONE", building_id=building.id)
|
||
category = EquipmentCategory(name="TEST_UI_020_B_LUMINAIRE")
|
||
db.session.add_all([zone, category])
|
||
db.session.flush()
|
||
room = Room(name="TEST_UI_020_B101", code="TEST_UI_020_B101", building_id=building.id, zone_id=zone.id)
|
||
db.session.add(room)
|
||
db.session.flush()
|
||
profile = RoomProfile(name="TEST_UI_020_B_CLASSE", description="Profil de validation")
|
||
db.session.add(profile)
|
||
db.session.flush()
|
||
item = RoomProfileItem(profile_id=profile.id, label="Luminaire", category_id=category.id, quantity=10, is_group=True)
|
||
db.session.add(item)
|
||
db.session.commit()
|
||
profile_id, room_id, item_id = profile.id, room.id, item.id
|
||
assert RoomProfile.query.get(profile_id) is not None
|
||
|
||
assert Equipment.query.filter_by(room_id=room_id).count() == 0
|
||
with app.app_context():
|
||
item = RoomProfileItem.query.get(item_id)
|
||
created = create_equipment(name=item.label, category_id=item.category_id,
|
||
room_id=room_id, quantity=4, is_group=item.is_group,
|
||
create_schedule=False)
|
||
db.session.commit()
|
||
assert created.id is not None
|
||
assert Equipment.query.filter_by(room_id=room_id).count() == 1
|
||
assert Equipment.query.filter_by(room_id=room_id).one().quantity == 4
|
||
|
||
|
||
def test_bulk_room_can_reference_profile_without_creating_equipment(authenticated_client, app):
|
||
with app.app_context():
|
||
building = Building(name="TEST_UI_020_B_BULK_BUILDING")
|
||
zone = Zone(name="TEST_UI_020_B_BULK_ZONE", building=building)
|
||
profile = RoomProfile(name="TEST_UI_020_B_BULK_PROFILE")
|
||
db.session.add_all([building, zone, profile])
|
||
db.session.commit()
|
||
building_id, zone_id, profile_id = building.id, zone.id, profile.id
|
||
with app.app_context():
|
||
rooms = [Room(name=name, code=name, building_id=building_id, zone_id=zone_id,
|
||
room_profile_id=profile_id, floor=1)
|
||
for name in ("TEST_UI_020_B101", "TEST_UI_020_B102")]
|
||
db.session.add_all(rooms)
|
||
db.session.commit()
|
||
saved = Room.query.filter(Room.building_id == building_id, Room.room_profile_id == profile_id).all()
|
||
assert len(saved) == 2
|
||
assert Equipment.query.filter(Equipment.room_id.in_([room.id for room in saved])).count() == 0
|
||
|
||
|
||
def test_bulk_200_is_single_batch_and_does_not_duplicate(app):
|
||
with app.app_context():
|
||
building = Building(name="TEST_UI_020_BENCH_BUILDING")
|
||
db.session.add(building); db.session.flush()
|
||
names = [f"B{i:03d}" for i in range(200)]
|
||
started = perf_counter()
|
||
db.session.add_all([Room(name=name, code=name, building_id=building.id, floor=i % 4) for i, name in enumerate(names)])
|
||
db.session.commit()
|
||
elapsed = perf_counter() - started
|
||
assert Room.query.filter(Room.building_id == building.id).count() == 200
|
||
# The isolated fixture is torn down at session end; this assertion is
|
||
# deliberately kept as a measurable smoke benchmark.
|
||
assert elapsed < 10
|
||
|
||
|
||
def test_profile_preview_route_after_ui_creation(authenticated_client, app):
|
||
created = authenticated_client.post('/room-profiles/new', data={'name': 'TEST_UI_B_HTTP_PROFILE_ROUTE'})
|
||
assert created.status_code == 302
|
||
profile_id = int(re.search(r'/room-profiles/(\d+)/edit', created.headers['Location']).group(1))
|
||
item_response = authenticated_client.post(f'/room-profiles/{profile_id}/items', data={
|
||
'label': 'TEST_UI_B_HTTP_LUMINAIRE', 'quantity': '2', 'is_group': '1', 'enabled_by_default': '1'
|
||
})
|
||
assert item_response.status_code == 302
|
||
building_response = authenticated_client.post('/equipments/buildings/new', data={'name': 'TEST_UI_B_HTTP_ROUTE_BUILDING'})
|
||
assert building_response.status_code == 303
|
||
with app.app_context():
|
||
db.session.remove(); building_id = Building.query.filter_by(name='TEST_UI_B_HTTP_ROUTE_BUILDING').one().id
|
||
zone_response = authenticated_client.post(f'/equipments/buildings/{building_id}/zones', data={'name': 'TEST_UI_B_HTTP_ROUTE_ZONE'})
|
||
assert zone_response.status_code == 303
|
||
with app.app_context():
|
||
db.session.remove(); zone_id = Zone.query.filter_by(name='TEST_UI_B_HTTP_ROUTE_ZONE').one().id
|
||
room_response = authenticated_client.post('/equipments/rooms/new', data={
|
||
'name': 'TEST_UI_B_HTTP_ROUTE_ROOM', 'code': 'UIB1', 'building_id': building_id,
|
||
'zone_id': zone_id, 'room_profile_id': profile_id, 'floor': '1'
|
||
})
|
||
assert room_response.status_code == 302
|
||
with app.app_context():
|
||
db.session.remove()
|
||
room = Room.query.filter_by(code='UIB1').first()
|
||
assert room is not None
|
||
room_id = room.id
|
||
db.session.remove()
|
||
preview = authenticated_client.get(f'/room-profiles/{profile_id}/preview?room_id={room_id}')
|
||
assert preview.status_code == 200
|
||
assert b'TEST_UI_B_HTTP_LUMINAIRE' in preview.data
|
||
with app.app_context():
|
||
db.session.remove()
|
||
item_id = RoomProfileItem.query.filter_by(profile_id=profile_id).one().id
|
||
applied = authenticated_client.post(f'/room-profiles/{profile_id}/preview', data={
|
||
'room_ids': str(room_id), 'item_ids': str(item_id), f'quantity_{item_id}': '2', 'validate': '1'
|
||
})
|
||
assert applied.status_code == 302
|
||
with app.app_context():
|
||
db.session.remove()
|
||
equipment = Equipment.query.filter_by(room_id=room_id, room_profile_item_id=item_id).one()
|
||
assert equipment.quantity == 2
|
||
# Une seconde application est idempotente.
|
||
authenticated_client.post(f'/room-profiles/{profile_id}/preview', data={
|
||
'room_ids': str(room_id), 'item_ids': str(item_id), f'quantity_{item_id}': '2', 'validate': '1'
|
||
})
|
||
with app.app_context():
|
||
db.session.remove()
|
||
assert Equipment.query.filter_by(room_id=room_id, room_profile_item_id=item_id).count() == 1
|
||
# Modifier le profil ne modifie pas silencieusement l’équipement déjà créé.
|
||
changed = authenticated_client.post(f'/room-profiles/{profile_id}/items/{item_id}/edit', data={
|
||
'label': 'TEST_UI_B_HTTP_LUMINAIRE', 'quantity': '5', 'is_group': '1', 'enabled_by_default': '1'
|
||
})
|
||
assert changed.status_code == 302
|
||
with app.app_context():
|
||
db.session.remove()
|
||
assert Equipment.query.filter_by(room_id=room_id, room_profile_item_id=item_id).one().quantity == 2
|
||
|
||
|
||
def test_work_schedule_ui_isolates_two_technicians(authenticated_client, app):
|
||
def create_user(username, full_name):
|
||
response = authenticated_client.post('/auth/users/new', data={
|
||
'username': username, 'email': f'{username}@example.com', 'full_name': full_name,
|
||
'password': 'Test-password-strong-123!', 'role': 'demandeur', 'role_ids': 'demandeur', 'is_active': 'y',
|
||
})
|
||
assert response.status_code == 302
|
||
with app.app_context():
|
||
db.session.remove(); return User.query.filter_by(username=username).one().id
|
||
from app_new.core.models.user import User
|
||
tech_a = create_user('TEST_UI_B_TECH_A', 'TEST_UI_B_TECH_A')
|
||
tech_b = create_user('TEST_UI_B_TECH_B', 'TEST_UI_B_TECH_B')
|
||
data_a = {'user_id': tech_a, 'academic_year': '2026'}
|
||
data_b = {'user_id': tech_b, 'academic_year': '2026'}
|
||
for data, start, end, lunch_start, lunch_end in ((data_a, '08:00', '17:00', '12:00', '13:30'), (data_b, '07:00', '15:30', '11:30', '12:30')):
|
||
data.update({'start_0': start, 'end_0': end, 'lunch_start_0': lunch_start, 'lunch_end_0': lunch_end})
|
||
response = authenticated_client.post('/planning/work-schedules', data=data)
|
||
assert response.status_code == 200
|
||
with app.app_context():
|
||
db.session.remove()
|
||
a = WorkSchedule.query.filter_by(user_id=tech_a, day_of_week=0, academic_year=2026, is_active=True).one()
|
||
b = WorkSchedule.query.filter_by(user_id=tech_b, day_of_week=0, academic_year=2026, is_active=True).one()
|
||
assert (a.start_time.hour, a.end_time.hour, a.lunch_start.hour, a.lunch_end.hour) == (8, 17, 12, 13)
|
||
assert (b.start_time.hour, b.end_time.hour, b.lunch_start.hour, b.lunch_end.hour) == (7, 15, 11, 12)
|
||
|
||
|
||
def test_configuration_center_renders_optional_states(authenticated_client):
|
||
response = authenticated_client.get('/configuration/')
|
||
assert response.status_code == 200
|
||
assert b'ESSENTIEL' in response.data
|
||
assert b'COMPTEURS' in response.data
|
||
assert b'CARTOGRAPHIE TECHNIQUE' in response.data
|
||
assert b'Facultatif' in response.data
|