Files
inbuxa-migrate/tests/seed_smoke.rs
T
2026-05-29 18:02:15 +02:00

286 lines
8.8 KiB
Rust

/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*/
mod integration;
mod seeder;
use integration::stalwart::shared as shared_stalwart;
use seeder::jmap::Jmap;
use seeder::seed::SeedStats;
use serde_json::{Value, json};
const CORE: &str = "urn:ietf:params:jmap:core";
const MAIL: &str = "urn:ietf:params:jmap:mail";
const SIEVE: &str = "urn:ietf:params:jmap:sieve";
const CONTACTS: &str = "urn:ietf:params:jmap:contacts";
const CALENDARS: &str = "urn:ietf:params:jmap:calendars";
const FILENODE: &str = "urn:ietf:params:jmap:filenode";
const PRINCIPALS: &str = "urn:ietf:params:jmap:principals";
const DEFAULT_MAILBOXES: usize = 5;
fn list_len(v: &Value) -> usize {
v.get("list")
.and_then(Value::as_array)
.map(|a| a.len())
.unwrap_or(usize::MAX)
}
fn ids_len(v: &Value) -> usize {
v.get("ids")
.and_then(Value::as_array)
.map(|a| a.len())
.unwrap_or(usize::MAX)
}
fn bare() -> SeedStats {
SeedStats {
mailboxes_created: 0,
emails: 0,
contacts: 0,
events: 0,
sieve_active: None,
identity: false,
file_nodes: 0,
address_books: 1,
calendars: 1,
}
}
fn verify_account(label: &str, user: &Jmap, account_id: &str, s: &SeedStats) {
let mailboxes = user
.call(
&[CORE, MAIL],
"Mailbox/get",
account_id,
json!({ "properties": ["id", "role"] }),
)
.expect("Mailbox/get");
assert_eq!(
list_len(&mailboxes),
DEFAULT_MAILBOXES + s.mailboxes_created,
"{label}: mailbox count"
);
let emails = user
.call(
&[CORE, MAIL],
"Email/query",
account_id,
json!({ "calculateTotal": true }),
)
.expect("Email/query");
let email_total = emails
.get("total")
.and_then(Value::as_u64)
.map(|n| n as usize)
.unwrap_or_else(|| ids_len(&emails));
assert_eq!(email_total, s.emails, "{label}: email count");
let scripts = user
.call(
&[CORE, SIEVE],
"SieveScript/get",
account_id,
json!({ "properties": ["id", "name", "isActive"] }),
)
.expect("SieveScript/get");
match s.sieve_active {
Some(active) => {
let list = scripts
.get("list")
.and_then(Value::as_array)
.expect("sieve list");
assert_eq!(list.len(), 2, "{label}: sieve script count");
let primary = list
.iter()
.find(|s| s.get("name").and_then(Value::as_str) == Some("vandelay-test-filter"))
.expect("primary sieve script present");
assert_eq!(
primary.get("isActive").and_then(Value::as_bool),
Some(active),
"{label}: primary sieve isActive"
);
}
None => assert_eq!(list_len(&scripts), 0, "{label}: expected no sieve scripts"),
}
let identities = user
.call(
&[CORE, MAIL],
"Identity/get",
account_id,
json!({ "properties": ["id", "name"] }),
)
.expect("Identity/get");
let custom = identities
.get("list")
.and_then(Value::as_array)
.map(|a| {
a.iter()
.filter(|i| {
i.get("name").and_then(Value::as_str) == Some(seeder::CUSTOM_IDENTITY_NAME)
})
.count()
})
.unwrap_or(0);
assert_eq!(
custom,
usize::from(s.identity),
"{label}: custom identity presence"
);
let files = user
.call(
&[CORE, FILENODE],
"FileNode/query",
account_id,
json!({ "calculateTotal": true }),
)
.expect("FileNode/query");
let file_total = files
.get("total")
.and_then(Value::as_u64)
.map(|n| n as usize)
.unwrap_or_else(|| ids_len(&files));
assert_eq!(file_total, s.file_nodes, "{label}: file node count");
let books = user
.call(
&[CORE, CONTACTS],
"AddressBook/get",
account_id,
json!({ "properties": ["id"] }),
)
.expect("AddressBook/get");
assert_eq!(
list_len(&books),
s.address_books,
"{label}: address book count"
);
let calendars = user
.call(
&[CORE, CALENDARS],
"Calendar/get",
account_id,
json!({ "properties": ["id"] }),
)
.expect("Calendar/get");
assert_eq!(list_len(&calendars), s.calendars, "{label}: calendar count");
let cards = user
.call(
&[CORE, CONTACTS],
"ContactCard/get",
account_id,
json!({ "properties": ["id"] }),
)
.expect("ContactCard/get");
assert_eq!(list_len(&cards), s.contacts, "{label}: contact card count");
let cal_events = user
.call(
&[CORE, CALENDARS],
"CalendarEvent/query",
account_id,
json!({ "calculateTotal": true }),
)
.expect("CalendarEvent/query");
let event_total = cal_events
.get("total")
.and_then(Value::as_u64)
.map(|n| n as usize)
.unwrap_or_else(|| ids_len(&cal_events));
assert_eq!(event_total, s.events, "{label}: calendar event count");
}
#[test]
#[ignore = "requires Docker"]
fn provision_seed_verify_and_teardown() {
let stalwart = shared_stalwart();
let fixture = seeder::provision(stalwart.base_url()).expect("provision");
assert_eq!(fixture.domain, seeder::DOMAIN);
assert!(!fixture.domain_id.is_empty());
assert_eq!(
fixture.accounts.len(),
seeder::SYNC_IN.len() + seeder::SYNC_OUT.len() + 1
);
for lp in seeder::SYNC_IN {
let a = fixture.account(lp).expect("sync-in account");
let stats = a.seeded.as_ref().expect("sync-in seeded stats");
assert!(stats.emails > 0, "{lp}: expected seeded emails");
let user = Jmap::connect(&fixture.base_url, &a.email, &a.password).expect("login sync-in");
assert_eq!(user.account_id, a.account_id);
verify_account(lp, &user, &a.account_id, stats);
}
let baseline = bare();
for lp in seeder::SYNC_OUT {
let a = fixture.account(lp).expect("sync-out account");
assert!(a.seeded.is_none(), "{lp}: targets must not be seeded");
let user = Jmap::connect(&fixture.base_url, &a.email, &a.password).expect("login sync-out");
verify_account(lp, &user, &a.account_id, &baseline);
}
let admin_fx = fixture
.account(seeder::ADMIN_LOCALPART)
.expect("admin account");
assert!(admin_fx.admin_role);
let admin = Jmap::connect(&fixture.base_url, &admin_fx.email, &admin_fx.password)
.expect("login admin account");
let test1 = fixture.account("test1").expect("test1");
let resolved = admin
.request(
&[CORE, PRINCIPALS],
json!([
["Principal/query", { "filter": { "name": test1.email } }, "q"],
["Principal/get", {
"#ids": { "resultOf": "q", "name": "Principal/query", "path": "/ids" },
"properties": ["id", "name", "accounts"]
}, "g"]
]),
)
.expect("Principal discovery");
let principals = resolved
.get("methodResponses")
.and_then(Value::as_array)
.and_then(|r| r.get(1))
.and_then(|r| r.get(1))
.and_then(|g| g.get("list"))
.and_then(Value::as_array)
.cloned()
.unwrap_or_default();
let exact: Vec<&Value> = principals
.iter()
.filter(|p| p.get("name").and_then(Value::as_str) == Some(test1.email.as_str()))
.collect();
assert_eq!(exact.len(), 1, "admin Principal exact-name resolution");
let owner = exact[0]
.get("accounts")
.and_then(Value::as_object)
.and_then(|m| {
m.iter().find_map(|(_, v)| {
v.get("urn:ietf:params:jmap:principals:owner")
.and_then(|o| o.get("accountIdForPrincipal"))
.and_then(Value::as_str)
})
})
.expect("accountIdForPrincipal");
assert_eq!(owner, test1.account_id, "resolved account id matches test1");
seeder::teardown(stalwart.base_url()).expect("teardown");
let (admin_user, admin_password) = &fixture.admin_login;
let gone = seeder::admin::Admin::connect(&fixture.base_url, admin_user, admin_password)
.expect("admin reconnect")
.domain_id(seeder::DOMAIN)
.expect("domain_id query");
assert!(gone.is_none(), "domain should be gone after teardown");
}