origin_platform/
memory_process.rs1use crate::process::{ProcessAllowlist, ProcessOutput, ProcessRunner};
7use async_trait::async_trait;
8use origin_domain::Result;
9use std::path::Path;
10use std::sync::Mutex;
11
12#[derive(Debug)]
17pub struct MemoryProcessRunner {
18 allowlist: ProcessAllowlist,
19 output: ProcessOutput,
20 calls: Mutex<Vec<(String, Vec<String>, String)>>,
22}
23
24impl MemoryProcessRunner {
25 pub fn new(allowlist: ProcessAllowlist, output: ProcessOutput) -> Self {
26 Self {
27 allowlist,
28 output,
29 calls: Mutex::new(Vec::new()),
30 }
31 }
32
33 pub fn success(allowlist: ProcessAllowlist) -> Self {
35 Self::new(
36 allowlist,
37 ProcessOutput {
38 status: 0,
39 stdout: Vec::new(),
40 stderr: Vec::new(),
41 },
42 )
43 }
44
45 pub fn calls(&self) -> Vec<(String, Vec<String>, String)> {
47 self.calls.lock().expect("recorder poisoned").clone()
48 }
49}
50
51#[async_trait]
52impl ProcessRunner for MemoryProcessRunner {
53 async fn run(&self, program: &str, args: &[String], cwd: &Path) -> Result<ProcessOutput> {
54 self.allowlist.check(program)?;
55
56 self.calls.lock().expect("recorder poisoned").push((
57 program.to_owned(),
58 args.to_vec(),
59 cwd.display().to_string(),
60 ));
61
62 Ok(self.output.clone())
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[tokio::test]
71 async fn rejects_unlisted_programs() {
72 let allowlist = ProcessAllowlist::new(["git"]);
73 let runner = MemoryProcessRunner::new(
74 allowlist,
75 ProcessOutput {
76 status: 0,
77 stdout: vec![],
78 stderr: vec![],
79 },
80 );
81
82 let result = runner.run("rm", &[], Path::new("/")).await;
83
84 assert!(result.is_err());
85 assert_eq!(
86 result.unwrap_err().kind(),
87 origin_domain::ErrorKind::Permission
88 );
89 }
90
91 #[tokio::test]
92 async fn allows_listed_programs_and_records_calls() {
93 let allowlist = ProcessAllowlist::new(["git", "npm"]);
94 let output = ProcessOutput {
95 status: 0,
96 stdout: b"ok".to_vec(),
97 stderr: vec![],
98 };
99 let runner = MemoryProcessRunner::new(allowlist, output.clone());
100
101 let result = runner
102 .run("git", &["status".to_owned()], Path::new("/repo"))
103 .await;
104
105 assert!(result.is_ok());
106 assert_eq!(result.unwrap(), output);
107
108 let calls = runner.calls();
109 assert_eq!(calls.len(), 1);
110 assert_eq!(calls[0].0, "git");
111 assert_eq!(calls[0].1, vec!["status"]);
112 assert!(calls[0].2.contains("repo"));
113 }
114}