1020 lines
35 KiB
Python
1020 lines
35 KiB
Python
import json
|
|
import tempfile
|
|
import uuid
|
|
from pathlib import Path
|
|
from unittest.mock import AsyncMock, Mock, patch
|
|
|
|
import httpx
|
|
import pytest
|
|
from aiohttp import web
|
|
|
|
from app.library.BackgroundWorker import BackgroundWorker
|
|
from app.library.encoder import Encoder
|
|
from app.library.Events import Event
|
|
from app.library.ItemDTO import ItemDTO
|
|
from app.library.Notifications import (
|
|
Notification,
|
|
NotificationEvents,
|
|
Target,
|
|
TargetRequest,
|
|
TargetRequestHeader,
|
|
)
|
|
|
|
|
|
class TestTargetRequestHeader:
|
|
"""Test the TargetRequestHeader dataclass."""
|
|
|
|
def test_target_request_header_creation(self):
|
|
"""Test creating a TargetRequestHeader."""
|
|
header = TargetRequestHeader(key="Authorization", value="Bearer token123")
|
|
|
|
assert header.key == "Authorization"
|
|
assert header.value == "Bearer token123"
|
|
|
|
def test_target_request_header_serialize(self):
|
|
"""Test serializing a TargetRequestHeader."""
|
|
header = TargetRequestHeader(key="Content-Type", value="application/json")
|
|
serialized = header.serialize()
|
|
|
|
expected = {"key": "Content-Type", "value": "application/json"}
|
|
assert serialized == expected
|
|
|
|
def test_target_request_header_json(self):
|
|
"""Test JSON serialization of TargetRequestHeader."""
|
|
header = TargetRequestHeader(key="X-API-Key", value="secret123")
|
|
json_str = header.json()
|
|
|
|
# Should be valid JSON
|
|
parsed = json.loads(json_str)
|
|
assert parsed["key"] == "X-API-Key"
|
|
assert parsed["value"] == "secret123"
|
|
|
|
def test_target_request_header_get(self):
|
|
"""Test get method of TargetRequestHeader."""
|
|
header = TargetRequestHeader(key="Authorization", value="Bearer token")
|
|
|
|
assert header.get("key") == "Authorization"
|
|
assert header.get("value") == "Bearer token"
|
|
assert header.get("nonexistent", "default") == "default"
|
|
|
|
|
|
class TestTargetRequest:
|
|
"""Test the TargetRequest dataclass."""
|
|
|
|
def test_target_request_creation_defaults(self):
|
|
"""Test creating a TargetRequest with defaults."""
|
|
request = TargetRequest(
|
|
type="json",
|
|
method="POST",
|
|
url="https://example.com/webhook"
|
|
)
|
|
|
|
assert request.type == "json"
|
|
assert request.method == "POST"
|
|
assert request.url == "https://example.com/webhook"
|
|
assert request.headers == []
|
|
assert request.data_key == "data"
|
|
|
|
def test_target_request_creation_with_headers(self):
|
|
"""Test creating a TargetRequest with headers."""
|
|
headers = [
|
|
TargetRequestHeader(key="Authorization", value="Bearer token"),
|
|
TargetRequestHeader(key="Content-Type", value="application/json")
|
|
]
|
|
request = TargetRequest(
|
|
type="json",
|
|
method="POST",
|
|
url="https://example.com/webhook",
|
|
headers=headers,
|
|
data_key="payload"
|
|
)
|
|
|
|
assert len(request.headers) == 2
|
|
assert request.headers[0].key == "Authorization"
|
|
assert request.data_key == "payload"
|
|
|
|
def test_target_request_serialize(self):
|
|
"""Test serializing a TargetRequest."""
|
|
headers = [TargetRequestHeader(key="X-Token", value="abc123")]
|
|
request = TargetRequest(
|
|
type="json",
|
|
method="PUT",
|
|
url="https://api.example.com/notify",
|
|
headers=headers,
|
|
data_key="content"
|
|
)
|
|
|
|
serialized = request.serialize()
|
|
expected = {
|
|
"type": "json",
|
|
"method": "PUT",
|
|
"url": "https://api.example.com/notify",
|
|
"data_key": "content",
|
|
"headers": [{"key": "X-Token", "value": "abc123"}]
|
|
}
|
|
assert serialized == expected
|
|
|
|
def test_target_request_json(self):
|
|
"""Test JSON serialization of TargetRequest."""
|
|
request = TargetRequest(
|
|
type="form",
|
|
method="POST",
|
|
url="https://webhook.site/test"
|
|
)
|
|
json_str = request.json()
|
|
|
|
parsed = json.loads(json_str)
|
|
assert parsed["type"] == "form"
|
|
assert parsed["method"] == "POST"
|
|
assert parsed["url"] == "https://webhook.site/test"
|
|
|
|
def test_target_request_get(self):
|
|
"""Test get method of TargetRequest."""
|
|
request = TargetRequest(
|
|
type="json",
|
|
method="POST",
|
|
url="https://example.com/webhook"
|
|
)
|
|
|
|
assert request.get("type") == "json"
|
|
assert request.get("method") == "POST"
|
|
assert request.get("nonexistent", "default") == "default"
|
|
|
|
|
|
class TestTarget:
|
|
"""Test the Target dataclass."""
|
|
|
|
def test_target_creation_minimal(self):
|
|
"""Test creating a Target with minimal required fields."""
|
|
request = TargetRequest(
|
|
type="json",
|
|
method="POST",
|
|
url="https://example.com/webhook"
|
|
)
|
|
target = Target(
|
|
id=str(uuid.uuid4()),
|
|
name="Test Webhook",
|
|
request=request
|
|
)
|
|
|
|
assert target.name == "Test Webhook"
|
|
assert target.on == []
|
|
assert target.presets == []
|
|
assert target.request == request
|
|
|
|
def test_target_creation_full(self):
|
|
"""Test creating a Target with all fields."""
|
|
target_id = str(uuid.uuid4())
|
|
request = TargetRequest(
|
|
type="json",
|
|
method="POST",
|
|
url="https://example.com/webhook"
|
|
)
|
|
target = Target(
|
|
id=target_id,
|
|
name="Full Test Webhook",
|
|
on=["item_completed", "item_failed"],
|
|
presets=["default", "audio_only"],
|
|
request=request
|
|
)
|
|
|
|
assert target.id == target_id
|
|
assert target.name == "Full Test Webhook"
|
|
assert target.on == ["item_completed", "item_failed"]
|
|
assert target.presets == ["default", "audio_only"]
|
|
|
|
def test_target_serialize(self):
|
|
"""Test serializing a Target."""
|
|
target_id = str(uuid.uuid4())
|
|
request = TargetRequest(
|
|
type="json",
|
|
method="POST",
|
|
url="https://example.com/webhook"
|
|
)
|
|
target = Target(
|
|
id=target_id,
|
|
name="Test Target",
|
|
on=["item_completed"],
|
|
presets=["default"],
|
|
request=request
|
|
)
|
|
|
|
serialized = target.serialize()
|
|
assert serialized["id"] == target_id
|
|
assert serialized["name"] == "Test Target"
|
|
assert serialized["on"] == ["item_completed"]
|
|
assert serialized["presets"] == ["default"]
|
|
assert isinstance(serialized["request"], dict)
|
|
|
|
def test_target_json(self):
|
|
"""Test JSON serialization of Target."""
|
|
request = TargetRequest(
|
|
type="json",
|
|
method="POST",
|
|
url="https://example.com/webhook"
|
|
)
|
|
target = Target(
|
|
id=str(uuid.uuid4()),
|
|
name="JSON Test",
|
|
request=request
|
|
)
|
|
|
|
json_str = target.json()
|
|
parsed = json.loads(json_str)
|
|
assert parsed["name"] == "JSON Test"
|
|
|
|
def test_target_get(self):
|
|
"""Test get method of Target."""
|
|
request = TargetRequest(
|
|
type="json",
|
|
method="POST",
|
|
url="https://example.com/webhook"
|
|
)
|
|
target = Target(
|
|
id=str(uuid.uuid4()),
|
|
name="Get Test",
|
|
request=request
|
|
)
|
|
|
|
assert target.get("name") == "Get Test"
|
|
assert target.get("nonexistent", "default") == "default"
|
|
|
|
|
|
class TestNotificationEvents:
|
|
"""Test the NotificationEvents class."""
|
|
|
|
def test_notification_events_constants(self):
|
|
"""Test that NotificationEvents has expected constants."""
|
|
assert hasattr(NotificationEvents, "TEST")
|
|
assert hasattr(NotificationEvents, "ITEM_ADDED")
|
|
assert hasattr(NotificationEvents, "ITEM_COMPLETED")
|
|
assert hasattr(NotificationEvents, "ITEM_CANCELLED")
|
|
assert hasattr(NotificationEvents, "ITEM_DELETED")
|
|
assert hasattr(NotificationEvents, "LOG_INFO")
|
|
assert hasattr(NotificationEvents, "LOG_ERROR")
|
|
|
|
def test_get_events(self):
|
|
"""Test get_events static method."""
|
|
events = NotificationEvents.get_events()
|
|
|
|
assert isinstance(events, dict)
|
|
assert "TEST" in events
|
|
assert "ITEM_ADDED" in events
|
|
assert "ITEM_COMPLETED" in events
|
|
|
|
def test_events_function(self):
|
|
"""Test events function."""
|
|
events_list = NotificationEvents.events()
|
|
|
|
assert isinstance(events_list, list)
|
|
assert len(events_list) > 0
|
|
|
|
def test_is_valid(self):
|
|
"""Test is_valid static method."""
|
|
# Valid events
|
|
assert NotificationEvents.is_valid("test")
|
|
assert NotificationEvents.is_valid("item_added")
|
|
assert NotificationEvents.is_valid("item_completed")
|
|
|
|
# Invalid events
|
|
assert not NotificationEvents.is_valid("invalid_event")
|
|
assert not NotificationEvents.is_valid("")
|
|
assert not NotificationEvents.is_valid(None)
|
|
|
|
|
|
class TestNotification:
|
|
"""Test the Notification singleton class."""
|
|
|
|
def setup_method(self):
|
|
"""Set up test environment before each test."""
|
|
# Reset singleton instance to ensure clean state
|
|
Notification._reset_singleton()
|
|
|
|
def teardown_method(self):
|
|
"""Clean up after each test."""
|
|
# Reset singleton instance to prevent test pollution
|
|
Notification._reset_singleton()
|
|
|
|
@patch("app.library.Notifications.Config")
|
|
@patch("app.library.Notifications.BackgroundWorker")
|
|
def test_notification_singleton(self, mock_background_worker, mock_config):
|
|
"""Test that Notification follows singleton pattern."""
|
|
mock_config.get_instance.return_value = Mock(
|
|
debug=False,
|
|
config_path="/tmp",
|
|
app_version="1.0.0"
|
|
)
|
|
mock_background_worker.get_instance.return_value = Mock()
|
|
|
|
instance1 = Notification.get_instance()
|
|
instance2 = Notification.get_instance()
|
|
|
|
assert instance1 is instance2
|
|
assert isinstance(instance1, Notification)
|
|
|
|
@patch("app.library.Notifications.Config")
|
|
@patch("app.library.Notifications.BackgroundWorker")
|
|
def test_notification_init_default_params(self, mock_background_worker, mock_config):
|
|
"""Test Notification initialization with default parameters."""
|
|
mock_config_instance = Mock(
|
|
debug=False,
|
|
config_path="/tmp/test",
|
|
app_version="1.0.0"
|
|
)
|
|
mock_config.get_instance.return_value = mock_config_instance
|
|
mock_background_worker.get_instance.return_value = Mock()
|
|
|
|
with patch("pathlib.Path.exists", return_value=False):
|
|
notification = Notification.get_instance()
|
|
|
|
assert notification._debug is False
|
|
assert notification._version == "1.0.0"
|
|
assert isinstance(notification._targets, list)
|
|
assert len(notification._targets) == 0
|
|
|
|
@patch("app.library.Notifications.Config")
|
|
@patch("app.library.Notifications.BackgroundWorker")
|
|
def test_notification_init_custom_params(self, mock_background_worker, mock_config):
|
|
"""Test Notification initialization with custom parameters."""
|
|
_ = mock_background_worker, mock_config # Suppress unused variable warnings
|
|
mock_config_instance = Mock(
|
|
debug=True,
|
|
config_path="/custom/path",
|
|
app_version="2.0.0"
|
|
)
|
|
mock_client = Mock(spec=httpx.AsyncClient)
|
|
mock_encoder = Mock(spec=Encoder)
|
|
mock_worker = Mock(spec=BackgroundWorker)
|
|
|
|
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
|
|
temp_path = temp_file.name
|
|
|
|
notification = Notification.get_instance(
|
|
file=temp_path,
|
|
client=mock_client,
|
|
encoder=mock_encoder,
|
|
config=mock_config_instance,
|
|
background_worker=mock_worker
|
|
)
|
|
|
|
assert notification._debug is True
|
|
assert notification._version == "2.0.0"
|
|
assert notification._client == mock_client
|
|
assert notification._encoder == mock_encoder
|
|
assert notification._offload == mock_worker
|
|
|
|
def test_get_targets_empty(self):
|
|
"""Test get_targets when no targets are loaded."""
|
|
with patch("app.library.Notifications.Config") as mock_config, \
|
|
patch("app.library.Notifications.BackgroundWorker") as mock_worker:
|
|
|
|
mock_config.get_instance.return_value = Mock(
|
|
debug=False, config_path="/tmp", app_version="1.0.0"
|
|
)
|
|
mock_worker.get_instance.return_value = Mock()
|
|
|
|
notification = Notification.get_instance()
|
|
targets = notification.get_targets()
|
|
|
|
assert isinstance(targets, list)
|
|
assert len(targets) == 0
|
|
|
|
def test_clear_targets(self):
|
|
"""Test clearing notification targets."""
|
|
with patch("app.library.Notifications.Config") as mock_config, \
|
|
patch("app.library.Notifications.BackgroundWorker") as mock_worker:
|
|
|
|
mock_config.get_instance.return_value = Mock(
|
|
debug=False, config_path="/tmp", app_version="1.0.0"
|
|
)
|
|
mock_worker.get_instance.return_value = Mock()
|
|
|
|
notification = Notification.get_instance()
|
|
|
|
# Add a dummy target
|
|
notification._targets = ["dummy_target"]
|
|
assert len(notification._targets) == 1
|
|
|
|
# Clear targets
|
|
result = notification.clear()
|
|
|
|
assert result == notification # Should return self
|
|
assert len(notification._targets) == 0
|
|
|
|
@patch("app.library.Notifications.Config")
|
|
@patch("app.library.Notifications.BackgroundWorker")
|
|
def test_save_targets(self, mock_worker, mock_config):
|
|
"""Test saving targets to file."""
|
|
mock_config.get_instance.return_value = Mock(
|
|
debug=False, config_path="/tmp", app_version="1.0.0"
|
|
)
|
|
mock_worker.get_instance.return_value = Mock()
|
|
|
|
with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp_file:
|
|
temp_path = temp_file.name
|
|
|
|
# Create a test target
|
|
request = TargetRequest(
|
|
type="json",
|
|
method="POST",
|
|
url="https://example.com/webhook"
|
|
)
|
|
target = Target(
|
|
id=str(uuid.uuid4()),
|
|
name="Test Target",
|
|
request=request
|
|
)
|
|
|
|
notification = Notification.get_instance(file=temp_path)
|
|
result = notification.save([target])
|
|
|
|
assert result == notification # Should return self
|
|
|
|
# Verify file was written
|
|
with open(temp_path) as f:
|
|
saved_data = json.load(f)
|
|
|
|
assert isinstance(saved_data, list)
|
|
assert len(saved_data) == 1
|
|
assert saved_data[0]["name"] == "Test Target"
|
|
|
|
# Clean up
|
|
Path(temp_path).unlink()
|
|
|
|
@patch("app.library.Notifications.Config")
|
|
@patch("app.library.Notifications.BackgroundWorker")
|
|
def test_load_targets_empty_file(self, mock_worker, mock_config):
|
|
"""Test loading targets from empty file."""
|
|
mock_config.get_instance.return_value = Mock(
|
|
debug=False, config_path="/tmp", app_version="1.0.0"
|
|
)
|
|
mock_worker.get_instance.return_value = Mock()
|
|
|
|
with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp_file:
|
|
temp_file.write("") # Empty file
|
|
temp_path = temp_file.name
|
|
|
|
notification = Notification.get_instance(file=temp_path)
|
|
result = notification.load()
|
|
|
|
assert result == notification # Should return self
|
|
assert len(notification._targets) == 0
|
|
|
|
# Clean up
|
|
Path(temp_path).unlink()
|
|
|
|
@patch("app.library.Notifications.Config")
|
|
@patch("app.library.Notifications.BackgroundWorker")
|
|
@patch("app.library.Notifications.Presets")
|
|
def test_load_targets_valid_file(self, mock_presets, mock_worker, mock_config):
|
|
"""Test loading targets from valid file."""
|
|
mock_config.get_instance.return_value = Mock(
|
|
debug=False, config_path="/tmp", app_version="1.0.0"
|
|
)
|
|
mock_worker.get_instance.return_value = Mock()
|
|
|
|
# Mock preset with name attribute
|
|
mock_preset = Mock()
|
|
mock_preset.name = "default"
|
|
mock_presets.get_instance.return_value.get_all.return_value = [mock_preset]
|
|
|
|
target_data = [{
|
|
"id": str(uuid.uuid4()),
|
|
"name": "Test Webhook",
|
|
"on": ["item_completed"],
|
|
"presets": ["default"],
|
|
"request": {
|
|
"type": "json",
|
|
"method": "POST",
|
|
"url": "https://example.com/webhook",
|
|
"data_key": "data",
|
|
"headers": []
|
|
}
|
|
}]
|
|
|
|
with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp_file:
|
|
json.dump(target_data, temp_file)
|
|
temp_path = temp_file.name
|
|
|
|
notification = Notification.get_instance(file=temp_path)
|
|
result = notification.load()
|
|
|
|
assert result == notification # Should return self
|
|
assert len(notification._targets) == 1
|
|
assert notification._targets[0].name == "Test Webhook"
|
|
|
|
# Clean up
|
|
Path(temp_path).unlink()
|
|
|
|
def test_make_target(self):
|
|
"""Test make_target method."""
|
|
with patch("app.library.Notifications.Config") as mock_config, \
|
|
patch("app.library.Notifications.BackgroundWorker") as mock_worker:
|
|
|
|
mock_config.get_instance.return_value = Mock(
|
|
debug=False, config_path="/tmp", app_version="1.0.0"
|
|
)
|
|
mock_worker.get_instance.return_value = Mock()
|
|
|
|
notification = Notification.get_instance()
|
|
|
|
target_dict = {
|
|
"id": str(uuid.uuid4()),
|
|
"name": "Test Target",
|
|
"on": ["item_completed"],
|
|
"presets": ["default"],
|
|
"request": {
|
|
"type": "json",
|
|
"method": "POST",
|
|
"url": "https://example.com/webhook",
|
|
"data_key": "payload",
|
|
"headers": [{"key": "Authorization", "value": "Bearer token"}]
|
|
}
|
|
}
|
|
|
|
target = notification.make_target(target_dict)
|
|
|
|
assert isinstance(target, Target)
|
|
assert target.name == "Test Target"
|
|
assert target.on == ["item_completed"]
|
|
assert target.presets == ["default"]
|
|
assert target.request.url == "https://example.com/webhook"
|
|
assert target.request.data_key == "payload"
|
|
assert len(target.request.headers) == 1
|
|
assert target.request.headers[0].key == "Authorization"
|
|
|
|
def test_validate_target_valid(self):
|
|
"""Test validate method with valid target."""
|
|
target_dict = {
|
|
"id": str(uuid.uuid4()),
|
|
"name": "Valid Target",
|
|
"request": {
|
|
"url": "https://example.com/webhook"
|
|
}
|
|
}
|
|
|
|
with patch("app.library.Notifications.NotificationEvents.get_events") as mock_events, \
|
|
patch("app.library.Notifications.Presets") as mock_presets:
|
|
|
|
mock_events.return_value.values.return_value = ["item_completed"]
|
|
mock_presets.get_instance.return_value.get_all.return_value = []
|
|
|
|
result = Notification.validate(target_dict)
|
|
assert result is True
|
|
|
|
def test_validate_target_missing_id(self):
|
|
"""Test validate method with missing ID."""
|
|
target_dict = {
|
|
"name": "Missing ID Target",
|
|
"request": {
|
|
"url": "https://example.com/webhook"
|
|
}
|
|
}
|
|
|
|
with pytest.raises(ValueError, match=r"Invalid notification target\. No ID found\."):
|
|
Notification.validate(target_dict)
|
|
|
|
def test_validate_target_invalid_id(self):
|
|
"""Test validate method with invalid UUID."""
|
|
target_dict = {
|
|
"id": "invalid-uuid",
|
|
"name": "Invalid ID Target",
|
|
"request": {
|
|
"url": "https://example.com/webhook"
|
|
}
|
|
}
|
|
|
|
with pytest.raises(ValueError, match=r"Invalid notification target\. No ID found\."):
|
|
Notification.validate(target_dict)
|
|
|
|
def test_validate_target_missing_name(self):
|
|
"""Test validate method with missing name."""
|
|
target_dict = {
|
|
"id": str(uuid.uuid4()),
|
|
"request": {
|
|
"url": "https://example.com/webhook"
|
|
}
|
|
}
|
|
|
|
with pytest.raises(ValueError, match=r"Invalid notification target\. No name found\."):
|
|
Notification.validate(target_dict)
|
|
|
|
def test_validate_target_missing_request(self):
|
|
"""Test validate method with missing request."""
|
|
target_dict = {
|
|
"id": str(uuid.uuid4()),
|
|
"name": "Missing Request Target"
|
|
}
|
|
|
|
with pytest.raises(ValueError, match=r"Invalid notification target\. No request details found\."):
|
|
Notification.validate(target_dict)
|
|
|
|
def test_validate_target_missing_url(self):
|
|
"""Test validate method with missing URL."""
|
|
target_dict = {
|
|
"id": str(uuid.uuid4()),
|
|
"name": "Missing URL Target",
|
|
"request": {}
|
|
}
|
|
|
|
with pytest.raises(ValueError, match=r"Invalid notification target\. No URL found\."):
|
|
Notification.validate(target_dict)
|
|
|
|
def test_validate_target_invalid_method(self):
|
|
"""Test validate method with invalid HTTP method."""
|
|
target_dict = {
|
|
"id": str(uuid.uuid4()),
|
|
"name": "Invalid Method Target",
|
|
"request": {
|
|
"url": "https://example.com/webhook",
|
|
"method": "GET" # Only POST and PUT are allowed
|
|
}
|
|
}
|
|
|
|
with pytest.raises(ValueError, match=r"Invalid notification target\. Invalid method found\."):
|
|
Notification.validate(target_dict)
|
|
|
|
def test_validate_target_invalid_type(self):
|
|
"""Test validate method with invalid request type."""
|
|
target_dict = {
|
|
"id": str(uuid.uuid4()),
|
|
"name": "Invalid Type Target",
|
|
"request": {
|
|
"url": "https://example.com/webhook",
|
|
"type": "xml" # Only json and form are allowed
|
|
}
|
|
}
|
|
|
|
with pytest.raises(ValueError, match=r"Invalid notification target\. Invalid type found\."):
|
|
Notification.validate(target_dict)
|
|
|
|
@patch("app.library.Notifications.Config")
|
|
@patch("app.library.Notifications.BackgroundWorker")
|
|
@patch("app.library.Notifications.EventBus")
|
|
def test_attach(self, mock_eventbus, mock_worker, mock_config):
|
|
"""Test attach method."""
|
|
mock_config.get_instance.return_value = Mock(
|
|
debug=False, config_path="/tmp", app_version="1.0.0"
|
|
)
|
|
mock_worker.get_instance.return_value = Mock()
|
|
mock_eventbus_instance = Mock()
|
|
mock_eventbus.get_instance.return_value = mock_eventbus_instance
|
|
|
|
notification = Notification.get_instance()
|
|
app = Mock(spec=web.Application)
|
|
|
|
with patch.object(notification, "load") as mock_load:
|
|
notification.attach(app)
|
|
|
|
mock_load.assert_called_once()
|
|
mock_eventbus_instance.subscribe.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("app.library.Notifications.Config")
|
|
@patch("app.library.Notifications.BackgroundWorker")
|
|
async def test_send_no_targets(self, mock_worker, mock_config):
|
|
"""Test send method with no targets."""
|
|
mock_config.get_instance.return_value = Mock(
|
|
debug=False, config_path="/tmp", app_version="1.0.0"
|
|
)
|
|
mock_worker.get_instance.return_value = Mock()
|
|
|
|
notification = Notification.get_instance()
|
|
event = Event(
|
|
event="test",
|
|
data={"test": "data"}
|
|
)
|
|
|
|
result = await notification.send(event)
|
|
assert result == []
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("app.library.Notifications.Config")
|
|
@patch("app.library.Notifications.BackgroundWorker")
|
|
async def test_send_invalid_event_data(self, mock_worker, mock_config):
|
|
"""Test send method with invalid event data."""
|
|
mock_config.get_instance.return_value = Mock(
|
|
debug=False, config_path="/tmp", app_version="1.0.0"
|
|
)
|
|
mock_worker.get_instance.return_value = Mock()
|
|
|
|
notification = Notification.get_instance()
|
|
# Add a target
|
|
request = TargetRequest(
|
|
type="json",
|
|
method="POST",
|
|
url="https://example.com/webhook"
|
|
)
|
|
target = Target(
|
|
id=str(uuid.uuid4()),
|
|
name="Test Target",
|
|
request=request
|
|
)
|
|
notification._targets = [target]
|
|
|
|
event = Event(
|
|
event="test",
|
|
data="invalid_string_data" # Should be ItemDTO or dict
|
|
)
|
|
|
|
result = await notification.send(event)
|
|
assert result == []
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("app.library.Notifications.Config")
|
|
@patch("app.library.Notifications.BackgroundWorker")
|
|
async def test_send_with_http_target(self, mock_worker, mock_config):
|
|
"""Test send method with HTTP target."""
|
|
mock_config.get_instance.return_value = Mock(
|
|
debug=False, config_path="/tmp", app_version="1.0.0"
|
|
)
|
|
mock_worker.get_instance.return_value = Mock()
|
|
|
|
# Mock HTTP client
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.text = "OK"
|
|
mock_client = AsyncMock()
|
|
mock_client.request = AsyncMock(return_value=mock_response)
|
|
|
|
notification = Notification.get_instance(client=mock_client)
|
|
|
|
# Add HTTP target
|
|
request = TargetRequest(
|
|
type="json",
|
|
method="POST",
|
|
url="https://example.com/webhook"
|
|
)
|
|
target = Target(
|
|
id=str(uuid.uuid4()),
|
|
name="Test HTTP Target",
|
|
request=request
|
|
)
|
|
notification._targets = [target]
|
|
|
|
# Create test event
|
|
item_dto = ItemDTO(
|
|
id="test_id",
|
|
url="https://youtube.com/watch?v=test",
|
|
title="Test Video",
|
|
folder="/downloads"
|
|
)
|
|
event = Event(
|
|
event="item_completed",
|
|
data=item_dto
|
|
)
|
|
|
|
result = await notification.send(event)
|
|
|
|
assert len(result) == 1
|
|
assert result[0]["status"] == 200
|
|
assert result[0]["text"] == "OK"
|
|
mock_client.request.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("app.library.Notifications.Config")
|
|
@patch("app.library.Notifications.BackgroundWorker")
|
|
async def test_send_with_apprise_target(self, mock_worker, mock_config):
|
|
"""Test send method with Apprise target."""
|
|
mock_config.get_instance.return_value = Mock(
|
|
debug=False, config_path="/tmp", app_version="1.0.0",
|
|
apprise_config="/tmp/apprise.conf"
|
|
)
|
|
mock_worker.get_instance.return_value = Mock()
|
|
|
|
notification = Notification.get_instance()
|
|
|
|
# Add Apprise target (non-HTTP URL)
|
|
request = TargetRequest(
|
|
type="json",
|
|
method="POST",
|
|
url="discord://webhook_id/webhook_token"
|
|
)
|
|
target = Target(
|
|
id=str(uuid.uuid4()),
|
|
name="Test Discord Target",
|
|
request=request
|
|
)
|
|
notification._targets = [target]
|
|
|
|
# Create test event
|
|
event = Event(
|
|
event="item_completed",
|
|
data={"test": "data"}
|
|
)
|
|
|
|
with patch("app.library.Notifications.Path") as mock_path, \
|
|
patch("builtins.__import__") as mock_import:
|
|
|
|
# Mock apprise config file doesn't exist
|
|
mock_path.return_value.exists.return_value = False
|
|
|
|
# Mock apprise import
|
|
mock_apprise = Mock()
|
|
mock_notify = Mock()
|
|
mock_apprise.Apprise.return_value = mock_notify
|
|
mock_import.return_value = mock_apprise
|
|
|
|
result = await notification.send(event)
|
|
|
|
# Should return empty dict from _apprise method
|
|
assert len(result) == 1
|
|
assert result[0] == {}
|
|
|
|
def test_check_preset_no_presets(self):
|
|
"""Test _check_preset method with target having no preset filters."""
|
|
with patch("app.library.Notifications.Config") as mock_config, \
|
|
patch("app.library.Notifications.BackgroundWorker") as mock_worker:
|
|
|
|
mock_config.get_instance.return_value = Mock(
|
|
debug=False, config_path="/tmp", app_version="1.0.0"
|
|
)
|
|
mock_worker.get_instance.return_value = Mock()
|
|
|
|
notification = Notification.get_instance()
|
|
|
|
request = TargetRequest(
|
|
type="json",
|
|
method="POST",
|
|
url="https://example.com/webhook"
|
|
)
|
|
target = Target(
|
|
id=str(uuid.uuid4()),
|
|
name="No Preset Filter",
|
|
presets=[], # No preset filter
|
|
request=request
|
|
)
|
|
|
|
event = Event(
|
|
event="item_completed",
|
|
data={"preset": "default"}
|
|
)
|
|
|
|
result = notification._check_preset(target, event)
|
|
assert result is True # Should pass when no preset filter
|
|
|
|
def test_check_preset_with_matching_preset(self):
|
|
"""Test _check_preset method with matching preset."""
|
|
with patch("app.library.Notifications.Config") as mock_config, \
|
|
patch("app.library.Notifications.BackgroundWorker") as mock_worker:
|
|
|
|
mock_config.get_instance.return_value = Mock(
|
|
debug=False, config_path="/tmp", app_version="1.0.0"
|
|
)
|
|
mock_worker.get_instance.return_value = Mock()
|
|
|
|
notification = Notification.get_instance()
|
|
|
|
request = TargetRequest(
|
|
type="json",
|
|
method="POST",
|
|
url="https://example.com/webhook"
|
|
)
|
|
target = Target(
|
|
id=str(uuid.uuid4()),
|
|
name="Preset Filter",
|
|
presets=["default", "audio_only"],
|
|
request=request
|
|
)
|
|
|
|
# Test with ItemDTO
|
|
item_dto = ItemDTO(
|
|
id="test_id",
|
|
url="https://youtube.com/test",
|
|
title="Test Video",
|
|
folder="/downloads",
|
|
preset="default"
|
|
)
|
|
event = Event(
|
|
event="item_completed",
|
|
data=item_dto
|
|
)
|
|
|
|
result = notification._check_preset(target, event)
|
|
assert result is True
|
|
|
|
def test_check_preset_with_non_matching_preset(self):
|
|
"""Test _check_preset method with non-matching preset."""
|
|
with patch("app.library.Notifications.Config") as mock_config, \
|
|
patch("app.library.Notifications.BackgroundWorker") as mock_worker:
|
|
|
|
mock_config.get_instance.return_value = Mock(
|
|
debug=False, config_path="/tmp", app_version="1.0.0"
|
|
)
|
|
mock_worker.get_instance.return_value = Mock()
|
|
|
|
notification = Notification.get_instance()
|
|
|
|
request = TargetRequest(
|
|
type="json",
|
|
method="POST",
|
|
url="https://example.com/webhook"
|
|
)
|
|
target = Target(
|
|
id=str(uuid.uuid4()),
|
|
name="Preset Filter",
|
|
presets=["audio_only"],
|
|
request=request
|
|
)
|
|
|
|
event = Event(
|
|
event="item_completed",
|
|
data={"preset": "video_only"} # Different preset
|
|
)
|
|
|
|
result = notification._check_preset(target, event)
|
|
assert result is False
|
|
|
|
def test_emit_invalid_event(self):
|
|
"""Test emit method with invalid event."""
|
|
with patch("app.library.Notifications.Config") as mock_config, \
|
|
patch("app.library.Notifications.BackgroundWorker") as mock_worker:
|
|
|
|
mock_config.get_instance.return_value = Mock(
|
|
debug=False, config_path="/tmp", app_version="1.0.0"
|
|
)
|
|
mock_worker_instance = Mock()
|
|
mock_worker.get_instance.return_value = mock_worker_instance
|
|
|
|
notification = Notification.get_instance()
|
|
|
|
# Add a target
|
|
request = TargetRequest(
|
|
type="json",
|
|
method="POST",
|
|
url="https://example.com/webhook"
|
|
)
|
|
target = Target(
|
|
id=str(uuid.uuid4()),
|
|
name="Test Target",
|
|
request=request
|
|
)
|
|
notification._targets = [target]
|
|
|
|
# Create invalid event
|
|
event = Event(
|
|
event="invalid_event",
|
|
data={"test": "data"}
|
|
)
|
|
|
|
with patch("app.library.Notifications.NotificationEvents.is_valid", return_value=False):
|
|
result = notification.emit(event, None)
|
|
|
|
# Should return None and not submit to background worker
|
|
assert result is None
|
|
mock_worker_instance.submit.assert_not_called()
|
|
|
|
def test_emit_valid_event(self):
|
|
"""Test emit method with valid event."""
|
|
with patch("app.library.Notifications.Config") as mock_config, \
|
|
patch("app.library.Notifications.BackgroundWorker") as mock_worker:
|
|
|
|
mock_config.get_instance.return_value = Mock(
|
|
debug=False, config_path="/tmp", app_version="1.0.0"
|
|
)
|
|
mock_worker_instance = Mock()
|
|
mock_worker.get_instance.return_value = mock_worker_instance
|
|
|
|
notification = Notification.get_instance()
|
|
|
|
# Add a target
|
|
request = TargetRequest(
|
|
type="json",
|
|
method="POST",
|
|
url="https://example.com/webhook"
|
|
)
|
|
target = Target(
|
|
id=str(uuid.uuid4()),
|
|
name="Test Target",
|
|
request=request
|
|
)
|
|
notification._targets = [target]
|
|
|
|
# Create valid event
|
|
event = Event(
|
|
event="item_completed",
|
|
data={"test": "data"}
|
|
)
|
|
|
|
with patch("app.library.Notifications.NotificationEvents.is_valid", return_value=True):
|
|
result = notification.emit(event, None)
|
|
|
|
# Should return None but submit to background worker
|
|
assert result is None
|
|
mock_worker_instance.submit.assert_called_once_with(notification.send, event)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_noop(self):
|
|
"""Test noop method."""
|
|
with patch("app.library.Notifications.Config") as mock_config, \
|
|
patch("app.library.Notifications.BackgroundWorker") as mock_worker:
|
|
|
|
mock_config.get_instance.return_value = Mock(
|
|
debug=False, config_path="/tmp", app_version="1.0.0"
|
|
)
|
|
mock_worker.get_instance.return_value = Mock()
|
|
|
|
notification = Notification.get_instance()
|
|
result = await notification.noop()
|
|
|
|
assert result is None
|