T12, T13: Sharkey and Akkoma in the pasture, and two bugs Akkoma found
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
This commit is contained in:
1 parent
aa7e43a61e
commit
ab526ed291
22 files changed
+361
-19
No files matched your search
@@ -0,0 +1,142 @@
|
||||
# Akkoma 3.20: discovery, follows, posts, CW, replies both ways, likes and boosts with their undos, emoji reactions
|
||||
# both ways, DMs, polls, quotes both ways, edits with their history, deletes, media with alt text, the published time
|
||||
# kept, blocks, statistics. Akkoma speaks the Mastodon API, with reactions under /api/v1/pleroma.
|
||||
AK=https://akkoma.test:6443
|
||||
acurl() { curl -sk --resolve akkoma.test:6443:127.0.0.1 "$@"; }
|
||||
KT=$(cat "$here/.state/akkoma.token" 2>/dev/null)
|
||||
KH="Authorization: Bearer $KT"
|
||||
# what Akkoma holds of an account, newest first; and the one status whose uri is given
|
||||
a_statuses() { acurl -H "$KH" "$AK/api/v1/accounts/$1/statuses?limit=40"; }
|
||||
a_status_by_uri() { a_statuses "$1" | j "print(json.dumps(next((s for s in d if s['uri']=='$2'), None)))"; }
|
||||
# a PrivaPub status in alice_akkoma's home whose text has the given words
|
||||
p_home_id() { curl -s -H "$PH" "$P/api/v1/timelines/home?limit=40" | j "print(next((s['id'] for s in d if '$1' in s['content']), ''))"; }
|
||||
|
||||
echo "akkoma"
|
||||
[ -n "$KT" ] && ok "Akkoma token for akuser" || { ko "Akkoma token"; return 1; }
|
||||
PT=$(privapub_token alice_akkoma)
|
||||
PH="Authorization: Bearer $PT"
|
||||
[ -n "$PT" ] && ok "PrivaPub token for alice_akkoma" || { ko "PrivaPub token for alice_akkoma"; return 1; }
|
||||
alice_self=$(curl -s -H "$PH" "$P/api/v1/accounts/verify_credentials" | j "print(d['id'])")
|
||||
|
||||
echo " discovery"
|
||||
alice_on_a=$(acurl -H "$KH" "$AK/api/v2/search?q=@alice_akkoma@privapub.test&resolve=true&type=accounts" | j "print(d['accounts'][0]['id'])")
|
||||
[ -n "$alice_on_a" ] && ok "Akkoma resolves @alice_akkoma@privapub.test" || ko "Akkoma cannot resolve alice_akkoma"
|
||||
ak_on_p=$(curl -s -H "$PH" "$P/api/v2/search?q=akuser@akkoma.test&resolve=true&type=accounts" | j "print(d['accounts'][0]['id'])")
|
||||
[ -n "$ak_on_p" ] && ok "PrivaPub resolves @akuser@akkoma.test" || ko "PrivaPub cannot resolve akuser"
|
||||
|
||||
echo " follows"
|
||||
acurl -o /dev/null -X POST -H "$KH" "$AK/api/v1/accounts/$alice_on_a/follow"
|
||||
until_true 30 '[ "$(acurl -H "$KH" "$AK/api/v1/accounts/relationships?id[]=$alice_on_a" | j "print(d[0][\"following\"])")" = "True" ]' && ok "akuser follows alice_akkoma (Accept arrived)" || ko "Akkoma's follow not accepted"
|
||||
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$ak_on_p/follow"
|
||||
until_true 30 '[ "$(curl -s -H "$PH" "$P/api/v1/accounts/relationships?id[]=$ak_on_p" | j "print(d[0][\"following\"])")" = "True" ]' && ok "alice_akkoma follows akuser (Accept arrived)" || ko "PrivaPub's follow not accepted"
|
||||
|
||||
echo " posts"
|
||||
p_post=$(curl -s -X POST -H "$PH" $P/api/v1/statuses -d 'status=Hello Akkoma from PrivaPub&visibility=public')
|
||||
p_post_id=$(echo "$p_post" | j "print(d['id'])"); p_post_uri=$(echo "$p_post" | j "print(d['uri'])")
|
||||
until_true 30 '[ "$(a_status_by_uri "$alice_on_a" "$p_post_uri")" != "null" ]' && ok "alice_akkoma's post reaches Akkoma" || ko "post missing on Akkoma"
|
||||
p_post_on_a=$(a_status_by_uri "$alice_on_a" "$p_post_uri" | j "print(d['id'])")
|
||||
a_post=$(acurl -X POST -H "$KH" "$AK/api/v1/statuses" -d 'status=Hello PrivaPub from Akkoma&visibility=public')
|
||||
a_post_id=$(echo "$a_post" | j "print(d['id'])")
|
||||
until_true 30 '[ -n "$(p_home_id "Hello PrivaPub from Akkoma")" ]' && ok "akuser's post reaches alice_akkoma's home" || ko "Akkoma's post missing on PrivaPub"
|
||||
a_post_on_p=$(p_home_id "Hello PrivaPub from Akkoma")
|
||||
a_created=$(echo "$a_post" | j "print(d['created_at'][:19])")
|
||||
[ "$(curl -s -H "$PH" "$P/api/v1/statuses/$a_post_on_p" | j "print(d['created_at'][:19])")" = "$a_created" ] \
|
||||
&& ok "akuser's post keeps its published time" || ko "published time changed on PrivaPub"
|
||||
cw_uri=$(curl -s -X POST -H "$PH" $P/api/v1/statuses -d 'status=behind a warning&spoiler_text=spoilers&visibility=public' | j "print(d['uri'])")
|
||||
until_true 30 '[ "$(a_status_by_uri "$alice_on_a" "$cw_uri" | j "print(d and d[\"spoiler_text\"]==\"spoilers\" and d[\"sensitive\"])")" = "True" ]' && ok "a content warning survives to Akkoma" || ko "content warning lost on Akkoma"
|
||||
fo_uri=$(curl -s -X POST -H "$PH" $P/api/v1/statuses -d 'status=only for followers&visibility=private' | j "print(d['uri'])")
|
||||
until_true 30 '[ "$(a_status_by_uri "$alice_on_a" "$fo_uri" | j "print(d and d[\"visibility\"])")" = "private" ]' && ok "a followers-only post reaches Akkoma as private" || ko "followers-only post missing or widened on Akkoma"
|
||||
|
||||
echo " replies"
|
||||
acurl -o /dev/null -X POST -H "$KH" "$AK/api/v1/statuses" -d "status=@alice_akkoma@privapub.test replying from Akkoma&in_reply_to_id=$p_post_on_a&visibility=public"
|
||||
until_true 30 'curl -s -H "$PH" "$P/api/v1/notifications" | j "print(any(n[\"type\"]==\"mention\" for n in d))" | grep -q True' && ok "akuser's reply notifies alice_akkoma" || ko "reply did not notify"
|
||||
until_true 15 '[ "$(curl -s -H "$PH" "$P/api/v1/statuses/$p_post_id/context" | j "print(len(d[\"descendants\"]))")" -ge 1 ]' && ok "the reply threads under alice_akkoma's post" || ko "reply not threaded on PrivaPub"
|
||||
curl -s -o /dev/null -X POST -H "$PH" $P/api/v1/statuses -d "status=@akuser@akkoma.test replying from PrivaPub&in_reply_to_id=$a_post_on_p&visibility=public"
|
||||
until_true 30 '[ "$(acurl -H "$KH" "$AK/api/v1/statuses/$a_post_id/context" | j "print(len(d[\"descendants\"]))")" -ge 1 ]' && ok "alice_akkoma's reply threads under akuser's post" || ko "outbound reply not threaded on Akkoma"
|
||||
|
||||
echo " likes, boosts and reactions"
|
||||
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses/$a_post_on_p/favourite"
|
||||
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses/$a_post_on_p/reblog"
|
||||
until_true 30 '[ "$(acurl -H "$KH" "$AK/api/v1/statuses/$a_post_id" | j "print(d[\"favourites_count\"], d[\"reblogs_count\"])")" = "1 1" ]' && ok "alice_akkoma's like and boost count on Akkoma" || ko "like or boost not counted on Akkoma"
|
||||
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses/$a_post_on_p/unfavourite"
|
||||
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses/$a_post_on_p/unreblog"
|
||||
until_true 30 '[ "$(acurl -H "$KH" "$AK/api/v1/statuses/$a_post_id" | j "print(d[\"favourites_count\"], d[\"reblogs_count\"])")" = "0 0" ]' && ok "alice_akkoma's unlike and unboost reach Akkoma" || ko "undo of like or boost not applied on Akkoma"
|
||||
acurl -o /dev/null -X POST -H "$KH" "$AK/api/v1/statuses/$p_post_on_a/favourite"
|
||||
acurl -o /dev/null -X POST -H "$KH" "$AK/api/v1/statuses/$p_post_on_a/reblog"
|
||||
until_true 30 '[ "$(curl -s -H "$PH" "$P/api/v1/statuses/$p_post_id" | j "print(d[\"favourites_count\"], d[\"reblogs_count\"])")" = "1 1" ]' && ok "akuser's like and boost count on PrivaPub" || ko "like or boost not counted on PrivaPub"
|
||||
acurl -o /dev/null -X POST -H "$KH" "$AK/api/v1/statuses/$p_post_on_a/unfavourite"
|
||||
acurl -o /dev/null -X POST -H "$KH" "$AK/api/v1/statuses/$p_post_on_a/unreblog"
|
||||
until_true 30 '[ "$(curl -s -H "$PH" "$P/api/v1/statuses/$p_post_id" | j "print(d[\"favourites_count\"], d[\"reblogs_count\"])")" = "0 0" ]' && ok "akuser's unlike and unboost reach PrivaPub" || ko "undo of like or boost not applied on PrivaPub"
|
||||
acurl -o /dev/null -X PUT -H "$KH" "$AK/api/v1/pleroma/statuses/$p_post_on_a/reactions/%F0%9F%8E%89"
|
||||
until_true 30 '[ "$(curl -s -H "$PH" "$P/api/v1/statuses/$p_post_id" | j "print(any(r[\"name\"]==\"🎉\" and r[\"count\"]==1 for r in d.get(\"emoji_reactions\") or []))")" = "True" ]' \
|
||||
&& ok "akuser's 🎉 (EmojiReact) arrives as a reaction" || ko "Akkoma's reaction missing on PrivaPub"
|
||||
acurl -o /dev/null -X DELETE -H "$KH" "$AK/api/v1/pleroma/statuses/$p_post_on_a/reactions/%F0%9F%8E%89"
|
||||
until_true 30 '[ "$(curl -s -H "$PH" "$P/api/v1/statuses/$p_post_id" | j "print(len(d.get(\"emoji_reactions\") or []))")" = "0" ]' && ok "akuser's withdrawn reaction is gone" || ko "withdrawn reaction still on PrivaPub"
|
||||
curl -s -o /dev/null -X PUT -H "$PH" "$P/api/v1/pleroma/statuses/$a_post_on_p/reactions/%F0%9F%91%8D"
|
||||
until_true 30 '[ "$(acurl -H "$KH" "$AK/api/v1/statuses/$a_post_id" | j "print(any(r[\"name\"]==\"👍\" and r[\"count\"]==1 for r in (d.get(\"pleroma\") or {}).get(\"emoji_reactions\") or d.get(\"emoji_reactions\") or []))")" = "True" ]' \
|
||||
&& ok "alice_akkoma's 👍 is a reaction on Akkoma" || ko "PrivaPub's reaction missing on Akkoma"
|
||||
curl -s -o /dev/null -X DELETE -H "$PH" "$P/api/v1/pleroma/statuses/$a_post_on_p/reactions/%F0%9F%91%8D"
|
||||
until_true 30 '[ "$(acurl -H "$KH" "$AK/api/v1/statuses/$a_post_id" | j "print(len((d.get(\"pleroma\") or {}).get(\"emoji_reactions\") or d.get(\"emoji_reactions\") or []))")" = "0" ]' && ok "alice_akkoma's withdrawn reaction is gone from Akkoma" || ko "withdrawn reaction still on Akkoma"
|
||||
|
||||
echo " direct messages"
|
||||
curl -s -o /dev/null -X POST -H "$PH" $P/api/v1/statuses -d 'status=@akuser@akkoma.test a secret for Akkoma&visibility=direct'
|
||||
until_true 30 'acurl -H "$KH" "$AK/api/v1/conversations" | grep -q "a secret for Akkoma"' && ok "alice_akkoma's DM reaches akuser" || ko "DM missing on Akkoma"
|
||||
# Akkoma's Linkify never takes @user@host.test for a mention, so its DM names alice with Pleroma's to[] instead
|
||||
acurl -o /dev/null -X POST -H "$KH" "$AK/api/v1/statuses" -d 'status=a secret for PrivaPub&visibility=direct&to[]=alice_akkoma@privapub.test'
|
||||
until_true 30 'curl -s -H "$PH" "$P/api/v1/conversations" | grep -q "a secret for PrivaPub"' && ok "akuser's DM reaches alice_akkoma" || ko "DM missing on PrivaPub"
|
||||
! curl -s "$P/api/v1/timelines/public" | grep -q "a secret for PrivaPub" && ok "the DM is not on PrivaPub's public timeline" || ko "DM leaked to the public timeline"
|
||||
|
||||
echo " polls"
|
||||
p_poll_uri=$(curl -s -X POST -H "$PH" $P/api/v1/statuses -d 'status=cats or dogs&visibility=public&poll[options][]=cats&poll[options][]=dogs&poll[expires_in]=3600' | j "print(d['uri'])")
|
||||
until_true 30 '[ "$(a_status_by_uri "$alice_on_a" "$p_poll_uri" | j "print(len(d[\"poll\"][\"options\"]))")" = "2" ]' && ok "alice_akkoma's poll reaches Akkoma as a poll" || ko "poll missing on Akkoma"
|
||||
p_poll_on_a=$(a_status_by_uri "$alice_on_a" "$p_poll_uri" | j "print(d['poll']['id'])")
|
||||
acurl -o /dev/null -X POST -H "$KH" "$AK/api/v1/polls/$p_poll_on_a/votes" -d 'choices[]=1'
|
||||
until_true 30 '[ "$(curl -s -H "$PH" "$P/api/v1/accounts/$alice_self/statuses" | j "print(next(s[\"poll\"][\"options\"][1][\"votes_count\"] for s in d if s[\"uri\"]==\"$p_poll_uri\"))")" = "1" ]' && ok "akuser's vote counts on PrivaPub" || ko "vote not counted on PrivaPub"
|
||||
a_poll=$(acurl -X POST -H "$KH" "$AK/api/v1/statuses" -d 'status=tea or coffee, Akkoma asks&visibility=public&poll[options][]=tea&poll[options][]=coffee&poll[expires_in]=3600' | j "print(d['id'])")
|
||||
until_true 30 'curl -s -H "$PH" "$P/api/v1/timelines/home" | j "print(any(s[\"poll\"] and \"Akkoma asks\" in s[\"content\"] for s in d))" | grep -q True' && ok "akuser's poll reaches PrivaPub as a poll" || ko "poll missing on PrivaPub"
|
||||
a_poll_on_p=$(curl -s -H "$PH" "$P/api/v1/timelines/home" | j "print(next(s['poll']['id'] for s in d if s['poll'] and 'Akkoma asks' in s['content']))")
|
||||
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/polls/$a_poll_on_p/votes" -d 'choices[]=0'
|
||||
until_true 30 '[ "$(acurl -H "$KH" "$AK/api/v1/statuses/$a_poll" | j "print(d[\"poll\"][\"options\"][0][\"votes_count\"])")" = "1" ]' && ok "alice_akkoma's vote counts on Akkoma" || ko "vote not counted on Akkoma"
|
||||
|
||||
echo " quotes"
|
||||
a_quote=$(acurl -X POST -H "$KH" "$AK/api/v1/statuses" -d "status=quoting PrivaPub&visibility=public"e_id=$p_post_on_a" | j "print(d['id'])")
|
||||
until_true 30 'curl -s -H "$PH" "$P/api/v1/timelines/home" | j "print(any(\"quoting PrivaPub\" in s[\"content\"] and (s.get(\"quote\") or {}).get(\"state\")==\"accepted\" for s in d))" | grep -q True' \
|
||||
&& ok "akuser's quote arrives as an accepted quote" || ko "Akkoma's quote not understood"
|
||||
p_quote_uri=$(curl -s -X POST -H "$PH" $P/api/v1/statuses -d "status=quoting Akkoma&visibility=public"ed_status_id=$a_post_on_p" | j "print(d['uri'])")
|
||||
until_true 30 '[ "$(a_status_by_uri "$alice_on_a" "$p_quote_uri" | j "print(d and ((d.get(\"quote\") or {}).get(\"id\") or d.get(\"quote_id\") or (d.get(\"pleroma\") or {}).get(\"quote_id\")) == \"$a_post_id\")")" = "True" ]' \
|
||||
&& ok "alice_akkoma's quote is a quote on Akkoma" || ko "quote not understood by Akkoma"
|
||||
|
||||
echo " media"
|
||||
make_png "$work/red.png"
|
||||
p_media=$(curl -s -X POST -H "$PH" "$P/api/v2/media" -F "file=@$work/red.png;type=image/png" -F 'description=a red square' | j "print(d['id'])")
|
||||
p_media_uri=$(curl -s -X POST -H "$PH" $P/api/v1/statuses -d "status=a picture&visibility=public&media_ids[]=$p_media" | j "print(d['uri'])")
|
||||
until_true 30 '[ "$(a_status_by_uri "$alice_on_a" "$p_media_uri" | j "print(d[\"media_attachments\"][0][\"description\"])")" = "a red square" ]' && ok "an image with alt text reaches Akkoma" || ko "image or alt text missing on Akkoma"
|
||||
a_media=$(acurl -X POST -H "$KH" "$AK/api/v1/media" -F "file=@$work/red.png;type=image/png" -F 'description=a red square from Akkoma' | j "print(d['id'])")
|
||||
acurl -o /dev/null -X POST -H "$KH" "$AK/api/v1/statuses" -d "status=a picture from Akkoma&visibility=public&media_ids[]=$a_media"
|
||||
until_true 30 'curl -s -H "$PH" "$P/api/v1/timelines/home" | j "print(any(\"a picture from Akkoma\" in s[\"content\"] and s[\"media_attachments\"] and \"/media/proxy/\" in s[\"media_attachments\"][0][\"url\"] and s[\"media_attachments\"][0][\"description\"]==\"a red square from Akkoma\" for s in d))" | grep -q True' \
|
||||
&& ok "an image with alt text from Akkoma arrives through our proxy" || ko "Akkoma's image missing, unproxied or without alt text"
|
||||
|
||||
echo " edits and deletes"
|
||||
curl -s -o /dev/null -X PUT -H "$PH" "$P/api/v1/statuses/$p_post_id" -d 'status=Hello Akkoma from PrivaPub, edited'
|
||||
until_true 30 'acurl -H "$KH" "$AK/api/v1/statuses/$p_post_on_a" | grep -q "edited"' && ok "alice_akkoma's edit reaches Akkoma" || ko "edit not applied on Akkoma"
|
||||
acurl -o /dev/null -X PUT -H "$KH" "$AK/api/v1/statuses/$a_post_id" -d 'status=Hello PrivaPub from Akkoma, edited'
|
||||
until_true 30 '[ "$(curl -s -H "$PH" "$P/api/v1/statuses/$a_post_on_p/history" | j "print(len(d))")" = "2" ]' && ok "akuser's edit reaches PrivaPub with its history" || ko "Akkoma's edit not applied on PrivaPub"
|
||||
cw_on_a=$(a_status_by_uri "$alice_on_a" "$cw_uri" | j "print(d['id'])")
|
||||
cw_id=$(curl -s -H "$PH" "$P/api/v1/accounts/$alice_self/statuses" | j "print(next(s['id'] for s in d if s['uri']=='$cw_uri'))")
|
||||
curl -s -o /dev/null -X DELETE -H "$PH" "$P/api/v1/statuses/$cw_id"
|
||||
until_true 30 '[ "$(acurl -o /dev/null -w "%{http_code}" -H "$KH" "$AK/api/v1/statuses/$cw_on_a")" = "404" ]' && ok "alice_akkoma's delete reaches Akkoma" || ko "delete not applied on Akkoma"
|
||||
acurl -o /dev/null -X DELETE -H "$KH" "$AK/api/v1/statuses/$a_poll"
|
||||
until_true 30 '! curl -s -H "$PH" "$P/api/v1/timelines/home" | grep -q "Akkoma asks"' && ok "akuser's delete reaches PrivaPub" || ko "Akkoma's delete not applied on PrivaPub"
|
||||
|
||||
echo " blocks and unfollows"
|
||||
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$ak_on_p/unfollow"
|
||||
until_true 30 '[ "$(acurl -H "$KH" "$AK/api/v1/accounts/relationships?id[]=$alice_on_a" | j "print(d[0][\"followed_by\"])")" = "False" ]' && ok "alice_akkoma's unfollow reaches Akkoma" || ko "unfollow not applied on Akkoma"
|
||||
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$ak_on_p/block"
|
||||
# Akkoma never reports a remote blocker as blocked_by, so the block is read from its user_relationships (1 is a block)
|
||||
a_blocked() { podman exec pasture-postgres psql -U pasture -d akkoma -tAc "select count(*) from user_relationships r join users a on a.id=r.source_id join users b on b.id=r.target_id where r.relationship_type=1 and a.nickname='alice_akkoma@privapub.test' and b.nickname='akuser'"; }
|
||||
until_true 30 '[ "$(a_blocked)" = "1" ]' && ok "alice_akkoma's block reaches Akkoma" || ko "block not applied on Akkoma"
|
||||
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$ak_on_p/unblock"
|
||||
until_true 30 '[ "$(a_blocked)" = "0" ]' && ok "alice_akkoma's unblock reaches Akkoma" || ko "unblock not applied on Akkoma"
|
||||
|
||||
echo " statistics"
|
||||
stats_check akkoma.test akkoma
|
||||
Reference in new issue
Block a user