diff options
Diffstat (limited to 'tests/tasks/test_extension.py')
| -rw-r--r-- | tests/tasks/test_extension.py | 175 |
1 files changed, 175 insertions, 0 deletions
diff --git a/tests/tasks/test_extension.py b/tests/tasks/test_extension.py new file mode 100644 index 0000000..5e9cdfa --- /dev/null +++ b/tests/tasks/test_extension.py @@ -0,0 +1,175 @@ +# SPDX-FileCopyrightText: 2026 Dennis Fink <me+coding@dennisfink.me> +# +# SPDX-License-Identifier: BSD-3-Clause + +from collections.abc import Callable +from typing import Any + +import huey as huey_package +import pytest +from flask import Flask, has_app_context +from huey import crontab + +from webmentions_ssg.tasks.extension import Huey + + +def test_init_without_app() -> None: + huey = Huey() + assert huey.app is None + with pytest.raises(RuntimeError, match="Huey has not been initialized"): + _ = huey.huey + + +def test_init_with_app(app: Flask) -> None: + huey = Huey(app) + assert huey.app is app + assert huey.huey.name == app.import_name + assert huey.huey.results is True + assert huey.huey.store_none is False + assert huey.huey.utc is True + assert huey.huey.immediate is True + assert app.extensions["huey"] is huey + + +def test_init_app_uses_huey_configuration(app: Flask) -> None: + app.config["HUEY"] = { + "url": "memory://", + "name": "custom-name", + "results": False, + "store_none": True, + "utc": False, + "immediate": False, + } + + huey = Huey() + huey.init_app(app) + + assert huey.huey.name == "custom-name" + assert huey.huey.results is False + assert huey.huey.store_none is True + assert huey.huey.utc is False + assert huey.huey.immediate is False + + +def test_huey_url_takes_precedence_over_huey_configuration(app: Flask) -> None: + app.config["HUEY"] = {"url": "blackhole://"} + app.config["HUEY_URL"] = "memory://" + + huey = Huey(app) + + assert type(huey.huey).__name__ == "MemoryHuey" + + +def task_decorator(huey: Huey, periodic: bool) -> Callable[[Callable[..., Any]], Any]: + if periodic: + return huey.periodic_task(crontab(minute="*")) + + return huey.task() + + +@pytest.mark.parametrize("periodic", [False, True], ids=["task", "periodic-task"]) +def test_task_runs_in_app_context(app: Flask, periodic: bool) -> None: + huey = Huey(app) + + @task_decorator(huey, periodic) + def add(a: int, b: int) -> int: + assert has_app_context() + return a + b + + assert add.call_local(2, 3) == 5 + + +@pytest.mark.parametrize("periodic", [False, True], ids=["task", "periodic-task"]) +def test_task_raises_without_app(app: Flask, periodic: bool) -> None: + huey = Huey(app) + + @task_decorator(huey, periodic) + def task() -> None: + pass + + huey.app = None + + with pytest.raises(RuntimeError, match="Flask app is not available"): + task.call_local() + + +def test_getattr_delegates_to_huey(app: Flask) -> None: + huey = Huey(app) + assert huey.immediate is True + + +@pytest.mark.parametrize( + ("url", "backend_name", "storage_kwargs"), + [ + ("redis://localhost:6379/0", "RedisHuey", {"url": "redis://localhost:6379/0"}), + ( + "rediss://localhost:6379/0", + "RedisHuey", + {"url": "rediss://localhost:6379/0"}, + ), + ( + "redis+priority://localhost:6379/0", + "PriorityRedisHuey", + {"url": "redis://localhost:6379/0"}, + ), + ( + "redis+expire://localhost:6379/0", + "RedisExpireHuey", + {"url": "redis://localhost:6379/0"}, + ), + ( + "redis+priority+expire://localhost:6379/0", + "PriorityRedisExpireHuey", + {"url": "redis://localhost:6379/0"}, + ), + ("sqlite:///var/huey.db", "SqliteHuey", {"filename": "var/huey.db"}), + ("file:///var/huey-queue", "FileHuey", {"path": "var/huey-queue"}), + ( + "postgres://user:password@localhost/database", + "PostgresHuey", + {"dsn": "postgres://user:password@localhost/database"}, + ), + ( + "postgresql://user:password@localhost/database", + "PostgresHuey", + {"dsn": "postgresql://user:password@localhost/database"}, + ), + ("memory://", "MemoryHuey", {}), + ("blackhole://", "BlackHoleHuey", {}), + ], +) +def test_backend_from_url( + monkeypatch: pytest.MonkeyPatch, + url: str, + backend_name: str, + storage_kwargs: dict[str, str], +) -> None: + backend_class = type(f"Test{backend_name}", (), {}) + + monkeypatch.setattr(huey_package, backend_name, backend_class) + + actual_class, actual_storage_kwargs = Huey.backend_from_url(url) + + assert actual_class is backend_class + assert actual_storage_kwargs == storage_kwargs + + +@pytest.mark.parametrize( + ("url", "message"), + [ + ( + "sqlite://var/huey.db", + "SQLite Huey URLs must look like sqlite:///var/huey.db", + ), + ("sqlite:///", "SQLite Huey URL must include a database path"), + ( + "file://var/huey-queue", + "File Huey URLs must look like file:///var/huey-queue", + ), + ("file:///", "File Huey URL must include a directory path"), + ("amqp://localhost", "Unsupported HUEY_URL scheme: 'amqp'"), + ], +) +def test_backend_from_url_rejects_invalid_url(url: str, message: str) -> None: + with pytest.raises(RuntimeError, match=message): + Huey.backend_from_url(url) |
