feat(prices): Rust Tauri command fetch_price + tests #155

Closed
opened 2026-04-27 00:15:35 +00:00 by maximus · 0 comments
Owner

Goal

Implement the Rust Tauri command fetch_price(symbol, date) -> Result<PriceResponse, FetchPriceError> in src-tauri/src/commands/balance_commands.rs and rename SIMPL_API_URLMAXIMUS_API_URL everywhere in src-tauri/.

Contract reference

docs/api-contract-prices.md §2 (endpoint), §3 (headers), §4 (success), §5 (errors), §12.1 (tests).

Fichiers concernés

  • src-tauri/Cargo.toml (add mockito = "1.6" to [dev-dependencies])
  • src-tauri/src/commands/balance_commands.rs (add fetch_price function + FetchPriceError enum + PriceResponse struct)
  • src-tauri/src/commands/license_commands.rs (rename SIMPL_API_URLMAXIMUS_API_URL, lines 300-301)
  • src-tauri/src/lib.rs (register fetch_price in invoke_handler)
  • src-tauri/src/commands/balance_commands.rs (new #[cfg(test)] mod tests section)

Depends on

Scope

  • Add to Cargo.toml [dev-dependencies] : mockito = "1.6"
  • Rename SIMPL_API_URLMAXIMUS_API_URL in license_commands.rs:300-301 (default URL https://api.lacompagniemaximus.com unchanged)
  • In balance_commands.rs, define types:
    • pub struct PriceResponse { symbol, date, actual_date: Option<String>, price: f64, currency, source, fetched_at, cached: bool }
    • pub enum FetchPriceError { Auth, PremiumRequired, SymbolNotFound, RateLimit { retry_after_s: u64 }, ProviderUnavailable, Network, Internal } (impl Display)
  • Implement pub async fn fetch_price(symbol: String, date: String) -> Result<PriceResponse, String> :
    • Read activation_token via existing activation_path accessors (DO NOT duplicate token loading — reuse license_commands::read_activation_token or equivalent helper)
    • reqwest::Client::builder().user_agent("simpl-resultat").build().map_err(|e| e.to_string())?
    • URL = format!("{}/v1/prices?symbol={}&date={}", base_url(), symbol, date) where base_url() reads MAXIMUS_API_URL with default
    • Request headers: ONLY Authorization: Bearer <token> + Accept: application/json (the UA is set on the client builder)
    • Map status + JSON error.code to typed FetchPriceError. Serialize the enum to a stable string for the JS layer (it does the i18n mapping)
  • Register in lib.rs::run() invoke_handler
  • Tests in balance_commands.rs mod tests (use mockito::Server::new_async()):
    • it_returns_price_on_200
    • it_returns_auth_error_on_401
    • it_returns_premium_required_on_403
    • it_returns_symbol_not_found_on_404
    • it_parses_retry_after_on_429 (from JSON body error.retry_after)
    • it_returns_provider_unavailable_on_502
    • it_sends_only_authorization_accept_user_agent — privacy headers test (use mock.match_header(...) or interceptor to assert allowed list exactly)

Critères d'acceptation

  • cargo test fetch_price green (≥ 7 tests)
  • cargo clippy --all-targets -- -D warnings clean
  • Privacy headers test asserts sent headers ⊆ {Authorization, Accept, User-Agent, Host} (no Accept-Language, no cookies, no X-*)
  • MAXIMUS_API_URL referenced in 2 files (license_commands.rs + balance_commands.rs), no remaining SIMPL_API_URL in src-tauri/
  • Command invokable from JS layer (vérification manuelle suffit)

Décisions prises ce soir

  • Env var renommée en MAXIMUS_API_URL (anciennement SIMPL_API_URL) — cohérence avec le nom du repo serveur. Migration incluse dans cette issue.
  • mockito version pinnée à 1.6 (stable actuelle, API moderne).
  • FetchPriceError reste côté Rust ; le mapping vers les clés i18n se fait côté TS (issue #156).

Spec source

docs/api-contract-prices.md

## Goal Implement the Rust Tauri command `fetch_price(symbol, date) -> Result<PriceResponse, FetchPriceError>` in `src-tauri/src/commands/balance_commands.rs` and rename `SIMPL_API_URL` → `MAXIMUS_API_URL` everywhere in `src-tauri/`. ## Contract reference `docs/api-contract-prices.md` §2 (endpoint), §3 (headers), §4 (success), §5 (errors), §12.1 (tests). ## Fichiers concernés - `src-tauri/Cargo.toml` (add `mockito = "1.6"` to `[dev-dependencies]`) - `src-tauri/src/commands/balance_commands.rs` (add `fetch_price` function + `FetchPriceError` enum + `PriceResponse` struct) - `src-tauri/src/commands/license_commands.rs` (rename `SIMPL_API_URL` → `MAXIMUS_API_URL`, lines 300-301) - `src-tauri/src/lib.rs` (register `fetch_price` in `invoke_handler`) - `src-tauri/src/commands/balance_commands.rs` (new `#[cfg(test)] mod tests` section) ## Depends on - #154 ## Scope - [ ] Add to `Cargo.toml` `[dev-dependencies]` : `mockito = "1.6"` - [ ] Rename `SIMPL_API_URL` → `MAXIMUS_API_URL` in `license_commands.rs:300-301` (default URL `https://api.lacompagniemaximus.com` unchanged) - [ ] In `balance_commands.rs`, define types: - `pub struct PriceResponse { symbol, date, actual_date: Option<String>, price: f64, currency, source, fetched_at, cached: bool }` - `pub enum FetchPriceError { Auth, PremiumRequired, SymbolNotFound, RateLimit { retry_after_s: u64 }, ProviderUnavailable, Network, Internal }` (impl Display) - [ ] Implement `pub async fn fetch_price(symbol: String, date: String) -> Result<PriceResponse, String>` : - Read `activation_token` via existing `activation_path` accessors (DO NOT duplicate token loading — reuse `license_commands::read_activation_token` or equivalent helper) - `reqwest::Client::builder().user_agent("simpl-resultat").build().map_err(|e| e.to_string())?` - URL = `format!("{}/v1/prices?symbol={}&date={}", base_url(), symbol, date)` where `base_url()` reads `MAXIMUS_API_URL` with default - Request headers: ONLY `Authorization: Bearer <token>` + `Accept: application/json` (the UA is set on the client builder) - Map status + JSON `error.code` to typed `FetchPriceError`. Serialize the enum to a stable string for the JS layer (it does the i18n mapping) - [ ] Register in `lib.rs::run()` invoke_handler - [ ] Tests in `balance_commands.rs` `mod tests` (use `mockito::Server::new_async()`): - `it_returns_price_on_200` - `it_returns_auth_error_on_401` - `it_returns_premium_required_on_403` - `it_returns_symbol_not_found_on_404` - `it_parses_retry_after_on_429` (from JSON body `error.retry_after`) - `it_returns_provider_unavailable_on_502` - `it_sends_only_authorization_accept_user_agent` — privacy headers test (use `mock.match_header(...)` or interceptor to assert allowed list exactly) ## Critères d'acceptation - [ ] `cargo test fetch_price` green (≥ 7 tests) - [ ] `cargo clippy --all-targets -- -D warnings` clean - [ ] Privacy headers test asserts sent headers ⊆ `{Authorization, Accept, User-Agent, Host}` (no `Accept-Language`, no cookies, no `X-*`) - [ ] `MAXIMUS_API_URL` referenced in 2 files (license_commands.rs + balance_commands.rs), no remaining `SIMPL_API_URL` in `src-tauri/` - [ ] Command invokable from JS layer (vérification manuelle suffit) ## Décisions prises ce soir - Env var renommée en `MAXIMUS_API_URL` (anciennement `SIMPL_API_URL`) — cohérence avec le nom du repo serveur. Migration incluse dans cette issue. - `mockito` version pinnée à `1.6` (stable actuelle, API moderne). - `FetchPriceError` reste côté Rust ; le mapping vers les clés i18n se fait côté TS (issue #156). ## Spec source `docs/api-contract-prices.md`
maximus added this to the spec-price-fetching milestone 2026-04-27 00:15:35 +00:00
maximus added the
status:ready
type:feature
source:human
labels 2026-04-27 00:15:35 +00:00
maximus modified the milestone from spec-price-fetching to overnight-2026-04-27-prices 2026-04-27 00:32:01 +00:00
maximus added
status:in-progress
and removed
source:human
status:ready
labels 2026-04-27 12:23:49 +00:00
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: maximus/Simpl-Resultat#155
No description provided.