# SPDX-FileCopyrightText: 2026 Dennis Fink # # 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)