use super::*;
#[test]
fn a_recorded_value_reads_back() {
let dir = tempfile::tempdir().expect("temp dir");
record_session_value(dir.path(), KIND_TRANSCRIPT, "sess-1", "/tmp/s.jsonl");
assert_eq!(
read_session_record(dir.path(), KIND_TRANSCRIPT, "sess-1").as_deref(),
Some("/tmp/s.jsonl")
);
}
#[test]
fn rejects_a_path_traversal_session_id() {
let dir = tempfile::tempdir().expect("temp dir");
assert_eq!(
session_record_path_in(dir.path(), KIND_MODEL, "../../etc/passwd"),
None
);
record_session_value(dir.path(), KIND_MODEL, "../../evil", "x");
assert!(
!dir.path().parent().expect("parent").join("evil").exists(),
"nothing may be written outside the root"
);
}
#[test]
fn an_unchanged_value_leaves_the_file_untouched() {
let dir = tempfile::tempdir().expect("temp dir");
record_session_value(dir.path(), KIND_MODEL, "sess-1", "claude-opus-4-1");
let path = session_record_path_in(dir.path(), KIND_MODEL, "sess-1").expect("path");
let first = std::fs::metadata(&path).expect("metadata").modified().ok();
record_session_value(dir.path(), KIND_MODEL, "sess-1", "claude-opus-4-1");
let second = std::fs::metadata(&path).expect("metadata").modified().ok();
assert_eq!(
first, second,
"an unchanged value must not rewrite the file"
);
}
#[test]
fn a_blank_value_is_never_recorded() {
let dir = tempfile::tempdir().expect("temp dir");
record_session_value(dir.path(), KIND_MODEL, "sess-1", " ");
assert_eq!(read_session_record(dir.path(), KIND_MODEL, "sess-1"), None);
}
#[test]
fn reading_an_absent_record_is_none() {
let dir = tempfile::tempdir().expect("temp dir");
assert_eq!(read_session_record(dir.path(), KIND_MODEL, "sess-x"), None);
}
fn config_dir_with_transcript(dir: &Path, session: &str) -> PathBuf {
let projects = dir.join("projects").join("slug");
std::fs::create_dir_all(&projects).expect("mkdir");
let transcript = projects.join(format!("{session}.jsonl"));
std::fs::write(&transcript, "{}\n").expect("write transcript");
transcript
}
#[test]
fn rejects_a_transcript_path_outside_the_config_dir() {
let config = tempfile::tempdir().expect("temp dir");
let elsewhere = tempfile::tempdir().expect("temp dir");
let outside = elsewhere.path().join("stolen.jsonl");
std::fs::write(&outside, "{}\n").expect("write");
assert_eq!(
contained_transcript_path(config.path(), &outside.to_string_lossy()),
None
);
assert_eq!(
contained_transcript_path(config.path(), "/etc/passwd"),
None
);
}
#[test]
fn rejects_a_traversing_transcript_path() {
let config = tempfile::tempdir().expect("temp dir");
config_dir_with_transcript(config.path(), "sess-1");
let traversing = config
.path()
.join("projects")
.join("..")
.join("..")
.join("etc")
.join("passwd");
assert_eq!(
contained_transcript_path(config.path(), &traversing.to_string_lossy()),
None
);
assert_eq!(
contained_transcript_path(config.path(), "projects/slug/sess-1.jsonl"),
None
);
}
#[test]
fn accepts_a_transcript_path_under_the_config_dir() {
let config = tempfile::tempdir().expect("temp dir");
let transcript = config_dir_with_transcript(config.path(), "sess-1");
let accepted = contained_transcript_path(config.path(), &transcript.to_string_lossy())
.expect("a transcript under the config dir must be accepted");
assert_eq!(accepted, transcript.canonicalize().expect("canonicalize"));
assert!(
contained_transcript_path(config.path(), &format!(" {} ", transcript.display()))
.is_some()
);
assert_eq!(contained_transcript_path(config.path(), " "), None);
}
#[cfg(unix)]
#[test]
fn rejects_a_symlink_under_the_config_dir_aimed_outside_it() {
let config = tempfile::tempdir().expect("temp dir");
let elsewhere = tempfile::tempdir().expect("temp dir");
let outside = elsewhere.path().join("stolen.jsonl");
std::fs::write(&outside, "{}\n").expect("write");
let root = config.path().canonicalize().expect("canonicalize config");
let link = root.join("linked.jsonl");
std::os::unix::fs::symlink(&outside, &link).expect("symlink");
assert!(
link.starts_with(&root),
"the link must look contained before it is resolved"
);
assert_eq!(
contained_transcript_path(&root, &link.to_string_lossy()),
None
);
}
#[test]
fn two_kinds_do_not_collide() {
let dir = tempfile::tempdir().expect("temp dir");
record_session_value(dir.path(), KIND_MODEL, "sess-1", "claude-opus-4-1");
record_session_value(dir.path(), KIND_TRANSCRIPT, "sess-1", "/tmp/s.jsonl");
assert_eq!(
read_session_record(dir.path(), KIND_MODEL, "sess-1").as_deref(),
Some("claude-opus-4-1")
);
assert_eq!(
read_session_record(dir.path(), KIND_TRANSCRIPT, "sess-1").as_deref(),
Some("/tmp/s.jsonl")
);
}
#[test]
fn screens_a_path_valued_candidate_the_same_way() {
let config = tempfile::tempdir().expect("temp dir");
let outside_dir = tempfile::tempdir().expect("temp dir");
let inside = config.path().join("in.jsonl");
std::fs::write(&inside, "{}\n").expect("write");
let outside = outside_dir.path().join("out.jsonl");
std::fs::write(&outside, "{}\n").expect("write");
for candidate in [&inside, &outside] {
assert_eq!(
contained_transcript_file(config.path(), candidate),
contained_transcript_path(config.path(), &candidate.to_string_lossy()),
"the two entry points must agree on {}",
candidate.display()
);
}
assert!(contained_transcript_file(config.path(), &inside).is_some());
assert_eq!(contained_transcript_file(config.path(), &outside), None);
}
#[test]
fn the_containment_screen_never_leans_on_frameworkpaths_default() {
let source = std::fs::read_to_string(concat!(
env!("CARGO_MANIFEST_DIR"),
"/src/core/session_record.rs"
))
.expect("read this module's own source");
for banned in ["FrameworkPaths::default()", "FrameworkPaths::home_base()"] {
assert!(
!source
.lines()
.filter(|l| !l.trim_start().starts_with("//"))
.any(|l| l.contains(banned)),
"{banned} must never back the containment boundary (#7290)"
);
}
assert!(
source.contains("FrameworkPaths::under") && source.contains("dirs::home_dir()?"),
"the boundary resolver must take an explicit, fallible home directory"
);
}
#[test]
fn session_record_ids_lists_written_ids() {
let dir = tempfile::tempdir().expect("temp dir");
let root = dir.path();
record_session_value(root, KIND_STARTUP_CONTEXT, "sess-a", "{\"tokens\":1}");
record_session_value(root, KIND_STARTUP_CONTEXT, "sess-b", "{\"tokens\":2}");
std::fs::write(
session_record_dir_in(root, KIND_STARTUP_CONTEXT).join("not a session id"),
"x",
)
.expect("write stray file");
let mut ids = session_record_ids(root, KIND_STARTUP_CONTEXT);
ids.sort();
assert_eq!(ids, vec!["sess-a".to_string(), "sess-b".to_string()]);
}
#[test]
fn session_record_ids_of_a_missing_kind_is_empty() {
let dir = tempfile::tempdir().expect("temp dir");
assert!(session_record_ids(dir.path(), KIND_STARTUP_CONTEXT).is_empty());
}