From 53980938a16afc7275949824d3ba2c1d47c7bed6 Mon Sep 17 00:00:00 2001 From: Ronan Lenouvel Date: Mon, 12 Oct 2026 12:13:41 +0200 Subject: [PATCH 1/6] =?UTF-8?q?=E2=9C=A8=20feat(BookingPrefill):=20ne=20ga?= =?UTF-8?q?rder=20du=20lien=20que=20le=20cr=C3=A9neau=20fiable=20(jour=20d?= =?UTF-8?q?e=20la=20fen=C3=AAtre,=20quarts=20d'heure,=20bornes=20de=20la?= =?UTF-8?q?=20politique)=20(#457)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Planning/Presenter/BookingPrefill.php | 51 ++++++++++++++++ .../Planning/Presenter/BookingPrefillTest.php | 61 +++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 src/Planning/Presenter/BookingPrefill.php create mode 100644 tests/Planning/Presenter/BookingPrefillTest.php diff --git a/src/Planning/Presenter/BookingPrefill.php b/src/Planning/Presenter/BookingPrefill.php new file mode 100644 index 00000000..cd3da158 --- /dev/null +++ b/src/Planning/Presenter/BookingPrefill.php @@ -0,0 +1,51 @@ + $query + * + * @return array{date: string, start: string, end: string}|null + */ + public static function from(array $query, \DateTimeImmutable $today): ?array + { + $date = $query['date'] ?? null; + $start = $query['start'] ?? null; + $end = $query['end'] ?? null; + if (!is_string($date) || !is_string($start) || !is_string($end)) { + return null; + } + + $day = \DateTimeImmutable::createFromFormat('!Y-m-d', $date); + if ($day === false || $day->format('Y-m-d') !== $date) { // « 2026-02-31 » déborde : refusé + return null; + } + $first = $today->setTime(0, 0); + if ($day < $first || $day > $first->modify('+' . FreeSlotBookingPolicy::MAX_DAYS_AHEAD . ' days')) { + return null; + } + + foreach ([$start, $end] as $time) { + if (preg_match('/^\d{2}:\d{2}$/', $time) !== 1 || !QuarterHour::isAligned($time)) { + return null; + } + } + if ($end <= $start || $end . ':00' > FreeSlotBookingPolicy::MAX_END_TIME) { + return null; + } + + return ['date' => $date, 'start' => $start, 'end' => $end]; + } +} diff --git a/tests/Planning/Presenter/BookingPrefillTest.php b/tests/Planning/Presenter/BookingPrefillTest.php new file mode 100644 index 00000000..b81f48f8 --- /dev/null +++ b/tests/Planning/Presenter/BookingPrefillTest.php @@ -0,0 +1,61 @@ + '2026-10-14', 'start' => '18:30', 'end' => '22:45'], $this->prefill(['date' => '2026-10-14', 'start' => '18:30', 'end' => '22:45'])); + } + + #[Test] + public function testTodayAndTheLastAllowedDayAreAccepted(): void + { + self::assertNotNull($this->prefill(['date' => '2026-10-12', 'start' => '10:00', 'end' => '11:00'])); + self::assertNotNull($this->prefill(['date' => '2026-11-11', 'start' => '10:00', 'end' => '11:00']), '30 jours devant'); + } + + /** @return array}> */ + public static function untrustworthy(): array + { + return [ + 'rien' => [[]], + 'sans fin' => [['date' => '2026-10-14', 'start' => '18:30']], + 'jour passé' => [['date' => '2026-10-11', 'start' => '18:30', 'end' => '19:00']], + 'trop loin' => [['date' => '2026-11-12', 'start' => '18:30', 'end' => '19:00']], + 'date inexistante' => [['date' => '2026-02-31', 'start' => '18:30', 'end' => '19:00']], + 'date mal formée' => [['date' => '14/10/2026', 'start' => '18:30', 'end' => '19:00']], + 'minutes hors quart d\'heure' => [['date' => '2026-10-14', 'start' => '18:20', 'end' => '19:00']], + 'fin avant le début' => [['date' => '2026-10-14', 'start' => '19:00', 'end' => '18:30']], + 'fin = début' => [['date' => '2026-10-14', 'start' => '19:00', 'end' => '19:00']], + 'fin après 23:30' => [['date' => '2026-10-14', 'start' => '22:00', 'end' => '23:45']], + 'tableau' => [['date' => ['2026-10-14'], 'start' => '18:30', 'end' => '19:00']], + 'injection' => [['date' => '2026-10-14">', 'start' => '18:30', 'end' => '22:45']))->body(); + + self::assertStringNotContainsString('