use std::io::BufRead;
use std::sync::Arc;
use std::time::Duration;
use clap::Subcommand;
use crate::admin;
use crate::audit::admin as audit_admin;
use crate::cli::CliError;
use crate::config::Config;
use crate::sqlite::db::Database;
use crate::sqlite::nonce::Nonce;
#[derive(Subcommand)]
pub enum NonceCommand {
Cleanup {
#[arg(long = "ttl-seconds")]
ttl_seconds: Option<u64>,
},
Count {
#[arg(long)]
json: bool,
},
}
pub async fn run_nonce_command(
command: NonceCommand,
yes: bool,
reader: &mut impl BufRead,
config: &Config,
database: Arc<Database>,
) -> Result<(), CliError> {
match command {
NonceCommand::Cleanup { ttl_seconds } => {
let ttl = Duration::from_secs(ttl_seconds.unwrap_or(config.nonce.ttl_seconds));
match admin::confirm_cleanup_nonces(ttl, yes, reader, database.clone()).await? {
None => println!("Cancelled."),
Some(removed) => {
if removed > 0 {
audit_admin::record_cli_action(&database, |actor, client| {
audit_admin::nonce_cleanup_completed(actor, client, removed)
})
.await;
}
println!("Removed {removed} nonce(s).");
}
}
}
NonceCommand::Count { json } => {
let count = Nonce::count(&database).await?;
let ttl = config.nonce.ttl_seconds;
if json {
println!("{}", admin::render_nonce_stats_json(count, ttl));
} else {
println!("{count} nonce(s), ttl {ttl}s.");
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn count_reports_the_table_and_the_configured_ttl() {
let database = Arc::new(Database::connect_in_memory().await.unwrap());
let mut config = Config::default();
config.nonce.ttl_seconds = 42;
for _ in 0..3 {
Nonce::new().save(&database).await.unwrap();
}
assert_eq!(Nonce::count(&database).await.unwrap(), 3);
assert_eq!(
admin::render_nonce_stats_json(3, config.nonce.ttl_seconds),
serde_json::json!({ "count": 3, "ttlSeconds": 42 }),
);
for json in [false, true] {
let mut reader: &[u8] = &[];
run_nonce_command(
NonceCommand::Count { json },
true,
&mut reader,
&config,
database.clone(),
)
.await
.unwrap();
}
}
#[tokio::test]
async fn cleanup_honours_the_configured_ttl_and_the_prompt() {
let database = Arc::new(
crate::sqlite::db::Database::connect_in_memory()
.await
.unwrap(),
);
let mut config = Config::default();
config.nonce.ttl_seconds = 1;
let mut declined: &[u8] = b"n\n";
run_nonce_command(
NonceCommand::Cleanup { ttl_seconds: None },
false,
&mut declined,
&config,
database.clone(),
)
.await
.unwrap();
let mut reader: &[u8] = &[];
run_nonce_command(
NonceCommand::Cleanup {
ttl_seconds: Some(60),
},
true,
&mut reader,
&config,
database,
)
.await
.unwrap();
}
}