Files
049-snack-and-lift/tests/unit/test_verification.py
2026-10-28 21:45:48 +08:00

247 lines
8.3 KiB
Python

"""@file test_verification.py
@brief The comparison of the bot's results with the My Bookings page, and its text.
"""
import pytest
from tests.unit.test_booking_entry import make_entry
from tests.unit.test_models import make_card
from snack_and_lift.domain.errors import PageFormatError
from snack_and_lift.domain.models import ClassResult, Outcome
from snack_and_lift.domain.verification import build_verification, format_verification
BOOKED = ClassResult(make_card("Tue, Aug 12"), Outcome.NEW_BOOKING)
WAITLISTED = ClassResult(make_card("Thu, Aug 14", card_id="2"), Outcome.NEW_WAITLIST)
def test_everything_found_is_a_success() -> None:
result = build_verification(
[BOOKED, WAITLISTED],
[make_entry("Tue, Aug 12, 6:00 PM"), make_entry("Thu, Aug 14, 6:00 PM", waitlisted=True)],
)
assert result.is_success
assert result.verified == ["Spin Class", "Spin Class (Waitlist)"]
assert (result.expected, result.found) == (2, 2)
def test_success_text_is_exact() -> None:
result = build_verification(
[BOOKED, WAITLISTED],
[make_entry("Tue, Aug 12, 6:00 PM"), make_entry("Thu, Aug 14, 6:00 PM", waitlisted=True)],
)
assert format_verification(result) == (
"--- VERIFYING ON MY BOOKINGS PAGE ---\n"
" ✓ Verified: Spin Class\n"
" ✓ Verified: Spin Class (Waitlist)\n"
"\n"
"--- VERIFICATION RESULT ---\n"
"Expected: 2 bookings\n"
"Found: 2 bookings\n"
"✅ SUCCESS: All bookings verified!"
)
def test_one_booking_is_singular() -> None:
text = format_verification(build_verification([BOOKED], [make_entry("Tue, Aug 12, 6:00 PM")]))
assert "Expected: 1 booking\nFound: 1 booking\n" in text
def test_missing_entry_is_a_mismatch_with_a_line() -> None:
result = build_verification([BOOKED, WAITLISTED], [make_entry("Tue, Aug 12, 6:00 PM")])
text = format_verification(result)
assert not result.is_success
assert result.missing == ["Spin Class (Waitlist) on Thu, Aug 14"]
assert "Expected: 2 bookings\nFound: 1 booking\n❌ MISMATCH: Missing 1 bookings" in text
assert text.endswith(" ✗ Missing: Spin Class (Waitlist) on Thu, Aug 14")
def test_unexpected_entry_inside_the_window_is_a_mismatch() -> None:
result = build_verification(
[BOOKED, WAITLISTED],
[
make_entry("Tue, Aug 12, 6:00 PM"),
make_entry("Thu, Aug 14, 6:00 PM", waitlisted=True),
make_entry("Thu, Aug 14, 6:00 PM", name="Yoga Class"),
],
)
text = format_verification(result)
assert not result.is_success
assert result.unexpected == ["Yoga Class on Thu, Aug 14"]
assert "❌ MISMATCH: Missing 0 bookings" in text
assert text.endswith(" ? Unexpected: Yoga Class on Thu, Aug 14")
def test_entries_outside_the_window_are_a_note_and_a_success() -> None:
result = build_verification(
[BOOKED, WAITLISTED],
[
make_entry("Tue, Aug 12, 6:00 PM"),
make_entry("Thu, Aug 14, 6:00 PM", waitlisted=True),
make_entry("Thu, Aug 7, 6:00 PM"),
make_entry("Tue, Aug 19, 6:00 PM", waitlisted=True),
],
)
assert result.is_success
assert (result.expected, result.found) == (2, 2)
assert result.also_booked == [
"Spin Class on Thu, Aug 7",
"Spin Class (Waitlist) on Tue, Aug 19",
]
assert format_verification(result) == (
"--- VERIFYING ON MY BOOKINGS PAGE ---\n"
" ✓ Verified: Spin Class\n"
" ✓ Verified: Spin Class (Waitlist)\n"
"\n"
"--- VERIFICATION RESULT ---\n"
"Expected: 2 bookings\n"
"Found: 2 bookings\n"
" i Also booked (not checked): Spin Class on Thu, Aug 7\n"
" i Also booked (not checked): Spin Class (Waitlist) on Tue, Aug 19\n"
"✅ SUCCESS: All bookings verified!"
)
def test_window_ends_are_inclusive() -> None:
result = build_verification(
[BOOKED, WAITLISTED],
[make_entry("Tue, Aug 12, 6:00 PM"), make_entry("Thu, Aug 14, 6:00 PM", waitlisted=True)],
)
assert result.also_booked == []
def test_missing_count_is_the_number_of_missing_classes_with_notes_outside() -> None:
result = build_verification(
[BOOKED, WAITLISTED],
[make_entry("Tue, Aug 12, 6:00 PM"), make_entry("Tue, Aug 26, 6:00 PM")],
)
text = format_verification(result)
assert result.missing == ["Spin Class (Waitlist) on Thu, Aug 14"]
assert "Expected: 2 bookings\nFound: 1 booking\n" in text
assert (
" i Also booked (not checked): Spin Class on Tue, Aug 26\n❌ MISMATCH: Missing 1 bookings"
in text
)
def test_missing_count_is_never_negative() -> None:
result = build_verification(
[BOOKED, WAITLISTED],
[
make_entry("Tue, Aug 12, 6:00 PM"),
make_entry("Wed, Aug 13, 6:00 PM"),
make_entry("Wed, Aug 13, 6:00 PM", name="Yoga Class"),
],
)
assert "Missing 1 bookings" in format_verification(result)
assert len(result.unexpected) == 2
def test_real_run_with_bookings_after_the_window_is_a_success() -> None:
first = ClassResult(make_card("Thu, Oct 29"), Outcome.ALREADY_BOOKED)
second = ClassResult(make_card("Tue, Nov 3", card_id="2"), Outcome.ALREADY_BOOKED)
entries = [
make_entry("Thu, Oct 29, 6:00 PM"),
make_entry("Tue, Nov 3, 6:00 PM"),
make_entry("Thu, Nov 12, 6:00 PM"),
make_entry("Tue, Nov 17, 6:00 PM"),
make_entry("Tue, Nov 24, 6:00 PM"),
]
result = build_verification([first, second], entries)
assert result.is_success
assert (result.expected, result.found) == (2, 2)
assert result.also_booked == [
"Spin Class on Thu, Nov 12",
"Spin Class on Tue, Nov 17",
"Spin Class on Tue, Nov 24",
]
def test_no_results_makes_every_entry_a_note_and_no_mismatch() -> None:
result = build_verification([], [make_entry("Tue, Aug 12, 6:00 PM")])
assert result.is_success
assert (result.expected, result.found) == (0, 0)
assert result.also_booked == ["Spin Class on Tue, Aug 12"]
def test_window_holds_over_the_turn_of_the_year() -> None:
december = ClassResult(make_card("Tue, Dec 30"), Outcome.NEW_BOOKING)
january = ClassResult(make_card("Thu, Jan 1", card_id="2"), Outcome.NEW_BOOKING)
result = build_verification(
[december, january],
[
make_entry("Tue, Dec 30, 6:00 PM"),
make_entry("Thu, Jan 1, 6:00 PM"),
make_entry("Thu, Jan 8, 6:00 PM"),
make_entry("Thu, Dec 18, 6:00 PM"),
],
)
assert result.is_success
assert result.also_booked == ["Spin Class on Thu, Jan 8", "Spin Class on Thu, Dec 18"]
def test_leap_day_is_a_valid_date() -> None:
leap = ClassResult(make_card("Thu, Feb 29"), Outcome.NEW_BOOKING)
assert build_verification([leap], [make_entry("Thu, Feb 29, 6:00 PM")]).is_success
@pytest.mark.parametrize("date_text", ["Tue, Foo 12", "Tue, Feb 30"])
def test_an_invalid_date_is_a_page_format_error(date_text: str) -> None:
bad = ClassResult(make_card(date_text), Outcome.NEW_BOOKING)
with pytest.raises(PageFormatError):
build_verification([bad], [])
def test_waitlist_flag_must_match() -> None:
result = build_verification([BOOKED], [make_entry("Tue, Aug 12, 6:00 PM", waitlisted=True)])
assert result.missing == ["Spin Class on Tue, Aug 12"]
assert result.unexpected == ["Spin Class (Waitlist) on Tue, Aug 12"]
assert "Missing 1 bookings" in format_verification(result)
def test_date_must_match() -> None:
result = build_verification([BOOKED], [make_entry("Tue, Aug 19, 6:00 PM")])
assert not result.is_success
def test_already_waitlisted_is_expected_as_waitlist() -> None:
results = [ClassResult(make_card("Thu, Aug 14"), Outcome.ALREADY_WAITLISTED)]
assert build_verification(
results, [make_entry("Thu, Aug 14, 6:00 PM", waitlisted=True)]
).is_success
def test_nothing_expected_and_nothing_found_is_a_success() -> None:
result = build_verification([], [])
assert result.is_success
assert "Expected: 0 bookings\nFound: 0 bookings\n✅ SUCCESS" in format_verification(result)
def test_same_class_twice_is_matched_one_to_one() -> None:
result = build_verification([BOOKED, BOOKED], [make_entry("Tue, Aug 12, 6:00 PM")])
assert result.verified == ["Spin Class"]
assert result.missing == ["Spin Class on Tue, Aug 12"]