import uuid from unittest.mock import Mock, call, patch import sqlalchemy as sa from flask import Flask from flask.testing import FlaskClient from webmentions_ssg import DATABASE as db from webmentions_ssg.models import ReceivedWebmention def test_endpoint_only_accepts_post( client: FlaskClient, ) -> None: response = client.get("/endpoint") assert response.status_code == 405 @patch( "webmentions_ssg.forms.validators.ensure_public_url", new=lambda url: None, ) def test_endpoint_returns_form_errors( client: FlaskClient, ) -> None: response = client.post( "/endpoint", data={ "source": "https://source.example/post", "target": "https://example.com/post", }, ) assert response.status_code == 400 errors = response.get_json() assert errors is not None assert "target" in errors @patch( "webmentions_ssg.forms.validators.ensure_public_url", new=lambda url: None, ) @patch("webmentions_ssg.views.verify_webmention") def test_endpoint_creates_webmention( verify_webmention: Mock, app: Flask, client: FlaskClient, ) -> None: source = "https://source.example/post" target = "https://dennisfink.me/blog/example/" response = client.post( "/endpoint", data={ "source": source, "target": target, }, ) assert response.status_code == 201 with app.app_context(): webmention = db.session.scalar(sa.select(ReceivedWebmention)) assert webmention is not None assert webmention.source == source assert webmention.target == target assert webmention.status == "received" assert webmention.failure_reason is None identifier = webmention.uuid assert response.headers["Location"].endswith(str(identifier)) verify_webmention.assert_called_once_with(identifier) @patch( "webmentions_ssg.forms.validators.ensure_public_url", new=lambda url: None, ) @patch("webmentions_ssg.views.verify_webmention") def test_endpoint_is_idempotent( verify_webmention: Mock, app: Flask, client: FlaskClient, ) -> None: data = { "source": "https://source.example/post", "target": "https://dennisfink.me/blog/example/", } first_response = client.post( "/endpoint", data=data, ) second_response = client.post( "/endpoint", data=data, ) assert first_response.status_code == 201 assert second_response.status_code == 201 assert first_response.headers["Location"] == second_response.headers["Location"] with app.app_context(): webmentions = db.session.scalars(sa.select(ReceivedWebmention)).all() assert len(webmentions) == 1 webmention = webmentions[0] assert webmention.source == data["source"] assert webmention.target == data["target"] assert webmention.status == "received" assert webmention.failure_reason is None identifier = webmention.uuid assert verify_webmention.call_args_list == [ call(identifier), call(identifier), ] @patch( "webmentions_ssg.forms.validators.ensure_public_url", new=lambda url: None, ) @patch("webmentions_ssg.views.verify_webmention") def test_resending_resets_failure_state( verify_webmention: Mock, app: Flask, client: FlaskClient, ) -> None: source = "https://source.example/post" target = "https://dennisfink.me/blog/example/" with app.app_context(): existing = ReceivedWebmention( uuid=uuid.uuid7(), source=source, target=target, status="failed", failure_reason="Previous failure", ) db.session.add(existing) db.session.commit() identifier = existing.uuid response = client.post( "/endpoint", data={ "source": source, "target": target, }, ) assert response.status_code == 201 with app.app_context(): webmention = db.session.get( ReceivedWebmention, identifier, ) assert webmention is not None assert webmention.status == "received" assert webmention.failure_reason is None verify_webmention.assert_called_once_with(identifier)