Files
SocialPub/tools/pasture/scenarios/ktistec.sh
T

125 lines
11 KiB
Bash
Raw Normal View History

2026-10-06 14:09:29 +02:00
# Ktistec 3.13.0: follows both ways; posts both ways; replies both ways; likes and boosts both ways with their undos; a
# poll from Ktistec and alice's vote counted there; FEP-044f quotes both ways; edits and deletions both ways; the
# unfollow; statistics. Ktistec speaks part of the Mastodon API (posts, replies, likes, boosts, timelines,
# relationships) with the bearer token of a signed-in session; what it has no route for there (follows, polls, edits,
# deletions, quotes) goes through its own outbox API, and what it holds (likes, boosts and votes included: its API counts
# none) is read from a copy of its SQLite database.
. "$here/peers/ktistec.sh"
K=https://ktistec.test:6443
KT=$(cat "$here/.state/ktistec/ktuser.token" 2>/dev/null)
kc() { site ktistec.test -s -H "Authorization: Bearer $KT" "$@"; }
# kn <activity>: an activity put in ktuser's outbox (Ktistec's own API), answering its status code
kn() { kc -o /dev/null -w '%{http_code}' -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' "$K/actors/ktuser/outbox" -d "$1"; }
# kt_sql <query>: Ktistec's database, read from a copy
kt_sql() {
local d
d=$(mktemp -d)
podman cp pasture-ktistec:/db/ktistec.db "$d/" 2>/dev/null
python3 -c "import sqlite3, sys; print('\n'.join('|'.join(map(str, r)) for r in sqlite3.connect(sys.argv[1]).execute(sys.argv[2])))" "$d/ktistec.db" "$1"
rm -rf "$d"
}
k_home_id() { kc "$K/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 '')), ''))"; }
k_status() { kc "$K/api/v1/statuses/$1" | j "print(d.get('$2'))"; }
# k_by_alice <object>: alice's likes and boosts of it that Ktistec holds (its API's counts stay at 0)
k_by_alice() { kt_sql "select count(*) from activities where type in ('ActivityPub::Activity::Like', 'ActivityPub::Activity::Announce') and actor_iri = '$alice_uri' and object_iri = '$1' and undone_at is null"; }
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 "ktistec"
[ -n "$(kc "$K/api/v1/accounts/verify_credentials" | j "print(d.get('username') or '')")" ] && ok "Ktistec's API as ktuser" \
|| { ko "Ktistec's API ($(kc "$K/api/v1/accounts/verify_credentials" | head -c 200))"; return 1; }
PT=$(privapub_token alice_ktistec)
PH="Authorization: Bearer $PT"
[ -n "$PT" ] && ok "PrivaPub token for alice_ktistec" || { ko "PrivaPub token for alice_ktistec"; return 1; }
alice_uri="$(curl -s -H "$PH" "$P/api/v1/accounts/verify_credentials" | j "print(d['url'])" | sed 's|/@|/peasants/|')"
kt_uri=https://ktistec.test/actors/ktuser
run=$(date +%s)
echo " follows"
kt_on_p=$(curl -s -H "$PH" "$P/api/v2/search?q=ktuser@ktistec.test&resolve=true&type=accounts" | j "print(d['accounts'][0]['id'])")
[ -n "$kt_on_p" ] && ok "PrivaPub resolves ktuser" || ko "PrivaPub cannot resolve ktuser"
alice_on_k=$(kc "$K/api/v1/accounts/lookup?acct=alice_ktistec@privapub.test" | j "print(d['id'])")
[ -n "$alice_on_k" ] && ok "Ktistec resolves alice" || ko "Ktistec cannot resolve alice"
# (a run before leaves both following: both follows below are made anew)
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$kt_on_p/unfollow"
kc -o /dev/null -X POST "$K/api/v1/accounts/$alice_on_k/unfollow"
until_true 30 '[ "$(curl -s -H "$PH" "$P/api/v1/accounts/relationships?id[]=$kt_on_p" | j "print(d[0][\"followed_by\"])")" = "False" ]' >/dev/null
kn "{\"type\":\"Follow\",\"object\":\"$alice_uri\"}" >/dev/null
until_true 60 '[ "$(curl -s -H "$PH" "$P/api/v1/accounts/relationships?id[]=$kt_on_p" | j "print(d[0][\"followed_by\"])")" = "True" ]' \
&& ok "ktuser follows alice" || ko "Ktistec's follow never reached alice"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$kt_on_p/follow"
until_true 60 '[ "$(curl -s -H "$PH" "$P/api/v1/accounts/relationships?id[]=$kt_on_p" | j "print(d[0][\"following\"])")" = "True" ]' \
&& ok "alice follows ktuser (Accept arrived)" || ko "Ktistec's Accept never arrived"
echo " posts"
k_post=$(kc -X POST "$K/api/v1/statuses" -d "status=a Ktistec post $run&visibility=public" | j "print(d['id'])")
until_true 60 '[ -n "$(p_home_id "a Ktistec post $run")" ]' && ok "ktuser's post reaches alice's home" || ko "ktuser's post never reached alice"
k_post_on_p=$(p_home_id "a Ktistec post $run")
k_post_uri=$(p_status "$k_post_on_p" uri)
p_post=$(curl -s -X POST -H "$PH" "$P/api/v1/statuses" -d "status=a PrivaPub post for Ktistec $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 "$(k_home_id "a PrivaPub post for Ktistec $run")" ]' && ok "alice's post reaches ktuser's home" || ko "alice's post never reached Ktistec"
p_post_on_k=$(k_home_id "a PrivaPub post for Ktistec $run")
echo " replies"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses" -d "status=@ktuser@ktistec.test a PrivaPub reply $run&in_reply_to_id=$k_post_on_p&visibility=public"
until_true 60 'kc "$K/api/v1/statuses/$k_post/context" | grep -q "a PrivaPub reply $run"' \
&& ok "alice's reply threads under ktuser's post" || ko "alice's reply never reached Ktistec"
kc -o /dev/null -X POST "$K/api/v1/statuses" -d "status=@alice_ktistec@privapub.test a Ktistec reply $run&in_reply_to_id=$p_post_on_k&visibility=public"
until_true 60 'p_replies_have "$p_post_id" "a Ktistec reply $run"' && ok "ktuser's reply threads under alice's post" || ko "ktuser's reply missing on PrivaPub"
echo " likes and boosts"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses/$k_post_on_p/favourite"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses/$k_post_on_p/reblog"
until_true 60 '[ "$(k_by_alice "$k_post_uri")" = "2" ]' && ok "alice's like and boost land on Ktistec" || ko "alice's like or boost never reached Ktistec"
kc -o /dev/null -X POST "$K/api/v1/statuses/$p_post_on_k/favourite"
kc -o /dev/null -X POST "$K/api/v1/statuses/$p_post_on_k/reblog"
until_true 60 '[ "$(p_status "$p_post_id" favourites_count) $(p_status "$p_post_id" reblogs_count)" = "1 1" ]' \
&& ok "ktuser's like and boost count on PrivaPub" || ko "ktuser's like or boost not counted on PrivaPub"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses/$k_post_on_p/unfavourite"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses/$k_post_on_p/unreblog"
until_true 60 '[ "$(k_by_alice "$k_post_uri")" = "0" ]' && ok "alice's undos reach Ktistec" || ko "alice's undos not applied on Ktistec"
kc -o /dev/null -X POST "$K/api/v1/statuses/$p_post_on_k/unfavourite"
kc -o /dev/null -X POST "$K/api/v1/statuses/$p_post_on_k/unreblog"
until_true 60 '[ "$(p_status "$p_post_id" favourites_count) $(p_status "$p_post_id" reblogs_count)" = "0 0" ]' \
&& ok "ktuser's undos reach PrivaPub" || ko "ktuser's undos not applied on PrivaPub"
echo " polls"
kn "{\"type\":\"Publish\",\"content\":\"tea or coffee, Ktistec asks $run\",\"poll-options\":[\"tea\",\"coffee\"],\"poll-duration\":\"3600\",\"visibility\":\"public\"}" >/dev/null
until_true 60 '[ "$(curl -s -H "$PH" "$P/api/v1/timelines/home?limit=40" | j "print(next((len(s[\"poll\"][\"options\"]) for s in d if s[\"poll\"] and \"Ktistec asks $run\" in s[\"content\"]), 0))")" = "2" ]' \
&& ok "ktuser's poll reaches alice as a poll" || ko "ktuser's poll never reached alice as one"
k_poll_on_p=$(curl -s -H "$PH" "$P/api/v1/timelines/home?limit=40" | j "print(next((s['poll']['id'] for s in d if s['poll'] and 'Ktistec asks $run' in s['content']), ''))")
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/polls/$k_poll_on_p/votes" -d 'choices[]=1'
# (Ktistec counts a poll of its own from the votes it keeps, which its API does not show)
until_true 60 '[ "$(kt_sql "select v.name from objects v join objects q on q.iri = v.in_reply_to_iri where v.special = '"'vote'"' and v.attributed_to_iri = '"'$alice_uri'"' and q.content like '"'%Ktistec asks $run%'"'")" = "coffee" ]' \
&& ok "alice's vote counts on Ktistec" || ko "alice's vote never counted on Ktistec"
echo " quotes"
a_quote=$(curl -s -X POST -H "$PH" "$P/api/v1/statuses" -d "status=quoting Ktistec $run&visibility=public&quoted_status_id=$k_post_on_p" | j "print(d['id'])")
until_true 60 '[ "$(curl -s -H "$PH" "$P/api/v1/statuses/$a_quote" | j "print((d.get(\"quote\") or {}).get(\"state\"))")" = "accepted" ]' \
&& ok "Ktistec approves alice's quote (FEP-044f)" || ko "alice's quote of a Ktistec post not approved ($(curl -s -H "$PH" "$P/api/v1/statuses/$a_quote" | j "print(d.get('quote'))"))"
kc -o /dev/null -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' "$K/remote/objects/$p_post_on_k/quote" \
-d "{\"content\":\"Ktistec quotes PrivaPub $run\",\"quote\":\"$p_post_uri\",\"visibility\":\"public\"}"
until_true 60 '[ "$(curl -s -H "$PH" "$P/api/v1/timelines/home?limit=40" | j "print(next((((s.get(\"quote\") or {}).get(\"quoted_status\") or {}).get(\"id\") for s in d if \"Ktistec quotes PrivaPub $run\" in s[\"content\"]), None))")" = "$p_post_id" ]' \
&& ok "PrivaPub approves ktuser's quote, which reaches alice quoting her post" || ko "ktuser's quote of alice's post missing or unapproved"
echo " edits and deletions"
kn "{\"type\":\"Publish\",\"object\":\"$k_post_uri\",\"content\":\"a Ktistec post $run, edited\",\"visibility\":\"public\"}" >/dev/null
until_true 60 'p_status "$k_post_on_p" content | grep -q "edited"' && ok "ktuser's edit reaches PrivaPub" || ko "ktuser'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 Ktistec $run, edited"
until_true 60 'k_status "$p_post_on_k" content | grep -q "edited"' && ok "alice's edit reaches Ktistec" || ko "alice's edit never reached Ktistec"
kn "{\"type\":\"Delete\",\"object\":\"$k_post_uri\"}" >/dev/null
until_true 60 '[ "$(curl -s -o /dev/null -w "%{http_code}" -H "$PH" "$P/api/v1/statuses/$k_post_on_p")" = "404" ]' \
&& ok "ktuser's deletion reaches PrivaPub" || ko "ktuser'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 '[ "$(kt_sql "select count(*) from objects where iri = '"'$p_post_uri'"' and deleted_at is null")" = "0" ]' \
&& ok "alice's deletion reaches Ktistec" || ko "alice's deleted post still on Ktistec"
echo " unfollow"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$kt_on_p/unfollow"
until_true 60 '[ "$(kt_sql "select count(*) from relationships where type = '"'Relationship::Social::Follow'"' and from_iri = '"'$alice_uri'"' and to_iri = '"'$kt_uri'"'")" = "0" ]' \
&& ok "alice's unfollow reaches Ktistec" || ko "Ktistec still counts alice following ktuser"
echo " statistics"
stats_check ktistec.test ktistec