storebound/apps/desktop/src-tauri/tests/confirm.rs
CRT 61a1e832a0 Desktop app shell (apps/desktop): Tauri 2 + React 19 around the storebound sidecar
- Rust host: spawns storebound serve --stdio with fixed args, hands over a per-session secret,
  mints confirm tokens only after a native dialog showing the summary and the dry run
- every app command re-checks window label and page origin; one capability (main window, app
  commands + event listen only, no plugin API in the webview); strict CSP; tests/security.rs pins it
- store keys picked in native dialogs, kept in the OS keychain, passed per call in memory only
- plugins: shell (Rust only), single-instance, deep-link storebound://, window-state, opener,
  dialog, notification, updater (placeholder endpoint and key until signing), process
- app-centric UI: Portfolio, App (Releases, Catalog, Listings, Health, Reviews, TestFlight),
  App folders, Accounts, Dependencies, Settings; light / dark with the storebound.dev tokens,
  reduced motion, home folder shown as ~
- macOS menu: Cmd+Q quits, Edit menu, Window menu keeps the main window; Linux DMABUF fix
- Bun workspace (npm files unchanged), ci runs desktop typecheck, build, cargo test and clippy;
  scrub covers home paths; docs/desktop.md (protocol, safety, dependency sources and licenses)
2026-10-11 01:57:33 +08:00

43 lines
2.1 KiB
Rust

//! The host mints exactly the tokens the sidecar verifies: same canonical JSON, same HMAC, same shape as
//! cli/lib/confirm-token.ts. The vector is shared with tests/serve.test.ts in the repository root.
use serde_json::json;
use storebound_desktop::confirm::*;
fn secret() -> Vec<u8> {
(0u8..32).collect()
}
#[test]
fn shared_vector() {
let p = json!({ "project": "/tmp/game", "args": { "only": "en-US,tr", "b": [1, true, null, "ü\"\n"] } });
assert_eq!(canonical(&p).unwrap(), "{\"args\":{\"b\":[1,true,null,\"ü\\\"\\n\"],\"only\":\"en-US,tr\"},\"project\":\"/tmp/game\"}");
assert_eq!(mac(&secret(), "play.listing", &p, 1_700_000_060_000, "AAAAAAAAAAAAAAAAAAAAAA").unwrap(), "JhSxUL2ej4cm4yrmlcA23rTCPq3YMKJthbB7n6aixxw");
}
#[test]
fn canonical_rules() {
assert_eq!(canonical(&json!({ "b": 1, "a": [true, null, "x"], "c": { "z": "" } })).unwrap(), "{\"a\":[true,null,\"x\"],\"b\":1,\"c\":{\"z\":\"\"}}");
assert!(canonical(&json!({ "f": 0.5 })).is_err());
assert!(canonical(&json!({ "f": 9_007_199_254_740_992u64 })).is_err());
assert_eq!(canonical(&json!(-9_007_199_254_740_991i64)).unwrap(), "-9007199254740991");
// keys sort by UTF-16 code units like JavaScript: "\u{e9}" (U+00E9) after "z", astral after BMP private use
assert_eq!(canonical(&json!({ "é": 1, "z": 2, "A": 3 })).unwrap(), "{\"A\":3,\"z\":2,\"é\":1}");
assert_eq!(canonical(&json!("\u{1}\t</")).unwrap(), "\"\\u0001\\t</\"");
}
#[test]
fn minted_tokens_have_the_sidecar_shape() {
let p = json!({ "project": "/p", "args": {} });
let t = mint(&secret(), "play.listing", &p, 1_000, "AAAAAAAAAAAAAAAAAAAAAA").unwrap();
let parts: Vec<&str> = t.split('.').collect();
assert_eq!(parts.len(), 4);
assert_eq!(parts[0], "sbt1");
assert_eq!(parts[1], (1_000 + TTL_MS).to_string());
assert_eq!(parts[3].len(), 43);
assert_eq!(parts[3], mac(&secret(), "play.listing", &p, 1_000 + TTL_MS, "AAAAAAAAAAAAAAAAAAAAAA").unwrap());
let n = new_nonce().unwrap();
assert_eq!(n.len(), 22);
assert_ne!(n, new_nonce().unwrap());
assert_ne!(new_secret().unwrap(), new_secret().unwrap());
assert_eq!(hex(&[0, 15, 255]), "000fff");
}