187 lines
5.4 KiB
Python
187 lines
5.4 KiB
Python
"""Tests for agenda.generate_booking_yaml."""
|
|
|
|
from dataclasses import dataclass
|
|
from datetime import date
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
import yaml
|
|
|
|
from agenda import generate_booking_yaml
|
|
|
|
|
|
@dataclass
|
|
class FakeTrip:
|
|
"""Small stand-in for agenda.types.Trip in matching tests."""
|
|
|
|
start: date
|
|
end: date | None
|
|
|
|
|
|
def test_yaml_format_description_includes_train_spec() -> None:
|
|
"""Train prompt docs should come from personal-data-yaml.md."""
|
|
description = generate_booking_yaml.yaml_format_description(
|
|
generate_booking_yaml.BOOKING_CONFIGS["train"]
|
|
)
|
|
|
|
assert "## General Rules" in description
|
|
assert "## Cross-File References" in description
|
|
assert "## `trains.yaml`" in description
|
|
assert "`operator`: booking/operator label" in description
|
|
|
|
|
|
def test_build_prompt_uses_spec_and_keeps_trip_exclusion() -> None:
|
|
"""The generated prompt should use the documented schema."""
|
|
prompt = generate_booking_yaml.build_prompt(
|
|
"Eurostar booking details",
|
|
generate_booking_yaml.BOOKING_CONFIGS["train"],
|
|
current_bookings="- operator: eurostar\n",
|
|
)
|
|
|
|
assert "Use this YAML format specification" in prompt
|
|
assert "## `trains.yaml`" in prompt
|
|
assert 'Exclude the top-level "trip" key' in prompt
|
|
assert "Eurostar booking details" in prompt
|
|
|
|
|
|
def test_booking_text_from_args_fetches_url(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""URL arguments should be fetched instead of reading stdin."""
|
|
monkeypatch.setattr(
|
|
generate_booking_yaml,
|
|
"url_to_booking_text",
|
|
lambda url: f"fetched {url}",
|
|
)
|
|
|
|
assert generate_booking_yaml.booking_text_from_args(["https://example.com"]) == (
|
|
"fetched https://example.com"
|
|
)
|
|
|
|
|
|
def test_matching_trip_date_uses_built_trip_end(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""Trip matching should use build_trip_list-derived end dates."""
|
|
monkeypatch.setattr(
|
|
generate_booking_yaml,
|
|
"build_trips",
|
|
lambda data_dir: [ # noqa: ARG005
|
|
FakeTrip(date(2026, 2, 6), date(2026, 2, 9)),
|
|
FakeTrip(date(2026, 3, 3), date(2026, 3, 5)),
|
|
],
|
|
)
|
|
|
|
assert generate_booking_yaml.matching_trip_date(
|
|
generate_booking_yaml.datetime_from_yaml_value("2026-02-08 12:00:00+01:00"),
|
|
Path("/tmp/personal-data"),
|
|
) == date(2026, 2, 6)
|
|
|
|
|
|
def test_matching_trip_date_falls_back_to_departure_date(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""A booking outside known trips should use its first departure date."""
|
|
monkeypatch.setattr(
|
|
generate_booking_yaml, "build_trips", lambda data_dir: [] # noqa: ARG005
|
|
)
|
|
|
|
assert generate_booking_yaml.matching_trip_date(
|
|
generate_booking_yaml.datetime_from_yaml_value("2026-04-10 12:00:00+01:00"),
|
|
Path("/tmp/personal-data"),
|
|
) == date(2026, 4, 10)
|
|
|
|
|
|
def test_import_train_booking_adds_trip_and_inserts_chronologically(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""Generated train bookings should be written into trains.yaml in order."""
|
|
(tmp_path / "trains.yaml").write_text("""- operator: early
|
|
from: A
|
|
to: B
|
|
trip: 2026-02-01
|
|
depart: 2026-02-01 10:00:00+00:00
|
|
arrive: 2026-02-01 11:00:00+00:00
|
|
legs: []
|
|
|
|
- operator: late
|
|
from: C
|
|
to: D
|
|
trip: 2026-02-10
|
|
depart: 2026-02-10 10:00:00+00:00
|
|
arrive: 2026-02-10 11:00:00+00:00
|
|
legs: []
|
|
""")
|
|
monkeypatch.setattr(
|
|
generate_booking_yaml,
|
|
"matching_trip_date",
|
|
lambda depart, data_dir: date(2026, 2, 6),
|
|
)
|
|
|
|
count = generate_booking_yaml.import_booking_yaml(
|
|
"""- operator: eurostar
|
|
from: London St Pancras
|
|
to: Brussels Midi
|
|
depart: 2026-02-06 15:04:00+00:00
|
|
arrive: 2026-02-06 18:12:00+01:00
|
|
legs: []
|
|
""",
|
|
generate_booking_yaml.BOOKING_CONFIGS["train"],
|
|
data_dir=tmp_path,
|
|
)
|
|
|
|
written = yaml.safe_load((tmp_path / "trains.yaml").read_text())
|
|
assert count == 1
|
|
assert [item["operator"] for item in written] == ["early", "eurostar", "late"]
|
|
assert written[1]["trip"] == date(2026, 2, 6)
|
|
assert list(written[1]).index("trip") == 3
|
|
assert "\n\n- operator: eurostar\n" in (tmp_path / "trains.yaml").read_text()
|
|
|
|
|
|
def test_import_flight_booking_adds_trip_and_inserts_chronologically(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""Generated flight bookings should be written into flights.yaml in order."""
|
|
(tmp_path / "flights.yaml").write_text("""---
|
|
- booking_reference: OLD
|
|
trip: 2026-02-01
|
|
flights:
|
|
- depart: 2026-02-01 10:00:00+00:00
|
|
from: BRS
|
|
to: AMS
|
|
flight_number: '1'
|
|
airline: U2
|
|
|
|
- booking_reference: NEWER
|
|
trip: 2026-02-10
|
|
flights:
|
|
- depart: 2026-02-10 10:00:00+00:00
|
|
from: BRS
|
|
to: AMS
|
|
flight_number: '2'
|
|
airline: U2
|
|
""")
|
|
monkeypatch.setattr(
|
|
generate_booking_yaml,
|
|
"matching_trip_date",
|
|
lambda depart, data_dir: date(2026, 2, 6),
|
|
)
|
|
|
|
count = generate_booking_yaml.import_booking_yaml(
|
|
"""booking_reference: MID
|
|
flights:
|
|
- depart: 2026-02-06 10:00:00+00:00
|
|
from: BRS
|
|
to: AMS
|
|
flight_number: '3'
|
|
airline: U2
|
|
""",
|
|
generate_booking_yaml.BOOKING_CONFIGS["flight"],
|
|
data_dir=tmp_path,
|
|
)
|
|
|
|
written = yaml.safe_load((tmp_path / "flights.yaml").read_text())
|
|
assert count == 1
|
|
assert [item["booking_reference"] for item in written] == ["OLD", "MID", "NEWER"]
|
|
assert written[1]["trip"] == date(2026, 2, 6)
|
|
assert list(written[1]).index("trip") == 1
|