web: add unit tests to utils functions

axum-login-upgrade
Manos Pitsidianakis 2023-05-09 15:26:02 +03:00
parent f8cc3852bb
commit 98b1aa6e06
Signed by: Manos Pitsidianakis
GPG Key ID: 7729C7707F7E09D0
2 changed files with 163 additions and 4 deletions

View File

@ -187,6 +187,46 @@ impl minijinja::value::StructObject for MailingList {
}
}
/// Return a vector of weeks, with each week being a vector of 7 days and
/// corresponding sum of posts per day.
///
///
/// # Example
///
/// ```rust
/// # use mailpot_web::minijinja_utils::calendarize;
/// # use minijinja::Environment;
/// # use minijinja::value::Value;
/// # use std::collections::HashMap;
///
/// let mut env = Environment::new();
/// env.add_function("calendarize", calendarize);
///
/// let month = "2001-09";
/// let mut hist = [0usize; 31];
/// hist[15] = 5;
/// hist[1] = 1;
/// hist[0] = 512;
/// hist[30] = 30;
/// assert_eq!(
/// &env.render_str(
/// "{% set c=calendarize(month, hists) %}Month: {{ c.month }} Month Name: {{ \
/// c.month_name }} Month Int: {{ c.month_int }} Year: {{ c.year }} Sum: {{ c.sum }} {% \
/// for week in c.weeks %}{% for day in week %}{% set num = c.hist[day-1] %}({{ day }}, \
/// {{ num }}){% endfor %}{% endfor %}",
/// minijinja::context! {
/// month,
/// hists => vec![(month.to_string(), hist)].into_iter().collect::<HashMap<String, [usize;
/// 31]>>(),
/// }
/// )
/// .unwrap(),
/// "Month: 2001-09 Month Name: September Month Int: 9 Year: 2001 Sum: 548 (0, 30)(0, 30)(0, \
/// 30)(0, 30)(0, 30)(1, 512)(2, 1)(3, 0)(4, 0)(5, 0)(6, 0)(7, 0)(8, 0)(9, 0)(10, 0)(11, \
/// 0)(12, 0)(13, 0)(14, 0)(15, 0)(16, 5)(17, 0)(18, 0)(19, 0)(20, 0)(21, 0)(22, 0)(23, \
/// 0)(24, 0)(25, 0)(26, 0)(27, 0)(28, 0)(29, 0)(30, 0)"
/// );
/// ```
pub fn calendarize(
_state: &minijinja::State,
args: Value,
@ -431,7 +471,7 @@ pub fn strip_carets(_state: &minijinja::State, arg: Value) -> std::result::Resul
/// `urlize` filter for [`minijinja`].
///
/// Returns a safe string for use in <a> href= attributes.
/// Returns a safe string for use in `<a href=..` attributes.
///
/// # Examples
///

View File

@ -19,6 +19,18 @@
use super::*;
/// Navigation crumbs, e.g.: Home > Page > Subpage
///
/// # Example
///
/// ```rust
/// # use mailpot_web::utils::Crumb;
/// let crumbs = vec![Crumb {
/// label: "Home".into(),
/// url: "/".into(),
/// }];
/// println!("{} {}", crumbs[0].label, crumbs[0].url);
/// ```
#[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize, serde::Serialize)]
pub struct Crumb {
pub label: Cow<'static, str>,
@ -26,7 +38,10 @@ pub struct Crumb {
pub url: Cow<'static, str>,
}
#[derive(Debug, Default, Hash, Copy, Clone, serde::Deserialize, serde::Serialize)]
/// Message urgency level or info.
#[derive(
Debug, Default, Hash, Copy, Clone, serde::Deserialize, serde::Serialize, PartialEq, Eq,
)]
pub enum Level {
Success,
#[default]
@ -35,7 +50,8 @@ pub enum Level {
Error,
}
#[derive(Debug, Hash, Clone, serde::Deserialize, serde::Serialize)]
/// UI message notifications.
#[derive(Debug, Hash, Clone, serde::Deserialize, serde::Serialize, PartialEq, Eq)]
pub struct Message {
pub message: Cow<'static, str>,
#[serde(default)]
@ -46,12 +62,62 @@ impl Message {
const MESSAGE_KEY: &str = "session-message";
}
/// Drain messages from session.
///
/// # Example
///
/// ```rust
/// # use mailpot_web::utils::{Message, Level, SessionMessages};
/// struct Session(Vec<Message>);
///
/// impl SessionMessages for Session {
/// type Error = std::convert::Infallible;
/// fn drain_messages(&mut self) -> Vec<Message> {
/// std::mem::take(&mut self.0)
/// }
///
/// fn add_message(&mut self, m: Message) -> Result<(), std::convert::Infallible> {
/// self.0.push(m);
/// Ok(())
/// }
/// }
/// let mut s = Session(vec![]);
/// s.add_message(Message {
/// message: "foo".into(),
/// level: Level::default(),
/// })
/// .unwrap();
/// s.add_message(Message {
/// message: "bar".into(),
/// level: Level::Error,
/// })
/// .unwrap();
/// assert_eq!(
/// s.drain_messages().as_slice(),
/// [
/// Message {
/// message: "foo".into(),
/// level: Level::default(),
/// },
/// Message {
/// message: "bar".into(),
/// level: Level::Error
/// }
/// ]
/// .as_slice()
/// );
/// assert!(s.0.is_empty());
/// ```
pub trait SessionMessages {
type Error;
fn drain_messages(&mut self) -> Vec<Message>;
fn add_message(&mut self, _: Message) -> Result<(), ResponseError>;
fn add_message(&mut self, _: Message) -> Result<(), Self::Error>;
}
impl SessionMessages for WritableSession {
type Error = ResponseError;
fn drain_messages(&mut self) -> Vec<Message> {
let ret = self.get(Message::MESSAGE_KEY).unwrap_or_default();
self.remove(Message::MESSAGE_KEY);
@ -67,6 +133,19 @@ impl SessionMessages for WritableSession {
}
}
/// Deserialize a string integer into `i64`, because POST parameters are
/// strings.
///
/// ```
/// # use mailpot_web::utils::IntPOST;
/// # use mailpot::serde_json::{self, json};
/// assert_eq!(
/// IntPOST(5),
/// serde_json::from_str::<IntPOST>("\"5\"").unwrap()
/// );
/// assert_eq!(IntPOST(5), serde_json::from_str::<IntPOST>("5").unwrap());
/// assert_eq!(&json! { IntPOST(5) }.to_string(), "5");
/// ```
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Hash)]
#[repr(transparent)]
pub struct IntPOST(pub i64);
@ -101,6 +180,13 @@ impl<'de> serde::Deserialize<'de> for IntPOST {
Ok(IntPOST(int))
}
fn visit_u64<E>(self, int: u64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(IntPOST(int.try_into().unwrap()))
}
fn visit_str<E>(self, int: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
@ -113,6 +199,22 @@ impl<'de> serde::Deserialize<'de> for IntPOST {
}
}
/// Deserialize a string integer into `bool`, because POST parameters are
/// strings.
///
/// ```
/// # use mailpot_web::utils::BoolPOST;
/// # use mailpot::serde_json::{self, json};
/// assert_eq!(
/// BoolPOST(true),
/// serde_json::from_str::<BoolPOST>("true").unwrap()
/// );
/// assert_eq!(
/// BoolPOST(true),
/// serde_json::from_str::<BoolPOST>("\"true\"").unwrap()
/// );
/// assert_eq!(&json! { BoolPOST(false) }.to_string(), "false");
/// ```
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, PartialOrd, Hash)]
#[repr(transparent)]
pub struct BoolPOST(pub bool);
@ -159,6 +261,23 @@ impl<'de> serde::Deserialize<'de> for BoolPOST {
}
}
/// ```
/// use axum::response::Redirect;
/// use mailpot_web::Next;
///
/// let next = Next {
/// next: Some("foo".to_string()),
/// };
/// assert_eq!(
/// format!("{:?}", Redirect::to("foo")),
/// format!("{:?}", next.or_else(|| "bar".to_string()))
/// );
/// let next = Next { next: None };
/// assert_eq!(
/// format!("{:?}", Redirect::to("bar")),
/// format!("{:?}", next.or_else(|| "bar".to_string()))
/// );
/// ```
#[derive(Debug, Clone, serde::Deserialize)]
pub struct Next {
#[serde(default, deserialize_with = "empty_string_as_none")]