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;
|
self.active = false;
|
||||||
Ok(self)
|
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)]
|
#[derive(Debug, Error)]
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
//! Contexts used in templates
|
//! Contexts used in templates
|
||||||
|
|
||||||
use oauth2_types::errors::OAuth2Error;
|
use oauth2_types::errors::OAuth2Error;
|
||||||
use serde::Serialize;
|
use serde::{ser::SerializeStruct, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{errors::ErroredForm, filters::CsrfToken, storage::SessionInfo};
|
use crate::{errors::ErroredForm, filters::CsrfToken, storage::SessionInfo};
|
||||||
@@ -51,15 +51,11 @@ pub trait TemplateContext {
|
|||||||
inner: self,
|
inner: self,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl TemplateContext for EmptyContext {}
|
fn sample() -> Vec<Self>
|
||||||
impl TemplateContext for IndexContext {}
|
where
|
||||||
impl TemplateContext for LoginContext {}
|
Self: Sized;
|
||||||
impl<T> TemplateContext for FormPostContext<T> {}
|
}
|
||||||
impl<T> TemplateContext for WithSession<T> {}
|
|
||||||
impl<T> TemplateContext for WithOptionalSession<T> {}
|
|
||||||
impl<T> TemplateContext for WithCsrf<T> {}
|
|
||||||
|
|
||||||
/// Context with a CSRF token in it
|
/// Context with a CSRF token in it
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
@@ -70,6 +66,21 @@ pub struct WithCsrf<T> {
|
|||||||
inner: 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
|
/// Context with a user session in it
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct WithSession<T> {
|
pub struct WithSession<T> {
|
||||||
@@ -79,6 +90,23 @@ pub struct WithSession<T> {
|
|||||||
inner: 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
|
/// Context with an optional user session in it
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct WithOptionalSession<T> {
|
pub struct WithOptionalSession<T> {
|
||||||
@@ -88,10 +116,52 @@ pub struct WithOptionalSession<T> {
|
|||||||
inner: 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
|
/// An empty context used for composition
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct EmptyContext;
|
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
|
/// Context used by the `index.html` template
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct IndexContext {
|
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)]
|
#[derive(Serialize, Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub enum LoginFormField {
|
pub enum LoginFormField {
|
||||||
@@ -118,6 +201,18 @@ pub struct LoginContext {
|
|||||||
form: ErroredForm<LoginFormField>,
|
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 {
|
impl LoginContext {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn with_form_error(form: ErroredForm<LoginFormField>) -> Self {
|
pub fn with_form_error(form: ErroredForm<LoginFormField>) -> Self {
|
||||||
@@ -140,6 +235,22 @@ pub struct FormPostContext<T> {
|
|||||||
params: 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> {
|
impl<T> FormPostContext<T> {
|
||||||
pub fn new(redirect_uri: Url, params: T) -> Self {
|
pub fn new(redirect_uri: Url, params: T) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -157,6 +268,22 @@ pub struct ErrorContext {
|
|||||||
details: Option<String>,
|
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 {
|
impl ErrorContext {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
@@ -32,6 +32,10 @@ macro_rules! register_templates {
|
|||||||
extra = { $( $extra_template:expr ),* };
|
extra = { $( $extra_template:expr ),* };
|
||||||
)?
|
)?
|
||||||
|
|
||||||
|
$(
|
||||||
|
generics = { $( $generic:ident = $sample:ty ),* };
|
||||||
|
)?
|
||||||
|
|
||||||
$(
|
$(
|
||||||
// Match any attribute on the function, such as #[doc], #[allow(dead_code)], etc.
|
// Match any attribute on the function, such as #[doc], #[allow(dead_code)], etc.
|
||||||
$( #[ $attr:meta ] )*
|
$( #[ $attr:meta ] )*
|
||||||
@@ -72,6 +76,34 @@ macro_rules! register_templates {
|
|||||||
.map_err(|source| TemplateError::Render { template: $template, source })
|
.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! {
|
register_templates! {
|
||||||
extra = { "base.html" };
|
extra = { "base.html" };
|
||||||
|
generics = { T = EmptyContext };
|
||||||
|
|
||||||
/// Render the login page
|
/// Render the login page
|
||||||
pub fn render_login(WithCsrf<LoginContext>) { "login.html" }
|
pub fn render_login(WithCsrf<LoginContext>) { "login.html" }
|
||||||
@@ -186,3 +187,14 @@ register_templates! {
|
|||||||
/// Render the HTML error page
|
/// Render the HTML error page
|
||||||
pub fn render_error(ErrorContext) { "error.html" }
|
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