2022-06-22 20:11:22 +02:00
|
|
|
from uuid import uuid4
|
|
|
|
|
|
|
|
import httpx
|
2022-06-29 20:43:17 +02:00
|
|
|
import pytest
|
2022-06-22 20:11:22 +02:00
|
|
|
import respx
|
|
|
|
from fastapi.testclient import TestClient
|
2022-06-29 20:43:17 +02:00
|
|
|
from sqlalchemy import select
|
2022-06-22 20:11:22 +02:00
|
|
|
|
|
|
|
from app import models
|
|
|
|
from app.actor import LOCAL_ACTOR
|
|
|
|
from app.ap_object import RemoteObject
|
2022-06-29 20:43:17 +02:00
|
|
|
from app.database import AsyncSession
|
2022-06-24 11:33:05 +02:00
|
|
|
from app.outgoing_activities import _MAX_RETRIES
|
2022-08-10 20:34:36 +02:00
|
|
|
from app.outgoing_activities import fetch_next_outgoing_activity
|
2022-06-24 11:33:05 +02:00
|
|
|
from app.outgoing_activities import new_outgoing_activity
|
|
|
|
from app.outgoing_activities import process_next_outgoing_activity
|
2022-06-22 20:11:22 +02:00
|
|
|
from tests import factories
|
|
|
|
|
|
|
|
|
|
|
|
def _setup_outbox_object() -> models.OutboxObject:
|
|
|
|
ra = factories.RemoteActorFactory(
|
|
|
|
base_url="https://example.com",
|
|
|
|
username="toto",
|
|
|
|
public_key="pk",
|
|
|
|
)
|
|
|
|
|
|
|
|
# And a Follow activity in the outbox
|
|
|
|
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,
|
2022-06-30 00:28:07 +02:00
|
|
|
),
|
|
|
|
LOCAL_ACTOR,
|
2022-06-22 20:11:22 +02:00
|
|
|
)
|
|
|
|
outbox_object = factories.OutboxObjectFactory.from_remote_object(
|
|
|
|
follow_id, follow_from_outbox
|
|
|
|
)
|
|
|
|
return outbox_object
|
|
|
|
|
|
|
|
|
2022-06-29 20:43:17 +02:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
async def test_new_outgoing_activity(
|
|
|
|
async_db_session: AsyncSession,
|
2022-06-22 20:11:22 +02:00
|
|
|
client: TestClient,
|
|
|
|
respx_mock: respx.MockRouter,
|
|
|
|
) -> None:
|
|
|
|
outbox_object = _setup_outbox_object()
|
|
|
|
inbox_url = "https://example.com/inbox"
|
|
|
|
|
2022-06-22 21:15:07 +02:00
|
|
|
if not outbox_object.id:
|
|
|
|
raise ValueError("Should never happen")
|
|
|
|
|
2022-06-22 20:11:22 +02:00
|
|
|
# When queuing the activity
|
2022-06-29 20:43:17 +02:00
|
|
|
outgoing_activity = await new_outgoing_activity(
|
|
|
|
async_db_session, inbox_url, outbox_object.id
|
|
|
|
)
|
2022-07-24 20:27:58 +02:00
|
|
|
await async_db_session.commit()
|
2022-06-22 20:11:22 +02:00
|
|
|
|
2022-06-29 20:43:17 +02:00
|
|
|
assert (
|
|
|
|
await async_db_session.execute(select(models.OutgoingActivity))
|
|
|
|
).scalar_one() == outgoing_activity
|
2022-06-22 20:11:22 +02:00
|
|
|
assert outgoing_activity.outbox_object_id == outbox_object.id
|
|
|
|
assert outgoing_activity.recipient == inbox_url
|
|
|
|
|
|
|
|
|
2022-08-10 20:34:36 +02:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
async def test_process_next_outgoing_activity__no_next_activity(
|
2022-06-22 20:11:22 +02:00
|
|
|
respx_mock: respx.MockRouter,
|
2022-08-10 20:34:36 +02:00
|
|
|
async_db_session: AsyncSession,
|
2022-06-22 20:11:22 +02:00
|
|
|
) -> None:
|
2022-08-11 12:24:17 +02:00
|
|
|
next_activity = await fetch_next_outgoing_activity(async_db_session)
|
2022-08-10 20:34:36 +02:00
|
|
|
assert next_activity is None
|
2022-06-22 20:11:22 +02:00
|
|
|
|
|
|
|
|
2022-08-10 20:34:36 +02:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
async def test_process_next_outgoing_activity__server_200(
|
|
|
|
async_db_session: AsyncSession,
|
2022-06-22 20:11:22 +02:00
|
|
|
respx_mock: respx.MockRouter,
|
|
|
|
) -> None:
|
|
|
|
# And an outgoing activity
|
|
|
|
outbox_object = _setup_outbox_object()
|
|
|
|
|
|
|
|
recipient_inbox_url = "https://example.com/users/toto/inbox"
|
|
|
|
respx_mock.post(recipient_inbox_url).mock(return_value=httpx.Response(204))
|
|
|
|
|
|
|
|
outgoing_activity = factories.OutgoingActivityFactory(
|
|
|
|
recipient=recipient_inbox_url,
|
|
|
|
outbox_object_id=outbox_object.id,
|
2022-07-08 21:17:08 +02:00
|
|
|
inbox_object_id=None,
|
2022-07-10 14:29:28 +02:00
|
|
|
webmention_target=None,
|
|
|
|
)
|
|
|
|
|
|
|
|
# When processing the next outgoing activity
|
|
|
|
# Then it is processed
|
2022-08-11 12:24:17 +02:00
|
|
|
next_activity = await fetch_next_outgoing_activity(async_db_session)
|
2022-08-10 20:34:36 +02:00
|
|
|
assert next_activity
|
|
|
|
await process_next_outgoing_activity(async_db_session, next_activity)
|
2022-07-10 14:29:28 +02:00
|
|
|
|
|
|
|
assert respx_mock.calls.call_count == 1
|
|
|
|
|
2022-08-10 20:34:36 +02:00
|
|
|
outgoing_activity = (
|
|
|
|
await async_db_session.execute(select(models.OutgoingActivity))
|
|
|
|
).scalar_one()
|
2022-07-10 14:29:28 +02:00
|
|
|
assert outgoing_activity.is_sent is True
|
|
|
|
assert outgoing_activity.last_status_code == 204
|
|
|
|
assert outgoing_activity.error is None
|
|
|
|
assert outgoing_activity.is_errored is False
|
|
|
|
|
|
|
|
|
2022-08-10 20:34:36 +02:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
async def test_process_next_outgoing_activity__webmention(
|
|
|
|
async_db_session: AsyncSession,
|
2022-07-10 14:29:28 +02:00
|
|
|
respx_mock: respx.MockRouter,
|
|
|
|
) -> None:
|
|
|
|
# And an outgoing activity
|
|
|
|
outbox_object = _setup_outbox_object()
|
|
|
|
|
|
|
|
recipient_url = "https://example.com/webmention"
|
|
|
|
respx_mock.post(recipient_url).mock(return_value=httpx.Response(204))
|
|
|
|
|
|
|
|
outgoing_activity = factories.OutgoingActivityFactory(
|
|
|
|
recipient=recipient_url,
|
|
|
|
outbox_object_id=outbox_object.id,
|
|
|
|
inbox_object_id=None,
|
|
|
|
webmention_target="http://example.com",
|
2022-06-22 20:11:22 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
# When processing the next outgoing activity
|
|
|
|
# Then it is processed
|
2022-08-11 12:24:17 +02:00
|
|
|
next_activity = await fetch_next_outgoing_activity(async_db_session)
|
2022-08-10 20:34:36 +02:00
|
|
|
assert next_activity
|
|
|
|
await process_next_outgoing_activity(async_db_session, next_activity)
|
2022-06-22 20:11:22 +02:00
|
|
|
|
|
|
|
assert respx_mock.calls.call_count == 1
|
|
|
|
|
2022-08-10 20:34:36 +02:00
|
|
|
outgoing_activity = (
|
|
|
|
await async_db_session.execute(select(models.OutgoingActivity))
|
|
|
|
).scalar_one()
|
2022-06-22 20:11:22 +02:00
|
|
|
assert outgoing_activity.is_sent is True
|
|
|
|
assert outgoing_activity.last_status_code == 204
|
|
|
|
assert outgoing_activity.error is None
|
|
|
|
assert outgoing_activity.is_errored is False
|
|
|
|
|
|
|
|
|
2022-08-10 20:34:36 +02:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
async def test_process_next_outgoing_activity__error_500(
|
|
|
|
async_db_session: AsyncSession,
|
2022-06-22 20:11:22 +02:00
|
|
|
respx_mock: respx.MockRouter,
|
|
|
|
) -> None:
|
|
|
|
outbox_object = _setup_outbox_object()
|
|
|
|
recipient_inbox_url = "https://example.com/inbox"
|
|
|
|
respx_mock.post(recipient_inbox_url).mock(
|
|
|
|
return_value=httpx.Response(500, text="oops")
|
|
|
|
)
|
|
|
|
|
|
|
|
# And an outgoing activity
|
|
|
|
outgoing_activity = factories.OutgoingActivityFactory(
|
|
|
|
recipient=recipient_inbox_url,
|
|
|
|
outbox_object_id=outbox_object.id,
|
2022-07-10 14:29:28 +02:00
|
|
|
inbox_object_id=None,
|
|
|
|
webmention_target=None,
|
2022-06-22 20:11:22 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
# When processing the next outgoing activity
|
|
|
|
# Then it is processed
|
2022-08-11 12:24:17 +02:00
|
|
|
next_activity = await fetch_next_outgoing_activity(async_db_session)
|
2022-08-10 20:34:36 +02:00
|
|
|
assert next_activity
|
|
|
|
await process_next_outgoing_activity(async_db_session, next_activity)
|
2022-06-22 20:11:22 +02:00
|
|
|
|
|
|
|
assert respx_mock.calls.call_count == 1
|
|
|
|
|
2022-08-10 20:34:36 +02:00
|
|
|
outgoing_activity = (
|
|
|
|
await async_db_session.execute(select(models.OutgoingActivity))
|
|
|
|
).scalar_one()
|
2022-06-22 20:11:22 +02:00
|
|
|
assert outgoing_activity.is_sent is False
|
|
|
|
assert outgoing_activity.last_status_code == 500
|
|
|
|
assert outgoing_activity.last_response == "oops"
|
|
|
|
assert outgoing_activity.is_errored is False
|
|
|
|
assert outgoing_activity.tries == 1
|
|
|
|
|
|
|
|
|
2022-08-10 20:34:36 +02:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
async def test_process_next_outgoing_activity__errored(
|
|
|
|
async_db_session: AsyncSession,
|
2022-06-22 20:11:22 +02:00
|
|
|
respx_mock: respx.MockRouter,
|
|
|
|
) -> None:
|
|
|
|
outbox_object = _setup_outbox_object()
|
|
|
|
recipient_inbox_url = "https://example.com/inbox"
|
2022-06-27 20:55:44 +02:00
|
|
|
respx_mock.post(recipient_inbox_url).mock(
|
|
|
|
return_value=httpx.Response(500, text="oops")
|
|
|
|
)
|
2022-06-22 20:11:22 +02:00
|
|
|
|
|
|
|
# And an outgoing activity
|
2022-07-24 20:27:58 +02:00
|
|
|
outgoing_activity = factories.OutgoingActivityFactory.create(
|
2022-06-22 20:11:22 +02:00
|
|
|
recipient=recipient_inbox_url,
|
|
|
|
outbox_object_id=outbox_object.id,
|
2022-07-10 14:29:28 +02:00
|
|
|
inbox_object_id=None,
|
|
|
|
webmention_target=None,
|
2022-06-27 20:55:44 +02:00
|
|
|
tries=_MAX_RETRIES - 1,
|
2022-06-22 20:11:22 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
# When processing the next outgoing activity
|
|
|
|
# Then it is processed
|
2022-08-11 12:24:17 +02:00
|
|
|
next_activity = await fetch_next_outgoing_activity(async_db_session)
|
2022-08-10 20:34:36 +02:00
|
|
|
assert next_activity
|
|
|
|
await process_next_outgoing_activity(async_db_session, next_activity)
|
2022-06-22 20:11:22 +02:00
|
|
|
|
|
|
|
assert respx_mock.calls.call_count == 1
|
|
|
|
|
2022-08-10 20:34:36 +02:00
|
|
|
outgoing_activity = (
|
|
|
|
await async_db_session.execute(select(models.OutgoingActivity))
|
|
|
|
).scalar_one()
|
2022-06-22 20:11:22 +02:00
|
|
|
assert outgoing_activity.is_sent is False
|
2022-06-27 20:55:44 +02:00
|
|
|
assert outgoing_activity.last_status_code == 500
|
|
|
|
assert outgoing_activity.last_response == "oops"
|
|
|
|
assert outgoing_activity.is_errored is True
|
2022-06-22 20:11:22 +02:00
|
|
|
|
2022-06-27 20:55:44 +02:00
|
|
|
# And it is skipped from processing
|
2022-08-11 12:24:17 +02:00
|
|
|
next_activity = await fetch_next_outgoing_activity(async_db_session)
|
2022-08-10 20:34:36 +02:00
|
|
|
assert next_activity is None
|
2022-06-22 20:11:22 +02:00
|
|
|
|
2022-06-27 20:55:44 +02:00
|
|
|
|
2022-08-10 20:34:36 +02:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
async def test_process_next_outgoing_activity__connect_error(
|
|
|
|
async_db_session: AsyncSession,
|
2022-06-22 20:11:22 +02:00
|
|
|
respx_mock: respx.MockRouter,
|
|
|
|
) -> None:
|
|
|
|
outbox_object = _setup_outbox_object()
|
|
|
|
recipient_inbox_url = "https://example.com/inbox"
|
2022-06-27 20:55:44 +02:00
|
|
|
respx_mock.post(recipient_inbox_url).mock(side_effect=httpx.ConnectError)
|
2022-06-22 20:11:22 +02:00
|
|
|
|
|
|
|
# And an outgoing activity
|
|
|
|
outgoing_activity = factories.OutgoingActivityFactory(
|
|
|
|
recipient=recipient_inbox_url,
|
|
|
|
outbox_object_id=outbox_object.id,
|
2022-07-08 21:17:08 +02:00
|
|
|
inbox_object_id=None,
|
2022-07-10 14:29:28 +02:00
|
|
|
webmention_target=None,
|
2022-06-22 20:11:22 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
# When processing the next outgoing activity
|
|
|
|
# Then it is processed
|
2022-08-11 12:24:17 +02:00
|
|
|
next_activity = await fetch_next_outgoing_activity(async_db_session)
|
2022-08-10 20:34:36 +02:00
|
|
|
assert next_activity
|
|
|
|
await process_next_outgoing_activity(async_db_session, next_activity)
|
2022-06-22 20:11:22 +02:00
|
|
|
|
|
|
|
assert respx_mock.calls.call_count == 1
|
|
|
|
|
2022-08-10 20:34:36 +02:00
|
|
|
outgoing_activity = (
|
|
|
|
await async_db_session.execute(select(models.OutgoingActivity))
|
|
|
|
).scalar_one()
|
2022-06-22 20:11:22 +02:00
|
|
|
assert outgoing_activity.is_sent is False
|
2022-06-27 20:55:44 +02:00
|
|
|
assert outgoing_activity.error is not None
|
|
|
|
assert outgoing_activity.tries == 1
|
2022-06-22 20:11:22 +02:00
|
|
|
|
|
|
|
|
|
|
|
# TODO(ts):
|
|
|
|
# - parse retry after
|