use std::fs;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
use kcode_rust_libs_v2::{Error, KcodeRustLibs, RustLibFile, RustLibPath};
static TEMP_COUNTER: AtomicU64 = AtomicU64::new(0);
struct TestDirectory(PathBuf);
impl TestDirectory {
fn new(label: &str) -> Self {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
let counter = TEMP_COUNTER.fetch_add(1, Ordering::Relaxed);
let path = std::env::temp_dir().join(format!(
"kcode-rust-libs-v2-test-{label}-{}-{timestamp}-{counter}",
std::process::id()
));
fs::create_dir(&path).unwrap();
Self(path)
}
fn path(&self) -> &Path {
&self.0
}
}
impl Drop for TestDirectory {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}
fn manager(test: &TestDirectory) -> KcodeRustLibs {
KcodeRustLibs::new(test.path().join("libraries"), "test-registry-token").unwrap()
}
#[test]
fn opens_one_complete_snapshot_without_a_second_documentation_representation() {
let test = TestDirectory::new("snapshot");
let manager = manager(&test);
let mut opened = manager.create_rust_lib("calculator").unwrap();
assert_eq!(opened.name(), "calculator");
assert_eq!(opened.version(), "0.1.0");
assert_eq!(
opened
.files()
.iter()
.map(|file| file.path.as_str())
.collect::<Vec<_>>(),
["Cargo.toml", "Documentation.md", "src/lib.rs"]
);
assert_eq!(
opened
.files()
.iter()
.filter(|file| file.path.as_str() == "Documentation.md")
.count(),
1
);
opened
.write(&[
RustLibFile::new(
RustLibPath::new("Documentation.md").unwrap(),
"Call `answer()` to obtain the answer.\n",
),
RustLibFile::new(
RustLibPath::new("src/lib.rs").unwrap(),
"pub fn answer() -> u8 { 42 }\n",
),
])
.unwrap();
assert_eq!(
opened
.files()
.iter()
.find(|file| file.path.as_str() == "Documentation.md")
.unwrap()
.contents,
"Call `answer()` to obtain the answer.\n"
);
}
#[test]
fn complete_file_writes_refresh_the_manifest_version_and_preserve_omitted_files() {
let test = TestDirectory::new("write");
let manager = manager(&test);
let mut opened = manager.create_rust_lib("demo").unwrap();
opened
.write(&[
RustLibFile::new(
RustLibPath::new("Cargo.toml").unwrap(),
"[package]\nname = \"demo\"\nversion = \"1.2.3\"\nedition = \"2024\"\n",
),
RustLibFile::new(
RustLibPath::new("tests/value.rs").unwrap(),
"#[test]\nfn works() {}\n",
),
])
.unwrap();
assert_eq!(opened.version(), "1.2.3");
assert!(
opened
.files()
.iter()
.any(|file| file.path.as_str() == "Documentation.md")
);
assert!(
opened
.files()
.iter()
.any(|file| file.path.as_str() == "tests/value.rs")
);
}
#[test]
fn rejects_invalid_metadata_before_replacing_an_earlier_file() {
let test = TestDirectory::new("metadata");
let manager = manager(&test);
let mut opened = manager.create_rust_lib("demo").unwrap();
let result = opened.write(&[
RustLibFile::new(RustLibPath::new("src/lib.rs").unwrap(), "changed\n"),
RustLibFile::new(
RustLibPath::new("Cargo.toml").unwrap(),
"[package]\nname = \"demo\"\nversion = \"1.2.3-beta\"\n",
),
]);
assert!(matches!(result, Err(Error::InvalidVersion(_))));
assert_eq!(
fs::read_to_string(test.path().join("libraries/demo/src/lib.rs")).unwrap(),
""
);
}
#[test]
fn rejects_empty_tokens_before_creating_the_persistent_root() {
for token in ["", " \n\t"] {
let test = TestDirectory::new("token");
let root = test.path().join("libraries");
assert!(matches!(
KcodeRustLibs::new(&root, token),
Err(Error::InvalidRegistryToken)
));
assert!(!root.exists());
}
}
#[test]
fn manager_debug_output_redacts_the_registry_token() {
let test = TestDirectory::new("debug");
let manager = KcodeRustLibs::new(test.path().join("libraries"), "debug-secret").unwrap();
let debug = format!("{manager:?}");
assert!(!debug.contains("debug-secret"));
assert!(debug.contains("[REDACTED]"));
}
#[test]
fn rejects_bad_names_and_duplicate_write_paths() {
let test = TestDirectory::new("validation");
let manager = manager(&test);
for name in ["", "../outside", "with/slash", "two words", "_leading"] {
assert!(matches!(
manager.create_rust_lib(name),
Err(Error::InvalidRustLibName(_))
));
}
let mut opened = manager.create_rust_lib("demo").unwrap();
let path = RustLibPath::new("src/lib.rs").unwrap();
assert!(matches!(
opened.write(&[
RustLibFile::new(path.clone(), "first"),
RustLibFile::new(path, "second"),
]),
Err(Error::DuplicateWritePath(_))
));
}
#[cfg(unix)]
#[test]
fn rejects_symlinks_during_open_and_write() {
use std::os::unix::fs::symlink;
let test = TestDirectory::new("symlink");
let manager = manager(&test);
let mut opened = manager.create_rust_lib("demo").unwrap();
let outside = test.path().join("outside");
fs::create_dir(&outside).unwrap();
symlink(&outside, test.path().join("libraries/demo/link")).unwrap();
assert!(matches!(
opened.write(&[RustLibFile::new(
RustLibPath::new("link/file.rs").unwrap(),
"outside"
)]),
Err(Error::SymlinkNotAllowed(_))
));
assert!(!outside.join("file.rs").exists());
drop(opened);
assert!(matches!(
manager.open_rust_lib("demo"),
Err(Error::SymlinkNotAllowed(_))
));
}