/* * SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ use crate::{ directory::internal::TestInternalDirectory, jmap::{ assert_is_empty, delivery::SmtpConnection, emails_purge_tombstoned, jmap_raw_request, mailbox::destroy_all_mailboxes, test_account_login, }, }; use email::mailbox::INBOX_ID; use jmap::blob::upload::DISABLE_UPLOAD_QUOTA; use jmap_client::{ core::set::{SetErrorType, SetObject}, email::EmailBodyPart, }; use jmap_proto::types::{collection::Collection, id::Id}; use smtp::queue::spool::SmtpSpool; use super::JMAPTest; pub async fn test(params: &mut JMAPTest) { println!("Running quota tests..."); let server = params.server.clone(); let mut account_id = Id::from(0u64); let mut other_account_id = Id::from(0u64); for (id, email, password, name) in [ ( &mut other_account_id, "jdoe@example.com", "12345", "John Doe", ), ( &mut account_id, "robert@example.com", "aabbcc", "Robert Foobar", ), ] { *id = Id::from( server .core .storage .data .create_test_user(email, password, name, &[email][..]) .await, ); } server .core .storage .data .set_test_quota("robert@example.com", 1024) .await; server .core .storage .data .add_to_group("robert@example.com", "jdoe@example.com") .await; // Delete temporary blobs from previous tests server.core.storage.data.blob_expire_all().await; // Test temporary blob quota (3 files) DISABLE_UPLOAD_QUOTA.store(false, std::sync::atomic::Ordering::Relaxed); let client = test_account_login("robert@example.com", "aabbcc").await; for i in 0..3 { assert_eq!( client .upload(None, vec![b'A' + i; 1024], None) .await .unwrap() .size(), 1024 ); } match client .upload(None, vec![b'Z'; 1024], None) .await .unwrap_err() { jmap_client::Error::Problem(err) if err.detail().unwrap().contains("quota") => (), other => panic!("Unexpected error: {:?}", other), } server.core.storage.data.blob_expire_all().await; // Test temporary blob quota (50000 bytes) for i in 0..2 { assert_eq!( client .upload(None, vec![b'a' + i; 25000], None) .await .unwrap() .size(), 25000 ); } match client .upload(None, vec![b'z'; 1024], None) .await .unwrap_err() { jmap_client::Error::Problem(err) if err.detail().unwrap().contains("quota") => (), other => panic!("Unexpected error: {:?}", other), } server.core.storage.data.blob_expire_all().await; // Test JMAP Quotas extension let response = jmap_raw_request( r#"[[ "Quota/get", { "accountId": "$$", "ids": null }, "0" ]]"# .replace("$$", &account_id.to_string()), "robert@example.com", "aabbcc", ) .await; assert!(response.contains("\"used\":0"), "{}", response); assert!(response.contains("\"hardLimit\":1024"), "{}", response); assert!(response.contains("\"scope\":\"account\""), "{}", response); assert!( response.contains("\"name\":\"robert@example.com\""), "{}", response ); // Test Email/import quota let inbox_id = Id::new(INBOX_ID as u64).to_string(); let mut message_ids = Vec::new(); for i in 0..2 { message_ids.push( client .email_import( create_message_with_size( "jdoe@example.com", "robert@example.com", &format!("Test {i}"), 512, ), vec![&inbox_id], None::>, None, ) .await .unwrap() .take_id(), ); } assert_over_quota( client .email_import( create_message_with_size("test@example.com", "jdoe@example.com", "Test 3", 100), vec![&inbox_id], None::>, None, ) .await, ); // Test JMAP Quotas extension let response = jmap_raw_request( r#"[[ "Quota/get", { "accountId": "$$", "ids": null }, "0" ]]"# .replace("$$", &account_id.to_string()), "robert@example.com", "aabbcc", ) .await; assert!(response.contains("\"used\":1024"), "{}", response); assert!(response.contains("\"hardLimit\":1024"), "{}", response); // Delete messages and check available quota for message_id in message_ids { client.email_destroy(&message_id).await.unwrap(); } emails_purge_tombstoned(&server).await; assert_eq!( server .get_used_quota(account_id.document_id()) .await .unwrap(), 0 ); // Test Email/set quota let mut message_ids = Vec::new(); for i in 0..2 { let mut request = client.build(); let create_item = request.set_email().create(); create_item .mailbox_ids([&inbox_id]) .subject(format!("Test {i}")) .from(["jdoe@example.com"]) .to(["robert@example.com"]) .body_value("a".to_string(), String::from_utf8(vec![b'A'; 200]).unwrap()) .text_body(EmailBodyPart::new().part_id("a")); let create_id = create_item.create_id().unwrap(); message_ids.push( request .send_set_email() .await .unwrap() .created(&create_id) .unwrap() .take_id(), ); } let mut request = client.build(); let create_item = request.set_email().create(); create_item .mailbox_ids([&inbox_id]) .subject("Test 3") .from(["jdoe@example.com"]) .to(["robert@example.com"]) .body_value("a".to_string(), String::from_utf8(vec![b'A'; 400]).unwrap()) .text_body(EmailBodyPart::new().part_id("a")); let create_id = create_item.create_id().unwrap(); assert_over_quota(request.send_set_email().await.unwrap().created(&create_id)); // Recalculate quota let prev_quota = server .get_used_quota(account_id.document_id()) .await .unwrap(); server .recalculate_quota(account_id.document_id()) .await .unwrap(); assert_eq!( server .get_used_quota(account_id.document_id()) .await .unwrap(), prev_quota ); // Delete messages and check available quota for message_id in message_ids { client.email_destroy(&message_id).await.unwrap(); } emails_purge_tombstoned(&server).await; assert_eq!( server .get_used_quota(account_id.document_id()) .await .unwrap(), 0 ); // Test Email/copy quota let other_client = test_account_login("jdoe@example.com", "12345").await; let mut other_message_ids = Vec::new(); let mut message_ids = Vec::new(); for i in 0..3 { other_message_ids.push( other_client .email_import( create_message_with_size( "jane@example.com", "jdoe@example.com", &format!("Other Test {i}"), 512, ), vec![&inbox_id], None::>, None, ) .await .unwrap() .take_id(), ); } for id in other_message_ids.iter().take(2) { message_ids.push( client .email_copy( other_account_id.to_string(), id, vec![&inbox_id], None::>, None, ) .await .unwrap() .take_id(), ); } assert_over_quota( client .email_copy( other_account_id.to_string(), &other_message_ids[2], vec![&inbox_id], None::>, None, ) .await, ); // Delete messages and check available quota for message_id in message_ids { client.email_destroy(&message_id).await.unwrap(); } emails_purge_tombstoned(&server).await; assert_eq!( server .get_used_quota(account_id.document_id()) .await .unwrap(), 0 ); // Test delivery quota let mut lmtp = SmtpConnection::connect().await; for i in 0..2 { lmtp.ingest( "jane@example.com", &["robert@example.com"], &String::from_utf8(create_message_with_size( "jane@example.com", "robert@example.com", &format!("Ingest test {i}"), 513, )) .unwrap(), ) .await; } let quota = server .get_used_quota(account_id.document_id()) .await .unwrap(); assert!(quota > 0 && quota <= 1024, "Quota is {}", quota); assert_eq!( server .get_document_ids(account_id.document_id(), Collection::Email) .await .unwrap() .unwrap() .len(), 1, ); DISABLE_UPLOAD_QUOTA.store(true, std::sync::atomic::Ordering::Relaxed); // Remove test data for account_id in [&account_id, &other_account_id] { params.client.set_default_account_id(account_id.to_string()); destroy_all_mailboxes(params).await; } for event in server.next_event().await { server .read_message(event.queue_id) .await .unwrap() .remove(&server, event.due) .await; } assert_is_empty(server).await; } fn assert_over_quota(result: Result) { match result { Ok(result) => panic!("Expected error, got {:?}", result), Err(jmap_client::Error::Set(err)) if err.error() == &SetErrorType::OverQuota => (), Err(err) => panic!("Expected OverQuota SetError, got {:?}", err), } } fn create_message_with_size(from: &str, to: &str, subject: &str, size: usize) -> Vec { let mut message = format!( "From: {}\r\nTo: {}\r\nSubject: {}\r\n\r\n", from, to, subject ); for _ in 0..size - message.len() { message.push('A'); } message.into_bytes() }