use crate::path_resolver::WorkspaceResolver;
use anyhow::{Context, Result, bail};
use chrono::{DateTime, Utc};
use parking_lot::RwLock;
use sqry_core::graph::unified::concurrent::CodeGraph;
use sqry_core::graph::unified::persistence::{
GraphStorage, Manifest, load_from_bytes, verify_snapshot_bytes,
};
use sqry_core::plugin::PluginManager;
use sqry_core::query::QueryExecutor;
use sqry_plugin_registry::create_plugin_manager;
use std::path::{Path, PathBuf};
use std::sync::{Arc, OnceLock};
use std::time::SystemTime;
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct GraphIdentity {
pub snapshot_sha256: String,
pub built_at: DateTime<Utc>,
pub schema_version: u32,
pub snapshot_format_version: u32,
pub workspace_root: PathBuf,
}
#[derive(Clone, Debug)]
pub struct ManifestMetadata {
pub mtime: SystemTime,
pub size: u64,
pub file_id: Option<u64>,
}
pub struct CachedEngine {
pub engine: Arc<Engine>,
pub identity: GraphIdentity,
pub metadata: ManifestMetadata,
}
#[allow(dead_code)]
static ENGINE: RwLock<Option<Arc<Engine>>> = RwLock::new(None);
pub struct Engine {
workspace_root: PathBuf,
executor: QueryExecutor,
graph_cache: RwLock<Option<Arc<CodeGraph>>>,
}
impl Engine {
#[allow(dead_code)]
fn initialize() -> Result<Self> {
let workspace_root = resolve_workspace_root()?;
tracing::info!(
workspace_root = %workspace_root.display(),
"Engine initializing with workspace root"
);
let plugin_manager = build_plugin_manager();
let executor = QueryExecutor::with_plugin_manager(plugin_manager);
Ok(Self {
workspace_root,
executor,
graph_cache: RwLock::new(None),
})
}
#[allow(clippy::unnecessary_wraps)] pub fn for_workspace(workspace_root: PathBuf) -> Result<Self> {
tracing::info!(
workspace_root = %workspace_root.display(),
"Creating Engine for specific workspace"
);
let plugin_manager = build_plugin_manager();
let executor = QueryExecutor::with_plugin_manager(plugin_manager);
Ok(Self {
workspace_root,
executor,
graph_cache: RwLock::new(None),
})
}
pub fn workspace_root(&self) -> &Path {
&self.workspace_root
}
pub fn executor(&self) -> &QueryExecutor {
&self.executor
}
#[must_use]
pub fn plugin_manager() -> PluginManager {
build_plugin_manager()
}
pub fn graph(&self) -> Option<Arc<CodeGraph>> {
{
let cache = self.graph_cache.read();
if let Some(graph) = cache.as_ref() {
tracing::debug!("Returning cached graph");
return Some(graph.clone());
}
}
let storage = GraphStorage::new(&self.workspace_root);
let snapshot_path = storage.snapshot_path();
tracing::info!(
snapshot_path = %snapshot_path.display(),
exists = snapshot_path.exists(),
"Checking for unified graph snapshot"
);
if !storage.exists() {
tracing::warn!(
workspace_root = %self.workspace_root.display(),
"No unified graph snapshot found"
);
return None;
}
let expected_sha256 = if storage.manifest_path().exists() {
match std::fs::File::open(storage.manifest_path()).and_then(|f| {
serde_json::from_reader::<_, Manifest>(f)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
}) {
Ok(manifest) => manifest.snapshot_sha256,
Err(e) => {
tracing::warn!(
error = %e,
manifest_path = %storage.manifest_path().display(),
"Manifest exists but cannot be read/parsed — refusing to load snapshot"
);
return None;
}
}
} else {
String::new()
};
let snapshot_bytes = match std::fs::read(storage.snapshot_path()) {
Ok(bytes) => bytes,
Err(e) => {
tracing::warn!(
error = %e,
snapshot_path = %storage.snapshot_path().display(),
"Failed to read snapshot"
);
return None;
}
};
if let Err(e) = verify_snapshot_bytes(&snapshot_bytes, &expected_sha256) {
tracing::warn!(
error = %e,
snapshot_path = %storage.snapshot_path().display(),
"Snapshot integrity verification failed"
);
return None;
}
match load_from_bytes(&snapshot_bytes, Some(self.executor.plugin_manager())) {
Ok(graph) => {
let arc = Arc::new(graph);
let mut cache = self.graph_cache.write();
*cache = Some(arc.clone());
tracing::info!("Successfully loaded unified graph");
Some(arc)
}
Err(e) => {
tracing::error!(
error = %e,
snapshot_path = %storage.snapshot_path().display(),
"Failed to load unified graph snapshot"
);
None
}
}
}
pub fn ensure_graph(&self) -> Result<Arc<CodeGraph>> {
if let Some(graph) = self.graph() {
return Ok(graph);
}
if !is_auto_index_enabled() {
bail!(
"No unified graph found. Auto-indexing is disabled (SQRY_AUTO_INDEX=false). \
Run `sqry index` to create the graph."
);
}
tracing::info!(
workspace = %self.workspace_root.display(),
"Auto-building graph index (no existing snapshot found)"
);
let plugins = create_plugin_manager();
let config = sqry_core::graph::unified::build::BuildConfig::default();
let (graph, _build_result) = sqry_core::graph::unified::build::build_and_persist_graph(
&self.workspace_root,
&plugins,
&config,
"mcp:auto_index",
)?;
let arc = Arc::new(graph);
let mut cache = self.graph_cache.write();
*cache = Some(arc.clone());
Ok(arc)
}
#[allow(dead_code)]
pub fn clear_graph_cache(&self) {
let mut cache = self.graph_cache.write();
*cache = None;
}
}
#[allow(dead_code)]
pub fn engine() -> Result<Arc<Engine>> {
{
let guard = ENGINE.read();
if let Some(ref engine) = *guard {
return Ok(engine.clone());
}
}
let mut guard = ENGINE.write();
if let Some(ref engine) = *guard {
return Ok(engine.clone());
}
let new_engine = Arc::new(Engine::initialize()?);
*guard = Some(new_engine.clone());
Ok(new_engine)
}
pub fn engine_for_workspace(explicit_path: Option<&PathBuf>) -> Result<Arc<Engine>> {
if let Some(workspace_root) = crate::workspace_session::current_workspace_override() {
return engine_for_workspace_root(&workspace_root);
}
let workspace_root = if let Some(path) = explicit_path {
crate::path_resolver::resolve_workspace_path(&path.to_string_lossy())?
} else {
let resolver = WorkspaceResolver::new(None);
resolver.resolve()?
};
engine_for_workspace_root(&workspace_root)
}
fn engine_for_workspace_root(workspace_root: &Path) -> Result<Arc<Engine>> {
if !workspace_root.is_absolute() {
bail!(
"BUG: engine_for_workspace requires canonical path, got: {}",
workspace_root.display()
);
}
if let Some(engine) = get_cached_engine(workspace_root)? {
tracing::debug!(
workspace = %workspace_root.display(),
"Using cached engine"
);
return Ok(engine);
}
tracing::info!(
workspace = %workspace_root.display(),
"Loading fresh engine (cache miss or stale)"
);
let engine = Arc::new(Engine::for_workspace(workspace_root.to_path_buf())?);
match read_graph_identity_with_metadata(workspace_root) {
Ok((identity, metadata)) => {
let mut cache = ENGINE_CACHE.lock();
let lru = cache
.as_mut()
.context("Engine cache not initialized - call init_engine_cache() first")?;
lru.put(
workspace_root.to_path_buf(),
CachedEngine {
engine: Arc::clone(&engine),
identity,
metadata,
},
);
tracing::debug!(
workspace = %workspace_root.display(),
cache_size = lru.len(),
"Engine cached"
);
}
Err(e) => {
tracing::info!(
workspace = %workspace_root.display(),
error = %e,
"No manifest found — engine created without cache identity \
(auto-index will create it on first tool call)"
);
}
}
Ok(engine)
}
#[allow(dead_code)]
pub fn resolve_workspace_root() -> Result<PathBuf> {
let root = std::env::var("SQRY_MCP_WORKSPACE_ROOT").ok();
let root_path = match root {
Some(r) => PathBuf::from(r),
None => std::env::current_dir().context("Failed to get current directory")?,
};
let canon = std::fs::canonicalize(&root_path).with_context(|| {
format!(
"Failed to canonicalize workspace root: {}",
root_path.display()
)
})?;
Ok(canon)
}
pub fn canonicalize_in_workspace(path_str: &str, workspace_root: &Path) -> Result<PathBuf> {
let input_path = Path::new(path_str);
let joined = if input_path.is_absolute() {
input_path.to_path_buf()
} else {
workspace_root.join(input_path)
};
let normalized = normalize_path(&joined);
if !normalized.starts_with(workspace_root) {
bail!(
"Path '{}' is outside of the workspace root '{}'",
normalized.display(),
workspace_root.display()
);
}
let canon = std::fs::canonicalize(&joined).map_err(|e| {
anyhow::anyhow!("Failed to canonicalize path: {} ({})", joined.display(), e)
})?;
let canon_root =
std::fs::canonicalize(workspace_root).unwrap_or_else(|_| workspace_root.to_path_buf());
if !canon.starts_with(&canon_root) {
bail!(
"Path '{}' is outside of the workspace root '{}'",
canon.display(),
canon_root.display()
);
}
Ok(canon)
}
fn normalize_path(path: &Path) -> PathBuf {
let mut components = Vec::new();
for component in path.components() {
match component {
std::path::Component::ParentDir => {
if !components.is_empty() {
components.pop();
}
}
std::path::Component::CurDir => {
}
comp => {
components.push(comp);
}
}
}
components.iter().collect()
}
fn build_plugin_manager() -> PluginManager {
create_plugin_manager()
}
fn is_auto_index_enabled() -> bool {
if let Ok(val) = std::env::var("SQRY_AUTO_INDEX") {
return val != "false" && val != "0";
}
true
}
#[allow(dead_code)]
pub static WORKSPACE_LOCK: OnceLock<RwLock<()>> = OnceLock::new();
#[allow(dead_code)]
pub fn workspace_lock() -> &'static RwLock<()> {
WORKSPACE_LOCK.get_or_init(|| RwLock::new(()))
}
pub fn read_graph_identity(workspace: &Path) -> Result<GraphIdentity> {
let manifest_path = workspace.join(".sqry/graph/manifest.json");
let file = std::fs::File::open(&manifest_path).with_context(|| {
format!(
"Manifest missing - run `sqry index` in workspace: {}",
workspace.display()
)
})?;
let manifest: sqry_core::graph::unified::persistence::Manifest = serde_json::from_reader(file)
.context("Failed to parse manifest.json - index may be corrupt")?;
let canonical_workspace = std::fs::canonicalize(workspace)?;
let manifest_root_path = PathBuf::from(&manifest.root_path);
let manifest_root = if manifest_root_path.is_absolute() {
std::fs::canonicalize(&manifest_root_path)?
} else {
std::fs::canonicalize(workspace.join(&manifest_root_path))?
};
if canonical_workspace != manifest_root {
bail!(
"Manifest root_path mismatch: expected {}, got {}. \
Possible symlinked .sqry/graph from different repo.",
canonical_workspace.display(),
manifest_root.display()
);
}
let built_at = DateTime::parse_from_rfc3339(&manifest.built_at)
.with_context(|| {
format!(
"Invalid built_at timestamp in manifest: {}",
manifest.built_at
)
})?
.with_timezone(&Utc);
Ok(GraphIdentity {
snapshot_sha256: manifest.snapshot_sha256,
built_at,
schema_version: manifest.schema_version,
snapshot_format_version: manifest.snapshot_format_version,
workspace_root: canonical_workspace,
})
}
#[allow(dead_code)]
pub fn read_manifest_metadata(workspace: &Path) -> Result<ManifestMetadata> {
let manifest_path = workspace.join(".sqry/graph/manifest.json");
let metadata = std::fs::metadata(&manifest_path).context("Failed to stat manifest.json")?;
let file_id = extract_file_id(&metadata);
Ok(ManifestMetadata {
mtime: metadata.modified()?,
size: metadata.len(),
file_id,
})
}
pub fn read_graph_identity_with_metadata(
workspace: &Path,
) -> Result<(GraphIdentity, ManifestMetadata)> {
let manifest_path = workspace.join(".sqry/graph/manifest.json");
let file = std::fs::File::open(&manifest_path).with_context(|| {
format!(
"Manifest missing - run `sqry index` in workspace: {}",
workspace.display()
)
})?;
let file_metadata = file
.metadata()
.context("Failed to stat manifest.json from open file handle")?;
let manifest: sqry_core::graph::unified::persistence::Manifest = serde_json::from_reader(file)
.context("Failed to parse manifest.json - index may be corrupt")?;
let canonical_workspace = std::fs::canonicalize(workspace)?;
let manifest_root_path = PathBuf::from(&manifest.root_path);
let manifest_root = if manifest_root_path.is_absolute() {
std::fs::canonicalize(&manifest_root_path)?
} else {
std::fs::canonicalize(workspace.join(&manifest_root_path))?
};
if canonical_workspace != manifest_root {
bail!(
"Manifest root_path mismatch: expected {}, got {}. \
Possible symlinked .sqry/graph from different repo.",
canonical_workspace.display(),
manifest_root.display()
);
}
let built_at = DateTime::parse_from_rfc3339(&manifest.built_at)
.with_context(|| {
format!(
"Invalid built_at timestamp in manifest: {}",
manifest.built_at
)
})?
.with_timezone(&Utc);
let identity = GraphIdentity {
snapshot_sha256: manifest.snapshot_sha256,
built_at,
schema_version: manifest.schema_version,
snapshot_format_version: manifest.snapshot_format_version,
workspace_root: canonical_workspace,
};
let file_id = extract_file_id(&file_metadata);
let metadata = ManifestMetadata {
mtime: file_metadata.modified()?,
size: file_metadata.len(),
file_id,
};
Ok((identity, metadata))
}
#[cfg(unix)]
#[allow(clippy::unnecessary_wraps)] fn extract_file_id(metadata: &std::fs::Metadata) -> Option<u64> {
use std::os::unix::fs::MetadataExt;
Some(metadata.ino())
}
#[cfg(windows)]
#[allow(clippy::unnecessary_wraps)] fn extract_file_id(_metadata: &std::fs::Metadata) -> Option<u64> {
None
}
#[cfg(not(any(unix, windows)))]
fn extract_file_id(_metadata: &std::fs::Metadata) -> Option<u64> {
None }
static ENGINE_CACHE: parking_lot::Mutex<Option<lru::LruCache<PathBuf, CachedEngine>>> =
parking_lot::Mutex::new(None);
pub fn init_engine_cache(capacity: std::num::NonZeroUsize) {
let mut cache = ENGINE_CACHE.lock();
if cache.is_none() {
tracing::info!(capacity = capacity.get(), "Initializing engine cache");
*cache = Some(lru::LruCache::new(capacity));
}
}
fn get_cached_engine(workspace: &Path) -> Result<Option<Arc<Engine>>> {
let (cached_engine, cached_identity, cached_metadata) = {
let mut cache = ENGINE_CACHE.lock();
let lru = cache
.as_mut()
.context("Engine cache not initialized - call init_engine_cache() first")?;
if let Some(cached) = lru.get(workspace) {
(
Arc::clone(&cached.engine),
cached.identity.clone(),
cached.metadata.clone(),
)
} else {
return Ok(None);
}
};
if is_manifest_fresh(&cached_metadata, workspace)? {
tracing::debug!(
workspace = %workspace.display(),
"Engine cache hit (fresh)"
);
return Ok(Some(cached_engine));
}
tracing::debug!(
workspace = %workspace.display(),
"Manifest changed, reloading identity"
);
let (new_identity, new_metadata) = read_graph_identity_with_metadata(workspace)?;
let mut cache = ENGINE_CACHE.lock();
let Some(lru) = cache.as_mut() else {
return Ok(None);
};
if let Some(current) = lru.get(workspace) {
if current.identity != cached_identity {
tracing::debug!(
workspace = %workspace.display(),
"Another thread updated cache, using their engine"
);
return Ok(Some(Arc::clone(¤t.engine)));
}
} else {
tracing::debug!(
workspace = %workspace.display(),
"Cache entry evicted during reload"
);
return Ok(None);
}
if new_identity == cached_identity {
tracing::debug!(
workspace = %workspace.display(),
"GraphIdentity unchanged, updating metadata only"
);
if let Some(cached) = lru.get_mut(workspace) {
cached.metadata = new_metadata;
}
Ok(Some(cached_engine))
} else {
tracing::info!(
workspace = %workspace.display(),
old_sha = %cached_identity.snapshot_sha256,
new_sha = %new_identity.snapshot_sha256,
"GraphIdentity changed, invalidating cache"
);
lru.pop(workspace);
Ok(None)
}
}
fn is_manifest_fresh(cached: &ManifestMetadata, workspace: &Path) -> Result<bool> {
let manifest_path = workspace.join(".sqry/graph/manifest.json");
let current = std::fs::metadata(&manifest_path)
.context("Failed to stat manifest.json for freshness check")?;
Ok(current.modified()? == cached.mtime
&& current.len() == cached.size
&& extract_file_id(¤t) == cached.file_id)
}
pub fn get_graph_identity(workspace: &Path) -> Result<GraphIdentity> {
{
let cache = ENGINE_CACHE.lock();
let lru = cache
.as_ref()
.context("Engine cache not initialized - call init_engine_cache() first")?;
if let Some(cached) = lru.peek(workspace) {
if is_manifest_fresh(&cached.metadata, workspace).unwrap_or(false) {
tracing::debug!(
workspace = %workspace.display(),
"Returning cached GraphIdentity"
);
return Ok(cached.identity.clone());
}
}
}
tracing::debug!(
workspace = %workspace.display(),
"Reading GraphIdentity from manifest"
);
read_graph_identity(workspace)
}
#[cfg(test)]
mod engine_cache_tests {
use super::*;
use std::io::Write;
use tempfile::TempDir;
fn reset_engine_cache() {
let mut cache = ENGINE_CACHE.lock();
*cache = None;
}
fn create_test_workspace() -> Result<TempDir> {
let temp_dir = TempDir::new()?;
let graph_dir = temp_dir.path().join(".sqry/graph");
std::fs::create_dir_all(&graph_dir)?;
let manifest = serde_json::json!({
"schema_version": 1,
"snapshot_format_version": 1,
"built_at": "2026-01-01T00:00:00Z",
"root_path": temp_dir.path().to_string_lossy(),
"node_count": 0,
"edge_count": 0,
"snapshot_sha256": "aaaa",
"build_provenance": {
"sqry_version": "4.10.0",
"build_timestamp": "2026-01-01T00:00:00Z",
"build_command": "test"
}
});
let manifest_path = graph_dir.join("manifest.json");
let mut file = std::fs::File::create(&manifest_path)?;
file.write_all(serde_json::to_string_pretty(&manifest)?.as_bytes())?;
file.sync_all()?;
Ok(temp_dir)
}
#[test]
fn test_manifest_freshness_detection() -> Result<()> {
let workspace = create_test_workspace()?;
let workspace_path = workspace.path();
let metadata1 = read_manifest_metadata(workspace_path)?;
assert!(is_manifest_fresh(&metadata1, workspace_path)?);
let manifest_path = workspace_path.join(".sqry/graph/manifest.json");
std::thread::sleep(std::time::Duration::from_millis(10)); let mut file = std::fs::OpenOptions::new()
.write(true)
.truncate(true)
.open(&manifest_path)?;
let new_manifest = serde_json::json!({
"schema_version": 1,
"snapshot_format_version": 1,
"built_at": "2026-01-02T00:00:00Z",
"root_path": workspace_path.to_string_lossy(),
"node_count": 0,
"edge_count": 0,
"snapshot_sha256": "bbbb",
"build_provenance": {
"sqry_version": "4.10.0",
"build_timestamp": "2026-01-02T00:00:00Z",
"build_command": "test"
}
});
file.write_all(serde_json::to_string_pretty(&new_manifest)?.as_bytes())?;
file.sync_all()?;
assert!(!is_manifest_fresh(&metadata1, workspace_path)?);
Ok(())
}
#[test]
#[serial_test::serial(engine_cache)]
fn test_cache_requires_initialization() {
reset_engine_cache();
let temp_dir = TempDir::new().unwrap();
let result = get_cached_engine(temp_dir.path());
match result {
Err(e) => assert!(e.to_string().contains("not initialized")),
Ok(_) => panic!("Expected error, got success"),
}
}
#[test]
#[serial_test::serial(engine_cache)]
fn test_cache_miss_returns_none() -> Result<()> {
init_engine_cache(std::num::NonZeroUsize::new(5).unwrap());
let temp_dir = TempDir::new()?;
let workspace_path = temp_dir.path();
let result = get_cached_engine(workspace_path)?;
assert!(result.is_none());
Ok(())
}
#[test]
fn test_normalize_path_resolves_parent_dir() {
let path = std::path::Path::new("/workspace/src/../lib");
let normalized = normalize_path(path);
assert_eq!(normalized, std::path::PathBuf::from("/workspace/lib"));
}
#[test]
fn test_normalize_path_resolves_current_dir() {
let path = std::path::Path::new("/workspace/./src");
let normalized = normalize_path(path);
assert_eq!(normalized, std::path::PathBuf::from("/workspace/src"));
}
#[test]
fn test_normalize_path_handles_multiple_traversals() {
let path = std::path::Path::new("/a/b/c/../../d");
let normalized = normalize_path(path);
assert_eq!(normalized, std::path::PathBuf::from("/a/d"));
}
#[test]
fn test_normalize_path_at_root_ignores_parent() {
let path = std::path::Path::new("/workspace");
let normalized = normalize_path(path);
assert_eq!(normalized, std::path::PathBuf::from("/workspace"));
}
#[test]
fn test_normalize_path_simple_path_unchanged() {
let path = std::path::Path::new("/workspace/src/lib.rs");
let normalized = normalize_path(path);
assert_eq!(
normalized,
std::path::PathBuf::from("/workspace/src/lib.rs")
);
}
#[test]
fn test_canonicalize_in_workspace_dot_returns_workspace_root() -> Result<()> {
let temp = TempDir::new()?;
let workspace = temp.path();
let result = canonicalize_in_workspace(".", workspace)?;
assert_eq!(result, workspace.canonicalize()?);
Ok(())
}
#[test]
fn test_canonicalize_in_workspace_outside_path_rejected() {
let temp = TempDir::new().unwrap();
let workspace = temp.path();
let result = canonicalize_in_workspace("../../etc/passwd", workspace);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("outside") || err.contains("Failed to canonicalize"));
}
#[test]
fn test_canonicalize_in_workspace_absolute_outside_rejected() {
let temp = TempDir::new().unwrap();
let workspace = temp.path();
let result = canonicalize_in_workspace("/etc/passwd", workspace);
assert!(result.is_err());
}
#[test]
fn test_canonicalize_in_workspace_valid_subdir() -> Result<()> {
let temp = TempDir::new()?;
let workspace = temp.path();
let subdir = workspace.join("src");
std::fs::create_dir(&subdir)?;
let result = canonicalize_in_workspace("src", workspace)?;
assert_eq!(result, subdir.canonicalize()?);
Ok(())
}
#[test]
fn test_read_graph_identity_valid_manifest() -> Result<()> {
let workspace = create_test_workspace()?;
let workspace_path = workspace.path();
let identity = read_graph_identity(workspace_path)?;
assert!(!identity.snapshot_sha256.is_empty());
assert_eq!(identity.schema_version, 1);
assert_eq!(identity.snapshot_format_version, 1);
Ok(())
}
#[test]
fn test_read_graph_identity_missing_manifest_errors() {
let temp = TempDir::new().unwrap();
let result = read_graph_identity(temp.path());
assert!(result.is_err());
let msg = result.unwrap_err().to_string();
assert!(msg.contains("Manifest missing") || msg.contains("run `sqry index`"));
}
#[test]
fn test_read_graph_identity_with_metadata_returns_both() -> Result<()> {
let workspace = create_test_workspace()?;
let workspace_path = workspace.path();
let (identity, metadata) = read_graph_identity_with_metadata(workspace_path)?;
assert!(!identity.snapshot_sha256.is_empty());
assert!(metadata.size > 0);
Ok(())
}
#[test]
#[serial_test::serial(engine_cache)]
fn test_init_engine_cache_idempotent() {
reset_engine_cache();
let cap = std::num::NonZeroUsize::new(4).unwrap();
init_engine_cache(cap);
init_engine_cache(cap);
let temp = TempDir::new().unwrap();
let result = get_cached_engine(temp.path());
assert!(result.is_ok());
assert!(result.unwrap().is_none()); }
#[test]
#[serial_test::serial(sqry_auto_index_env)]
fn test_is_auto_index_enabled_defaults_true() {
unsafe { std::env::remove_var("SQRY_AUTO_INDEX") };
assert!(is_auto_index_enabled());
}
#[test]
#[serial_test::serial(sqry_auto_index_env)]
fn test_is_auto_index_enabled_false_when_set_to_false() {
unsafe { std::env::set_var("SQRY_AUTO_INDEX", "false") };
assert!(!is_auto_index_enabled());
unsafe { std::env::remove_var("SQRY_AUTO_INDEX") };
}
#[test]
#[serial_test::serial(sqry_auto_index_env)]
fn test_is_auto_index_enabled_false_when_set_to_zero() {
unsafe { std::env::set_var("SQRY_AUTO_INDEX", "0") };
assert!(!is_auto_index_enabled());
unsafe { std::env::remove_var("SQRY_AUTO_INDEX") };
}
#[test]
#[serial_test::serial(sqry_auto_index_env)]
fn test_is_auto_index_enabled_true_when_set_to_true() {
unsafe { std::env::set_var("SQRY_AUTO_INDEX", "true") };
assert!(is_auto_index_enabled());
unsafe { std::env::remove_var("SQRY_AUTO_INDEX") };
}
#[test]
fn test_engine_for_workspace_sets_root() -> Result<()> {
let temp = TempDir::new()?;
let workspace = temp.path().to_path_buf();
let engine = Engine::for_workspace(workspace.clone())?;
assert_eq!(engine.workspace_root(), workspace.as_path());
Ok(())
}
#[test]
fn test_engine_graph_returns_none_without_snapshot() -> Result<()> {
let temp = TempDir::new()?;
let engine = Engine::for_workspace(temp.path().to_path_buf())?;
let graph = engine.graph();
assert!(graph.is_none());
Ok(())
}
#[test]
fn test_graph_identity_equality() -> Result<()> {
let workspace = create_test_workspace()?;
let workspace_path = workspace.path();
let id1 = read_graph_identity(workspace_path)?;
let id2 = read_graph_identity(workspace_path)?;
assert_eq!(id1, id2);
Ok(())
}
#[test]
fn test_read_manifest_metadata_valid() -> Result<()> {
let workspace = create_test_workspace()?;
let metadata = read_manifest_metadata(workspace.path())?;
assert!(metadata.size > 0);
Ok(())
}
#[test]
fn test_read_manifest_metadata_missing_errors() {
let temp = TempDir::new().unwrap();
let result = read_manifest_metadata(temp.path());
assert!(result.is_err());
}
#[test]
#[serial_test::serial(engine_cache)]
fn test_get_graph_identity_not_initialized_errors() {
reset_engine_cache();
let temp = TempDir::new().unwrap();
let result = get_graph_identity(temp.path());
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not initialized"));
}
#[test]
#[serial_test::serial(engine_cache)]
fn test_get_graph_identity_falls_back_to_manifest() -> Result<()> {
init_engine_cache(std::num::NonZeroUsize::new(4).unwrap());
let workspace = create_test_workspace()?;
let identity = get_graph_identity(workspace.path())?;
assert!(!identity.snapshot_sha256.is_empty());
Ok(())
}
#[test]
fn test_workspace_lock_returns_same_instance() {
let lock1 = workspace_lock();
let lock2 = workspace_lock();
assert!(std::ptr::eq(lock1, lock2));
}
#[test]
fn test_is_manifest_fresh_missing_file_errors() {
let temp = TempDir::new().unwrap();
let _manifest_path = temp.path().join(".sqry/graph/manifest.json");
let fake_metadata = ManifestMetadata {
mtime: std::time::SystemTime::now(),
size: 100,
file_id: None,
};
let result = is_manifest_fresh(&fake_metadata, temp.path());
assert!(result.is_err());
}
#[test]
fn test_engine_graph_rejects_corrupted_snapshot() {
let temp = tempfile::tempdir().unwrap();
let graph_dir = temp.path().join(".sqry/graph");
std::fs::create_dir_all(&graph_dir).unwrap();
let snapshot_data = b"fake snapshot data";
std::fs::write(graph_dir.join("snapshot.sqry"), snapshot_data).unwrap();
let manifest = serde_json::json!({
"root_path": temp.path().to_string_lossy(),
"node_count": 0,
"edge_count": 0,
"snapshot_sha256": "0000000000000000000000000000000000000000000000000000000000000000",
"built_at": "2026-01-01T00:00:00+00:00",
"schema_version": 5,
"snapshot_format_version": 5,
"build_provenance": { "sqry_version": "test", "rustc_version": "test" }
});
std::fs::write(
graph_dir.join("manifest.json"),
serde_json::to_string_pretty(&manifest).unwrap(),
)
.unwrap();
let engine =
Engine::for_workspace(temp.path().to_path_buf()).expect("engine should create");
assert!(
engine.graph().is_none(),
"Engine::graph() should return None when snapshot hash is wrong"
);
}
#[test]
fn test_engine_graph_rejects_corrupt_manifest() {
let temp = tempfile::tempdir().unwrap();
let graph_dir = temp.path().join(".sqry/graph");
std::fs::create_dir_all(&graph_dir).unwrap();
std::fs::write(graph_dir.join("snapshot.sqry"), b"some data").unwrap();
std::fs::write(graph_dir.join("manifest.json"), b"not valid json!!!").unwrap();
let engine =
Engine::for_workspace(temp.path().to_path_buf()).expect("engine should create");
assert!(
engine.graph().is_none(),
"Engine::graph() must return None when manifest is corrupt (fail closed)"
);
}
#[test]
fn test_engine_graph_accepts_empty_hash() {
let temp = tempfile::tempdir().unwrap();
let graph_dir = temp.path().join(".sqry/graph");
std::fs::create_dir_all(&graph_dir).unwrap();
std::fs::write(graph_dir.join("snapshot.sqry"), b"not a real snapshot").unwrap();
let manifest = serde_json::json!({
"root_path": temp.path().to_string_lossy(),
"node_count": 0,
"edge_count": 0,
"snapshot_sha256": "",
"built_at": "2026-01-01T00:00:00+00:00",
"schema_version": 5,
"snapshot_format_version": 5,
"build_provenance": { "sqry_version": "test", "rustc_version": "test" }
});
std::fs::write(
graph_dir.join("manifest.json"),
serde_json::to_string_pretty(&manifest).unwrap(),
)
.unwrap();
let engine =
Engine::for_workspace(temp.path().to_path_buf()).expect("engine should create");
assert!(
engine.graph().is_none(),
"Should return None (deserialization fails on garbage data, but integrity check skipped)"
);
}
}