Files
samiandClaude Opus 5.5 a8f56ee00f
ci / build (ubuntu-22.04) (push) Canceled after 0s
ci / build (windows-2025) (push) Canceled after 0s
ci / deny (push) Canceled after 0s
Sign in with Google, Apple…: remember prompt, item row, new-login picker
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 15:29:50 +07:00

85 lines
3.1 KiB
Rust

//! Headless stand-in for the desktop app, for end-to-end tests of the browser extension and
//! `kestrove-nmh`. It runs the same `Broker` as the app over the same IPC endpoint, with an
//! in-memory vault holding one test login, and plays the user: it approves pairings, saves and passkeys.
//! Never ship this: it auto-approves.
//!
//! KESTROVE_IPC_DIR=/tmp/kv-e2e KESTROVE_TEST_ORIGIN=http://127.0.0.1:8765 \
//! cargo run --example test-host
//!
//! Prints `READY <endpoint>`, then `PAIRED <words>`, `SAVED <origin> <username>` and
//! `PASSKEY <create|get> <rp_id> <user>` as they happen.
use std::io::Write;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use kestrove_desktop::broker::{Broker, PairingStore, PairingView, SaveView, Ui};
use kestrove_desktop::passkey::{PasskeyKind, PasskeyView};
use kestrove_desktop::ipc;
use kv_ffi::{ItemDto, ItemKindDto, VaultCore};
struct Quiet;
impl Ui for Quiet {
fn pairing_requested(&self, _: &PairingView) {}
fn save_requested(&self, _: &SaveView) {}
fn passkey_requested(&self, _: &PasskeyView) {}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let origin = std::env::var("KESTROVE_TEST_ORIGIN").unwrap_or_else(|_| "http://127.0.0.1:8765".into());
let core = VaultCore::new_in_memory();
core.create_account("test master password".into())?;
core.save_item(ItemDto {
id: String::new(),
kind: ItemKindDto::Login,
title: "Test login".into(),
username: Some("[email protected]".into()),
password: Some("correct-horse-42".into()),
urls: vec![origin],
totp: Some("JBSWY3DPEHPK3PXP".into()),
notes: None,
favorite: false,
tags: vec![],
sign_in_with: None,
modified_at: 0,
})?;
let broker = Broker::new(core, Arc::new(Quiet), PairingStore::in_memory());
let listener = ipc::Listener::bind()?;
println!("READY {}", ipc::endpoint()?.display());
std::io::stdout().flush()?;
let b = broker.clone();
thread::spawn(move || loop {
thread::sleep(Duration::from_millis(200));
if let Some(p) = b.pending_pairing() {
if b.resolve_pairing(&p.id, true).is_ok() {
println!("PAIRED {}", p.words.join(" "));
}
}
for p in b.pending_passkeys() {
if b.resolve_passkey(&p.id, true, None).is_ok() {
let (kind, user) = match p.kind {
PasskeyKind::Create => ("create", p.user_name.clone()),
PasskeyKind::Get => ("get", p.choices.first().map(|c| c.user_name.clone()).unwrap_or_default()),
};
println!("PASSKEY {kind} {} {user}", p.rp_id);
}
}
for s in b.pending_saves() {
if b.resolve_save(&s.id, true).is_ok() {
println!("SAVED {} {}", s.origin, s.username);
}
}
let _ = std::io::stdout().flush();
});
loop {
let conn = listener.accept()?;
let b = broker.clone();
thread::spawn(move || {
let _ = b.serve(conn);
});
}
}