Record queue lifecycle release test
This commit is contained in:
Executable
+215
@@ -0,0 +1,215 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Exercise Queue administration, retry preservation, expiry, and DSN creation
|
||||
# against a running local Bongo installation.
|
||||
|
||||
set -eu
|
||||
|
||||
user=${1:-stqqueue}
|
||||
domain=${2:-bongo.test}
|
||||
queue_tool=${BONGO_QUEUE_TOOL:-/usr/bin/bongo-queuetool}
|
||||
store_tool=${BONGO_STORE_TOOL:-/usr/bin/bongo-storetool}
|
||||
service=${BONGO_SERVICE:-bongo.service}
|
||||
run_as=${BONGO_TEST_RUN_AS:-bongo}
|
||||
as_root=${BONGO_TEST_AS_ROOT:-sudo -n}
|
||||
|
||||
if [ "$(id -un)" = "$run_as" ]; then
|
||||
as_bongo=
|
||||
else
|
||||
as_bongo="sudo -n -u $run_as"
|
||||
fi
|
||||
|
||||
work=$(mktemp -d "${TMPDIR:-/tmp}/bongo-stq05.XXXXXX")
|
||||
chmod 0755 "$work"
|
||||
managed_id=
|
||||
retry_id=
|
||||
expiry_id=
|
||||
|
||||
cleanup()
|
||||
{
|
||||
status=$?
|
||||
for queue_id in "$managed_id" "$retry_id" "$expiry_id"; do
|
||||
if [ -n "$queue_id" ]; then
|
||||
$as_bongo "$queue_tool" delete "$queue_id" \
|
||||
>/dev/null 2>&1 || true
|
||||
fi
|
||||
done
|
||||
rm -rf "$work"
|
||||
exit "$status"
|
||||
}
|
||||
trap cleanup EXIT HUP INT TERM
|
||||
|
||||
wait_queue()
|
||||
{
|
||||
attempts=30
|
||||
while [ "$attempts" -gt 0 ]; do
|
||||
if $as_bongo "$queue_tool" list >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
attempts=$((attempts - 1))
|
||||
sleep 1
|
||||
done
|
||||
echo "STQ-05: Queue did not become ready" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
queue_contains()
|
||||
{
|
||||
$as_bongo "$queue_tool" list |
|
||||
grep -F "$1 " >/dev/null
|
||||
}
|
||||
|
||||
wait_message()
|
||||
{
|
||||
queue_id=$1
|
||||
output=$2
|
||||
attempts=30
|
||||
while [ "$attempts" -gt 0 ]; do
|
||||
if $as_bongo "$queue_tool" message "$queue_id" \
|
||||
>"$output" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
attempts=$((attempts - 1))
|
||||
sleep 1
|
||||
done
|
||||
echo "STQ-05: queue entry $queue_id did not become readable" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
printf '%s\r\n' \
|
||||
"From: STQ-05 <stq05@$domain>" \
|
||||
"To: $user@$domain" \
|
||||
'Subject: STQ-05 queue lifecycle' \
|
||||
'Date: Thu, 23 Jul 2026 12:34:56 +0200' \
|
||||
'Message-ID: <stq05-lifecycle@bongo.test>' \
|
||||
'' \
|
||||
'Queue listing, hold, retry, expiry, bounce, and deletion.' \
|
||||
>"$work/message.eml"
|
||||
chmod 0644 "$work/message.eml"
|
||||
|
||||
# A normal committed entry must be listable, movable to hold, preserved across
|
||||
# restart, releasable to an explicit target, and deletable.
|
||||
managed_id=$($as_bongo "$queue_tool" hold-local "$work/message.eml" 9)
|
||||
case "$managed_id" in
|
||||
009-*) ;;
|
||||
*)
|
||||
echo "STQ-05: unexpected management fixture ID: $managed_id" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
queue_contains "$managed_id"
|
||||
|
||||
held_id=$($as_bongo "$queue_tool" hold "$managed_id")
|
||||
managed_id=$held_id
|
||||
case "$managed_id" in
|
||||
999-*) ;;
|
||||
*)
|
||||
echo "STQ-05: hold did not return a queue-999 ID: $managed_id" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
queue_contains "$managed_id"
|
||||
|
||||
$as_root /usr/bin/systemctl restart "$service"
|
||||
wait_queue
|
||||
queue_contains "$managed_id"
|
||||
|
||||
released_id=$($as_bongo "$queue_tool" release "$managed_id" 9)
|
||||
managed_id=$released_id
|
||||
case "$managed_id" in
|
||||
009-*) ;;
|
||||
*)
|
||||
echo "STQ-05: release did not return a queue-9 ID: $managed_id" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
queue_contains "$managed_id"
|
||||
$as_bongo "$queue_tool" delete "$managed_id"
|
||||
if queue_contains "$managed_id"; then
|
||||
echo "STQ-05: deleted entry remains in the listing" >&2
|
||||
exit 1
|
||||
fi
|
||||
managed_id=
|
||||
|
||||
# A failed remote attempt must remain queued without changing the message.
|
||||
retry_id=$($as_bongo "$queue_tool" enqueue-remote \
|
||||
"$work/message.eml" "$user@$domain" nobody@retry.invalid 7)
|
||||
case "$retry_id" in
|
||||
007-*) ;;
|
||||
*)
|
||||
echo "STQ-05: unexpected retry fixture ID: $retry_id" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
wait_message "$retry_id" "$work/retry-before.eml"
|
||||
retry_before=$(sha256sum "$work/retry-before.eml" | awk '{ print $1 }')
|
||||
$as_bongo "$queue_tool" flush
|
||||
wait_message "$retry_id" "$work/retry-after.eml"
|
||||
retry_after=$(sha256sum "$work/retry-after.eml" | awk '{ print $1 }')
|
||||
if [ "$retry_before" != "$retry_after" ]; then
|
||||
echo "STQ-05: retry changed the queued message" >&2
|
||||
exit 1
|
||||
fi
|
||||
$as_bongo "$queue_tool" delete "$retry_id"
|
||||
retry_id=
|
||||
|
||||
# Deterministic expiry starts in hold so the normal remote worker cannot race
|
||||
# the diagnostic command. Exactly one DSN must appear in the isolated inbox.
|
||||
$as_bongo "$store_tool" -u "$user" \
|
||||
document-list /mail/INBOX >"$work/inbox-before"
|
||||
expiry_id=$($as_bongo "$queue_tool" enqueue-remote \
|
||||
"$work/message.eml" "$user@$domain" nobody@retry.invalid 999)
|
||||
case "$expiry_id" in
|
||||
999-*) ;;
|
||||
*)
|
||||
echo "STQ-05: unexpected expiry fixture ID: $expiry_id" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
$as_bongo "$queue_tool" expire "$expiry_id" >/dev/null
|
||||
expiry_id=
|
||||
|
||||
attempts=30
|
||||
while [ "$attempts" -gt 0 ]; do
|
||||
$as_bongo "$store_tool" -u "$user" \
|
||||
document-list /mail/INBOX >"$work/inbox-after"
|
||||
awk '{ print $1 }' "$work/inbox-before" | sort -u \
|
||||
>"$work/inbox-before.ids"
|
||||
awk '{ print $1 }' "$work/inbox-after" | sort -u \
|
||||
>"$work/inbox-after.ids"
|
||||
comm -13 "$work/inbox-before.ids" "$work/inbox-after.ids" \
|
||||
>"$work/new.ids"
|
||||
if [ "$(wc -l <"$work/new.ids")" -eq 1 ]; then
|
||||
break
|
||||
fi
|
||||
attempts=$((attempts - 1))
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if [ "$(wc -l <"$work/new.ids")" -ne 1 ]; then
|
||||
echo "STQ-05: expiry did not create exactly one DSN" >&2
|
||||
diff -u "$work/inbox-before" "$work/inbox-after" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
document=$(sed -n '1p' "$work/new.ids")
|
||||
document_path=$(awk -v id="$document" '$1 == id { print $3 }' \
|
||||
"$work/inbox-after")
|
||||
$as_bongo "$store_tool" -u "$user" \
|
||||
document-get "$document_path" - >"$work/dsn.eml"
|
||||
|
||||
for expected in \
|
||||
'Subject: Returned mail: Delivery time exceeded' \
|
||||
'Original-recipient: rfc822;nobody@retry.invalid' \
|
||||
'Final-recipient: rfc822;nobody@retry.invalid' \
|
||||
'Action: failed' \
|
||||
'Status: 5.4.7'
|
||||
do
|
||||
if ! grep -F "$expected" "$work/dsn.eml" >/dev/null; then
|
||||
echo "STQ-05: generated DSN lacks '$expected'" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
printf 'STQ-05 PASS management=hold/restart/release/delete retry-sha256=%s dsn=%s status=5.4.7\n' \
|
||||
"$retry_after" "$document"
|
||||
@@ -110,7 +110,7 @@ inputs and outputs is absent from the ZIP.
|
||||
| STQ-02 | Local message survives Queue-to-Store delivery byte-for-byte | [PASS](test-evidence/0.7-r1.md#stq-02) | | |
|
||||
| STQ-03 | Queue free-space calculation is correct on 32/64-bit and reserves space | [PASS](test-evidence/0.7-r1.md#stq-03) | | |
|
||||
| STQ-04 | Full-disk and unreadable-spool failures defer safely without mail loss | [PASS](test-evidence/0.7-r1.md#stq-04) | | |
|
||||
| STQ-05 | Retry, expiry, bounce, listing, hold/release, and deletion | | | |
|
||||
| STQ-05 | Retry, expiry, bounce, listing, hold/release, and deletion | [PASS](test-evidence/0.7-r1.md#stq-05) | | |
|
||||
| STQ-06 | Concurrent delivery/read/move/delete/restart preserves integrity | | | |
|
||||
| STQ-07 | Backup/export and restore reproduce mail, metadata, contacts, calendars | | | |
|
||||
| STQ-08 | Invalid commands, oversized lines, and unauthorised access fail safely | | | |
|
||||
|
||||
@@ -1523,3 +1523,59 @@ Bongo finished active/running as manager PID 1225547 with systemd result
|
||||
`success`. The temporary fixture, fill allocation, and failed submission were
|
||||
removed; no Store object, external mail, SMTP relay, collector source, or
|
||||
LMTP endpoint was used.
|
||||
|
||||
## STQ-05
|
||||
|
||||
Result: **PASS**
|
||||
|
||||
Commit `efc5173d` added deterministic Queue listing, hold/release controls,
|
||||
the reserved Queue 999 class, and recovery of held entries across manager
|
||||
restarts. It also fixed `QMOVE` to use its parsed destination. Commit
|
||||
`69ab7094` added complete remote-delivery fixtures and deterministic expiry
|
||||
diagnostics. Commit `1441ab1a` fixed the expired-recipient envelope boundary
|
||||
so the generated delivery-status recipient and enhanced status code are
|
||||
parsed correctly instead of falling back to a generic internal error. Unit
|
||||
tests cover the Queue protocol clients, movement, listing, recovery, expiry,
|
||||
and exact bounce-recipient formatting. The complete rebuilt suite passed 86
|
||||
of 86 tests in 11.26 seconds.
|
||||
|
||||
The commits were pushed to `master`, installed as
|
||||
`mail-mta/bongo-9999::bongo` with GCC 15.3.0, and exercised on 2026-07-23:
|
||||
|
||||
```sh
|
||||
./contrib/testing/queue-lifecycle-check.sh
|
||||
```
|
||||
|
||||
The repeatable live test first listed a Queue 009 entry, moved it to hold,
|
||||
restarted Bongo, and proved that Queue 999 recovery retained it. It released
|
||||
the message to Queue 009, listed it there, deleted it, and confirmed that it
|
||||
was gone. A remote-delivery fixture for `nobody@retry.invalid` remained in
|
||||
Queue 007 after a flush with identical message bytes:
|
||||
|
||||
```text
|
||||
retry SHA-256 39b785579f9bdc5914216aa84f83cd19ca6014eef95d49e388b1d52f209f26a6
|
||||
```
|
||||
|
||||
A separate held remote fixture was then expired without racing the normal
|
||||
remote worker. It left the Queue and created exactly one new Store object,
|
||||
`000000000000001b`, in the isolated `stqqueue` inbox. The multipart delivery
|
||||
status notification contained:
|
||||
|
||||
```text
|
||||
Subject: Returned mail: Delivery time exceeded
|
||||
Original-recipient: rfc822;nobody@retry.invalid
|
||||
Final-recipient: rfc822;nobody@retry.invalid
|
||||
Action: failed
|
||||
Status: 5.4.7
|
||||
```
|
||||
|
||||
The script reported:
|
||||
|
||||
```text
|
||||
STQ-05 PASS management=hold/restart/release/delete retry-sha256=39b785579f9bdc5914216aa84f83cd19ca6014eef95d49e388b1d52f209f26a6 dsn=000000000000001b status=5.4.7
|
||||
```
|
||||
|
||||
All temporary Queue entries were removed. The intentionally retained DSN is
|
||||
local release evidence; no external mail, SMTP relay, collector source, or
|
||||
LMTP endpoint was used. Bongo finished active/running as manager PID 1253396
|
||||
with systemd result `success`.
|
||||
|
||||
Reference in New Issue
Block a user