119 lines
4.1 KiB
Rust
119 lines
4.1 KiB
Rust
use hermes_grocy_mcp::{
|
|
client::ApiBase,
|
|
credentials::{
|
|
CredentialStore, DesktopStore, EnrollmentConsent, providers::discover_providers,
|
|
},
|
|
model::{AccessMode, PrivateProfile, ProfileSelection, TransportPolicy},
|
|
};
|
|
use secrecy::{ExposeSecret, SecretString};
|
|
use std::{
|
|
io::Write,
|
|
process::{Child, Command, Stdio},
|
|
time::{Duration, Instant},
|
|
};
|
|
|
|
struct WalletDaemon(Child);
|
|
impl Drop for WalletDaemon {
|
|
fn drop(&mut self) {
|
|
let _ = self.0.kill();
|
|
let _ = self.0.wait();
|
|
}
|
|
}
|
|
|
|
fn profile(key: &str) -> PrivateProfile {
|
|
PrivateProfile {
|
|
api_base: ApiBase::parse("https://grocy.example/api", TransportPolicy::HttpsOnly).unwrap(),
|
|
api_key: SecretString::from(key.to_owned()),
|
|
access: AccessMode::Full,
|
|
timezone: Some("Etc/UTC".into()),
|
|
defaults: serde_json::json!({}),
|
|
ca_path: None,
|
|
transport: TransportPolicy::HttpsOnly,
|
|
unsafe_storage: false,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn encrypted_wallet_roundtrip() {
|
|
if std::env::var_os("GROCY_ENCRYPTED_WALLET_TEST_CHILD").is_none() {
|
|
// A private bus and data directory ensure this cannot reach the user's wallet.
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let result = Command::new("dbus-run-session")
|
|
.arg("--")
|
|
.arg(std::env::current_exe().unwrap())
|
|
.args(["--exact", "encrypted_wallet_roundtrip", "--nocapture"])
|
|
.env("GROCY_ENCRYPTED_WALLET_TEST_CHILD", "1")
|
|
.env("XDG_DATA_HOME", dir.path())
|
|
.env("XDG_RUNTIME_DIR", dir.path())
|
|
.env("G_DEBUG", "fatal-criticals")
|
|
.output()
|
|
.expect("Install dbus and gnome-keyring to run the isolated wallet test");
|
|
assert!(
|
|
result.status.success(),
|
|
"Isolated wallet roundtrip failed:\n{}\n{}",
|
|
String::from_utf8_lossy(&result.stdout),
|
|
String::from_utf8_lossy(&result.stderr)
|
|
);
|
|
return;
|
|
}
|
|
|
|
let mut daemon = WalletDaemon(
|
|
Command::new("gnome-keyring-daemon")
|
|
.args(["--foreground", "--unlock", "--components=secrets"])
|
|
.arg("--control-directory")
|
|
.arg(std::env::var_os("XDG_RUNTIME_DIR").unwrap())
|
|
.stdin(Stdio::piped())
|
|
.stdout(Stdio::null())
|
|
.stderr(Stdio::null())
|
|
.spawn()
|
|
.expect("Install gnome-keyring to run the isolated wallet test"),
|
|
);
|
|
daemon
|
|
.0
|
|
.stdin
|
|
.take()
|
|
.unwrap()
|
|
.write_all(b"synthetic-isolated-wallet-password")
|
|
.unwrap();
|
|
let deadline = Instant::now() + Duration::from_secs(10);
|
|
let provider = loop {
|
|
if let Ok(providers) = discover_providers()
|
|
&& let Some(identity) = providers.into_iter().find_map(|p| p.identity)
|
|
{
|
|
break identity;
|
|
}
|
|
assert!(Instant::now() < deadline, "Isolated wallet did not start");
|
|
std::thread::sleep(Duration::from_millis(50));
|
|
};
|
|
let selection = ProfileSelection {
|
|
profile: "synthetic-roundtrip".into(),
|
|
provider,
|
|
};
|
|
tokio::runtime::Runtime::new().unwrap().block_on(async {
|
|
assert!(matches!(
|
|
DesktopStore.load(selection.clone()).await,
|
|
Err(e) if e.code == "PROFILE_NOT_FOUND"
|
|
));
|
|
for key in ["synthetic-first-key", "synthetic-replacement-key"] {
|
|
DesktopStore
|
|
.put(
|
|
selection.clone(),
|
|
profile(key),
|
|
EnrollmentConsent::ProtectedByUser,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
let loaded = DesktopStore.load(selection.clone()).await.unwrap();
|
|
assert!(loaded.api_key.expose_secret() == key);
|
|
assert!(loaded.api_base.as_str() == profile(key).api_base.as_str());
|
|
assert_eq!(loaded.access, AccessMode::Full);
|
|
assert!(!loaded.unsafe_storage);
|
|
}
|
|
DesktopStore.forget(selection.clone()).await.unwrap();
|
|
assert!(matches!(
|
|
DesktopStore.load(selection).await,
|
|
Err(e) if e.code == "PROFILE_NOT_FOUND"
|
|
));
|
|
});
|
|
}
|