Add support for Move activity
This commit is contained in:
parent
4ae198d074
commit
02c09f2363
6 changed files with 196 additions and 6 deletions
|
@ -68,6 +68,21 @@ def build_accept_activity(
|
|||
}
|
||||
|
||||
|
||||
def build_move_activity(
|
||||
from_remote_actor: actor.RemoteActor,
|
||||
for_remote_object: actor.RemoteActor,
|
||||
outbox_public_id: str | None = None,
|
||||
) -> ap.RawObject:
|
||||
return {
|
||||
"@context": ap.AS_CTX,
|
||||
"type": "Move",
|
||||
"id": from_remote_actor.ap_id + "/move/" + (outbox_public_id or uuid4().hex),
|
||||
"actor": from_remote_actor.ap_id,
|
||||
"object": from_remote_actor.ap_id,
|
||||
"target": for_remote_object.ap_id,
|
||||
}
|
||||
|
||||
|
||||
def build_note_object(
|
||||
from_remote_actor: actor.RemoteActor,
|
||||
outbox_public_id: str | None = None,
|
||||
|
@ -123,11 +138,13 @@ class RemoteActorFactory(factory.Factory):
|
|||
"base_url",
|
||||
"username",
|
||||
"public_key",
|
||||
"also_known_as",
|
||||
)
|
||||
|
||||
class Params:
|
||||
icon_url = None
|
||||
summary = "I like unit tests"
|
||||
also_known_as: list[str] = []
|
||||
|
||||
ap_actor = factory.LazyAttribute(
|
||||
lambda o: {
|
||||
|
@ -152,6 +169,7 @@ class RemoteActorFactory(factory.Factory):
|
|||
"owner": o.base_url,
|
||||
"publicKeyPem": o.public_key,
|
||||
},
|
||||
"alsoKnownAs": o.also_known_as,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -240,3 +258,8 @@ class InboxObjectFactory(factory.alchemy.SQLAlchemyModelFactory):
|
|||
class FollowerFactory(factory.alchemy.SQLAlchemyModelFactory):
|
||||
class Meta(BaseModelMeta):
|
||||
model = models.Follower
|
||||
|
||||
|
||||
class FollowingFactory(factory.alchemy.SQLAlchemyModelFactory):
|
||||
class Meta(BaseModelMeta):
|
||||
model = models.Following
|
||||
|
|
|
@ -21,6 +21,7 @@ from tests.utils import run_async
|
|||
from tests.utils import setup_inbox_delete
|
||||
from tests.utils import setup_remote_actor
|
||||
from tests.utils import setup_remote_actor_as_follower
|
||||
from tests.utils import setup_remote_actor_as_following
|
||||
|
||||
|
||||
async def _process_next_incoming_activity(db_session: AsyncSession) -> None:
|
||||
|
@ -353,3 +354,72 @@ def test_inbox__actor_is_blocked(
|
|||
)
|
||||
== 0
|
||||
)
|
||||
|
||||
|
||||
def test_inbox__move_activity(
|
||||
db: Session,
|
||||
client: TestClient,
|
||||
respx_mock: respx.MockRouter,
|
||||
) -> None:
|
||||
# Given a remote actor
|
||||
ra = setup_remote_actor(respx_mock)
|
||||
|
||||
# Which is followed by the local actor
|
||||
following = setup_remote_actor_as_following(ra)
|
||||
old_actor = following.actor
|
||||
assert old_actor
|
||||
assert following.outbox_object
|
||||
follow_id = following.outbox_object.ap_id
|
||||
|
||||
# When receiving a Move activity
|
||||
new_ra = setup_remote_actor(
|
||||
respx_mock,
|
||||
base_url="https://new-account.com",
|
||||
also_known_as=[ra.ap_id],
|
||||
)
|
||||
move_activity = RemoteObject(
|
||||
factories.build_move_activity(ra, new_ra),
|
||||
ra,
|
||||
)
|
||||
|
||||
with mock_httpsig_checker(ra):
|
||||
response = client.post(
|
||||
"/inbox",
|
||||
headers={"Content-Type": ap.AS_CTX},
|
||||
json=move_activity.ap_object,
|
||||
)
|
||||
|
||||
# Then the server returns a 204
|
||||
assert response.status_code == 202
|
||||
|
||||
run_async(_process_next_incoming_activity)
|
||||
|
||||
# And the Move activity was saved in the inbox
|
||||
inbox_activity = db.execute(select(models.InboxObject)).scalar_one()
|
||||
assert inbox_activity.ap_type == "Move"
|
||||
assert inbox_activity.actor_id == old_actor.id
|
||||
|
||||
# And the following actor was deleted
|
||||
assert db.scalar(select(func.count(models.Following.id))) == 0
|
||||
|
||||
# And the follow was undone
|
||||
assert (
|
||||
db.scalar(
|
||||
select(func.count(models.OutboxObject.id)).where(
|
||||
models.OutboxObject.ap_type == "Undo",
|
||||
models.OutboxObject.activity_object_ap_id == follow_id,
|
||||
)
|
||||
)
|
||||
== 1
|
||||
)
|
||||
|
||||
# And the new account was followed
|
||||
assert (
|
||||
db.scalar(
|
||||
select(func.count(models.OutboxObject.id)).where(
|
||||
models.OutboxObject.ap_type == "Follow",
|
||||
models.OutboxObject.activity_object_ap_id == new_ra.ap_id,
|
||||
)
|
||||
)
|
||||
== 1
|
||||
)
|
||||
|
|
|
@ -40,11 +40,16 @@ def generate_admin_session_cookies() -> dict[str, Any]:
|
|||
return {"session": session_serializer.dumps({"is_logged_in": True})}
|
||||
|
||||
|
||||
def setup_remote_actor(respx_mock: respx.MockRouter) -> actor.RemoteActor:
|
||||
def setup_remote_actor(
|
||||
respx_mock: respx.MockRouter,
|
||||
base_url="https://example.com",
|
||||
also_known_as=None,
|
||||
) -> actor.RemoteActor:
|
||||
ra = factories.RemoteActorFactory(
|
||||
base_url="https://example.com",
|
||||
base_url=base_url,
|
||||
username="toto",
|
||||
public_key="pk",
|
||||
also_known_as=also_known_as if also_known_as else [],
|
||||
)
|
||||
respx_mock.get(ra.ap_id + "/outbox").mock(
|
||||
return_value=httpx.Response(
|
||||
|
@ -86,6 +91,30 @@ def setup_remote_actor_as_follower(ra: actor.RemoteActor) -> models.Follower:
|
|||
return follower
|
||||
|
||||
|
||||
def setup_remote_actor_as_following(ra: actor.RemoteActor) -> models.Following:
|
||||
actor = factories.ActorFactory.from_remote_actor(ra)
|
||||
|
||||
follow_id = uuid4().hex
|
||||
follow_from_outbox = RemoteObject(
|
||||
factories.build_follow_activity(
|
||||
from_remote_actor=LOCAL_ACTOR,
|
||||
for_remote_actor=ra,
|
||||
outbox_public_id=follow_id,
|
||||
),
|
||||
LOCAL_ACTOR,
|
||||
)
|
||||
outbox_object = factories.OutboxObjectFactory.from_remote_object(
|
||||
follow_id, follow_from_outbox
|
||||
)
|
||||
|
||||
following = factories.FollowingFactory(
|
||||
outbox_object_id=outbox_object.id,
|
||||
actor_id=actor.id,
|
||||
ap_actor_id=actor.ap_id,
|
||||
)
|
||||
return following
|
||||
|
||||
|
||||
def setup_inbox_delete(
|
||||
actor: models.Actor, deleted_object_ap_id: str
|
||||
) -> models.InboxObject:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue