//! Account handling: what is stored where, what is shown, and the key checks. No real keys anywhere. use serde_json::{json, Map}; use storebound_desktop::accounts::*; use storebound_desktop::commands::{dialog_excerpt, dialog_summary, has_config, tilde}; use storebound_desktop::sidecar::managed_tool_env; use storebound_desktop::store::{self, Project, State}; const FAKE_PEM: &str = "-----BEGIN PRIVATE KEY-----\nnot-a-real-key\n-----END PRIVATE KEY-----"; #[test] fn shown_metadata_carries_no_secret() { let a = Account { id: "abc".into(), label: "Studio Play".into(), kind: AccountKind::Play, created: "2026-10-10T00:00:00Z".into(), studio: Some("s1".into()) }; let v = serde_json::to_value(&a).unwrap(); assert_eq!(v.as_object().unwrap().keys().cloned().collect::>(), vec!["created", "id", "kind", "label", "studio"]); assert_eq!(v["kind"], "play"); } #[test] fn secrets_map_to_the_cli_env_names() { let mut m = Map::new(); Secret::Apple { key_id: "ABCDEFGH12".into(), issuer_id: "00000000-0000-0000-0000-000000000000".into(), p8: FAKE_PEM.into() }.to_credentials(&mut m); Secret::Play { service_account: "{}".into() }.to_credentials(&mut m); assert_eq!(m.keys().cloned().collect::>(), vec!["ASC_ISSUER_ID", "ASC_KEY_ID", "ASC_P8", "PLAY_SERVICE_ACCOUNT"]); let s = Secret::Play { service_account: "{}".into() }; let back: Secret = serde_json::from_str(&serde_json::to_string(&s).unwrap()).unwrap(); assert_eq!(back, s); } #[test] fn key_checks() { let sa = json!({ "type": "service_account", "client_email": "bot@example.com", "private_key": FAKE_PEM }).to_string(); assert!(check_service_account(&sa).is_ok()); assert!(check_service_account("{\"client_email\":\"x@example.com\"}").is_err()); assert!(check_service_account("not json").is_err()); assert_eq!(check_key_id(" ABCDEFGH12 ").unwrap(), "ABCDEFGH12"); assert!(check_key_id("abc").is_err()); assert!(check_key_id("abcdefgh12").is_err()); let upper = "00000000-0000-0000-0000-00000000000A"; assert_eq!(check_issuer_id(upper).unwrap(), upper.to_ascii_lowercase()); assert!(check_issuer_id("0000-0000").is_err()); assert!(check_p8(FAKE_PEM).is_ok()); assert!(check_p8("hello").is_err()); assert!(check_label("").is_err()); assert!(check_label("a\u{7}").is_err()); assert!(check_label(&"x".repeat(61)).is_err()); assert_eq!(check_label(" Studio ").unwrap(), "Studio"); } #[test] fn state_file_round_trip_is_atomic_and_tolerant() { let dir = tempfile::tempdir().unwrap(); assert_eq!(store::load(dir.path()), State::default()); let st = State { projects: vec![Project { id: "1".into(), path: "/games/a".into(), name: "A".into(), play_account: Some("p".into()), apple_account: None, added: "2026-10-10T00:00:00Z".into() }], accounts: vec![], ..State::default() }; store::save(dir.path(), &st).unwrap(); assert_eq!(store::load(dir.path()), st); assert!(!dir.path().join("state.json.tmp").exists()); let text = std::fs::read_to_string(dir.path().join("state.json")).unwrap(); assert!(text.contains("\"playAccount\": \"p\"")); std::fs::write(dir.path().join("state.json"), "{broken").unwrap(); assert_eq!(store::load(dir.path()), State::default()); assert_eq!(store::iso_from_unix(0), "1970-01-01T00:00:00Z"); assert_eq!(store::iso_from_unix(1_700_000_000), "2023-11-14T22:13:20Z"); assert_eq!(store::new_id().unwrap().len(), 16); } #[test] fn managed_tools_are_passed_only_when_present() { let dir = tempfile::tempdir().unwrap(); assert!(managed_tool_env(dir.path()).is_empty()); std::fs::create_dir_all(dir.path().join("tools/bin")).unwrap(); let env = managed_tool_env(dir.path()); assert_eq!(env.len(), 1); assert_eq!(env[0].0, "STOREBOUND_TOOL_PATH"); assert!(env[0].1.ends_with("bin")); } #[test] fn confirm_dialog_text() { let s = dialog_summary("Push Play listing text", "Title and descriptions.", "Prism Puddle", true, true); assert!(s.starts_with("Push Play listing text ยท Prism Puddle")); assert!(s.contains("not public until you submit")); assert!(s.contains("replaces")); let out: String = (1..=40).map(|i| format!("line {i}\n")).collect(); let ex = dialog_excerpt(&out); assert!(ex.starts_with("(12 earlier lines")); assert!(ex.ends_with("line 40")); assert!(dialog_excerpt(&"x".repeat(5000)).chars().count() < 2_500); assert_eq!(tilde("would write /home/you/a/index.html", Some("/home/you/")), "would write ~/a/index.html"); assert_eq!(tilde("/x", None), "/x"); assert_eq!(tilde("/x", Some("/")), "/x"); let dir = tempfile::tempdir().unwrap(); assert!(!has_config(dir.path())); std::fs::write(dir.path().join("storebound.config.ts"), "export default {}").unwrap(); assert!(has_config(dir.path())); }