Sharkey 2025.4.7 is the Misskey peer under another name, image, database, Redis db and home. Its scenario is Misskey's plus edits both ways and the FEP-e232 quote tag: 40 checks pass. Akkoma 3.20.1 publishes no image, so tools/pasture/images/akkoma installs its OTP release, pinned by checksum, and appends Caddy's CA to the CA bundles the release ships. Its scenario has 44 checks, which pass three runs in a row: - follows, posts, CW, followers-only posts and the published time; - replies, likes, boosts and their undos; - EmojiReact both ways and withdrawn; - DMs, polls, quotes, media, edits with history and deletes, both ways; - unfollow, block, unblock and statistics. The two bugs, both fixed: - Followers-only posts arrived as DMs on Pleroma and Akkoma. They call a post private only if an address in `to` contains "/followers" or its cc is not empty. Ours is /groupies, and a post mentioning nobody had an empty cc. The followers collection is now named in cc as well, which tells nobody anything new. - Akkoma's open polls showed as ended and refused votes. Akkoma carries an open poll's end in `closed` and sends no `endTime`. A `closed` in the future is now read as the end. Neither of these is a PrivaPub bug: - Akkoma's Linkify never takes @user@host.test for a mention, so its DM addresses alice with to[]. - Its API never reports a remote blocker as blocked_by, so the block is read from its database. Also in this commit: - The rate limits are configuration (RateLimits: AccountsPerMinute, InboxBurst, InboxPerTenSeconds), with the old values as defaults. The pasture raises the accounts limit, which back-to-back runs from one address had hit. - A new Lemmy never sends what it queued for a server before it started that server's send worker, so the scenario waits for the worker before its first follow. All six peers in one clean pass: GoToSocial 54 (+1 expected), Mastodon 49 (+2), Misskey 35, Sharkey 40, Akkoma 44, and Lemmy 20 (+3) once the worker wait was added. 642 tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ELjqpznMFMNrJoJUj6K5p2
115 lines
9.7 KiB
Bash
115 lines
9.7 KiB
Bash
# Lemmy 1.0: communities both ways (FEP-1b12), threads with titles, comments both ways, votes up and down both ways,
|
|
# private messages both ways, a moderator's removal (P7), statistics. Lemmy is driven through its v4 API.
|
|
LM=https://lemmy.test:6443
|
|
LT=$(cat "$here/.state/lemmy.token" 2>/dev/null)
|
|
# lm <method> <path> [json]: a Lemmy v4 API call as lemmyuser
|
|
lm() {
|
|
local method=$1 path=$2 body=${3:-}
|
|
if [ -n "$body" ]; then
|
|
site lemmy.test -s -X "$method" "$LM/api/v4/$path" -H "Authorization: Bearer $LT" -H 'Content-Type: application/json' -d "$body"
|
|
else
|
|
site lemmy.test -s -X "$method" "$LM/api/v4/$path" -H "Authorization: Bearer $LT"
|
|
fi
|
|
}
|
|
# the posts Lemmy holds in a community, and the one whose ap_id is given
|
|
lm_posts() { lm GET "post/list?community_id=$1&sort=new&limit=50" | j "print(json.dumps([p['post'] for p in (d.get('items') or d.get('posts') or [])]))"; }
|
|
# 1 once Lemmy runs a send worker for privapub.test
|
|
lm_worker() { podman exec pasture-postgres psql -U pasture -d lemmy -tAc "select count(*) from federation_queue_state q join instance i on i.id=q.instance_id where i.domain='privapub.test'"; }
|
|
lm_post_by_ap() { lm_posts "$1" | j "print(json.dumps(next((p for p in d if p['ap_id']=='$2'), None)))"; }
|
|
|
|
echo "lemmy"
|
|
[ -n "$LT" ] && ok "Lemmy token for lemmyuser" || { ko "Lemmy token"; return 1; }
|
|
LAT=$(privapub_token alice_lemmy)
|
|
LAH="Authorization: Bearer $LAT"
|
|
[ -n "$LAT" ] && ok "PrivaPub token for alice_lemmy" || { ko "PrivaPub token for alice_lemmy"; return 1; }
|
|
jwt=$(privapub_root)
|
|
alice_id=$(curl -s -H "$LAH" "$P/api/v1/accounts/verify_credentials" | j "print(d['id'])")
|
|
dogs="dogs$(date +%s)"
|
|
cats="cats$(date +%s)"
|
|
|
|
echo " communities"
|
|
dogs_gid=$(curl -s -X POST $P/clientapi/group/insert -H 'Content-Type: application/json' -H "Authorization: Bearer $jwt" \
|
|
-d "{\"avatarId\":\"$alice_id\",\"userName\":\"$dogs\",\"name\":\"Pasture dogs\",\"description\":\"a PrivaPub community\",\"isCommunity\":true}" | j "print(d['id'])")
|
|
dogs_on_lm=$(lm GET "resolve_object?q=!$dogs@privapub.test" | j "print(d['community']['id'])")
|
|
[ -n "$dogs_on_lm" ] && ok "Lemmy resolves a PrivaPub community" || ko "Lemmy cannot resolve the PrivaPub community"
|
|
# Lemmy 1.0 starts its send worker for a newly seen server at the newest activity and never sends anything queued for
|
|
# that server before then, so on a clean pasture the first follow waits for the worker (up to a minute)
|
|
until_true 45 '[ "$(lm_worker)" = "1" ]' || true
|
|
lm POST community/follow "{\"community_id\":$dogs_on_lm,\"follow\":true}" >/dev/null
|
|
until_true 30 '[ "$(lm GET "community?id=$dogs_on_lm" | j "print(((d[\"community_view\"].get(\"community_actions\") or {}).get(\"follow_state\") or \"\").lower())")" = "accepted" ]' \
|
|
&& ok "lemmyuser follows the PrivaPub community (Accept arrived)" || ko "Lemmy's community follow not accepted"
|
|
cats_on_lm=$(lm POST community "{\"name\":\"$cats\",\"title\":\"Pasture cats\"}" | j "print(d['community_view']['community']['id'])")
|
|
cats_on_p=$(curl -s -H "$LAH" "$P/api/v2/search?q=@$cats@lemmy.test&resolve=true&type=accounts" | j "print(d['accounts'][0]['id'])")
|
|
[ -n "$cats_on_p" ] && ok "PrivaPub resolves a Lemmy community" || ko "PrivaPub cannot resolve the Lemmy community"
|
|
curl -s -o /dev/null -X POST -H "$LAH" "$P/api/v1/accounts/$cats_on_p/follow"
|
|
until_true 30 '[ "$(curl -s -H "$LAH" "$P/api/v1/accounts/relationships?id[]=$cats_on_p" | j "print(d[0][\"following\"])")" = "True" ]' && ok "alice follows the Lemmy community (Accept arrived)" || ko "PrivaPub's community follow not accepted"
|
|
|
|
echo " threads"
|
|
lm_thread=$(lm POST post "{\"name\":\"A Lemmy thread\",\"body\":\"posted from Lemmy into PrivaPub\",\"community_id\":$dogs_on_lm}" | j "print(d['post_view']['post']['ap_id'])")
|
|
dogs_on_p=$(curl -s -H "$LAH" "$P/api/v1/accounts/lookup?acct=$dogs" | j "print(d['id'])")
|
|
until_true 30 '[ "$(curl -s -H "$LAH" "$P/api/v1/accounts/$dogs_on_p/statuses" | j "print(any(s[\"uri\"]==\"$lm_thread\" and (s.get(\"privapub\") or {}).get(\"title\")==\"A Lemmy thread\" for s in d))")" = "True" ]' \
|
|
&& ok "a Lemmy thread in the PrivaPub community arrives with its title" || ko "Lemmy's thread missing or untitled on PrivaPub"
|
|
curl -s -o /dev/null -X POST $P/clientapi/post/insert -H 'Content-Type: application/json' -H "Authorization: Bearer $jwt" \
|
|
-d "{\"avatarId\":\"$alice_id\",\"title\":\"A PrivaPub community thread\",\"text\":\"posted into our own community\",\"groupId\":\"$dogs_gid\"}"
|
|
dogs_post=$(podman exec pasture-mongo mongosh --quiet PrivaPub --eval 'print(db.Post.findOne({Text:/posted into our own community/}, {}, {sort:{_id:-1}}).ObjectURI)')
|
|
until_true 30 '[ "$(lm_post_by_ap "$dogs_on_lm" "$dogs_post" | j "print(d and d[\"name\"])")" = "A PrivaPub community thread" ]' \
|
|
&& ok "a titled post in the PrivaPub community reaches Lemmy through its announce" || ko "PrivaPub community post missing on Lemmy"
|
|
# Lemmy 1.0 holds its user's thread in a remote community as pending until the community announces it back, and clears
|
|
# the flag even though it then answers that Announce 400 ("Object is not remote"): the echo is what publishes it.
|
|
until_true 30 '[ "$(lm_post_by_ap "$dogs_on_lm" "$lm_thread" | j "print(d and d[\"federation_pending\"])")" = "False" ]' \
|
|
&& ok "the PrivaPub community's announce back publishes Lemmy's thread there" || ko "Lemmy's thread stays federation_pending"
|
|
lm_cats_thread=$(lm POST post "{\"name\":\"Cats only\",\"body\":\"a Lemmy community post\",\"community_id\":$cats_on_lm}" | j "print(d['post_view']['post']['ap_id'])")
|
|
until_true 30 'curl -s -H "$LAH" "$P/api/v1/timelines/home" | j "print(any(s[\"uri\"]==\"$lm_cats_thread\" or (s.get(\"reblog\") or {}).get(\"uri\")==\"$lm_cats_thread\" for s in d))" | grep -q True' \
|
|
&& ok "the Lemmy community's announce brings its thread to alice's home" || ko "Lemmy community thread missing from alice's home"
|
|
p_thread=$(curl -s -X POST -H "$LAH" $P/api/v1/statuses --data-urlencode "status=A PrivaPub thread
|
|
@$cats@lemmy.test posted from PrivaPub" -d 'visibility=public')
|
|
p_thread_id=$(echo "$p_thread" | j "print(d['id'])"); p_thread_uri=$(echo "$p_thread" | j "print(d['uri'])")
|
|
until_true 30 '[ "$(lm_post_by_ap "$cats_on_lm" "$p_thread_uri")" != "null" ]' && ok "alice's thread lands in the Lemmy community" || ko "PrivaPub thread missing from the Lemmy community"
|
|
p_thread_on_lm=$(lm_post_by_ap "$cats_on_lm" "$p_thread_uri" | j "print(d['id'])")
|
|
|
|
echo " comments"
|
|
lm POST comment "{\"post_id\":$p_thread_on_lm,\"content\":\"a Lemmy comment on PrivaPub\"}" >/dev/null
|
|
until_true 30 '[ "$(curl -s -H "$LAH" "$P/api/v1/statuses/$p_thread_id/context" | j "print(any(\"a Lemmy comment on PrivaPub\" in s[\"content\"] for s in d[\"descendants\"]))")" = "True" ]' \
|
|
&& ok "Lemmy's comment threads under alice's post" || ko "Lemmy's comment missing on PrivaPub"
|
|
lm_cats_on_p=$(curl -s -H "$LAH" "$P/api/v1/timelines/home" | j "print(next((s.get('reblog') or s)['id'] for s in d if (s.get('reblog') or s)['uri']=='$lm_cats_thread'))")
|
|
curl -s -o /dev/null -X POST -H "$LAH" $P/api/v1/statuses -d "status=a PrivaPub comment on Lemmy&in_reply_to_id=$lm_cats_on_p&visibility=public"
|
|
lm_cats_id=$(lm GET "resolve_object?q=$lm_cats_thread" | j "print(d['post']['id'])")
|
|
until_true 30 '[ "$(lm GET "comment/list?post_id=$lm_cats_id" | j "print(any(\"a PrivaPub comment on Lemmy\" in c[\"comment\"][\"content\"] for c in (d.get(\"items\") or d.get(\"comments\") or [])))")" = "True" ]' \
|
|
&& ok "alice's reply becomes a comment on Lemmy" || ko "PrivaPub reply missing as a Lemmy comment"
|
|
|
|
echo " votes"
|
|
# Lemmy sends a vote only to the community, which relays it inside an Announce: P7 trusts those for the community's own posts.
|
|
lm POST post/like "{\"post_id\":$p_thread_on_lm,\"is_upvote\":true}" >/dev/null
|
|
if until_true 15 '[ "$(curl -s -H "$LAH" "$P/api/v1/statuses/$p_thread_id" | j "print(d[\"favourites_count\"])")" = "1" ]'; then
|
|
ok "Lemmy's upvote counts as a like on PrivaPub"
|
|
else
|
|
xf "Lemmy's upvote counts as a like on PrivaPub (votes relayed by a community are P7)"
|
|
fi
|
|
lm POST post/like "{\"post_id\":$p_thread_on_lm,\"is_upvote\":false}" >/dev/null
|
|
if until_true 15 '[ "$(curl -s -H "$LAH" "$P/api/v1/statuses/$p_thread_id" | j "print(d[\"favourites_count\"], ((d.get(\"privapub\") or {}).get(\"votes\") or {}).get(\"down\"))")" = "0 1" ]'; then
|
|
ok "Lemmy's change to a downvote reaches PrivaPub"
|
|
else
|
|
xf "Lemmy's change to a downvote reaches PrivaPub (votes relayed by a community are P7)"
|
|
fi
|
|
curl -s -o /dev/null -X POST -H "$LAH" "$P/api/v1/statuses/$lm_cats_on_p/favourite"
|
|
until_true 30 '[ "$(lm GET "post?id=$lm_cats_id" | j "print(d[\"post_view\"][\"post\"].get(\"upvotes\"))")" = "2" ]' && ok "alice's like is an upvote on Lemmy" || ko "like not counted as an upvote on Lemmy"
|
|
|
|
echo " private messages"
|
|
alice_on_lm=$(lm GET "resolve_object?q=@alice_lemmy@privapub.test" | j "print(d['person']['id'])")
|
|
lm POST private_message "{\"content\":\"a secret from Lemmy\",\"recipient_id\":$alice_on_lm}" >/dev/null
|
|
until_true 30 'curl -s -H "$LAH" "$P/api/v1/conversations" | grep -q "a secret from Lemmy"' && ok "Lemmy's private message arrives as a DM" || ko "Lemmy's private message missing on PrivaPub"
|
|
curl -s -o /dev/null -X POST -H "$LAH" $P/api/v1/statuses -d 'status=@lemmyuser@lemmy.test a secret from PrivaPub&visibility=direct'
|
|
until_true 30 'lm GET "account/notification/list?limit=50" | grep -q "a secret from PrivaPub"' && ok "alice's DM arrives as a Lemmy private message" || ko "DM missing on Lemmy"
|
|
|
|
echo " moderation"
|
|
lm POST post/remove "{\"post_id\":$lm_cats_id,\"removed\":true,\"reason\":\"pasture\"}" >/dev/null
|
|
sleep 10
|
|
if curl -s -H "$LAH" "$P/api/v1/timelines/home" | j "print(any((s.get('reblog') or s)['uri']=='$lm_cats_thread' for s in d))" | grep -q False; then
|
|
ok "a moderator's removal reaches PrivaPub"
|
|
else
|
|
xf "a moderator's removal reaches PrivaPub (the Lemmy moderation set is P7)"
|
|
fi
|
|
|
|
echo " statistics"
|
|
stats_check lemmy.test lemmy
|