test(api): couvrir l'édition Web d'une journée créée par l'API (étape 5)
Co-authored-by: OpenAI/GPT-5.6-Luna-Pro <vibecoder@antoineve.me>
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from app import db
|
||||
from app.api import reset_rate_limiter
|
||||
from app.models import TimeSlot, WorkplacePresenceEvent
|
||||
from app.models import TimeSlot, WorkEntry, WorkplacePresenceEvent
|
||||
|
||||
TOKEN = "test-api-token"
|
||||
URL = "/api/v1/workplace-presence"
|
||||
@@ -39,6 +41,156 @@ def test_arrival_and_departure_create_expected_records(client, app):
|
||||
assert db.session.query(TimeSlot).count() == 1
|
||||
|
||||
|
||||
def test_arrival_entry_can_be_fully_edited_from_web_form(client, app):
|
||||
arrival = post(
|
||||
client,
|
||||
{
|
||||
"event": "arrival",
|
||||
"occurred_at": "2026-08-13T08:00:00+02:00",
|
||||
"idempotency_key": "web-edit-arrival",
|
||||
},
|
||||
)
|
||||
assert arrival.status_code == 201
|
||||
entry_id = arrival.json["entry_id"]
|
||||
|
||||
form = client.get(f"/entries/{entry_id}/edit")
|
||||
assert form.status_code == 200
|
||||
assert "Véhicule à moteur seul" in form.text
|
||||
assert re.search(
|
||||
r'<option value="moteur_seul".*?data-has-motor="true".*?selected',
|
||||
form.text,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert "Citadine électrique" in form.text
|
||||
assert re.search(r'name="motor_vehicle_id" value="citadine".*?checked', form.text, re.DOTALL)
|
||||
assert 'name="start_time"' in form.text
|
||||
assert 'name="end_time"' in form.text
|
||||
|
||||
formation = client.post(
|
||||
f"/entries/{entry_id}/edit",
|
||||
data={
|
||||
"date": "2026-08-13",
|
||||
"day_type": "FORMATION",
|
||||
"journey_profile_id": "moteur_velo",
|
||||
"motor_vehicle_id": "familiale",
|
||||
"start_time": ["08:00"],
|
||||
"end_time": ["12:00"],
|
||||
"comment": "Formation",
|
||||
},
|
||||
)
|
||||
assert formation.status_code == 302
|
||||
|
||||
with app.app_context():
|
||||
entry = db.session.get(WorkEntry, entry_id)
|
||||
assert entry.day_type == "FORMATION"
|
||||
assert entry.journey_profile_id == "moteur_velo"
|
||||
assert entry.motor_vehicle_id == "familiale"
|
||||
assert [(slot.start_time.hour, slot.end_time.hour) for slot in entry.time_slots] == [
|
||||
(8, 12)
|
||||
]
|
||||
|
||||
garde = client.post(
|
||||
f"/entries/{entry_id}/edit",
|
||||
data={
|
||||
"date": "2026-08-13",
|
||||
"day_type": "GARDE",
|
||||
"journey_profile_id": "moteur_seul",
|
||||
"motor_vehicle_id": "moto",
|
||||
"start_time": ["08:00", "13:00", ""],
|
||||
"end_time": ["12:00", "17:00", ""],
|
||||
"comment": "Garde",
|
||||
},
|
||||
)
|
||||
assert garde.status_code == 302
|
||||
|
||||
with app.app_context():
|
||||
entry = db.session.get(WorkEntry, entry_id)
|
||||
assert entry.day_type == "GARDE"
|
||||
assert entry.journey_profile_id == "moteur_seul"
|
||||
assert entry.motor_vehicle_id == "moto"
|
||||
assert [(slot.start_time.hour, slot.end_time.hour) for slot in entry.time_slots] == [
|
||||
(8, 12),
|
||||
(13, 17),
|
||||
]
|
||||
event = db.session.scalar(
|
||||
db.select(WorkplacePresenceEvent).where(
|
||||
WorkplacePresenceEvent.idempotency_key == "web-edit-arrival"
|
||||
)
|
||||
)
|
||||
assert event.entry_id == entry_id
|
||||
assert event.time_slot_id is None
|
||||
|
||||
empty_slots = client.post(
|
||||
f"/entries/{entry_id}/edit",
|
||||
data={
|
||||
"date": "2026-08-13",
|
||||
"day_type": "GARDE",
|
||||
"journey_profile_id": "moteur_seul",
|
||||
"motor_vehicle_id": "moto",
|
||||
"start_time": [""],
|
||||
"end_time": [""],
|
||||
"comment": "Garde sans plage",
|
||||
},
|
||||
)
|
||||
assert empty_slots.status_code == 302
|
||||
|
||||
with app.app_context():
|
||||
entry = db.session.get(WorkEntry, entry_id)
|
||||
assert entry.time_slots == []
|
||||
assert entry.day_type == "GARDE"
|
||||
assert db.session.query(WorkplacePresenceEvent).count() == 1
|
||||
|
||||
|
||||
def test_web_edit_recreates_slots_without_losing_arrival_departure_events(client, app):
|
||||
arrival = post(
|
||||
client,
|
||||
{
|
||||
"event": "arrival",
|
||||
"occurred_at": "2026-08-14T08:00:00+02:00",
|
||||
"idempotency_key": "recreate-arrival",
|
||||
},
|
||||
)
|
||||
departure = post(
|
||||
client,
|
||||
{
|
||||
"event": "departure",
|
||||
"occurred_at": "2026-08-14T17:00:00+02:00",
|
||||
"idempotency_key": "recreate-departure",
|
||||
},
|
||||
)
|
||||
assert arrival.status_code == 201
|
||||
assert departure.status_code == 200
|
||||
entry_id = arrival.json["entry_id"]
|
||||
|
||||
response = client.post(
|
||||
f"/entries/{entry_id}/edit",
|
||||
data={
|
||||
"date": "2026-08-14",
|
||||
"day_type": "FORMATION",
|
||||
"journey_profile_id": "moteur_velo",
|
||||
"motor_vehicle_id": "familiale",
|
||||
"start_time": ["09:00", "13:00", ""],
|
||||
"end_time": ["12:00", "17:00", ""],
|
||||
"comment": "Plages recréées",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 302
|
||||
|
||||
with app.app_context():
|
||||
entry = db.session.get(WorkEntry, entry_id)
|
||||
assert len(entry.time_slots) == 2
|
||||
events = db.session.scalars(
|
||||
db.select(WorkplacePresenceEvent)
|
||||
.where(WorkplacePresenceEvent.entry_id == entry_id)
|
||||
.order_by(WorkplacePresenceEvent.event_type)
|
||||
).all()
|
||||
assert {event.idempotency_key for event in events} == {
|
||||
"recreate-arrival",
|
||||
"recreate-departure",
|
||||
}
|
||||
assert all(event.time_slot_id is None for event in events)
|
||||
|
||||
|
||||
def test_replay_does_not_duplicate(client, app):
|
||||
payload = {
|
||||
"event": "arrival",
|
||||
|
||||
Reference in New Issue
Block a user