"""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