diff --git a/src/lib.rs b/src/lib.rs
index 28f9dc9..c07ca09 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -18,10 +18,13 @@ mod util;
mod watches;
// things we want in the crate namespace
+use optional_optional_user::OptionalOptionalUser;
use templates::*;
use users::User;
use watches::{templates::*, ShowKind, Watch};
+use crate::watches::handlers::get_watch;
+
type AuthContext =
axum_login::extractors::AuthContext>;
@@ -46,6 +49,8 @@ pub async fn app(db_pool: sqlx::SqlitePool, session_secret: &[u8]) -> axum::Rout
.route("/login", get(get_login).post(post_login))
.route("/logout", get(get_logout).post(post_logout))
.route("/watches", get(get_watches))
+ .route("/watch", get(get_watch))
+ .route("/watch/:id", get(get_watch))
.route("/search", get(get_search_watch).post(post_search_watch))
.route(
"/add",
@@ -62,3 +67,39 @@ pub async fn app(db_pool: sqlx::SqlitePool, session_secret: &[u8]) -> axum::Rout
.layer(session_layer)
.with_state(db_pool)
}
+
+//-************************************************************************
+// tests for the proc macro for optional user
+//-************************************************************************
+#[cfg(test)]
+mod test {
+ use super::{CreateUserSuccess, MainPage, OptionalOptionalUser, User};
+
+ #[test]
+ fn main_page_has_optional_user() {
+ assert!(MainPage::default().has_optional_user());
+ }
+
+ #[test]
+ fn signup_success_has_no_optional_user() {
+ assert!(!CreateUserSuccess::default().has_optional_user());
+ }
+
+ #[test]
+ fn user_is_not_optional() {
+ #[derive(Default, OptionalOptionalUser)]
+ struct TestThing {
+ user: User,
+ }
+ assert!(!TestThing::default().has_optional_user());
+ }
+
+ #[test]
+ fn user_is_not_user() {
+ #[derive(Default, OptionalOptionalUser)]
+ struct TestThing {
+ user: Option,
+ }
+ assert!(!TestThing::default().has_optional_user());
+ }
+}
diff --git a/src/signup.rs b/src/signup.rs
index 171a643..a2e73f0 100644
--- a/src/signup.rs
+++ b/src/signup.rs
@@ -81,10 +81,9 @@ pub async fn post_create_user(
if password != verify {
return Err(CreateUserErrorKind::PasswordMismatch.into());
}
-
- let password = password.trim();
+ let pwlen = password.graphemes(true).size_hint().1.unwrap_or(0);
let password = password.as_bytes();
- if !(4..=50).contains(&password.len()) {
+ if !(4..=50).contains(&pwlen) {
return Err(CreateUserErrorKind::BadPassword.into());
}
@@ -352,6 +351,35 @@ mod test {
assert_eq!(&expected, body);
}
+ #[tokio::test]
+ async fn multibyte_password_too_short() {
+ let pw = "🤡";
+ // min length is 4
+ assert_eq!(pw.len(), 4);
+
+ let pool = get_db_pool().await;
+ let server = server_with_pool(&pool).await;
+ let form =
+ format!("username=test_user&displayname=Test+User&password={pw}&pw_verify={pw}");
+ let body = massage(&form);
+
+ let resp = server
+ .post("/signup")
+ // failure to sign up is not failure to submit the request
+ .expect_success()
+ .bytes(body)
+ .content_type(FORM_CONTENT_TYPE)
+ .await;
+
+ // no user in db
+ let user = User::try_get("test_user", &pool).await;
+ assert!(user.is_err());
+
+ let body = std::str::from_utf8(resp.bytes()).unwrap();
+ let expected = CreateUserError(CreateUserErrorKind::BadPassword).to_string();
+ assert_eq!(&expected, body);
+ }
+
#[tokio::test]
async fn username_short() {
let pool = get_db_pool().await;
diff --git a/src/templates.rs b/src/templates.rs
index 6fc1b88..664533f 100644
--- a/src/templates.rs
+++ b/src/templates.rs
@@ -1,8 +1,7 @@
use askama::Template;
-use optional_optional_user::OptionalOptionalUser;
use serde::{Deserialize, Serialize};
-use crate::User;
+use crate::{OptionalOptionalUser, User};
#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)]
#[template(path = "signup.html")]
@@ -47,36 +46,3 @@ pub struct LogoutPost;
pub struct MainPage {
pub user: Option,
}
-
-#[cfg(test)]
-mod test {
- use super::*;
-
- #[test]
- fn main_page_has_optional_user() {
- assert!(MainPage::default().has_optional_user());
- }
-
- #[test]
- fn signup_success_has_no_optional_user() {
- assert!(!CreateUserSuccess::default().has_optional_user());
- }
-
- #[test]
- fn user_is_not_optional() {
- #[derive(Default, OptionalOptionalUser)]
- struct TestThing {
- user: User,
- }
- assert!(!TestThing::default().has_optional_user());
- }
-
- #[test]
- fn user_is_not_user() {
- #[derive(Default, OptionalOptionalUser)]
- struct TestThing {
- user: Option,
- }
- assert!(!TestThing::default().has_optional_user());
- }
-}
diff --git a/src/util.rs b/src/util.rs
index 466660c..022c0f8 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -1,5 +1,7 @@
use std::{error::Error, ops::Range};
+use unicode_segmentation::UnicodeSegmentation;
+
pub fn validate_optional_length(
opt: &Option,
len_range: Range,
@@ -7,7 +9,8 @@ pub fn validate_optional_length(
) -> Result
+
+{% endblock %}
diff --git a/templates/get_watch.html b/templates/get_watch.html
new file mode 100644
index 0000000..4af344f
--- /dev/null
+++ b/templates/get_watch.html
@@ -0,0 +1,33 @@
+{% extends "base.html" %}
+{% import "macros.html" as m %}
+
+{% block title %}Welcome to Witch Watch, Bish{% endblock %}
+
+{% block content %}
+
+Whatcha Watchin?
+
+{% match watch %}
+
+{% when Some with (watch) %}
+
+
+ {{watch.title}} -- {% call m::get_or_default(watch.release_date, "when??") %}
+
+
+{% else %}
+
+
+
+{% endmatch %}
+
+
+
+
+
+
+{% endblock %}