Files
SocialPub/tools/pasture/scenarios/forte.sh
T
thepraandClaude Opus 5.5 3df3c333bf Forte in the pasture
Forte 26.9.10, the ActivityPub-only fork of Streams, built from its tag (images/forte: composer on Hubzilla's PHP image)
with portable identities (did:key actors behind /.well-known/apgateway). scenarios/forte.sh, 21 checks: follows, posts,
comments, likes, edits and deletions both ways, a third channel's comment passed on by the thread's owner, the
unfollow, statistics. INTEROP.md records what it does its own way: follows of profile pages, edits only as
Add{Update} under the Create's id, collections that misname themselves.

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

100 lines
8.5 KiB
Bash

# Forte 26.9.10 (the ActivityPub-only fork of Streams): follows both ways; posts both ways; comments both ways; likes both
# ways; a comment by ftfriend in ftuser's thread, which Forte passes on to alice as the thread's owner; edits and
# deletions both ways; the unfollow; statistics. Forte's API (api/z/1.0) posts, edits and reads; what it has no route
# for is done through its own PHP (connecting) or its web pages signed in (liking, dropping; peers/forte.sh), and
# what it holds is read from its MySQL (database forte, the item table by mid).
. "$here/peers/forte.sh"
FT=https://forte.test:6443
# ftc <nick> <curl args...>: Forte's API as <nick>
ftc() { local nick=$1; shift; site forte.test -s -u "$nick@forte.test:$FORTE_PASSWORD" "$@"; }
ft_sql() { podman exec pasture-mysql mysql -upasture -ppasture forte -Nse "$1" 2>/dev/null; }
# ft_item <mid> [nick]: the id of the item Forte holds under that ActivityPub id in a channel (ftuser's)
ft_item() { ft_sql "select id from item where mid = '$1' and uid = (select channel_id from channel where channel_address = '${2:-ftuser}') and item_deleted = 0 limit 1"; }
# ft_disconnect <nick> <url>: the channel's connection to that address gone, so connecting again sends a new Follow
ft_disconnect() { forte_eval "use Code\\Lib\\Channel; \$c = Channel::from_username('$1'); \$r = q(\"select abook_id from abook join xchan on abook_xchan = xchan_hash where abook_channel = %d and (xchan_url = '%s' or xchan_hash = '%s' or xchan_addr = '%s')\", intval(\$c['channel_id']), dbesc('$2'), dbesc('$2'), dbesc('$2')); foreach (\$r ?: [] as \$a) contact_remove(\$c['channel_id'], \$a['abook_id']);"; }
# ft_connect <nick> <address>: the channel connects, and Forte's notifier sends its Follow at once (Forte starts it as a
# background process, which does not outlive a command run through podman exec)
ft_connect() {
local abook
abook=$(forte_eval "use Code\\Lib\\Connect; use Code\\Lib\\Channel; \$c = Channel::from_username('$1'); \$r = Connect::connect(\$c, '$2'); echo \$r['success'] ? \$r['abook']['abook_id'] : '';" | tail -1)
[ -n "$abook" ] && forte_php src/Daemon/Run.php Notifier permissions_create "$abook" >/dev/null 2>&1
echo "$abook"
}
p_home_id() { curl -s -H "$PH" "$P/api/v1/timelines/home?limit=40" | j "print(next((o['id'] for o in ((s.get('reblog') or s) for s in d) if '$1' in (o['content'] or '')), ''))"; }
p_status() { curl -s -H "$PH" "$P/api/v1/statuses/$1" | j "print(d.get('$2'))"; }
p_replies_have() { [ "$(curl -s -H "$PH" "$P/api/v1/statuses/$1/context" | j "print(any('$2' in s['content'] for s in d['descendants']))")" = "True" ]; }
echo "forte"
[ -n "$(ftc ftuser "$FT/api/z/1.0/verify" | j "print(d.get('channel_address') or '')")" ] && ok "Forte's API as ftuser" \
|| { ko "Forte's API ($(ftc ftuser "$FT/api/z/1.0/verify" | head -c 200))"; return 1; }
PT=$(privapub_token alice_forte)
PH="Authorization: Bearer $PT"
[ -n "$PT" ] && ok "PrivaPub token for alice_forte" || { ko "PrivaPub token for alice_forte"; return 1; }
alice_uri="$(curl -s -H "$PH" "$P/api/v1/accounts/verify_credentials" | j "print(d['url'])" | sed 's|/@|/peasants/|')"
run=$(date +%s)
echo " follows"
ft_on_p=$(curl -s -H "$PH" "$P/api/v2/search?q=ftuser@forte.test&resolve=true&type=accounts" | j "print(d['accounts'][0]['id'])")
[ -n "$ft_on_p" ] && ok "PrivaPub resolves ftuser" || ko "PrivaPub cannot resolve ftuser"
# (left from an earlier run: alice and ftuser unfollow each other and ftfriend leaves ftuser, so every follow below is new)
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$ft_on_p/unfollow"
ft_disconnect ftuser "$alice_uri" >/dev/null; ft_disconnect ftuser "$(curl -s -H "$PH" "$P/api/v1/accounts/verify_credentials" | j "print(d['url'])")" >/dev/null
ft_disconnect ftfriend ftuser@forte.test >/dev/null; ft_disconnect ftuser ftfriend@forte.test >/dev/null
sleep 3
ft_connect ftuser "$alice_uri" >/dev/null
until_true 60 '[ "$(curl -s -H "$PH" "$P/api/v1/accounts/verify_credentials" | j "print(d[\"followers_count\"])")" -ge 1 ]' \
&& ok "ftuser follows alice" || ko "Forte's follow never reached alice"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$ft_on_p/follow"
until_true 60 '[ "$(curl -s -H "$PH" "$P/api/v1/accounts/relationships?id[]=$ft_on_p" | j "print(d[0][\"following\"])")" = "True" ]' \
&& ok "alice follows ftuser (Accept arrived)" || ko "Forte's Accept never arrived"
# (ftfriend connects to ftuser now: a channel comments only on what reached it, and only once the other side has let it)
ft_connect ftfriend ftuser@forte.test >/dev/null
echo " posts"
ftc ftuser -o /dev/null -X POST "$FT/api/z/1.0/item/update" --data-urlencode "body=a Forte post $run"
until_true 60 '[ -n "$(p_home_id "a Forte post $run")" ]' && ok "ftuser's post reaches alice's home" || ko "ftuser's post never reached alice"
ft_post=$(p_home_id "a Forte post $run")
ft_post_uri=$(p_status "$ft_post" uri)
p_post=$(curl -s -X POST -H "$PH" "$P/api/v1/statuses" -d "status=a PrivaPub post for Forte $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 60 '[ -n "$(ft_item "$p_post_uri")" ]' && ok "alice's post reaches ftuser" || ko "alice's post never reached Forte"
echo " comments"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses" -d "status=@ftuser@forte.test a PrivaPub comment $run&in_reply_to_id=$ft_post&visibility=public"
until_true 60 '[ "$(ft_sql "select count(*) from item where body like '"'%a PrivaPub comment $run%'"' and thr_parent = '"'$ft_post_uri'"'")" -ge 1 ]' \
&& ok "alice's comment threads under ftuser's post" || ko "alice's comment never reached Forte"
ftc ftuser -o /dev/null -X POST "$FT/api/z/1.0/item/update" --data-urlencode "body=a Forte comment $run" -d "parent=$(ft_item "$p_post_uri")"
until_true 60 'p_replies_have "$p_post_id" "a Forte comment $run"' && ok "ftuser's comment threads under alice's post" || ko "ftuser's comment missing on PrivaPub"
# ftfriend, who alice does not follow, comments in ftuser's thread: Forte, the thread's owner, passes it on to alice
until_true 30 '[ -n "$(ft_item "$ft_post_uri" ftfriend)" ]' >/dev/null
ftc ftfriend -o /dev/null -X POST "$FT/api/z/1.0/item/update" --data-urlencode "body=a word from ftfriend $run" -d "parent=$(ft_item "$ft_post_uri" ftfriend)"
until_true 60 'p_replies_have "$ft_post" "a word from ftfriend $run"' \
&& ok "ftfriend's comment in ftuser's thread reaches alice, passed on" || ko "ftfriend's comment never reached alice"
echo " likes"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses/$ft_post/favourite"
until_true 60 '[ "$(ft_sql "select count(*) from item where verb = '"'Like'"' and thr_parent = '"'$ft_post_uri'"' and item_deleted = 0")" -ge 1 ]' \
&& ok "alice's like lands on Forte" || ko "alice's like never reached Forte"
forte_web ftuser -o /dev/null "$FT/like/$(ft_item "$p_post_uri")?verb=Like"
until_true 60 '[ "$(p_status "$p_post_id" favourites_count)" = "1" ]' && ok "ftuser's like counts on PrivaPub" || ko "ftuser's like never counted"
echo " edits and deletions"
ftc ftuser -o /dev/null -X POST "$FT/api/z/1.0/item/update" -d "post_id=$(ft_item "$ft_post_uri")" --data-urlencode "body=a Forte post $run, edited"
until_true 60 'p_status "$ft_post" content | grep -q "edited"' && ok "ftuser's edit reaches PrivaPub" || ko "ftuser's edit never reached PrivaPub"
curl -s -o /dev/null -X PUT -H "$PH" "$P/api/v1/statuses/$p_post_id" -d "status=a PrivaPub post for Forte $run, edited"
until_true 60 '[ "$(ft_sql "select count(*) from item where mid = '"'$p_post_uri'"' and body like '"'%edited%'"'")" -ge 1 ]' \
&& ok "alice's edit reaches Forte" || ko "alice's edit never reached Forte"
forte_web ftuser -o /dev/null "$FT/item/drop/$(ft_item "$ft_post_uri")"
until_true 60 '[ "$(curl -s -o /dev/null -w "%{http_code}" -H "$PH" "$P/api/v1/statuses/$ft_post")" = "404" ]' \
&& ok "ftuser's deletion reaches PrivaPub" || ko "ftuser's deleted post still on PrivaPub"
curl -s -o /dev/null -X DELETE -H "$PH" "$P/api/v1/statuses/$p_post_id"
until_true 60 '[ -z "$(ft_item "$p_post_uri")" ]' && ok "alice's deletion reaches Forte" || ko "alice's deleted post still on Forte"
echo " unfollow"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$ft_on_p/unfollow"
until_true 20 '[ "$(ft_sql "select count(*) from abconfig where xchan = '"'$alice_uri'"' and cat = '"'their_perms'"' and v = '"'1'"' and chan = (select channel_id from channel where channel_address = '"'ftuser'"')")" = "0" ]' \
&& ok "alice's unfollow reaches Forte" || ko "Forte keeps alice following ftuser after her Undo{Follow}"
echo " statistics"
stats_check forte.test forte