avios-cli 0.1.5__tar.gz → 0.1.6__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 (44) hide show
  1. {avios_cli-0.1.5 → avios_cli-0.1.6}/CHANGELOG.md +22 -1
  2. {avios_cli-0.1.5 → avios_cli-0.1.6}/PKG-INFO +14 -10
  3. {avios_cli-0.1.5 → avios_cli-0.1.6}/README.md +13 -9
  4. {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/__init__.py +1 -1
  5. {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/auth.py +4 -2
  6. {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/cli.py +32 -21
  7. {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/client.py +6 -7
  8. {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/models.py +7 -3
  9. {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/tui/app.py +9 -3
  10. {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/tui/widgets.py +2 -2
  11. avios_cli-0.1.6/tests/fixtures/balance.json +1 -0
  12. {avios_cli-0.1.5 → avios_cli-0.1.6}/tests/test_cli.py +18 -10
  13. {avios_cli-0.1.5 → avios_cli-0.1.6}/tests/test_client.py +8 -8
  14. {avios_cli-0.1.5 → avios_cli-0.1.6}/tests/test_models.py +9 -7
  15. {avios_cli-0.1.5 → avios_cli-0.1.6}/tests/test_tui.py +1 -1
  16. avios_cli-0.1.5/tests/fixtures/accounts.json +0 -3
  17. avios_cli-0.1.5/tests/fixtures/balance.json +0 -1
  18. {avios_cli-0.1.5 → avios_cli-0.1.6}/.editorconfig +0 -0
  19. {avios_cli-0.1.5 → avios_cli-0.1.6}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  20. {avios_cli-0.1.5 → avios_cli-0.1.6}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  21. {avios_cli-0.1.5 → avios_cli-0.1.6}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  22. {avios_cli-0.1.5 → avios_cli-0.1.6}/.github/dependabot.yml +0 -0
  23. {avios_cli-0.1.5 → avios_cli-0.1.6}/.github/workflows/ci.yml +0 -0
  24. {avios_cli-0.1.5 → avios_cli-0.1.6}/.github/workflows/publish.yml +0 -0
  25. {avios_cli-0.1.5 → avios_cli-0.1.6}/.gitignore +0 -0
  26. {avios_cli-0.1.5 → avios_cli-0.1.6}/.pre-commit-config.yaml +0 -0
  27. {avios_cli-0.1.5 → avios_cli-0.1.6}/.python-version +0 -0
  28. {avios_cli-0.1.5 → avios_cli-0.1.6}/CONTRIBUTING.md +0 -0
  29. {avios_cli-0.1.5 → avios_cli-0.1.6}/LICENSE +0 -0
  30. {avios_cli-0.1.5 → avios_cli-0.1.6}/SECURITY.md +0 -0
  31. {avios_cli-0.1.5 → avios_cli-0.1.6}/docs/dashboard.svg +0 -0
  32. {avios_cli-0.1.5 → avios_cli-0.1.6}/pyproject.toml +0 -0
  33. {avios_cli-0.1.5 → avios_cli-0.1.6}/scripts/screenshot.py +0 -0
  34. {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/config.py +0 -0
  35. {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/endpoints.py +0 -0
  36. {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/session.py +0 -0
  37. {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/tui/__init__.py +0 -0
  38. {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/tui/art.py +0 -0
  39. {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/tui/styles.tcss +0 -0
  40. {avios_cli-0.1.5 → avios_cli-0.1.6}/tests/conftest.py +0 -0
  41. {avios_cli-0.1.5 → avios_cli-0.1.6}/tests/fixtures/transactions.json +0 -0
  42. {avios_cli-0.1.5 → avios_cli-0.1.6}/tests/test_auth.py +0 -0
  43. {avios_cli-0.1.5 → avios_cli-0.1.6}/tests/test_session.py +0 -0
  44. {avios_cli-0.1.5 → avios_cli-0.1.6}/uv.lock +0 -0
@@ -6,6 +6,26 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.1.6] - 2026-07-24
10
+
11
+ ### Fixed
12
+ - **`balance` finally works after login.** It (and `whoami`) were querying the
13
+ `manage-avios`/`spend-avios` micro-app APIs, which need a separate per-app
14
+ session and returned 401 ("Session expired") even when you were logged in.
15
+ Probing a real session showed the balance is served by
16
+ `/shell/api/users/current/accounts` (`{balance, individual, household}`) and the
17
+ profile by `/auth-gateway/user` — both of which authenticate with the browser
18
+ session. `balance`/`whoami` now use those.
19
+
20
+ ### Changed
21
+ - `balance` shows total / individual / household; `whoami` shows name, tier,
22
+ membership and email.
23
+ - The TUI shows your balance even if transactions are unavailable.
24
+ - `transactions`, `pending` and `overview` are marked **experimental** — they need
25
+ the `manage-avios` app session, which the cookie/SSO login can't yet establish.
26
+ - Removed the `accounts` command (its endpoint is the balance source, now shown by
27
+ `balance`).
28
+
9
29
  ## [0.1.5] - 2026-07-24
10
30
 
11
31
  ### Fixed
@@ -87,7 +107,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
87
107
  - Reward-flight **availability** search is not yet implemented (needs a British
88
108
  Airways capture).
89
109
 
90
- [Unreleased]: https://github.com/alexechoi/avios-cli/compare/v0.1.5...HEAD
110
+ [Unreleased]: https://github.com/alexechoi/avios-cli/compare/v0.1.6...HEAD
111
+ [0.1.6]: https://github.com/alexechoi/avios-cli/compare/v0.1.5...v0.1.6
91
112
  [0.1.5]: https://github.com/alexechoi/avios-cli/compare/v0.1.4...v0.1.5
92
113
  [0.1.4]: https://github.com/alexechoi/avios-cli/compare/v0.1.3...v0.1.4
93
114
  [0.1.3]: https://github.com/alexechoi/avios-cli/compare/v0.1.2...v0.1.3
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: avios-cli
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: A CLI and TUI for avios.com — view your Avios balance and transactions from the terminal.
5
5
  Project-URL: Homepage, https://github.com/alexechoi/avios-cli
6
6
  Project-URL: Repository, https://github.com/alexechoi/avios-cli
@@ -119,17 +119,21 @@ expires after ~a day; just run `avios login` again. Run `avios logout` to remove
119
119
  ## Usage
120
120
 
121
121
  ```bash
122
- avios balance # your Avios balance
123
- avios transactions --limit 20 # recent transactions
124
- avios pending # pending Avios
125
- avios accounts # linked loyalty accounts
126
- avios overview # dashboard summary (raw JSON)
127
- avios whoami # your profile (raw JSON)
128
- avios raw /manage-avios/api/user/current # hit any endpoint directly
122
+ avios balance # total / individual / household Avios
123
+ avios whoami # name, tier, membership, email
124
+ avios raw /shell/api/users/current/accounts # hit any endpoint directly
129
125
  ```
130
126
 
131
- Add `--json` to `balance`, `transactions`, `pending` or `accounts` for scriptable
132
- output.
127
+ Add `--json` to `balance` or `whoami` for scriptable output.
128
+
129
+ **Experimental** (need the `manage-avios` app session, which the cookie/SSO login
130
+ can't yet establish — these currently return "session expired"):
131
+
132
+ ```bash
133
+ avios transactions --limit 20
134
+ avios pending
135
+ avios overview
136
+ ```
133
137
 
134
138
  ## TUI
135
139
 
@@ -84,17 +84,21 @@ expires after ~a day; just run `avios login` again. Run `avios logout` to remove
84
84
  ## Usage
85
85
 
86
86
  ```bash
87
- avios balance # your Avios balance
88
- avios transactions --limit 20 # recent transactions
89
- avios pending # pending Avios
90
- avios accounts # linked loyalty accounts
91
- avios overview # dashboard summary (raw JSON)
92
- avios whoami # your profile (raw JSON)
93
- avios raw /manage-avios/api/user/current # hit any endpoint directly
87
+ avios balance # total / individual / household Avios
88
+ avios whoami # name, tier, membership, email
89
+ avios raw /shell/api/users/current/accounts # hit any endpoint directly
94
90
  ```
95
91
 
96
- Add `--json` to `balance`, `transactions`, `pending` or `accounts` for scriptable
97
- output.
92
+ Add `--json` to `balance` or `whoami` for scriptable output.
93
+
94
+ **Experimental** (need the `manage-avios` app session, which the cookie/SSO login
95
+ can't yet establish — these currently return "session expired"):
96
+
97
+ ```bash
98
+ avios transactions --limit 20
99
+ avios pending
100
+ avios overview
101
+ ```
98
102
 
99
103
  ## TUI
100
104
 
@@ -3,4 +3,4 @@
3
3
  View your Avios balance, transactions and account details from the terminal.
4
4
  """
5
5
 
6
- __version__ = "0.1.5"
6
+ __version__ = "0.1.6"
@@ -149,7 +149,9 @@ def _wait_for_auth(ctx: Any, page: Any, base_url: str, timeout_ms: int) -> bool:
149
149
 
150
150
  def _is_authenticated(ctx: Any, base_url: str) -> bool:
151
151
  try:
152
- return bool(ctx.request.get(f"{base_url}{endpoints.AUTH_USER}").ok)
152
+ # Use the same endpoint `balance` reads, so a successful login guarantees
153
+ # the balance command will work.
154
+ return bool(ctx.request.get(f"{base_url}{endpoints.ACCOUNTS}").ok)
153
155
  except Exception:
154
156
  return False
155
157
 
@@ -257,7 +259,7 @@ def _cookies_authenticate(settings: Settings, cookies: list[dict[str, Any]]) ->
257
259
  "cookie": header,
258
260
  },
259
261
  ) as client:
260
- return client.get(endpoints.AUTH_USER).status_code == 200
262
+ return client.get(endpoints.ACCOUNTS).status_code == 200
261
263
  except httpx.HTTPError:
262
264
  return False
263
265
 
@@ -165,8 +165,10 @@ def balance(json_out: bool = JSON_OPTION) -> None:
165
165
  def _render_balance(result: Balance) -> None:
166
166
  table = Table(show_header=False, box=None)
167
167
  table.add_row("[bold]Avios[/]", f"[bold cyan]{result.balance:,}[/]")
168
- if result.household_avios_balance is not None:
169
- table.add_row("Household", f"{result.household_avios_balance:,}")
168
+ if result.individual is not None:
169
+ table.add_row("Individual", f"{result.individual:,}")
170
+ if result.household is not None:
171
+ table.add_row("Household", f"{result.household:,}")
170
172
  console.print(table)
171
173
 
172
174
 
@@ -175,7 +177,7 @@ def transactions(
175
177
  limit: int = typer.Option(20, help="Number of transactions to show."),
176
178
  json_out: bool = JSON_OPTION,
177
179
  ) -> None:
178
- """List recent Avios transactions."""
180
+ """List recent Avios transactions (experimental; needs the manage-avios session)."""
179
181
  with _handle_errors():
180
182
  items = _client().get_transactions(limit=limit)
181
183
  records = [item.as_dict() for item in items]
@@ -187,7 +189,7 @@ def transactions(
187
189
 
188
190
  @app.command()
189
191
  def pending(json_out: bool = JSON_OPTION) -> None:
190
- """List pending Avios transactions."""
192
+ """List pending Avios transactions (experimental; needs the manage-avios session)."""
191
193
  with _handle_errors():
192
194
  items = _client().get_pending_transactions()
193
195
  records = [item.as_dict() for item in items]
@@ -197,32 +199,41 @@ def pending(json_out: bool = JSON_OPTION) -> None:
197
199
  _render_records(records, "Pending")
198
200
 
199
201
 
200
- @app.command()
201
- def accounts(json_out: bool = JSON_OPTION) -> None:
202
- """List linked loyalty accounts."""
203
- with _handle_errors():
204
- items = _client().get_accounts()
205
- records = [item.as_dict() for item in items]
206
- if json_out:
207
- _print_json(records)
208
- return
209
- _render_records(records, "Accounts")
210
-
211
-
212
202
  @app.command()
213
203
  def overview() -> None:
214
- """Show the dashboard overview (raw JSON; shape not yet finalised)."""
204
+ """Show the dashboard overview (experimental; needs the manage-avios session)."""
215
205
  with _handle_errors():
216
206
  result = _client().get_overview()
217
207
  _print_json(result.as_dict())
218
208
 
219
209
 
220
210
  @app.command()
221
- def whoami() -> None:
222
- """Show your profile (raw JSON; shape not yet finalised)."""
211
+ def whoami(json_out: bool = JSON_OPTION) -> None:
212
+ """Show your profile (name, tier, membership)."""
223
213
  with _handle_errors():
224
- result = _client().get_profile()
225
- _print_json(result.as_dict())
214
+ data = _client().get_profile().as_dict()
215
+ if json_out:
216
+ _print_json(data)
217
+ return
218
+ claims = data.get("tokenContent", {})
219
+
220
+ def claim(key: str) -> str:
221
+ return str(claims.get(f"https://avios.com/{key}", "") or "")
222
+
223
+ table = Table(show_header=False, box=None)
224
+ name = claims.get("name") or f"{claims.get('given_name', '')} {claims.get('family_name', '')}"
225
+ for label, value in (
226
+ ("Name", str(name).strip()),
227
+ ("Tier", claim("customer_tier_name")),
228
+ ("Membership", claim("membership_id")),
229
+ ("Email", str(claims.get("email", "") or "")),
230
+ ):
231
+ if value:
232
+ table.add_row(f"[bold]{label}[/]", value)
233
+ if table.row_count:
234
+ console.print(table)
235
+ else:
236
+ _print_json(data)
226
237
 
227
238
 
228
239
  @app.command()
@@ -10,7 +10,7 @@ from __future__ import annotations
10
10
  from typing import Any
11
11
 
12
12
  from avios import endpoints
13
- from avios.models import Account, Balance, Overview, Profile, Transaction
13
+ from avios.models import Balance, Overview, Profile, Transaction
14
14
  from avios.session import Session
15
15
 
16
16
 
@@ -36,18 +36,17 @@ class AviosClient:
36
36
  self.session = session or Session()
37
37
 
38
38
  def get_balance(self) -> Balance:
39
- return Balance.model_validate(self.session.get_json(endpoints.BALANCE))
39
+ # /shell/api/users/current/accounts authenticates with the SSO session and
40
+ # returns {balance, individual, household}.
41
+ return Balance.model_validate(self.session.get_json(endpoints.ACCOUNTS))
40
42
 
41
43
  def get_profile(self) -> Profile:
42
- return Profile.model_validate(self.session.get_json(endpoints.PROFILE))
44
+ # /auth-gateway/user returns the SSO user (idToken + tokenContent claims).
45
+ return Profile.model_validate(self.session.get_json(endpoints.AUTH_USER))
43
46
 
44
47
  def get_overview(self) -> Overview:
45
48
  return Overview.model_validate(self.session.get_json(endpoints.OVERVIEW))
46
49
 
47
- def get_accounts(self) -> list[Account]:
48
- payload = self.session.get_json(endpoints.ACCOUNTS)
49
- return [Account.model_validate(item) for item in _extract_list(payload)]
50
-
51
50
  def get_transactions(self, limit: int | None = None) -> list[Transaction]:
52
51
  payload = self.session.get_json(endpoints.TRANSACTIONS)
53
52
  items = _extract_list(payload)
@@ -11,7 +11,7 @@ from __future__ import annotations
11
11
 
12
12
  from typing import Any
13
13
 
14
- from pydantic import BaseModel, ConfigDict, Field
14
+ from pydantic import BaseModel, ConfigDict
15
15
 
16
16
 
17
17
  class AviosModel(BaseModel):
@@ -25,10 +25,14 @@ class AviosModel(BaseModel):
25
25
 
26
26
 
27
27
  class Balance(AviosModel):
28
- """Avios balance (from ``/en-GB/spend-avios/api/avios-balance``)."""
28
+ """Avios balance (from ``/shell/api/users/current/accounts``).
29
+
30
+ Shape: ``{"balance": N, "individual": N, "household": N}``.
31
+ """
29
32
 
30
33
  balance: int
31
- household_avios_balance: int | None = Field(default=None, alias="householdAviosBalance")
34
+ individual: int | None = None
35
+ household: int | None = None
32
36
 
33
37
 
34
38
  class Transaction(AviosModel):
@@ -61,9 +61,6 @@ class AviosApp(App[None]):
61
61
  async def _load(self) -> None:
62
62
  try:
63
63
  balance = await asyncio.to_thread(self._client.get_balance)
64
- transactions = await asyncio.to_thread(
65
- self._client.get_transactions, TRANSACTIONS_TO_SHOW
66
- )
67
64
  except (NotAuthenticated, SessionExpired) as exc:
68
65
  self._show_error(f"{exc} — run `avios login`")
69
66
  return
@@ -71,6 +68,15 @@ class AviosApp(App[None]):
71
68
  self._show_error(f"Request failed: {exc}")
72
69
  return
73
70
  self.query_one("#balance", BalanceDisplay).update_balance(balance)
71
+
72
+ # Transactions are experimental (manage-avios session); degrade gracefully
73
+ # so the balance still shows if they're unavailable.
74
+ try:
75
+ transactions = await asyncio.to_thread(
76
+ self._client.get_transactions, TRANSACTIONS_TO_SHOW
77
+ )
78
+ except (NotAuthenticated, SessionExpired, httpx.HTTPError):
79
+ transactions = []
74
80
  self._populate_transactions(transactions)
75
81
 
76
82
  def _show_error(self, message: str) -> None:
@@ -20,8 +20,8 @@ class BalanceDisplay(Static):
20
20
  text = Text()
21
21
  text.append("Avios ", style="bold")
22
22
  text.append(f"{balance.balance:,}", style="bold cyan")
23
- if balance.household_avios_balance is not None:
24
- text.append(f" Household {balance.household_avios_balance:,}", style="dim")
23
+ if balance.household is not None:
24
+ text.append(f" Household {balance.household:,}", style="dim")
25
25
  self.last_text = text.plain
26
26
  self.update(text)
27
27
 
@@ -0,0 +1 @@
1
+ {"balance": 75751, "individual": 75751, "household": 112430}
@@ -15,7 +15,7 @@ from typer.testing import CliRunner
15
15
  from avios import __version__
16
16
  from avios.auth import ImportResult, LoginError
17
17
  from avios.cli import app
18
- from avios.models import Account, Balance, Overview, Profile, Transaction
18
+ from avios.models import Balance, Overview, Profile, Transaction
19
19
  from avios.session import NotAuthenticated, SessionExpired
20
20
 
21
21
  runner = CliRunner()
@@ -28,7 +28,7 @@ class FakeClient:
28
28
  pass
29
29
 
30
30
  def get_balance(self) -> Balance:
31
- return Balance(balance=100, household_avios_balance=200)
31
+ return Balance(balance=100, individual=100, household=200)
32
32
 
33
33
  def get_transactions(self, limit: int | None = None) -> list[Transaction]:
34
34
  items = [
@@ -40,14 +40,20 @@ class FakeClient:
40
40
  def get_pending_transactions(self) -> list[Transaction]:
41
41
  return []
42
42
 
43
- def get_accounts(self) -> list[Account]:
44
- return [Account.model_validate({"programme": "BAEC", "balance": 100})]
45
-
46
43
  def get_overview(self) -> Overview:
47
44
  return Overview.model_validate({"tier": "Blue"})
48
45
 
49
46
  def get_profile(self) -> Profile:
50
- return Profile.model_validate({"firstName": "Alex"})
47
+ return Profile.model_validate(
48
+ {
49
+ "idToken": "jwt",
50
+ "tokenContent": {
51
+ "name": "Alex Choi",
52
+ "https://avios.com/customer_tier_name": "Gold",
53
+ "https://avios.com/membership_id": "BA123",
54
+ },
55
+ }
56
+ )
51
57
 
52
58
  def raw(self, path: str) -> dict[str, str]:
53
59
  return {"path": path}
@@ -81,7 +87,7 @@ def test_balance_json(fake_client: None) -> None:
81
87
  result = runner.invoke(app, ["balance", "--json"])
82
88
  assert result.exit_code == 0
83
89
  assert '"balance"' in result.stdout
84
- assert '"householdAviosBalance"' in result.stdout
90
+ assert '"household"' in result.stdout
85
91
 
86
92
 
87
93
  def test_transactions_respects_limit(fake_client: None) -> None:
@@ -97,10 +103,12 @@ def test_transactions_json(fake_client: None) -> None:
97
103
  assert '"avios"' in result.stdout
98
104
 
99
105
 
100
- def test_accounts(fake_client: None) -> None:
101
- result = runner.invoke(app, ["accounts"])
106
+ def test_whoami(fake_client: None) -> None:
107
+ result = runner.invoke(app, ["whoami"])
102
108
  assert result.exit_code == 0
103
- assert "BAEC" in result.stdout
109
+ out = " ".join(result.stdout.split())
110
+ assert "Alex Choi" in out
111
+ assert "Gold" in out
104
112
 
105
113
 
106
114
  def test_raw(fake_client: None) -> None:
@@ -34,10 +34,10 @@ def _client(settings: Settings, routes: dict[str, Any]) -> AviosClient:
34
34
 
35
35
 
36
36
  def test_get_balance(settings: Settings, load_fixture: Callable[[str], Any]) -> None:
37
- client = _client(settings, {endpoints.BALANCE: load_fixture("balance.json")})
37
+ client = _client(settings, {endpoints.ACCOUNTS: load_fixture("balance.json")})
38
38
  balance = client.get_balance()
39
39
  assert balance.balance == 75751
40
- assert balance.household_avios_balance == 75752
40
+ assert balance.household == 112430
41
41
 
42
42
 
43
43
  def test_get_transactions_from_bare_list(
@@ -64,11 +64,11 @@ def test_get_transactions_from_wrapped_object(settings: Settings) -> None:
64
64
  assert txns[0].as_dict()["avios"] == 100
65
65
 
66
66
 
67
- def test_get_accounts(settings: Settings, load_fixture: Callable[[str], Any]) -> None:
68
- client = _client(settings, {endpoints.ACCOUNTS: load_fixture("accounts.json")})
69
- accounts = client.get_accounts()
70
- assert len(accounts) == 1
71
- assert accounts[0].as_dict()["programme"] == "British Airways Executive Club"
67
+ def test_get_profile(settings: Settings) -> None:
68
+ payload = {"idToken": "jwt", "tokenContent": {"name": "Alex"}}
69
+ client = _client(settings, {endpoints.AUTH_USER: payload})
70
+ profile = client.get_profile()
71
+ assert profile.as_dict()["tokenContent"]["name"] == "Alex"
72
72
 
73
73
 
74
74
  def test_raw_normalises_leading_slash(settings: Settings) -> None:
@@ -77,7 +77,7 @@ def test_raw_normalises_leading_slash(settings: Settings) -> None:
77
77
 
78
78
 
79
79
  def test_expired_session_propagates(settings: Settings) -> None:
80
- client = _client(settings, {endpoints.BALANCE: 302})
80
+ client = _client(settings, {endpoints.ACCOUNTS: 302})
81
81
  with pytest.raises(SessionExpired):
82
82
  client.get_balance()
83
83
 
@@ -8,20 +8,22 @@ from typing import Any
8
8
  from avios.models import Balance, Transaction
9
9
 
10
10
 
11
- def test_balance_parses_confirmed_shape(load_fixture: Callable[[str], Any]) -> None:
11
+ def test_balance_parses_shell_shape(load_fixture: Callable[[str], Any]) -> None:
12
12
  balance = Balance.model_validate(load_fixture("balance.json"))
13
13
  assert balance.balance == 75751
14
- assert balance.household_avios_balance == 75752
14
+ assert balance.individual == 75751
15
+ assert balance.household == 112430
15
16
 
16
17
 
17
- def test_balance_alias_round_trips() -> None:
18
- balance = Balance(balance=100, household_avios_balance=200)
19
- assert balance.as_dict() == {"balance": 100, "householdAviosBalance": 200}
18
+ def test_balance_round_trips() -> None:
19
+ balance = Balance(balance=100, individual=100, household=200)
20
+ assert balance.as_dict() == {"balance": 100, "individual": 100, "household": 200}
20
21
 
21
22
 
22
- def test_balance_household_is_optional() -> None:
23
+ def test_balance_extras_are_optional() -> None:
23
24
  balance = Balance.model_validate({"balance": 42})
24
- assert balance.household_avios_balance is None
25
+ assert balance.individual is None
26
+ assert balance.household is None
25
27
 
26
28
 
27
29
  def test_extra_fields_are_preserved() -> None:
@@ -15,7 +15,7 @@ from avios.tui.widgets import BalanceDisplay
15
15
 
16
16
  class FakeClient:
17
17
  def get_balance(self) -> Balance:
18
- return Balance(balance=100, household_avios_balance=200)
18
+ return Balance(balance=100, individual=100, household=200)
19
19
 
20
20
  def get_transactions(self, limit: int | None = None) -> list[Transaction]:
21
21
  return [
@@ -1,3 +0,0 @@
1
- [
2
- {"programme": "British Airways Executive Club", "balance": 75751}
3
- ]
@@ -1 +0,0 @@
1
- {"balance": 75751, "householdAviosBalance": 75752}
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