08.06 — Data gaps (G-DATA-1, G-DATA-2)#
Two major gaps on data integrity.
G-DATA-1 — No server-side validation of visit_date#
Source#
// appointment.php
array_diff_key($_POST, array(
"facility" => "",
"hospital_readmission" => "",
"programs" => "",
"visit_date" => "",
"comment" => ""
))This checks for key presence only. It never validates the value of visit_date.
Symptom#
- Empty visit_date: if you strip the HTML5
requiredattribute via JS and submit with an empty date → booking accepted. - Past visit_date: if you send a backdated booking → booking accepted.
- The booking is added to history as-is.
Related tests#
| Test | Method |
|---|---|
Appointment - Server accepts empty date when client bypass applied | Strip the required attribute, send with empty booking date → expects rejection by the system (FAIL) |
Appointment - Past date booking accepted | Send a booking request for yesterday → expects rejection by the system (FAIL) |
Both tests fail intentionally — they document the gap.
“Past date booking”#
# src/tests/scenarios/appointment/past_date_booking.py
"""Past-date booking accepted — no temporal validation (§9.7 gap).
Flow:
open appointment form → fill (facility, program, comment, date = yesterday) → submit
→ verify NOT confirmed
(intentional FAIL — CURA accepts past dates, §9.7 gap)
A healthcare scheduling system must not accept bookings for dates that have
already passed. CURA performs no temporal validation: yesterday's date is
accepted and a confirmation is returned. The test asserts rejection and fails
when the confirmation appears.
Pre-fragments: login_as_demo_user
Post-fragments: (none)
"""
def _scenario_past_date_booking(driver, logger) -> TestChain:
on_appointment = AppointmentPage(driver=driver)
on_confirmation = ConfirmationPage(driver=driver)
log_error = create_just_log_error(logger=logger)
log_success = create_just_log_success(logger=logger)
log_and_screenshot = create_log_success_and_take_screenshot(driver=driver, logger=logger)
return [
drive_page(
act(on_appointment, open_appointment_page)
.failure(log_error("Failed to open appointment form"))
.success(log_success("Opened appointment form")),
act(on_appointment, fill_appointment(
facility="Hongkong CURA Healthcare Center",
programs="Medicare",
visit_date=in_days(-1), # ← yesterday
comment="Past-date booking attempt",
))
.failure(log_error("Failed to fill form"))
.success(log_and_screenshot("Filled form with past date")),
act(on_appointment, submit_appointment)
.failure(log_error("Failed to submit"))
.success(log_success("Submitted")),
),
drive_page(
act(on_confirmation, verify_booking_not_confirmed)
.failure(log_error("CURA accepted past date — §9.7 gap"))
.success(log_and_screenshot("Rejection confirmed")),
),
]verify_booking_not_confirmedsucceeds if we stayed on/appointment.php(= rejection).- If redirected to
/confirmation.php(= acceptance), it raises →.failure()logs “CURA accepted past date — §9.7 gap”.
verify_booking_not_confirmed#
# pages/confirmation.py
_NOT_CONFIRMED_TIMEOUT = 10 # ← fixed, independent of --wait-timeout
class ConfirmationPage(...):
def verify_booking_not_confirmed(self) -> ConfirmationPage:
try:
WebDriverWait(self._driver, _NOT_CONFIRMED_TIMEOUT).until(
ec.url_contains("appointment.php")
)
return self # ✅ we stayed on appointment → rejection
except TimeoutException as exc:
raise BookingNotRejectedError(
f"CURA accepted the booking (url: {self._driver.current_url}); §9.7 gap"
) from excurl_contains("appointment.php"): URL-based, not DOM-based — matches CURA’s actual behavior._NOT_CONFIRMED_TIMEOUT = 10: fixed at 10s, independent of--wait-timeout.BookingNotRejectedError: a specific exception — notAssertionError, notTimeoutException. This is not a flake.
G-DATA-2 — No uniqueness constraint or conflict check#
Source#
// appointment.php
array_push($_SESSION['history'], $_POST);Unconditional array_push. No checks:
- No “does this user already have an appointment on this date?” check.
- No “are these two appointments geographically compatible?” check (Hongkong + Tokyo, same day).
Symptoms#
- Duplicate: two identical bookings → both are accepted and appear in history.
- Overlap: two bookings on the same day at very distant locations (Hongkong + Tokyo) → both are accepted.
Related tests#
| Test | Method | FAIL? |
|---|---|---|
Appointments - Duplicate booking | Book A, then book A again | FAIL (CURA accepts) |
Appointments - Overlapping appointments | Book Hongkong/2025-05-18, book Tokyo/2025-05-18 | FAIL (CURA accepts) |
book_then_verify_not_confirmed
For these tests, we reuse the G-DATA-1 pattern:
- Book a first appointment (accepted).
- Book a second nonsensical one (should be rejected).
verify_booking_not_confirmedon the second.
If the second booking is accepted too → fail (gap confirmed).
_NOT_CONFIRMED_TIMEOUT
All these gap tests use this constant:
_NOT_CONFIRMED_TIMEOUT = 10This constant deliberately bypasses CLI parameters. Bump --wait-timeout to 30 to stabilize another test — the gap tests stay at 10. No risk of silently inflating the gap assertion window.
CURA_TEST_STRATEGY.md:
The assertion window is fixed at
_NOT_CONFIRMED_TIMEOUT(10 s, defined inconfirmation.py) and is intentionally independent of--wait-timeout: bumping the global wait flag for driver stability must not silently inflate every gap test’s assertion budget.
saturation_booking#
Example:
saturation_booking.py— 5 consecutive bookings on different dates in one session, all accepted; documents the absence of a rate limit.
Exploratory, not a gap test. It passes — documents that CURA accepts 5 rapid bookings. If CURA ever adds a rate-limit that rejects the 5th, this test breaks → revise it to reflect the new reality.
Pedagogy#
| Concept | Test |
|---|---|
| How to express a gap test | past_date_booking.py |
| How to handle an independent timeout | _NOT_CONFIRMED_TIMEOUT |
| How to distinguish a “failing SUT” exception from an “expected behavior” | BookingNotRejectedError vs TimeoutException |
| How to document observed behavior without a gap assertion | saturation_booking.py |