Skip to content

Commit

Permalink
Undelete tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mdecimus committed Aug 17, 2024
1 parent ec23236 commit 547c671
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 9 deletions.
16 changes: 8 additions & 8 deletions crates/jmap/src/api/management/enterprise/undelete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,21 @@ use crate::{
JMAP,
};

#[derive(serde::Deserialize)]
struct UndeleteRequest<H, C, T> {
hash: H,
collection: C,
#[derive(serde::Deserialize, serde::Serialize)]
pub struct UndeleteRequest<H, C, T> {
pub hash: H,
pub collection: C,
#[serde(rename = "restoreTime")]
time: T,
pub time: T,
#[serde(rename = "cancelDeletion")]
#[serde(default)]
cancel_deletion: Option<T>,
pub cancel_deletion: Option<T>,
}

#[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 },
Expand Down
2 changes: 1 addition & 1 deletion tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
165 changes: 165 additions & 0 deletions tests/src/jmap/enterprise.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <[email protected]>
*
* 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: [email protected]
To: [email protected]
Subject: undelete test
test
";

#[derive(serde::Deserialize, Debug)]
#[allow(dead_code)]
pub(super) struct List<T> {
pub items: Vec<T>,
pub total: usize,
}

async fn undelete(params: &mut JMAPTest) {
// Create test account
params
.directory
.create_test_user_with_email("[email protected]", "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::<serde_json::Value>("/api/store/purge/account/[email protected]")
.await
.unwrap();
let deleted = api
.get::<List<DeletedBlob<String, String, String>>>("/api/store/undelete/[email protected]")
.await
.unwrap()
.unwrap_data()
.items;
assert_eq!(deleted.len(), 1);
let deleted = deleted.into_iter().next().unwrap();

// Undelete
let result = api
.post::<Vec<UndeleteResponse>>(
"/api/store/undelete/[email protected]",
&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");
}
10 changes: 10 additions & 0 deletions tests/src/jmap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -752,6 +754,14 @@ impl ManagementApi {
})
}

pub async fn get<T: DeserializeOwned>(&self, query: &str) -> Result<Response<T>, String> {
self.request_raw(Method::GET, query, None)
.await
.map(|result| {
serde_json::from_str::<Response<T>>(&result)
.unwrap_or_else(|err| panic!("{err}: {result}"))
})
}
pub async fn request<T: DeserializeOwned>(
&self,
method: Method,
Expand Down

0 comments on commit 547c671

Please sign in to comment.