avios-cli 0.6.0__tar.gz → 0.6.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. {avios_cli-0.6.0 → avios_cli-0.6.1}/CHANGELOG.md +8 -0
  2. {avios_cli-0.6.0 → avios_cli-0.6.1}/PKG-INFO +1 -1
  3. {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/__init__.py +1 -1
  4. {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/auth.py +20 -4
  5. {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/test_auth.py +23 -4
  6. {avios_cli-0.6.0 → avios_cli-0.6.1}/.editorconfig +0 -0
  7. {avios_cli-0.6.0 → avios_cli-0.6.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  8. {avios_cli-0.6.0 → avios_cli-0.6.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  9. {avios_cli-0.6.0 → avios_cli-0.6.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  10. {avios_cli-0.6.0 → avios_cli-0.6.1}/.github/dependabot.yml +0 -0
  11. {avios_cli-0.6.0 → avios_cli-0.6.1}/.github/workflows/ci.yml +0 -0
  12. {avios_cli-0.6.0 → avios_cli-0.6.1}/.github/workflows/publish.yml +0 -0
  13. {avios_cli-0.6.0 → avios_cli-0.6.1}/.gitignore +0 -0
  14. {avios_cli-0.6.0 → avios_cli-0.6.1}/.pre-commit-config.yaml +0 -0
  15. {avios_cli-0.6.0 → avios_cli-0.6.1}/.python-version +0 -0
  16. {avios_cli-0.6.0 → avios_cli-0.6.1}/CONTRIBUTING.md +0 -0
  17. {avios_cli-0.6.0 → avios_cli-0.6.1}/LICENSE +0 -0
  18. {avios_cli-0.6.0 → avios_cli-0.6.1}/README.md +0 -0
  19. {avios_cli-0.6.0 → avios_cli-0.6.1}/SECURITY.md +0 -0
  20. {avios_cli-0.6.0 → avios_cli-0.6.1}/docs/dashboard.svg +0 -0
  21. {avios_cli-0.6.0 → avios_cli-0.6.1}/docs/reward-flights.svg +0 -0
  22. {avios_cli-0.6.0 → avios_cli-0.6.1}/pyproject.toml +0 -0
  23. {avios_cli-0.6.0 → avios_cli-0.6.1}/scripts/screenshot.py +0 -0
  24. {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/accounts.py +0 -0
  25. {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/aggregate.py +0 -0
  26. {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/cli.py +0 -0
  27. {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/client.py +0 -0
  28. {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/config.py +0 -0
  29. {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/endpoints.py +0 -0
  30. {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/finnair.py +0 -0
  31. {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/mcp_entry.py +0 -0
  32. {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/mcp_server.py +0 -0
  33. {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/models.py +0 -0
  34. {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/programmes.py +0 -0
  35. {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/rewards.py +0 -0
  36. {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/session.py +0 -0
  37. {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/tui/__init__.py +0 -0
  38. {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/tui/app.py +0 -0
  39. {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/tui/art.py +0 -0
  40. {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/tui/rewards.py +0 -0
  41. {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/tui/styles.tcss +0 -0
  42. {avios_cli-0.6.0 → avios_cli-0.6.1}/src/avios/tui/widgets.py +0 -0
  43. {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/conftest.py +0 -0
  44. {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/fixtures/balance.json +0 -0
  45. {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/fixtures/reward_search.rsc +0 -0
  46. {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/fixtures/transactions.json +0 -0
  47. {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/test_accounts.py +0 -0
  48. {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/test_aggregate.py +0 -0
  49. {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/test_cli.py +0 -0
  50. {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/test_client.py +0 -0
  51. {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/test_finnair.py +0 -0
  52. {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/test_mcp_server.py +0 -0
  53. {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/test_models.py +0 -0
  54. {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/test_rewards.py +0 -0
  55. {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/test_session.py +0 -0
  56. {avios_cli-0.6.0 → avios_cli-0.6.1}/tests/test_tui.py +0 -0
  57. {avios_cli-0.6.0 → avios_cli-0.6.1}/uv.lock +0 -0
@@ -6,6 +6,14 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.6.1] - 2026-11-11
10
+
11
+ ### Fixed
12
+ - `avios login ba` no longer reports "reward-flight login did not succeed" when BA's
13
+ reward app runs on the main `ba_session_id` session instead of issuing a separate
14
+ `__session`/`appSession*` cookie. The login now completes once the reward page is
15
+ loaded with a valid session.
16
+
9
17
  ## [0.6.0] - 2026-08-11
10
18
 
11
19
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: avios-cli
3
- Version: 0.6.0
3
+ Version: 0.6.1
4
4
  Summary: A CLI, TUI and MCP server for Avios balances, transactions, and BA reward-flight availability.
5
5
  Project-URL: Homepage, https://github.com/alexechoi/avios-cli
6
6
  Project-URL: Repository, https://github.com/alexechoi/avios-cli
@@ -4,4 +4,4 @@ View your Avios balance, transactions and reward-flight availability from the
4
4
  terminal, or serve them to an AI agent with ``avios-mcp``.
5
5
  """
6
6
 
7
- __version__ = "0.6.0"
7
+ __version__ = "0.6.1"
@@ -33,6 +33,7 @@ from avios.session import cookie_header_from
33
33
  LOGIN_TIMEOUT_MS = 300_000 # 5 minutes to complete password + captcha + MFA
34
34
  LOGIN_POLL_MS = 2_000 # how often to check whether the session is authenticated
35
35
  SUPPORTED_BROWSERS = ("chrome", "firefox", "edge", "brave", "safari", "chromium")
36
+ BA_ACCOUNT_SESSION_COOKIE = "ba_session_id"
36
37
  BA_REWARD_SEARCH_PATH = "/en-GB/spend-avios/search-reward-flights"
37
38
 
38
39
  # Chromium-family user-data roots per browser (relative to home), used to discover
@@ -140,14 +141,29 @@ def login_via_browser(
140
141
  return _only_avios(cookies)
141
142
 
142
143
 
143
- def _has_ba_reward_session(ctx: Any) -> bool:
144
- """Whether BA's separate reward-search application session is present."""
145
- names = {
144
+ def _avios_cookie_names(ctx: Any) -> set[str]:
145
+ return {
146
146
  str(cookie.get("name", ""))
147
147
  for cookie in ctx.cookies()
148
148
  if "avios.com" in str(cookie.get("domain", ""))
149
149
  }
150
- return "__session" in names or any(name.startswith("appSession") for name in names)
150
+
151
+
152
+ def _has_ba_reward_session(ctx: Any) -> bool:
153
+ """Whether a session usable by BA's reward-search application is present.
154
+
155
+ BA historically issued a separate ``__session``/``appSession*`` cookie for the
156
+ reward app. It now also serves the reward app from the main ``ba_session_id``
157
+ session without setting those cookies, so that cookie counts too. The caller
158
+ still requires the browser to be sitting on the reward page, so a redirect to a
159
+ separate login keeps the wait going.
160
+ """
161
+ names = _avios_cookie_names(ctx)
162
+ return (
163
+ "__session" in names
164
+ or BA_ACCOUNT_SESSION_COOKIE in names
165
+ or any(name.startswith("appSession") for name in names)
166
+ )
151
167
 
152
168
 
153
169
  def _wait_for_ba_reward_auth(ctx: Any, page: Any, base_url: str, timeout_ms: int) -> bool:
@@ -124,13 +124,14 @@ class _FakeRequest:
124
124
 
125
125
 
126
126
  class _FakePage:
127
- def __init__(self) -> None:
127
+ def __init__(self, redirect_to: str | None = None) -> None:
128
128
  self.visited: list[str] = []
129
129
  self.url = ""
130
+ self._redirect_to = redirect_to
130
131
 
131
132
  def goto(self, url: str, **kwargs: object) -> None:
132
133
  self.visited.append(url)
133
- self.url = url
134
+ self.url = self._redirect_to or url
134
135
 
135
136
  def wait_for_timeout(self, ms: int) -> None:
136
137
  pass
@@ -140,11 +141,16 @@ class _FakePage:
140
141
 
141
142
 
142
143
  class _FakeContext:
143
- def __init__(self, ok_after: int, cookies: list[dict[str, str]]) -> None:
144
+ def __init__(
145
+ self,
146
+ ok_after: int,
147
+ cookies: list[dict[str, str]],
148
+ redirect_to: str | None = None,
149
+ ) -> None:
144
150
  self.request = _FakeRequest(ok_after)
145
151
  self._cookies = cookies
146
152
  self.closed = False
147
- self.page = _FakePage()
153
+ self.page = _FakePage(redirect_to)
148
154
 
149
155
  def new_page(self) -> _FakePage:
150
156
  return self.page
@@ -208,10 +214,23 @@ def test_login_captures_only_after_authenticated(settings: Settings) -> None:
208
214
  assert ctx.page.visited[-1].endswith("/en-GB/spend-avios/search-reward-flights")
209
215
 
210
216
 
217
+ def test_ba_login_accepts_account_session_on_reward_page(settings: Settings) -> None:
218
+ cookies = [{"name": "ba_session_id", "value": "x", "domain": "www.avios.com"}]
219
+ ctx = _FakeContext(ok_after=1, cookies=cookies)
220
+
221
+ captured = login_via_browser(
222
+ BA, settings=settings, timeout_ms=4_000, playwright_factory=_FakeFactory(ctx)
223
+ )
224
+
225
+ assert captured == cookies
226
+ assert ctx.page.visited[-1].endswith("/en-GB/spend-avios/search-reward-flights")
227
+
228
+
211
229
  def test_ba_login_waits_for_separate_reward_session(settings: Settings) -> None:
212
230
  ctx = _FakeContext(
213
231
  ok_after=1,
214
232
  cookies=[{"name": "ba_session_id", "value": "x", "domain": "www.avios.com"}],
233
+ redirect_to="https://www.avios.com/reward-login",
215
234
  )
216
235
 
217
236
  with pytest.raises(LoginError, match="reward-flight login"):
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes