use std::fs::File;
use std::path::Path;
use std::time::{Duration, SystemTime};
use tempfile::tempdir;
use super::*;
use crate::config::TierSynthesisMode;
use crate::tier_documents::{abstract_path, overview_path, read_overview};
#[cfg(unix)]
use std::os::unix::fs::symlink;
#[test]
fn resolve_tier_synthesis_mode_defaults_to_deterministic() {
assert_eq!(
resolve_tier_synthesis_mode(Some("semantic")),
TierSynthesisMode::SemanticLite
);
assert_eq!(
resolve_tier_synthesis_mode(Some("semantic-lite")),
TierSynthesisMode::SemanticLite
);
assert_eq!(
resolve_tier_synthesis_mode(Some("deterministic")),
TierSynthesisMode::Deterministic
);
assert_eq!(
resolve_tier_synthesis_mode(Some("")),
TierSynthesisMode::Deterministic
);
assert_eq!(
resolve_tier_synthesis_mode(None),
TierSynthesisMode::Deterministic
);
}
#[test]
fn resolve_internal_tier_policy_defaults_to_virtual() {
assert_eq!(
resolve_internal_tier_policy(Some("persist")),
InternalTierPolicy::Persist
);
assert_eq!(
resolve_internal_tier_policy(Some("virtual")),
InternalTierPolicy::Virtual
);
assert_eq!(
resolve_internal_tier_policy(Some("invalid-policy")),
InternalTierPolicy::Virtual
);
assert_eq!(
resolve_internal_tier_policy(Some("")),
InternalTierPolicy::Virtual
);
assert_eq!(
resolve_internal_tier_policy(None),
InternalTierPolicy::Virtual
);
}
#[test]
fn virtual_internal_tier_policy_skips_persist_for_internal_scopes() {
assert!(should_persist_scope_tiers(
Scope::Resources,
InternalTierPolicy::Virtual
));
assert!(should_persist_scope_tiers(
Scope::User,
InternalTierPolicy::Virtual
));
assert!(!should_persist_scope_tiers(
Scope::Temp,
InternalTierPolicy::Virtual
));
assert!(!should_persist_scope_tiers(
Scope::Queue,
InternalTierPolicy::Virtual
));
assert!(should_persist_scope_tiers(
Scope::Temp,
InternalTierPolicy::Persist
));
assert!(should_persist_scope_tiers(
Scope::Queue,
InternalTierPolicy::Persist
));
}
#[test]
fn initialize_honors_internal_tier_policy_for_internal_scopes() {
let temp = tempdir().expect("tempdir");
let app = AxiomSync::new(temp.path()).expect("app new");
app.initialize().expect("init");
let policy = app.config.indexing.internal_tier_policy;
let queue_root = AxiomUri::root(Scope::Queue);
let temp_root = AxiomUri::root(Scope::Temp);
let internal_should_persist = matches!(policy, InternalTierPolicy::Persist);
assert_eq!(
abstract_path(&app.fs, &queue_root).exists(),
internal_should_persist
);
assert_eq!(
overview_path(&app.fs, &queue_root).exists(),
internal_should_persist
);
assert_eq!(
abstract_path(&app.fs, &temp_root).exists(),
internal_should_persist
);
assert_eq!(
overview_path(&app.fs, &temp_root).exists(),
internal_should_persist
);
let resources_root = AxiomUri::root(Scope::Resources);
assert!(abstract_path(&app.fs, &resources_root).exists());
assert!(overview_path(&app.fs, &resources_root).exists());
}
#[test]
fn virtual_internal_policy_prunes_existing_generated_tiers_in_internal_scopes() {
let temp = tempdir().expect("tempdir");
let app = AxiomSync::new(temp.path()).expect("app new");
if matches!(
app.config.indexing.internal_tier_policy,
InternalTierPolicy::Persist
) {
return;
}
app.fs.initialize().expect("fs init");
let queue_uri = AxiomUri::parse("axiom://queue/traces").expect("queue uri");
app.fs
.create_dir_all(&queue_uri, true)
.expect("mkdir queue");
fs::write(app.fs.resolve_uri(&queue_uri).join(".abstract.md"), "stale").expect("write");
fs::write(app.fs.resolve_uri(&queue_uri).join(".overview.md"), "stale").expect("write");
let temp_uri = AxiomUri::parse("axiom://temp/ingest").expect("temp uri");
app.fs.create_dir_all(&temp_uri, true).expect("mkdir temp");
fs::write(app.fs.resolve_uri(&temp_uri).join(".abstract.md"), "stale").expect("write");
fs::write(app.fs.resolve_uri(&temp_uri).join(".overview.md"), "stale").expect("write");
app.ensure_scope_tiers().expect("ensure scope tiers");
assert!(!app.fs.resolve_uri(&queue_uri).join(".abstract.md").exists());
assert!(!app.fs.resolve_uri(&queue_uri).join(".overview.md").exists());
assert!(!app.fs.resolve_uri(&temp_uri).join(".abstract.md").exists());
assert!(!app.fs.resolve_uri(&temp_uri).join(".overview.md").exists());
}
#[test]
fn semantic_tier_synthesis_emits_summary_and_topics() {
let temp = tempdir().expect("tempdir");
let dir = temp.path().join("semantic-tier");
fs::create_dir_all(&dir).expect("mkdir");
fs::write(
dir.join("auth.md"),
"OAuth authorization flow with token exchange",
)
.expect("write auth");
fs::write(
dir.join("storage.md"),
"SQLite persistence cache storage guide",
)
.expect("write storage");
let uri = AxiomUri::parse("axiom://resources/semantic-tier").expect("uri parse");
let (abstract_text, overview) =
synthesize_directory_tiers(&uri, &dir, TierSynthesisMode::SemanticLite)
.expect("synthesize semantic");
assert!(abstract_text.contains("semantic summary"));
assert!(overview.contains("Summary:"));
assert!(overview.contains("- topics:"));
assert!(overview.contains("- auth.md"));
assert!(overview.contains("- storage.md"));
}
#[test]
fn semantic_tier_synthesis_falls_back_for_empty_directory() {
let temp = tempdir().expect("tempdir");
let dir = temp.path().join("empty-tier");
fs::create_dir_all(&dir).expect("mkdir");
let uri = AxiomUri::parse("axiom://resources/empty-tier").expect("uri parse");
let (abstract_text, overview) =
synthesize_directory_tiers(&uri, &dir, TierSynthesisMode::SemanticLite)
.expect("synthesize empty");
assert_eq!(
abstract_text,
"axiom://resources/empty-tier contains 0 items"
);
assert!(overview.contains("(empty)"));
}
#[test]
fn synthesize_directory_tiers_ignores_generated_and_internal_files() {
let temp = tempdir().expect("tempdir");
let dir = temp.path().join("tier-visible");
fs::create_dir_all(&dir).expect("mkdir");
fs::write(dir.join(".abstract.md"), "generated").expect("write");
fs::write(dir.join(".overview.md"), "generated").expect("write");
fs::write(dir.join(".meta.json"), "{}").expect("write");
fs::write(dir.join(".relations.json"), "[]").expect("write");
fs::write(dir.join("messages.jsonl"), "{}\n").expect("write");
fs::write(dir.join("visible.md"), "visible").expect("write");
let uri = AxiomUri::parse("axiom://resources/tier-visible").expect("uri parse");
let (abstract_text, overview) =
synthesize_directory_tiers(&uri, &dir, TierSynthesisMode::Deterministic)
.expect("synthesize deterministic");
assert!(abstract_text.contains("contains 1 items"));
assert!(overview.contains("visible.md"));
assert!(!overview.contains(".abstract.md"));
assert!(!overview.contains(".overview.md"));
assert!(!overview.contains(".meta.json"));
assert!(!overview.contains(".relations.json"));
assert!(!overview.contains("messages.jsonl"));
}
#[test]
fn synthesize_directory_tiers_fails_for_non_directory_path() {
let temp = tempdir().expect("tempdir");
let file_path = temp.path().join("not-a-directory.txt");
fs::write(&file_path, "payload").expect("write file");
let uri = AxiomUri::parse("axiom://resources/not-a-directory").expect("uri parse");
let err = synthesize_directory_tiers(&uri, &file_path, TierSynthesisMode::Deterministic)
.expect_err("must fail");
assert!(matches!(err, AxiomError::Io(_)));
}
#[test]
fn ensure_directory_tiers_rewrites_when_directory_contents_change() {
let temp = tempdir().expect("tempdir");
let app = AxiomSync::new(temp.path()).expect("app new");
app.fs.initialize().expect("fs init");
let uri = AxiomUri::parse("axiom://resources/tier-refresh").expect("uri parse");
app.fs.create_dir_all(&uri, true).expect("mkdir");
app.fs
.write(&uri.join("alpha.md").expect("join"), "alpha payload", true)
.expect("write alpha");
app.ensure_directory_tiers(&uri).expect("first synth");
let before = read_overview(&app.fs, &uri).expect("before overview");
app.fs
.write(&uri.join("beta.md").expect("join"), "beta payload", true)
.expect("write beta");
app.ensure_directory_tiers(&uri).expect("second synth");
let after = read_overview(&app.fs, &uri).expect("after overview");
assert_ne!(before, after);
assert!(after.contains("beta.md"));
}
#[test]
fn reindex_uri_tree_truncates_large_text_files_for_indexing() {
let temp = tempdir().expect("tempdir");
let app = AxiomSync::new(temp.path()).expect("app new");
app.initialize().expect("init");
let uri = AxiomUri::parse("axiom://resources/large-index").expect("uri parse");
app.fs.create_dir_all(&uri, true).expect("mkdir");
let large = "x".repeat(MAX_INDEX_READ_BYTES + 128);
fs::write(app.fs.resolve_uri(&uri).join("big.txt"), large).expect("write large");
app.reindex_uri_tree(&uri).expect("reindex");
let leaf_uri = "axiom://resources/large-index/big.txt";
let index = app.index.read().expect("index read");
let record = index.get(leaf_uri).expect("record");
assert!(
record.content.contains("[indexing truncated at"),
"large payload should be truncated in indexed content"
);
drop(index);
}
#[test]
fn reindex_uri_tree_truncated_markdown_appends_tail_heading_keys() {
let temp = tempdir().expect("tempdir");
let app = AxiomSync::new(temp.path()).expect("app new");
app.initialize().expect("init");
let uri = AxiomUri::parse("axiom://resources/large-md-index").expect("uri parse");
app.fs.create_dir_all(&uri, true).expect("mkdir");
let mut large_markdown = String::from("# Intro\n");
large_markdown.push_str(&"x".repeat(MAX_INDEX_READ_BYTES + 256));
large_markdown.push_str("\n## Tail Heading Signal 20260224\n");
large_markdown.push_str("tail payload");
fs::write(app.fs.resolve_uri(&uri).join("big.md"), large_markdown).expect("write large md");
fs::write(
app.fs.resolve_uri(&uri).join("other.md"),
"# Other\nTail Heading Signal reference text",
)
.expect("write distractor");
app.reindex_uri_tree(&uri).expect("reindex");
let big_uri = "axiom://resources/large-md-index/big.md";
let index = app.index.read().expect("index read");
let record = index.get(big_uri).expect("record");
assert!(
record.content.contains("[index markdown heading keys]"),
"truncated markdown should include heading-key appendix"
);
assert!(
record.content.contains("Tail Heading Signal 20260224"),
"tail heading must be available in indexed text"
);
drop(index);
let result = app
.find(
"Tail Heading Signal 20260224",
Some("axiom://resources/large-md-index"),
Some(5),
None,
None,
)
.expect("find");
let first = result.query_results.first().expect("first result");
assert_eq!(first.uri, big_uri);
}
#[test]
fn reindex_uri_tree_truncated_config_appends_tail_keys() {
let temp = tempdir().expect("tempdir");
let app = AxiomSync::new(temp.path()).expect("app new");
app.initialize().expect("init");
let uri = AxiomUri::parse("axiom://resources/large-config-index").expect("uri parse");
app.fs.create_dir_all(&uri, true).expect("mkdir");
let mut large_config = String::from("service: search\n");
large_config.push_str(&"x".repeat(MAX_INDEX_READ_BYTES + 512));
large_config.push_str("\nqueue_dead_letter_rate: 0.27\n");
large_config.push_str("search.om_hint.max_chars: 4096\n");
fs::write(app.fs.resolve_uri(&uri).join("runtime.yaml"), large_config).expect("write config");
app.reindex_uri_tree(&uri).expect("reindex");
let config_uri = "axiom://resources/large-config-index/runtime.yaml";
let index = app.index.read().expect("index read");
let record = index.get(config_uri).expect("record");
assert!(
record.content.contains("[index config tail keys]"),
"truncated config should include tail key section"
);
assert!(record.content.contains("queue_dead_letter_rate"));
drop(index);
let result = app
.find(
"queue_dead_letter_rate",
Some("axiom://resources/large-config-index"),
Some(5),
None,
None,
)
.expect("find");
let first = result.query_results.first().expect("first result");
assert_eq!(first.uri, config_uri);
}
#[test]
fn reindex_uri_tree_truncated_code_appends_tail_signatures() {
let temp = tempdir().expect("tempdir");
let app = AxiomSync::new(temp.path()).expect("app new");
app.initialize().expect("init");
let uri = AxiomUri::parse("axiom://resources/large-code-index").expect("uri parse");
app.fs.create_dir_all(&uri, true).expect("mkdir");
let mut large_code = String::from("// indexing tail signature test\n");
large_code.push_str(&"a".repeat(MAX_INDEX_READ_BYTES + 512));
large_code.push_str("\npub fn queue_dead_letter_rate(limit: f64) -> f64 { limit }\n");
fs::write(app.fs.resolve_uri(&uri).join("worker.rs"), large_code).expect("write code");
app.reindex_uri_tree(&uri).expect("reindex");
let code_uri = "axiom://resources/large-code-index/worker.rs";
let index = app.index.read().expect("index read");
let record = index.get(code_uri).expect("record");
assert!(
record.content.contains("[index code tail signatures]"),
"truncated code should include tail signature section"
);
assert!(record.content.contains("queue_dead_letter_rate"));
drop(index);
let result = app
.find(
"queue_dead_letter_rate",
Some("axiom://resources/large-code-index"),
Some(5),
None,
None,
)
.expect("find");
let first = result.query_results.first().expect("first result");
assert_eq!(first.uri, code_uri);
}
#[test]
fn reindex_uri_tree_truncated_log_appends_tail_signals() {
let temp = tempdir().expect("tempdir");
let app = AxiomSync::new(temp.path()).expect("app new");
app.initialize().expect("init");
let uri = AxiomUri::parse("axiom://resources/large-log-index").expect("uri parse");
app.fs.create_dir_all(&uri, true).expect("mkdir");
let mut large_log = "x".repeat(MAX_INDEX_READ_BYTES + 2048);
large_log.push('\n');
large_log.push_str("2026-03-01T00:00:00Z INFO heartbeat ok\n");
large_log.push_str("2026-03-01T00:12:10Z ERROR queue_dead_letter timeout exceeded\n");
fs::write(app.fs.resolve_uri(&uri).join("events.log"), large_log).expect("write log");
app.reindex_uri_tree(&uri).expect("reindex");
let log_uri = "axiom://resources/large-log-index/events.log";
let index = app.index.read().expect("index read");
let record = index.get(log_uri).expect("record");
assert!(
record.content.contains("[index log tail signals]"),
"truncated log should include tail signal section"
);
assert!(
record
.content
.contains("queue_dead_letter timeout exceeded")
);
drop(index);
let result = app
.find(
"dead_letter timeout exceeded",
Some("axiom://resources/large-log-index"),
Some(5),
None,
None,
)
.expect("find");
let first = result.query_results.first().expect("first result");
assert_eq!(first.uri, log_uri);
}
#[test]
fn reindex_uri_tree_updates_index_state_when_only_mtime_changes() {
let temp = tempdir().expect("tempdir");
let app = AxiomSync::new(temp.path()).expect("app new");
app.initialize().expect("init");
let uri = AxiomUri::parse("axiom://resources/mtime-index").expect("uri parse");
app.fs.create_dir_all(&uri, true).expect("mkdir");
let file_path = app.fs.resolve_uri(&uri).join("same.txt");
fs::write(&file_path, "same-content").expect("write v1");
app.reindex_uri_tree(&uri).expect("first reindex");
let state_v1 = app
.state
.get_index_state("axiom://resources/mtime-index/same.txt")
.expect("state v1")
.expect("missing v1");
std::thread::sleep(Duration::from_millis(2));
fs::write(&file_path, "same-content").expect("write v2");
app.reindex_uri_tree(&uri).expect("second reindex");
let state_v2 = app
.state
.get_index_state("axiom://resources/mtime-index/same.txt")
.expect("state v2")
.expect("missing v2");
assert_eq!(state_v1.0, state_v2.0, "hash should stay stable");
assert!(state_v2.1 >= state_v1.1, "mtime should refresh in state");
}
fn set_file_modified_time(path: &Path, modified: SystemTime) {
let file = File::options()
.read(true)
.write(true)
.open(path)
.expect("open file for set_times");
file.set_times(std::fs::FileTimes::new().set_modified(modified))
.expect("set modified");
}
#[test]
fn reindex_all_does_not_refresh_static_documents() {
let temp = tempdir().expect("tempdir");
let app = AxiomSync::new(temp.path()).expect("app new");
app.initialize().expect("init");
let uri = AxiomUri::parse("axiom://resources/recency-static").expect("uri parse");
app.fs.create_dir_all(&uri, true).expect("mkdir");
let file_path = app.fs.resolve_uri(&uri).join("static.md");
fs::write(&file_path, "# Static\n\nrecency test").expect("write");
let old_time = SystemTime::UNIX_EPOCH + Duration::from_secs(1_700_000_000);
set_file_modified_time(&file_path, old_time);
app.reindex_uri_tree(&uri).expect("first reindex");
let first_updated_at = {
let index = app.index.read().expect("index read");
index
.get("axiom://resources/recency-static/static.md")
.expect("record")
.updated_at
};
std::thread::sleep(Duration::from_millis(5));
app.reindex_uri_tree(&uri).expect("second reindex");
let second_updated_at = {
let index = app.index.read().expect("index read");
index
.get("axiom://resources/recency-static/static.md")
.expect("record")
.updated_at
};
assert_eq!(
first_updated_at, second_updated_at,
"reindex without source changes must not refresh recency"
);
}
#[test]
fn modified_file_gains_recency_without_global_shift() {
let temp = tempdir().expect("tempdir");
let app = AxiomSync::new(temp.path()).expect("app new");
app.initialize().expect("init");
let uri = AxiomUri::parse("axiom://resources/recency-targeted").expect("uri parse");
app.fs.create_dir_all(&uri, true).expect("mkdir");
let file_a = app.fs.resolve_uri(&uri).join("a.md");
let file_b = app.fs.resolve_uri(&uri).join("b.md");
fs::write(&file_a, "# A\n\ntoken").expect("write a");
fs::write(&file_b, "# B\n\ntoken").expect("write b");
let old_a = SystemTime::UNIX_EPOCH + Duration::from_secs(1_650_000_000);
let old_b = SystemTime::UNIX_EPOCH + Duration::from_secs(1_660_000_000);
set_file_modified_time(&file_a, old_a);
set_file_modified_time(&file_b, old_b);
app.reindex_uri_tree(&uri).expect("baseline reindex");
let (a_before, b_before) = {
let index = app.index.read().expect("index read");
let a = index
.get("axiom://resources/recency-targeted/a.md")
.expect("record a")
.updated_at;
let b = index
.get("axiom://resources/recency-targeted/b.md")
.expect("record b")
.updated_at;
(a, b)
};
std::thread::sleep(Duration::from_millis(5));
fs::write(&file_b, "# B\n\ntoken updated").expect("write b update");
let new_b = SystemTime::now();
set_file_modified_time(&file_b, new_b);
let file_b_uri = AxiomUri::parse("axiom://resources/recency-targeted/b.md").expect("uri");
app.reindex_document_with_ancestors(&file_b_uri)
.expect("targeted reindex");
let (a_after, b_after) = {
let index = app.index.read().expect("index read");
let a = index
.get("axiom://resources/recency-targeted/a.md")
.expect("record a")
.updated_at;
let b = index
.get("axiom://resources/recency-targeted/b.md")
.expect("record b")
.updated_at;
(a, b)
};
assert_eq!(
a_before, a_after,
"unchanged peer document must keep recency"
);
assert!(b_after > b_before, "modified file must gain recency");
}
#[test]
fn directory_ancestor_chain_lists_parent_to_root_in_order() {
let uri = AxiomUri::parse("axiom://resources/a/b/c.md").expect("uri parse");
let parent = uri.parent().expect("parent");
let chain = directory_ancestor_chain(&parent)
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>();
assert_eq!(
chain,
vec![
"axiom://resources/a/b".to_string(),
"axiom://resources/a".to_string(),
"axiom://resources".to_string(),
]
);
}
#[cfg(unix)]
#[test]
fn reindex_document_with_ancestors_ignores_unrelated_invalid_names() {
let temp = tempdir().expect("tempdir");
let app = AxiomSync::new(temp.path()).expect("app new");
app.initialize().expect("init");
let root_uri = AxiomUri::parse("axiom://resources/targeted-reindex").expect("root parse");
app.fs.create_dir_all(&root_uri, true).expect("mkdir");
let doc_uri = root_uri.join("guide.md").expect("join");
app.fs
.write(&doc_uri, "# Guide\n\nalpha targeted token", true)
.expect("write doc");
let bad_path = app.fs.resolve_uri(&root_uri).join("bad\\name.md");
fs::write(bad_path, "invalid sibling path").expect("write bad sibling");
app.reindex_document_with_ancestors(&doc_uri)
.expect("targeted reindex");
let result = app
.find(
"alpha targeted token",
Some("axiom://resources/targeted-reindex"),
Some(5),
None,
None,
)
.expect("find");
assert!(
result
.query_results
.iter()
.any(|hit| hit.uri == "axiom://resources/targeted-reindex/guide.md")
);
}
#[cfg(unix)]
#[test]
fn reindex_uri_tree_skips_broken_symlink_entries() {
let temp = tempdir().expect("tempdir");
let app = AxiomSync::new(temp.path()).expect("app new");
app.initialize().expect("init");
let uri = AxiomUri::parse("axiom://resources/reindex-broken").expect("uri parse");
app.fs.create_dir_all(&uri, true).expect("mkdir");
let broken_target = temp.path().join("missing-target.txt");
let broken_link = app.fs.resolve_uri(&uri).join("broken-link.md");
symlink(&broken_target, &broken_link).expect("symlink");
app.reindex_uri_tree(&uri).expect("reindex");
let indexed = app.state.list_index_state_uris().expect("list index state");
assert!(
!indexed
.iter()
.any(|item| item == "axiom://resources/reindex-broken/broken-link.md")
);
}
#[cfg(unix)]
#[test]
fn reindex_uri_tree_does_not_follow_symlinked_external_files() {
let temp = tempdir().expect("tempdir");
let outside = tempdir().expect("outside");
let app = AxiomSync::new(temp.path()).expect("app new");
app.initialize().expect("init");
let uri = AxiomUri::parse("axiom://resources/reindex-symlink").expect("uri parse");
app.fs.create_dir_all(&uri, true).expect("mkdir");
let outside_file = outside.path().join("secret.md");
fs::write(&outside_file, "SYMLINK_ESCAPE_SENTINEL").expect("write outside file");
let inside_link = app.fs.resolve_uri(&uri).join("linked-secret.md");
symlink(&outside_file, &inside_link).expect("symlink file");
app.reindex_uri_tree(&uri).expect("reindex");
let indexed = app.state.list_index_state_uris().expect("list index state");
assert!(
!indexed
.iter()
.any(|item| item == "axiom://resources/reindex-symlink/linked-secret.md")
);
let result = app
.find(
"SYMLINK_ESCAPE_SENTINEL",
Some("axiom://resources/reindex-symlink"),
Some(5),
None,
None,
)
.expect("find");
assert!(!result.query_results.iter().any(|hit| {
hit.uri == "axiom://resources/reindex-symlink/linked-secret.md"
|| hit.abstract_text.contains("SYMLINK_ESCAPE_SENTINEL")
}));
}