makea-cli 0.2.14__tar.gz → 0.2.15__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 (69) hide show
  1. {makea_cli-0.2.14 → makea_cli-0.2.15}/CHANGELOG.md +28 -0
  2. {makea_cli-0.2.14 → makea_cli-0.2.15}/PKG-INFO +2 -2
  3. {makea_cli-0.2.14 → makea_cli-0.2.15}/README.md +1 -1
  4. makea_cli-0.2.15/makea_cli/__init__.py +1 -0
  5. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/api_client.py +12 -0
  6. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/metrics/cmd.py +27 -65
  7. {makea_cli-0.2.14 → makea_cli-0.2.15}/pyproject.toml +1 -1
  8. {makea_cli-0.2.14 → makea_cli-0.2.15}/tests/golden_manifest.json +3 -3
  9. makea_cli-0.2.15/tests/test_currency_conversion.py +106 -0
  10. makea_cli-0.2.14/makea_cli/__init__.py +0 -1
  11. {makea_cli-0.2.14 → makea_cli-0.2.15}/.claude/skills/create-supplier-profile/SKILL.md +0 -0
  12. {makea_cli-0.2.14 → makea_cli-0.2.15}/.claude/skills/lark-cli-setup/SKILL.md +0 -0
  13. {makea_cli-0.2.14 → makea_cli-0.2.15}/.claude/skills/link-supplier-user/SKILL.md +0 -0
  14. {makea_cli-0.2.14 → makea_cli-0.2.15}/.claude/skills/makea-cli-setup/SKILL.md +0 -0
  15. {makea_cli-0.2.14 → makea_cli-0.2.15}/.claude/skills/payment-revenue-report/SKILL.md +0 -0
  16. {makea_cli-0.2.14 → makea_cli-0.2.15}/.claude/skills/supplier-user-id-migration/SKILL.md +0 -0
  17. {makea_cli-0.2.14 → makea_cli-0.2.15}/.gitignore +0 -0
  18. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea-collection-upload/SKILL.md +0 -0
  19. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea-collection-upload/scripts/create_products.py +0 -0
  20. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/__main__.py +0 -0
  21. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/auth_pkce.py +0 -0
  22. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/broker.py +0 -0
  23. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/__init__.py +0 -0
  24. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/_util.py +0 -0
  25. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/analytics/__init__.py +0 -0
  26. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/analytics/cmd.py +0 -0
  27. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/auth/__init__.py +0 -0
  28. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/auth/cmd.py +0 -0
  29. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/brand/__init__.py +0 -0
  30. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/brand/cmd.py +0 -0
  31. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/designer/__init__.py +0 -0
  32. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/designer/cmd.py +0 -0
  33. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/directory/__init__.py +0 -0
  34. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/directory/cmd.py +0 -0
  35. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/document/__init__.py +0 -0
  36. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/document/cmd.py +0 -0
  37. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/email/__init__.py +0 -0
  38. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/email/cmd.py +0 -0
  39. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/factory/__init__.py +0 -0
  40. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/factory/cmd.py +0 -0
  41. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/library/__init__.py +0 -0
  42. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/library/cmd.py +0 -0
  43. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/metrics/__init__.py +0 -0
  44. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/orders/__init__.py +0 -0
  45. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/orders/cmd.py +0 -0
  46. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/product/__init__.py +0 -0
  47. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/product/cmd.py +0 -0
  48. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/production/__init__.py +0 -0
  49. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/production/cmd.py +0 -0
  50. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/production/pricing_spec.py +0 -0
  51. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/supplier/__init__.py +0 -0
  52. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/commands/supplier/cmd.py +0 -0
  53. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/config.py +0 -0
  54. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/errors.py +0 -0
  55. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/identity.py +0 -0
  56. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/main.py +0 -0
  57. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/output.py +0 -0
  58. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/posthog_client.py +0 -0
  59. {makea_cli-0.2.14 → makea_cli-0.2.15}/makea_cli/registry.py +0 -0
  60. {makea_cli-0.2.14 → makea_cli-0.2.15}/npm/makea-cli/README.md +0 -0
  61. {makea_cli-0.2.14 → makea_cli-0.2.15}/npm/makea-cli/makea-cli.mjs +0 -0
  62. {makea_cli-0.2.14 → makea_cli-0.2.15}/npm/makea-cli/package.json +0 -0
  63. {makea_cli-0.2.14 → makea_cli-0.2.15}/tests/test_broker.py +0 -0
  64. {makea_cli-0.2.14 → makea_cli-0.2.15}/tests/test_output_contract.py +0 -0
  65. {makea_cli-0.2.14 → makea_cli-0.2.15}/tests/test_persona_commands.py +0 -0
  66. {makea_cli-0.2.14 → makea_cli-0.2.15}/tests/test_pm_reads.py +0 -0
  67. {makea_cli-0.2.14 → makea_cli-0.2.15}/tests/test_registry.py +0 -0
  68. {makea_cli-0.2.14 → makea_cli-0.2.15}/tests/test_supplier_scorecard.py +0 -0
  69. {makea_cli-0.2.14 → makea_cli-0.2.15}/uv.lock +0 -0
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.15
4
+
5
+ **Behaviour change: `admin get currency-conversion` returns different
6
+ numbers.** This is not only a bug fix. Anyone who reconciled figures with
7
+ this command should expect them to move, by up to several percent per
8
+ currency. The same call on 2026-10-16:
9
+
10
+ | GBP per | 0.2.14 | 0.2.15 | 1,000 converted, 0.2.14 → 0.2.15 |
11
+ |---|---|---|---|
12
+ | CNY | 0.12 | 0.113 | 120.00 → 113.00 (−5.8%) |
13
+ | USD | 0.76 | 0.752 | 760.00 → 752.00 |
14
+ | EUR | 0.85 | 0.848 | 850.00 → 848.00 |
15
+
16
+ What changed:
17
+
18
+ - Rates now come from the platform (`GET /admin/fx/rates`, backend
19
+ 2026-10-16) instead of the CLI fetching the free FX source itself. They are
20
+ rounded **up to 3 decimals**, as the platform rounds them; 0.2.14 rounded
21
+ up to 2, which overstated cheap currencies such as CNY most.
22
+ - With one `--from` and `--amount`, `converted` is the platform's own
23
+ conversion, rounded **up** to the cent; 0.2.14 rounded to the nearest cent.
24
+ - So the numbers now match the platform's prices and revenue metrics, which
25
+ the 0.2.14 ones did not.
26
+ - Output keys are unchanged except `rounding` (now `"up"`, was
27
+ `"ceil_2dp"`), plus new `rate_decimals` and `unavailable`. A currency with
28
+ no rate still fails the command. Needs an admin login, like every `admin`
29
+ command.
30
+
3
31
  ## 0.2.14
4
32
 
5
33
  - `admin update tracking-status`: help text no longer describes it as "the
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: makea-cli
3
- Version: 0.2.14
3
+ Version: 0.2.15
4
4
  Summary: Makea admin CLI (browser login + Makea admin HTTP API)
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: click<9,>=8.1
@@ -100,7 +100,7 @@ exit code:`0` 成功 · `2` 用法错 · `3` 未登录或身份与 persona 不
100
100
  | `makea-cli admin generate pricing-groups` | write(ask) | `POST /admin/production_orders/{id}/details/update` | Lay a production order's variants into pricing groups along one axis (--by style \| colour \| variant \| all) with th… | `production-groups-generate` |
101
101
  | `makea-cli admin get active-users` | read | `POST /admin/analytics/query` | Distinct active users per day over the last N days (DAU series). | `analytics-active-users` |
102
102
  | `makea-cli admin get analytics-query` | read | `POST /admin/analytics/query` | Run an arbitrary HogQL query (escape hatch). Example: analytics-query "SELECT count() FROM events" | `analytics-query` |
103
- | `makea-cli admin get currency-conversion` | read | `currency-api` | avoids repeated lookups. | `convert-currency` |
103
+ | `makea-cli admin get currency-conversion` | read | `GET /admin/fx/rates` | the platform's own exchange rates and rounding (rate table for several --from; one --from with --amount converts it). | `convert-currency` |
104
104
  | `makea-cli admin get feature-usage` | read | `POST /admin/analytics/query` | How many distinct users used a feature recently, plus event volume. Matches $pageview path and feature_time_spent eve… | `analytics-feature-usage` |
105
105
  | `makea-cli admin get library-schema` | read | `GET /admin/library_components/schema` | the tech-pack library taxonomy: sections and their library-role field keys (run this before create/update). | `library-schema` |
106
106
  | `makea-cli admin get production-pricing` | read | `GET /admin/production_orders/{id}/details` | show a production order's price tiers, MOQ, sizes/colours, pricing groups and cart coverage. | `production-pricing-show` |
@@ -88,7 +88,7 @@ exit code:`0` 成功 · `2` 用法错 · `3` 未登录或身份与 persona 不
88
88
  | `makea-cli admin generate pricing-groups` | write(ask) | `POST /admin/production_orders/{id}/details/update` | Lay a production order's variants into pricing groups along one axis (--by style \| colour \| variant \| all) with th… | `production-groups-generate` |
89
89
  | `makea-cli admin get active-users` | read | `POST /admin/analytics/query` | Distinct active users per day over the last N days (DAU series). | `analytics-active-users` |
90
90
  | `makea-cli admin get analytics-query` | read | `POST /admin/analytics/query` | Run an arbitrary HogQL query (escape hatch). Example: analytics-query "SELECT count() FROM events" | `analytics-query` |
91
- | `makea-cli admin get currency-conversion` | read | `currency-api` | avoids repeated lookups. | `convert-currency` |
91
+ | `makea-cli admin get currency-conversion` | read | `GET /admin/fx/rates` | the platform's own exchange rates and rounding (rate table for several --from; one --from with --amount converts it). | `convert-currency` |
92
92
  | `makea-cli admin get feature-usage` | read | `POST /admin/analytics/query` | How many distinct users used a feature recently, plus event volume. Matches $pageview path and feature_time_spent eve… | `analytics-feature-usage` |
93
93
  | `makea-cli admin get library-schema` | read | `GET /admin/library_components/schema` | the tech-pack library taxonomy: sections and their library-role field keys (run this before create/update). | `library-schema` |
94
94
  | `makea-cli admin get production-pricing` | read | `GET /admin/production_orders/{id}/details` | show a production order's price tiers, MOQ, sizes/colours, pricing groups and cart coverage. | `production-pricing-show` |
@@ -0,0 +1 @@
1
+ __version__ = "0.2.15"
@@ -844,6 +844,18 @@ def get_cumulative_payment_and_platform_revenue(
844
844
  )
845
845
 
846
846
 
847
+ def get_fx_rates(*, to: str, sources: list[str], amount: float | None = None) -> Any:
848
+ """
849
+ GET /admin/fx/rates — the platform's own exchange rates (rounded up to its
850
+ RATE_DECIMALS), and with one source and an amount, that amount converted the
851
+ way the platform converts it.
852
+ """
853
+ params: dict[str, Any] = {"to": to, "from": ",".join(sources)}
854
+ if amount is not None:
855
+ params["amount"] = amount
856
+ return get_json("/admin/fx/rates", params=params)
857
+
858
+
847
859
  def extract_ai_tech_specifications(product_reference_id: str, *, timeout: float = 300.0) -> Any:
848
860
  """
849
861
  GET /admin/products/{id}/extract-tech-specifications — synchronous AI extraction;
@@ -1,8 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import math
4
-
5
- import httpx
6
3
  import typer
7
4
  from rich.console import Console
8
5
 
@@ -16,38 +13,6 @@ console = Console()
16
13
  _GRANULARITIES = ("day", "week", "month")
17
14
  _ORDER_TYPES = ("all", "sampling", "production")
18
15
 
19
- # Same free FX source the backend uses (FreeCurrencyService), so converted
20
- # amounts match the platform's revenue metrics.
21
- _FX_PRIMARY = (
22
- "https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/{base}.json"
23
- )
24
- _FX_FALLBACK = "https://latest.currency-api.pages.dev/v1/currencies/{base}.json"
25
-
26
-
27
- def _fx_rate(base: str, target: str) -> float:
28
- """Exchange rate base -> target, rounded UP to 2dp exactly like the backend
29
- (math.ceil(raw * 100) / 100) so totals reconcile with revenue metrics."""
30
- base, target = base.lower(), target.lower()
31
- if base == target:
32
- return 1.0
33
- last_err: Exception | None = None
34
- for url in (_FX_PRIMARY.format(base=base), _FX_FALLBACK.format(base=base)):
35
- try:
36
- with httpx.Client(timeout=10) as client:
37
- resp = client.get(url)
38
- resp.raise_for_status()
39
- data = resp.json()
40
- raw = data.get(base, {}).get(target)
41
- if raw is not None:
42
- return math.ceil(float(raw) * 100) / 100
43
- except Exception as e: # noqa: BLE001 - try fallback URL
44
- last_err = e
45
- continue
46
- raise RuntimeError(
47
- f"FX rate {base.upper()}->{target.upper()} unavailable"
48
- + (f": {last_err}" if last_err else "")
49
- )
50
-
51
16
 
52
17
  def register(app: typer.Typer) -> None:
53
18
  @command(
@@ -141,13 +106,13 @@ def register(app: typer.Typer) -> None:
141
106
  "get",
142
107
  "currency-conversion",
143
108
  legacy="convert-currency",
144
- api="GET currency-api",
145
- external="currency-api",
109
+ api="GET /admin/fx/rates",
146
110
  help=(
147
- "Convert money between currencies using the same free FX source and "
148
- "rounding as the platform's revenue metrics. Pass several source "
149
- "currencies via --from (comma-separated) to get a full rate table in "
150
- "one call, then convert locally — avoids repeated lookups."
111
+ "GET /admin/fx/rates — convert money between currencies at the "
112
+ "platform's own rates and rounding, so totals reconcile with its "
113
+ "prices and revenue metrics. Pass several source currencies via --from "
114
+ "(comma-separated) to get a full rate table in one call, then convert "
115
+ "locally — avoids repeated lookups."
151
116
  ),
152
117
  )
153
118
  def convert_currency(
@@ -165,17 +130,20 @@ def register(app: typer.Typer) -> None:
165
130
  help="Optional amount to convert (only meaningful with a single --from).",
166
131
  ),
167
132
  ) -> None:
168
- """Return source->target rates (rounded like the backend), and optionally a
169
- converted amount.
133
+ """Return source->target rates, and optionally a converted amount, as the
134
+ platform computes them.
170
135
 
171
136
  Use it to collapse a mixed-currency set into one currency: collect the
172
137
  distinct currencies, call once with --from a,b,c to get every rate, then
173
- multiply locally. Rates use math.ceil(raw * 100) / 100, matching the
174
- platform's FreeCurrencyService, so totals reconcile with revenue metrics.
175
-
176
- Output JSON: {"to", "rates": {CUR: rate, ...}, "rounding", "source"} plus
177
- {"from", "rate", "amount", "converted"} when a single --from and --amount
178
- are given.
138
+ multiply locally. The rates are the platform's (FreeCurrencyService,
139
+ rounded up to ``rate_decimals``) and come from the backend rather than
140
+ being fetched and rounded here, so they cannot drift from it.
141
+
142
+ Output JSON: {"to", "rates": {CUR: rate, ...}, "rate_decimals",
143
+ "rounding", "source", "unavailable"} plus {"from", "rate", "amount",
144
+ "converted"} when a single --from and --amount are given; "converted" is
145
+ the platform's own conversion, rounding included. A currency with no
146
+ rate fails the command rather than coming back null.
179
147
  """
180
148
  sources = [s.strip() for s in from_.split(",") if s.strip()]
181
149
  if not sources:
@@ -183,22 +151,16 @@ def register(app: typer.Typer) -> None:
183
151
  raise typer.Exit(1)
184
152
 
185
153
  def _do() -> dict:
186
- rates = {s.upper(): _fx_rate(s, to) for s in sources}
187
- out: dict = {
188
- "to": to.upper(),
189
- "rates": rates,
190
- "rounding": "ceil_2dp",
191
- "source": "fawazahmed0/currency-api",
192
- }
193
- if amount is not None and len(sources) == 1:
194
- rate = rates[sources[0].upper()]
195
- out.update(
196
- {
197
- "from": sources[0].upper(),
198
- "rate": rate,
199
- "amount": amount,
200
- "converted": round(amount * rate, 2),
201
- }
154
+ payload = api_client.get_fx_rates(
155
+ to=to,
156
+ sources=sources,
157
+ amount=amount if len(sources) == 1 else None,
158
+ )
159
+ out = (payload or {}).get("result") or {}
160
+ if out.get("unavailable"):
161
+ raise RuntimeError(
162
+ "FX rate unavailable: "
163
+ + ", ".join(f"{s}->{to.upper()}" for s in out["unavailable"])
202
164
  )
203
165
  return out
204
166
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "makea-cli"
7
- version = "0.2.14"
7
+ version = "0.2.15"
8
8
  description = "Makea admin CLI (browser login + Makea admin HTTP API)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -980,8 +980,8 @@
980
980
  "verb": "get",
981
981
  "noun": "currency-conversion",
982
982
  "access": "read",
983
- "api": "GET currency-api",
984
- "external": "currency-api",
983
+ "api": "GET /admin/fx/rates",
984
+ "external": null,
985
985
  "side_effects": [],
986
986
  "approval": "auto",
987
987
  "reversible": null,
@@ -990,7 +990,7 @@
990
990
  "legacy": [
991
991
  "convert-currency"
992
992
  ],
993
- "help": "Convert money between currencies using the same free FX source and rounding as the platform's revenue metrics. Pass several source currencies via --from (comma-separated) to get a full rate table in one call, then convert locally — avoids repeated lookups.",
993
+ "help": "GET /admin/fx/rates — convert money between currencies at the platform's own rates and rounding, so totals reconcile with its prices and revenue metrics. Pass several source currencies via --from (comma-separated) to get a full rate table in one call, then convert locally — avoids repeated lookups.",
994
994
  "function": "makea_cli.commands.metrics.cmd.register.<locals>.convert_currency",
995
995
  "flags": [
996
996
  {
@@ -0,0 +1,106 @@
1
+ """`admin get currency-conversion` reads the platform's rates from the backend.
2
+
3
+ It used to fetch the free FX source itself and round rates to 2 decimals and
4
+ amounts to the nearest cent, while the platform rounds rates up to 3 decimals
5
+ and amounts up to the cent -- so its totals drifted from the platform's. It
6
+ now asks GET /admin/fx/rates and returns what that says.
7
+
8
+ Run with ``.venv/bin/python -m unittest tests.test_currency_conversion``.
9
+ """
10
+
11
+ import json
12
+ import unittest
13
+ from unittest import mock
14
+
15
+ import httpx
16
+ from click.testing import CliRunner
17
+
18
+ from makea_cli import api_client, identity, output
19
+ from makea_cli.main import build_cli
20
+
21
+ RESULT = {
22
+ "to": "GBP",
23
+ "rates": {"USD": 0.752, "CNY": 0.113},
24
+ "unavailable": [],
25
+ "rate_decimals": 3,
26
+ "rounding": "up",
27
+ "source": "platform (FreeCurrencyService)",
28
+ }
29
+
30
+
31
+ class ApiClientTest(unittest.TestCase):
32
+ def test_rates(self):
33
+ with mock.patch.object(api_client, "get_json", return_value={}) as get:
34
+ api_client.get_fx_rates(to="gbp", sources=["usd", "cny"])
35
+ get.assert_called_once_with("/admin/fx/rates", params={"to": "gbp", "from": "usd,cny"})
36
+
37
+ def test_amount(self):
38
+ with mock.patch.object(api_client, "get_json", return_value={}) as get:
39
+ api_client.get_fx_rates(to="gbp", sources=["cny"], amount=250)
40
+ get.assert_called_once_with(
41
+ "/admin/fx/rates", params={"to": "gbp", "from": "cny", "amount": 250}
42
+ )
43
+
44
+
45
+ class CommandTest(unittest.TestCase):
46
+ def setUp(self):
47
+ self.cli = build_cli()
48
+ self.runner = CliRunner()
49
+ output.state.force_json = True
50
+
51
+ def tearDown(self):
52
+ output.state.force_json = False
53
+
54
+ def _run(self, args, payload):
55
+ with (
56
+ mock.patch.object(identity, "current_persona", return_value="admin"),
57
+ mock.patch.object(api_client, "get_fx_rates", return_value=payload) as get,
58
+ # Nothing is fetched from an FX source directly any more.
59
+ mock.patch.object(httpx, "Client", side_effect=AssertionError("no network")),
60
+ ):
61
+ result = self.runner.invoke(self.cli, ["admin", "get", "currency-conversion", *args])
62
+ return result, get
63
+
64
+ def test_rate_table_is_the_platforms(self):
65
+ result, get = self._run(
66
+ ["--to", "gbp", "--from", "usd,cny"], {"success": True, "result": RESULT}
67
+ )
68
+ self.assertEqual(result.exit_code, 0, result.output)
69
+ get.assert_called_once_with(to="gbp", sources=["usd", "cny"], amount=None)
70
+ self.assertEqual(json.loads(result.output), RESULT)
71
+
72
+ def test_single_amount_is_converted_by_the_platform(self):
73
+ converted = {
74
+ **RESULT,
75
+ "rates": {"CNY": 0.113},
76
+ "from": "CNY",
77
+ "rate": 0.113,
78
+ "amount": 250.0,
79
+ "converted": 28.25,
80
+ }
81
+ result, get = self._run(
82
+ ["--to", "gbp", "--from", "cny", "--amount", "250"],
83
+ {"success": True, "result": converted},
84
+ )
85
+ self.assertEqual(result.exit_code, 0, result.output)
86
+ get.assert_called_once_with(to="gbp", sources=["cny"], amount=250.0)
87
+ self.assertEqual(json.loads(result.output)["converted"], 28.25)
88
+
89
+ def test_amount_with_several_currencies_is_not_sent(self):
90
+ _, get = self._run(
91
+ ["--to", "gbp", "--from", "usd,cny", "--amount", "250"],
92
+ {"success": True, "result": RESULT},
93
+ )
94
+ get.assert_called_once_with(to="gbp", sources=["usd", "cny"], amount=None)
95
+
96
+ def test_a_missing_rate_fails_rather_than_returning_null(self):
97
+ missing = {**RESULT, "rates": {"XYZ": None}, "unavailable": ["XYZ"]}
98
+ result, _ = self._run(
99
+ ["--to", "gbp", "--from", "xyz"], {"success": True, "result": missing}
100
+ )
101
+ self.assertEqual(result.exit_code, 1, result.output)
102
+ self.assertIn("XYZ->GBP", result.output)
103
+
104
+
105
+ if __name__ == "__main__":
106
+ unittest.main()
@@ -1 +0,0 @@
1
- __version__ = "0.2.14"
File without changes
File without changes
File without changes