From 547c67120a320482ca6757f485494aab9d724cff Mon Sep 17 00:00:00 2001 From: mdecimus Date: Sat, 17 Aug 2024 18:56:41 +0200 Subject: [PATCH] Undelete tests --- .../src/api/management/enterprise/undelete.rs | 16 +- tests/Cargo.toml | 2 +- tests/src/jmap/enterprise.rs | 165 ++++++++++++++++++ tests/src/jmap/mod.rs | 10 ++ 4 files changed, 184 insertions(+), 9 deletions(-) create mode 100644 tests/src/jmap/enterprise.rs diff --git a/crates/jmap/src/api/management/enterprise/undelete.rs b/crates/jmap/src/api/management/enterprise/undelete.rs index d6657981..08fff17b 100644 --- a/crates/jmap/src/api/management/enterprise/undelete.rs +++ b/crates/jmap/src/api/management/enterprise/undelete.rs @@ -31,21 +31,21 @@ use crate::{ JMAP, }; -#[derive(serde::Deserialize)] -struct UndeleteRequest { - hash: H, - collection: C, +#[derive(serde::Deserialize, serde::Serialize)] +pub struct UndeleteRequest { + pub hash: H, + pub collection: C, #[serde(rename = "restoreTime")] - time: T, + pub time: T, #[serde(rename = "cancelDeletion")] #[serde(default)] - cancel_deletion: Option, + pub cancel_deletion: Option, } -#[derive(serde::Serialize)] +#[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq, Debug)] #[serde(tag = "type")] #[serde(rename_all = "camelCase")] -enum UndeleteResponse { +pub enum UndeleteResponse { Success, NotFound, Error { reason: String }, diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 6b8a236c..51d2d81b 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -20,7 +20,7 @@ redis = ["store/redis"] store = { path = "../crates/store", features = ["test_mode", "enterprise"] } nlp = { path = "../crates/nlp" } directory = { path = "../crates/directory", features = ["test_mode"] } -jmap = { path = "../crates/jmap", features = ["test_mode"] } +jmap = { path = "../crates/jmap", features = ["test_mode", "enterprise"] } jmap_proto = { path = "../crates/jmap-proto" } imap = { path = "../crates/imap", features = ["test_mode"] } imap_proto = { path = "../crates/imap-proto" } diff --git a/tests/src/jmap/enterprise.rs b/tests/src/jmap/enterprise.rs new file mode 100644 index 00000000..488b943c --- /dev/null +++ b/tests/src/jmap/enterprise.rs @@ -0,0 +1,165 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd + * + * SPDX-License-Identifier: LicenseRef-SEL + * + * This file is subject to the Stalwart Enterprise License Agreement (SEL) and + * is NOT open source software. + * + */ + +use std::time::Duration; + +use common::enterprise::{license::LicenseKey, undelete::DeletedBlob, Enterprise}; +use imap_proto::ResponseType; +use jmap::api::management::enterprise::undelete::{UndeleteRequest, UndeleteResponse}; +use store::write::now; + +use crate::imap::{ImapConnection, Type}; + +use super::{delivery::AssertResult, JMAPTest, ManagementApi}; + +pub async fn test(params: &mut JMAPTest) { + // Enable Enterprise + let mut core = params.server.shared_core.load_full().as_ref().clone(); + core.enterprise = Enterprise { + license: LicenseKey { + valid_to: now() + 3600, + valid_from: now() - 3600, + hostname: String::new(), + accounts: 100, + }, + undelete_period: Duration::from_secs(2).into(), + trace_hold_period: Duration::from_secs(1).into(), + trace_store: core.storage.data.clone().into(), + } + .into(); + params.server.shared_core.store(core.into()); + assert!(params.server.shared_core.load().is_enterprise_edition()); + + // Undelete + undelete(params).await; + + // Disable Enterprise + let mut core = params.server.shared_core.load_full().as_ref().clone(); + core.enterprise = None; + params.server.shared_core.store(core.into()); +} + +const RAW_MESSAGE: &str = "From: john@example.com +To: john@example.com +Subject: undelete test + +test +"; + +#[derive(serde::Deserialize, Debug)] +#[allow(dead_code)] +pub(super) struct List { + pub items: Vec, + pub total: usize, +} + +async fn undelete(params: &mut JMAPTest) { + // Create test account + params + .directory + .create_test_user_with_email("jdoe@example.com", "secret", "John Doe") + .await; + + // Authenticate + let mut imap = ImapConnection::connect(b"_x ").await; + imap.send("AUTHENTICATE PLAIN {32+}\r\nAGpkb2VAZXhhbXBsZS5jb20Ac2VjcmV0") + .await; + imap.assert_read(Type::Tagged, ResponseType::Ok).await; + + // Insert test message + imap.send("STATUS INBOX (MESSAGES)").await; + imap.assert_read(Type::Tagged, ResponseType::Ok) + .await + .assert_contains("MESSAGES 0"); + imap.send(&format!("APPEND INBOX {{{}}}", RAW_MESSAGE.len())) + .await; + imap.assert_read(Type::Continuation, ResponseType::Ok).await; + imap.send_untagged(RAW_MESSAGE).await; + imap.assert_read(Type::Tagged, ResponseType::Ok).await; + + // Make sure the message is there + imap.send("STATUS INBOX (MESSAGES)").await; + imap.assert_read(Type::Tagged, ResponseType::Ok) + .await + .assert_contains("MESSAGES 1"); + imap.send("SELECT INBOX").await; + imap.assert_read(Type::Tagged, ResponseType::Ok).await; + + // Fetch message body + imap.send("FETCH 1 BODY[]").await; + imap.assert_read(Type::Tagged, ResponseType::Ok) + .await + .assert_contains("Subject: undelete test"); + + // Delete and expunge message + imap.send("STORE 1 +FLAGS (\\Deleted)").await; + imap.assert_read(Type::Tagged, ResponseType::Ok).await; + imap.send("EXPUNGE").await; + imap.assert_read(Type::Tagged, ResponseType::Ok).await; + + // Logout and reconnect + imap.send("LOGOUT").await; + imap.assert_read(Type::Tagged, ResponseType::Ok).await; + let mut imap = ImapConnection::connect(b"_x ").await; + imap.send("AUTHENTICATE PLAIN {32+}\r\nAGpkb2VAZXhhbXBsZS5jb20Ac2VjcmV0") + .await; + imap.assert_read(Type::Tagged, ResponseType::Ok).await; + + // Make sure the message is gone + imap.send("STATUS INBOX (MESSAGES)").await; + imap.assert_read(Type::Tagged, ResponseType::Ok) + .await + .assert_contains("MESSAGES 0"); + + // Query undelete API + let api = ManagementApi::new(8899, "admin", "secret"); + api.get::("/api/store/purge/account/jdoe@example.com") + .await + .unwrap(); + let deleted = api + .get::>>("/api/store/undelete/jdoe@example.com") + .await + .unwrap() + .unwrap_data() + .items; + assert_eq!(deleted.len(), 1); + let deleted = deleted.into_iter().next().unwrap(); + + // Undelete + let result = api + .post::>( + "/api/store/undelete/jdoe@example.com", + &vec![UndeleteRequest { + hash: deleted.hash, + collection: deleted.collection, + time: deleted.deleted_at, + cancel_deletion: deleted.expires_at.into(), + }], + ) + .await + .unwrap() + .unwrap_data(); + assert_eq!(result, vec![UndeleteResponse::Success]); + + // Make sure the message is back + imap.send("STATUS INBOX (MESSAGES)").await; + imap.assert_read(Type::Tagged, ResponseType::Ok) + .await + .assert_contains("MESSAGES 1"); + + imap.send("SELECT INBOX").await; + imap.assert_read(Type::Tagged, ResponseType::Ok).await; + + // Fetch message body + imap.send("FETCH 1 BODY[]").await; + imap.assert_read(Type::Tagged, ResponseType::Ok) + .await + .assert_contains("Subject: undelete test"); +} diff --git a/tests/src/jmap/mod.rs b/tests/src/jmap/mod.rs index 58b7af65..70b06fd3 100644 --- a/tests/src/jmap/mod.rs +++ b/tests/src/jmap/mod.rs @@ -55,6 +55,7 @@ pub mod email_query_changes; pub mod email_search_snippet; pub mod email_set; pub mod email_submission; +pub mod enterprise; pub mod event_source; pub mod mailbox; pub mod purge; @@ -325,6 +326,7 @@ pub async fn jmap_tests() { crypto::test(&mut params).await; blob::test(&mut params).await; purge::test(&mut params).await; + enterprise::test(&mut params).await; if delete { params.temp_dir.delete(); @@ -752,6 +754,14 @@ impl ManagementApi { }) } + pub async fn get(&self, query: &str) -> Result, String> { + self.request_raw(Method::GET, query, None) + .await + .map(|result| { + serde_json::from_str::>(&result) + .unwrap_or_else(|err| panic!("{err}: {result}")) + }) + } pub async fn request( &self, method: Method,