Add support for changing the update interval for feeds
Some checks failed
Test and build Docker image / docker (push) Has been cancelled
Some checks failed
Test and build Docker image / docker (push) Has been cancelled
This commit is contained in:
parent
567273678e
commit
24d4d7a293
18 changed files with 803 additions and 119 deletions
|
|
@ -4,9 +4,13 @@ import tempfile
|
|||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from reader import Entry, Feed, Reader, make_reader
|
||||
from reader import Entry
|
||||
from reader import Feed
|
||||
from reader import Reader
|
||||
from reader import make_reader
|
||||
|
||||
from discord_rss_bot.filter.blacklist import entry_should_be_skipped, feed_has_blacklist_tags
|
||||
from discord_rss_bot.filter.blacklist import entry_should_be_skipped
|
||||
from discord_rss_bot.filter.blacklist import feed_has_blacklist_tags
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterable
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ import tempfile
|
|||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from discord_rss_bot.custom_filters import encode_url, entry_is_blacklisted, entry_is_whitelisted
|
||||
from discord_rss_bot.custom_filters import encode_url
|
||||
from discord_rss_bot.custom_filters import entry_is_blacklisted
|
||||
from discord_rss_bot.custom_filters import entry_is_whitelisted
|
||||
from discord_rss_bot.settings import get_reader
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
|
|||
|
|
@ -4,19 +4,20 @@ import os
|
|||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import LiteralString
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from reader import Feed, Reader, make_reader
|
||||
from reader import Feed
|
||||
from reader import Reader
|
||||
from reader import make_reader
|
||||
|
||||
from discord_rss_bot.feeds import (
|
||||
extract_domain,
|
||||
is_youtube_feed,
|
||||
send_entry_to_discord,
|
||||
send_to_discord,
|
||||
should_send_embed_check,
|
||||
truncate_webhook_message,
|
||||
)
|
||||
from discord_rss_bot.feeds import extract_domain
|
||||
from discord_rss_bot.feeds import is_youtube_feed
|
||||
from discord_rss_bot.feeds import send_entry_to_discord
|
||||
from discord_rss_bot.feeds import send_to_discord
|
||||
from discord_rss_bot.feeds import should_send_embed_check
|
||||
from discord_rss_bot.feeds import truncate_webhook_message
|
||||
from discord_rss_bot.missing_tags import add_missing_tags
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import urllib.parse
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
|
@ -289,3 +290,152 @@ def test_backup_endpoint_returns_error_when_not_configured(monkeypatch: pytest.M
|
|||
assert "Git backup is not configured" in response.text or "GIT_BACKUP_PATH" in response.text, (
|
||||
"Error message about backup not being configured should be shown"
|
||||
)
|
||||
|
||||
|
||||
def test_show_more_entries_button_visible_when_many_entries() -> None:
|
||||
"""Test that the 'Show more entries' button is visible when there are more than 20 entries."""
|
||||
# Add the webhook first
|
||||
client.post(url="/delete_webhook", data={"webhook_url": webhook_url})
|
||||
response: Response = client.post(
|
||||
url="/add_webhook",
|
||||
data={"webhook_name": webhook_name, "webhook_url": webhook_url},
|
||||
)
|
||||
assert response.status_code == 200, f"Failed to add webhook: {response.text}"
|
||||
|
||||
# Remove the feed if it already exists
|
||||
feeds: Response = client.get(url="/")
|
||||
if feed_url in feeds.text:
|
||||
client.post(url="/remove", data={"feed_url": feed_url})
|
||||
|
||||
# Add the feed
|
||||
response: Response = client.post(url="/add", data={"feed_url": feed_url, "webhook_dropdown": webhook_name})
|
||||
assert response.status_code == 200, f"Failed to add feed: {response.text}"
|
||||
|
||||
# Get the feed page
|
||||
response: Response = client.get(url="/feed", params={"feed_url": feed_url})
|
||||
assert response.status_code == 200, f"Failed to get /feed: {response.text}"
|
||||
|
||||
# Check if the feed has more than 20 entries by looking at the response
|
||||
# The button should be visible if there are more than 20 entries
|
||||
# We check for both the button text and the link structure
|
||||
if "Show more entries" in response.text:
|
||||
# Button is visible - verify it has the correct structure
|
||||
assert "starting_after=" in response.text, "Show more entries button should contain starting_after parameter"
|
||||
# The button should be a link to the feed page with pagination
|
||||
assert (
|
||||
f'href="/feed?feed_url={urllib.parse.quote(feed_url)}' in response.text
|
||||
or f'href="/feed?feed_url={encoded_feed_url(feed_url)}' in response.text
|
||||
), "Show more entries button should link back to the feed page"
|
||||
|
||||
|
||||
def test_show_more_entries_button_not_visible_when_few_entries() -> None:
|
||||
"""Test that the 'Show more entries' button is not visible when there are 20 or fewer entries."""
|
||||
# Use a feed with very few entries
|
||||
small_feed_url = "https://lovinator.space/rss_test_small.xml"
|
||||
|
||||
# Clean up if exists
|
||||
client.post(url="/remove", data={"feed_url": small_feed_url})
|
||||
|
||||
# Add a small feed (this may not exist, so this test is conditional)
|
||||
response: Response = client.post(url="/add", data={"feed_url": small_feed_url, "webhook_dropdown": webhook_name})
|
||||
|
||||
if response.status_code == 200:
|
||||
# Get the feed page
|
||||
response: Response = client.get(url="/feed", params={"feed_url": small_feed_url})
|
||||
assert response.status_code == 200, f"Failed to get /feed: {response.text}"
|
||||
|
||||
# If the feed has 20 or fewer entries, the button should not be visible
|
||||
# We check the total entry count in the page
|
||||
if "0 entries" in response.text or " entries)" in response.text:
|
||||
# Extract entry count and verify button visibility
|
||||
|
||||
match: re.Match[str] | None = re.search(r"\((\d+) entries\)", response.text)
|
||||
if match:
|
||||
entry_count = int(match.group(1))
|
||||
if entry_count <= 20:
|
||||
assert "Show more entries" not in response.text, (
|
||||
f"Show more entries button should not be visible when there are {entry_count} entries"
|
||||
)
|
||||
|
||||
# Clean up
|
||||
client.post(url="/remove", data={"feed_url": small_feed_url})
|
||||
|
||||
|
||||
def test_show_more_entries_pagination_works() -> None:
|
||||
"""Test that pagination with starting_after parameter works correctly."""
|
||||
# Add the webhook first
|
||||
client.post(url="/delete_webhook", data={"webhook_url": webhook_url})
|
||||
response: Response = client.post(
|
||||
url="/add_webhook",
|
||||
data={"webhook_name": webhook_name, "webhook_url": webhook_url},
|
||||
)
|
||||
assert response.status_code == 200, f"Failed to add webhook: {response.text}"
|
||||
|
||||
# Remove the feed if it already exists
|
||||
feeds: Response = client.get(url="/")
|
||||
if feed_url in feeds.text:
|
||||
client.post(url="/remove", data={"feed_url": feed_url})
|
||||
|
||||
# Add the feed
|
||||
response: Response = client.post(url="/add", data={"feed_url": feed_url, "webhook_dropdown": webhook_name})
|
||||
assert response.status_code == 200, f"Failed to add feed: {response.text}"
|
||||
|
||||
# Get the first page
|
||||
response: Response = client.get(url="/feed", params={"feed_url": feed_url})
|
||||
assert response.status_code == 200, f"Failed to get /feed: {response.text}"
|
||||
|
||||
# Check if pagination is available
|
||||
if "Show more entries" in response.text and "starting_after=" in response.text:
|
||||
# Extract the starting_after parameter from the button link
|
||||
match: re.Match[str] | None = re.search(r'starting_after=([^"&]+)', response.text)
|
||||
if match:
|
||||
starting_after_id: str = match.group(1)
|
||||
|
||||
# Request the second page
|
||||
response: Response = client.get(
|
||||
url="/feed", params={"feed_url": feed_url, "starting_after": starting_after_id}
|
||||
)
|
||||
assert response.status_code == 200, f"Failed to get paginated feed: {response.text}"
|
||||
|
||||
# Verify we got a valid response (the page should contain entries)
|
||||
assert "entries)" in response.text, "Paginated page should show entry count"
|
||||
|
||||
|
||||
def test_show_more_entries_button_context_variable() -> None:
|
||||
"""Test that the button visibility variable is correctly passed to the template context."""
|
||||
# Add the webhook first
|
||||
client.post(url="/delete_webhook", data={"webhook_url": webhook_url})
|
||||
response: Response = client.post(
|
||||
url="/add_webhook",
|
||||
data={"webhook_name": webhook_name, "webhook_url": webhook_url},
|
||||
)
|
||||
assert response.status_code == 200, f"Failed to add webhook: {response.text}"
|
||||
|
||||
# Remove the feed if it already exists
|
||||
feeds: Response = client.get(url="/")
|
||||
if feed_url in feeds.text:
|
||||
client.post(url="/remove", data={"feed_url": feed_url})
|
||||
|
||||
# Add the feed
|
||||
response: Response = client.post(url="/add", data={"feed_url": feed_url, "webhook_dropdown": webhook_name})
|
||||
assert response.status_code == 200, f"Failed to add feed: {response.text}"
|
||||
|
||||
# Get the feed page
|
||||
response: Response = client.get(url="/feed", params={"feed_url": feed_url})
|
||||
assert response.status_code == 200, f"Failed to get /feed: {response.text}"
|
||||
|
||||
# Extract the total entries count from the page
|
||||
match: re.Match[str] | None = re.search(r"\((\d+) entries\)", response.text)
|
||||
if match:
|
||||
entry_count = int(match.group(1))
|
||||
|
||||
# If more than 20 entries, button should be visible
|
||||
if entry_count > 20:
|
||||
assert "Show more entries" in response.text, (
|
||||
f"Button should be visible when there are {entry_count} entries (more than 20)"
|
||||
)
|
||||
# If 20 or fewer entries, button should not be visible
|
||||
else:
|
||||
assert "Show more entries" not in response.text, (
|
||||
f"Button should not be visible when there are {entry_count} entries (20 or fewer)"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ import tempfile
|
|||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from reader import Feed, Reader, make_reader
|
||||
from reader import Feed
|
||||
from reader import Reader
|
||||
from reader import make_reader
|
||||
|
||||
from discord_rss_bot.search import create_search_context
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ from pathlib import Path
|
|||
|
||||
from reader import Reader
|
||||
|
||||
from discord_rss_bot.settings import data_dir, default_custom_message, get_reader
|
||||
from discord_rss_bot.settings import data_dir
|
||||
from discord_rss_bot.settings import default_custom_message
|
||||
from discord_rss_bot.settings import get_reader
|
||||
|
||||
|
||||
def test_reader() -> None:
|
||||
|
|
|
|||
88
tests/test_update_interval.py
Normal file
88
tests/test_update_interval.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import urllib.parse
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from discord_rss_bot.main import app
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from httpx import Response
|
||||
|
||||
client: TestClient = TestClient(app)
|
||||
webhook_name: str = "Test Webhook for Update Interval"
|
||||
webhook_url: str = "https://discord.com/api/webhooks/1234567890/test_update_interval"
|
||||
feed_url: str = "https://lovinator.space/rss_test.xml"
|
||||
|
||||
|
||||
def test_global_update_interval() -> None:
|
||||
"""Test setting the global update interval."""
|
||||
# Set global update interval to 30 minutes
|
||||
response: Response = client.post("/set_global_update_interval", data={"interval_minutes": "30"})
|
||||
assert response.status_code == 200, f"Failed to set global interval: {response.text}"
|
||||
|
||||
# Check that the settings page shows the new interval
|
||||
response = client.get("/settings")
|
||||
assert response.status_code == 200, f"Failed to get settings page: {response.text}"
|
||||
assert "30" in response.text, "Global interval not updated on settings page"
|
||||
|
||||
|
||||
def test_per_feed_update_interval() -> None:
|
||||
"""Test setting per-feed update interval."""
|
||||
# Clean up any existing feed/webhook
|
||||
client.post(url="/delete_webhook", data={"webhook_url": webhook_url})
|
||||
client.post(url="/remove", data={"feed_url": feed_url})
|
||||
|
||||
# Add webhook and feed
|
||||
response: Response = client.post(
|
||||
url="/add_webhook",
|
||||
data={"webhook_name": webhook_name, "webhook_url": webhook_url},
|
||||
)
|
||||
assert response.status_code == 200, f"Failed to add webhook: {response.text}"
|
||||
|
||||
response = client.post(url="/add", data={"feed_url": feed_url, "webhook_dropdown": webhook_name})
|
||||
assert response.status_code == 200, f"Failed to add feed: {response.text}"
|
||||
|
||||
# Set feed-specific update interval to 15 minutes
|
||||
response = client.post("/set_update_interval", data={"feed_url": feed_url, "interval_minutes": "15"})
|
||||
assert response.status_code == 200, f"Failed to set feed interval: {response.text}"
|
||||
|
||||
# Check that the feed page shows the custom interval
|
||||
encoded_url = urllib.parse.quote(feed_url)
|
||||
response = client.get(f"/feed?feed_url={encoded_url}")
|
||||
assert response.status_code == 200, f"Failed to get feed page: {response.text}"
|
||||
assert "15" in response.text, "Feed interval not displayed on feed page"
|
||||
assert "Custom" in response.text, "Custom badge not shown for feed-specific interval"
|
||||
|
||||
|
||||
def test_reset_feed_update_interval() -> None:
|
||||
"""Test resetting feed update interval to global default."""
|
||||
# First set a custom interval
|
||||
response: Response = client.post("/set_update_interval", data={"feed_url": feed_url, "interval_minutes": "15"})
|
||||
assert response.status_code == 200, f"Failed to set feed interval: {response.text}"
|
||||
|
||||
# Reset to global default
|
||||
response = client.post("/reset_update_interval", data={"feed_url": feed_url})
|
||||
assert response.status_code == 200, f"Failed to reset feed interval: {response.text}"
|
||||
|
||||
# Check that the feed page shows global default
|
||||
encoded_url = urllib.parse.quote(feed_url)
|
||||
response = client.get(f"/feed?feed_url={encoded_url}")
|
||||
assert response.status_code == 200, f"Failed to get feed page: {response.text}"
|
||||
assert "Using global default" in response.text, "Global default badge not shown after reset"
|
||||
|
||||
|
||||
def test_update_interval_validation() -> None:
|
||||
"""Test that update interval validation works."""
|
||||
# Try to set an interval below minimum (should be clamped to 1)
|
||||
response: Response = client.post("/set_global_update_interval", data={"interval_minutes": "0"})
|
||||
assert response.status_code == 200, f"Failed to handle minimum interval: {response.text}"
|
||||
|
||||
# Try to set an interval above maximum (should be clamped to 10080)
|
||||
response = client.post("/set_global_update_interval", data={"interval_minutes": "20000"})
|
||||
assert response.status_code == 200, f"Failed to handle maximum interval: {response.text}"
|
||||
|
||||
# Clean up
|
||||
client.post(url="/remove", data={"feed_url": feed_url})
|
||||
client.post(url="/delete_webhook", data={"webhook_url": webhook_url})
|
||||
Loading…
Add table
Add a link
Reference in a new issue