🔧 fix: Resolve clippy warnings and formatting issues
- Remove unnecessary borrowing where values can be used directly - Use variables directly in format strings for better readability - Apply consistent code formatting per rustfmt standards - All clippy warnings resolved with -D warnings flag - Code now follows Rust idioms and best practices
This commit is contained in:
committed by
Jeremiah Russell
parent
2bee42d7ba
commit
171f441f1d
@@ -195,7 +195,7 @@ enum SubCmds {
|
|||||||
/// retention periods, label targeting, and automated actions.
|
/// retention periods, label targeting, and automated actions.
|
||||||
#[clap(name = "rules", display_order = 2)]
|
#[clap(name = "rules", display_order = 2)]
|
||||||
Rules(RulesCli),
|
Rules(RulesCli),
|
||||||
|
|
||||||
/// Export and import OAuth2 tokens for ephemeral environments.
|
/// Export and import OAuth2 tokens for ephemeral environments.
|
||||||
///
|
///
|
||||||
/// Supports token export to compressed strings and automatic import from
|
/// Supports token export to compressed strings and automatic import from
|
||||||
@@ -511,17 +511,20 @@ async fn run_rules(client: &mut GmailClient, rules: Rules, execute: bool) -> Res
|
|||||||
/// - CI/CD pipelines with stored token secrets
|
/// - CI/CD pipelines with stored token secrets
|
||||||
/// - Ephemeral compute environments requiring periodic Gmail access
|
/// - Ephemeral compute environments requiring periodic Gmail access
|
||||||
fn restore_tokens_if_available(config: &Config, client_config: &ClientConfig) -> Result<()> {
|
fn restore_tokens_if_available(config: &Config, client_config: &ClientConfig) -> Result<()> {
|
||||||
let token_env_var = config.get_string("token_cache_env")
|
let token_env_var = config
|
||||||
|
.get_string("token_cache_env")
|
||||||
.unwrap_or_else(|_| "CULL_GMAIL_TOKEN_CACHE".to_string());
|
.unwrap_or_else(|_| "CULL_GMAIL_TOKEN_CACHE".to_string());
|
||||||
|
|
||||||
if let Ok(token_data) = env::var(&token_env_var) {
|
if let Ok(token_data) = env::var(&token_env_var) {
|
||||||
log::info!("Found {} environment variable, restoring tokens", token_env_var);
|
log::info!("Found {token_env_var} environment variable, restoring tokens");
|
||||||
restore_tokens_from_string(&token_data, client_config.persist_path())?;
|
restore_tokens_from_string(&token_data, client_config.persist_path())?;
|
||||||
log::info!("Tokens successfully restored from environment variable");
|
log::info!("Tokens successfully restored from environment variable");
|
||||||
} else {
|
} else {
|
||||||
log::debug!("No {} environment variable found, proceeding with normal token flow", token_env_var);
|
log::debug!(
|
||||||
|
"No {token_env_var} environment variable found, proceeding with normal token flow"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
//! ## Overview
|
//! ## Overview
|
||||||
//!
|
//!
|
||||||
//! The token management system allows users to:
|
//! The token management system allows users to:
|
||||||
//!
|
//!
|
||||||
//! - **Export tokens**: Extract current OAuth2 tokens to a compressed base64 string
|
//! - **Export tokens**: Extract current OAuth2 tokens to a compressed base64 string
|
||||||
//! - **Import tokens**: Recreate token files from environment variables
|
//! - **Import tokens**: Recreate token files from environment variables
|
||||||
//! - **Ephemeral workflows**: Run in clean environments by restoring tokens from env vars
|
//! - **Ephemeral workflows**: Run in clean environments by restoring tokens from env vars
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
//! ```bash
|
//! ```bash
|
||||||
//! # Export tokens from development environment
|
//! # Export tokens from development environment
|
||||||
//! cull-gmail token export
|
//! cull-gmail token export
|
||||||
//!
|
//!
|
||||||
//! # Set environment variable in container
|
//! # Set environment variable in container
|
||||||
//! docker run -e CULL_GMAIL_TOKEN_CACHE="<exported-string>" my-app
|
//! docker run -e CULL_GMAIL_TOKEN_CACHE="<exported-string>" my-app
|
||||||
//! ```
|
//! ```
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
//! ```bash
|
//! ```bash
|
||||||
//! # Store tokens as secret in CI system
|
//! # Store tokens as secret in CI system
|
||||||
//! cull-gmail token export > token.secret
|
//! cull-gmail token export > token.secret
|
||||||
//!
|
//!
|
||||||
//! # Use in pipeline
|
//! # Use in pipeline
|
||||||
//! export CULL_GMAIL_TOKEN_CACHE=$(cat token.secret)
|
//! export CULL_GMAIL_TOKEN_CACHE=$(cat token.secret)
|
||||||
//! cull-gmail messages list --query "older_than:30d"
|
//! cull-gmail messages list --query "older_than:30d"
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
//! ```bash
|
//! ```bash
|
||||||
//! # One-time setup: export tokens
|
//! # One-time setup: export tokens
|
||||||
//! TOKENS=$(cull-gmail token export)
|
//! TOKENS=$(cull-gmail token export)
|
||||||
//!
|
//!
|
||||||
//! # Recurring job: restore and use
|
//! # Recurring job: restore and use
|
||||||
//! export CULL_GMAIL_TOKEN_CACHE="$TOKENS"
|
//! export CULL_GMAIL_TOKEN_CACHE="$TOKENS"
|
||||||
//! cull-gmail rules run
|
//! cull-gmail rules run
|
||||||
@@ -57,12 +57,12 @@
|
|||||||
//! - Token metadata and expiration
|
//! - Token metadata and expiration
|
||||||
//! - Encoded as base64 for environment variable compatibility
|
//! - Encoded as base64 for environment variable compatibility
|
||||||
|
|
||||||
|
use crate::{ClientConfig, Result};
|
||||||
|
use base64::{Engine as _, engine::general_purpose::STANDARD as Base64Engine};
|
||||||
|
use clap::Subcommand;
|
||||||
|
use cull_gmail::Error;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use clap::Subcommand;
|
|
||||||
use base64::{Engine as _, engine::general_purpose::STANDARD as Base64Engine};
|
|
||||||
use crate::{Result, ClientConfig};
|
|
||||||
use cull_gmail::Error;
|
|
||||||
|
|
||||||
/// Token management operations for ephemeral environments.
|
/// Token management operations for ephemeral environments.
|
||||||
///
|
///
|
||||||
@@ -81,10 +81,10 @@ use cull_gmail::Error;
|
|||||||
/// ```bash
|
/// ```bash
|
||||||
/// # Export to stdout
|
/// # Export to stdout
|
||||||
/// cull-gmail token export
|
/// cull-gmail token export
|
||||||
///
|
///
|
||||||
/// # Export to file
|
/// # Export to file
|
||||||
/// cull-gmail token export > tokens.env
|
/// cull-gmail token export > tokens.env
|
||||||
///
|
///
|
||||||
/// # Export to environment variable
|
/// # Export to environment variable
|
||||||
/// export MY_TOKENS=$(cull-gmail token export)
|
/// export MY_TOKENS=$(cull-gmail token export)
|
||||||
/// ```
|
/// ```
|
||||||
@@ -93,7 +93,7 @@ use cull_gmail::Error;
|
|||||||
/// ```bash
|
/// ```bash
|
||||||
/// # Set environment variable
|
/// # Set environment variable
|
||||||
/// export CULL_GMAIL_TOKEN_CACHE="<base64-string>"
|
/// export CULL_GMAIL_TOKEN_CACHE="<base64-string>"
|
||||||
///
|
///
|
||||||
/// # Run normally - tokens will be restored automatically
|
/// # Run normally - tokens will be restored automatically
|
||||||
/// cull-gmail labels
|
/// cull-gmail labels
|
||||||
/// ```
|
/// ```
|
||||||
@@ -116,7 +116,7 @@ pub enum TokenCommand {
|
|||||||
/// or CI/CD secret systems.
|
/// or CI/CD secret systems.
|
||||||
///
|
///
|
||||||
/// ## Output Format
|
/// ## Output Format
|
||||||
///
|
///
|
||||||
/// The output is a single line containing a base64-encoded string that represents
|
/// The output is a single line containing a base64-encoded string that represents
|
||||||
/// the compressed JSON structure of all OAuth2 tokens and metadata.
|
/// the compressed JSON structure of all OAuth2 tokens and metadata.
|
||||||
///
|
///
|
||||||
@@ -125,15 +125,15 @@ pub enum TokenCommand {
|
|||||||
/// ```bash
|
/// ```bash
|
||||||
/// # Basic export
|
/// # Basic export
|
||||||
/// cull-gmail token export
|
/// cull-gmail token export
|
||||||
///
|
///
|
||||||
/// # Store in environment variable
|
/// # Store in environment variable
|
||||||
/// export TOKENS=$(cull-gmail token export)
|
/// export TOKENS=$(cull-gmail token export)
|
||||||
///
|
///
|
||||||
/// # Save to file
|
/// # Save to file
|
||||||
/// cull-gmail token export > token.secret
|
/// cull-gmail token export > token.secret
|
||||||
/// ```
|
/// ```
|
||||||
Export,
|
Export,
|
||||||
|
|
||||||
/// Import OAuth2 tokens from environment variable.
|
/// Import OAuth2 tokens from environment variable.
|
||||||
///
|
///
|
||||||
/// This command is typically not called directly, as token import happens
|
/// This command is typically not called directly, as token import happens
|
||||||
@@ -145,7 +145,7 @@ pub enum TokenCommand {
|
|||||||
/// ```bash
|
/// ```bash
|
||||||
/// # Set the environment variable
|
/// # Set the environment variable
|
||||||
/// export CULL_GMAIL_TOKEN_CACHE="<base64-string>"
|
/// export CULL_GMAIL_TOKEN_CACHE="<base64-string>"
|
||||||
///
|
///
|
||||||
/// # Import explicitly (usually automatic)
|
/// # Import explicitly (usually automatic)
|
||||||
/// cull-gmail token import
|
/// cull-gmail token import
|
||||||
/// ```
|
/// ```
|
||||||
@@ -210,66 +210,73 @@ impl TokenCli {
|
|||||||
async fn export_tokens(config: &ClientConfig) -> Result<()> {
|
async fn export_tokens(config: &ClientConfig) -> Result<()> {
|
||||||
let token_path = Path::new(config.persist_path());
|
let token_path = Path::new(config.persist_path());
|
||||||
let mut token_data = std::collections::HashMap::new();
|
let mut token_data = std::collections::HashMap::new();
|
||||||
|
|
||||||
if token_path.is_file() {
|
if token_path.is_file() {
|
||||||
// OAuth2 token is stored as a single file
|
// OAuth2 token is stored as a single file
|
||||||
let filename = token_path.file_name()
|
let filename = token_path
|
||||||
|
.file_name()
|
||||||
.and_then(|n| n.to_str())
|
.and_then(|n| n.to_str())
|
||||||
.ok_or_else(|| Error::FileIo("Invalid token filename".to_string()))?;
|
.ok_or_else(|| Error::FileIo("Invalid token filename".to_string()))?;
|
||||||
|
|
||||||
let content = fs::read_to_string(&token_path)
|
let content = fs::read_to_string(token_path)
|
||||||
.map_err(|e| Error::FileIo(format!("Failed to read token file: {}", e)))?;
|
.map_err(|e| Error::FileIo(format!("Failed to read token file: {e}")))?;
|
||||||
|
|
||||||
token_data.insert(filename.to_string(), content);
|
token_data.insert(filename.to_string(), content);
|
||||||
} else if token_path.is_dir() {
|
} else if token_path.is_dir() {
|
||||||
// Token directory with multiple files (legacy support)
|
// Token directory with multiple files (legacy support)
|
||||||
for entry in fs::read_dir(token_path).map_err(|e| Error::FileIo(e.to_string()))? {
|
for entry in fs::read_dir(token_path).map_err(|e| Error::FileIo(e.to_string()))? {
|
||||||
let entry = entry.map_err(|e| Error::FileIo(e.to_string()))?;
|
let entry = entry.map_err(|e| Error::FileIo(e.to_string()))?;
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
|
|
||||||
if path.is_file() {
|
if path.is_file() {
|
||||||
let filename = path.file_name()
|
let filename = path
|
||||||
|
.file_name()
|
||||||
.and_then(|n| n.to_str())
|
.and_then(|n| n.to_str())
|
||||||
.ok_or_else(|| Error::FileIo("Invalid filename in token cache".to_string()))?;
|
.ok_or_else(|| Error::FileIo("Invalid filename in token cache".to_string()))?;
|
||||||
|
|
||||||
let content = fs::read_to_string(&path)
|
let content = fs::read_to_string(&path).map_err(|e| {
|
||||||
.map_err(|e| Error::FileIo(format!("Failed to read token file {}: {}", filename, e)))?;
|
Error::FileIo(format!("Failed to read token file {filename}: {e}"))
|
||||||
|
})?;
|
||||||
|
|
||||||
token_data.insert(filename.to_string(), content);
|
token_data.insert(filename.to_string(), content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::TokenNotFound(format!(
|
return Err(Error::TokenNotFound(format!(
|
||||||
"Token cache not found: {}",
|
"Token cache not found: {}",
|
||||||
token_path.display()
|
token_path.display()
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if token_data.is_empty() {
|
if token_data.is_empty() {
|
||||||
return Err(Error::TokenNotFound("No token data found in cache".to_string()));
|
return Err(Error::TokenNotFound(
|
||||||
|
"No token data found in cache".to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize to JSON
|
// Serialize to JSON
|
||||||
let json_data = serde_json::to_string(&token_data)
|
let json_data = serde_json::to_string(&token_data)
|
||||||
.map_err(|e| Error::SerializationError(format!("Failed to serialize token data: {}", e)))?;
|
.map_err(|e| Error::SerializationError(format!("Failed to serialize token data: {e}")))?;
|
||||||
|
|
||||||
// Compress using flate2
|
// Compress using flate2
|
||||||
use flate2::write::GzEncoder;
|
|
||||||
use flate2::Compression;
|
use flate2::Compression;
|
||||||
|
use flate2::write::GzEncoder;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
|
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
|
||||||
encoder.write_all(json_data.as_bytes())
|
encoder
|
||||||
.map_err(|e| Error::SerializationError(format!("Failed to compress token data: {}", e)))?;
|
.write_all(json_data.as_bytes())
|
||||||
let compressed_data = encoder.finish()
|
.map_err(|e| Error::SerializationError(format!("Failed to compress token data: {e}")))?;
|
||||||
.map_err(|e| Error::SerializationError(format!("Failed to finalize compression: {}", e)))?;
|
let compressed_data = encoder
|
||||||
|
.finish()
|
||||||
|
.map_err(|e| Error::SerializationError(format!("Failed to finalize compression: {e}")))?;
|
||||||
|
|
||||||
// Encode to base64
|
// Encode to base64
|
||||||
let encoded = Base64Engine.encode(&compressed_data);
|
let encoded = Base64Engine.encode(&compressed_data);
|
||||||
|
|
||||||
// Output to stdout
|
// Output to stdout
|
||||||
println!("{}", encoded);
|
println!("{encoded}");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,13 +309,12 @@ async fn export_tokens(config: &ClientConfig) -> Result<()> {
|
|||||||
/// - Decoding/decompression errors for malformed token data
|
/// - Decoding/decompression errors for malformed token data
|
||||||
/// - I/O errors creating token files
|
/// - I/O errors creating token files
|
||||||
pub async fn import_tokens(config: &ClientConfig) -> Result<()> {
|
pub async fn import_tokens(config: &ClientConfig) -> Result<()> {
|
||||||
let token_env = std::env::var("CULL_GMAIL_TOKEN_CACHE")
|
let token_env = std::env::var("CULL_GMAIL_TOKEN_CACHE").map_err(|_| {
|
||||||
.map_err(|_| Error::TokenNotFound(
|
Error::TokenNotFound("CULL_GMAIL_TOKEN_CACHE environment variable not set".to_string())
|
||||||
"CULL_GMAIL_TOKEN_CACHE environment variable not set".to_string()
|
})?;
|
||||||
))?;
|
|
||||||
|
|
||||||
restore_tokens_from_string(&token_env, config.persist_path())?;
|
restore_tokens_from_string(&token_env, config.persist_path())?;
|
||||||
|
|
||||||
log::info!("Tokens successfully imported from environment variable");
|
log::info!("Tokens successfully imported from environment variable");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -332,85 +338,100 @@ pub async fn import_tokens(config: &ClientConfig) -> Result<()> {
|
|||||||
/// Created token files are set to 600 (owner read/write only) for security.
|
/// Created token files are set to 600 (owner read/write only) for security.
|
||||||
pub fn restore_tokens_from_string(token_string: &str, persist_path: &str) -> Result<()> {
|
pub fn restore_tokens_from_string(token_string: &str, persist_path: &str) -> Result<()> {
|
||||||
// Decode from base64
|
// Decode from base64
|
||||||
let compressed_data = Base64Engine.decode(token_string.trim())
|
let compressed_data = Base64Engine.decode(token_string.trim()).map_err(|e| {
|
||||||
.map_err(|e| Error::SerializationError(format!("Failed to decode base64 token data: {}", e)))?;
|
Error::SerializationError(format!("Failed to decode base64 token data: {e}"))
|
||||||
|
})?;
|
||||||
|
|
||||||
// Decompress
|
// Decompress
|
||||||
use flate2::read::GzDecoder;
|
use flate2::read::GzDecoder;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
let mut decoder = GzDecoder::new(compressed_data.as_slice());
|
let mut decoder = GzDecoder::new(compressed_data.as_slice());
|
||||||
let mut json_data = String::new();
|
let mut json_data = String::new();
|
||||||
decoder.read_to_string(&mut json_data)
|
decoder
|
||||||
.map_err(|e| Error::SerializationError(format!("Failed to decompress token data: {}", e)))?;
|
.read_to_string(&mut json_data)
|
||||||
|
.map_err(|e| Error::SerializationError(format!("Failed to decompress token data: {e}")))?;
|
||||||
|
|
||||||
// Parse JSON
|
// Parse JSON
|
||||||
let token_files: std::collections::HashMap<String, String> = serde_json::from_str(&json_data)
|
let token_files: std::collections::HashMap<String, String> =
|
||||||
.map_err(|e| Error::SerializationError(format!("Failed to parse token JSON: {}", e)))?;
|
serde_json::from_str(&json_data)
|
||||||
|
.map_err(|e| Error::SerializationError(format!("Failed to parse token JSON: {e}")))?;
|
||||||
|
|
||||||
let token_path = Path::new(persist_path);
|
let token_path = Path::new(persist_path);
|
||||||
|
|
||||||
// Count files for logging
|
// Count files for logging
|
||||||
let file_count = token_files.len();
|
let file_count = token_files.len();
|
||||||
|
|
||||||
if file_count == 1 && token_files.keys().next().map(|k| k.as_str()) == token_path.file_name().and_then(|n| n.to_str()) {
|
if file_count == 1
|
||||||
|
&& token_files.keys().next().map(|k| k.as_str())
|
||||||
|
== token_path.file_name().and_then(|n| n.to_str())
|
||||||
|
{
|
||||||
// Single file case - write directly to the persist path
|
// Single file case - write directly to the persist path
|
||||||
let content = token_files.into_values().next().unwrap();
|
let content = token_files.into_values().next().unwrap();
|
||||||
|
|
||||||
// Create parent directory if needed
|
// Create parent directory if needed
|
||||||
if let Some(parent) = token_path.parent() {
|
if let Some(parent) = token_path.parent() {
|
||||||
fs::create_dir_all(parent)
|
fs::create_dir_all(parent).map_err(|e| {
|
||||||
.map_err(|e| Error::FileIo(format!("Failed to create token directory {}: {}", parent.display(), e)))?;
|
Error::FileIo(format!(
|
||||||
|
"Failed to create token directory {}: {}",
|
||||||
|
parent.display(),
|
||||||
|
e
|
||||||
|
))
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
fs::write(&token_path, &content)
|
fs::write(token_path, &content)
|
||||||
.map_err(|e| Error::FileIo(format!("Failed to write token file: {}", e)))?;
|
.map_err(|e| Error::FileIo(format!("Failed to write token file: {e}")))?;
|
||||||
|
|
||||||
// Set secure permissions (600 - owner read/write only)
|
// Set secure permissions (600 - owner read/write only)
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
let mut perms = fs::metadata(&token_path)
|
let mut perms = fs::metadata(token_path)
|
||||||
.map_err(|e| Error::FileIo(format!("Failed to get file metadata: {}", e)))?
|
.map_err(|e| Error::FileIo(format!("Failed to get file metadata: {e}")))?
|
||||||
.permissions();
|
.permissions();
|
||||||
perms.set_mode(0o600);
|
perms.set_mode(0o600);
|
||||||
fs::set_permissions(&token_path, perms)
|
fs::set_permissions(token_path, perms)
|
||||||
.map_err(|e| Error::FileIo(format!("Failed to set file permissions: {}", e)))?;
|
.map_err(|e| Error::FileIo(format!("Failed to set file permissions: {e}")))?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Multiple files case - create directory structure
|
// Multiple files case - create directory structure
|
||||||
fs::create_dir_all(token_path)
|
fs::create_dir_all(token_path).map_err(|e| {
|
||||||
.map_err(|e| Error::FileIo(format!("Failed to create token directory {}: {}", persist_path, e)))?;
|
Error::FileIo(format!(
|
||||||
|
"Failed to create token directory {persist_path}: {e}"
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
// Write token files
|
// Write token files
|
||||||
for (filename, content) in token_files {
|
for (filename, content) in token_files {
|
||||||
let file_path = token_path.join(&filename);
|
let file_path = token_path.join(&filename);
|
||||||
fs::write(&file_path, &content)
|
fs::write(&file_path, &content).map_err(|e| {
|
||||||
.map_err(|e| Error::FileIo(format!("Failed to write token file {}: {}", filename, e)))?;
|
Error::FileIo(format!("Failed to write token file {filename}: {e}"))
|
||||||
|
})?;
|
||||||
|
|
||||||
// Set secure permissions (600 - owner read/write only)
|
// Set secure permissions (600 - owner read/write only)
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
let mut perms = fs::metadata(&file_path)
|
let mut perms = fs::metadata(&file_path)
|
||||||
.map_err(|e| Error::FileIo(format!("Failed to get file metadata: {}", e)))?
|
.map_err(|e| Error::FileIo(format!("Failed to get file metadata: {e}")))?
|
||||||
.permissions();
|
.permissions();
|
||||||
perms.set_mode(0o600);
|
perms.set_mode(0o600);
|
||||||
fs::set_permissions(&file_path, perms)
|
fs::set_permissions(&file_path, perms)
|
||||||
.map_err(|e| Error::FileIo(format!("Failed to set file permissions: {}", e)))?;
|
.map_err(|e| Error::FileIo(format!("Failed to set file permissions: {e}")))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!("Restored {} token files to {}", file_count, persist_path);
|
log::info!("Restored {file_count} token files to {persist_path}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use tempfile::TempDir;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_token_export_import_cycle() {
|
fn test_token_export_import_cycle() {
|
||||||
@@ -418,69 +439,72 @@ mod tests {
|
|||||||
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
||||||
let token_dir = temp_dir.path().join("gmail1");
|
let token_dir = temp_dir.path().join("gmail1");
|
||||||
fs::create_dir_all(&token_dir).expect("Failed to create token dir");
|
fs::create_dir_all(&token_dir).expect("Failed to create token dir");
|
||||||
|
|
||||||
// Create mock token files
|
// Create mock token files
|
||||||
let mut test_files = HashMap::new();
|
let mut test_files = HashMap::new();
|
||||||
test_files.insert("tokencache.json".to_string(),
|
test_files.insert(
|
||||||
r#"{"access_token":"test_access","refresh_token":"test_refresh"}"#.to_string());
|
"tokencache.json".to_string(),
|
||||||
test_files.insert("metadata.json".to_string(),
|
r#"{"access_token":"test_access","refresh_token":"test_refresh"}"#.to_string(),
|
||||||
r#"{"created":"2023-01-01","expires":"2023-12-31"}"#.to_string());
|
);
|
||||||
|
test_files.insert(
|
||||||
|
"metadata.json".to_string(),
|
||||||
|
r#"{"created":"2023-01-01","expires":"2023-12-31"}"#.to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
for (filename, content) in &test_files {
|
for (filename, content) in &test_files {
|
||||||
fs::write(token_dir.join(filename), content)
|
fs::write(token_dir.join(filename), content).expect("Failed to write test token file");
|
||||||
.expect("Failed to write test token file");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test export
|
// Test export
|
||||||
let config = crate::ClientConfig::builder()
|
let config = crate::ClientConfig::builder()
|
||||||
.with_client_id("test")
|
.with_client_id("test")
|
||||||
.with_config_path(temp_dir.path().to_str().unwrap())
|
.with_config_path(temp_dir.path().to_str().unwrap())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Export tokens (this would normally print to stdout)
|
// Export tokens (this would normally print to stdout)
|
||||||
// We'll test the internal function instead
|
// We'll test the internal function instead
|
||||||
let result = tokio_test::block_on(export_tokens(&config));
|
let result = tokio_test::block_on(export_tokens(&config));
|
||||||
assert!(result.is_ok(), "Export should succeed");
|
assert!(result.is_ok(), "Export should succeed");
|
||||||
|
|
||||||
// For full integration test, we would capture stdout and test import
|
// For full integration test, we would capture stdout and test import
|
||||||
// but that requires more complex setup with process isolation
|
// but that requires more complex setup with process isolation
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_restore_tokens_from_string() {
|
fn test_restore_tokens_from_string() {
|
||||||
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
||||||
let persist_path = temp_dir.path().join("gmail1").to_string_lossy().to_string();
|
let persist_path = temp_dir.path().join("gmail1").to_string_lossy().to_string();
|
||||||
|
|
||||||
// Create test data
|
// Create test data
|
||||||
let mut token_data = HashMap::new();
|
let mut token_data = HashMap::new();
|
||||||
token_data.insert("test.json".to_string(), r#"{"token":"value"}"#.to_string());
|
token_data.insert("test.json".to_string(), r#"{"token":"value"}"#.to_string());
|
||||||
|
|
||||||
let json_str = serde_json::to_string(&token_data).unwrap();
|
let json_str = serde_json::to_string(&token_data).unwrap();
|
||||||
|
|
||||||
// Compress
|
// Compress
|
||||||
use flate2::write::GzEncoder;
|
|
||||||
use flate2::Compression;
|
use flate2::Compression;
|
||||||
|
use flate2::write::GzEncoder;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
|
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
|
||||||
encoder.write_all(json_str.as_bytes()).unwrap();
|
encoder.write_all(json_str.as_bytes()).unwrap();
|
||||||
let compressed = encoder.finish().unwrap();
|
let compressed = encoder.finish().unwrap();
|
||||||
|
|
||||||
// Encode
|
// Encode
|
||||||
let encoded = Base64Engine.encode(&compressed);
|
let encoded = Base64Engine.encode(&compressed);
|
||||||
|
|
||||||
// Test restore
|
// Test restore
|
||||||
let result = restore_tokens_from_string(&encoded, &persist_path);
|
let result = restore_tokens_from_string(&encoded, &persist_path);
|
||||||
assert!(result.is_ok(), "Restore should succeed: {:?}", result);
|
assert!(result.is_ok(), "Restore should succeed: {result:?}");
|
||||||
|
|
||||||
// Verify file was created
|
// Verify file was created
|
||||||
let restored_path = Path::new(&persist_path).join("test.json");
|
let restored_path = Path::new(&persist_path).join("test.json");
|
||||||
assert!(restored_path.exists(), "Token file should be restored");
|
assert!(restored_path.exists(), "Token file should be restored");
|
||||||
|
|
||||||
let restored_content = fs::read_to_string(restored_path).unwrap();
|
let restored_content = fs::read_to_string(restored_path).unwrap();
|
||||||
assert_eq!(restored_content, r#"{"token":"value"}"#);
|
assert_eq!(restored_content, r#"{"token":"value"}"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_missing_token_directory() {
|
fn test_missing_token_directory() {
|
||||||
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
||||||
@@ -488,19 +512,19 @@ mod tests {
|
|||||||
.with_client_id("test")
|
.with_client_id("test")
|
||||||
.with_config_path(temp_dir.path().join("nonexistent").to_str().unwrap())
|
.with_config_path(temp_dir.path().join("nonexistent").to_str().unwrap())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let result = tokio_test::block_on(export_tokens(&config));
|
let result = tokio_test::block_on(export_tokens(&config));
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert!(matches!(result.unwrap_err(), Error::TokenNotFound(_)));
|
assert!(matches!(result.unwrap_err(), Error::TokenNotFound(_)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_invalid_base64_restore() {
|
fn test_invalid_base64_restore() {
|
||||||
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
||||||
let persist_path = temp_dir.path().to_string_lossy().to_string();
|
let persist_path = temp_dir.path().to_string_lossy().to_string();
|
||||||
|
|
||||||
let result = restore_tokens_from_string("invalid-base64!", &persist_path);
|
let result = restore_tokens_from_string("invalid-base64!", &persist_path);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert!(matches!(result.unwrap_err(), Error::SerializationError(_)));
|
assert!(matches!(result.unwrap_err(), Error::SerializationError(_)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user