1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-08-09 04:22:45 +03:00

WIP: generate sample template contexts for testing

This commit is contained in:
Quentin Gliech
2021-09-23 23:51:50 +02:00
parent ff41ae5762
commit ddf155b901
4 changed files with 192 additions and 10 deletions

View File

@@ -69,6 +69,17 @@ impl SessionInfo {
self.active = false;
Ok(self)
}
pub(crate) fn samples() -> Vec<Self> {
vec![SessionInfo {
id: 1,
user_id: 2,
username: "john".to_string(),
active: true,
created_at: Utc::now(),
last_authd_at: Some(Utc::now()),
}]
}
}
#[derive(Debug, Error)]

View File

@@ -15,7 +15,7 @@
//! Contexts used in templates
use oauth2_types::errors::OAuth2Error;
use serde::Serialize;
use serde::{ser::SerializeStruct, Serialize};
use url::Url;
use crate::{errors::ErroredForm, filters::CsrfToken, storage::SessionInfo};
@@ -51,15 +51,11 @@ pub trait TemplateContext {
inner: self,
}
}
}
impl TemplateContext for EmptyContext {}
impl TemplateContext for IndexContext {}
impl TemplateContext for LoginContext {}
impl<T> TemplateContext for FormPostContext<T> {}
impl<T> TemplateContext for WithSession<T> {}
impl<T> TemplateContext for WithOptionalSession<T> {}
impl<T> TemplateContext for WithCsrf<T> {}
fn sample() -> Vec<Self>
where
Self: Sized;
}
/// Context with a CSRF token in it
#[derive(Serialize)]
@@ -70,6 +66,21 @@ pub struct WithCsrf<T> {
inner: T,
}
impl<T: TemplateContext> TemplateContext for WithCsrf<T> {
fn sample() -> Vec<Self>
where
Self: Sized,
{
T::sample()
.into_iter()
.map(|inner| WithCsrf {
csrf_token: "fake_csrf_token".into(),
inner,
})
.collect()
}
}
/// Context with a user session in it
#[derive(Serialize)]
pub struct WithSession<T> {
@@ -79,6 +90,23 @@ pub struct WithSession<T> {
inner: T,
}
impl<T: TemplateContext> TemplateContext for WithSession<T> {
fn sample() -> Vec<Self>
where
Self: Sized,
{
SessionInfo::samples()
.into_iter()
.flat_map(|session| {
T::sample().into_iter().map(move |inner| WithSession {
current_session: session.clone(),
inner,
})
})
.collect()
}
}
/// Context with an optional user session in it
#[derive(Serialize)]
pub struct WithOptionalSession<T> {
@@ -88,10 +116,52 @@ pub struct WithOptionalSession<T> {
inner: T,
}
impl<T: TemplateContext> TemplateContext for WithOptionalSession<T> {
fn sample() -> Vec<Self>
where
Self: Sized,
{
SessionInfo::samples()
.into_iter()
.map(Some) // Wrap all samples in an Option
.chain(std::iter::once(None)) // Add the "None" option
.flat_map(|session| {
T::sample()
.into_iter()
.map(move |inner| WithOptionalSession {
current_session: session.clone(),
inner,
})
})
.collect()
}
}
/// An empty context used for composition
#[derive(Serialize)]
pub struct EmptyContext;
impl Serialize for EmptyContext {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut s = serializer.serialize_struct("EmptyContext", 0)?;
// FIXME: for some reason, serde seems to not like struct flattening with empty
// stuff
s.serialize_field("__UNUSED", &())?;
s.end()
}
}
impl TemplateContext for EmptyContext {
fn sample() -> Vec<Self>
where
Self: Sized,
{
vec![EmptyContext]
}
}
/// Context used by the `index.html` template
#[derive(Serialize)]
pub struct IndexContext {
@@ -105,6 +175,19 @@ impl IndexContext {
}
}
impl TemplateContext for IndexContext {
fn sample() -> Vec<Self>
where
Self: Sized,
{
vec![Self {
discovery_url: "https://example.com/.well-known/openid-configuration"
.parse()
.unwrap(),
}]
}
}
#[derive(Serialize, Debug, Clone, Copy, Hash, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum LoginFormField {
@@ -118,6 +201,18 @@ pub struct LoginContext {
form: ErroredForm<LoginFormField>,
}
impl TemplateContext for LoginContext {
fn sample() -> Vec<Self>
where
Self: Sized,
{
// TODO: samples with errors
vec![LoginContext {
form: ErroredForm::default(),
}]
}
}
impl LoginContext {
#[must_use]
pub fn with_form_error(form: ErroredForm<LoginFormField>) -> Self {
@@ -140,6 +235,22 @@ pub struct FormPostContext<T> {
params: T,
}
impl<T: TemplateContext> TemplateContext for FormPostContext<T> {
fn sample() -> Vec<Self>
where
Self: Sized,
{
let sample_params = T::sample();
sample_params
.into_iter()
.map(|params| FormPostContext {
redirect_uri: "https://example.com/callback".parse().unwrap(),
params,
})
.collect()
}
}
impl<T> FormPostContext<T> {
pub fn new(redirect_uri: Url, params: T) -> Self {
Self {
@@ -157,6 +268,22 @@ pub struct ErrorContext {
details: Option<String>,
}
impl TemplateContext for ErrorContext {
fn sample() -> Vec<Self>
where
Self: Sized,
{
vec![
Self::new()
.with_code("sample_error")
.with_description("A fancy description".into())
.with_details("Something happened".into()),
Self::new().with_code("another_error"),
Self::new(),
]
}
}
impl ErrorContext {
#[must_use]
pub fn new() -> Self {

View File

@@ -32,6 +32,10 @@ macro_rules! register_templates {
extra = { $( $extra_template:expr ),* };
)?
$(
generics = { $( $generic:ident = $sample:ty ),* };
)?
$(
// Match any attribute on the function, such as #[doc], #[allow(dead_code)], etc.
$( #[ $attr:meta ] )*
@@ -72,6 +76,34 @@ macro_rules! register_templates {
.map_err(|source| TemplateError::Render { template: $template, source })
}
)*
pub fn check_render(&self) -> Result<(), TemplateError> {
self.check_render_inner $( ::< $( $sample ),+ > )? ()
}
fn check_render_inner
$( < $( $generic ),+ > )?
(&self) -> Result<(), TemplateError>
$( where
$( $generic : TemplateContext + Serialize, )+
)?
{
$(
{
let samples: Vec< $param > = TemplateContext::sample();
let name = $template;
for sample in samples {
::tracing::info!(name, "Rendering template");
self. $name (&sample)?;
}
}
)*
Ok(())
}
}
};
}

View File

@@ -167,6 +167,7 @@ impl Reject for TemplateError {}
register_templates! {
extra = { "base.html" };
generics = { T = EmptyContext };
/// Render the login page
pub fn render_login(WithCsrf<LoginContext>) { "login.html" }
@@ -186,3 +187,14 @@ register_templates! {
/// Render the HTML error page
pub fn render_error(ErrorContext) { "error.html" }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn check_all_templates() {
let templates = Templates::load(None, true).unwrap();
templates.check_render().unwrap();
}
}