From 791b75b3afd8a701633cd90088ea45f1995b918b Mon Sep 17 00:00:00 2001 From: lonelyhentxi Date: Wed, 7 May 2025 02:15:46 +0800 Subject: [PATCH] test: add mikan client login test --- Cargo.lock | 10 +++ apps/recorder/Cargo.toml | 1 + apps/recorder/src/crypto/service.rs | 15 ++-- apps/recorder/src/extract/mikan/client.rs | 89 ++++++++++++++++++++++ apps/recorder/src/models/credential_3rd.rs | 12 +-- 5 files changed, 112 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c6c2e0c..5059b8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3959,6 +3959,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b" +[[package]] +name = "nanoid" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" +dependencies = [ + "rand 0.8.5", +] + [[package]] name = "native-tls" version = "0.2.14" @@ -5152,6 +5161,7 @@ dependencies = [ "maplit", "mockito", "moka", + "nanoid", "once_cell", "opendal", "openidconnect", diff --git a/apps/recorder/Cargo.toml b/apps/recorder/Cargo.toml index 008bd05..9b4c57d 100644 --- a/apps/recorder/Cargo.toml +++ b/apps/recorder/Cargo.toml @@ -115,6 +115,7 @@ reqwest_cookie_store = "0.8.0" downloader = { workspace = true } util = { workspace = true } fetch = { workspace = true } +nanoid = "0.4.0" [dev-dependencies] serial_test = "3" diff --git a/apps/recorder/src/crypto/service.rs b/apps/recorder/src/crypto/service.rs index 466bd3f..97c25a7 100644 --- a/apps/recorder/src/crypto/service.rs +++ b/apps/recorder/src/crypto/service.rs @@ -16,7 +16,7 @@ impl CryptoService { Ok(Self { config }) } - pub fn encrypt_data(&self, data: String) -> Result { + pub fn encrypt_string(&self, data: String) -> Result { let key = rand::rng().random::<[u8; 32]>(); let mut cocoon = Cocoon::new(&key); @@ -32,7 +32,7 @@ impl CryptoService { Ok(BASE64_URL_SAFE.encode(combined)) } - pub fn decrypt_data(&self, data: &str) -> Result { + pub fn decrypt_string(&self, data: &str) -> Result { let decoded = BASE64_URL_SAFE.decode(data)?; let (key, remain) = decoded.split_at(32); @@ -45,20 +45,17 @@ impl CryptoService { String::from_utf8(data).map_err(CryptoError::from) } - pub fn encrypt_credentials( - &self, - credentials: &T, - ) -> Result { + pub fn encrypt_serialize(&self, credentials: &T) -> Result { let json = serde_json::to_string(credentials)?; - self.encrypt_data(json) + self.encrypt_string(json) } - pub fn decrypt_credentials Deserialize<'de>>( + pub fn decrypt_deserialize Deserialize<'de>>( &self, encrypted: &str, ) -> Result { - let data = self.decrypt_data(encrypted)?; + let data = self.decrypt_string(encrypted)?; serde_json::from_str(&data).map_err(CryptoError::from) } diff --git a/apps/recorder/src/extract/mikan/client.rs b/apps/recorder/src/extract/mikan/client.rs index 25084a9..4ca6d01 100644 --- a/apps/recorder/src/extract/mikan/client.rs +++ b/apps/recorder/src/extract/mikan/client.rs @@ -242,3 +242,92 @@ impl Deref for MikanClient { } impl HttpClientTrait for MikanClient {} + +#[cfg(test)] +mod tests { + #![allow(unused_variables)] + use std::assert_matches::assert_matches; + + use rstest::{fixture, rstest}; + use tracing::Level; + + use super::*; + use crate::test_utils::{ + app::UnitTestAppContext, + crypto::build_testing_crypto_service, + database::build_testing_database_service, + mikan::{MikanMockServer, build_testing_mikan_client, build_testing_mikan_credential_form}, + tracing::try_init_testing_tracing, + }; + + async fn create_testing_context( + mikan_base_url: Url, + ) -> RecorderResult> { + let mikan_client = build_testing_mikan_client(mikan_base_url.clone()).await?; + let db_service = build_testing_database_service().await?; + let crypto_service = build_testing_crypto_service().await?; + let ctx = UnitTestAppContext::builder() + .db(db_service) + .crypto(crypto_service) + .mikan(mikan_client) + .build(); + + Ok(Arc::new(ctx)) + } + + #[fixture] + fn before_each() { + try_init_testing_tracing(Level::DEBUG); + } + + #[rstest] + #[tokio::test] + async fn test_mikan_client_submit_credential_form(before_each: ()) -> RecorderResult<()> { + let mut mikan_server = MikanMockServer::new().await?; + + let app_ctx = create_testing_context(mikan_server.base_url().clone()).await?; + + let _login_mock = mikan_server.mock_get_login_page(); + + let mikan_client = app_ctx.mikan(); + let crypto_service = app_ctx.crypto(); + + let credential_form = build_testing_mikan_credential_form(); + + let credential_model = mikan_client + .submit_credential_form(app_ctx.clone(), 1, credential_form.clone()) + .await?; + + let expected_username = &credential_form.username; + let expected_password = &credential_form.password; + + let found_username = crypto_service + .decrypt_string(credential_model.username.as_deref().unwrap_or_default())?; + let found_password = crypto_service + .decrypt_string(credential_model.password.as_deref().unwrap_or_default())?; + + assert_eq!(&found_username, expected_username); + assert_eq!(&found_password, expected_password); + + let has_login = mikan_client.has_login().await?; + + assert!(!has_login); + + assert_matches!( + mikan_client.login().await, + Err(RecorderError::Credential3rdError { .. }) + ); + + let mikan_client = mikan_client + .fork_with_credential(app_ctx.clone(), credential_model.id) + .await?; + + mikan_client.login().await?; + + let has_login = mikan_client.has_login().await?; + + assert!(has_login); + + Ok(()) + } +} diff --git a/apps/recorder/src/models/credential_3rd.rs b/apps/recorder/src/models/credential_3rd.rs index 68b4c1a..6fb0f4f 100644 --- a/apps/recorder/src/models/credential_3rd.rs +++ b/apps/recorder/src/models/credential_3rd.rs @@ -74,17 +74,17 @@ impl ActiveModel { let crypto = ctx.crypto(); if let ActiveValue::Set(Some(username)) = self.username { - let username_enc = crypto.encrypt_credentials(&username)?; + let username_enc = crypto.encrypt_string(username)?; self.username = ActiveValue::Set(Some(username_enc)); } if let ActiveValue::Set(Some(password)) = self.password { - let password_enc = crypto.encrypt_credentials(&password)?; + let password_enc = crypto.encrypt_string(password)?; self.password = ActiveValue::Set(Some(password_enc)); } if let ActiveValue::Set(Some(cookies)) = self.cookies { - let cookies_enc = crypto.encrypt_credentials(&cookies)?; + let cookies_enc = crypto.encrypt_string(cookies)?; self.cookies = ActiveValue::Set(Some(cookies_enc)); } @@ -115,7 +115,7 @@ impl Model { source: None.into(), })?; - let username: String = crypto.decrypt_credentials(&username_enc)?; + let username: String = crypto.decrypt_string(&username_enc)?; let password_enc = self .password @@ -124,10 +124,10 @@ impl Model { source: None.into(), })?; - let password: String = crypto.decrypt_credentials(&password_enc)?; + let password: String = crypto.decrypt_string(&password_enc)?; let cookies: Option = if let Some(cookies_enc) = self.cookies { - let cookies = crypto.decrypt_credentials(&cookies_enc)?; + let cookies = crypto.decrypt_string(&cookies_enc)?; Some(cookies) } else { None