//! Tests for Session and API Token repository operations. use chrono::{Duration, Utc}; use nx9_wg_core::crypto::{generate_api_token, hash_password}; use nx9_wg_core::types::auth::{ApiToken, Session}; use nx9_wg_db::Store; use uuid::Uuid; #[tokio::test] async fn test_session_lifecycle() { let store = Store::connect_in_memory().await.expect("connect"); store.migrate().await.expect("migrate"); let pw_hash = hash_password("AdminPass123!").expect("hash"); store.create_admin("admin", &pw_hash).await.expect("admin"); let now = Utc::now().naive_utc(); let session_id = Uuid::new_v4().to_string(); let session = Session { id: session_id.clone(), admin_id: 1, created_at: now, expires_at: now + Duration::hours(24), last_seen_at: Some(now), ip_address: Some("10.0.0.5".to_string()), user_agent: Some("TestAgent/1.0".to_string()), }; store .create_session(&session) .await .expect("create_session"); let fetched = store .get_session(&session_id) .await .expect("get_session") .expect("session found"); assert_eq!(fetched.id, session_id); assert_eq!(fetched.admin_id, 1); assert_eq!(fetched.ip_address.as_deref(), Some("10.0.0.5")); // Touch session store .touch_session(&session_id) .await .expect("touch_session"); // Test delete expired sessions let expired_id = Uuid::new_v4().to_string(); let expired_session = Session { id: expired_id.clone(), admin_id: 1, created_at: now - Duration::hours(48), expires_at: now - Duration::hours(24), last_seen_at: None, ip_address: None, user_agent: None, }; store .create_session(&expired_session) .await .expect("expired session"); let deleted = store .delete_expired_sessions() .await .expect("delete expired"); assert_eq!(deleted, 1); assert!(store.get_session(&expired_id).await.expect("get").is_none()); assert!(store.get_session(&session_id).await.expect("get").is_some()); // Delete single session store .delete_session(&session_id) .await .expect("delete session"); assert!(store.get_session(&session_id).await.expect("get").is_none()); // Test delete_all_admin_sessions let s1 = Session { id: "s1".to_string(), admin_id: 1, created_at: now, expires_at: now + Duration::hours(1), last_seen_at: None, ip_address: None, user_agent: None, }; let s2 = Session { id: "s2".to_string(), admin_id: 1, created_at: now, expires_at: now + Duration::hours(1), last_seen_at: None, ip_address: None, user_agent: None, }; store.create_session(&s1).await.expect("s1"); store.create_session(&s2).await.expect("s2"); let deleted_all = store .delete_all_admin_sessions(1) .await .expect("delete all"); assert_eq!(deleted_all, 2); } #[tokio::test] async fn test_api_token_lifecycle() { let store = Store::connect_in_memory().await.expect("connect"); store.migrate().await.expect("migrate"); let pw_hash = hash_password("AdminPass123!").expect("hash"); store.create_admin("admin", &pw_hash).await.expect("admin"); let (raw_token, token_hash) = generate_api_token(); let token_id = Uuid::new_v4().to_string(); let now = Utc::now().naive_utc(); let token = ApiToken { id: token_id.clone(), admin_id: 1, name: "CI/CD Deployment Token".to_string(), token_hash: token_hash.clone(), created_at: now, expires_at: Some(now + Duration::days(30)), last_used_at: None, revoked_at: None, revoked: false, }; store.create_token(&token).await.expect("create_token"); // Lookup by hash let found = store .find_token_by_hash(&token_hash) .await .expect("find by hash") .expect("token found"); assert_eq!(found.id, token_id); assert_eq!(found.name, "CI/CD Deployment Token"); assert!(!found.revoked); // Verify raw token is never in the stored record let debug_out = format!("{:?}", found); assert!(debug_out.contains("[REDACTED]")); assert!(!debug_out.contains(&raw_token)); // Mark token used store.mark_token_used(&token_id).await.expect("mark used"); let after_use = store .get_token(&token_id) .await .expect("get") .expect("token"); assert!(after_use.last_used_at.is_some()); // List tokens let tokens = store.list_tokens().await.expect("list tokens"); assert_eq!(tokens.len(), 1); // Revoke token store.revoke_token(&token_id).await.expect("revoke token"); let revoked = store .get_token(&token_id) .await .expect("get") .expect("token"); assert!(revoked.revoked); assert!(revoked.revoked_at.is_some()); // Delete token store.delete_token(&token_id).await.expect("delete token"); assert!(store.get_token(&token_id).await.expect("get").is_none()); }