kych

OAuth 2.0 API for Swiyu to enable Taler integration of Swiyu for KYC (experimental)
Log | Files | Refs

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 }