add integration tests

This commit is contained in:
Lennart
2025-12-12 14:12:09 +01:00
parent d3e7ede93c
commit 38b5a3812e
15 changed files with 405 additions and 9 deletions

View File

@@ -2,7 +2,10 @@ use crate::{
SqliteStore, addressbook_store::SqliteAddressbookStore, calendar_store::SqliteCalendarStore,
principal_store::SqlitePrincipalStore,
};
use rustical_store::auth::{AuthenticationProvider, Principal, PrincipalType};
use rustical_store::{
Secret,
auth::{AuthenticationProvider, Principal, PrincipalType},
};
use sqlx::SqlitePool;
use tokio::sync::OnceCell;
@@ -31,6 +34,10 @@ async fn get_test_db() -> SqlitePool {
)
.await
.unwrap();
principal_store
.add_app_token("user", "test".to_string(), "pass".to_string())
.await
.unwrap();
db
})

View File

@@ -0,0 +1,111 @@
use crate::integration_tests::{ResponseExtractString, get_app};
use axum::body::Body;
use axum::extract::Request;
use headers::{Authorization, HeaderMapExt};
use http::{HeaderValue, StatusCode};
use rstest::rstest;
use tower::ServiceExt;
#[rstest]
#[tokio::test]
async fn test_caldav_root(
#[from(get_app)]
#[future]
app: axum::Router,
) {
let app = app.await;
let request_template = || {
Request::builder()
.method("PROPFIND")
.uri("/caldav")
.body(Body::empty())
.unwrap()
};
// Try without authentication
let request = request_template();
let response = app.clone().oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
let body = response.extract_string().await;
insta::assert_snapshot!(body);
// Try with wrong password
let mut request = request_template();
request
.headers_mut()
.typed_insert(Authorization::basic("user", "wrongpass"));
let response = app.clone().oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
let body = response.extract_string().await;
insta::assert_snapshot!(body);
// Try with correct credentials
let mut request = request_template();
request
.headers_mut()
.typed_insert(Authorization::basic("user", "pass"));
let response = app.oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::MULTI_STATUS);
let body = response.extract_string().await;
insta::assert_snapshot!(body);
}
#[rstest]
#[tokio::test]
async fn test_caldav_principal(
#[from(get_app)]
#[future]
app: axum::Router,
) {
let app = app.await;
let request_template = || {
Request::builder()
.method("PROPFIND")
.uri("/caldav/principal/user")
.body(Body::empty())
.unwrap()
};
// Try without authentication
let request = request_template();
let response = app.clone().oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
let body = response.extract_string().await;
insta::assert_snapshot!(body);
// Try with wrong password
let mut request = request_template();
request
.headers_mut()
.typed_insert(Authorization::basic("user", "wrongpass"));
let response = app.clone().oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
let body = response.extract_string().await;
insta::assert_snapshot!(body);
// Try with correct credentials
let mut request = request_template();
request
.headers_mut()
.typed_insert(Authorization::basic("user", "pass"));
let response = app.clone().oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::MULTI_STATUS);
let body = response.extract_string().await;
insta::assert_snapshot!(body);
// Try with Depth: 1
let mut request = request_template();
request
.headers_mut()
.typed_insert(Authorization::basic("user", "pass"));
request
.headers_mut()
.insert("Depth", HeaderValue::from_static("1"));
let response = app.clone().oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::MULTI_STATUS);
let body = response.extract_string().await;
insta::assert_snapshot!(body);
}

View File

@@ -0,0 +1,5 @@
---
source: src/integration_tests/caldav/mod.rs
expression: body
---

View File

@@ -0,0 +1,53 @@
---
source: src/integration_tests/caldav/mod.rs
expression: body
---
<?xml version="1.0" encoding="utf-8"?>
<multistatus xmlns="DAV:" xmlns:CAL="urn:ietf:params:xml:ns:caldav" xmlns:CARD="urn:ietf:params:xml:ns:carddav" xmlns:CS="http://calendarserver.org/ns/" xmlns:PUSH="https://bitfire.at/webdav-push">
<response xmlns="DAV:" xmlns:CAL="urn:ietf:params:xml:ns:caldav" xmlns:CARD="urn:ietf:params:xml:ns:carddav" xmlns:CS="http://calendarserver.org/ns/" xmlns:PUSH="https://bitfire.at/webdav-push">
<href>/caldav/principal/user/</href>
<propstat>
<prop>
<CAL:calendar-user-type>INDIVIDUAL</CAL:calendar-user-type>
<CAL:calendar-user-address-set>
<href>/caldav/principal/user/</href>
</CAL:calendar-user-address-set>
<principal-URL>
<href>/caldav/principal/user/</href>
</principal-URL>
<group-membership>
</group-membership>
<group-member-set>
</group-member-set>
<alternate-URI-set/>
<supported-report-set>
<supported-report>
<report>
<principal-match/>
</report>
</supported-report>
</supported-report-set>
<CAL:calendar-home-set>
<href>/caldav/principal/user/</href>
</CAL:calendar-home-set>
<resourcetype>
<collection/>
<principal/>
</resourcetype>
<displayname>user</displayname>
<current-user-principal>
<href>/caldav/principal/user/</href>
</current-user-principal>
<current-user-privilege-set>
<privilege>
<all/>
</privilege>
</current-user-privilege-set>
<owner>
<href>/caldav/principal/user/</href>
</owner>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
</multistatus>

View File

@@ -0,0 +1,53 @@
---
source: src/integration_tests/caldav/mod.rs
expression: body
---
<?xml version="1.0" encoding="utf-8"?>
<multistatus xmlns="DAV:" xmlns:CAL="urn:ietf:params:xml:ns:caldav" xmlns:CARD="urn:ietf:params:xml:ns:carddav" xmlns:CS="http://calendarserver.org/ns/" xmlns:PUSH="https://bitfire.at/webdav-push">
<response xmlns="DAV:" xmlns:CAL="urn:ietf:params:xml:ns:caldav" xmlns:CARD="urn:ietf:params:xml:ns:carddav" xmlns:CS="http://calendarserver.org/ns/" xmlns:PUSH="https://bitfire.at/webdav-push">
<href>/caldav/principal/user/</href>
<propstat>
<prop>
<CAL:calendar-user-type>INDIVIDUAL</CAL:calendar-user-type>
<CAL:calendar-user-address-set>
<href>/caldav/principal/user/</href>
</CAL:calendar-user-address-set>
<principal-URL>
<href>/caldav/principal/user/</href>
</principal-URL>
<group-membership>
</group-membership>
<group-member-set>
</group-member-set>
<alternate-URI-set/>
<supported-report-set>
<supported-report>
<report>
<principal-match/>
</report>
</supported-report>
</supported-report-set>
<CAL:calendar-home-set>
<href>/caldav/principal/user/</href>
</CAL:calendar-home-set>
<resourcetype>
<collection/>
<principal/>
</resourcetype>
<displayname>user</displayname>
<current-user-principal>
<href>/caldav/principal/user/</href>
</current-user-principal>
<current-user-privilege-set>
<privilege>
<all/>
</privilege>
</current-user-privilege-set>
<owner>
<href>/caldav/principal/user/</href>
</owner>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
</multistatus>

View File

@@ -0,0 +1,5 @@
---
source: src/integration_tests/caldav/mod.rs
expression: body
---

View File

@@ -0,0 +1,5 @@
---
source: src/integration_tests/caldav/mod.rs
expression: body
---

View File

@@ -0,0 +1,27 @@
---
source: src/integration_tests/caldav/mod.rs
expression: body
---
<?xml version="1.0" encoding="utf-8"?>
<multistatus xmlns="DAV:" xmlns:CAL="urn:ietf:params:xml:ns:caldav" xmlns:CARD="urn:ietf:params:xml:ns:carddav" xmlns:CS="http://calendarserver.org/ns/" xmlns:PUSH="https://bitfire.at/webdav-push">
<response xmlns="DAV:" xmlns:CAL="urn:ietf:params:xml:ns:caldav" xmlns:CARD="urn:ietf:params:xml:ns:carddav" xmlns:CS="http://calendarserver.org/ns/" xmlns:PUSH="https://bitfire.at/webdav-push">
<href>/caldav/</href>
<propstat>
<prop>
<resourcetype>
<collection/>
</resourcetype>
<displayname>RustiCal DAV root</displayname>
<current-user-principal>
<href>/caldav/principal/user/</href>
</current-user-principal>
<current-user-privilege-set>
<privilege>
<all/>
</privilege>
</current-user-privilege-set>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
</multistatus>

View File

@@ -0,0 +1,5 @@
---
source: src/integration_tests/caldav/mod.rs
expression: body
---

View File

@@ -0,0 +1,53 @@
use crate::integration_tests::{ResponseExtractString, get_app};
use axum::body::Body;
use axum::extract::Request;
use headers::{Authorization, HeaderMapExt};
use http::StatusCode;
use rstest::rstest;
use tower::ServiceExt;
#[rstest]
#[tokio::test]
async fn test_carddav_root(
#[from(get_app)]
#[future]
app: axum::Router,
) {
let app = app.await;
let request_template = || {
Request::builder()
.method("PROPFIND")
.uri("/carddav")
.body(Body::empty())
.unwrap()
};
// Try without authentication
let request = request_template();
let response = app.clone().oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
let body = response.extract_string().await;
insta::assert_snapshot!(body);
// Try with wrong password
let mut request = request_template();
request
.headers_mut()
.typed_insert(Authorization::basic("user", "wrongpass"));
let response = app.clone().oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
let body = response.extract_string().await;
insta::assert_snapshot!(body);
// Try with correct credentials
let mut request = request_template();
request
.headers_mut()
.typed_insert(Authorization::basic("user", "pass"));
let response = app.oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::MULTI_STATUS);
let body = response.extract_string().await;
insta::assert_snapshot!(body);
}

View File

@@ -0,0 +1,5 @@
---
source: src/integration_tests/carddav/mod.rs
expression: body
---

View File

@@ -0,0 +1,27 @@
---
source: src/integration_tests/carddav/mod.rs
expression: body
---
<?xml version="1.0" encoding="utf-8"?>
<multistatus xmlns="DAV:" xmlns:CAL="urn:ietf:params:xml:ns:caldav" xmlns:CARD="urn:ietf:params:xml:ns:carddav" xmlns:CS="http://calendarserver.org/ns/" xmlns:PUSH="https://bitfire.at/webdav-push">
<response xmlns="DAV:" xmlns:CAL="urn:ietf:params:xml:ns:caldav" xmlns:CARD="urn:ietf:params:xml:ns:carddav" xmlns:CS="http://calendarserver.org/ns/" xmlns:PUSH="https://bitfire.at/webdav-push">
<href>/carddav/</href>
<propstat>
<prop>
<resourcetype>
<collection/>
</resourcetype>
<displayname>RustiCal DAV root</displayname>
<current-user-principal>
<href>/carddav/principal/user/</href>
</current-user-principal>
<current-user-privilege-set>
<privilege>
<all/>
</privilege>
</current-user-privilege-set>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
</multistatus>

View File

@@ -0,0 +1,5 @@
---
source: src/integration_tests/carddav/mod.rs
expression: body
---

View File

@@ -1,4 +1,6 @@
use crate::{app::make_app, config::NextcloudLoginConfig};
use axum::extract::Request;
use axum::{body::Body, response::Response};
use rstest::rstest;
use rustical_frontend::FrontendConfig;
use rustical_store_sqlite::{
@@ -12,10 +14,10 @@ use rustical_store_sqlite::{
},
};
use std::sync::Arc;
use tower::ServiceExt;
#[rstest]
#[tokio::test]
async fn test_app(
#[rstest::fixture]
pub async fn get_app(
#[from(get_test_calendar_store)]
#[future]
cal_store: SqliteCalendarStore,
@@ -28,13 +30,13 @@ async fn test_app(
#[from(get_test_subscription_store)]
#[future]
sub_store: SqliteStore,
) {
) -> axum::Router {
let addr_store = Arc::new(addr_store.await);
let cal_store = Arc::new(cal_store.await);
let sub_store = Arc::new(sub_store.await);
let principal_store = Arc::new(principal_store.await);
let _app = make_app(
make_app(
addr_store,
cal_store,
sub_store,
@@ -48,5 +50,38 @@ async fn test_app(
false,
true,
20,
);
)
}
pub trait ResponseExtractString {
#[allow(async_fn_in_trait)]
async fn extract_string(self) -> String;
}
impl ResponseExtractString for Response {
async fn extract_string(self) -> String {
let bytes = axum::body::to_bytes(self.into_body(), usize::MAX)
.await
.unwrap();
String::from_utf8(bytes.to_vec()).unwrap()
}
}
#[rstest]
#[tokio::test]
async fn test_ping(
#[from(get_app)]
#[future]
app: axum::Router,
) {
let app = app.await;
let response = app
.oneshot(Request::builder().uri("/ping").body(Body::empty()).unwrap())
.await
.unwrap();
assert!(response.status().is_success());
}
mod caldav;
mod carddav;

View File

@@ -30,9 +30,9 @@ use tracing::info;
mod app;
mod commands;
mod config;
mod setup_tracing;
#[cfg(test)]
mod tests;
pub mod integration_tests;
mod setup_tracing;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]