use crate::{
error::{Error, Result},
types::{
cluster::{PendingDevices, PendingFolders},
config::{
Configuration, DeviceConfiguration, FolderConfiguration, NewDeviceConfiguration,
NewFolderConfiguration,
},
db::Completion,
events::Event,
system::Connections,
},
};
use reqwest::{StatusCode, header};
use tokio::sync::broadcast::Sender;
const ADDR: &str = "http://localhost:8384/rest";
#[must_use]
pub struct ClientBuilder {
base_url: Option<String>,
api_key: String,
}
impl ClientBuilder {
pub fn new(api_key: impl Into<String>) -> Self {
Self {
base_url: None,
api_key: api_key.into(),
}
}
pub fn base_url(mut self, url: impl Into<String>) -> Self {
self.base_url = Some(url.into());
self
}
pub fn build(self) -> Result<Client> {
let base_url = self.base_url.unwrap_or_else(|| ADDR.to_string());
let mut headers = header::HeaderMap::new();
let mut api_key_header = header::HeaderValue::from_str(&self.api_key)?;
api_key_header.set_sensitive(true);
headers.insert("X-API-KEY", api_key_header);
let client = reqwest::Client::builder()
.default_headers(headers)
.build()?;
Ok(Client { client, base_url })
}
}
#[derive(Clone, Debug)]
pub struct Client {
client: reqwest::Client,
base_url: String,
}
impl Client {
#[must_use]
pub fn new(api_key: &str) -> Self {
ClientBuilder::new(api_key).build().expect("Client::new()")
}
pub fn builder(api_key: impl Into<String>) -> ClientBuilder {
ClientBuilder::new(api_key)
}
pub async fn get_connections(&self) -> Result<Connections> {
log::debug!("GET /system/connections");
Ok(self
.client
.get(format!("{}/system/connections", self.base_url))
.send()
.await?
.error_for_status()?
.json()
.await?)
}
pub async fn ping(&self) -> Result<()> {
log::debug!("GET /system/ping");
self.client
.get(format!("{}/system/ping", self.base_url))
.send()
.await?
.error_for_status()?;
Ok(())
}
pub async fn health(&self) -> Result<()> {
log::debug!("GET /noauth/health");
self.client
.get(format!("{}/noauth/health", self.base_url))
.send()
.await?
.error_for_status()?;
Ok(())
}
pub async fn get_id(&self) -> Result<String> {
log::debug!("GET /noauth/health");
Ok(self
.client
.get(format!("{}/noauth/health", self.base_url))
.send()
.await?
.error_for_status()?
.headers()
.get("X-Syncthing-Id")
.ok_or(Error::HeaderDeviceIDError)?
.to_str()
.map_err(|_| Error::HeaderParseError)?
.to_string())
}
pub async fn get_events(&self, tx: Sender<Event>, mut skip_old: bool) -> Result<()> {
let mut current_id = 0;
loop {
log::debug!("GET /events");
let events: Vec<Event> = self
.client
.get(format!("{}/events?since={}", self.base_url, current_id))
.send()
.await?
.error_for_status()?
.json()
.await?;
log::debug!("received {} new events", events.len());
for event in events {
current_id = event.id;
if !skip_old {
tx.send(event)?;
}
}
log::debug!("current event id is {current_id}");
skip_old = false;
}
}
pub async fn get_configuration(&self) -> Result<Configuration> {
log::debug!("GET /config");
Ok(self
.client
.get(format!("{}/config", self.base_url))
.send()
.await?
.error_for_status()?
.json()
.await?)
}
pub async fn post_folder(&self, folder: impl Into<NewFolderConfiguration>) -> Result<()> {
let folder = folder.into();
log::debug!("POST /config/folders {:?}", folder);
self.client
.post(format!("{}/config/folders", self.base_url))
.json(&folder)
.send()
.await?
.error_for_status()?;
Ok(())
}
pub async fn add_folder(&self, folder: impl Into<NewFolderConfiguration>) -> Result<()> {
let folder = folder.into();
match self.get_folder(folder.get_id()).await {
Ok(_) => return Err(Error::DuplicateFolderError),
Err(Error::UnknownFolderError) => (),
Err(e) => return Err(e),
}
self.post_folder(folder).await
}
pub async fn get_folder(&self, folder_id: &str) -> Result<FolderConfiguration> {
log::debug!("GET /config/folders/{}", folder_id);
let response = self
.client
.get(format!("{}/config/folders/{}", self.base_url, folder_id))
.send()
.await?;
if response.status() == StatusCode::NOT_FOUND {
Err(Error::UnknownFolderError)
} else {
Ok(response.error_for_status()?.json().await?)
}
}
pub async fn delete_folder(&self, folder_id: &str) -> Result<()> {
log::debug!("DELETE /config/folders/{}", folder_id);
self.client
.delete(format!("{}/config/folders/{}", self.base_url, folder_id))
.send()
.await?
.error_for_status()?;
Ok(())
}
pub async fn post_device(&self, device: impl Into<NewDeviceConfiguration>) -> Result<()> {
let device = device.into();
log::debug!("POST /config/devices {:?}", device);
self.client
.post(format!("{}/config/devices", self.base_url))
.json(&device)
.send()
.await?
.error_for_status()?;
Ok(())
}
pub async fn add_device(&self, device: impl Into<NewDeviceConfiguration>) -> Result<()> {
let device = device.into();
match self.get_device(device.get_device_id()).await {
Ok(_) => return Err(Error::DuplicateDeviceError),
Err(Error::UnknownDeviceError) => (),
Err(e) => return Err(e),
}
self.post_device(device).await
}
pub async fn get_device(&self, device_id: &str) -> Result<DeviceConfiguration> {
log::debug!("GET /config/devices/{}", device_id);
let response = self
.client
.get(format!("{}/config/devices/{}", self.base_url, device_id))
.send()
.await?;
if response.status() == StatusCode::NOT_FOUND {
Err(Error::UnknownDeviceError)
} else {
Ok(response.error_for_status()?.json().await?)
}
}
pub async fn delete_device(&self, device_id: &str) -> Result<()> {
log::debug!("DELETE /config/devices/{}", device_id);
self.client
.delete(format!("{}/config/devices/{}", self.base_url, device_id))
.send()
.await?
.error_for_status()?;
Ok(())
}
pub async fn get_pending_devices(&self) -> Result<PendingDevices> {
log::debug!("GET /cluster/pending/devices");
Ok(self
.client
.get(format!("{}/cluster/pending/devices", self.base_url))
.send()
.await?
.error_for_status()?
.json()
.await?)
}
pub async fn get_pending_folders(&self) -> Result<PendingFolders> {
log::debug!("GET /cluster/pending/folders");
Ok(self
.client
.get(format!("{}/cluster/pending/folders", self.base_url))
.send()
.await?
.error_for_status()?
.json()
.await?)
}
pub async fn dismiss_pending_device(&self, device_id: &str) -> Result<()> {
log::debug!("DELETE /cluster/pending/devices?device={device_id}");
self.client
.delete(format!(
"{}/cluster/pending/devices?device={}",
self.base_url, device_id
))
.send()
.await?
.error_for_status()?;
Ok(())
}
pub async fn dismiss_pending_folder(
&self,
folder_id: &str,
device_id: Option<&str>,
) -> Result<()> {
let device_str = match device_id {
Some(device_id) => format!("&device={}", device_id),
None => String::new(),
};
log::debug!("DELETE /cluster/pending/folders?folder={folder_id}{device_str}");
self.client
.delete(format!(
"{}/cluster/pending/folders?folder={}{}",
self.base_url, folder_id, device_str
))
.send()
.await?
.error_for_status()?;
Ok(())
}
pub async fn get_default_device(&self) -> Result<DeviceConfiguration> {
log::debug!("GET /config/defaults/device");
Ok(self
.client
.get(format!("{}/config/defaults/device", self.base_url))
.send()
.await?
.error_for_status()?
.json()
.await?)
}
pub async fn get_default_folder(&self) -> Result<FolderConfiguration> {
log::debug!("GET /config/defaults/folder");
Ok(self
.client
.get(format!("{}/config/defaults/folder", self.base_url))
.send()
.await?
.error_for_status()?
.json()
.await?)
}
pub async fn get_completion(
&self,
folder_id: Option<&str>,
device_id: Option<&str>,
) -> Result<Completion> {
let folder_str = match folder_id {
Some(folder_id) => format!("folder={}", folder_id),
None => String::new(),
};
let device_str = match device_id {
Some(device_id) => format!("device={}", device_id),
None => String::new(),
};
let questionmark = if folder_id.is_some() || device_id.is_some() {
"?"
} else {
""
};
let and = if folder_id.is_some() && device_id.is_some() {
"&"
} else {
""
};
let query = format!("{}{}{}{}", questionmark, folder_str, and, device_str);
log::debug!("GET /db/completion{}", query);
Ok(self
.client
.get(format!("{}/db/completion{}", self.base_url, query))
.send()
.await?
.error_for_status()?
.json()
.await?)
}
}
#[cfg(test)]
mod tests {
use crate::types::{config::FolderDeviceConfiguration, events::EventType};
use super::*;
use httpmock::prelude::*;
use testcontainers::{
ContainerAsync, GenericImage, ImageExt,
core::{ContainerPort::Tcp, WaitFor},
runners::AsyncRunner,
};
use tokio::sync::broadcast;
use rstest::*;
const DEVICE_ID: &str = "MFZWI3D-BONSGYC-YLTMRWG-C43ENR5-QXGZDMM-FZWI3DP-BONSGYY-LTMRWAD";
#[fixture]
async fn syncthing_setup() -> (ContainerAsync<GenericImage>, Client) {
let api_key = "foobar";
let container = GenericImage::new("syncthing/syncthing", "latest")
.with_exposed_port(Tcp(8384))
.with_wait_for(WaitFor::message_on_stdout("GUI and API listening on "))
.with_env_var("STGUIAPIKEY", api_key)
.start()
.await
.expect("failed to start syncthing container");
let host = container
.get_host()
.await
.expect("could not get syncthing host");
let port = container
.get_host_port_ipv4(8384)
.await
.expect("could not get syncthing port");
let url = format!("http://{host}:{port}/rest");
let client = ClientBuilder::new(api_key).base_url(url).build().unwrap();
(container, client)
}
#[test]
fn test_new() {
let client = Client::new("foo");
assert_eq!(client.base_url, "http://localhost:8384/rest");
}
#[tokio::test]
async fn test_ping() {
let server = MockServer::start();
let ping_mock = server.mock(|when, then| {
when.method(GET).path("/system/ping");
then.status(200)
.header("content-type", "application/json")
.body(r#"{"ping": "pong"}"#);
});
let client = ClientBuilder::new("")
.base_url(server.base_url())
.build()
.unwrap();
let result = client.ping().await;
ping_mock.assert();
assert!(result.is_ok());
}
#[tokio::test]
async fn test_single_event() {
let server = MockServer::start();
let event_mock = server.mock(|when, then| {
when.method(GET).path("/events");
then.status(200)
.header("content-type", "application/json")
.body(
r#"
[
{
"id": 1,
"globalID": 1,
"time": "2025-05-07T17:05:44.514050967+02:00",
"type": "Starting",
"data": {
"home": "/home/user/.config/syncthing",
"myID": "XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX"
}
}
]
"#,
);
});
let client = ClientBuilder::new("")
.base_url(server.base_url())
.build()
.unwrap();
let (tx, mut rx) = broadcast::channel(1);
tokio::spawn(async move {
let result = client.get_events(tx, false).await;
unreachable!("get_events should not have returned: {:?}", result);
});
let event = rx.recv().await;
assert!(event_mock.hits() > 0);
assert!(event.is_ok());
assert!(matches!(event.unwrap().ty, EventType::Starting { home: _ }));
}
#[tokio::test]
async fn container_test_health() {
let container = GenericImage::new("syncthing/syncthing", "latest")
.with_exposed_port(Tcp(8384))
.with_wait_for(WaitFor::message_on_stdout("GUI and API listening on "))
.start()
.await
.expect("failed to start syncthing container");
let host = container
.get_host()
.await
.expect("could not get syncthing host");
let port = container
.get_host_port_ipv4(8384)
.await
.expect("could not get syncthing port");
let url = format!("http://{host}:{port}/rest");
let client = ClientBuilder::new("idk").base_url(url).build().unwrap();
client.health().await.unwrap();
}
#[tokio::test]
async fn container_test_id() {
let container = GenericImage::new("syncthing/syncthing", "latest")
.with_exposed_port(Tcp(8384))
.with_wait_for(WaitFor::message_on_stdout("GUI and API listening on "))
.start()
.await
.expect("failed to start syncthing container");
let host = container
.get_host()
.await
.expect("could not get syncthing host");
let port = container
.get_host_port_ipv4(8384)
.await
.expect("could not get syncthing port");
let url = format!("http://{host}:{port}/rest");
let client = ClientBuilder::new("idk").base_url(url).build().unwrap();
client.get_id().await.unwrap();
}
#[rstest]
#[tokio::test]
async fn container_test_ping(
#[future] syncthing_setup: (ContainerAsync<GenericImage>, Client),
) {
let (_container, client) = syncthing_setup.await;
client.ping().await.unwrap();
}
#[rstest]
#[tokio::test]
async fn container_test_get_config(
#[future] syncthing_setup: (ContainerAsync<GenericImage>, Client),
) {
let (_container, client) = syncthing_setup.await;
client
.get_configuration()
.await
.expect("could not get config");
}
#[rstest]
#[tokio::test]
async fn container_test_post_folder(
#[future] syncthing_setup: (ContainerAsync<GenericImage>, Client),
) {
let (_container, client) = syncthing_setup.await;
let folder_id = "this-is-a-new-folder";
let path = "/tmp";
let folder = NewFolderConfiguration::new(folder_id.to_string(), path.to_string());
client
.post_folder(folder)
.await
.expect("could not post folder");
let api_folder = client
.get_folder(folder_id)
.await
.expect("could not get folder");
assert_eq!(&api_folder.id, folder_id);
assert_eq!(&api_folder.path, path);
}
#[rstest]
#[tokio::test]
async fn container_test_add_folder(
#[future] syncthing_setup: (ContainerAsync<GenericImage>, Client),
) {
let (_container, client) = syncthing_setup.await;
let folder_id = "this-is-a-new-folder";
let path = "/tmp";
let folder = NewFolderConfiguration::new(folder_id.to_string(), path.to_string());
client
.add_folder(folder)
.await
.expect("could not add folder");
let api_folder = client
.get_folder(folder_id)
.await
.expect("could not get folder");
assert_eq!(&api_folder.id, folder_id);
assert_eq!(&api_folder.path, path);
}
#[rstest]
#[tokio::test]
#[should_panic(expected = "DuplicateFolderError")]
async fn container_test_add_folder_twice_panic(
#[future] syncthing_setup: (ContainerAsync<GenericImage>, Client),
) {
let (_container, client) = syncthing_setup.await;
let folder_id = "this-is-a-new-folder";
let path = "/tmp";
let folder = NewFolderConfiguration::new(folder_id.to_string(), path.to_string());
client
.add_folder(folder)
.await
.expect("could not add folder");
let duplicate_path = "/usr";
let duplicate_folder =
NewFolderConfiguration::new(folder_id.to_string(), duplicate_path.to_string());
client
.add_folder(duplicate_folder)
.await
.expect("could not add folder")
}
#[rstest]
#[tokio::test]
async fn container_test_post_folder_twice(
#[future] syncthing_setup: (ContainerAsync<GenericImage>, Client),
) {
let (_container, client) = syncthing_setup.await;
let folder_id = "this-is-a-new-folder";
let path = "/tmp";
let folder = NewFolderConfiguration::new(folder_id.to_string(), path.to_string());
client
.add_folder(folder)
.await
.expect("could not add folder");
let duplicate_path = "/usr";
let duplicate_folder =
NewFolderConfiguration::new(folder_id.to_string(), duplicate_path.to_string());
client
.post_folder(duplicate_folder)
.await
.expect("could not post folder");
let api_folder = client
.get_folder(folder_id)
.await
.expect("could not get folder");
assert_eq!(&api_folder.id, folder_id);
assert_eq!(&api_folder.path, duplicate_path);
}
#[rstest]
#[tokio::test]
async fn container_test_post_device(
#[future] syncthing_setup: (ContainerAsync<GenericImage>, Client),
) {
let (_container, client) = syncthing_setup.await;
let device = NewDeviceConfiguration::new(DEVICE_ID.to_string());
client
.post_device(device)
.await
.expect("could not post device");
let api_device = client
.get_device(DEVICE_ID)
.await
.expect("could not get device");
assert_eq!(&api_device.device_id, DEVICE_ID);
}
#[rstest]
#[tokio::test]
async fn container_test_add_device(
#[future] syncthing_setup: (ContainerAsync<GenericImage>, Client),
) {
let (_container, client) = syncthing_setup.await;
let device = NewDeviceConfiguration::new(DEVICE_ID.to_string());
client
.add_device(device)
.await
.expect("could not add device");
let api_device = client
.get_device(DEVICE_ID)
.await
.expect("could not get device");
assert_eq!(&api_device.device_id, DEVICE_ID);
}
#[rstest]
#[tokio::test]
#[should_panic(expected = "DuplicateDeviceError")]
async fn container_test_add_device_twice_panic(
#[future] syncthing_setup: (ContainerAsync<GenericImage>, Client),
) {
let (_container, client) = syncthing_setup.await;
let device = NewDeviceConfiguration::new(DEVICE_ID.to_string());
client
.add_device(device)
.await
.expect("could not add device");
let duplicate_device = NewDeviceConfiguration::new(DEVICE_ID.to_string());
client
.add_device(duplicate_device)
.await
.expect("could not add device")
}
#[rstest]
#[tokio::test]
async fn container_test_post_device_twice(
#[future] syncthing_setup: (ContainerAsync<GenericImage>, Client),
) {
let (_container, client) = syncthing_setup.await;
let name = "original";
let device = NewDeviceConfiguration::new(DEVICE_ID.to_string()).name(name.to_string());
client
.add_device(device)
.await
.expect("could not add device");
let duplicate_name = "duplicate";
let duplicate_device =
NewDeviceConfiguration::new(DEVICE_ID.to_string()).name(duplicate_name.to_string());
client
.post_device(duplicate_device)
.await
.expect("could not post device");
let api_device = client
.get_device(DEVICE_ID)
.await
.expect("could not get device");
assert_eq!(&api_device.device_id, DEVICE_ID);
assert_eq!(&api_device.name, duplicate_name);
}
#[rstest]
#[tokio::test]
async fn container_test_pending_device(
#[future]
#[from(syncthing_setup)]
first: (ContainerAsync<GenericImage>, Client),
#[future]
#[from(syncthing_setup)]
second: (ContainerAsync<GenericImage>, Client),
) {
let (_first_container, first_client) = first.await;
let (_second_container, second_client) = second.await;
let first_id = first_client
.get_id()
.await
.expect("could not get id of first container");
let second_id = second_client
.get_id()
.await
.expect("could not get id of second container");
let (event_tx, mut event_rx) = broadcast::channel(10);
let first_client_handle = first_client.clone();
tokio::spawn(async move {
first_client_handle
.get_events(event_tx, true)
.await
.unwrap();
});
second_client
.add_device(NewDeviceConfiguration::new(first_id))
.await
.expect("could not add device");
loop {
let event = event_rx.recv().await.unwrap();
if let EventType::PendingDevicesChanged {
added: Some(added), ..
} = event.ty
{
if !added.is_empty() {
break;
}
}
}
let pending = first_client
.get_pending_devices()
.await
.expect("could not get pending devices");
assert!(pending.devices.contains_key(&second_id));
}
#[rstest]
#[tokio::test]
async fn container_test_delete_pending_device(
#[future]
#[from(syncthing_setup)]
first: (ContainerAsync<GenericImage>, Client),
#[future]
#[from(syncthing_setup)]
second: (ContainerAsync<GenericImage>, Client),
) {
let (_first_container, first_client) = first.await;
let (_second_container, second_client) = second.await;
let first_id = first_client
.get_id()
.await
.expect("could not get id of first container");
let second_id = second_client
.get_id()
.await
.expect("could not get id of second container");
let (event_tx, mut event_rx) = broadcast::channel(10);
let first_client_handle = first_client.clone();
tokio::spawn(async move {
first_client_handle
.get_events(event_tx, true)
.await
.unwrap();
});
second_client
.add_device(NewDeviceConfiguration::new(first_id))
.await
.expect("could not add device");
loop {
let event = event_rx.recv().await.unwrap();
if let EventType::PendingDevicesChanged {
added: Some(added), ..
} = event.ty
{
if !added.is_empty() {
break;
}
}
}
let pending = first_client
.get_pending_devices()
.await
.expect("could not get pending devices");
assert!(pending.devices.contains_key(&second_id));
first_client
.dismiss_pending_device(&second_id)
.await
.expect("could not delete pending device");
loop {
let event = event_rx.recv().await.unwrap();
if let EventType::PendingDevicesChanged {
removed: Some(removed),
..
} = event.ty
{
if !removed.is_empty() {
break;
}
}
}
let pending = first_client
.get_pending_devices()
.await
.expect("could not get pending devices");
assert!(!pending.devices.contains_key(&second_id));
assert_eq!(pending.devices.len(), 0)
}
#[rstest]
#[tokio::test]
async fn container_test_get_default_device(
#[future] syncthing_setup: (ContainerAsync<GenericImage>, Client),
) {
let (_container, client) = syncthing_setup.await;
client
.get_default_device()
.await
.expect("could not get default device");
}
#[rstest]
#[tokio::test]
async fn container_test_get_default_folder(
#[future] syncthing_setup: (ContainerAsync<GenericImage>, Client),
) {
let (_container, client) = syncthing_setup.await;
client
.get_default_folder()
.await
.expect("could not get default folder");
}
#[rstest]
#[tokio::test]
async fn container_test_system_connections(
#[future]
#[from(syncthing_setup)]
first: (ContainerAsync<GenericImage>, Client),
#[future]
#[from(syncthing_setup)]
second: (ContainerAsync<GenericImage>, Client),
) {
let (_first_container, first_client) = first.await;
let (_second_container, second_client) = second.await;
let first_id = first_client
.get_id()
.await
.expect("could not get id of first container");
let second_id = second_client
.get_id()
.await
.expect("could not get id of second container");
let (event_tx, mut event_rx) = broadcast::channel(10);
let first_client_handle = first_client.clone();
tokio::spawn(async move {
first_client_handle
.get_events(event_tx, true)
.await
.unwrap();
});
second_client
.add_device(NewDeviceConfiguration::new(first_id))
.await
.expect("could not add device");
loop {
let event = event_rx.recv().await.unwrap();
if let EventType::PendingDevicesChanged {
added: Some(added), ..
} = event.ty
{
if !added.is_empty() {
break;
}
}
}
let pending = first_client
.get_pending_devices()
.await
.expect("could not get pending devices");
assert!(pending.devices.contains_key(&second_id));
first_client
.add_device(NewDeviceConfiguration::new(second_id.clone()))
.await
.expect("could not add device");
let first_connections = first_client
.get_connections()
.await
.expect("could not get connections");
assert_eq!(first_connections.connections.len(), 1);
assert!(first_connections.connections.contains_key(&second_id));
assert!(
!first_connections
.connections
.get(&second_id)
.unwrap()
.paused
);
}
#[rstest]
#[tokio::test]
async fn container_test_completion(
#[future]
#[from(syncthing_setup)]
first: (ContainerAsync<GenericImage>, Client),
#[future]
#[from(syncthing_setup)]
second: (ContainerAsync<GenericImage>, Client),
) {
let (_first_container, first_client) = first.await;
let (_second_container, second_client) = second.await;
let first_id = first_client
.get_id()
.await
.expect("could not get id of first container");
let second_id = second_client
.get_id()
.await
.expect("could not get id of second container");
let (event_tx, mut event_rx) = broadcast::channel(10);
let first_client_handle = first_client.clone();
tokio::spawn(async move {
first_client_handle
.get_events(event_tx, true)
.await
.unwrap();
});
second_client
.add_device(NewDeviceConfiguration::new(first_id.clone()))
.await
.expect("could not add device");
loop {
let event = event_rx.recv().await.unwrap();
if let EventType::PendingDevicesChanged {
added: Some(added), ..
} = event.ty
{
if !added.is_empty() {
break;
}
}
}
let pending = first_client
.get_pending_devices()
.await
.expect("could not get pending devices");
assert!(pending.devices.contains_key(&second_id));
first_client
.add_device(NewDeviceConfiguration::new(second_id.clone()))
.await
.expect("could not add device");
let folder_id = "this-is-a-new-folder";
let path = "/tmp";
let folder_on_first = NewFolderConfiguration::new(folder_id.to_string(), path.to_string())
.devices(vec![FolderDeviceConfiguration {
device_id: second_id.clone(),
introduced_by: String::new(),
encryption_password: String::new(),
}]);
let folder_on_second = NewFolderConfiguration::new(folder_id.to_string(), path.to_string())
.devices(vec![FolderDeviceConfiguration {
device_id: first_id,
introduced_by: String::new(),
encryption_password: String::new(),
}]);
first_client
.post_folder(folder_on_first)
.await
.expect("could not post folder on first");
second_client
.post_folder(folder_on_second)
.await
.expect("could not post folder on second");
let _total_completion = first_client
.get_completion(None, None)
.await
.expect("could not get completion");
let _device_completion = first_client
.get_completion(None, Some(&second_id))
.await
.expect("could not get completion for device");
let _folder_completion = first_client
.get_completion(Some(folder_id), None)
.await
.expect("could not get completion for folder");
}
#[rstest]
#[tokio::test]
async fn container_test_delete_folder(
#[future] syncthing_setup: (ContainerAsync<GenericImage>, Client),
) {
let (_container, client) = syncthing_setup.await;
let folder_id = "this-is-a-new-folder";
let path = "/tmp";
let folder = NewFolderConfiguration::new(folder_id.to_string(), path.to_string());
client
.post_folder(folder)
.await
.expect("could not post folder");
let api_folder = client
.get_folder(folder_id)
.await
.expect("could not get folder");
let num_folders = client
.get_configuration()
.await
.expect("could not get config")
.folders
.len();
assert_eq!(&api_folder.id, folder_id);
assert_eq!(&api_folder.path, path);
client
.delete_folder(folder_id)
.await
.expect("could not delete folder");
let config = client
.get_configuration()
.await
.expect("could not get config");
assert_eq!(config.folders.len(), num_folders - 1);
}
#[rstest]
#[tokio::test]
async fn container_test_delete_device(
#[future] syncthing_setup: (ContainerAsync<GenericImage>, Client),
) {
let (_container, client) = syncthing_setup.await;
let device = NewDeviceConfiguration::new(DEVICE_ID.to_string());
client
.post_device(device)
.await
.expect("could not post device");
let api_device = client
.get_device(DEVICE_ID)
.await
.expect("could not get device");
let num_devices = client
.get_configuration()
.await
.expect("could not get config")
.devices
.len();
assert_eq!(&api_device.device_id, DEVICE_ID);
client
.delete_device(DEVICE_ID)
.await
.expect("could not delete folder");
let config = client
.get_configuration()
.await
.expect("could not get config");
assert_eq!(config.devices.len(), num_devices - 1);
}
}