use crate::common::harness::{copy_plugin, copy_plugin_lib, EditorTestHarness};
use crossterm::event::{KeyCode, KeyModifiers};
use fresh::config::Config;
use fresh::config_io::DirectoryContext;
use std::fs;
use std::path::{Path, PathBuf};
use tempfile::TempDir;
fn setup_env_manager(working_dir: &PathBuf) {
let plugins_dir = working_dir.join("plugins");
fs::create_dir_all(&plugins_dir).expect("create plugins dir");
copy_plugin(&plugins_dir, "env-manager");
copy_plugin_lib(&plugins_dir);
}
fn make_venv(root: &Path) {
let venv = root.join(".venv").join("bin");
fs::create_dir_all(&venv).expect("create venv");
fs::write(
venv.join("activate"),
b"#!/bin/sh\nexport VIRTUAL_ENV=/tmp\n",
)
.expect("write activate");
fs::write(venv.join("python"), b"").expect("write python");
}
fn make_envrc(root: &Path) {
fs::write(root.join(".envrc"), b"export FOO=bar\n").expect("write .envrc");
}
fn make_cargo_toml(root: &Path) {
fs::write(
root.join("Cargo.toml"),
b"[package]\nname = \"demo\"\nversion = \"0.0.1\"\nedition = \"2021\"\n",
)
.expect("write Cargo.toml");
}
fn pty_available() -> bool {
portable_pty::native_pty_system()
.openpty(portable_pty::PtySize {
rows: 1,
cols: 1,
pixel_width: 0,
pixel_height: 0,
})
.is_ok()
}
fn boot_harness_like_main(width: u16, height: u16, project: PathBuf) -> EditorTestHarness {
boot_with_dir_context(width, height, project, None)
}
fn boot_with_dir_context(
width: u16,
height: u16,
project: PathBuf,
dir_context: Option<DirectoryContext>,
) -> EditorTestHarness {
let mut harness = match dir_context {
Some(dc) => EditorTestHarness::with_shared_dir_context(
width,
height,
Config::default(),
project,
dc,
)
.unwrap(),
None => EditorTestHarness::with_config_and_working_dir(
width,
height,
Config::default(),
project,
)
.unwrap(),
};
let store_path = {
let editor = harness.editor();
let working_dir = editor.working_dir().to_path_buf();
editor.dir_context().project_state_dir(&working_dir)
};
let store = fresh::services::workspace_trust::TrustStore::for_project_dir(&store_path);
harness
.editor()
.authority()
.workspace_trust
.set_store(Some(store));
harness.editor_mut().maybe_prompt_workspace_trust();
harness.editor_mut().update_plugin_state_snapshot();
harness.editor_mut().fire_plugins_loaded_hook();
harness.render().unwrap();
harness
}
#[test]
fn test_venv_prompts_then_activates_on_trust() {
let tmp = TempDir::new().unwrap();
let project = tmp.path().to_path_buf();
make_venv(&project);
setup_env_manager(&project);
let mut harness = boot_harness_like_main(120, 40, project);
harness
.wait_until(|h| {
let s = h.screen_to_string();
s.contains("SECURITY WARNING") && s.contains("Detected: .venv")
})
.unwrap();
let s = harness.screen_to_string();
assert!(
!s.contains("Trust & activate") && !s.contains("Environment detected"),
"venv must not surface the env-manager popup"
);
harness
.send_key(KeyCode::Char('t'), KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness
.wait_until(|h| {
let s = h.screen_to_string();
!s.contains("SECURITY WARNING")
&& s.contains(".venv")
&& (s.contains("Activating") || s.contains("active"))
})
.unwrap();
}
#[test]
fn test_orchestrator_session_prompts_then_activates_venv() {
if !pty_available() {
eprintln!("Skipping orchestrator env activation test: PTY not available");
return;
}
let tmp = TempDir::new().unwrap();
let launch = tmp.path().join("launch");
fs::create_dir_all(&launch).unwrap();
setup_env_manager(&launch);
let venv_proj = tmp.path().join("venvproj");
fs::create_dir_all(&venv_proj).unwrap();
make_venv(&venv_proj);
let mut harness = boot_harness_like_main(120, 40, launch);
let born = harness.editor().local_session_authority(&venv_proj);
harness
.editor_mut()
.create_window_with_terminal(
venv_proj.clone(),
"venvproj".into(),
Some(venv_proj.clone()),
Some(vec!["sh".into(), "-c".into(), "sleep 60".into()]),
None,
born,
None,
)
.expect("create orchestrator session window");
harness
.wait_until(|h| {
let s = h.screen_to_string();
s.contains("SECURITY WARNING") && s.contains("Detected: .venv")
})
.unwrap();
harness
.send_key(KeyCode::Char('t'), KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness
.wait_until(|h| {
let s = h.screen_to_string();
!s.contains("SECURITY WARNING")
&& s.contains(".venv")
&& (s.contains("Activating") || s.contains("active"))
})
.unwrap();
}
#[test]
fn test_envrc_raises_core_trust_modal_and_activates_on_trust() {
let tmp = TempDir::new().unwrap();
let project = tmp.path().to_path_buf();
make_envrc(&project);
setup_env_manager(&project);
let mut harness = boot_harness_like_main(140, 40, project);
harness
.wait_until(|h| {
let s = h.screen_to_string();
s.contains("SECURITY WARNING") && s.contains("Detected: .envrc")
})
.unwrap();
let snapshot = harness.screen_to_string();
assert!(
!snapshot.contains("Environment detected") && !snapshot.contains("Trust & activate"),
".envrc must not surface the env-manager trust popup (duplicate removed)"
);
harness
.send_key(KeyCode::Char('t'), KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness
.wait_until(|h| {
let s = h.screen_to_string();
!s.contains("SECURITY WARNING") && s.contains("Activating direnv")
})
.unwrap();
}
#[test]
fn test_cargo_toml_raises_trust_modal_with_concrete_framing() {
let tmp = TempDir::new().unwrap();
let project = tmp.path().to_path_buf();
make_cargo_toml(&project);
setup_env_manager(&project);
let mut harness = boot_harness_like_main(140, 40, project);
harness
.wait_until(|h| {
let s = h.screen_to_string();
s.contains("SECURITY WARNING") && s.contains("Detected: Cargo.toml")
})
.unwrap();
let s = harness.screen_to_string();
assert!(
!s.contains("Environment detected"),
"Cargo.toml-only folder must not fire the env popup"
);
}
#[test]
fn test_quit_cancels_trust_modal_without_recording_decision() {
let tmp = TempDir::new().unwrap();
let project = tmp.path().to_path_buf();
make_cargo_toml(&project);
setup_env_manager(&project);
let state_tmp = TempDir::new().unwrap();
let dir_context = DirectoryContext::for_testing(state_tmp.path());
{
let mut harness =
boot_with_dir_context(140, 40, project.clone(), Some(dir_context.clone()));
harness
.wait_until(|h| {
let s = h.screen_to_string();
s.contains("SECURITY WARNING") && s.contains("Detected: Cargo.toml")
})
.unwrap();
harness
.send_key(KeyCode::Char('q'), KeyModifiers::CONTROL)
.unwrap();
}
let mut harness2 = boot_with_dir_context(140, 40, project, Some(dir_context));
harness2
.wait_until(|h| {
let s = h.screen_to_string();
s.contains("SECURITY WARNING") && s.contains("Detected: Cargo.toml")
})
.unwrap();
}