aboutsummaryrefslogtreecommitdiff
path: root/tests/tasks/test_extension.py
blob: 5e9cdfa837d948809b534a24573a2e71bef36f0b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
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)