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 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
}
fn run_palette_command(harness: &mut EditorTestHarness, query: &str) {
harness
.send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
.unwrap();
harness.wait_for_prompt().unwrap();
harness.type_text(query).unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.wait_for_prompt_closed().unwrap();
harness.render().unwrap();
}
#[test]
fn test_venv_silently_auto_activates() {
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(".venv") && (s.contains("Activating") || s.contains("active"))
})
.unwrap();
run_palette_command(&mut harness, "Env: Show");
let status = harness.screen_to_string();
assert!(
status.contains("Environment active") && status.contains(".venv"),
"expected `Env: Show` to confirm .venv active. Screen:\n{}",
status
);
assert!(
!status.contains("Trust & activate"),
"venv must not surface the combined trust+activate popup"
);
assert!(
!status.contains("SECURITY WARNING"),
"venv-only folder must not raise the core trust modal"
);
}
#[test]
fn test_envrc_shows_combined_trust_popup_and_elevates_on_accept() {
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("Environment detected") && s.contains("Trust & activate")
})
.unwrap();
let snapshot = harness.screen_to_string();
assert!(
!snapshot.contains("SECURITY WARNING"),
".envrc must not stack the core trust modal alongside env popup"
);
harness
.send_key(KeyCode::Char('t'), KeyModifiers::ALT)
.unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness
.wait_until(|h| {
let s = h.screen_to_string();
!s.contains("Environment detected") && 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();
}