|
1 | 1 | """Tests for Celery health check.""" |
2 | 2 |
|
| 3 | +import datetime |
| 4 | +import logging |
3 | 5 | from unittest import mock |
4 | 6 |
|
5 | 7 | import pytest |
6 | 8 |
|
7 | 9 | pytest.importorskip("celery") |
8 | 10 |
|
| 11 | +from kombu import Queue |
| 12 | + |
9 | 13 | from health_check.contrib.celery import Ping as CeleryPingHealthCheck |
10 | 14 | from health_check.exceptions import ServiceUnavailable |
| 15 | +from tests.testapp.celery import app as celery_app |
| 16 | + |
| 17 | +logger = logging.getLogger(__name__) |
11 | 18 |
|
12 | 19 |
|
13 | 20 | class TestCelery: |
14 | 21 | """Test Celery ping health check.""" |
15 | 22 |
|
16 | | - @pytest.mark.asyncio |
17 | | - async def test_check_status__success(self): |
18 | | - """Report healthy when workers respond correctly.""" |
19 | | - app = mock.MagicMock() |
20 | | - app.ping.return_value = [{"celery@worker1": {"ok": "pong"}}] |
21 | | - check = CeleryPingHealthCheck() |
22 | | - check.app = app |
23 | | - |
24 | | - result = await check.get_result() |
25 | | - assert result.error is None |
26 | | - |
27 | | - @pytest.mark.asyncio |
28 | | - async def test_check_status__no_workers(self): |
29 | | - """Raise ServiceUnavailable when no workers respond.""" |
30 | | - mock_app = mock.MagicMock() |
31 | | - mock_app.control.ping.return_value = {} |
32 | | - check = CeleryPingHealthCheck() |
33 | | - check.app = mock_app |
34 | | - |
35 | | - result = await check.get_result() |
36 | | - assert result.error is not None |
37 | | - assert isinstance(result.error, ServiceUnavailable) |
38 | | - assert "unavailable" in str(result.error).lower() |
39 | | - |
40 | 23 | @pytest.mark.asyncio |
41 | 24 | async def test_check_status__unexpected_response(self): |
42 | 25 | """Raise ServiceUnavailable when worker response is incorrect.""" |
43 | | - mock_result = {"celery@worker1": {"bad": "response"}} |
44 | 26 | mock_app = mock.MagicMock() |
45 | | - mock_app.control.ping.return_value = [mock_result] |
| 27 | + mock_app.control.ping.return_value = [{"celery@worker1": {"bad": "response"}}] |
46 | 28 | check = CeleryPingHealthCheck() |
47 | 29 | check.app = mock_app |
48 | 30 |
|
@@ -87,24 +69,92 @@ async def test_check_status__unknown_error(self): |
87 | 69 | assert result.error is not None |
88 | 70 | assert isinstance(result.error, ServiceUnavailable) |
89 | 71 |
|
| 72 | + @pytest.mark.asyncio |
| 73 | + async def test_check_status__success(self): |
| 74 | + """Report healthy when using real Celery app with configured queues.""" |
| 75 | + default_queue = Queue("default", routing_key="default") |
| 76 | + celery_app.conf.task_queues = [default_queue] |
| 77 | + celery_app.conf.task_default_queue = "default" |
| 78 | + |
| 79 | + check = CeleryPingHealthCheck(app=celery_app) |
| 80 | + |
| 81 | + # Mock the control methods directly on the app before get_result runs |
| 82 | + mock_ping = mock.MagicMock(return_value=[{"celery@worker1": {"ok": "pong"}}]) |
| 83 | + mock_inspect_obj = mock.MagicMock() |
| 84 | + mock_inspect_obj.active_queues.return_value = { |
| 85 | + "celery@worker1": [{"name": "default"}] |
| 86 | + } |
| 87 | + mock_inspect = mock.MagicMock(return_value=mock_inspect_obj) |
| 88 | + |
| 89 | + original_ping = check.app.control.ping |
| 90 | + original_inspect = check.app.control.inspect |
| 91 | + |
| 92 | + try: |
| 93 | + check.app.control.ping = mock_ping |
| 94 | + check.app.control.inspect = mock_inspect |
| 95 | + |
| 96 | + result = await check.get_result() |
| 97 | + assert result.error is None |
| 98 | + finally: |
| 99 | + check.app.control.ping = original_ping |
| 100 | + check.app.control.inspect = original_inspect |
| 101 | + |
| 102 | + @pytest.mark.asyncio |
| 103 | + async def test_check_status__no_workers(self): |
| 104 | + """Raise ServiceUnavailable when real app receives no worker response.""" |
| 105 | + default_queue = Queue("default", routing_key="default") |
| 106 | + celery_app.conf.task_queues = [default_queue] |
| 107 | + celery_app.conf.task_default_queue = "default" |
| 108 | + |
| 109 | + check = CeleryPingHealthCheck( |
| 110 | + app=celery_app, |
| 111 | + timeout=datetime.timedelta(milliseconds=100), |
| 112 | + ) |
| 113 | + result = await check.get_result() |
| 114 | + |
| 115 | + assert result.error is not None |
| 116 | + assert isinstance(result.error, ServiceUnavailable) |
| 117 | + assert "unavailable" in str(result.error).lower() |
| 118 | + |
90 | 119 | @pytest.mark.asyncio |
91 | 120 | async def test_check_status__missing_queue_worker(self): |
92 | | - """Raise ServiceUnavailable when a defined queue has no active workers.""" |
93 | | - mock_result = {"celery@worker1": {"ok": "pong"}} |
| 121 | + """Verify queue validation with real Celery app configuration.""" |
| 122 | + multiple_queues = [ |
| 123 | + Queue("default", routing_key="default"), |
| 124 | + Queue("integration_queue", routing_key="integration_queue"), |
| 125 | + ] |
| 126 | + celery_app.conf.task_queues = multiple_queues |
| 127 | + celery_app.conf.task_default_queue = "default" |
| 128 | + |
| 129 | + ping_response = [{"celery@worker1": {"ok": "pong"}}] |
| 130 | + inspect_response = {"celery@worker1": [{"name": "default"}]} |
| 131 | + |
| 132 | + check = CeleryPingHealthCheck(app=celery_app) |
| 133 | + check.app.control.ping = lambda **kwargs: ping_response |
| 134 | + check.app.control.inspect = lambda *args: mock.MagicMock( |
| 135 | + active_queues=lambda: inspect_response |
| 136 | + ) |
| 137 | + |
| 138 | + result = await check.get_result() |
| 139 | + assert result.error is not None |
| 140 | + assert isinstance(result.error, ServiceUnavailable) |
| 141 | + assert "integration_queue" in str(result.error) |
| 142 | + |
| 143 | + @pytest.mark.asyncio |
| 144 | + async def test_check_status__raise_type_error__default_queue(self): |
| 145 | + """Verify that default queue is checked when task_queues is None.""" |
94 | 146 | mock_app = mock.MagicMock() |
95 | | - mock_app.control.ping.return_value = [mock_result] |
96 | | - mock_queue = mock.MagicMock() |
97 | | - mock_queue.name = "missing_queue" |
98 | | - mock_app.conf.task_queues = [mock_queue] |
| 147 | + mock_app.conf.task_queues = None |
| 148 | + mock_app.conf.task_default_queue = "default" |
| 149 | + mock_app.control.ping.return_value = [{"celery@worker1": {"ok": "pong"}}] |
99 | 150 | mock_inspect = mock.MagicMock() |
100 | 151 | mock_inspect.active_queues.return_value = { |
101 | | - "celery@worker1": [{"name": "celery"}] |
| 152 | + "celery@worker1": [{"name": "default"}] |
102 | 153 | } |
103 | 154 | mock_app.control.inspect.return_value = mock_inspect |
| 155 | + |
104 | 156 | check = CeleryPingHealthCheck() |
105 | 157 | check.app = mock_app |
106 | 158 |
|
107 | 159 | result = await check.get_result() |
108 | | - assert result.error is not None |
109 | | - assert isinstance(result.error, ServiceUnavailable) |
110 | | - assert "missing_queue" in str(result.error) |
| 160 | + assert result.error is None |
0 commit comments