Skip to content

Commit 86ba783

Browse files
chore: add some basic integration tests
Signed-off-by: Henry Gressmann <[email protected]>
1 parent e2f6769 commit 86ba783

File tree

10 files changed

+141
-24
lines changed

10 files changed

+141
-24
lines changed

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.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ maxminddb={version="0.24", optional=true}
7070
[dev-dependencies]
7171
figment={version="*", features=["test"]}
7272
poem={version="*", features=["test"]}
73+
cookie={version="*"}
7374

7475
[features]
7576
default=["geoip"]

src/web/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
mod routes;
2-
mod session;
3-
mod webext;
1+
pub mod routes;
2+
pub mod session;
3+
pub mod webext;
44

55
use crate::app::models::Event;
66
use crate::app::Liwan;

src/web/routes/auth.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,26 @@ use poem::web::{cookie::CookieJar, Data};
1212
use poem::{Endpoint, EndpointExt};
1313
use poem_openapi::payload::{Json, Response};
1414
use poem_openapi::{Object, OpenApi};
15-
use serde::Deserialize;
15+
use serde::{Deserialize, Serialize};
1616
use tower::limit::RateLimitLayer;
1717

18-
#[derive(Deserialize, Object)]
19-
struct LoginRequest {
20-
username: String,
21-
password: String,
18+
#[derive(Serialize, Object)]
19+
pub struct LoginRequest {
20+
pub username: String,
21+
pub password: String,
2222
}
2323

24-
#[derive(Deserialize, Object)]
25-
struct SetupRequest {
26-
token: String,
27-
username: String,
28-
password: String,
24+
#[derive(Deserialize, Serialize, Object)]
25+
pub struct SetupRequest {
26+
pub token: String,
27+
pub username: String,
28+
pub password: String,
2929
}
3030

31-
#[derive(Object)]
32-
struct MeResponse {
33-
username: String,
34-
role: UserRole,
31+
#[derive(Object, Serialize)]
32+
pub struct MeResponse {
33+
pub username: String,
34+
pub role: UserRole,
3535
}
3636

3737
fn login_rate_limit(ep: impl Endpoint + 'static) -> impl Endpoint {

src/web/routes/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
mod admin;
2-
mod auth;
3-
mod dashboard;
4-
mod event;
1+
pub mod admin;
2+
pub mod auth;
3+
pub mod dashboard;
4+
pub mod event;
55

66
pub use admin::AdminAPI;
77
pub use auth::AuthApi;

tests/admin.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

tests/auth.rs

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,76 @@
1+
use eyre::Result;
2+
use liwan::app::models::UserRole;
3+
use poem::http::{header, status::StatusCode};
4+
use serde_json::json;
5+
16
mod common;
27

38
#[tokio::test]
4-
async fn it_adds_two() {
9+
async fn test_login() -> Result<()> {
10+
let app = common::app();
11+
let (tx, _rx) = common::events();
12+
let router = common::router(app.clone(), tx);
13+
let client = poem::test::TestClient::new(router);
14+
15+
app.users.create("test", "test", UserRole::User, &[])?;
16+
17+
// login
18+
let login = &json!({ "username": "test", "password": "test" });
19+
let res = client.post("/api/dashboard/auth/login").body_json(login).send().await;
20+
21+
res.assert_status_is_ok();
22+
let cookies = common::cookies(&res);
23+
24+
// user info
25+
let res = client.get("/api/dashboard/auth/me").header(header::COOKIE, common::cookie_header(&cookies)).send().await;
26+
res.assert_status_is_ok();
27+
res.assert_json(json!({ "username": "test", "role": "user" })).await;
28+
29+
// logout
30+
let res =
31+
client.post("/api/dashboard/auth/logout").header(header::COOKIE, common::cookie_header(&cookies)).send().await;
32+
res.assert_status_is_ok();
33+
34+
// test that the user is logged out
35+
let res = client.get("/api/dashboard/auth/me").header(header::COOKIE, common::cookie_header(&cookies)).send().await;
36+
37+
res.assert_status(StatusCode::UNAUTHORIZED);
38+
Ok(())
39+
}
40+
41+
#[tokio::test]
42+
async fn test_setup() -> Result<()> {
543
let app = common::app();
644
let (tx, _rx) = common::events();
7-
let router = common::router(app, tx);
45+
let router = common::router(app.clone(), tx);
846
let client = poem::test::TestClient::new(router);
947

10-
client.get("/").send().await;
48+
let token = app.onboarding.token().unwrap().expect("onboarding should exist");
49+
50+
// Invalid token should return 401
51+
let setup = &json!({ "token": "invalid_token", "username": "admin2", "password": "admin2" });
52+
let res = client.post("/api/dashboard/auth/setup").body_json(setup).send().await;
53+
res.assert_status(StatusCode::UNAUTHORIZED);
54+
55+
// Valid token should return 200
56+
let setup = &json!({ "token": token, "username": "admin", "password": "admin" });
57+
let res = client.post("/api/dashboard/auth/setup").body_json(setup).send().await;
58+
res.assert_status_is_ok();
59+
60+
// Check that the user is created
61+
let login = &json!({ "username": "admin", "password": "admin" });
62+
let res = client.post("/api/dashboard/auth/login").body_json(login).send().await;
63+
res.assert_status_is_ok();
64+
65+
// Check that the onboarding is cleared
66+
assert_eq!(app.onboarding.token().unwrap(), None, "onboarding should be cleared");
67+
let setup = &json!({ "token": token, "username": "admin", "password": "admin" });
68+
let res = client.post("/api/dashboard/auth/setup").body_json(setup).send().await;
69+
res.assert_status(StatusCode::UNAUTHORIZED);
70+
71+
let setup = &json!({ "token": token, "username": "admin2", "password": "admin2" });
72+
let res = client.post("/api/dashboard/auth/setup").body_json(setup).send().await;
73+
res.assert_status(StatusCode::UNAUTHORIZED);
74+
75+
Ok(())
1176
}

tests/common/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
#![allow(unused)]
2+
3+
use cookie::Cookie;
14
use liwan::{
25
app::{models::Event, Liwan},
36
config::Config,
@@ -12,3 +15,17 @@ pub fn events() -> (crossbeam::channel::Sender<Event>, crossbeam::channel::Recei
1215
}
1316

1417
pub use liwan::web::create_router as router;
18+
use poem::test::TestResponse;
19+
20+
pub fn cookies(res: &TestResponse) -> Vec<cookie::Cookie<'static>> {
21+
res.0
22+
.headers()
23+
.get_all("Set-Cookie")
24+
.iter()
25+
.map(|v| Cookie::parse(v.to_str().unwrap().to_owned()).unwrap())
26+
.collect::<Vec<_>>()
27+
}
28+
29+
pub fn cookie_header(cookies: &[Cookie]) -> String {
30+
cookies.iter().map(|cookie| cookie.to_string()).collect::<Vec<_>>().join("; ")
31+
}

tests/dashboard.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

tests/event.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
mod common;
2+
use eyre::Result;
3+
use liwan::app::models::Entity;
4+
use poem::http::{header, status::StatusCode};
5+
use serde_json::json;
6+
7+
#[tokio::test]
8+
async fn test_event() -> Result<()> {
9+
let app = common::app();
10+
let (tx, _rx) = common::events();
11+
let router = common::router(app.clone(), tx);
12+
let client = poem::test::TestClient::new(router);
13+
app.entities.create(&Entity { display_name: "Entity 1".to_string(), id: "entity-1".to_string() }, &[])?;
14+
15+
let event = &json!({
16+
"entity_id": "entity-1",
17+
"name": "pageview",
18+
"url": "https://example.com/"
19+
});
20+
21+
// Require User-Agent
22+
let res = client.post("/api/event").body_json(event).send().await;
23+
res.assert_status(StatusCode::BAD_REQUEST);
24+
25+
// Create event
26+
let res = client.post("/api/event").header(header::USER_AGENT, "test").body_json(event).send().await;
27+
res.assert_status_is_ok();
28+
29+
// TODO: hook up the event handler and test that the event is processed
30+
Ok(())
31+
}

0 commit comments

Comments
 (0)