OXYGENHUB
# da84e98f104e spectra / tests / common / mod.rs
3.7 KiB · Rust · last changed The 0.1 tools: brief, rules, agents, status, say, read, inbox, wait with the idle guard, ack, search, redact; every call on the record (#8) 2 days ago source blame history raw
1
//! Helpers for end-to-end tests: a hermetic environment, the owner's
2
//! delegation as `oxy identity delegate add` makes it, agents added through
3
//! the CLI, and the daemon started and stopped as a real process.
4
#![allow(dead_code)]
5
6
use oxy_core::durable::{write_atomic, write_atomic_private};
7
use oxy_core::identity::{Delegate, SecretKey};
8
use spectra_core::keys::KeyPair;
9
use spectra_core::principals::DELEGATION_SCOPE;
10
use std::path::{Path, PathBuf};
11
use std::process::{Child, Command, Output};
12
13
pub fn hermetic(dir: &Path) -> Command {
14
    let mut c = Command::new(env!("CARGO_BIN_EXE_spectra"));
15
    c.env("SPECTRA_DATA", dir.join("data"))
16
        .env("OXY_HOME", dir.join("owner"))
17
        .env("HOME", dir)
18
        .env("XDG_DATA_HOME", dir.join("xdg"))
19
        .env("NO_COLOR", "1");
20
    c
21
}
22
23
pub fn run(dir: &Path, args: &[&str]) -> Output {
24
    hermetic(dir).args(args).output().unwrap()
25
}
26
27
pub fn text(o: &Output) -> String {
28
    format!("{}{}", String::from_utf8_lossy(&o.stdout), String::from_utf8_lossy(&o.stderr))
29
}
30
31
/// `oxy identity delegate add` in the owner's home.
32
pub fn delegate(owner_home: &Path, agent: &KeyPair) {
33
    let cur = KeyPair::load_home(owner_home).unwrap();
34
    let keys = owner_home.join("keys");
35
    let (_, secret) = SecretKey::decode(&std::fs::read(keys.join(format!("{}.secret", cur.key.to_hex()))).unwrap()).unwrap();
36
    let mut d = cur.identity.delegates.clone();
37
    d.push(Delegate { key: agent.key, scope: DELEGATION_SCOPE, expires: None });
38
    let next = cur.identity.successor(d, None, spectra_core::ids::now_ms());
39
    let k = next.hash();
40
    write_atomic_private(&keys.join(format!("{}.secret", k.to_hex())), &secret.encode(&k)).unwrap();
41
    write_atomic(&keys.join(format!("{}.identity", k.to_hex())), &next.encode()).unwrap();
42
    oxy_core::repo::Config::set_user(&owner_home.join("config.toml"), "author.key", &k.to_hex()).unwrap();
43
}
44
45
/// Make an agent's home, delegate to it, and add it.
46
pub fn add_agent(d: &Path, name: &str, extra: &[&str]) -> KeyPair {
47
    let home = d.join("agents").join(name);
48
    let pair = KeyPair::create_home(&home, name, "").unwrap();
49
    delegate(&d.join("owner"), &pair);
50
    let mut args =
51
        vec!["agent", "add", name, "--kind", "claude-code", "--project", "oxygen", "--home", home.to_str().unwrap(), "--wait", "0"];
52
    args.extend_from_slice(extra);
53
    let o = run(d, &args);
54
    assert!(o.status.success(), "{}", text(&o));
55
    pair
56
}
57
58
pub struct Serving {
59
    pub child: Child,
60
    pub socket: PathBuf,
61
}
62
63
impl Serving {
64
    pub fn start(d: &Path) -> Serving {
65
        let mut child = hermetic(d).args(["serve", "--bind", "127.0.0.1:0"]).stdout(std::process::Stdio::piped()).spawn().unwrap();
66
        let socket = d.join("data/spectra.sock");
67
        for _ in 0..200 {
68
            if std::os::unix::net::UnixStream::connect(&socket).is_ok() {
69
                return Serving { child, socket };
70
            }
71
            std::thread::sleep(std::time::Duration::from_millis(25));
72
        }
73
        child.kill().ok();
74
        child.wait().ok();
75
        panic!("spectra serve did not start");
76
    }
77
    pub fn stop(mut self) {
78
        unsafe { libc::kill(self.child.id() as i32, libc::SIGTERM) };
79
        let status = self.child.wait().unwrap();
80
        assert!(status.success(), "a clean stop on SIGTERM");
81
        assert!(!self.socket.exists(), "the socket is removed");
82
    }
83
}
84
85
pub fn setup() -> tempfile::TempDir {
86
    let dir = tempfile::tempdir().unwrap();
87
    let d = dir.path();
88
    KeyPair::create_home(&d.join("owner"), "Andy McCall", "").unwrap();
89
    assert!(run(d, &["init"]).status.success());
90
    std::fs::create_dir_all(d.join("oxygen")).unwrap();
91
    assert!(run(d, &["project", "add", d.join("oxygen").to_str().unwrap()]).status.success());
92
    dir
93
}