use super::{ResponseExtractString, get_app};
use axum::body::Body;
use axum::extract::Request;
use headers::{Authorization, HeaderMapExt};
use http::{HeaderValue, StatusCode};
use rstest::rstest;
use rustical_store::{CalendarMetadata, CalendarStore};
use rustical_store_sqlite::tests::{TestStoreContext, test_store_context};
use tower::ServiceExt;
pub fn mkcalendar_template(
CalendarMetadata {
displayname,
order: _order,
description,
color,
}: &CalendarMetadata,
) -> String {
format!(
r#"
{displayname}
{description}
{color}
"#,
displayname = displayname.as_deref().unwrap_or_default(),
description = description.as_deref().unwrap_or_default(),
color = color.as_deref().unwrap_or_default(),
)
}
#[rstest]
#[tokio::test]
async fn test_caldav_calendar(
#[from(test_store_context)]
#[future]
context: TestStoreContext,
) {
let context = context.await;
let app = get_app(context.clone());
let cal_store = context.cal_store;
let mut calendar_meta = CalendarMetadata {
displayname: Some("Calendar".to_string()),
description: Some("Description".to_string()),
color: Some("#00FF00".to_string()),
order: 0,
};
let (principal, cal_id) = ("user", "calendar");
let url = format!("/caldav/principal/{principal}/{cal_id}");
let request_template = || {
Request::builder()
.method("MKCALENDAR")
.uri(&url)
.body(Body::from(mkcalendar_template(&calendar_meta)))
.unwrap()
};
// Try OPTIONS without authentication
let request = Request::builder()
.method("OPTIONS")
.uri(&url)
.body(Body::empty())
.unwrap();
let response = app.clone().oneshot(request).await.unwrap();
insta::assert_debug_snapshot!(response, @r#"
Response {
status: 200,
version: HTTP/1.1,
headers: {
"dav": "1, 3, access-control, calendar-access, webdav-push",
"allow": "PROPFIND, PROPPATCH, COPY, MOVE, DELETE, OPTIONS, REPORT, GET, HEAD, POST, MKCOL, MKCALENDAR, IMPORT",
},
body: Body(
UnsyncBoxBody,
),
}
"#);
// Try without authentication
let request = request_template();
let response = app.clone().oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
// 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::CREATED);
let body = response.extract_string().await;
insta::assert_snapshot!("mkcalendar_body", body);
let mut request = Request::builder()
.method("GET")
.uri(&url)
.body(Body::empty())
.unwrap();
request
.headers_mut()
.typed_insert(Authorization::basic("user", "pass"));
let response = app.clone().oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let body = response.extract_string().await;
insta::assert_snapshot!("get_body", body);
assert_eq!(
cal_store
.get_calendar(principal, cal_id, false)
.await
.unwrap()
.meta,
calendar_meta
);
let mut request = Request::builder()
.method("PROPFIND")
.uri(&url)
.body(Body::empty())
.unwrap();
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::with_settings!({
filters => vec![
(r"[0-9a-f-]+", "[PUSH_TOPIC]")
]
}, {
insta::assert_snapshot!("propfind_body", body);
});
let proppatch_request: &str = r#"
New Displayname
Test
"#;
let mut request = Request::builder()
.method("PROPPATCH")
.uri(&url)
.body(Body::from(proppatch_request))
.unwrap();
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!("proppatch_body", body);
calendar_meta.displayname = Some("New Displayname".to_string());
calendar_meta.description = None;
assert_eq!(
cal_store
.get_calendar(principal, cal_id, false)
.await
.unwrap()
.meta,
calendar_meta
);
let mut request = Request::builder()
.method("DELETE")
.uri(&url)
.header("X-No-Trashbin", HeaderValue::from_static("1"))
.body(Body::empty())
.unwrap();
request
.headers_mut()
.typed_insert(Authorization::basic("user", "pass"));
let response = app.clone().oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let body = response.extract_string().await;
insta::assert_snapshot!("delete_body", body);
assert!(matches!(
cal_store.get_calendar(principal, cal_id, false).await,
Err(rustical_store::Error::NotFound)
));
}
#[rstest]
#[tokio::test]
async fn test_rfc4791_5_3_2(
#[from(test_store_context)]
#[future]
context: TestStoreContext,
) {
let context = context.await;
let app = get_app(context.clone());
let calendar_meta = CalendarMetadata {
displayname: Some("Calendar".to_string()),
description: Some("Description".to_string()),
color: Some("#00FF00".to_string()),
order: 0,
};
let (principal, cal_id) = ("user", "calendar");
let url = format!("/caldav/principal/{principal}/{cal_id}");
let request_template = || {
Request::builder()
.method("MKCALENDAR")
.uri(&url)
.body(Body::from(mkcalendar_template(&calendar_meta)))
.unwrap()
};
// 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::CREATED);
let ical = r"BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Example Corp.//CalDAV Client//EN
BEGIN:VEVENT
UID:20010712T182145Z-123401@example.com
DTSTAMP:20060712T182145Z
DTSTART:20060714T170000Z
DTEND:20060715T040000Z
SUMMARY:Bastille Day Party
END:VEVENT
END:VCALENDAR";
let mut request = Request::builder()
.method("PUT")
.uri(format!("{url}/qwue23489.ics"))
.header("If-None-Match", "*")
.header("Content-Type", "text/calendar")
.body(Body::from(ical))
.unwrap();
request
.headers_mut()
.typed_insert(Authorization::basic("user", "pass"));
let response = app.clone().oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::CREATED);
let mut request = Request::builder()
.method("PUT")
.uri(format!("{url}/qwue23489.ics"))
.header("If-None-Match", "*")
.header("Content-Type", "text/calendar")
.body(Body::from(ical))
.unwrap();
request
.headers_mut()
.typed_insert(Authorization::basic("user", "pass"));
let response = app.clone().oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::CONFLICT);
let mut request = Request::builder()
.method("REPORT")
.uri(&url)
.header("Depth", "infinity")
.header("Content-Type", "text/xml; charset=\"utf-8\"")
.body(Body::from(format!(
r#"
{url}/qwue23489.ics
/home/bernard/addressbook/vcf1.vcf
"#
)))
.unwrap();
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!("multiget_body", body);
}