diff --git a/app/activitypub.py b/app/activitypub.py index dd445c9..4cfa6b5 100644 --- a/app/activitypub.py +++ b/app/activitypub.py @@ -87,6 +87,10 @@ class BaseActor: def ap_actor(self) -> RawObject: return self._ap_actor + @property + def ap_id(self) -> RawObject: + return self._ap_actor["id"] + @property def inbox_url(self) -> str: return self.ap_actor["inbox"] diff --git a/tests/factories.py b/tests/factories.py new file mode 100644 index 0000000..aacae64 --- /dev/null +++ b/tests/factories.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +import factory +import app.activitypub as ap +from app import models + +from sqlalchemy import orm + +from app import activitypub as ap +from app import actor +from app import models +from app.database import SessionLocal + +_Session = orm.scoped_session(SessionLocal) + + +class BaseModelMeta: + sqlalchemy_session = _Session + sqlalchemy_session_persistence = "commit" + + +class RemoteActorFactory(factory.Factory): # pyright: disable + class Meta: + model = ap.BaseActor + exclude = ( + "base_url", + "username", + "public_key", + "also_known_as", + ) + + ap_actor = factory.LazyAttribute( + lambda o: { + "@context": ap.AS_CTX, + "type": "Person", + "id": o.base_url, + "following": o.base_url + "/following", + "followers": o.base_url + "/followers", + "inbox": o.base_url + "/inbox", + "outbox": o.base_url + "/outbox", + "preferredUsername": o.username, + "name": o.username, + "summary": "test user", + "endpoints": {}, + "url": o.base_url, + "manuallyApprovesFollowers": False, + "attachment": [], + "icon": {}, + "publicKey": { + "id": f"{o.base_url}#main-key", + "owner": o.base_url, + "publicKeyPem": o.public_key, + }, + "alsoKnownAs": [], + } + ) + + +class ActorFactory(factory.alchemy.SQLAlchemyModelFactory): + class Meta(BaseModelMeta): + model = models.Actor + + ap_type = "Person" + ap_id = "stub" diff --git a/tests/test_inbox.py b/tests/test_inbox.py index 6a7fca5..c4da4a7 100644 --- a/tests/test_inbox.py +++ b/tests/test_inbox.py @@ -2,16 +2,23 @@ import pytest import fastapi import respx +import httpx +from uuid import uuid4 +from tests import factories # type: ignore + +from unittest import mock from app.main import app from app.utils import precheck from fastapi.testclient import TestClient from sqlalchemy.orm import Session from app import activitypub as ap, httpsig from app.config import AP_CONTENT_TYPE +from app import models +from sqlalchemy import select -def test_inbox_announce_request( +def test_inbox_follow_request( db: Session, client: TestClient, respx_mock: respx.MockRouter, @@ -22,14 +29,38 @@ def test_inbox_announce_request( ) -> bool: return True - app.dependency_overrides[precheck.inbox_prechecker] = inbox_prechecker - - response = client.post( - "/inbox", - headers={"Content-Type": AP_CONTENT_TYPE}, - json={"stub": 1}, + ra = factories.RemoteActorFactory( + base_url="https://example.com", + username="test", + public_key="pk", ) - print(response.headers) - print(response.content) - assert response.status_code == 200 + ap_id = ra.ap_id # type: ignore + respx_mock.get(ap_id).mock(return_value=httpx.Response(200,json=ra.ap_actor)) + respx_mock.post(ap_id + "/inbox").mock(return_value=httpx.Response(202)) + + app.dependency_overrides[precheck.inbox_prechecker] = inbox_prechecker + + with mock.patch("app.boxes.MANUALLY_APPROVES_FOLLOWERS", False): + response = client.post( + "/inbox", + headers={"Content-Type": AP_CONTENT_TYPE}, + json={ + "@context": ap.AS_CTX, + "type": "Follow", + "id": ap_id + "/follow/" + (uuid4().hex), + "actor": ap_id, + "object": ap.ME["id"], + }, + ) + + assert response.status_code == 202 + + saved_actor = db.execute(select(models.Actor)).scalar_one() + assert saved_actor.ap_id == ap_id + + follower_actor = db.execute(select(models.Follower)).scalar_one() + assert follower_actor.ap_actor_id == ap_id + + outbox_object = db.execute(select(models.OutboxObject)).scalar_one() + assert outbox_object.ap_type == "Accept"