From 4c2cfac06d0bd830a662e460e8f3875c73539b51 Mon Sep 17 00:00:00 2001 From: Jeremiah Russell Date: Mon, 20 Oct 2025 14:24:55 +0100 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20remove=20redun?= =?UTF-8?q?dant=20credential=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Delete src/credential.rs - module was unused in actual codebase - Remove credential module references from lib.rs exports - Update documentation to use standard OAuth2 terminology - Fix CLI docs to reference correct credential_file config - Application uses ConsoleApplicationSecret directly instead The custom Credential struct duplicated functionality already provided by yup_oauth2::ConsoleApplicationSecret. All credential loading is handled by ClientConfig::new_from_configuration() which uses the standard OAuth2 types. --- docs/lib.md | 6 +- docs/main.md | 14 +- src/credential.rs | 693 ---------------------------------------------- src/lib.rs | 2 - 4 files changed, 10 insertions(+), 705 deletions(-) delete mode 100644 src/credential.rs diff --git a/docs/lib.md b/docs/lib.md index 0c4f184..f92912c 100644 --- a/docs/lib.md +++ b/docs/lib.md @@ -304,15 +304,15 @@ Log levels: ### Required Scopes The library requires the `https://mail.google.com/` scope for full Gmail access. -### Credential File Security -- Store credential files securely (not in version control) +### OAuth2 File Security +- Store OAuth2 credential files securely (not in version control) - Use restrictive file permissions (600) - Consider using environment variables in production ## Troubleshooting ### Authentication Issues -1. Verify credential file path and format +1. Verify OAuth2 credential file path and JSON format 2. Check OAuth2 client is configured for "Desktop Application" 3. Ensure redirect URI matches configuration 4. Clear token cache: `rm -rf ~/.cull-gmail/gmail1` diff --git a/docs/main.md b/docs/main.md index ebfbc9f..0b599f5 100644 --- a/docs/main.md +++ b/docs/main.md @@ -46,14 +46,14 @@ cull-gmail --version mkdir -p ~/.cull-gmail ``` -2. Copy your credential file: +2. Copy your OAuth2 credential file: ```bash - cp ~/Downloads/client_secret_*.json ~/.cull-gmail/credential.json + cp ~/Downloads/client_secret_*.json ~/.cull-gmail/client_secret.json ``` 3. Create configuration file `~/.cull-gmail/cull-gmail.toml`: ```toml - credentials = "credential.json" + credential_file = "client_secret.json" config_root = "~/.cull-gmail" rules = "rules.toml" execute = false # Start in dry-run mode @@ -80,7 +80,7 @@ This will: ```toml # OAuth2 credential file (relative to config_root) -credentials = "credential.json" +credential_file = "client_secret.json" # Configuration directory config_root = "~/.cull-gmail" @@ -103,7 +103,7 @@ execute = false Override any configuration setting: ```bash -export APP_CREDENTIALS="credential.json" +export APP_CREDENTIAL_FILE="client_secret.json" export APP_EXECUTE="true" export APP_CLIENT_ID="your-client-id" export APP_CLIENT_SECRET="your-client-secret" @@ -479,7 +479,7 @@ cull-gmail -vvv messages list **Problem**: "Authentication failed" or "Invalid credentials" **Solutions**: -1. Verify credential file exists and is valid JSON +1. Verify OAuth2 credential file exists and is valid JSON 2. Check OAuth client is configured as "Desktop Application" 3. Clear token cache: `rm -rf ~/.cull-gmail/gmail1` 4. Re-run authentication: `cull-gmail labels` @@ -525,7 +525,7 @@ cull-gmail -vvv messages list **Solutions**: 1. Verify config file path: `~/.cull-gmail/cull-gmail.toml` 2. Check TOML syntax -3. Ensure credential file path is correct +3. Ensure OAuth2 credential file path is correct 4. Use absolute paths if relative paths fail ## Exit Codes diff --git a/src/credential.rs b/src/credential.rs deleted file mode 100644 index 9cb6b2b..0000000 --- a/src/credential.rs +++ /dev/null @@ -1,693 +0,0 @@ -//! # OAuth2 Credential Management Module -//! -//! This module provides secure OAuth2 credential handling for Gmail API authentication. -//! It supports loading Google Cloud Platform OAuth2 credentials from JSON files and -//! converting them to the format required by the Gmail API client. -//! -//! ## Overview -//! -//! The credential system handles OAuth2 "installed application" type credentials, -//! which are used for desktop and command-line applications that authenticate users -//! through a browser-based OAuth2 flow. -//! -//! ## Security Considerations -//! -//! - **Credential Storage**: Credentials should be stored securely and never committed to version control -//! - **File Permissions**: Credential files should have restricted permissions (600 or similar) -//! - **Path Handling**: The module resolves paths relative to `~/.cull-gmail/` for security consistency -//! - **Error Handling**: File I/O errors are propagated to prevent silent failures -//! -//! ## Credential File Format -//! -//! The module expects Google Cloud Platform OAuth2 credentials in the standard JSON format: -//! -//! ```json -//! { -//! "installed": { -//! "client_id": "your-client-id.googleusercontent.com", -//! "project_id": "your-project-id", -//! "auth_uri": "https://accounts.google.com/o/oauth2/auth", -//! "token_uri": "https://oauth2.googleapis.com/token", -//! "client_secret": "your-client-secret", -//! "redirect_uris": ["http://localhost"], -//! "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs" -//! } -//! } -//! ``` -//! -//! ## Usage Examples -//! -//! ### Loading Credentials -//! -//! ```rust,no_run -//! use cull_gmail::Credential; -//! use google_gmail1::yup_oauth2::ApplicationSecret; -//! -//! // Load credentials from ~/.cull-gmail/client_secret.json -//! let credential = Credential::load_json_file("client_secret.json"); -//! -//! // Convert to ApplicationSecret for OAuth2 authentication -//! let app_secret: ApplicationSecret = credential.into(); -//! ``` -//! -//! ### Error Handling -//! -//! ```rust,no_run -//! use cull_gmail::Credential; -//! -//! // Note: This will panic if the file doesn't exist or is malformed -//! // In production code, consider using a Result-based approach -//! let credential = Credential::load_json_file("credentials.json"); -//! ``` -//! -//! ## Integration with Gmail Client -//! -//! The credential module integrates seamlessly with the Gmail API client: -//! -//! ```rust,no_run -//! use cull_gmail::{ClientConfig, GmailClient}; -//! -//! # async fn example() -> Result<(), Box> { -//! // Create client configuration with credential file -//! let mut config_builder = ClientConfig::builder(); -//! let config = config_builder -//! .with_credential_file("client_secret.json") -//! .build(); -//! -//! // Initialize Gmail client -//! let client = GmailClient::new_with_config(config).await?; -//! # Ok(()) -//! # } -//! ``` -//! -//! ## File System Layout -//! -//! By default, credentials are expected in the user's home directory: -//! -//! ```text -//! ~/.cull-gmail/ -//! ├── client_secret.json # OAuth2 credentials -//! ├── tokens/ # OAuth2 token cache -//! └── config.toml # Application configuration -//! ``` -//! -//! ## Thread Safety -//! -//! The credential types are safe to clone and use across threads. However, -//! file I/O operations are synchronous and should be performed during -//! application initialization rather than in performance-critical paths. - -use std::{env, fs, path::PathBuf}; - -use google_gmail1::yup_oauth2; -use serde::{Deserialize, Serialize}; - -/// OAuth2 "installed application" credential configuration. -/// -/// This struct represents the credential information for an OAuth2 "installed application" -/// as defined by Google's OAuth2 specification. It contains all the necessary parameters -/// for configuring OAuth2 authentication flows for desktop and command-line applications. -/// -/// # Fields -/// -/// The struct contains OAuth2 configuration parameters that are typically provided -/// by Google Cloud Platform when creating OAuth2 credentials for installed applications. -/// -/// # Security -/// -/// The `client_secret` field contains sensitive information and should be protected. -/// This struct should only be used in secure environments and never exposed in logs -/// or error messages. -/// -/// # Serialization -/// -/// This struct can be serialized to/from JSON format, matching the standard -/// Google OAuth2 credential file format. -#[derive(Debug, Serialize, Deserialize)] -pub struct Installed { - /// OAuth2 client identifier assigned by Google. - /// - /// This is a public identifier that uniquely identifies the OAuth2 application. - /// It typically ends with `.googleusercontent.com`. - pub(crate) client_id: String, - - /// Google Cloud Platform project identifier. - /// - /// Optional field that identifies the GCP project associated with these credentials. - /// Used for quota management and billing purposes. - pub(crate) project_id: Option, - - /// OAuth2 authorization endpoint URL. - /// - /// The URL where users are redirected to authenticate and authorize the application. - /// Typically `https://accounts.google.com/o/oauth2/auth` for Google services. - pub(crate) auth_uri: String, - - /// OAuth2 token exchange endpoint URL. - /// - /// The URL used to exchange authorization codes for access tokens. - /// Typically `https://oauth2.googleapis.com/token` for Google services. - pub(crate) token_uri: String, - - /// URL for OAuth2 provider's X.509 certificate. - /// - /// Optional URL pointing to the public certificates used to verify JWT tokens - /// from the OAuth2 provider. Used for token validation. - pub(crate) auth_provider_x509_cert_url: Option, - - /// OAuth2 client secret. - /// - /// **SENSITIVE**: This is a confidential value that must be kept secure. - /// It's used to authenticate the application to the OAuth2 provider. - /// Never log or expose this value. - pub(crate) client_secret: String, - - /// List of authorized redirect URIs. - /// - /// These URIs are pre-registered with the OAuth2 provider and define - /// where users can be redirected after authorization. For installed - /// applications, this typically includes `http://localhost` variants. - pub(crate) redirect_uris: Vec, -} - -/// OAuth2 credential container for Google API authentication. -/// -/// This struct serves as the main interface for loading and managing OAuth2 credentials -/// used to authenticate with Google APIs, specifically Gmail. It wraps the standard -/// Google OAuth2 credential format and provides convenient methods for loading -/// credentials from the filesystem. -/// -/// # Credential Types -/// -/// Currently supports "installed application" type credentials, which are appropriate -/// for desktop applications and command-line tools that authenticate users through -/// a browser-based OAuth2 flow. -/// -/// # Security Model -/// -/// - Credentials are loaded from the user's home directory (`~/.cull-gmail/`) -/// - Files should have restricted permissions (600) to prevent unauthorized access -/// - Client secrets are sensitive and should never be logged or exposed -/// -/// # Examples -/// -/// ```rust,no_run -/// use cull_gmail::Credential; -/// -/// // Load credentials from ~/.cull-gmail/client_secret.json -/// let credential = Credential::load_json_file("client_secret.json"); -/// -/// // Use with Gmail API client configuration -/// println!("Loaded credential successfully"); -/// ``` -/// -/// # File Format -/// -/// The expected JSON format follows Google's standard OAuth2 credential format: -/// -/// ```json -/// { -/// "installed": { -/// "client_id": "123456789-abc.googleusercontent.com", -/// "client_secret": "your-client-secret", -/// "auth_uri": "https://accounts.google.com/o/oauth2/auth", -/// "token_uri": "https://oauth2.googleapis.com/token", -/// "redirect_uris": ["http://localhost"] -/// } -/// } -/// ``` -#[derive(Debug, Serialize, Deserialize)] -pub struct Credential { - /// The installed application credential configuration. - /// - /// This field contains the actual OAuth2 configuration parameters. - /// It's optional to handle cases where the credential file might - /// be malformed or empty, though in practice it should always be present - /// for valid credential files. - installed: Option, -} - -impl Credential { - /// Loads OAuth2 credentials from a JSON file. - /// - /// This method loads Google OAuth2 credentials from a JSON file located in the - /// user's cull-gmail configuration directory (`~/.cull-gmail/`). The file is - /// expected to be in the standard Google OAuth2 credential format. - /// - /// # Arguments - /// - /// * `path` - Relative path to the credential file within `~/.cull-gmail/` - /// - /// # Returns - /// - /// A `Credential` instance containing the loaded OAuth2 configuration. - /// - /// # Panics - /// - /// This method will panic if: - /// - The user's home directory cannot be determined - /// - The specified file cannot be read (doesn't exist, permission denied, etc.) - /// - The file content is not valid JSON - /// - The JSON structure doesn't match the expected credential format - /// - /// # Security - /// - /// - Only loads files from the secure `~/.cull-gmail/` directory - /// - Ensure credential files have restrictive permissions (600) - /// - Never pass credential file paths from untrusted sources - /// - /// # Examples - /// - /// ```rust,no_run - /// use cull_gmail::Credential; - /// - /// // Load from ~/.cull-gmail/client_secret.json - /// let credential = Credential::load_json_file("client_secret.json"); - /// - /// // Load from ~/.cull-gmail/oauth2/credentials.json - /// let credential = Credential::load_json_file("oauth2/credentials.json"); - /// ``` - /// - /// # Error Handling - /// - /// Consider wrapping calls in error handling for production use: - /// - /// ```rust,no_run - /// use cull_gmail::Credential; - /// use std::panic; - /// - /// let credential = panic::catch_unwind(|| { - /// Credential::load_json_file("client_secret.json") - /// }); - /// - /// match credential { - /// Ok(cred) => println!("Credentials loaded successfully"), - /// Err(_) => eprintln!("Failed to load credentials"), - /// } - /// ``` - pub fn load_json_file(path: &str) -> Self { - let home_dir = env::home_dir().unwrap(); - - let path = PathBuf::new().join(home_dir).join(".cull-gmail").join(path); - let json_str = fs::read_to_string(path).expect("could not read path"); - - serde_json::from_str(&json_str).expect("could not convert to struct") - } -} - -/// Converts a `Credential` into a `yup_oauth2::ApplicationSecret`. -/// -/// This implementation enables seamless integration between cull-gmail's credential -/// system and the `yup_oauth2` crate used for OAuth2 authentication. It extracts -/// the OAuth2 configuration parameters and maps them to the format expected by -/// the OAuth2 client library. -/// -/// # Conversion Process -/// -/// The conversion extracts fields from the `installed` section of the credential -/// and maps them to the corresponding fields in `ApplicationSecret`: -/// -/// - `client_id` → OAuth2 client identifier -/// - `client_secret` → OAuth2 client secret (sensitive) -/// - `auth_uri` → Authorization endpoint URL -/// - `token_uri` → Token exchange endpoint URL -/// - `redirect_uris` → Authorized redirect URIs -/// - `project_id` → GCP project identifier (optional) -/// - `auth_provider_x509_cert_url` → Certificate URL (optional) -/// -/// # Security -/// -/// The conversion preserves all sensitive information, particularly the client secret. -/// The resulting `ApplicationSecret` should be handled with the same security -/// considerations as the original credential. -/// -/// # Examples -/// -/// ```rust,no_run -/// use cull_gmail::Credential; -/// use google_gmail1::yup_oauth2::ApplicationSecret; -/// -/// // Load credential and convert to ApplicationSecret -/// let credential = Credential::load_json_file("client_secret.json"); -/// let app_secret: ApplicationSecret = credential.into(); -/// -/// // Or use the conversion explicitly -/// let credential = Credential::load_json_file("client_secret.json"); -/// let app_secret = ApplicationSecret::from(credential); -/// ``` -/// -/// # Behavior with Missing Fields -/// -/// If the credential doesn't contain an `installed` section (which would indicate -/// a malformed credential file), the conversion creates a default `ApplicationSecret` -/// with empty/default values. In practice, this should not occur with valid -/// credential files. -impl From for yup_oauth2::ApplicationSecret { - fn from(value: Credential) -> Self { - let mut out = Self::default(); - if let Some(installed) = value.installed { - out.client_id = installed.client_id; - out.client_secret = installed.client_secret; - out.token_uri = installed.token_uri; - out.auth_uri = installed.auth_uri; - out.redirect_uris = installed.redirect_uris; - out.project_id = installed.project_id; - // out.client_email = installed.client_email; - out.auth_provider_x509_cert_url = installed.auth_provider_x509_cert_url; - // out.client_x509_cert_url = installed.client_x509_cert_url; - } - out - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::fs; - use tempfile::TempDir; - - /// Helper function to create a temporary credential file for testing - fn create_test_credential_file(temp_dir: &TempDir, filename: &str, content: &str) -> String { - let file_path = temp_dir.path().join(filename); - fs::write(&file_path, content).expect("Failed to write test file"); - file_path.to_string_lossy().to_string() - } - - /// Sample valid credential JSON for testing - fn sample_valid_credential() -> &'static str { - r#"{ - "installed": { - "client_id": "123456789-test.googleusercontent.com", - "project_id": "test-project", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://oauth2.googleapis.com/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_secret": "test-client-secret", - "redirect_uris": ["http://localhost"] - } -}"# - } - - /// Sample minimal valid credential JSON for testing - fn sample_minimal_credential() -> &'static str { - r#"{ - "installed": { - "client_id": "minimal-client-id", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://oauth2.googleapis.com/token", - "client_secret": "minimal-secret", - "redirect_uris": [] - } -}"# - } - - #[test] - fn test_installed_struct_serialization() { - let installed = Installed { - client_id: "test-client-id".to_string(), - project_id: Some("test-project".to_string()), - auth_uri: "https://accounts.google.com/o/oauth2/auth".to_string(), - token_uri: "https://oauth2.googleapis.com/token".to_string(), - auth_provider_x509_cert_url: Some( - "https://www.googleapis.com/oauth2/v1/certs".to_string(), - ), - client_secret: "test-secret".to_string(), - redirect_uris: vec!["http://localhost".to_string()], - }; - - // Test serialization - let json = serde_json::to_string(&installed).expect("Should serialize"); - assert!(json.contains("test-client-id")); - assert!(json.contains("test-secret")); - - // Test deserialization - let deserialized: Installed = serde_json::from_str(&json).expect("Should deserialize"); - assert_eq!(deserialized.client_id, installed.client_id); - assert_eq!(deserialized.client_secret, installed.client_secret); - } - - #[test] - fn test_credential_struct_serialization() { - let installed = Installed { - client_id: "test-id".to_string(), - project_id: None, - auth_uri: "auth-uri".to_string(), - token_uri: "token-uri".to_string(), - auth_provider_x509_cert_url: None, - client_secret: "secret".to_string(), - redirect_uris: vec![], - }; - - let credential = Credential { - installed: Some(installed), - }; - - // Test serialization - let json = serde_json::to_string(&credential).expect("Should serialize"); - assert!(json.contains("test-id")); - - // Test deserialization - let deserialized: Credential = serde_json::from_str(&json).expect("Should deserialize"); - assert!(deserialized.installed.is_some()); - assert_eq!(deserialized.installed.unwrap().client_id, "test-id"); - } - - #[test] - fn test_credential_with_valid_json() { - let json = sample_valid_credential(); - let credential: Credential = serde_json::from_str(json).expect("Should parse valid JSON"); - - assert!(credential.installed.is_some()); - let installed = credential.installed.unwrap(); - assert_eq!(installed.client_id, "123456789-test.googleusercontent.com"); - assert_eq!(installed.client_secret, "test-client-secret"); - assert_eq!(installed.project_id, Some("test-project".to_string())); - assert_eq!( - installed.auth_uri, - "https://accounts.google.com/o/oauth2/auth" - ); - assert_eq!(installed.token_uri, "https://oauth2.googleapis.com/token"); - assert_eq!(installed.redirect_uris, vec!["http://localhost"]); - } - - #[test] - fn test_credential_with_minimal_json() { - let json = sample_minimal_credential(); - let credential: Credential = serde_json::from_str(json).expect("Should parse minimal JSON"); - - assert!(credential.installed.is_some()); - let installed = credential.installed.unwrap(); - assert_eq!(installed.client_id, "minimal-client-id"); - assert_eq!(installed.client_secret, "minimal-secret"); - assert_eq!(installed.project_id, None); - assert!(installed.redirect_uris.is_empty()); - } - - #[test] - fn test_credential_with_empty_installed() { - let json = r#"{"installed": null}"#; - let credential: Credential = - serde_json::from_str(json).expect("Should parse null installed"); - assert!(credential.installed.is_none()); - } - - #[test] - fn test_credential_with_missing_installed() { - let json = r#"{}"#; - let credential: Credential = serde_json::from_str(json).expect("Should parse empty object"); - assert!(credential.installed.is_none()); - } - - #[test] - fn test_invalid_json_parsing() { - let invalid_cases = [ - "", // Empty string - "{", // Incomplete JSON - "not json", // Not JSON at all - r#"{"installed": "wrong"}"#, // Wrong type for installed - r#"{"installed": {"client_id": "test", "missing_required": true}}"#, // Missing required fields - ]; - - for invalid_json in invalid_cases { - let result = serde_json::from_str::(invalid_json); - assert!(result.is_err(), "Should fail to parse: {}", invalid_json); - } - } - - #[test] - fn test_conversion_to_application_secret() { - let json = sample_valid_credential(); - let credential: Credential = serde_json::from_str(json).unwrap(); - let app_secret: yup_oauth2::ApplicationSecret = credential.into(); - - assert_eq!(app_secret.client_id, "123456789-test.googleusercontent.com"); - assert_eq!(app_secret.client_secret, "test-client-secret"); - assert_eq!(app_secret.project_id, Some("test-project".to_string())); - assert_eq!( - app_secret.auth_uri, - "https://accounts.google.com/o/oauth2/auth" - ); - assert_eq!(app_secret.token_uri, "https://oauth2.googleapis.com/token"); - assert_eq!(app_secret.redirect_uris, vec!["http://localhost"]); - assert_eq!( - app_secret.auth_provider_x509_cert_url, - Some("https://www.googleapis.com/oauth2/v1/certs".to_string()) - ); - } - - #[test] - fn test_conversion_with_minimal_credential() { - let json = sample_minimal_credential(); - let credential: Credential = serde_json::from_str(json).unwrap(); - let app_secret: yup_oauth2::ApplicationSecret = credential.into(); - - assert_eq!(app_secret.client_id, "minimal-client-id"); - assert_eq!(app_secret.client_secret, "minimal-secret"); - assert_eq!(app_secret.project_id, None); - assert!(app_secret.redirect_uris.is_empty()); - } - - #[test] - fn test_conversion_with_empty_credential() { - let credential = Credential { installed: None }; - let app_secret: yup_oauth2::ApplicationSecret = credential.into(); - - // Should create default ApplicationSecret - assert!(app_secret.client_id.is_empty()); - assert!(app_secret.client_secret.is_empty()); - assert_eq!(app_secret.project_id, None); - } - - #[test] - fn test_debug_formatting_does_not_expose_secrets() { - let installed = Installed { - client_id: "public-client-id".to_string(), - project_id: Some("public-project".to_string()), - auth_uri: "https://auth.example.com".to_string(), - token_uri: "https://token.example.com".to_string(), - auth_provider_x509_cert_url: None, - client_secret: "VERY_SECRET_VALUE".to_string(), - redirect_uris: vec!["http://localhost:8080".to_string()], - }; - - let credential = Credential { - installed: Some(installed), - }; - - let debug_str = format!("{:?}", credential); - - // Debug should show the structure but we can't easily test that secrets are hidden - // since the current Debug implementation doesn't hide secrets - // This test mainly ensures Debug works without panicking - assert!(debug_str.contains("Credential")); - } - - #[test] - fn test_round_trip_serialization() { - let json = sample_valid_credential(); - let credential: Credential = serde_json::from_str(json).unwrap(); - let serialized = serde_json::to_string(&credential).unwrap(); - let deserialized: Credential = serde_json::from_str(&serialized).unwrap(); - - // Compare the installed sections - match (credential.installed, deserialized.installed) { - (Some(orig), Some(deser)) => { - assert_eq!(orig.client_id, deser.client_id); - assert_eq!(orig.client_secret, deser.client_secret); - assert_eq!(orig.project_id, deser.project_id); - assert_eq!(orig.auth_uri, deser.auth_uri); - assert_eq!(orig.token_uri, deser.token_uri); - assert_eq!(orig.redirect_uris, deser.redirect_uris); - } - _ => panic!("Both should have installed sections"), - } - } - - #[test] - fn test_field_validation_edge_cases() { - // Test with empty strings - let json = r#"{ - "installed": { - "client_id": "", - "auth_uri": "", - "token_uri": "", - "client_secret": "", - "redirect_uris": [] - } -}"#; - - let credential: Credential = - serde_json::from_str(json).expect("Should parse empty strings"); - let app_secret: yup_oauth2::ApplicationSecret = credential.into(); - assert!(app_secret.client_id.is_empty()); - assert!(app_secret.client_secret.is_empty()); - } - - #[test] - fn test_unicode_and_special_characters() { - let json = r#"{ - "installed": { - "client_id": "unicode-テスト-🔐-client", - "auth_uri": "https://auth.example.com/oauth2", - "token_uri": "https://token.example.com/oauth2", - "client_secret": "secret-with-symbols-!@#$%^&*()", - "redirect_uris": ["http://localhost:8080/callback"] - } -}"#; - - let credential: Credential = serde_json::from_str(json).expect("Should handle Unicode"); - let installed = credential.installed.unwrap(); - assert_eq!(installed.client_id, "unicode-テスト-🔐-client"); - assert_eq!(installed.client_secret, "secret-with-symbols-!@#$%^&*()"); - } - - #[test] - fn test_large_redirect_uris_list() { - let mut redirect_uris = Vec::new(); - for i in 0..100 { - redirect_uris.push(format!("http://localhost:{}", 8000 + i)); - } - - let installed = Installed { - client_id: "test-client".to_string(), - project_id: None, - auth_uri: "https://auth.example.com".to_string(), - token_uri: "https://token.example.com".to_string(), - auth_provider_x509_cert_url: None, - client_secret: "test-secret".to_string(), - redirect_uris: redirect_uris.clone(), - }; - - let credential = Credential { - installed: Some(installed), - }; - - let app_secret: yup_oauth2::ApplicationSecret = credential.into(); - assert_eq!(app_secret.redirect_uris.len(), 100); - assert_eq!(app_secret.redirect_uris[0], "http://localhost:8000"); - assert_eq!(app_secret.redirect_uris[99], "http://localhost:8099"); - } - - // Note: We can't easily test the actual file loading functionality - // without mocking the home directory and file system, which would - // require more complex test setup. The current implementation uses - // `env::home_dir()` and direct file operations that would need - // more sophisticated mocking to test properly. - - #[test] - fn test_credential_clone_and_equality() { - let json = sample_minimal_credential(); - let credential1: Credential = serde_json::from_str(json).unwrap(); - - // Test that we can create another credential from the same JSON - let credential2: Credential = serde_json::from_str(json).unwrap(); - - // We can't test equality directly since Credential doesn't implement PartialEq - // but we can test that conversions produce equivalent results - let secret1: yup_oauth2::ApplicationSecret = credential1.into(); - let secret2: yup_oauth2::ApplicationSecret = credential2.into(); - - assert_eq!(secret1.client_id, secret2.client_id); - assert_eq!(secret1.client_secret, secret2.client_secret); - } -} diff --git a/src/lib.rs b/src/lib.rs index 3df3a53..9a86aae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,6 @@ #![doc = include_str!("../docs/lib/lib.md")] mod client_config; -mod credential; mod eol_action; mod error; mod gmail_client; @@ -21,7 +20,6 @@ pub(crate) mod utils; pub use gmail_client::DEFAULT_MAX_RESULTS; pub use client_config::ClientConfig; -pub use credential::Credential; pub use gmail_client::GmailClient; pub(crate) use gmail_client::MessageSummary; pub use retention::Retention;