1use std::path::Path;
2use std::sync::{Arc, Mutex};
3use std::time::Duration;
4
5use crate::ast_parser::AstParser;
6use crate::budget_tracker::{BudgetTracker, UsageReport};
7use crate::cache_manager::CacheManager;
8use crate::confidence_router::ConfidenceRouter;
9use crate::cost_calculator::{CostCalculator, SessionCostSummary};
10use crate::ctx_format::CtxFormat;
11use crate::error::{Result, SqzError};
12use crate::model_router::ModelRouter;
13use crate::pin_manager::PinManager;
14use crate::pipeline::CompressionPipeline;
15use crate::plugin_api::PluginLoader;
16use crate::preset::{Preset, PresetParser};
17use crate::session_store::{SessionStore, SessionSummary};
18use crate::terse_mode::TerseMode;
19use crate::types::{CompressedContent, PinEntry, Provenance, SessionId};
20use crate::verifier::Verifier;
21
22pub struct SqzEngine {
33 preset: Arc<Mutex<Preset>>,
35 pipeline: Arc<Mutex<CompressionPipeline>>,
36 model_router: Arc<Mutex<ModelRouter>>,
37
38 session_store: SessionStore,
40 cache_manager: CacheManager,
41 budget_tracker: BudgetTracker,
42 cost_calculator: CostCalculator,
43 ast_parser: AstParser,
44 terse_mode: TerseMode,
45 pin_manager: PinManager,
46 confidence_router: ConfidenceRouter,
47 _plugin_loader: PluginLoader,
48}
49
50impl SqzEngine {
51 pub fn new() -> Result<Self> {
56 let preset = Preset::default();
57 let store_path = Self::default_store_path();
58 Self::with_preset_and_store(preset, &store_path)
59 }
60
61 fn default_store_path() -> std::path::PathBuf {
70 if let Some(custom) = Self::store_path_from_env(std::env::var("SQZ_DB_PATH").ok()) {
71 return custom;
72 }
73 if let Some(home) = dirs_next::home_dir() {
74 let sqz_dir = home.join(".sqz");
75 if std::fs::create_dir_all(&sqz_dir).is_ok() {
76 #[cfg(unix)]
79 {
80 use std::os::unix::fs::PermissionsExt;
81 let _ = std::fs::set_permissions(
82 &sqz_dir,
83 std::fs::Permissions::from_mode(0o700),
84 );
85 }
86 return sqz_dir.join("sessions.db");
87 }
88 }
89 let dir = std::env::temp_dir();
91 dir.join(format!(
92 "sqz_session_{}_{}.db",
93 std::process::id(),
94 std::time::SystemTime::now()
95 .duration_since(std::time::UNIX_EPOCH)
96 .map(|d| d.as_nanos())
97 .unwrap_or(0)
98 ))
99 }
100
101 fn store_path_from_env(value: Option<String>) -> Option<std::path::PathBuf> {
105 let raw = value?;
106 let trimmed = raw.trim();
107 if trimmed.is_empty() {
108 return None;
109 }
110 let path = std::path::PathBuf::from(trimmed);
111 if let Some(parent) = path.parent() {
112 if !parent.as_os_str().is_empty() && !parent.exists() {
113 if std::fs::create_dir_all(parent).is_ok() {
116 #[cfg(unix)]
117 {
118 use std::os::unix::fs::PermissionsExt;
119 let _ = std::fs::set_permissions(
120 parent,
121 std::fs::Permissions::from_mode(0o700),
122 );
123 }
124 }
125 }
126 }
127 Some(path)
128 }
129
130 pub fn quiet_stderr() -> bool {
138 match std::env::var("SQZ_QUIET") {
139 Ok(v) => !v.is_empty() && v != "0",
140 Err(_) => false,
141 }
142 }
143
144 fn ref_ttl_from_env(value: Option<String>) -> Duration {
151 const DEFAULT: Duration = Duration::from_secs(30 * 60);
152 match value.as_deref().map(str::trim) {
153 Some(raw) if !raw.is_empty() => raw
154 .parse::<u64>()
155 .map(Duration::from_secs)
156 .unwrap_or(DEFAULT),
157 _ => DEFAULT,
158 }
159 }
160
161 pub fn with_preset_and_store(preset: Preset, store_path: &Path) -> Result<Self> {
167 let pipeline = CompressionPipeline::new(&preset);
168 let window_size = preset.budget.default_window_size;
169
170 let session_store = SessionStore::open_or_create(store_path)?;
172 let cache_store = SessionStore::open_or_create(store_path)?;
173 let pin_store = SessionStore::open_or_create(store_path)?;
174
175 Ok(SqzEngine {
176 preset: Arc::new(Mutex::new(preset.clone())),
177 pipeline: Arc::new(Mutex::new(pipeline)),
178 model_router: Arc::new(Mutex::new(ModelRouter::new(&preset))),
179 session_store,
180 cache_manager: CacheManager::with_ref_age_duration(
181 cache_store,
182 512 * 1024 * 1024,
183 Self::ref_ttl_from_env(std::env::var("SQZ_REF_TTL_SECS").ok()),
184 ),
185 budget_tracker: BudgetTracker::new(window_size, &preset),
186 cost_calculator: CostCalculator::with_defaults(),
187 ast_parser: AstParser::new(),
188 terse_mode: TerseMode,
189 pin_manager: PinManager::new(pin_store),
190 confidence_router: ConfidenceRouter::new(),
191 _plugin_loader: PluginLoader::new(Path::new("plugins")),
192 })
193 }
194
195 pub fn compress(&self, input: &str) -> Result<CompressedContent> {
203 let preset = self.preset.lock()
204 .map_err(|_| SqzError::Other("preset lock poisoned".into()))?;
205 let pipeline = self.pipeline.lock()
206 .map_err(|_| SqzError::Other("pipeline lock poisoned".into()))?;
207 let ctx = crate::pipeline::SessionContext {
208 session_id: "engine".to_string(),
209 };
210
211 let mode = self.confidence_router.route(input);
213
214 if mode == crate::confidence_router::CompressionMode::Safe {
216 if std::env::var_os("SQZ_VERBOSE").is_some() {
219 eprintln!("[sqz] safe mode: high-risk content passed through unchanged");
220 }
221 return self.compress_safe(input, &pipeline, &ctx);
222 }
223
224 let mut result = pipeline.compress(input, &ctx, &preset)?;
226
227 let verify = Verifier::verify(input, &result.data);
229 let fallback = verify.fallback_triggered;
230 result.verify = Some(verify);
231
232 if fallback && result.data != input {
234 if !Self::quiet_stderr() {
235 eprintln!("[sqz] fallback: verifier confidence {:.2} below threshold — re-compressing in safe mode",
236 result.verify.as_ref().map(|v| v.confidence).unwrap_or(0.0));
237 }
238 let safe_result = self.compress_safe(input, &pipeline, &ctx)?;
239 return Ok(safe_result);
240 }
241
242 Ok(result)
243 }
244
245 pub fn compress_with_cache(&self, input: &str) -> Result<crate::cache_manager::CacheResult> {
261 let pipeline = self.pipeline.lock()
262 .map_err(|_| SqzError::Other("pipeline lock poisoned".into()))?;
263 self.cache_manager.get_or_compress(
269 std::path::Path::new(""),
270 input.as_bytes(),
271 &pipeline,
272 )
273 }
274
275 pub fn compress_with_cache_lossless(&self, input: &str) -> Result<crate::cache_manager::CacheResult> {
281 let pipeline = self.pipeline.lock()
282 .map_err(|_| SqzError::Other("pipeline lock poisoned".into()))?;
283 self.cache_manager.get_or_compress_lossless(
284 std::path::Path::new(""),
285 input.as_bytes(),
286 &pipeline,
287 )
288 }
289
290 pub fn compress_or_passthrough(&self, input: &str) -> CompressedContent {
298 match self.compress(input) {
299 Ok(result) => result,
300 Err(_) => {
301 let tokens = (input.len() as u32 + 3) / 4;
302 CompressedContent {
303 data: input.to_string(),
304 tokens_compressed: tokens,
305 tokens_original: tokens,
306 stages_applied: vec![],
307 compression_ratio: 1.0,
308 provenance: crate::types::Provenance::default(),
309 verify: None,
310 }
311 }
312 }
313 }
314
315 pub fn compress_with_mode(&self, input: &str, mode: crate::confidence_router::CompressionMode) -> Result<CompressedContent> {
321 let pipeline = self.pipeline.lock()
322 .map_err(|_| SqzError::Other("pipeline lock poisoned".into()))?;
323 let ctx = crate::pipeline::SessionContext {
324 session_id: "engine".to_string(),
325 };
326
327 match mode {
328 crate::confidence_router::CompressionMode::Safe => {
329 self.compress_safe(input, &pipeline, &ctx)
330 }
331 _ => {
332 drop(pipeline); self.compress(input)
335 }
336 }
337 }
338
339 fn compress_safe(
341 &self,
342 input: &str,
343 pipeline: &crate::pipeline::CompressionPipeline,
344 ctx: &crate::pipeline::SessionContext,
345 ) -> Result<CompressedContent> {
346 use crate::preset::{
347 CompressionConfig, CondenseConfig, CustomTransformsConfig, BudgetConfig,
348 ModelConfig, PresetMeta, TerseModeConfig, TerseLevel, ToolSelectionConfig,
349 };
350
351 let safe_preset = Preset {
352 preset: PresetMeta {
353 name: "safe".to_string(),
354 version: "1.0".to_string(),
355 description: "Safe fallback — minimal compression".to_string(),
356 },
357 compression: CompressionConfig {
358 stages: vec!["condense".to_string()],
359 keep_fields: None,
360 strip_fields: None,
361 condense: Some(CondenseConfig { enabled: true, max_repeated_lines: 3 }),
362 git_diff_fold: None,
363 strip_nulls: None,
364 flatten: None,
365 truncate_strings: None,
366 collapse_arrays: None,
367 custom_transforms: Some(CustomTransformsConfig { enabled: false }),
368 },
369 tool_selection: ToolSelectionConfig {
370 max_tools: 5,
371 similarity_threshold: 0.7,
372 default_tools: vec![],
373 },
374 budget: BudgetConfig {
375 warning_threshold: 0.70,
376 ceiling_threshold: 0.85,
377 default_window_size: 200_000,
378 agents: Default::default(),
379 },
380 terse_mode: TerseModeConfig { enabled: false, level: TerseLevel::Moderate },
381 model: ModelConfig {
382 family: "anthropic".to_string(),
383 primary: String::new(),
384 local: String::new(),
385 complexity_threshold: 0.4,
386 pricing: None,
387 },
388 };
389
390 let mut result = pipeline.compress(input, ctx, &safe_preset)?;
391 let verify = Verifier::verify(input, &result.data);
392 result.verify = Some(verify);
393 result.provenance = Provenance {
394 label: Some("safe-fallback".to_string()),
395 ..Default::default()
396 };
397 Ok(result)
398 }
399
400 pub fn compress_with_provenance(
402 &self,
403 input: &str,
404 provenance: Provenance,
405 ) -> Result<CompressedContent> {
406 let mut result = self.compress(input)?;
407 result.provenance = provenance;
408 Ok(result)
409 }
410
411 pub fn export_ctx(&self, session_id: &str) -> Result<String> {
413 let session = self.session_store.load_session(session_id.to_string())?;
414 CtxFormat::serialize(&session)
415 }
416
417 pub fn import_ctx(&self, ctx: &str) -> Result<SessionId> {
419 let session = CtxFormat::deserialize(ctx)?;
420 self.session_store.save_session(&session)
421 }
422
423 pub fn pin(&self, session_id: &str, turn_index: usize, reason: &str, tokens: u32) -> Result<PinEntry> {
425 self.pin_manager.pin(session_id, turn_index, reason, tokens)
426 }
427
428 pub fn unpin(&self, session_id: &str, turn_index: usize) -> Result<()> {
430 self.pin_manager.unpin(session_id, turn_index)
431 }
432
433 pub fn search_sessions(&self, query: &str) -> Result<Vec<SessionSummary>> {
435 self.session_store.search(query)
436 }
437
438 pub fn usage_report(&self, agent_id: &str) -> UsageReport {
440 self.budget_tracker.usage_report(agent_id.to_string())
441 }
442
443 pub fn cost_summary(&self, session_id: &str) -> Result<SessionCostSummary> {
445 let session = self.session_store.load_session(session_id.to_string())?;
446 Ok(self.cost_calculator.session_summary(&session))
447 }
448
449 pub fn reload_preset(&mut self, toml: &str) -> Result<()> {
451 let new_preset = PresetParser::parse(toml)?;
452 if let Ok(mut pipeline) = self.pipeline.lock() {
453 pipeline.reload_preset(&new_preset)?;
454 }
455 if let Ok(mut router) = self.model_router.lock() {
456 *router = ModelRouter::new(&new_preset);
457 }
458 if let Ok(mut preset) = self.preset.lock() {
459 *preset = new_preset;
460 }
461 Ok(())
462 }
463
464 pub fn watch_preset_file(&self, path: &Path) -> Result<notify::RecommendedWatcher> {
469 use notify::{Event, EventKind, RecursiveMode, Watcher};
470
471 let preset_arc = Arc::clone(&self.preset);
472 let pipeline_arc = Arc::clone(&self.pipeline);
473 let router_arc = Arc::clone(&self.model_router);
474 let watched_path = path.to_owned();
475
476 let mut watcher = notify::recommended_watcher(move |res: notify::Result<Event>| {
477 if let Ok(event) = res {
478 if matches!(event.kind, EventKind::Modify(_) | EventKind::Create(_)) {
479 match std::fs::read_to_string(&watched_path) {
480 Ok(toml_str) => match PresetParser::parse(&toml_str) {
481 Ok(new_preset) => {
482 if let Ok(mut p) = pipeline_arc.lock() {
483 let _ = p.reload_preset(&new_preset);
484 }
485 if let Ok(mut r) = router_arc.lock() {
486 *r = ModelRouter::new(&new_preset);
487 }
488 if let Ok(mut pr) = preset_arc.lock() {
489 *pr = new_preset;
490 }
491 }
492 Err(e) => eprintln!("[sqz] invalid preset: {e}"),
493 },
494 Err(e) => eprintln!("[sqz] preset read error: {e}"),
495 }
496 }
497 }
498 })
499 .map_err(|e| SqzError::Other(format!("watcher error: {e}")))?;
500
501 watcher
502 .watch(path, RecursiveMode::NonRecursive)
503 .map_err(|e| SqzError::Other(format!("watch error: {e}")))?;
504
505 Ok(watcher)
506 }
507
508 pub fn session_store(&self) -> &SessionStore {
510 &self.session_store
511 }
512
513 pub fn cache_manager(&self) -> &CacheManager {
515 &self.cache_manager
516 }
517
518 pub fn ast_parser(&self) -> &AstParser {
520 &self.ast_parser
521 }
522
523 pub fn terse_mode(&self) -> &TerseMode {
525 &self.terse_mode
526 }
527
528 pub fn reorder_context(
534 &self,
535 sections: &mut Vec<crate::litm_positioner::ContextSection>,
536 strategy: crate::litm_positioner::LitmStrategy,
537 ) {
538 let positioner = crate::litm_positioner::LitmPositioner::new(strategy);
539 positioner.reorder(sections);
540 }
541
542 pub fn route_compression_mode(&self, content: &str) -> crate::confidence_router::CompressionMode {
545 self.confidence_router.route(content)
546 }
547
548 pub fn count_tokens(&self, text: &str) -> u32 {
550 let preset = self.preset.lock().unwrap();
551 self.pipeline.lock().unwrap().count_tokens(text, &preset)
552 }
553}
554
555
556#[cfg(test)]
557mod tests {
558 use super::*;
559 use crate::types::{BudgetState, CorrectionLog, ModelFamily, SessionState};
560 use chrono::Utc;
561 use std::path::PathBuf;
562
563 fn make_session(id: &str) -> SessionState {
564 let now = Utc::now();
565 SessionState {
566 id: id.to_string(),
567 project_dir: PathBuf::from("/tmp/test"),
568 conversation: vec![],
569 corrections: CorrectionLog::default(),
570 pins: vec![],
571 learnings: vec![],
572 compressed_summary: "test session".to_string(),
573 budget: BudgetState {
574 window_size: 200_000,
575 consumed: 0,
576 pinned: 0,
577 model_family: ModelFamily::AnthropicClaude,
578 },
579 tool_usage: vec![],
580 created_at: now,
581 updated_at: now,
582 }
583 }
584
585 #[test]
586 fn test_engine_new() {
587 let engine = SqzEngine::new();
588 assert!(engine.is_ok(), "SqzEngine::new() should succeed");
589 }
590
591 #[test]
592 fn test_compress_or_passthrough_returns_result_on_valid_input() {
593 let engine = SqzEngine::new().unwrap();
594 let result = engine.compress_or_passthrough("hello world");
595 assert_eq!(result.data, "hello world");
596 assert!(result.tokens_original > 0);
597 }
598
599 #[test]
600 fn test_compress_or_passthrough_never_panics_on_empty() {
601 let engine = SqzEngine::new().unwrap();
602 let result = engine.compress_or_passthrough("");
603 assert_eq!(result.data, "");
604 assert_eq!(result.compression_ratio, 1.0);
605 }
606
607 #[test]
608 fn test_compress_or_passthrough_handles_json() {
609 let engine = SqzEngine::new().unwrap();
610 let result = engine.compress_or_passthrough(r#"{"key":"value"}"#);
611 assert!(!result.data.is_empty());
613 }
614
615 #[test]
616 fn test_compress_or_passthrough_handles_binary_garbage() {
617 let engine = SqzEngine::new().unwrap();
618 let garbage = "\x00\x01\x02\x7f invalid control chars \t\n\r";
620 let result = engine.compress_or_passthrough(garbage);
621 assert!(!result.data.is_empty());
622 }
623
624 #[test]
625 fn test_compress_plain_text() {
626 let engine = SqzEngine::new().unwrap();
627 let result = engine.compress("hello world");
628 assert!(result.is_ok());
629 assert_eq!(result.unwrap().data, "hello world");
630 }
631
632 #[test]
633 fn test_compress_json_applies_toon() {
634 let engine = SqzEngine::new().unwrap();
635 let result = engine.compress(r#"{"name":"Alice","age":30}"#).unwrap();
636 assert!(result.data.starts_with("TOON:"), "JSON should be TOON-encoded");
637 }
638
639 #[test]
640 fn test_export_import_ctx_round_trip() {
641 let dir = tempfile::tempdir().unwrap();
642 let store_path = dir.path().join("store.db");
643 let engine = SqzEngine::with_preset_and_store(Preset::default(), &store_path).unwrap();
644
645 let session = make_session("sess-rt");
646 engine.session_store().save_session(&session).unwrap();
647
648 let ctx = engine.export_ctx("sess-rt").unwrap();
649 let imported_id = engine.import_ctx(&ctx).unwrap();
650 assert_eq!(imported_id, "sess-rt");
651 }
652
653 #[test]
654 fn test_search_sessions() {
655 let dir = tempfile::tempdir().unwrap();
656 let store_path = dir.path().join("store.db");
657 let engine = SqzEngine::with_preset_and_store(Preset::default(), &store_path).unwrap();
658
659 let mut session = make_session("sess-search");
660 session.compressed_summary = "authentication refactor".to_string();
661 engine.session_store().save_session(&session).unwrap();
662
663 let results = engine.search_sessions("authentication").unwrap();
664 assert_eq!(results.len(), 1);
665 assert_eq!(results[0].id, "sess-search");
666 }
667
668 #[test]
669 fn test_usage_report_starts_at_zero() {
670 let engine = SqzEngine::new().unwrap();
671 let report = engine.usage_report("default");
672 assert_eq!(report.consumed, 0);
673 assert_eq!(report.available, report.allocated);
674 }
675
676 #[test]
677 fn test_cost_summary() {
678 let dir = tempfile::tempdir().unwrap();
679 let store_path = dir.path().join("store.db");
680 let engine = SqzEngine::with_preset_and_store(Preset::default(), &store_path).unwrap();
681
682 let session = make_session("sess-cost");
683 engine.session_store().save_session(&session).unwrap();
684
685 let summary = engine.cost_summary("sess-cost").unwrap();
686 assert_eq!(summary.total_tokens, 0);
687 assert!((summary.total_usd - 0.0).abs() < f64::EPSILON);
688 }
689
690 #[test]
691 fn test_reload_preset_updates_state() {
692 let mut engine = SqzEngine::new().unwrap();
693 let toml = r#"
694[preset]
695name = "reloaded"
696version = "2.0"
697
698[compression]
699stages = []
700
701[tool_selection]
702max_tools = 5
703similarity_threshold = 0.7
704
705[budget]
706warning_threshold = 0.70
707ceiling_threshold = 0.85
708default_window_size = 200000
709
710[terse_mode]
711enabled = false
712level = "moderate"
713
714[model]
715family = "anthropic"
716primary = "claude-sonnet-4-20250514"
717complexity_threshold = 0.4
718"#;
719 assert!(engine.reload_preset(toml).is_ok());
720 let preset = engine.preset.lock().unwrap();
722 assert_eq!(preset.preset.name, "reloaded");
723 }
724
725 #[test]
726 fn test_reload_invalid_preset_returns_error() {
727 let mut engine = SqzEngine::new().unwrap();
728 let result = engine.reload_preset("not valid toml [[[");
729 assert!(result.is_err(), "invalid TOML should return error");
730 }
731
732 #[test]
733 fn test_export_nonexistent_session_returns_error() {
734 let engine = SqzEngine::new().unwrap();
735 let result = engine.export_ctx("does-not-exist");
736 assert!(result.is_err());
737 }
738
739 #[test]
740 fn test_import_invalid_ctx_returns_error() {
741 let engine = SqzEngine::new().unwrap();
742 let result = engine.import_ctx("not valid json {{{");
743 assert!(result.is_err());
744 }
745
746 #[test]
747 fn store_path_env_override_used_when_set() {
748 let dir = tempfile::tempdir().unwrap();
749 let target = dir.path().join("nested").join("project.db");
750 let resolved =
751 SqzEngine::store_path_from_env(Some(target.to_string_lossy().to_string()));
752 assert_eq!(resolved, Some(target.clone()));
753 assert!(target.parent().unwrap().exists());
755 }
756
757 #[test]
758 fn store_path_env_ignores_empty_values() {
759 assert_eq!(SqzEngine::store_path_from_env(None), None);
760 assert_eq!(SqzEngine::store_path_from_env(Some(String::new())), None);
761 assert_eq!(SqzEngine::store_path_from_env(Some(" ".to_string())), None);
762 }
763
764 #[test]
765 fn store_path_env_leaves_existing_parent_permissions_alone() {
766 let dir = tempfile::tempdir().unwrap();
767 #[cfg(unix)]
768 {
769 use std::os::unix::fs::PermissionsExt;
770 std::fs::set_permissions(dir.path(), std::fs::Permissions::from_mode(0o755))
771 .unwrap();
772 }
773 let target = dir.path().join("sessions.db");
774 let resolved =
775 SqzEngine::store_path_from_env(Some(target.to_string_lossy().to_string()));
776 assert_eq!(resolved, Some(target));
777 #[cfg(unix)]
778 {
779 use std::os::unix::fs::PermissionsExt;
780 let mode = std::fs::metadata(dir.path()).unwrap().permissions().mode() & 0o777;
781 assert_eq!(mode, 0o755, "pre-existing parent must not be chmodded");
782 }
783 }
784
785 #[test]
786 fn ref_ttl_env_parses_seconds_and_falls_back() {
787 use std::time::Duration;
788 assert_eq!(SqzEngine::ref_ttl_from_env(None), Duration::from_secs(1800));
790 assert_eq!(
791 SqzEngine::ref_ttl_from_env(Some(String::new())),
792 Duration::from_secs(1800)
793 );
794 assert_eq!(
795 SqzEngine::ref_ttl_from_env(Some(" ".into())),
796 Duration::from_secs(1800)
797 );
798 assert_eq!(
799 SqzEngine::ref_ttl_from_env(Some("ten".into())),
800 Duration::from_secs(1800)
801 );
802 assert_eq!(
803 SqzEngine::ref_ttl_from_env(Some("-5".into())),
804 Duration::from_secs(1800)
805 );
806 assert_eq!(
808 SqzEngine::ref_ttl_from_env(Some("300".into())),
809 Duration::from_secs(300)
810 );
811 assert_eq!(
812 SqzEngine::ref_ttl_from_env(Some(" 60 ".into())),
813 Duration::from_secs(60)
814 );
815 assert_eq!(
816 SqzEngine::ref_ttl_from_env(Some("0".into())),
817 Duration::ZERO
818 );
819 }
820
821 #[test]
822 fn engine_with_custom_store_path_round_trips() {
823 let dir = tempfile::tempdir().unwrap();
824 let db = dir.path().join("proj").join("sessions.db");
825 let resolved = SqzEngine::store_path_from_env(Some(db.to_string_lossy().to_string()))
826 .unwrap();
827 let engine = SqzEngine::with_preset_and_store(Preset::default(), &resolved).unwrap();
828 engine
829 .session_store()
830 .log_compression(100, 40, &[], "test")
831 .unwrap();
832 assert!(db.exists(), "database file must land at the custom path");
833 let stats = engine.session_store().compression_stats().unwrap();
834 assert_eq!(stats.total_compressions, 1);
835 }
836}