Files
SocialPub/tools/pasture/scenarios/snac.sh
T
thepraandClaude Opus 5.5 5721bfd796 Pasture: snac2 2.95
snac2 joins the pasture, built from its tag (images/snac2); scenarios/snac.sh drives its Mastodon API and reads its
own files, since its timelines lag behind what it has taken in: follows, posts, replies, likes, boosts, a poll, edits,
deletions, direct messages, the unfollow and statistics, 27 checks, with no change to PrivaPub.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
2026-10-06 00:19:56 +02:00

109 lines
8.8 KiB
Bash

# snac2 2.95: follows both ways, posts, replies, likes, boosts, a poll, an edit and a deletion both ways, direct
# messages, the unfollow, statistics. snac keeps everything in files and federates in plain ActivityPub. Driven through
# its Mastodon API as snacuser, with the token peers/snac.sh got.
MI=https://snac.test:6443
MIT=$(cat "$here/.state/snac/snacuser.token" 2>/dev/null)
mi() { site snac.test -s -H "Authorization: Bearer $MIT" "$@"; }
mi_json() { local method=$1 path=$2 body=$3; mi -X "$method" "$MI$path" -H 'Content-Type: application/json' -d "$body"; }
mi_status_of() { mi "$MI/api/v1/statuses/${1:-none}"; }
# snac_stored <uri> <expression>: the expression over snac's own copy of an object, read from its files (`o`, None when
# it holds none). Its API's timelines lag behind what it has taken in, and give an object a new id when it changes
snac_stored() {
local md5
md5=$(printf '%s' "$1" | md5sum | cut -c1-32)
podman exec pasture-snac cat "/data/data/object/${md5:0:2}/$md5.json" 2>/dev/null | j "o = d; print($2)"
}
# snac_in_timeline <uri>: whether the object is in snacuser's timeline (its private.idx)
snac_in_timeline() { podman exec pasture-snac grep -q "$(printf '%s' "$1" | md5sum | cut -c1-32)" /data/data/user/snacuser/private.idx; }
p_home_has() { curl -s -H "$PH" "$P/api/v1/timelines/home?limit=40" | j "print(next(((s.get('reblog') or s)['id'] for s in d if '$1' in ((s.get('reblog') or s)['content'] or '')), ''))"; }
p_status() { curl -s -H "$PH" "$P/api/v1/statuses/$1" | j "print(d.get('$2'))"; }
echo "snac"
[ "$(mi "$MI/api/v1/accounts/verify_credentials" | j "print(d['username'])")" = "snacuser" ] && ok "snac token for snacuser" || { ko "snac token"; return 1; }
PT=$(privapub_token alice_snac)
PH="Authorization: Bearer $PT"
[ -n "$PT" ] && ok "PrivaPub token for alice_snac" || { ko "PrivaPub token for alice_snac"; return 1; }
run=$(date +%s)
echo " follows"
alice_on_mi=$(mi "$MI/api/v2/search?q=alice_snac%40privapub.test&resolve=true" | j "print(d['accounts'][0]['id'])")
[ -n "$alice_on_mi" ] && ok "snac resolves alice" || ko "snac cannot resolve alice"
mi -o /dev/null -X POST "$MI/api/v1/accounts/$alice_on_mi/follow"
until_true 45 '[ "$(mi "$MI/api/v1/accounts/relationships?id[]=$alice_on_mi" | j "print(d[0][\"following\"])")" = "True" ]' \
&& ok "snacuser follows alice (Accept arrived)" || ko "alice's Accept never reached snac"
mi_on_p=$(curl -s -H "$PH" "$P/api/v2/search?q=snacuser@snac.test&resolve=true&type=accounts" | j "print(d['accounts'][0]['id'])")
[ -n "$mi_on_p" ] && ok "PrivaPub resolves snacuser" || ko "PrivaPub cannot resolve snacuser"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$mi_on_p/follow"
until_true 45 '[ "$(curl -s -H "$PH" "$P/api/v1/accounts/relationships?id[]=$mi_on_p" | j "print(d[0][\"following\"])")" = "True" ]' \
&& ok "alice follows snacuser (Accept arrived)" || ko "snac's Accept never arrived"
echo " posts"
mi_post=$(mi_json POST /api/v1/statuses "{\"status\":\"a snac post $run\",\"visibility\":\"public\"}")
mi_post_id=$(echo "$mi_post" | j "print(d['id'])")
until_true 45 '[ -n "$(p_home_has "a snac post $run")" ]' && ok "snacuser's post reaches alice's home" || ko "snacuser's post never reached alice"
mi_on_p_post=$(p_home_has "a snac post $run")
p_post=$(curl -s -X POST -H "$PH" $P/api/v1/statuses -d "status=a PrivaPub post for snac $run&visibility=public")
p_post_id=$(echo "$p_post" | j "print(d['id'])"); p_post_uri=$(echo "$p_post" | j "print(d['uri'])")
until_true 45 '[ "$(mi "$MI/api/v1/timelines/home?limit=40" | j "print(any(s[\"uri\"] == \"$p_post_uri\" for s in d))")" = "True" ]' \
&& ok "alice's post reaches snacuser's home" || ko "alice's post never reached snac"
p_on_mi=$(mi "$MI/api/v1/timelines/home?limit=40" | j "print(next(s['id'] for s in d if s['uri'] == '$p_post_uri'))")
echo " replies"
mi_json POST /api/v1/statuses "{\"status\":\"@alice_snac@privapub.test a snac reply $run\",\"in_reply_to_id\":\"$p_on_mi\",\"visibility\":\"public\"}" >/dev/null
until_true 45 '[ "$(curl -s -H "$PH" "$P/api/v1/statuses/$p_post_id/context" | j "print(any(\"a snac reply $run\" in s[\"content\"] for s in d[\"descendants\"]))")" = "True" ]' \
&& ok "snacuser's reply threads under alice's post" || ko "snacuser's reply missing on PrivaPub (replying to ${p_on_mi:-nothing})"
curl -s -o /dev/null -X POST -H "$PH" $P/api/v1/statuses -d "status=@snacuser@snac.test a PrivaPub reply $run&in_reply_to_id=$mi_on_p_post&visibility=public"
until_true 45 '[ "$(mi "$MI/api/v1/statuses/$mi_post_id/context" | j "print(any(\"a PrivaPub reply $run\" in s[\"content\"] for s in d[\"descendants\"]))")" = "True" ]' \
&& ok "alice's reply threads under snacuser's post" || ko "alice's reply missing on snac"
echo " likes and boosts"
mi -o /dev/null -X POST "$MI/api/v1/statuses/$p_on_mi/favourite"
until_true 45 '[ "$(p_status $p_post_id favourites_count)" = "1" ]' && ok "snacuser's like counts on PrivaPub" || ko "snacuser's like never counted"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses/$mi_on_p_post/favourite"
until_true 45 '[ "$(mi_status_of $mi_post_id | j "print(d[\"favourites_count\"])")" = "1" ]' && ok "alice's like counts on snac" || ko "alice's like never counted on snac"
mi -o /dev/null -X POST "$MI/api/v1/statuses/$p_on_mi/reblog"
until_true 45 '[ "$(p_status $p_post_id reblogs_count)" = "1" ]' && ok "snacuser's repost is a boost on PrivaPub" || ko "snacuser's repost never counted"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses/$mi_on_p_post/reblog"
until_true 45 '[ "$(mi_status_of $mi_post_id | j "print(d[\"reblogs_count\"])")" = "1" ]' && ok "alice's boost counts on snac" || ko "alice's boost never counted on snac"
echo " polls"
# (its API reads a poll from form fields only)
mi_poll=$(mi -X POST "$MI/api/v1/statuses" -d "status=a snac poll $run&visibility=public&poll[options][]=hay&poll[options][]=clover&poll[expires_in]=3600")
until_true 45 '[ -n "$(p_home_has "a snac poll $run")" ]' && ok "snacuser's poll reaches alice" || ko "snacuser's poll never reached alice"
p_poll=$(curl -s -H "$PH" "$P/api/v1/statuses/$(p_home_has "a snac poll $run")" | j "print(d['poll']['id'] if d.get('poll') else '')")
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/polls/$p_poll/votes" -d 'choices[]=1'
until_true 45 '[ "$(mi_status_of $(echo "$mi_poll" | j "print(d[\"id\"])") | j "print(d[\"poll\"][\"votes_count\"])")" = "1" ]' \
&& ok "alice's vote counts on snac" || ko "alice's vote never counted on snac"
echo " edits and deletions"
mi_json PUT "/api/v1/statuses/$mi_post_id" "{\"status\":\"a snac post $run, edited\"}" >/dev/null
until_true 45 '[ "$(p_status $mi_on_p_post content | grep -c edited)" = "1" ]' && ok "snacuser's edit reaches PrivaPub" || ko "snacuser's edit never arrived"
curl -s -o /dev/null -X PUT -H "$PH" "$P/api/v1/statuses/$p_post_id" -d "status=a PrivaPub post for snac $run, edited"
# (snac works through what it receives one message at a time, sometimes minutes behind)
until_true 120 '[ "$(snac_stored "$p_post_uri" "o is not None and \"edited\" in o[\"content\"]")" = "True" ]' && ok "alice's edit reaches snac" || ko "alice's edit never reached snac ($(snac_stored "$p_post_uri" "o and o['content'][-60:]"))"
gone=$(mi_json POST /api/v1/statuses "{\"status\":\"a snac post to delete $run\",\"visibility\":\"public\"}" | j "print(d['id'])")
until_true 45 '[ -n "$(p_home_has "a snac post to delete $run")" ]'
gone_on_p=$(p_home_has "a snac post to delete $run")
mi -o /dev/null -X DELETE "$MI/api/v1/statuses/$gone"
until_true 45 '[ "$(curl -s -o /dev/null -w "%{http_code}" -H "$PH" "$P/api/v1/statuses/$gone_on_p")" = "404" ]' \
&& ok "snacuser's deletion reaches PrivaPub" || ko "snacuser's deleted post still shows on PrivaPub"
curl -s -o /dev/null -X DELETE -H "$PH" "$P/api/v1/statuses/$p_post_id"
# (snac keeps the object and takes it out of snacuser's timeline)
until_true 45 '! snac_in_timeline "$p_post_uri"' \
&& ok "alice's deletion reaches snac" || ko "alice's deleted post still on snac"
echo " direct messages"
mi_json POST /api/v1/statuses "{\"status\":\"@alice_snac@privapub.test a snac secret $run\",\"visibility\":\"direct\"}" >/dev/null
until_true 45 'curl -s -H "$PH" "$P/api/v1/conversations" | grep -q "a snac secret $run"' && ok "snacuser's DM arrives" || ko "snacuser's DM never arrived"
dm_uri=$(curl -s -X POST -H "$PH" $P/api/v1/statuses -d "status=@snacuser@snac.test a PrivaPub secret $run&visibility=direct" | j "print(d['uri'])")
until_true 120 '[ "$(snac_stored "$dm_uri" "o is not None and \"a PrivaPub secret\" in o[\"content\"]")" = "True" ]' \
&& ok "alice's DM reaches snac" || ko "alice's DM never reached snac"
echo " unfollow"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$mi_on_p/unfollow"
until_true 45 '[ "$(mi "$MI/api/v1/accounts/relationships?id[]=$alice_on_mi" | j "print(d[0][\"followed_by\"])")" = "False" ]' \
&& ok "alice's unfollow reaches snac" || ko "snac still counts alice as a follower"
echo " statistics"
stats_check snac.test snac