1use std::env;
4use std::fs;
5use std::path::{Path, PathBuf};
6use std::sync::OnceLock;
7use tracing::{debug, info, instrument};
8use tracing_subscriber::{
9 filter::filter_fn,
10 fmt::{self, format::FmtSpan},
11 prelude::*,
12 EnvFilter,
13};
14
15use crate::infrastructure::repositories::sqlite::migration;
16use crate::infrastructure::repositories::sqlite::repository::SqliteBookmarkRepository;
17
18#[derive(Debug)]
21pub struct TestEnv {
22 pub db_path: PathBuf,
24 pub resources: Vec<&'static str>,
26}
27
28impl TestEnv {
29 fn new() -> Self {
31 Self {
32 db_path: PathBuf::from("../db/bkmr.db"),
33 resources: vec![
34 "tests/resources/schema_v1_migration_test.db",
35 "tests/resources/schema_v2_with_embeddings.db",
36 "tests/resources/schema_v2_no_embeddings.db",
37 ],
38 }
39 }
40}
41
42static TEST_ENV: OnceLock<TestEnv> = OnceLock::new();
44
45pub fn init_test_env() -> &'static TestEnv {
51 let env_data = TEST_ENV.get_or_init(|| {
53 let data = TestEnv::new();
54 setup_test_logging(); crate::infrastructure::repositories::sqlite::register_sqlite_vec();
58
59 info!("Test environment initialized with DummyEmbedding");
60 data
61 });
62 env_data
63}
64
65fn setup_test_logging() {
67 debug!("Attempting logger init from testing.rs");
68 if tracing::dispatcher::has_been_set() {
69 debug!("Tracing subscriber already set");
70 return;
71 }
72
73 if env::var("RUST_LOG").is_err() {
74 env::set_var("RUST_LOG", "trace");
75 }
76
77 env::set_var("SKIM_LOG", "info");
79 env::set_var("TUIKIT_LOG", "info");
80
81 let noisy_modules = [
82 "skim",
83 "html5ever",
84 "reqwest",
85 "mio",
86 "want",
87 "hyper_util",
88 ];
89 let module_filter = filter_fn(move |metadata| {
90 !noisy_modules
91 .iter()
92 .any(|name| metadata.target().starts_with(name))
93 });
94
95 let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug"));
96
97 let subscriber = tracing_subscriber::registry().with(
98 fmt::layer()
99 .with_writer(std::io::stderr)
100 .with_target(true)
101 .with_thread_names(false)
102 .with_span_events(FmtSpan::CLOSE)
103 .with_filter(module_filter)
104 .with_filter(env_filter),
105 );
106
107 subscriber.try_init().unwrap_or_else(|e| {
108 eprintln!("Error: Failed to set up logging: {}", e);
109 });
110}
111
112#[derive(Debug, Clone)]
113pub struct EnvGuard {
114 db_url: Option<String>,
115 fzf_opts: Option<String>,
116}
117
118impl Default for EnvGuard {
119 fn default() -> Self {
120 Self::new()
121 }
122}
123
124impl EnvGuard {
125 pub fn new() -> Self {
126 Self {
127 db_url: env::var("BKMR_DB_URL").ok(),
128 fzf_opts: env::var("BKMR_FZF_OPTS").ok(),
129 }
130 }
131}
132
133impl Drop for EnvGuard {
134 #[instrument(level = "trace")]
135 fn drop(&mut self) {
136 env::remove_var("BKMR_DB_URL");
137 env::remove_var("BKMR_FZF_OPTS");
138 if let Some(val) = &self.db_url {
139 env::set_var("BKMR_DB_URL", val);
140 }
141 if let Some(val) = &self.fzf_opts {
142 env::set_var("BKMR_FZF_OPTS", val);
143 }
144 }
145}
146
147pub fn setup_test_db() -> SqliteBookmarkRepository {
149 let env_data = init_test_env();
150 let repository =
151 SqliteBookmarkRepository::from_url(env_data.db_path.to_string_lossy().as_ref())
152 .expect("Failed to create SqliteBookmarkRepository");
153 let mut conn = repository
154 .get_connection()
155 .expect("Failed to get connection from SqliteBookmarkRepository");
156 migration::init_db(&mut conn).expect("Failed to initialize DB schema");
157 repository
158}
159
160pub fn setup_temp_dir() -> PathBuf {
162 use fs_extra::dir::CopyOptions;
163 use tempfile::tempdir;
164
165 let env_data = init_test_env(); let tempdir = tempdir().expect("Failed to create temp dir");
167 let options = CopyOptions::new().overwrite(true);
168
169 fs_extra::copy_items(&env_data.resources, "../db", &options)
170 .expect("Failed to copy test resources into ../db");
171
172 tempdir.keep()
173}
174
175pub fn teardown_temp_dir(temp_dir: &Path) {
177 if env::var("NO_CLEANUP").is_err() && temp_dir.exists() {
178 let _ = fs::remove_dir_all(temp_dir);
179 } else {
180 info!("Test artifacts left at: {}", temp_dir.display());
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn given_test_env_when_init_then_db_path_exists() {
190 let test_env = init_test_env();
191 let _guard = EnvGuard::new();
192 assert!(test_env.db_path.exists());
193 info!("test logic here");
194 }
195
196 #[test]
197 fn given_test_env_when_setup_test_db_then_returns_working_repository() {
198 let _ = init_test_env();
199 let repo = setup_test_db();
200 assert!(repo.get_connection().is_ok());
201 }
202
203 #[test]
204 fn given_test_env_when_setup_temp_dir_then_creates_directory_with_resources() {
205 let _ = init_test_env();
206 let temp_dir = setup_temp_dir();
207 assert!(temp_dir.exists());
208 teardown_temp_dir(&temp_dir);
209 }
210}