aboutsummaryrefslogtreecommitdiff
path: root/tests/tasks/test_scanner.py
diff options
context:
space:
mode:
authorDennis Fink2026-08-21 10:37:56 +0200
committerDennis Fink2026-08-21 10:37:56 +0200
commitc5492398f100ccb154fa557d657bae2322f95cf7 (patch)
tree21efa67e00c26ab0733a931e6a5810b4c933388b /tests/tasks/test_scanner.py
parent5bfcc3c06ebe6d6694221c25f07a4901572f97f5 (diff)
downloadwebmentions-ssg-c5492398f100ccb154fa557d657bae2322f95cf7.tar.gz
webmentions-ssg-c5492398f100ccb154fa557d657bae2322f95cf7.zip
test(core): align tests with module ownership
Move direct validator, model, and Huey extension coverage into dedicated test modules and mock dependencies where behavior belongs to another module. Expand Huey extension typing and coverage while removing duplicate assertions from scanner, sender, form, and view tests.
Diffstat (limited to '')
-rw-r--r--tests/tasks/test_scanner.py97
1 files changed, 23 insertions, 74 deletions
diff --git a/tests/tasks/test_scanner.py b/tests/tasks/test_scanner.py
index 332b8bf..3de5e16 100644
--- a/tests/tasks/test_scanner.py
+++ b/tests/tasks/test_scanner.py
@@ -4,6 +4,7 @@
from pathlib import Path
from types import ModuleType
+from unittest.mock import Mock
from uuid import UUID
import pytest
@@ -20,13 +21,11 @@ SOURCE_URL = f"{BASE_URL}example/"
@pytest.fixture
-def scanner_module(app: Flask) -> ModuleType:
- # Importing scanner registers Huey tasks. The dependency on the
- # app fixture guarantees that Huey has been initialized first.
+def scanner_module(app: Flask, monkeypatch: pytest.MonkeyPatch) -> ModuleType:
_ = app
-
from webmentions_ssg.tasks import scanner
+ monkeypatch.setattr(scanner, "is_http_url", Mock(return_value=True))
return scanner
@@ -96,11 +95,6 @@ def configure_scanner(app: Flask, root: Path, *, base_url: str | None = None) ->
app.config["WEBMENTIONS_SSG_SOURCE_BASE_URL"] = base_url
-# ---------------------------------------------------------------------------
-# Microformats parsing
-# ---------------------------------------------------------------------------
-
-
def test_parse_entry_returns_microformats_entry(scanner_module: ModuleType) -> None:
document = parse_html(
f"""
@@ -277,11 +271,6 @@ def test_primary_entry_rejects_nonmatching_u_url(scanner_module: ModuleType) ->
scanner_module.primary_entry(document, SOURCE_URL)
-# ---------------------------------------------------------------------------
-# e-content
-# ---------------------------------------------------------------------------
-
-
def test_content_element_returns_e_content(scanner_module: ModuleType) -> None:
document = parse_html(
"""
@@ -346,11 +335,6 @@ def test_content_element_rejects_multiple_e_content(scanner_module: ModuleType)
scanner_module.content_element(entry)
-# ---------------------------------------------------------------------------
-# Canonical URLs
-# ---------------------------------------------------------------------------
-
-
def test_canonical_url_returns_absolute_url(scanner_module: ModuleType) -> None:
document = parse_html(
f"""
@@ -393,6 +377,8 @@ def test_canonical_url_returns_none_when_missing(scanner_module: ModuleType) ->
def test_canonical_url_resolves_relative_url_with_base_url(
scanner_module: ModuleType,
) -> None:
+ scanner_module.is_http_url.side_effect = [False, True]
+
document = parse_html(
"""
<html>
@@ -414,6 +400,8 @@ def test_canonical_url_resolves_relative_url_with_base_url(
def test_relative_canonical_requires_base_url(scanner_module: ModuleType) -> None:
+ scanner_module.is_http_url.return_value = False
+
document = parse_html(
"""
<html>
@@ -453,11 +441,6 @@ def test_empty_canonical_is_rejected(scanner_module: ModuleType) -> None:
)
-# ---------------------------------------------------------------------------
-# Target extraction
-# ---------------------------------------------------------------------------
-
-
def test_scan_source_uses_canonical_without_base_url(
scanner_module: ModuleType, tmp_path: Path
) -> None:
@@ -657,24 +640,6 @@ def test_scan_source_extracts_reaction_properties(
assert scanned.targets == frozenset({target})
-def test_scan_source_extracts_anchor_from_e_content(
- scanner_module: ModuleType, tmp_path: Path
-) -> None:
- path = write_post(
- tmp_path,
- "example",
- """
- <a href="https://example.com/target">
- Target
- </a>
- """,
- )
-
- scanned = scanner_module.scan_source_file(path, root=tmp_path, base_url=None)
-
- assert scanned.targets == frozenset({"https://example.com/target"})
-
-
@pytest.mark.parametrize("tag", ("link", "area"))
def test_scan_source_ignores_non_anchor_href_elements(
scanner_module: ModuleType, tmp_path: Path, tag: str
@@ -714,11 +679,6 @@ def test_scan_source_ignores_self_fragment(
assert scanned.targets == frozenset({"https://example.com/target"})
-# ---------------------------------------------------------------------------
-# Database reconciliation
-# ---------------------------------------------------------------------------
-
-
def test_scan_creates_source_and_webmentions(
app: Flask, scanner_module: ModuleType, tmp_path: Path, monkeypatch: MonkeyPatch
) -> None:
@@ -900,7 +860,6 @@ def test_updated_source_increments_revision(
assert webmention.desired_revision == 2
assert webmention.processed_revision == 1
assert webmention.sent_revision == 1
- assert webmention.pending
identifier = webmention.uuid
@@ -972,7 +931,6 @@ def test_new_target_is_added_on_update(
assert webmention.desired_revision == 2
assert webmention.processed_revision is None
assert webmention.sent_revision is None
- assert webmention.pending
identifier = webmention.uuid
@@ -1055,7 +1013,6 @@ def test_removed_sent_target_is_queued_again(
assert webmention.desired_revision == 2
assert webmention.processed_revision == 1
assert webmention.sent_revision == 1
- assert webmention.pending
assert queued == [identifier]
@@ -1124,16 +1081,10 @@ def test_removed_unsent_target_is_not_queued(
assert webmention.desired_revision == 2
assert webmention.processed_revision == 2
assert webmention.sent_revision is None
- assert not webmention.pending
assert queued == []
-# ---------------------------------------------------------------------------
-# Source deletion/restoration
-# ---------------------------------------------------------------------------
-
-
def test_deleted_source_queues_previously_sent_webmention(
app: Flask, scanner_module: ModuleType, tmp_path: Path, monkeypatch: MonkeyPatch
) -> None:
@@ -1186,7 +1137,6 @@ def test_deleted_source_queues_previously_sent_webmention(
assert webmention.desired_revision == 2
assert webmention.processed_revision == 1
assert webmention.sent_revision == 1
- assert webmention.pending
assert queued == [identifier]
@@ -1231,7 +1181,6 @@ def test_deleted_source_does_not_queue_unsent_webmention(
assert webmention.desired_revision == 2
assert webmention.processed_revision == 2
assert webmention.sent_revision is None
- assert not webmention.pending
assert queued == []
@@ -1298,18 +1247,12 @@ def test_restored_source_creates_new_revision(
assert webmention.active
assert webmention.desired_revision == 3
assert webmention.sent_revision == 1
- assert webmention.pending
identifier = webmention.uuid
assert queued == [identifier]
-# ---------------------------------------------------------------------------
-# Queue recovery and invalid sources
-# ---------------------------------------------------------------------------
-
-
def test_pending_webmention_is_requeued_on_next_scan(
app: Flask, scanner_module: ModuleType, tmp_path: Path, monkeypatch: MonkeyPatch
) -> None:
@@ -1436,6 +1379,8 @@ def test_scan_sources_accepts_valid_base_url(
def test_scan_sources_rejects_invalid_base_url(
app: Flask, scanner_module: ModuleType, tmp_path: Path
) -> None:
+ scanner_module.is_http_url.return_value = False
+
configure_scanner(app, tmp_path, base_url="not-a-url")
with pytest.raises(RuntimeError, match="absolute HTTP or HTTPS URL"):
@@ -1565,6 +1510,8 @@ def test_scan_source_ignores_reaction_to_matching_hostname(
def test_canonical_url_rejects_non_http_resolved_url(
scanner_module: ModuleType,
) -> None:
+ scanner_module.is_http_url.return_value = False
+
document = parse_html(
"""
<html>
@@ -1634,19 +1581,21 @@ def test_parse_entry_rejects_invalid_parser_output(
scanner_module.parse_entry(element, SOURCE_URL)
-@pytest.mark.parametrize(
- "value",
- [
- pytest.param(" ", id="empty"),
- pytest.param("mailto:example@example.com", id="non-http"),
- ],
-)
-def test_normalize_target_ignores_invalid_target(
- scanner_module: ModuleType, value: str
+def test_normalize_target_ignores_empty_target(scanner_module: ModuleType) -> None:
+ assert (
+ scanner_module.normalize_target(" ", base_url=SOURCE_URL, source_url=SOURCE_URL)
+ is None
+ )
+
+
+def test_normalize_target_ignores_url_rejected_by_validator(
+ scanner_module: ModuleType,
) -> None:
+ scanner_module.is_http_url.return_value = False
+
assert (
scanner_module.normalize_target(
- value, base_url=SOURCE_URL, source_url=SOURCE_URL
+ "https://example.com/target", base_url=SOURCE_URL, source_url=SOURCE_URL
)
is None
)