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.
- {avios_cli-0.1.5 → avios_cli-0.1.6}/CHANGELOG.md +22 -1
- {avios_cli-0.1.5 → avios_cli-0.1.6}/PKG-INFO +14 -10
- {avios_cli-0.1.5 → avios_cli-0.1.6}/README.md +13 -9
- {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/__init__.py +1 -1
- {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/auth.py +4 -2
- {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/cli.py +32 -21
- {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/client.py +6 -7
- {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/models.py +7 -3
- {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/tui/app.py +9 -3
- {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/tui/widgets.py +2 -2
- avios_cli-0.1.6/tests/fixtures/balance.json +1 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/tests/test_cli.py +18 -10
- {avios_cli-0.1.5 → avios_cli-0.1.6}/tests/test_client.py +8 -8
- {avios_cli-0.1.5 → avios_cli-0.1.6}/tests/test_models.py +9 -7
- {avios_cli-0.1.5 → avios_cli-0.1.6}/tests/test_tui.py +1 -1
- avios_cli-0.1.5/tests/fixtures/accounts.json +0 -3
- avios_cli-0.1.5/tests/fixtures/balance.json +0 -1
- {avios_cli-0.1.5 → avios_cli-0.1.6}/.editorconfig +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/.github/dependabot.yml +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/.github/workflows/ci.yml +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/.github/workflows/publish.yml +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/.gitignore +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/.pre-commit-config.yaml +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/.python-version +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/CONTRIBUTING.md +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/LICENSE +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/SECURITY.md +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/docs/dashboard.svg +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/pyproject.toml +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/scripts/screenshot.py +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/config.py +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/endpoints.py +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/session.py +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/tui/__init__.py +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/tui/art.py +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/src/avios/tui/styles.tcss +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/tests/conftest.py +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/tests/fixtures/transactions.json +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/tests/test_auth.py +0 -0
- {avios_cli-0.1.5 → avios_cli-0.1.6}/tests/test_session.py +0 -0
- {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.
|
|
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.
|
|
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 #
|
|
123
|
-
avios
|
|
124
|
-
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
|
|
132
|
-
|
|
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 #
|
|
88
|
-
avios
|
|
89
|
-
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
|
|
97
|
-
|
|
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
|
|
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
169
|
-
table.add_row("
|
|
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 (
|
|
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 (
|
|
211
|
+
def whoami(json_out: bool = JSON_OPTION) -> None:
|
|
212
|
+
"""Show your profile (name, tier, membership)."""
|
|
223
213
|
with _handle_errors():
|
|
224
|
-
|
|
225
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 ``/
|
|
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
|
-
|
|
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.
|
|
24
|
-
text.append(f" Household {balance.
|
|
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
|
|
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,
|
|
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(
|
|
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 '"
|
|
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
|
|
101
|
-
result = runner.invoke(app, ["
|
|
106
|
+
def test_whoami(fake_client: None) -> None:
|
|
107
|
+
result = runner.invoke(app, ["whoami"])
|
|
102
108
|
assert result.exit_code == 0
|
|
103
|
-
|
|
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.
|
|
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.
|
|
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
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
assert
|
|
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.
|
|
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
|
|
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.
|
|
14
|
+
assert balance.individual == 75751
|
|
15
|
+
assert balance.household == 112430
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
def
|
|
18
|
-
balance = Balance(balance=100,
|
|
19
|
-
assert balance.as_dict() == {"balance": 100, "
|
|
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
|
|
23
|
+
def test_balance_extras_are_optional() -> None:
|
|
23
24
|
balance = Balance.model_validate({"balance": 42})
|
|
24
|
-
assert balance.
|
|
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,
|
|
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 +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
|
|
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
|