mirror of
https://github.com/lennart-k/rustical.git
synced 2026-01-30 11:48:18 +00:00
Compare commits
3 Commits
af60a446ad
...
233cf2ea37
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
233cf2ea37 | ||
|
|
494f31f992 | ||
|
|
c1758e2cba |
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -3319,6 +3319,7 @@ dependencies = [
|
||||
"caldata",
|
||||
"clap",
|
||||
"figment",
|
||||
"futures-util",
|
||||
"headers",
|
||||
"http",
|
||||
"insta",
|
||||
@@ -3344,6 +3345,7 @@ dependencies = [
|
||||
"similar-asserts",
|
||||
"sqlx",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"toml 0.9.11+spec-1.1.0",
|
||||
"tower",
|
||||
"tower-http",
|
||||
@@ -4471,6 +4473,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -73,6 +73,7 @@ tokio = { version = "1.48", features = [
|
||||
"rt-multi-thread",
|
||||
"full",
|
||||
] }
|
||||
tokio-util = { version = "0.7", features = ["rt"] }
|
||||
url = "2.5"
|
||||
base64 = "0.22"
|
||||
thiserror = "2.0"
|
||||
@@ -166,6 +167,7 @@ caldata.workspace = true
|
||||
toml.workspace = true
|
||||
serde.workspace = true
|
||||
tokio.workspace = true
|
||||
tokio-util.workspace = true
|
||||
tracing.workspace = true
|
||||
anyhow.workspace = true
|
||||
clap.workspace = true
|
||||
@@ -204,3 +206,4 @@ tower-http.workspace = true
|
||||
axum-extra.workspace = true
|
||||
headers.workspace = true
|
||||
http.workspace = true
|
||||
futures-util.workspace = true
|
||||
|
||||
10
src/lib.rs
10
src/lib.rs
@@ -17,6 +17,7 @@ use rustical_store_sqlite::principal_store::SqlitePrincipalStore;
|
||||
use rustical_store_sqlite::{SqliteStore, create_db_pool};
|
||||
use setup_tracing::setup_tracing;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Notify;
|
||||
use tokio::sync::mpsc::Receiver;
|
||||
use tower::Layer;
|
||||
use tower_http::normalize_path::NormalizePathLayer;
|
||||
@@ -105,7 +106,11 @@ pub async fn get_data_stores(
|
||||
}
|
||||
|
||||
#[allow(clippy::missing_errors_doc, clippy::missing_panics_doc)]
|
||||
pub async fn cmd_default(args: Args, config: Config) -> Result<()> {
|
||||
pub async fn cmd_default(
|
||||
args: Args,
|
||||
config: Config,
|
||||
start_notifier: Option<Arc<Notify>>,
|
||||
) -> Result<()> {
|
||||
setup_tracing(&config.tracing);
|
||||
|
||||
let (addr_store, cal_store, subscription_store, principal_store, update_recv) =
|
||||
@@ -144,6 +149,9 @@ pub async fn cmd_default(args: Args, config: Config) -> Result<()> {
|
||||
let listener = tokio::net::TcpListener::bind(&address).await?;
|
||||
tasks.push(tokio::spawn(async move {
|
||||
info!("RustiCal serving on http://{address}");
|
||||
if let Some(start_notifier) = start_notifier {
|
||||
start_notifier.notify_waiters();
|
||||
}
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
}));
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
None => {
|
||||
let config: Config = parse_config()?;
|
||||
cmd_default(args, config).await
|
||||
cmd_default(args, config, None).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
use rustical::{
|
||||
Args, cmd_default,
|
||||
config::{Config, DataStoreConfig, HttpConfig, SqliteDataStoreConfig},
|
||||
};
|
||||
use std::{
|
||||
net::{Ipv4Addr, SocketAddrV4, TcpListener},
|
||||
sync::Arc,
|
||||
thread::{self, JoinHandle},
|
||||
};
|
||||
use tokio::sync::Notify;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
pub fn find_free_port() -> Option<u16> {
|
||||
let mut port = 15000;
|
||||
// Frees the socket on drop such that this function returns a free port
|
||||
while TcpListener::bind(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port)).is_err() {
|
||||
port += 1;
|
||||
|
||||
if port >= 16000 {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(port)
|
||||
}
|
||||
|
||||
pub fn rustical_process() -> (CancellationToken, u16, JoinHandle<()>, Arc<Notify>) {
|
||||
let port = find_free_port().unwrap();
|
||||
let token = CancellationToken::new();
|
||||
let cloned_token = token.clone();
|
||||
let start_notify = Arc::new(Notify::new());
|
||||
let cloned_start_notify = start_notify.clone();
|
||||
|
||||
let main_process = thread::spawn(move || {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let fut = async {
|
||||
cmd_default(
|
||||
Args {
|
||||
config_file: "asldajldakjsdkj".to_owned(),
|
||||
no_migrations: false,
|
||||
command: None,
|
||||
},
|
||||
Config {
|
||||
data_store: DataStoreConfig::Sqlite(SqliteDataStoreConfig {
|
||||
db_url: ":memory:".to_owned(),
|
||||
run_repairs: true,
|
||||
skip_broken: false,
|
||||
}),
|
||||
http: HttpConfig {
|
||||
host: "127.0.0.1".to_owned(),
|
||||
port,
|
||||
..Default::default()
|
||||
},
|
||||
frontend: Default::default(),
|
||||
oidc: None,
|
||||
tracing: Default::default(),
|
||||
dav_push: Default::default(),
|
||||
nextcloud_login: Default::default(),
|
||||
caldav: Default::default(),
|
||||
},
|
||||
Some(cloned_start_notify),
|
||||
)
|
||||
.await
|
||||
};
|
||||
rt.block_on(async {
|
||||
tokio::select! {
|
||||
_ = cloned_token.cancelled() => {},
|
||||
_ = fut => {}
|
||||
}
|
||||
});
|
||||
});
|
||||
(token, port, main_process, start_notify)
|
||||
}
|
||||
|
||||
44
tests/http_integration.rs
Normal file
44
tests/http_integration.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
// This integration test checks whether the HTTP server works by actually running rustical in a new
|
||||
// thread.
|
||||
use common::rustical_process;
|
||||
use http::StatusCode;
|
||||
use std::time::Duration;
|
||||
|
||||
mod common;
|
||||
|
||||
pub async fn test_runner<O, F>(inner: F)
|
||||
where
|
||||
O: IntoFuture<Output = ()>,
|
||||
// <O as IntoFuture>::IntoFuture: UnwindSafe,
|
||||
F: FnOnce(String) -> O,
|
||||
{
|
||||
// Start RustiCal process
|
||||
let (token, port, main_process, start_notify) = rustical_process();
|
||||
let origin = format!("http://localhost:{port}");
|
||||
|
||||
// Wait for RustiCal server to listen
|
||||
tokio::time::timeout(Duration::new(2, 0), start_notify.notified())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// We use catch_unwind to make sure we'll always correctly stop RustiCal
|
||||
// Otherwise, our process would just run indefinitely
|
||||
inner(origin).into_future().await;
|
||||
|
||||
// Signal RustiCal to stop
|
||||
token.cancel();
|
||||
main_process.join().unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_ping() {
|
||||
test_runner(async |origin| {
|
||||
let resp = reqwest::get(origin.clone() + "/ping").await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
// Ensure that path normalisation works as intended
|
||||
let resp = reqwest::get(origin + "/ping/").await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
source: tests/integration_tests/carddav/addressbook.rs
|
||||
expression: body
|
||||
---
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
source: tests/integration_tests/carddav/addressbook.rs
|
||||
expression: body
|
||||
---
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
source: tests/integration_tests/carddav/addressbook.rs
|
||||
expression: body
|
||||
---
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
source: tests/integration_tests/carddav/addressbook.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>
|
||||
<href>/carddav/principal/user/contacts/newcard.vcf</href>
|
||||
<propstat>
|
||||
<prop>
|
||||
<getetag>"ea0bf4a2ce7ef84606a4cf9235776dbc11b3e7ce351ddf35f27cbc0088acca7e"</getetag>
|
||||
<CARD:address-data>BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
FN:Cyrus Daboo
|
||||
N:Daboo;Cyrus
|
||||
ADR;TYPE=POSTAL:;2822 Email HQ;Suite 2821;RFCVille;PA;15213;USA
|
||||
EMAIL;TYPE=INTERNET,PREF:cyrus@example.com
|
||||
NICKNAME:me
|
||||
NOTE:Example VCard.
|
||||
ORG:Self Employed
|
||||
TEL;TYPE=WORK,VOICE:412 605 0499
|
||||
TEL;TYPE=FAX:412 605 0705
|
||||
URL:http://www.example.com
|
||||
UID:1234-5678-9000-1
|
||||
END:VCARD
|
||||
</CARD:address-data>
|
||||
</prop>
|
||||
<status>HTTP/1.1 200 OK</status>
|
||||
</propstat>
|
||||
</response>
|
||||
<response>
|
||||
<href>/home/bernard/addressbook/vcf1.vcf</href>
|
||||
<status>HTTP/1.1 404 Not Found</status>
|
||||
</response>
|
||||
</multistatus>
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
source: tests/integration_tests/carddav/addressbook.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>
|
||||
<href>/carddav/principal/user/contacts/</href>
|
||||
<propstat>
|
||||
<prop>
|
||||
<CARD:addressbook-description>Amazing contacts!</CARD:addressbook-description>
|
||||
<CARD:supported-address-data>
|
||||
<CARD:address-data-type content-type="text/vcard" version="3.0"/>
|
||||
<CARD:address-data-type content-type="text/vcard" version="4.0"/>
|
||||
</CARD:supported-address-data>
|
||||
<CARD:supported-collation-set>
|
||||
<CARD:supported-collation>i;ascii-casemap</CARD:supported-collation>
|
||||
<CARD:supported-collation>i;unicode-casemap</CARD:supported-collation>
|
||||
<CARD:supported-collation>i;octet</CARD:supported-collation>
|
||||
</CARD:supported-collation-set>
|
||||
<supported-report-set>
|
||||
<supported-report>
|
||||
<report>
|
||||
<CARD:addressbook-multiget/>
|
||||
</report>
|
||||
</supported-report>
|
||||
<supported-report>
|
||||
<report>
|
||||
<sync-collection/>
|
||||
</report>
|
||||
</supported-report>
|
||||
</supported-report-set>
|
||||
<CARD:max-resource-size>10000000</CARD:max-resource-size>
|
||||
<sync-token>github.com/lennart-k/rustical/ns/0</sync-token>
|
||||
<CS:getctag>github.com/lennart-k/rustical/ns/0</CS:getctag>
|
||||
<PUSH:transports>
|
||||
<PUSH:web-push/>
|
||||
</PUSH:transports>
|
||||
<PUSH:topic>[PUSH_TOPIC]</PUSH:topic>
|
||||
<PUSH:supported-triggers>
|
||||
<PUSH:content-update>
|
||||
<depth>1</depth>
|
||||
</PUSH:content-update>
|
||||
<PUSH:property-update>
|
||||
<depth>1</depth>
|
||||
</PUSH:property-update>
|
||||
</PUSH:supported-triggers>
|
||||
<resourcetype>
|
||||
<collection/>
|
||||
<CARD:addressbook/>
|
||||
</resourcetype>
|
||||
<displayname>Contacts</displayname>
|
||||
<current-user-principal>
|
||||
<href>/carddav/principal/user/</href>
|
||||
</current-user-principal>
|
||||
<current-user-privilege-set>
|
||||
<privilege>
|
||||
<all/>
|
||||
</privilege>
|
||||
</current-user-privilege-set>
|
||||
<owner>
|
||||
<href>/carddav/principal/user/</href>
|
||||
</owner>
|
||||
</prop>
|
||||
<status>HTTP/1.1 200 OK</status>
|
||||
</propstat>
|
||||
</response>
|
||||
</multistatus>
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
source: tests/integration_tests/carddav/addressbook.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>
|
||||
<href>/carddav/principal/user/contacts</href>
|
||||
<propstat>
|
||||
<prop>
|
||||
<displayname xmlns="DAV:"/>
|
||||
<addressbook-description xmlns="urn:ietf:params:xml:ns:carddav"/>
|
||||
<addressbook-description/>
|
||||
</prop>
|
||||
<status>HTTP/1.1 200 OK</status>
|
||||
</propstat>
|
||||
<propstat>
|
||||
<prop>
|
||||
</prop>
|
||||
<status>HTTP/1.1 404 Not Found</status>
|
||||
</propstat>
|
||||
<propstat>
|
||||
<prop>
|
||||
</prop>
|
||||
<status>HTTP/1.1 409 Conflict</status>
|
||||
</propstat>
|
||||
</response>
|
||||
</multistatus>
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
source: tests/integration_tests/carddav/addressbook_import.rs
|
||||
expression: body
|
||||
---
|
||||
BEGIN:VCARD
|
||||
VERSION:4.0
|
||||
FN:John Doe
|
||||
N:Doe;John;;;,
|
||||
BDAY:--0203
|
||||
GENDER:M
|
||||
UID:[UID]
|
||||
END:VCARD
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
source: tests/integration_tests/carddav/addressbook_import.rs
|
||||
expression: body
|
||||
---
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
source: tests/integration_tests/carddav/mod.rs
|
||||
expression: body
|
||||
---
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
source: tests/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>
|
||||
<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>
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
source: tests/integration_tests/carddav/mod.rs
|
||||
expression: body
|
||||
---
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
mod integration_tests;
|
||||
3
tests/run_integration_tests.rs
Normal file
3
tests/run_integration_tests.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file is just an entrypoint for integration_tests
|
||||
// since the test runner only looks for files to run.
|
||||
mod integration_tests;
|
||||
Reference in New Issue
Block a user