Add webhook text length stuff
Some checks failed
Test and build Docker image / docker (push) Failing after 15s
Some checks failed
Test and build Docker image / docker (push) Failing after 15s
This commit is contained in:
parent
d16fa92e4d
commit
85f25263f3
9 changed files with 525 additions and 26 deletions
|
|
@ -379,6 +379,43 @@ def test_get_feed_media_gallery_image_limit_defaults_to_first_image() -> None:
|
|||
assert result == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("tag_value", "expected_limit"),
|
||||
[
|
||||
(1, 1),
|
||||
(25, 25),
|
||||
(-1, 1),
|
||||
(99_999, 4000),
|
||||
("200", 200),
|
||||
("0", 1),
|
||||
("unknown", 4000),
|
||||
],
|
||||
)
|
||||
def test_get_feed_webhook_text_length_limit_normalizes_stored_tag(
|
||||
tag_value: feeds.JsonValue,
|
||||
expected_limit: int,
|
||||
) -> None:
|
||||
reader = MagicMock()
|
||||
feed = MagicMock()
|
||||
feed.url = "https://example.com/feed.xml"
|
||||
reader.get_tag.return_value = tag_value
|
||||
|
||||
result = feeds.get_feed_webhook_text_length_limit(reader, feed)
|
||||
|
||||
assert result == expected_limit
|
||||
|
||||
|
||||
def test_get_feed_webhook_text_length_limit_defaults_to_discord_limit() -> None:
|
||||
reader = MagicMock()
|
||||
feed = MagicMock()
|
||||
feed.url = "https://example.com/feed.xml"
|
||||
reader.get_tag.side_effect = lambda resource, key, default=None: default # noqa: ARG005
|
||||
|
||||
result = feeds.get_feed_webhook_text_length_limit(reader, feed)
|
||||
|
||||
assert result == 4000
|
||||
|
||||
|
||||
def test_create_feed_inherits_global_screenshot_layout() -> None:
|
||||
reader = MagicMock()
|
||||
reader.get_tag.side_effect = lambda resource, key, default=None: { # noqa: ARG005
|
||||
|
|
@ -435,6 +472,41 @@ def test_create_feed_sets_default_media_gallery_image_limit() -> None:
|
|||
)
|
||||
|
||||
|
||||
def test_create_feed_sets_default_webhook_text_length_limit() -> None:
|
||||
reader = MagicMock()
|
||||
reader.get_tag.side_effect = lambda resource, key, default=None: { # noqa: ARG005
|
||||
"webhooks": [{"name": "Main", "url": "https://discord.com/api/webhooks/123/abc"}],
|
||||
"screenshot_layout": "desktop",
|
||||
"delivery_mode": "embed",
|
||||
}.get(key, default)
|
||||
|
||||
create_feed(reader, "https://example.com/feed.xml", "Main")
|
||||
|
||||
reader.set_tag.assert_any_call(
|
||||
"https://example.com/feed.xml",
|
||||
"webhook_text_length_limit",
|
||||
4000,
|
||||
)
|
||||
|
||||
|
||||
def test_create_feed_inherits_global_webhook_text_length_limit() -> None:
|
||||
reader = MagicMock()
|
||||
reader.get_tag.side_effect = lambda resource, key, default=None: { # noqa: ARG005
|
||||
"webhooks": [{"name": "Main", "url": "https://discord.com/api/webhooks/123/abc"}],
|
||||
"screenshot_layout": "desktop",
|
||||
"delivery_mode": "embed",
|
||||
"webhook_text_length_limit": 2500,
|
||||
}.get(key, default)
|
||||
|
||||
create_feed(reader, "https://example.com/feed.xml", "Main")
|
||||
|
||||
reader.set_tag.assert_any_call(
|
||||
"https://example.com/feed.xml",
|
||||
"webhook_text_length_limit",
|
||||
2500,
|
||||
)
|
||||
|
||||
|
||||
def test_create_feed_falls_back_to_embed_when_global_delivery_mode_is_invalid() -> None:
|
||||
reader = MagicMock()
|
||||
reader.get_tag.side_effect = lambda resource, key, default=None: { # noqa: ARG005
|
||||
|
|
@ -664,6 +736,32 @@ def test_create_screenshot_webhook_falls_back_to_text_on_failure(
|
|||
)
|
||||
|
||||
|
||||
@patch("discord_rss_bot.feeds.replace_tags_in_text_message")
|
||||
def test_create_text_webhook_uses_feed_text_length_limit(mock_replace_tags_in_text_message: MagicMock) -> None:
|
||||
mock_replace_tags_in_text_message.return_value = "start-" + ("x" * 40) + "-end"
|
||||
|
||||
entry = MagicMock()
|
||||
entry.feed.url = "https://example.com/feed.xml"
|
||||
reader = MagicMock()
|
||||
reader.get_tag.side_effect = lambda resource, key, default=None: { # noqa: ARG005
|
||||
"custom_message": "{{entry_title}}",
|
||||
"webhook_text_length_limit": 20,
|
||||
}.get(key, default)
|
||||
|
||||
webhook = feeds.create_text_webhook(
|
||||
"https://discord.com/api/webhooks/123/abc",
|
||||
entry,
|
||||
reader,
|
||||
use_default_message_on_empty=False,
|
||||
)
|
||||
|
||||
assert webhook.content is not None
|
||||
assert len(webhook.content) == 20
|
||||
assert webhook.content.startswith("start-")
|
||||
assert webhook.content.endswith("-end")
|
||||
assert "..." in webhook.content
|
||||
|
||||
|
||||
@patch("discord_rss_bot.feeds.fetch_ttvdrops_campaign_media_items", return_value=[])
|
||||
@patch("discord_rss_bot.feeds.replace_tags_in_embed")
|
||||
def test_create_embed_webhook_uses_media_gallery_for_entry_images(
|
||||
|
|
@ -671,7 +769,10 @@ def test_create_embed_webhook_uses_media_gallery_for_entry_images(
|
|||
mock_fetch_ttvdrops_campaign_media_items: MagicMock,
|
||||
) -> None:
|
||||
reader = MagicMock()
|
||||
reader.get_tag.return_value = 10
|
||||
reader.get_tag.side_effect = lambda resource, key, default=None: { # noqa: ARG005
|
||||
"media_gallery_image_limit": 10,
|
||||
"webhook_text_length_limit": 4000,
|
||||
}.get(key, default)
|
||||
entry = MagicMock()
|
||||
entry.id = "entry-1"
|
||||
entry.title = "Entry title"
|
||||
|
|
@ -707,6 +808,38 @@ def test_create_embed_webhook_uses_media_gallery_for_entry_images(
|
|||
]
|
||||
|
||||
|
||||
@patch("discord_rss_bot.feeds.fetch_ttvdrops_campaign_media_items", return_value=[])
|
||||
@patch("discord_rss_bot.feeds.replace_tags_in_embed")
|
||||
def test_create_embed_webhook_uses_feed_text_length_limit_for_media_gallery(
|
||||
mock_replace_tags_in_embed: MagicMock,
|
||||
mock_fetch_ttvdrops_campaign_media_items: MagicMock,
|
||||
) -> None:
|
||||
reader = MagicMock()
|
||||
reader.get_tag.side_effect = lambda resource, key, default=None: { # noqa: ARG005
|
||||
"media_gallery_image_limit": 10,
|
||||
"webhook_text_length_limit": 20,
|
||||
}.get(key, default)
|
||||
entry = MagicMock()
|
||||
entry.id = "entry-1"
|
||||
entry.title = "Entry title"
|
||||
entry.link = "https://example.com/entry"
|
||||
entry.summary = '<img src="https://example.com/summary.jpg" />'
|
||||
entry.content = [MagicMock(value='<img src="https://example.com/content-1.jpg" />')]
|
||||
entry.feed.url = "https://example.com/feed.xml"
|
||||
mock_replace_tags_in_embed.return_value = feeds.CustomEmbed(description="x" * 100)
|
||||
|
||||
webhook = feeds.create_embed_webhook("https://discord.com/api/webhooks/123/abc", entry, reader)
|
||||
|
||||
assert webhook.flags == 1 << 15
|
||||
components = get_test_webhook_components(webhook)
|
||||
text_component = components[0]
|
||||
assert isinstance(text_component, dict)
|
||||
assert isinstance(text_component["content"], str)
|
||||
assert len(text_component["content"]) == 20
|
||||
assert text_component["content"].endswith("...")
|
||||
mock_fetch_ttvdrops_campaign_media_items.assert_called_once_with(entry)
|
||||
|
||||
|
||||
@patch("discord_rss_bot.feeds.fetch_ttvdrops_campaign_media_items", return_value=[])
|
||||
@patch("discord_rss_bot.feeds.replace_tags_in_embed")
|
||||
def test_create_embed_webhook_can_limit_media_gallery_to_first_image(
|
||||
|
|
@ -714,7 +847,10 @@ def test_create_embed_webhook_can_limit_media_gallery_to_first_image(
|
|||
mock_fetch_ttvdrops_campaign_media_items: MagicMock,
|
||||
) -> None:
|
||||
reader = MagicMock()
|
||||
reader.get_tag.return_value = 1
|
||||
reader.get_tag.side_effect = lambda resource, key, default=None: { # noqa: ARG005
|
||||
"media_gallery_image_limit": 1,
|
||||
"webhook_text_length_limit": 4000,
|
||||
}.get(key, default)
|
||||
entry = MagicMock()
|
||||
entry.id = "entry-1"
|
||||
entry.title = "Entry title"
|
||||
|
|
@ -744,7 +880,10 @@ def test_create_embed_webhook_can_disable_media_images(
|
|||
mock_fetch_ttvdrops_campaign_media_items: MagicMock,
|
||||
) -> None:
|
||||
reader = MagicMock()
|
||||
reader.get_tag.return_value = 0
|
||||
reader.get_tag.side_effect = lambda resource, key, default=None: { # noqa: ARG005
|
||||
"media_gallery_image_limit": 0,
|
||||
"webhook_text_length_limit": 4000,
|
||||
}.get(key, default)
|
||||
entry = MagicMock()
|
||||
entry.id = "entry-1"
|
||||
entry.title = "Entry title"
|
||||
|
|
@ -770,6 +909,38 @@ def test_create_embed_webhook_can_disable_media_images(
|
|||
mock_fetch_ttvdrops_campaign_media_items.assert_not_called()
|
||||
|
||||
|
||||
@patch("discord_rss_bot.feeds.fetch_ttvdrops_campaign_media_items", return_value=[])
|
||||
@patch("discord_rss_bot.feeds.replace_tags_in_embed")
|
||||
def test_create_embed_webhook_uses_feed_text_length_limit_for_regular_embed_description(
|
||||
mock_replace_tags_in_embed: MagicMock,
|
||||
mock_fetch_ttvdrops_campaign_media_items: MagicMock,
|
||||
) -> None:
|
||||
reader = MagicMock()
|
||||
reader.get_tag.side_effect = lambda resource, key, default=None: { # noqa: ARG005
|
||||
"media_gallery_image_limit": 0,
|
||||
"webhook_text_length_limit": 20,
|
||||
}.get(key, default)
|
||||
entry = MagicMock()
|
||||
entry.id = "entry-1"
|
||||
entry.title = "Entry title"
|
||||
entry.link = "https://example.com/entry"
|
||||
entry.summary = ""
|
||||
entry.content = []
|
||||
entry.feed.url = "https://example.com/feed.xml"
|
||||
mock_replace_tags_in_embed.return_value = feeds.CustomEmbed(description="x" * 100)
|
||||
|
||||
webhook = feeds.create_embed_webhook("https://discord.com/api/webhooks/123/abc", entry, reader)
|
||||
|
||||
assert "components" not in webhook.json
|
||||
embeds = webhook.json.get("embeds")
|
||||
assert isinstance(embeds, list)
|
||||
assert isinstance(embeds[0], dict)
|
||||
assert isinstance(embeds[0].get("description"), str)
|
||||
assert len(embeds[0]["description"]) == 20
|
||||
assert embeds[0]["description"].endswith("...")
|
||||
mock_fetch_ttvdrops_campaign_media_items.assert_not_called()
|
||||
|
||||
|
||||
@patch("discord_rss_bot.feeds.fetch_ttvdrops_campaign_media_items")
|
||||
@patch("discord_rss_bot.feeds.replace_tags_in_embed")
|
||||
def test_create_embed_webhook_prefers_ttvdrops_reward_images_and_alt_text(
|
||||
|
|
|
|||
|
|
@ -508,6 +508,26 @@ def test_post_set_global_screenshot_layout_triggers_backup(monkeypatch: pytest.M
|
|||
assert "Set global screenshot layout to mobile" in commit_message
|
||||
|
||||
|
||||
def test_post_set_global_webhook_text_length_limit_triggers_backup(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""Posting to /set_global_webhook_text_length_limit should trigger a git backup."""
|
||||
backup_path: Path = tmp_path / "backup"
|
||||
monkeypatch.setenv("GIT_BACKUP_PATH", str(backup_path))
|
||||
monkeypatch.delenv("GIT_BACKUP_REMOTE", raising=False)
|
||||
|
||||
with patch("discord_rss_bot.main.commit_state_change") as mock_commit:
|
||||
response = client.post(url="/set_global_webhook_text_length_limit", data={"text_length_limit": "2500"})
|
||||
assert response.status_code == 200, f"Failed to set global webhook text length limit: {response.text}"
|
||||
mock_commit.assert_called_once()
|
||||
|
||||
call_args = mock_commit.call_args
|
||||
assert call_args is not None
|
||||
commit_message: str = call_args[0][1]
|
||||
assert "Set global webhook text length limit to 2500" in commit_message
|
||||
|
||||
|
||||
def test_post_custom_message_triggers_backup(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
"""Posting to /custom should trigger a git backup."""
|
||||
backup_path: Path = tmp_path / "backup"
|
||||
|
|
|
|||
|
|
@ -283,8 +283,13 @@ def test_get() -> None:
|
|||
assert "This feed" in response.text
|
||||
assert "Screenshot Delivery" in response.text
|
||||
assert "Image Delivery" in response.text
|
||||
assert "Text Delivery" in response.text
|
||||
assert "main embed" in response.text
|
||||
assert "2000 characters" in response.text
|
||||
assert 'type="range"' in response.text
|
||||
assert 'max="10"' in response.text
|
||||
assert 'id="text_length_limit"' in response.text
|
||||
assert 'max="4000"' in response.text
|
||||
|
||||
response: Response = client.get(url="/")
|
||||
assert response.status_code == 200, f"/ failed: {response.text}"
|
||||
|
|
@ -603,6 +608,10 @@ def test_settings_page_shows_screenshot_layout_setting() -> None:
|
|||
assert response.status_code == 200, f"/settings failed: {response.text}"
|
||||
assert "Default delivery mode for new feeds" in response.text
|
||||
assert "Default screenshot layout for new feeds" in response.text
|
||||
assert "Default webhook text limit for new feeds" in response.text
|
||||
assert "regular embed mode" in response.text
|
||||
assert "2000 characters" in response.text
|
||||
assert 'id="global_text_length_limit"' in response.text
|
||||
assert "uv run playwright install chromium" in response.text
|
||||
|
||||
|
||||
|
|
@ -615,6 +624,18 @@ def test_set_global_delivery_mode() -> None:
|
|||
assert re.search(r"<option\s+value=\"text\"[^>]*\bselected\b", response.text)
|
||||
|
||||
|
||||
def test_set_global_webhook_text_length_limit() -> None:
|
||||
response: Response = client.post(
|
||||
url="/set_global_webhook_text_length_limit",
|
||||
data={"text_length_limit": "2500"},
|
||||
)
|
||||
assert response.status_code == 200, f"Failed to set global webhook text length limit: {response.text}"
|
||||
|
||||
response = client.get(url="/settings")
|
||||
assert response.status_code == 200, f"/settings failed after setting webhook text length limit: {response.text}"
|
||||
assert 'value="2500"' in response.text
|
||||
|
||||
|
||||
def test_add_page_shows_global_default_delivery_mode_hint() -> None:
|
||||
response: Response = client.post(url="/set_global_delivery_mode", data={"delivery_mode": "text"})
|
||||
assert response.status_code == 200, f"Failed to set global delivery mode: {response.text}"
|
||||
|
|
@ -742,6 +763,41 @@ def test_set_feed_media_gallery_image_limit_route_updates_stored_tag() -> None:
|
|||
app.dependency_overrides = {}
|
||||
|
||||
|
||||
def test_set_feed_webhook_text_length_limit_route_updates_stored_tag() -> None:
|
||||
@dataclass(slots=True)
|
||||
class DummyFeed:
|
||||
url: str
|
||||
title: str
|
||||
|
||||
class StubReader:
|
||||
def __init__(self) -> None:
|
||||
self.feed = DummyFeed(url="https://example.com/feed.xml", title="Example")
|
||||
self.tags: dict[tuple[str, str], int] = {}
|
||||
|
||||
def get_feed(self, feed_url: str) -> DummyFeed:
|
||||
assert feed_url == self.feed.url
|
||||
return self.feed
|
||||
|
||||
def set_tag(self, resource: str, key: str, value: int) -> None:
|
||||
self.tags[resource, key] = value
|
||||
|
||||
stub_reader = StubReader()
|
||||
app.dependency_overrides[get_reader_dependency] = lambda: stub_reader
|
||||
|
||||
try:
|
||||
with patch("discord_rss_bot.main.commit_state_change"):
|
||||
response: Response = client.post(
|
||||
url="/set_feed_webhook_text_length_limit",
|
||||
data={"feed_url": stub_reader.feed.url, "text_length_limit": "2500"},
|
||||
follow_redirects=False,
|
||||
)
|
||||
|
||||
assert response.status_code == 303, f"/set_feed_webhook_text_length_limit failed: {response.text}"
|
||||
assert stub_reader.tags[stub_reader.feed.url, "webhook_text_length_limit"] == 2500
|
||||
finally:
|
||||
app.dependency_overrides = {}
|
||||
|
||||
|
||||
def test_sent_webhooks_view_shows_saved_records() -> None:
|
||||
@dataclass(slots=True)
|
||||
class DummyFeed:
|
||||
|
|
|
|||
|
|
@ -192,6 +192,48 @@ def test_get_reader_preserves_existing_global_delivery_mode() -> None:
|
|||
get_reader.cache_clear()
|
||||
|
||||
|
||||
def test_get_reader_sets_default_global_webhook_text_length_limit() -> None:
|
||||
"""get_reader should initialize global webhook text length limit to 4000 when missing."""
|
||||
get_reader.cache_clear()
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
Path.mkdir(Path(temp_dir), exist_ok=True)
|
||||
|
||||
custom_loc: pathlib.Path = pathlib.Path(temp_dir, "webhook_text_length_limit_default_db.sqlite")
|
||||
reader: Reader = get_reader(custom_location=custom_loc)
|
||||
|
||||
webhook_text_length_limit = reader.get_tag((), "webhook_text_length_limit", None)
|
||||
assert webhook_text_length_limit == 4000, (
|
||||
f"Expected default global webhook text length limit to be 4000, got: {webhook_text_length_limit}"
|
||||
)
|
||||
|
||||
reader.close()
|
||||
get_reader.cache_clear()
|
||||
|
||||
|
||||
def test_get_reader_preserves_existing_global_webhook_text_length_limit() -> None:
|
||||
"""get_reader should not overwrite an existing global webhook text length limit value."""
|
||||
get_reader.cache_clear()
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
Path.mkdir(Path(temp_dir), exist_ok=True)
|
||||
|
||||
custom_loc: pathlib.Path = pathlib.Path(temp_dir, "webhook_text_length_limit_existing_db.sqlite")
|
||||
first_reader: Reader = get_reader(custom_location=custom_loc)
|
||||
first_reader.set_tag((), "webhook_text_length_limit", 2500) # pyright: ignore[reportArgumentType]
|
||||
first_reader.close()
|
||||
get_reader.cache_clear()
|
||||
|
||||
second_reader: Reader = get_reader(custom_location=custom_loc)
|
||||
webhook_text_length_limit = second_reader.get_tag((), "webhook_text_length_limit", None)
|
||||
assert webhook_text_length_limit == 2500, (
|
||||
f"Expected existing global webhook text length limit to stay 2500, got: {webhook_text_length_limit}"
|
||||
)
|
||||
|
||||
second_reader.close()
|
||||
get_reader.cache_clear()
|
||||
|
||||
|
||||
def test_get_reader_enables_autodiscover_plugin() -> None:
|
||||
"""get_reader should store advertised feed links when HTML parsing fails."""
|
||||
get_reader.cache_clear()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue