fixed reverse chronological ordering in a pair of FTs clogging the pipeline; added ActivityPub to project; new apps.ap for WebFinger, Actor, Outbox views; apps.lyric.models now contains ap_public_key, ap_private_key fields + ensure_keypair(); new apps.lyric migration accordingly; new in drama.models are to_activity() w. JoinGate, SelectRole, Create compat. & None verb support; new core.urls for /.well-known/webfinger + /ap/ included; cryptography installed, added to reqs.txt; 24 new green UTs & ITs; in sum, project is now read-only ActivityPub node
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
Disco DeDisco
2026-04-02 15:22:04 -04:00
parent 8538f76b13
commit ca38875660
17 changed files with 389 additions and 2 deletions

View File

@@ -0,0 +1,119 @@
import json
from django.test import TestCase
from apps.drama.models import GameEvent, record
from apps.epic.models import Room
from apps.lyric.models import User
class WebFingerTest(TestCase):
def setUp(self):
self.user = User.objects.create(email="actor@test.io", username="actor")
def test_returns_jrd_for_known_user(self):
response = self.client.get(
"/.well-known/webfinger",
{"resource": "acct:actor@earthmanrpg.me"},
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response["Content-Type"], "application/jrd+json")
def test_jrd_links_to_actor_url(self):
response = self.client.get(
"/.well-known/webfinger",
{"resource": "acct:actor@earthmanrpg.me"},
)
data = json.loads(response.content)
hrefs = [link["href"] for link in data["links"]]
self.assertTrue(any("/ap/users/actor/" in href for href in hrefs))
def test_returns_404_for_unknown_user(self):
response = self.client.get(
"/.well-known/webfinger",
{"resource": "acct:nobody@earthmanrpg.me"},
)
self.assertEqual(response.status_code, 404)
def test_returns_400_for_missing_resource(self):
response = self.client.get("/.well-known/webfinger")
self.assertEqual(response.status_code, 400)
class ActorViewTest(TestCase):
def setUp(self):
self.user = User.objects.create(email="actor@test.io", username="actor")
def test_returns_200_for_known_user(self):
response = self.client.get("/ap/users/actor/")
self.assertEqual(response.status_code, 200)
def test_returns_activity_json_content_type(self):
response = self.client.get("/ap/users/actor/")
self.assertEqual(response["Content-Type"], "application/activity+json")
def test_actor_has_required_fields(self):
response = self.client.get("/ap/users/actor/")
data = json.loads(response.content)
self.assertEqual(data["type"], "Person")
self.assertIn("id", data)
self.assertIn("outbox", data)
self.assertIn("publicKey", data)
def test_requires_no_authentication(self):
# AP Actor endpoints must be publicly accessible
self.client.logout()
response = self.client.get("/ap/users/actor/")
self.assertEqual(response.status_code, 200)
def test_returns_404_for_unknown_user(self):
response = self.client.get("/ap/users/nobody/")
self.assertEqual(response.status_code, 404)
class OutboxViewTest(TestCase):
def setUp(self):
self.user = User.objects.create(email="actor@test.io", username="actor")
self.room = Room.objects.create(name="Test Room", owner=self.user)
record(
self.room, GameEvent.SLOT_FILLED, actor=self.user,
slot_number=1, token_type="coin",
token_display="Coin", renewal_days=7,
)
record(
self.room, GameEvent.ROLE_SELECTED, actor=self.user,
role="PC", slot_number=1, role_display="Player",
)
# INVITE_SENT is unsupported — should be excluded from outbox
record(self.room, GameEvent.INVITE_SENT, actor=self.user)
def test_returns_200(self):
response = self.client.get("/ap/users/actor/outbox/")
self.assertEqual(response.status_code, 200)
def test_returns_activity_json_content_type(self):
response = self.client.get("/ap/users/actor/outbox/")
self.assertEqual(response["Content-Type"], "application/activity+json")
def test_outbox_is_ordered_collection(self):
response = self.client.get("/ap/users/actor/outbox/")
data = json.loads(response.content)
self.assertEqual(data["type"], "OrderedCollection")
def test_total_items_excludes_unsupported_verbs(self):
response = self.client.get("/ap/users/actor/outbox/")
data = json.loads(response.content)
# 2 supported events (SLOT_FILLED + ROLE_SELECTED); INVITE_SENT excluded
self.assertEqual(data["totalItems"], 2)
def test_requires_no_authentication(self):
self.client.logout()
response = self.client.get("/ap/users/actor/outbox/")
self.assertEqual(response.status_code, 200)
def test_returns_404_for_unknown_user(self):
response = self.client.get("/ap/users/nobody/outbox/")
self.assertEqual(response.status_code, 404)