Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ jobs:
matrix:
python-version: ["3.10", "3.13"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand All @@ -26,8 +26,8 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Install build tools
Expand Down Expand Up @@ -55,8 +55,8 @@ jobs:
id-token: write
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Install build tools
Expand All @@ -71,10 +71,12 @@ jobs:
id: changelog
run: |
VERSION="${GITHUB_REF_NAME#swoop-v}"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
# Extract the section between this version's header and the next version header
awk "/^## \\[${VERSION}\\]/{found=1; next} /^## \\[/{if(found) exit} found{print}" CHANGELOG.md > release-notes.md
cat release-notes.md
- name: Create GitHub Release
run: gh release create "${{ github.ref_name }}" --notes-file release-notes.md
run: gh release create "${{ github.ref_name }}" --title "v${VERSION}" --notes-file release-notes.md
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.changelog.outputs.version }}
6 changes: 3 additions & 3 deletions .github/workflows/live-canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Install dependencies
Expand All @@ -24,7 +24,7 @@ jobs:
run: python -m pytest tests/test_live_contract.py -v -m live
- name: Upload live canary artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: live-canary-artifacts
path: ${{ runner.temp }}/swoop-live-artifacts
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/mutation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ jobs:
mutmut:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Install dependencies
Expand All @@ -21,7 +21,7 @@ jobs:
run: mutmut results > mutmut-results.txt || true
- name: Upload mutation results
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: mutmut-results
path: mutmut-results.txt
Expand Down
9 changes: 8 additions & 1 deletion swoop/rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -621,11 +621,18 @@ def get_booking_results(
transport=transport,
)

return _parse_booking_rpc_response(
options = _parse_booking_rpc_response(
res.text,
registry_version=registry_version,
required_keys=required_keys,
)
if not options:
logger.warning(
"get_booking_results %s->%s on %s returned 0 options "
"(upstream RPC succeeded; token may be stale or itinerary unbookable)",
origin, destination, date,
)
return options


def get_trip_booking_results(
Expand Down
34 changes: 24 additions & 10 deletions tests/test_live_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,15 +235,18 @@ def _record_booking_artifacts(case_id: str, rpc_captures: list[dict[str, str]],
)


def _find_bookable_itinerary(search_result: Any) -> Itinerary:
def _bookable_candidates(search_result: Any, *, limit: int = 5) -> list[Itinerary]:
candidates: list[Itinerary] = []
for option in search_result.results:
for leg in option.legs:
itinerary = leg.itinerary
if itinerary is None:
continue
if itinerary.booking_token and rpc._build_selected_legs(itinerary):
return itinerary
raise AssertionError("Expected at least one itinerary with booking token and selected legs")
candidates.append(itinerary)
if len(candidates) >= limit:
return candidates
return candidates


class TestShoppingContract:
Expand Down Expand Up @@ -317,16 +320,27 @@ def test_booking_results_parseable_and_artifacted(self, monkeypatch: pytest.Monk
)
assert search_result.results, "Expected at least one itinerary before live booking lookup"

itinerary = _find_bookable_itinerary(search_result)
candidates = _bookable_candidates(search_result)
assert candidates, "Expected at least one itinerary with booking token and selected legs"

rpc_captures = _capture_rpc_texts(monkeypatch)
options = rpc.get_booking_results(
itinerary,
registry_version=date.today().isoformat(),
transport=TransportConfig(timeout=30, retries=1),
)
options: list[Any] = []
itinerary: Itinerary | None = None
for candidate in candidates:
options = rpc.get_booking_results(
candidate,
registry_version=date.today().isoformat(),
transport=TransportConfig(timeout=30, retries=1),
)
if options:
itinerary = candidate
break

assert rpc_captures, "Expected a live booking RPC capture"
assert options, "Expected at least one booking option from live booking lookup"
assert options and itinerary is not None, (
f"Expected at least one booking option after trying {len(candidates)} "
"itineraries (upstream returned empty for every candidate)"
)

for option in options[:5]:
assert option.price > 0
Expand Down