Skip to content

Commit a9533ee

Browse files
committed
category tests and other minor testing changes
1 parent 2abda34 commit a9533ee

27 files changed

Lines changed: 570 additions & 128 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dtos/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ services = { path = "../services" }
99
serde = { workspace = true }
1010
sea-orm = { workspace = true }
1111
argon2 = { workspace = true }
12+
anyhow = { workspace = true }
1213
regex = "1.12.3"
1314

1415
[lints]

dtos/src/category/post.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use entity::category;
2-
use serde::Deserialize;
2+
use serde::{Deserialize, Serialize};
33

4-
#[derive(Debug, Clone, Deserialize)]
4+
#[derive(Debug, Clone, Serialize, Deserialize)]
55
pub struct CategoryPostDto {
66
pub name: String,
77
}

dtos/src/email_string.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! variant of <https://stackoverflow.com/questions/74482350/adding-length-limit-when-deserializing-a-string-a-vec-or-an-array>
22
3+
use anyhow::anyhow;
34
use regex::Regex;
45
use serde::{de, ser};
56
use std::{ops::Deref, sync::LazyLock};
@@ -15,6 +16,18 @@ impl Deref for EmailString {
1516
}
1617
}
1718

19+
impl TryFrom<String> for EmailString {
20+
type Error = anyhow::Error;
21+
22+
fn try_from(value: String) -> Result<Self, Self::Error> {
23+
if EMAIL_RX.is_match(&value) {
24+
Ok(EmailString(value))
25+
} else {
26+
Err(anyhow!("The string is not an email string"))
27+
}
28+
}
29+
}
30+
1831
impl From<EmailString> for String {
1932
fn from(v: EmailString) -> Self {
2033
v.0
@@ -30,13 +43,8 @@ impl<'de> de::Deserialize<'de> for EmailString {
3043
where
3144
D: de::Deserializer<'de>,
3245
{
33-
<String as de::Deserialize>::deserialize(deserializer).and_then(|inner| {
34-
if EMAIL_RX.is_match(&inner) {
35-
Ok(EmailString(inner))
36-
} else {
37-
Err(de::Error::custom("The string is not an email string"))
38-
}
39-
})
46+
<String as de::Deserialize>::deserialize(deserializer)
47+
.and_then(|inner| Self::try_from(inner).map_err(de::Error::custom))
4048
}
4149
}
4250

src/constants/fairing.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use rocket::{
55
fairing::{self, Fairing, Info, Kind},
66
};
77

8+
use crate::constants::AdminEmail;
9+
810
use super::{ADMIN_EMAIL, JWT_SECRET};
911

1012
pub struct LazyProcFairing;
@@ -25,6 +27,8 @@ impl Fairing for LazyProcFairing {
2527
};
2628
let _ = LazyLock::<_>::force(&ADMIN_EMAIL);
2729

30+
let r = r.manage(AdminEmail(ADMIN_EMAIL.clone()));
31+
2832
Ok(r)
2933
}
3034
}

src/constants/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ pub static JWT_SECRET: LazyLock<Option<&str>> =
2727
}
2828
});
2929

30-
pub static ADMIN_EMAIL: LazyLock<Option<&str>> =
30+
pub static ADMIN_EMAIL: LazyLock<Option<String>> =
3131
LazyLock::new(|| match dotenvy::var("ADMIN_EMAIL") {
3232
Ok(var) => {
3333
if dtos::email_string::EMAIL_RX.is_match(&var) {
34-
Some(var.leak())
34+
Some(var)
3535
} else {
3636
log::warn!("ADMIN_EMAIL doesn't match the email regex");
3737
None
@@ -42,3 +42,7 @@ pub static ADMIN_EMAIL: LazyLock<Option<&str>> =
4242
None
4343
}
4444
});
45+
46+
/// The type in which rocket manages the admin email
47+
#[allow(dead_code)]
48+
pub struct AdminEmail(pub Option<String>);

src/database.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ impl Fairing for DatabaseFairing {
5959
}
6060

6161
async fn register_admin(db: &DbConn) -> Result<(), DbErr> {
62-
let Some(email) = *ADMIN_EMAIL else {
62+
let Some(email) = &*ADMIN_EMAIL else {
6363
return Ok(());
6464
};
6565
let Some(user) = User::find_by_email(email).one(db).await? else {

src/jwt.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ impl<'a> FromRequest<'a> for JwtClaims {
178178
};
179179
let jwt = jwt.to_string();
180180

181+
let jwt = jwt.clone().split_once(";").map_or(jwt, |s| s.0.to_string());
182+
181183
let Some((_key, jwt)) = jwt.split_once("=") else {
182184
jar.remove(JWT_KEY);
183185
return Outcome::Error((

src/responder/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ pub enum Responder {
4040
Conflict(String),
4141
#[response(status = 404)]
4242
NotFound(String),
43-
#[response(status = 403)]
44-
BadRequest(String),
4543
#[response(status = 401)]
4644
Unauhorized(String),
45+
#[response(status = 400)]
46+
BadRequest(String),
4747
}
4848

4949
impl Display for Responder {

src/routes/categories/get.rs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,126 @@ pub async fn all(db: &State<DbConn>) -> Result<Json<Vec<CategoryGetDto>>, Respon
2626

2727
Ok(Json(categories))
2828
}
29+
30+
#[cfg(test)]
31+
mod tests {
32+
use anyhow::anyhow;
33+
use dtos::CategoryGetDto;
34+
use rocket::local::asynchronous::Client;
35+
36+
use super::*;
37+
use crate::testing::{self, category};
38+
39+
#[tokio::test]
40+
async fn categories_get_all_tracked() -> anyhow::Result<()> {
41+
let db = category::db().await?;
42+
let r = category::rocket(db).await?;
43+
44+
let client = Client::tracked(r).await?;
45+
46+
let req = client.get("/api/categories/");
47+
let res = req.dispatch().await;
48+
let categories = res
49+
.into_json::<Vec<CategoryGetDto>>()
50+
.await
51+
.ok_or(anyhow!("Couldn't parse into json"))?;
52+
53+
// testing::category::seed_db has 3 non-deleted rows
54+
assert_eq!(categories.len(), 3);
55+
56+
Ok(())
57+
}
58+
59+
#[tokio::test]
60+
async fn categories_get_all_func() -> anyhow::Result<()> {
61+
let db = category::db().await?;
62+
let db = State::from(&db);
63+
64+
let categories = all(db).await?;
65+
66+
// testing::category::seed_db has 3 non-deleted rows
67+
assert_eq!(categories.len(), 3);
68+
69+
Ok(())
70+
}
71+
72+
#[tokio::test]
73+
async fn categories_get_by_id_exists_tracked() -> anyhow::Result<()> {
74+
let db = category::db().await?;
75+
let r = category::rocket(db).await?;
76+
77+
let client = Client::tracked(r).await?;
78+
79+
let req = client.get("/api/categories/1");
80+
let res = req.dispatch().await;
81+
let category = res.into_json::<CategoryGetDto>().await;
82+
83+
assert!(category.is_some(), "The first category is not deleted");
84+
85+
Ok(())
86+
}
87+
88+
#[tokio::test]
89+
async fn categories_get_by_id_deleted_tracked() -> anyhow::Result<()> {
90+
let db = category::db().await?;
91+
let r = category::rocket(db).await?;
92+
93+
let client = Client::tracked(r).await?;
94+
95+
let req = client.get("/api/categories/5");
96+
let res = req.dispatch().await;
97+
98+
assert_eq!(res.status().code, 404, "The fifth category is deleted");
99+
100+
Ok(())
101+
}
102+
103+
#[tokio::test]
104+
async fn categories_get_by_id_not_existant_tracked() -> anyhow::Result<()> {
105+
let db = category::db().await?;
106+
let r = category::rocket(db).await?;
107+
108+
let client = Client::tracked(r).await?;
109+
110+
let req = client.get("/api/categories/1000");
111+
let res = req.dispatch().await;
112+
113+
assert_eq!(res.status().code, 404, "The 1000th category does not exist");
114+
115+
Ok(())
116+
}
117+
118+
#[tokio::test]
119+
async fn categories_get_by_id_exists_func() -> anyhow::Result<()> {
120+
let db = category::db().await?;
121+
let db = State::from(&db);
122+
123+
let res = one(1, db).await;
124+
assert!(res.is_ok(), "The first category is not deleted");
125+
126+
Ok(())
127+
}
128+
129+
#[tokio::test]
130+
async fn categories_get_by_id_deleted_func() -> anyhow::Result<()> {
131+
let db = testing::db().await?;
132+
let db = State::from(&db);
133+
testing::category::seed_db(db).await?;
134+
135+
let res = one(5, db).await;
136+
assert!(res.is_err(), "The fifth category is deleted");
137+
138+
Ok(())
139+
}
140+
141+
#[tokio::test]
142+
async fn categories_get_by_id_not_existant_tracked_func() -> anyhow::Result<()> {
143+
let db = category::db().await?;
144+
let db = State::from(&db);
145+
146+
let res = one(1000, db).await;
147+
assert!(res.is_err(), "The 1000th category does not exist");
148+
149+
Ok(())
150+
}
151+
}

0 commit comments

Comments
 (0)