Files
SocialPub/tools/pasture/town/selftest.py
T

157 lines
6.3 KiB
Python
Raw Normal View History

"""Each driver against its own server, before any federation is trusted: two accounts of the platform make, like,
boost, reply to, vote on, edit and delete objects, and the database view (`stored`) must agree with what was done and
with the API. A failing selftest means the driver is wrong, not the federation."""
import time
import traceback
from dialects import driver
from dialects.base import Account, PostSpec, Unsupported
import state
PASSWORD = "Town-Selftest-Pass-7!"
def wait(fn, tries=20, delay=1.0):
for _ in range(tries):
value = fn()
if value:
return value
time.sleep(delay)
return fn()
class Result:
def __init__(self, peer):
self.peer = peer
self.passed = 0
self.failed = 0
self.skipped = 0
def check(self, cond, what):
if cond:
self.passed += 1
print(f" ok {what}")
else:
self.failed += 1
print(f" FAIL {what}")
def skip(self, what):
self.skipped += 1
print(f" n/a {what}")
def accounts(peer):
root = "st_root" if peer == "privapub" else None
# Mastodon reserves names that contain its own, so the platform goes in as its two-letter code
import gen
short = gen.SHORT.get(peer, peer[:2])
return [Account(peer, f"st_a_{short}", PASSWORD, name="Selftest A", bio="selftest", root=root,
fields=[("site", "pasture")], avatar_seed=f"st-a-{peer}"),
Account(peer, f"st_b_{short}", PASSWORD, name="Selftest B", root=root)]
def run(peers):
total_failed = 0
for peer in peers:
print(peer)
r = Result(peer)
try:
if peer == "lemmy":
lemmy(r)
else:
microblog(peer, r)
except Exception as e:
r.check(False, f"selftest stopped: {e!r}")
traceback.print_exc()
print(f" {r.passed} passed, {r.failed} failed, {r.skipped} not applicable")
total_failed += r.failed
return 1 if total_failed else 0
def microblog(peer, r):
d = driver(peer)
a, b = state.provision(peer, accounts(peer))
r.check(a.token and b.token, "both accounts signed in")
try:
d.update_profile(a, a.account)
r.check(True, "profile updated (name, bio, fields, avatar)")
except Unsupported as e:
r.skip(str(e))
made = d.post(a, PostSpec(text=f"selftest post on {peer} (st1)", tags=["townselftest"]))
row = wait(lambda: d.stored([made.uri])[made.uri].exists and d.stored([made.uri])[made.uri])
r.check(row and row.exists, f"the post is in the database under its uri {made.uri}")
r.check(row and str(row.local_id) == str(made.local_id), f"database id {row and row.local_id} is the API id {made.local_id}")
r.check(row and row.visibility == "public", f"stored visibility public (got {row and row.visibility})")
d.like(b, made.uri)
r.check(wait(lambda: d.stored([made.uri])[made.uri].likes == 1), "a like counts once")
try:
d.boost(b, made.uri)
r.check(wait(lambda: d.stored([made.uri])[made.uri].boosts == 1), "a boost counts once")
except Unsupported as e:
r.skip(str(e))
reply = d.post(b, PostSpec(text=f"selftest reply on {peer} (st2)", reply_to_uri=made.uri))
rrow = wait(lambda: d.stored([reply.uri])[reply.uri].exists and d.stored([reply.uri])[reply.uri])
r.check(rrow and rrow.parent_uri == made.uri, f"the reply names its parent (got {rrow and rrow.parent_uri})")
r.check(wait(lambda: (d.stored([made.uri])[made.uri].replies or 0) >= 1), "the parent counts the reply")
if "poll" in d.caps:
poll = d.post(a, PostSpec(text=f"selftest poll on {peer} (st3)", poll={"options": ["tea", "coffee"], "expires_in": 86400}))
d.vote(b, poll.uri, [1])
r.check(wait(lambda: (d.stored([poll.uri])[poll.uri].votes or [None, None])[1] == 1),
f"a vote counts on the second option (got {d.stored([poll.uri])[poll.uri].votes})")
else:
r.skip(f"{peer} posts no polls")
private = d.post(a, PostSpec(text=f"selftest followers-only on {peer} (st4)", visibility="followers"))
prow = d.stored([private.uri])[private.uri]
r.check(prow.visibility == "followers", f"stored visibility followers (got {prow.visibility})")
seen = d.seen(b, [made.uri, private.uri])
r.check(seen[made.uri], "a non-follower sees the public post")
r.check(not seen[private.uri], "a non-follower does not see the followers-only post")
try:
d.edit(a, made.uri, PostSpec(text=f"selftest post on {peer}, edited (st1)"))
r.check(wait(lambda: d.stored([made.uri])[made.uri].edited), "the edit is recorded")
except Unsupported as e:
r.skip(str(e))
d.delete(b, reply.uri)
gone = wait(lambda: (lambda x: not x.exists or x.deleted)(d.stored([reply.uri])[reply.uri]))
r.check(gone, "a deleted reply is gone or tombstoned")
try:
d.react(b, made.uri, "🎉")
r.check(wait(lambda: (d.stored([made.uri])[made.uri].reactions or {}).get("🎉", 0) >= 1), "a reaction is stored")
except Unsupported as e:
r.skip(str(e))
def lemmy(r):
d = driver("lemmy")
a, b = state.provision("lemmy", accounts("lemmy"))
r.check(a.token and b.token, "both accounts signed in")
name = f"stc{int(time.time())}"
c = d.community(a, name, "Selftest community")
thread = d.post(a, PostSpec(text="selftest thread body (st1)", title="Selftest thread", group=c["id"]))
row = d.stored([thread.uri])[thread.uri]
r.check(row.exists and row.local_id == thread.local_id, f"the thread is in the database ({row.local_id})")
d.like(b, thread.uri)
r.check(wait(lambda: d.stored([thread.uri])[thread.uri].likes >= 2), "an upvote counts (with the author's own)")
comment = d.post(b, PostSpec(text="selftest comment (st2)", reply_to_uri=thread.uri))
crow = d.stored([comment.uri])[comment.uri]
r.check(crow.parent_uri == thread.uri, f"the comment names its thread (got {crow.parent_uri})")
reply = d.post(a, PostSpec(text="selftest comment reply (st3)", reply_to_uri=comment.uri))
rrow = d.stored([reply.uri])[reply.uri]
r.check(rrow.parent_uri == comment.uri, f"a nested comment names its parent comment (got {rrow.parent_uri})")
d.downvote(a, comment.uri)
r.check(wait(lambda: (d.stored([comment.uri])[comment.uri].reactions or {}).get("down") == 1), "a downvote counts")
d.edit(a, thread.uri, PostSpec(text="selftest thread body, edited (st1)"))
r.check(wait(lambda: d.stored([thread.uri])[thread.uri].edited), "the edit is recorded")
pm = d.post(a, PostSpec(text="selftest private message (st4)", visibility="direct",
mentions=[f"{b.account.username}@lemmy.test"]))
r.check(d.stored([pm.uri])[pm.uri].exists, "a private message is in the database")
d.delete(a, reply.uri)
r.check(wait(lambda: d.stored([reply.uri])[reply.uri].deleted), "a deleted comment is marked deleted")