diff --git a/tests/integration/test_phase1_documents_planning.py b/tests/integration/test_phase1_documents_planning.py index 15a0d9d..2345e7a 100644 --- a/tests/integration/test_phase1_documents_planning.py +++ b/tests/integration/test_phase1_documents_planning.py @@ -1,4 +1,5 @@ from datetime import date, datetime, time +import os from app_new.extensions import db from app_new.core.models.college import Building, Room, RoomSchedule, Zone @@ -6,7 +7,10 @@ from app_new.core.models.cleaning import CommercialProduct, ProductGeneric, Prod from app_new.core.models.equipment import Equipment, EquipmentCategory, EquipmentDocument from app_new.core.models.maintenance import Intervention, InterventionDocument from app_new.core.models.rbac import Permission, RolePermission -from app_new.core.services.room_planning import active_room_schedules, room_is_available, sync_pronote_schedules +from app_new.core.services.room_planning import ( + active_room_schedules, normalize_pronote_lessons, room_is_available, + sync_pronote_schedules, +) def test_document_center_aggregates_sources_and_download_handles_missing_file(authenticated_client, app): @@ -52,6 +56,21 @@ def test_document_center_aggregates_sources_and_download_handles_missing_file(au assert authenticated_client.get(f"/documents/download/intervention/{intervention_id}").status_code == 302 assert authenticated_client.get(f"/documents/download/equipment/{equipment_id}").status_code == 302 assert authenticated_client.get(f"/documents/download/product/{product_id}").status_code == 302 + from flask import current_app + intervention_doc.filepath = os.path.join(current_app.config["UPLOAD_FOLDER"], "..", "..", "etc", "passwd") + db.session.commit() + assert authenticated_client.get(f"/documents/download/intervention/{intervention_id}").status_code == 302 + assert authenticated_client.get("/documents/?source=intervention&document_type=rapport").status_code == 200 + assert "intervention.pdf" in authenticated_client.get("/documents/?q=Phase1%20intervention").get_data(as_text=True) + assert "notice.pdf" not in authenticated_client.get("/documents/?source=intervention").get_data(as_text=True) + assert authenticated_client.get("/documents/download/unknown/1").status_code == 403 + + +def test_document_center_requires_source_permission(app, monkeypatch): + from app_new.documents import service + monkeypatch.setattr(service, "has_permission", lambda permission, user=None: permission == "documents.view") + with app.app_context(): + assert service.list_documents(user=object()) == [] def test_room_schedule_replaces_only_unprotected_manual_slot(app): @@ -117,3 +136,50 @@ def test_pronote_sync_supersedes_initial_manual_and_preserves_protected(app): assert initial.resolution_status == "superseded" assert protected.protected_from_sync is True assert any(row.external_id == "sync-conflict" and row.conflict_note for row in rows) + + +def test_pronote_sync_change_removal_ambiguity_and_freshness(app): + with app.app_context(): + building = Building(name="Phase15 edge building") + db.session.add(building) + db.session.flush() + room = Room(name="Phase15 edge room", building_id=building.id) + db.session.add(room) + db.session.flush() + week = date(2026, 9, 7) + # Deux manuels compatibles rendent le rapprochement ambigu. + manuals = [RoomSchedule( + room_id=room.id, week_start=week, day_of_week=0, + start_time=time(8), end_time=time(9), subject="Maths", + source="manual", protected_from_sync=False, + ) for _ in range(2)] + db.session.add_all(manuals) + db.session.commit() + sync_at = datetime(2026, 9, 1, 8, 15) + sync_pronote_schedules(room.id, [{ + "external_id": "edge-course", "week_start": week, "day_of_week": 0, + "start_time": time(8, 30), "end_time": time(9, 30), "subject": "Maths", + }], synced_at=sync_at) + row = RoomSchedule.query.filter_by(room_id=room.id, external_id="edge-course").one() + assert row.last_synced_at == sync_at + assert row.conflict_note and "ambiguë" in row.conflict_note + assert all(item.resolution_status == "active" for item in manuals) + + # Une synchronisation réussie sans ce créneau le désactive sans DELETE. + sync_pronote_schedules(room.id, [], synced_at=datetime(2026, 9, 2, 8, 15)) + db.session.refresh(row) + assert row.resolution_status == "disabled" + assert db.session.get(RoomSchedule, row.id) is not None + + +def test_normalize_pronote_lessons_is_stable_and_skips_invalid_entries(): + week = date(2026, 9, 7) + lessons = [{ + "day_of_week": 0, "start_time": "08:00", "end_time": "09:00", + "subject": "Maths", "teacher": "Mme Test", "class_name": "6e A", + }, {"day_of_week": 0, "start_time": "invalide", "end_time": "09:00"}] + first = normalize_pronote_lessons(lessons, week) + second = normalize_pronote_lessons(lessons, week) + assert len(first) == 1 + assert first == second + assert len(first[0]["external_id"]) == 64