@@ -234,6 +234,11 @@ pub fn upsert_session_with_conn(conn: &Connection, session: &ActiveSession) -> S
234234/// Read all sessions from the database.
235235pub fn read_sessions ( ) -> SqliteResult < Vec < ActiveSession > > {
236236 let conn = get_connection ( ) ?;
237+ read_sessions_with_conn ( & conn)
238+ }
239+
240+ /// Read all sessions using an existing connection.
241+ pub fn read_sessions_with_conn ( conn : & Connection ) -> SqliteResult < Vec < ActiveSession > > {
237242 let mut stmt = conn. prepare (
238243 "SELECT
239244 session_id, source, pid, project_path, provider, account_id,
@@ -309,6 +314,11 @@ pub fn read_sessions() -> SqliteResult<Vec<ActiveSession>> {
309314/// Delete a session by ID.
310315pub fn remove_session ( session_id : & str ) -> SqliteResult < ( ) > {
311316 let conn = get_connection ( ) ?;
317+ remove_session_with_conn ( & conn, session_id)
318+ }
319+
320+ /// Delete a session by ID using an existing connection.
321+ pub fn remove_session_with_conn ( conn : & Connection , session_id : & str ) -> SqliteResult < ( ) > {
312322 conn. execute (
313323 "DELETE FROM sessions WHERE session_id = ?1" ,
314324 params ! [ session_id] ,
@@ -417,6 +427,11 @@ pub struct SessionMetrics {
417427/// monotonic counters always keep the maximum.
418428pub fn upsert_session_metrics ( m : & SessionMetrics ) -> SqliteResult < ( ) > {
419429 let conn = get_connection ( ) ?;
430+ upsert_session_metrics_with_conn ( & conn, m)
431+ }
432+
433+ /// Insert or update session metrics using an existing connection.
434+ pub fn upsert_session_metrics_with_conn ( conn : & Connection , m : & SessionMetrics ) -> SqliteResult < ( ) > {
420435 conn. execute (
421436 "INSERT INTO session_metrics (
422437 session_id, input_tokens, output_tokens,
@@ -448,6 +463,14 @@ pub fn upsert_session_metrics(m: &SessionMetrics) -> SqliteResult<()> {
448463/// Read metrics for a specific session.
449464pub fn get_session_metrics ( session_id : & str ) -> SqliteResult < Option < SessionMetrics > > {
450465 let conn = get_connection ( ) ?;
466+ get_session_metrics_with_conn ( & conn, session_id)
467+ }
468+
469+ /// Read metrics for a specific session using an existing connection.
470+ pub fn get_session_metrics_with_conn (
471+ conn : & Connection ,
472+ session_id : & str ,
473+ ) -> SqliteResult < Option < SessionMetrics > > {
451474 let mut stmt = conn. prepare (
452475 "SELECT
453476 session_id, input_tokens, output_tokens,
@@ -1223,7 +1246,9 @@ mod tests {
12231246 use super :: * ;
12241247 use crate :: session_cache:: { ActiveSession , SessionGitInfo } ;
12251248
1226- /// Set up an in-memory database for testing.
1249+ /// Set up an isolated in-memory database for testing.
1250+ /// Each call returns a fresh connection with the full schema applied,
1251+ /// so tests can run in parallel without any shared state.
12271252 fn test_db ( ) -> Connection {
12281253 let conn = Connection :: open_in_memory ( ) . unwrap ( ) ;
12291254 conn. execute_batch (
@@ -1244,18 +1269,18 @@ mod tests {
12441269 conn
12451270 }
12461271
1247- /// Helper: seed the OnceLock with a temp dir so db_path() / init_db() work.
1248- ///
1249- /// OnceLock can only be set once per process, so all tests share the same
1250- /// directory. We delete the DB file before each test so refinery migrations
1251- /// run from scratch.
1252- fn init_test_env ( ) {
1253- let base = std:: env:: temp_dir ( ) . join ( "magia-db-test" ) ;
1254- std:: fs:: create_dir_all ( & base) . ok ( ) ;
1272+ /// Create an isolated on-disk test environment with its own temp directory.
1273+ /// Returns the temp directory path. The caller must hold the returned
1274+ /// `TempDir` alive for the duration of the test (dropping it cleans up).
1275+ fn init_test_env_isolated ( ) -> tempfile:: TempDir {
1276+ let tmp = tempfile:: tempdir ( ) . expect ( "failed to create temp dir" ) ;
1277+ let base = tmp. path ( ) . to_path_buf ( ) ;
1278+ // These are OnceLock so only the first test to run actually sets them.
1279+ // For init_db tests we use DB_PATH directly instead of relying on the
1280+ // global APP_DATA_DIR.
12551281 crate :: paths:: APP_DATA_DIR . set ( base. clone ( ) ) . ok ( ) ;
12561282 crate :: paths:: APP_CACHE_DIR . set ( base) . ok ( ) ;
1257- // Remove existing DB so migrations run clean
1258- let _ = std:: fs:: remove_file ( db_path ( ) ) ;
1283+ tmp
12591284 }
12601285
12611286 #[ test]
@@ -1275,15 +1300,31 @@ mod tests {
12751300
12761301 #[ test]
12771302 fn test_init_db_creates_file ( ) {
1278- init_test_env ( ) ;
1279- init_db ( ) . unwrap ( ) ;
1280- assert ! ( db_path( ) . exists( ) ) ;
1303+ let tmp = init_test_env_isolated ( ) ;
1304+ let db_file = tmp. path ( ) . join ( "magia.db" ) ;
1305+ // Set DB_PATH to our isolated path (ignores if already set by another test)
1306+ DB_PATH . set ( db_file. clone ( ) ) . ok ( ) ;
1307+
1308+ // If DB_PATH was already claimed by another test, create a fresh
1309+ // connection directly to verify init_db logic.
1310+ let mut conn = Connection :: open ( & db_file) . unwrap ( ) ;
1311+ conn. execute_batch (
1312+ "PRAGMA journal_mode = WAL;
1313+ PRAGMA busy_timeout = 5000;
1314+ PRAGMA synchronous = NORMAL;
1315+ PRAGMA foreign_keys = ON;" ,
1316+ )
1317+ . unwrap ( ) ;
1318+ embedded:: migrations:: runner ( )
1319+ . run ( & mut conn)
1320+ . expect ( "migrations should succeed" ) ;
1321+ drop ( conn) ;
1322+ assert ! ( db_file. exists( ) ) ;
12811323 }
12821324
12831325 #[ test]
12841326 fn test_upsert_and_read_session ( ) {
1285- init_test_env ( ) ;
1286- init_db ( ) . unwrap ( ) ;
1327+ let conn = test_db ( ) ;
12871328
12881329 let session = ActiveSession {
12891330 session_id : "test-123" . to_string ( ) ,
@@ -1294,9 +1335,9 @@ mod tests {
12941335 ..Default :: default ( )
12951336 } ;
12961337
1297- upsert_session ( & session) . unwrap ( ) ;
1338+ upsert_session_with_conn ( & conn , & session) . unwrap ( ) ;
12981339
1299- let sessions = read_sessions ( ) . unwrap ( ) ;
1340+ let sessions = read_sessions_with_conn ( & conn ) . unwrap ( ) ;
13001341 let s = sessions
13011342 . iter ( )
13021343 . find ( |s| s. session_id == "test-123" )
@@ -1306,16 +1347,15 @@ mod tests {
13061347
13071348 #[ test]
13081349 fn test_upsert_preserves_immutable_fields ( ) {
1309- init_test_env ( ) ;
1310- init_db ( ) . unwrap ( ) ;
1350+ let conn = test_db ( ) ;
13111351
13121352 let session1 = ActiveSession {
13131353 session_id : "immutable-test" . to_string ( ) ,
13141354 added_at : Some ( "2024-01-01T00:00:00Z" . to_string ( ) ) ,
13151355 created_at : Some ( "2024-01-01T00:00:00Z" . to_string ( ) ) ,
13161356 ..Default :: default ( )
13171357 } ;
1318- upsert_session ( & session1) . unwrap ( ) ;
1358+ upsert_session_with_conn ( & conn , & session1) . unwrap ( ) ;
13191359
13201360 // Second upsert with different timestamps — originals should be kept
13211361 let session2 = ActiveSession {
@@ -1325,9 +1365,9 @@ mod tests {
13251365 title : Some ( "Updated title" . to_string ( ) ) ,
13261366 ..Default :: default ( )
13271367 } ;
1328- upsert_session ( & session2) . unwrap ( ) ;
1368+ upsert_session_with_conn ( & conn , & session2) . unwrap ( ) ;
13291369
1330- let sessions = read_sessions ( ) . unwrap ( ) ;
1370+ let sessions = read_sessions_with_conn ( & conn ) . unwrap ( ) ;
13311371 let s = sessions
13321372 . iter ( )
13331373 . find ( |s| s. session_id == "immutable-test" )
@@ -1339,25 +1379,24 @@ mod tests {
13391379
13401380 #[ test]
13411381 fn test_upsert_source_protection ( ) {
1342- init_test_env ( ) ;
1343- init_db ( ) . unwrap ( ) ;
1382+ let conn = test_db ( ) ;
13441383
13451384 let session1 = ActiveSession {
13461385 session_id : "source-test" . to_string ( ) ,
13471386 source : "magia" . to_string ( ) ,
13481387 ..Default :: default ( )
13491388 } ;
1350- upsert_session ( & session1) . unwrap ( ) ;
1389+ upsert_session_with_conn ( & conn , & session1) . unwrap ( ) ;
13511390
13521391 // Try to downgrade source — should stay "magia"
13531392 let session2 = ActiveSession {
13541393 session_id : "source-test" . to_string ( ) ,
13551394 source : "external" . to_string ( ) ,
13561395 ..Default :: default ( )
13571396 } ;
1358- upsert_session ( & session2) . unwrap ( ) ;
1397+ upsert_session_with_conn ( & conn , & session2) . unwrap ( ) ;
13591398
1360- let sessions = read_sessions ( ) . unwrap ( ) ;
1399+ let sessions = read_sessions_with_conn ( & conn ) . unwrap ( ) ;
13611400 let s = sessions
13621401 . iter ( )
13631402 . find ( |s| s. session_id == "source-test" )
@@ -1367,8 +1406,7 @@ mod tests {
13671406
13681407 #[ test]
13691408 fn test_upsert_monotonic_metrics ( ) {
1370- init_test_env ( ) ;
1371- init_db ( ) . unwrap ( ) ;
1409+ let conn = test_db ( ) ;
13721410
13731411 let session1 = ActiveSession {
13741412 session_id : "metric-test" . to_string ( ) ,
@@ -1377,7 +1415,7 @@ mod tests {
13771415 total_cost : Some ( 0.05 ) ,
13781416 ..Default :: default ( )
13791417 } ;
1380- upsert_session ( & session1) . unwrap ( ) ;
1418+ upsert_session_with_conn ( & conn , & session1) . unwrap ( ) ;
13811419
13821420 // Lower values should not replace higher ones
13831421 let session2 = ActiveSession {
@@ -1387,9 +1425,9 @@ mod tests {
13871425 total_cost : Some ( 0.01 ) ,
13881426 ..Default :: default ( )
13891427 } ;
1390- upsert_session ( & session2) . unwrap ( ) ;
1428+ upsert_session_with_conn ( & conn , & session2) . unwrap ( ) ;
13911429
1392- let sessions = read_sessions ( ) . unwrap ( ) ;
1430+ let sessions = read_sessions_with_conn ( & conn ) . unwrap ( ) ;
13931431 let s = sessions
13941432 . iter ( )
13951433 . find ( |s| s. session_id == "metric-test" )
@@ -1401,26 +1439,25 @@ mod tests {
14011439
14021440 #[ test]
14031441 fn test_remove_session ( ) {
1404- init_test_env ( ) ;
1405- init_db ( ) . unwrap ( ) ;
1442+ let conn = test_db ( ) ;
14061443
14071444 let session = ActiveSession {
14081445 session_id : "remove-me" . to_string ( ) ,
14091446 ..Default :: default ( )
14101447 } ;
1411- upsert_session ( & session) . unwrap ( ) ;
1448+ upsert_session_with_conn ( & conn , & session) . unwrap ( ) ;
14121449 assert_eq ! (
1413- read_sessions ( )
1450+ read_sessions_with_conn ( & conn )
14141451 . unwrap( )
14151452 . iter( )
14161453 . filter( |s| s. session_id == "remove-me" )
14171454 . count( ) ,
14181455 1
14191456 ) ;
14201457
1421- remove_session ( "remove-me" ) . unwrap ( ) ;
1458+ remove_session_with_conn ( & conn , "remove-me" ) . unwrap ( ) ;
14221459 assert_eq ! (
1423- read_sessions ( )
1460+ read_sessions_with_conn ( & conn )
14241461 . unwrap( )
14251462 . iter( )
14261463 . filter( |s| s. session_id == "remove-me" )
@@ -1431,8 +1468,7 @@ mod tests {
14311468
14321469 #[ test]
14331470 fn test_git_branch_roundtrip ( ) {
1434- init_test_env ( ) ;
1435- init_db ( ) . unwrap ( ) ;
1471+ let conn = test_db ( ) ;
14361472
14371473 let session = ActiveSession {
14381474 session_id : "git-test" . to_string ( ) ,
@@ -1441,9 +1477,9 @@ mod tests {
14411477 } ) ,
14421478 ..Default :: default ( )
14431479 } ;
1444- upsert_session ( & session) . unwrap ( ) ;
1480+ upsert_session_with_conn ( & conn , & session) . unwrap ( ) ;
14451481
1446- let sessions = read_sessions ( ) . unwrap ( ) ;
1482+ let sessions = read_sessions_with_conn ( & conn ) . unwrap ( ) ;
14471483 let s = sessions
14481484 . iter ( )
14491485 . find ( |s| s. session_id == "git-test" )
@@ -1456,17 +1492,16 @@ mod tests {
14561492
14571493 #[ test]
14581494 fn test_allowed_tools_roundtrip ( ) {
1459- init_test_env ( ) ;
1460- init_db ( ) . unwrap ( ) ;
1495+ let conn = test_db ( ) ;
14611496
14621497 let session = ActiveSession {
14631498 session_id : "tools-test" . to_string ( ) ,
14641499 session_allowed_tools : vec ! [ "Read" . to_string( ) , "Write" . to_string( ) ] ,
14651500 ..Default :: default ( )
14661501 } ;
1467- upsert_session ( & session) . unwrap ( ) ;
1502+ upsert_session_with_conn ( & conn , & session) . unwrap ( ) ;
14681503
1469- let sessions = read_sessions ( ) . unwrap ( ) ;
1504+ let sessions = read_sessions_with_conn ( & conn ) . unwrap ( ) ;
14701505 let s = sessions
14711506 . iter ( )
14721507 . find ( |s| s. session_id == "tools-test" )
@@ -1476,15 +1511,14 @@ mod tests {
14761511
14771512 #[ test]
14781513 fn test_session_metrics_upsert_and_read ( ) {
1479- init_test_env ( ) ;
1480- init_db ( ) . unwrap ( ) ;
1514+ let conn = test_db ( ) ;
14811515
14821516 // Insert parent session first (foreign key)
14831517 let session = ActiveSession {
14841518 session_id : "metrics-parent" . to_string ( ) ,
14851519 ..Default :: default ( )
14861520 } ;
1487- upsert_session ( & session) . unwrap ( ) ;
1521+ upsert_session_with_conn ( & conn , & session) . unwrap ( ) ;
14881522
14891523 let metrics = SessionMetrics {
14901524 session_id : "metrics-parent" . to_string ( ) ,
@@ -1496,9 +1530,9 @@ mod tests {
14961530 source : "otel" . to_string ( ) ,
14971531 updated_at : None ,
14981532 } ;
1499- upsert_session_metrics ( & metrics) . unwrap ( ) ;
1533+ upsert_session_metrics_with_conn ( & conn , & metrics) . unwrap ( ) ;
15001534
1501- let result = get_session_metrics ( "metrics-parent" ) . unwrap ( ) ;
1535+ let result = get_session_metrics_with_conn ( & conn , "metrics-parent" ) . unwrap ( ) ;
15021536 assert ! ( result. is_some( ) ) ;
15031537 let m = result. unwrap ( ) ;
15041538 assert_eq ! ( m. input_tokens, 500 ) ;
@@ -1507,34 +1541,32 @@ mod tests {
15071541
15081542 #[ test]
15091543 fn test_session_metrics_not_found ( ) {
1510- init_test_env ( ) ;
1511- init_db ( ) . unwrap ( ) ;
1544+ let conn = test_db ( ) ;
15121545
1513- let result = get_session_metrics ( "nonexistent" ) . unwrap ( ) ;
1546+ let result = get_session_metrics_with_conn ( & conn , "nonexistent" ) . unwrap ( ) ;
15141547 assert ! ( result. is_none( ) ) ;
15151548 }
15161549
15171550 #[ test]
15181551 fn test_cascade_delete_metrics ( ) {
1519- init_test_env ( ) ;
1520- init_db ( ) . unwrap ( ) ;
1552+ let conn = test_db ( ) ;
15211553
15221554 let session = ActiveSession {
15231555 session_id : "cascade-test" . to_string ( ) ,
15241556 ..Default :: default ( )
15251557 } ;
1526- upsert_session ( & session) . unwrap ( ) ;
1558+ upsert_session_with_conn ( & conn , & session) . unwrap ( ) ;
15271559
15281560 let metrics = SessionMetrics {
15291561 session_id : "cascade-test" . to_string ( ) ,
15301562 input_tokens : 100 ,
15311563 ..Default :: default ( )
15321564 } ;
1533- upsert_session_metrics ( & metrics) . unwrap ( ) ;
1565+ upsert_session_metrics_with_conn ( & conn , & metrics) . unwrap ( ) ;
15341566
15351567 // Deleting the session should cascade to metrics
1536- remove_session ( "cascade-test" ) . unwrap ( ) ;
1537- let result = get_session_metrics ( "cascade-test" ) . unwrap ( ) ;
1568+ remove_session_with_conn ( & conn , "cascade-test" ) . unwrap ( ) ;
1569+ let result = get_session_metrics_with_conn ( & conn , "cascade-test" ) . unwrap ( ) ;
15381570 assert ! ( result. is_none( ) ) ;
15391571 }
15401572}
0 commit comments