db.rs (10996B)
1 // Database tests for OAuth2 Gateway 2 // Requires TEST_DATABASE_URL environment variable or uses default connection. 3 4 use oauth2_gateway::db; 5 use sqlx::PgPool; 6 use serial_test::serial; 7 8 fn get_test_database_url() -> String { 9 std::env::var("TEST_DATABASE_URL") 10 .unwrap_or_else(|_| "postgresql://oauth2gw:password@localhost:5432/oauth2gw".to_string()) 11 } 12 13 async fn setup_test_db() -> PgPool { 14 let pool = db::create_pool(&get_test_database_url()) 15 .await 16 .expect("Failed to connect to test database"); 17 clean_test_data(&pool).await; 18 pool 19 } 20 21 async fn clean_test_data(pool: &PgPool) { 22 let _ = sqlx::query("DELETE FROM oauth2gw.notification_pending_webhooks").execute(pool).await; 23 let _ = sqlx::query("DELETE FROM oauth2gw.authorization_codes").execute(pool).await; 24 let _ = sqlx::query("DELETE FROM oauth2gw.access_tokens").execute(pool).await; 25 let _ = sqlx::query("DELETE FROM oauth2gw.verification_sessions").execute(pool).await; 26 let _ = sqlx::query("DELETE FROM oauth2gw.clients").execute(pool).await; 27 } 28 29 async fn teardown_test_db(pool: &PgPool) { 30 clean_test_data(pool).await; 31 } 32 33 #[tokio::test] 34 #[serial] 35 async fn test_client_registration() { 36 let pool = setup_test_db().await; 37 38 let client = db::clients::register_client( 39 &pool, 40 "test-exchange-1", 41 "secret123", 42 "https://exchange.example.com/kyc/webhook", 43 "https://verifier.swiyu.io", 44 None, 45 ) 46 .await 47 .expect("Failed to register client"); 48 49 assert_eq!(client.client_id, "test-exchange-1"); 50 assert_eq!(client.webhook_url, "https://exchange.example.com/kyc/webhook"); 51 assert_eq!(client.verifier_url, "https://verifier.swiyu.io"); 52 assert_eq!(client.verifier_management_api_path, "/management/api/verifications"); 53 54 teardown_test_db(&pool).await; 55 } 56 57 #[tokio::test] 58 #[serial] 59 async fn test_client_lookup_by_client_id() { 60 let pool = setup_test_db().await; 61 62 let registered = db::clients::register_client( 63 &pool, 64 "lookup-test", 65 "secret456", 66 "https://example.com/webhook", 67 "https://verifier.example.com", 68 Some("/custom/path"), 69 ) 70 .await 71 .unwrap(); 72 73 let found = db::clients::get_client_by_id(&pool, "lookup-test") 74 .await 75 .unwrap() 76 .expect("Client not found"); 77 78 assert_eq!(found.id, registered.id); 79 assert_eq!(found.verifier_management_api_path, "/custom/path"); 80 81 teardown_test_db(&pool).await; 82 } 83 84 #[tokio::test] 85 #[serial] 86 async fn test_client_not_found() { 87 let pool = setup_test_db().await; 88 89 let result = db::clients::get_client_by_id(&pool, "nonexistent") 90 .await 91 .unwrap(); 92 93 assert!(result.is_none()); 94 95 teardown_test_db(&pool).await; 96 } 97 98 #[tokio::test] 99 #[serial] 100 async fn test_session_creation() { 101 let pool = setup_test_db().await; 102 103 let _client = db::clients::register_client( 104 &pool, 105 "session-client", 106 "secret", 107 "https://example.com/webhook", 108 "https://verifier.example.com", 109 None, 110 ) 111 .await 112 .unwrap(); 113 114 let session = db::sessions::create_session( 115 &pool, 116 "session-client", 117 "nonce-abc123", 118 "first_name last_name age_over_18", 119 15, 120 ) 121 .await 122 .expect("Failed to create session") 123 .expect("Session should be created"); 124 125 assert_eq!(session.nonce, "nonce-abc123"); 126 assert_eq!(session.scope, "first_name last_name age_over_18"); 127 assert_eq!(session.status, db::sessions::SessionStatus::Pending); 128 assert!(session.verification_url.is_none()); 129 assert!(session.request_id.is_none()); 130 assert!(session.verifier_nonce.is_none()); 131 132 teardown_test_db(&pool).await; 133 } 134 135 #[tokio::test] 136 #[serial] 137 async fn test_get_session_for_authorize() { 138 let pool = setup_test_db().await; 139 140 let _client = db::clients::register_client( 141 &pool, 142 "authorize-client", 143 "secret", 144 "https://example.com/webhook", 145 "https://verifier.example.com", 146 Some("/custom/api/path"), 147 ) 148 .await 149 .unwrap(); 150 151 let session = db::sessions::create_session( 152 &pool, 153 "authorize-client", 154 "authorize-test-nonce", 155 "first_name last_name", 156 15, 157 ) 158 .await 159 .unwrap() 160 .unwrap(); 161 162 // Fetch session with client data 163 let data = db::sessions::get_session_for_authorize( 164 &pool, 165 "authorize-test-nonce", 166 "authorize-client", 167 ) 168 .await 169 .unwrap() 170 .expect("Session should be found"); 171 172 assert_eq!(data.session_id, session.id); 173 assert_eq!(data.status, db::sessions::SessionStatus::Pending); 174 assert_eq!(data.scope, "first_name last_name"); 175 assert_eq!(data.verifier_url, "https://verifier.example.com"); 176 assert_eq!(data.verifier_management_api_path, "/custom/api/path"); 177 assert!(data.verification_url.is_none()); 178 179 // Wrong client_id should return None 180 let not_found = db::sessions::get_session_for_authorize( 181 &pool, 182 "authorize-test-nonce", 183 "wrong-client", 184 ) 185 .await 186 .unwrap(); 187 assert!(not_found.is_none()); 188 189 teardown_test_db(&pool).await; 190 } 191 192 #[tokio::test] 193 #[serial] 194 async fn test_authorization_code_creation_and_exchange() { 195 let pool = setup_test_db().await; 196 197 let _client = db::clients::register_client( 198 &pool, 199 "code-client", 200 "secret", 201 "https://example.com/webhook", 202 "https://verifier.example.com", 203 None, 204 ) 205 .await 206 .unwrap(); 207 208 let session = db::sessions::create_session( 209 &pool, 210 "code-client", 211 "code-nonce", 212 "scope", 213 15, 214 ) 215 .await 216 .unwrap() 217 .unwrap(); 218 219 // Create authorization code 220 let code = db::authorization_codes::create_authorization_code( 221 &pool, 222 session.id, 223 "auth-code-xyz123", 224 10, 225 ) 226 .await 227 .unwrap(); 228 229 assert_eq!(code.code, "auth-code-xyz123"); 230 assert_eq!(code.session_id, session.id); 231 assert!(!code.used); 232 233 // Exchange code - first time should mark as used 234 let exchange1 = db::authorization_codes::get_code_for_token_exchange( 235 &pool, 236 "auth-code-xyz123", 237 ) 238 .await 239 .unwrap() 240 .expect("Code should be found"); 241 242 assert!(!exchange1.was_already_used); // First use 243 assert_eq!(exchange1.session_id, session.id); 244 assert!(exchange1.existing_token.is_none()); 245 246 // Exchange code - second time should show as already used 247 let exchange2 = db::authorization_codes::get_code_for_token_exchange( 248 &pool, 249 "auth-code-xyz123", 250 ) 251 .await 252 .unwrap() 253 .expect("Code should still be found"); 254 255 assert!(exchange2.was_already_used); // Already used 256 257 teardown_test_db(&pool).await; 258 } 259 260 #[tokio::test] 261 #[serial] 262 async fn test_create_token_and_complete_session() { 263 let pool = setup_test_db().await; 264 265 let _client = db::clients::register_client( 266 &pool, 267 "complete-client", 268 "secret", 269 "https://example.com/webhook", 270 "https://verifier.example.com", 271 None, 272 ) 273 .await 274 .unwrap(); 275 276 let session = db::sessions::create_session( 277 &pool, 278 "complete-client", 279 "complete-nonce", 280 "scope", 281 15, 282 ) 283 .await 284 .unwrap() 285 .unwrap(); 286 287 // Create token and complete session atomically 288 let token = db::tokens::create_token_and_complete_session( 289 &pool, 290 session.id, 291 "atomic-token-abc", 292 3600, 293 ) 294 .await 295 .unwrap(); 296 297 assert_eq!(token.token, "atomic-token-abc"); 298 assert_eq!(token.session_id, session.id); 299 300 // Verify session was updated to completed 301 let data = db::sessions::get_session_for_authorize( 302 &pool, 303 "complete-nonce", 304 "complete-client", 305 ) 306 .await 307 .unwrap() 308 .unwrap(); 309 310 assert_eq!(data.status, db::sessions::SessionStatus::Completed); 311 312 teardown_test_db(&pool).await; 313 } 314 315 #[tokio::test] 316 #[serial] 317 async fn test_notification_webhook_queue() { 318 let pool = setup_test_db().await; 319 320 let client = db::clients::register_client( 321 &pool, 322 "webhook-client", 323 "secret", 324 "https://example.com/webhook", 325 "https://verifier.example.com", 326 None, 327 ) 328 .await 329 .unwrap(); 330 331 let session = db::sessions::create_session( 332 &pool, 333 "webhook-client", 334 "webhook-nonce", 335 "scope", 336 15, 337 ) 338 .await 339 .unwrap() 340 .unwrap(); 341 342 // Create authorization code first (needed for webhook join) 343 db::authorization_codes::create_authorization_code( 344 &pool, 345 session.id, 346 "webhook-code", 347 10, 348 ) 349 .await 350 .unwrap(); 351 352 // Insert pending webhook 353 let serial = db::notification_webhooks::insert_pending_webhook( 354 &pool, 355 session.id, 356 client.id, 357 "https://example.com/webhook", 358 r#"{"nonce":"webhook-nonce","status":"verified"}"#, 359 ) 360 .await 361 .unwrap(); 362 363 assert!(serial > 0); 364 365 // Fetch pending webhooks 366 let pending = db::notification_webhooks::get_pending_webhooks(&pool, 100) 367 .await 368 .unwrap(); 369 370 assert_eq!(pending.len(), 1); 371 assert_eq!(pending[0].session_id, session.id); 372 assert_eq!(pending[0].code, "webhook-code"); 373 assert_eq!(pending[0].url, "https://example.com/webhook"); 374 375 // Delete webhook after "successful delivery" 376 let deleted = db::notification_webhooks::delete_webhook(&pool, serial) 377 .await 378 .unwrap(); 379 assert!(deleted); 380 381 // Should be empty now 382 let pending_after = db::notification_webhooks::get_pending_webhooks(&pool, 100) 383 .await 384 .unwrap(); 385 assert!(pending_after.is_empty()); 386 387 teardown_test_db(&pool).await; 388 } 389 390 #[tokio::test] 391 #[serial] 392 async fn test_get_token_with_session() { 393 let pool = setup_test_db().await; 394 395 let _client = db::clients::register_client( 396 &pool, 397 "info-client", 398 "secret", 399 "https://example.com/webhook", 400 "https://verifier.example.com", 401 None, 402 ) 403 .await 404 .unwrap(); 405 406 let session = db::sessions::create_session( 407 &pool, 408 "info-client", 409 "info-nonce", 410 "scope", 411 15, 412 ) 413 .await 414 .unwrap() 415 .unwrap(); 416 417 // Create token 418 db::tokens::create_token_and_complete_session( 419 &pool, 420 session.id, 421 "info-token-xyz", 422 3600, 423 ) 424 .await 425 .unwrap(); 426 427 // Fetch token with session data 428 let data = db::tokens::get_token_with_session(&pool, "info-token-xyz") 429 .await 430 .unwrap() 431 .expect("Token should be found"); 432 433 assert!(!data.revoked); 434 assert_eq!(data.session_status, db::sessions::SessionStatus::Completed); 435 436 // Non-existent token returns None 437 let not_found = db::tokens::get_token_with_session(&pool, "nonexistent-token") 438 .await 439 .unwrap(); 440 assert!(not_found.is_none()); 441 442 teardown_test_db(&pool).await; 443 }