Files
maigret/tests/test_mastodon_engine.py
SoxojandGitHub b8b6745dd0 Give thirteen Mastodon instances their own probe back (#3247)
#3121 moved 77 instances onto a shared Mastodon engine that probes /api/v1/accounts/lookup. Real Mastodon answers that 200 or 404, but Akkoma, Sharkey and Iceshrimp answer 200 for a real account and 500 for a missing one. detect_error_page turns any 5xx into CheckError("Server") before checkType is read, so those instances now return UNKNOWN for every name instead of a confident "not found". Eleven of them used to carry a webfinger probe of their own and lost it to the engine.

Two more entries lost a working check the same way. polymaths.social answers the lookup path 401 for every name, claimed or not, and foxes.day was a message check on the profile page looking for "username":.

#3107 made a field the entry states win over the engine, so these thirteen state their old probe and keep the engine for everything else. The other 64 instances are untouched.

Measured in one run against the patched database, a real account and a made up name on each of the thirteen: 26 verdicts out of 26 correct, claimed for the real name and available for the made up one. The test from #3121 that pinned foxes.day to the engine now pins all thirteen to their own probe, so a regeneration cannot quietly take them away again.
2026-10-10 18:46:57 +03:00

202 lines
6.7 KiB
Python

"""Shared Mastodon engine: lookup urlProbe lives on the engine, inside site."""
from unittest.mock import Mock
from maigret.checking import make_site_result
from maigret.sites import MaigretEngine, MaigretSite
# Hosts that use the same /@{username} profile URL shape but are not Mastodon.
# Verified by GET /api/v1/instance — they do not return an instance document.
URL_SHAPE_LOOKALIKES = ("TikTok", "Threads", "Figma")
# Pychess uses /@/{username} (note the extra slash) and is issue #3100 — leave it.
PYCHESS_URL = "https://www.pychess.org/@/{username}"
def _mastodon_engine() -> MaigretEngine:
return MaigretEngine(
"Mastodon",
{
"name": "Mastodon",
"site": {
"checkType": "status_code",
"url": "{urlMain}/@{username}",
"urlProbe": "{urlMain}/api/v1/accounts/lookup?acct={username}",
},
},
)
def test_engine_fields_live_inside_site():
"""update_from_engine copies engine.site only. A sibling key is ignored.
That is how 34 vBulletin sites ended up with empty presenseStrs: the
strings were declared next to "site", not inside it.
"""
engine = _mastodon_engine()
assert list(engine.site.keys()) == ["checkType", "url", "urlProbe"]
assert "urlProbe" not in {k for k in engine.json if k != "site"}
assert engine.site["url"] == "{urlMain}/@{username}"
assert engine.site["urlProbe"] == "{urlMain}/api/v1/accounts/lookup?acct={username}"
def test_site_inherits_lookup_urlprobe_from_engine():
site = MaigretSite(
"ExampleMastodon",
{
"engine": "Mastodon",
"urlMain": "https://example.social",
"usernameClaimed": "alice",
"usernameUnclaimed": "noonewouldeverusethis7",
},
)
site.update_from_engine(_mastodon_engine())
assert site.check_type == "status_code"
assert site.url == "{urlMain}/@{username}"
assert site.url_probe == "{urlMain}/api/v1/accounts/lookup?acct={username}"
stripped = site.strip_engine_data().json
assert stripped["engine"] == "Mastodon"
assert "url" not in stripped
assert "urlProbe" not in stripped
assert "checkType" not in stripped
def test_make_site_result_uses_lookup_probe_and_profile_url():
site = MaigretSite(
"ExampleMastodon",
{"urlMain": "https://example.social"},
)
site.update_from_engine(_mastodon_engine())
checker = Mock()
checker.prepare.return_value = None
results = make_site_result(
site,
"alice",
{
"id_type": "username",
"parsing": False,
"timeout": 1,
"forced": False,
"checkers": {"": lambda: checker},
},
Mock(),
)
assert results["url_user"] == "https://example.social/@alice"
assert (
results["url_probe"]
== "https://example.social/api/v1/accounts/lookup?acct=alice"
)
def test_shipped_engine_matches_template(default_db):
engine = default_db.engines_dict["Mastodon"]
assert engine.site == _mastodon_engine().site
# nothing inherited except through site — no stray top-level check fields
assert list(engine.site.keys()) == ["checkType", "url", "urlProbe"]
def test_confirmed_instances_use_engine(default_db):
mastodon_sites = [s for s in default_db.sites if s.engine == "Mastodon"]
assert len(mastodon_sites) >= 70
names = {s.name for s in mastodon_sites}
for expected in (
"mastodon.social",
"social.tchncs.de",
"Framapiaf",
"infosec.exchange",
"hachyderm.io",
"c.im",
"Fosstodon",
"fuzzies.wtf",
):
assert expected in names
site = default_db.sites_dict[expected]
assert site.check_type == "status_code"
assert site.url == "{urlMain}/@{username}"
assert site.url_probe == "{urlMain}/api/v1/accounts/lookup?acct={username}"
assert not site.url_main.endswith("/")
# disabled instance still moved so a later re-enable gets the probe
assert default_db.sites_dict["fuzzies.wtf"].disabled is True
# urlMain used to point at chaos.social; the engine templates need the
# host from the profile URL.
assert default_db.sites_dict["mastodon.social"].url_main == (
"https://mastodon.social"
)
def test_lookalikes_and_pychess_are_not_on_the_engine(default_db):
for name in URL_SHAPE_LOOKALIKES:
site = default_db.sites_dict[name]
assert site.engine != "Mastodon"
assert "/@{username}" in site.url
assert not (site.url_probe or "").endswith("accounts/lookup?acct={username}")
pychess = default_db.sites_dict["Pychess"]
assert pychess.engine != "Mastodon"
assert pychess.url == PYCHESS_URL
# unverified /api/v1/instance (TLS failure from this environment)
chaos = default_db.sites_dict["chaos.social"]
assert chaos.engine != "Mastodon"
# Instances whose own probe beats the engine's lookup: the eleven forks answer
# /api/v1/accounts/lookup with 500 instead of 404 for a missing account, which
# detect_error_page turns into an error, and the last two never answered it.
WEBFINGER_FORKS = (
"capivarinha.club",
"fedi.social",
"hai.z0ne.social",
"ibe.social",
"milu.moe",
"minazukey.uk",
"mk.absturztau.be",
"plasmatrap.com",
"sakurajima.social",
"stelpolva.moe",
"transfem.social",
)
def test_forks_keep_their_own_probe(default_db):
"""Thirteen entries state a probe of their own and the engine must not win.
Without the override they inherit a probe their server answers with 500 or
401, and the check reports UNKNOWN for every name.
"""
for name in WEBFINGER_FORKS:
site = default_db.sites_dict[name]
assert site.engine == "Mastodon"
assert site.check_type == "status_code"
assert site.url_probe == (
f"https://{name}/.well-known/webfinger?resource=acct:{{username}}@{name}"
)
foxes = default_db.sites_dict["foxes.day"]
assert foxes.engine == "Mastodon"
assert foxes.check_type == "message"
assert foxes.url_probe == "https://foxes.day/@{username}"
assert foxes.presense_strs == ['"username":']
polymaths = default_db.sites_dict["polymaths.social"]
assert polymaths.engine == "Mastodon"
assert polymaths.url_probe == "https://polymaths.social/@{username}"
# everyone else on the engine still takes the lookup probe
overrides = set(WEBFINGER_FORKS) | {"foxes.day", "polymaths.social"}
inheriting = [
s
for s in default_db.sites
if s.engine == "Mastodon" and s.name not in overrides
]
assert len(inheriting) >= 60
for site in inheriting:
assert site.url_probe == "{urlMain}/api/v1/accounts/lookup?acct={username}"