Files
pkh/src/context/mod.rs
T
vhaudiquet e5adf600c3
CI / build (push) Successful in 2m54s
CI / test (push) Skipped
CI / snap (push) Failing after 12s
deb: change ui/ux of pkh deb
2026-08-22 22:26:20 +02:00

182 lines
6.0 KiB
Rust

mod api;
mod capture;
mod local;
mod manager;
mod schroot;
mod ssh;
mod unshare;
pub use api::{Context, ContextCommand, ContextConfig, LineSink, Stream};
pub use manager::ContextManager;
use std::sync::Arc;
/// Obtain global context manager
pub fn manager() -> &'static ContextManager {
&manager::MANAGER
}
/// Obtain current context
pub fn current() -> Arc<Context> {
manager::MANAGER.current()
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::NamedTempFile;
#[test]
fn test_ensure_available_local() {
let temp_dir = tempfile::tempdir().unwrap();
let src_file = temp_dir.path().join("src.txt");
fs::write(&src_file, "local").unwrap();
let ctx = Context::new(ContextConfig::Local);
let dest = ctx.ensure_available(&src_file, "/tmp").unwrap();
// Should return a path that exists and has the same content
assert!(dest.exists());
let content = fs::read_to_string(&dest).unwrap();
assert_eq!(content, "local");
// The dest should be in the /tmp directory
assert!(dest.starts_with("/tmp"));
}
#[test]
fn test_context_manager_crud() {
let temp_file = NamedTempFile::new().unwrap();
let path = temp_file.path().to_path_buf();
let mgr = ContextManager::with_path(path.clone());
// Add
let ssh_cfg = ContextConfig::Ssh {
host: "10.0.0.1".into(),
user: Some("admin".into()),
port: Some(2222),
};
mgr.add_context("myserver", ssh_cfg.clone()).unwrap();
assert!(mgr.list_contexts().contains(&"myserver".to_string()));
// List
let list = mgr.list_contexts();
assert!(list.contains(&"myserver".to_string()));
// Set Current
mgr.set_current("myserver").unwrap();
assert_eq!(mgr.current_name(), "myserver".to_string());
// Remove
mgr.remove_context("myserver").unwrap();
assert!(!mgr.list_contexts().contains(&"myserver".to_string()));
}
#[test]
fn test_persistence() {
let temp_dir = tempfile::tempdir().unwrap();
let config_path = temp_dir.path().join("contexts.json");
{
let mgr = ContextManager::with_path(config_path.clone());
mgr.add_context("persistent", ContextConfig::Local).unwrap();
mgr.set_current("persistent").unwrap();
}
let content = fs::read_to_string(&config_path).unwrap();
let loaded_config: super::manager::Config = serde_json::from_str(&content).unwrap();
assert_eq!(loaded_config.context, "persistent".to_string());
assert!(loaded_config.contexts.contains_key("persistent"));
}
#[test]
fn test_context_fallback_on_removal() {
let temp_file = NamedTempFile::new().unwrap();
let path = temp_file.path().to_path_buf();
let mgr = ContextManager::with_path(path);
// 1. Add and set a context
mgr.add_context("temp", ContextConfig::Local).unwrap();
mgr.set_current("temp").unwrap();
assert_eq!(mgr.current_name(), "temp");
// 2. Remove it
mgr.remove_context("temp").unwrap();
// 3. Should have fallen back to local
assert_eq!(mgr.current_name(), "local");
assert!(mgr.list_contexts().contains(&"local".to_string()));
}
#[test]
fn test_context_file_ops() {
let temp_dir = tempfile::tempdir().unwrap();
let ctx = Context::new(ContextConfig::Local);
let file_path = temp_dir.path().join("test.txt");
let content = "hello world";
// 1. Write file
ctx.write_file(&file_path, content).unwrap();
// 2. Read file
let read_content = ctx.read_file(&file_path).unwrap();
assert_eq!(read_content, content);
// 3. Copy path
let dest_path = temp_dir.path().join("test_copy.txt");
ctx.copy_path(&file_path, &dest_path).unwrap();
let copied_content = ctx.read_file(&dest_path).unwrap();
assert_eq!(copied_content, content);
// 4. Recursive copy
let subdir = temp_dir.path().join("subdir");
std::fs::create_dir_all(&subdir).unwrap();
let subfile = subdir.join("subfile.txt");
ctx.write_file(&subfile, "subcontent").unwrap();
let subdir_copy = temp_dir.path().join("subdir_copy");
ctx.copy_path(&subdir, &subdir_copy).unwrap();
assert!(subdir_copy.exists());
assert!(subdir_copy.join("subfile.txt").exists());
assert_eq!(
ctx.read_file(&subdir_copy.join("subfile.txt")).unwrap(),
"subcontent"
);
}
/// Copying a tree containing a dangling symlink must reproduce the link
/// itself rather than failing to follow it (which previously surfaced as
/// a bare "No such file or directory (os error 2)").
#[test]
fn test_context_copy_preserves_dangling_symlink() {
use std::os::unix::fs::symlink;
let temp_dir = tempfile::tempdir().unwrap();
let ctx = Context::new(ContextConfig::Local);
let src_dir = temp_dir.path().join("src");
std::fs::create_dir_all(&src_dir).unwrap();
// A regular file alongside the symlink, to ensure normal copies still work.
std::fs::write(src_dir.join("real.txt"), "data").unwrap();
// A dangling symlink pointing to a non-existent target.
symlink("/nonexistent/target", src_dir.join("dangling")).unwrap();
let dest_dir = temp_dir.path().join("dest");
ctx.copy_path(&src_dir, &dest_dir).unwrap();
// The regular file was copied.
assert_eq!(ctx.read_file(&dest_dir.join("real.txt")).unwrap(), "data");
// The symlink was reproduced as a symlink (not followed).
let meta = std::fs::symlink_metadata(dest_dir.join("dangling")).unwrap();
assert!(meta.file_type().is_symlink());
assert_eq!(
std::fs::read_link(dest_dir.join("dangling")).unwrap(),
std::path::Path::new("/nonexistent/target")
);
}
}