Add unit tests

This commit is contained in:
jude 2024-02-24 15:01:54 +00:00
parent dd7e681285
commit 53e13844f9
6 changed files with 177 additions and 0 deletions

View File

@ -5,6 +5,7 @@ use crate::{Context, Error};
/// Configure whether other users can set reminders to your direct messages /// Configure whether other users can set reminders to your direct messages
#[poise::command(slash_command, rename = "dm")] #[poise::command(slash_command, rename = "dm")]
#[cfg(not(test))]
pub async fn allowed_dm(_ctx: Context<'_>) -> Result<(), Error> { pub async fn allowed_dm(_ctx: Context<'_>) -> Result<(), Error> {
Ok(()) Ok(())
} }

View File

@ -33,6 +33,7 @@ impl Recordable for Options {
/// Allow other users to set reminders in your direct messages /// Allow other users to set reminders in your direct messages
#[poise::command(slash_command, rename = "allow", identifying_name = "set_allowed_dm")] #[poise::command(slash_command, rename = "allow", identifying_name = "set_allowed_dm")]
#[cfg(not(test))]
pub async fn set(ctx: Context<'_>) -> Result<(), Error> { pub async fn set(ctx: Context<'_>) -> Result<(), Error> {
(Options {}).run(ctx).await (Options {}).run(ctx).await
} }

View File

@ -31,6 +31,48 @@ impl Recordable for Options {
/// Get the link to the web dashboard /// Get the link to the web dashboard
#[poise::command(slash_command, rename = "dashboard", identifying_name = "dashboard")] #[poise::command(slash_command, rename = "dashboard", identifying_name = "dashboard")]
#[cfg(not(test))]
pub async fn command(ctx: Context<'_>) -> Result<(), Error> { pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
(Options {}).run(ctx).await (Options {}).run(ctx).await
} }
#[cfg(test)]
mod test {
use std::env;
use sqlx::Pool;
use tokio::sync::{broadcast, Mutex};
use crate::{
commands::dashboard::Options,
test::{MockCache, TestContext, TestData},
utils::Recordable,
Data,
};
#[tokio::test]
async fn dashboard_command() {
let (tx, _rx) = broadcast::channel(16);
let database = Pool::connect(&env::var("DATABASE_URL").expect("No database URL provided"))
.await
.unwrap();
let ctx = TestContext {
data: &Data { database, popular_timezones: vec![], _broadcast: tx },
cache: &MockCache {},
test_data: &Mutex::new(TestData { replies: vec![] }),
shard_id: 0,
};
let res = (Options {}).run(ctx).await;
assert!(res.is_ok(), "command OK");
assert_eq!(ctx.test_data.lock().await.replies.len(), 1, "one message sent");
assert!(
ctx.sent_content()
.await
.contains(&String::from("**https://beta.reminder-bot.com/dashboard**")),
"content correct"
);
}
}

View File

@ -1,21 +1,41 @@
#[cfg(not(test))]
pub mod allowed_dm; pub mod allowed_dm;
#[cfg(not(test))]
mod autocomplete; mod autocomplete;
#[cfg(not(test))]
pub mod clock; pub mod clock;
#[cfg(not(test))]
pub mod clock_context_menu; pub mod clock_context_menu;
#[cfg(not(test))]
pub mod command_macro; pub mod command_macro;
pub mod dashboard; pub mod dashboard;
#[cfg(not(test))]
pub mod delete; pub mod delete;
#[cfg(not(test))]
pub mod donate; pub mod donate;
#[cfg(not(test))]
pub mod help; pub mod help;
#[cfg(not(test))]
pub mod info; pub mod info;
#[cfg(not(test))]
pub mod look; pub mod look;
#[cfg(not(test))]
pub mod multiline; pub mod multiline;
#[cfg(not(test))]
pub mod nudge; pub mod nudge;
#[cfg(not(test))]
pub mod offset; pub mod offset;
#[cfg(not(test))]
pub mod pause; pub mod pause;
#[cfg(not(test))]
pub mod remind; pub mod remind;
#[cfg(not(test))]
pub mod settings; pub mod settings;
#[cfg(not(test))]
pub mod timer; pub mod timer;
#[cfg(not(test))]
pub mod timezone; pub mod timezone;
#[cfg(not(test))]
pub mod todo; pub mod todo;
#[cfg(not(test))]
pub mod webhook; pub mod webhook;

View File

@ -4,12 +4,18 @@
extern crate lazy_static; extern crate lazy_static;
mod commands; mod commands;
#[cfg(not(test))]
mod component_models; mod component_models;
mod consts; mod consts;
#[cfg(not(test))]
mod event_handlers; mod event_handlers;
#[cfg(not(test))]
mod hooks; mod hooks;
mod interval_parser; mod interval_parser;
#[cfg(not(test))]
mod models; mod models;
#[cfg(test)]
mod test;
mod time_parser; mod time_parser;
mod utils; mod utils;
@ -33,6 +39,9 @@ use poise::serenity_prelude::{
use sqlx::{MySql, Pool}; use sqlx::{MySql, Pool};
use tokio::sync::{broadcast, broadcast::Sender, RwLock}; use tokio::sync::{broadcast, broadcast::Sender, RwLock};
#[cfg(test)]
use crate::test::TestContext;
#[cfg(not(test))]
use crate::{ use crate::{
commands::{ commands::{
allowed_dm, clock, clock_context_menu::clock_context_menu, command_macro, dashboard, allowed_dm, clock, clock_context_menu::clock_context_menu, command_macro, dashboard,
@ -48,11 +57,18 @@ use crate::{
type Database = MySql; type Database = MySql;
type Error = Box<dyn std::error::Error + Send + Sync>; type Error = Box<dyn std::error::Error + Send + Sync>;
#[cfg(test)]
type Context<'a> = TestContext<'a>;
#[cfg(not(test))]
type Context<'a> = poise::Context<'a, Data, Error>; type Context<'a> = poise::Context<'a, Data, Error>;
type ApplicationContext<'a> = poise::ApplicationContext<'a, Data, Error>; type ApplicationContext<'a> = poise::ApplicationContext<'a, Data, Error>;
pub struct Data { pub struct Data {
database: Pool<Database>, database: Pool<Database>,
#[cfg(not(test))]
recording_macros: RwLock<HashMap<(GuildId, UserId), CommandMacro>>, recording_macros: RwLock<HashMap<(GuildId, UserId), CommandMacro>>,
popular_timezones: Vec<Tz>, popular_timezones: Vec<Tz>,
_broadcast: Sender<()>, _broadcast: Sender<()>,
@ -81,6 +97,7 @@ impl Display for Ended {
impl StdError for Ended {} impl StdError for Ended {}
#[tokio::main(flavor = "multi_thread")] #[tokio::main(flavor = "multi_thread")]
#[cfg(not(test))]
async fn main() -> Result<(), Box<dyn StdError + Send + Sync>> { async fn main() -> Result<(), Box<dyn StdError + Send + Sync>> {
let (tx, mut rx) = broadcast::channel(16); let (tx, mut rx) = broadcast::channel(16);
@ -90,6 +107,7 @@ async fn main() -> Result<(), Box<dyn StdError + Send + Sync>> {
} }
} }
#[cfg(not(test))]
async fn _main(tx: Sender<()>) -> Result<(), Box<dyn StdError + Send + Sync>> { async fn _main(tx: Sender<()>) -> Result<(), Box<dyn StdError + Send + Sync>> {
env_logger::init(); env_logger::init();

95
src/test/mod.rs Normal file
View File

@ -0,0 +1,95 @@
use poise::{
serenity_prelude::{GuildId, UserId},
CreateReply,
};
use serde_json::Value;
use tokio::sync::Mutex;
use crate::{Data, Error};
pub(crate) struct TestData {
pub(crate) replies: Vec<CreateReply>,
}
#[derive(Copy, Clone)]
pub(crate) struct TestContext<'a> {
pub(crate) data: &'a Data,
pub(crate) cache: &'a MockCache,
pub(crate) test_data: &'a Mutex<TestData>,
pub(crate) shard_id: usize,
}
pub(crate) struct MockUser {
pub(crate) id: UserId,
}
pub(crate) struct MockCache {}
impl<'a> TestContext<'a> {
pub async fn say(&self, message: impl Into<String>) -> Result<(), Error> {
self.test_data.lock().await.replies.push(CreateReply::default().content(message));
Ok(())
}
pub async fn send(&self, reply: CreateReply) -> Result<(), Error> {
self.test_data.lock().await.replies.push(reply.clone());
Ok(())
}
pub fn guild_id(&self) -> Option<GuildId> {
Some(GuildId::new(1))
}
pub async fn defer_ephemeral(&self) -> Result<(), Error> {
Ok(())
}
pub fn author(&self) -> MockUser {
MockUser { id: UserId::new(1) }
}
pub fn data(&self) -> &Data {
return &self.data;
}
pub fn serenity_context(&self) -> &Self {
return &self;
}
pub async fn sent_content(&self) -> Vec<String> {
let data = self.test_data.lock().await;
data.replies
.iter()
.map(|r| {
let reply = r.clone();
let content = reply.content.unwrap_or(String::new());
let embed_content = reply
.embeds
.iter()
.map(|e| {
let map = serde_json::to_value(e).unwrap();
let description =
map.get("description").cloned().unwrap_or(Value::String(String::new()));
return format!("{}", description.as_str().unwrap());
})
.collect::<Vec<String>>()
.join("\n");
return if content.is_empty() {
embed_content
} else {
format!("{}\n{}", content, embed_content)
};
})
.collect::<Vec<String>>()
}
}
impl MockCache {
pub fn shard_count(&self) -> usize {
return 1;
}
}