test(integration): composer From offers shared/group identities (#569)

Provision a Stalwart group (team@example.org) with carol as a member before her
first login, and assert the composer's From selector offers the group address.
This confirms the group-membership scenario of #569 already works out of the
box: Stalwart returns the group's send-as identity on the member's own account,
so the app's normal single-account identity load surfaces it (identities.length
> 1 -> the From <select> renders with team@).

- stalwart: create the `team` Group in plan-accounts and add carol via
  User.memberGroupIds in the entrypoint (id resolved after apply, like
  DOMAIN_ID). carol, not alice/bob, so the sync specs stay unshared.
- helpers: GROUP config, openComposer/composerFromOptions, and JmapClient
  accounts + sharedAccountNames (Identity/get needs the submission capability).
- composer: add data-testid="composer-from" to the From <select> and its
  single-identity <span> fallback.

Ref: https://github.com/bulwarkmail/webmail/issues/569
This commit is contained in:
Stefan Hildebrandt
2026-07-16 17:59:52 +02:00
committed by Linus Rath
parent 8d8bc7cb13
commit 578339c400
7 changed files with 140 additions and 2 deletions
+25
View File
@@ -108,6 +108,31 @@ if [ ! -f "${MARKER}" ]; then
"${STALWART_CLI}" apply --file "${PLAN_ACCOUNTS}" --quiet
rm -f "${PLAN_ACCOUNTS}"
# Make 'carol' a member of the 'team' group *before her first login*, so she
# already has the shared group mailbox (its folders show under "Shared") and
# the team@ send-as identity. This provisions the issue #569 scenario: the
# composer's From dropdown should then offer the group address. Membership is
# a set keyed by the group's server-assigned account id (see the User schema's
# memberGroupIds), so the ids are resolved here, mirroring DOMAIN_ID above.
# carol (not alice/bob) is used so the single-/multi-account sync specs, which
# drive alice and bob, keep a clean unshared environment.
q_account_id() {
STALWART_URL=${LOCAL_URL} STALWART_USER=admin STALWART_PASSWORD=${ADMIN_PASS} \
"${STALWART_CLI}" query Account --json 2>/dev/null \
| grep "\"emailAddress\":\"$1\"" \
| sed -E 's/.*"id":"([^"]+)".*/\1/'
}
CAROL_ID=$(q_account_id "carol@example.org")
TEAM_ID=$(q_account_id "team@example.org")
if [ -n "${CAROL_ID}" ] && [ -n "${TEAM_ID}" ]; then
log "Adding carol (${CAROL_ID}) to the team group (${TEAM_ID}) [issue #569]"
STALWART_URL=${LOCAL_URL} STALWART_USER=admin STALWART_PASSWORD=${ADMIN_PASS} \
"${STALWART_CLI}" update Account "${CAROL_ID}" \
--field "memberGroupIds={\"${TEAM_ID}\":true}" >/dev/null
else
log "WARNING: could not resolve account ids (carol='${CAROL_ID}' team='${TEAM_ID}'); skipping group membership"
fi
# Default inbound throttles (sender->recipient + sender-IP) otherwise trip
# 452 4.4.5 when a test blasts many messages. Stalwart re-seeds the defaults
# on every start when absent, so deleting is useless; disable them instead,
@@ -1,6 +1,7 @@
{"@type":"create","object":"Account","value":{"alice":{"@type":"User","name":"alice","domainId":"${DOMAIN_ID}","description":"Integration test mailbox alice","credentials":{"0":{"@type":"Password","secret":"${TEST_ACCOUNT_PASSWORD}"}}}}}
{"@type":"create","object":"Account","value":{"bob":{"@type":"User","name":"bob","domainId":"${DOMAIN_ID}","description":"Integration test mailbox bob","credentials":{"0":{"@type":"Password","secret":"${TEST_ACCOUNT_PASSWORD}"}}}}}
{"@type":"create","object":"Account","value":{"carol":{"@type":"User","name":"carol","domainId":"${DOMAIN_ID}","description":"Integration test mailbox carol","credentials":{"0":{"@type":"Password","secret":"${TEST_ACCOUNT_PASSWORD}"}}}}}
{"@type":"create","object":"Account","value":{"team":{"@type":"Group","name":"team","domainId":"${DOMAIN_ID}","description":"Team shared mailbox"}}}
{"@type":"create","object":"NetworkListener","value":{"submission":{"name":"submission","protocol":"smtp","bind":{"[::]:587":true},"tlsImplicit":false,"useTls":false,"socketReuseAddress":true,"socketNoDelay":true}}}
{"@type":"create","object":"NetworkListener","value":{"imap":{"name":"imap","protocol":"imap","bind":{"[::]:143":true},"tlsImplicit":false,"useTls":false,"socketReuseAddress":true,"socketNoDelay":true}}}
{"@type":"update","object":"MtaStageAuth","value":{"saslMechanisms":{"match":{"0":{"if":"local_port != 25","then":"[plain, login, oauthbearer, xoauth2]"}},"else":"false"}}}