gmao/tests/integration/test_room_profiles_checkpoint_b.py

190 lines
10 KiB
Python
Raw Normal View History

"""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
2026-08-23 20:10:40 +02:00
from app_new.core.models.planning import WorkSchedule
from time import perf_counter
2026-08-23 20:10:40 +02:00
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
2026-08-23 20:10:40 +02:00
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
2026-08-23 20:34:23 +02:00
def test_room_duplication_http_creates_new_identity(authenticated_client, app):
building_response = authenticated_client.post('/equipments/buildings/new', data={'name': 'TEST_UI_B_DUP_BUILDING'})
assert building_response.status_code == 303
with app.app_context():
db.session.remove(); building_id = Building.query.filter_by(name='TEST_UI_B_DUP_BUILDING').one().id
room_response = authenticated_client.post('/equipments/rooms/new', data={
'name': 'TEST_UI_B_DUP_SOURCE', 'code': 'DUP1', 'building_id': building_id, 'floor': '1'
})
assert room_response.status_code == 302
with app.app_context():
db.session.remove(); source_id = Room.query.filter_by(code='DUP1').one().id
duplicate_response = authenticated_client.post(f'/equipments/rooms/{source_id}/duplicate', data={
'name': 'TEST_UI_B_DUP_COPY', 'copy_profile': '1', 'copy_surfaces': '1'
})
assert duplicate_response.status_code == 302
with app.app_context():
db.session.remove()
assert Room.query.filter_by(name='TEST_UI_B_DUP_COPY').count() == 1