You've already forked authentication-service
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:
@@ -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)]
|
||||
|
@@ -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 {
|
||||
|
@@ -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(())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user