2
2
import os
3
3
import pathlib
4
4
import subprocess
5
- import warnings
6
- from unittest .mock import MagicMock , patch
5
+ from unittest .mock import patch
7
6
8
7
import pytest
9
8
10
- from notifications_utils .logging .celery import set_up_logging , setup_logging_connect
9
+ from notifications_utils .logging .celery import setup_logging_connect
11
10
12
11
13
- def test_set_up_logging_success ():
14
- """Test that set_up_logging correctly overrides warnings.showwarning."""
15
- logger = MagicMock ()
16
- set_up_logging (logger )
12
+ class Config :
13
+ CELERY_WORKER_LOG_LEVEL = "CRITICAL"
14
+ CELERY_BEAT_LOG_LEVEL = "INFO"
17
15
18
- # Assert that warnings.showwarning is overridden
19
- assert warnings .showwarning != warnings ._showwarning_orig
20
-
21
- # Simulate a warning and check if it logs correctly
22
- warnings .showwarning ("Test message" , UserWarning , "test_file.py" , 42 )
23
- logger .warning .assert_called_once_with (
24
- "Test message" ,
25
- {
26
- "level" : "WARNING" ,
27
- "message" : "Test message" ,
28
- "category" : "UserWarning" ,
29
- "filename" : "test_file.py" ,
30
- "lineno" : 42 ,
31
- },
32
- )
33
-
34
-
35
- def test_set_up_logging_missing_logger ():
36
- logger = None
37
-
38
- with pytest .raises (AttributeError , match = "The provided logger object is invalid." ):
39
- set_up_logging (logger )
16
+ def get (self , key , default = None ):
17
+ return getattr (self , key , default )
40
18
41
19
42
20
@patch ("notifications_utils.logging.celery.dictConfig" )
21
+ @patch ("notifications_utils.logging.celery.config" , Config ())
43
22
def test_setup_logging_connect_success (mock_dict_config ):
44
23
"""Test that setup_logging_connect successfully configures logging."""
24
+
45
25
setup_logging_connect ()
46
26
47
27
# Assert that dictConfig was called
48
28
mock_dict_config .assert_called_once ()
49
29
50
30
51
- def test_celery_dummy_logs (tmp_path ):
52
- command = [
53
- "celery" ,
54
- "--quiet" ,
55
- "-A" ,
56
- "dummy_celery_app" ,
57
- "worker" ,
58
- "--loglevel=INFO" ,
59
- ]
60
-
61
- # Set the environment variable
62
- env = os .environ .copy ()
63
- env ["CELERY_LOG_LEVEL" ] = "INFO"
31
+ def assert_command_has_outputs (tmp_path , command , filename , expected_messages , unexpected_messages = None , env = None ):
32
+ if unexpected_messages is None :
33
+ unexpected_messages = []
64
34
65
- # assemble an example celery app directory, able to access notifications_utils
66
- # via a cwd link
67
35
(tmp_path / "notifications_utils" ).symlink_to (pathlib .Path (__file__ ).parent .parent .parent / "notifications_utils" )
68
- (tmp_path / "dummy_celery_app.py" ).symlink_to (pathlib .Path (__file__ ).parent / "dummy_celery_app.py" )
36
+ (tmp_path / filename ).symlink_to (pathlib .Path (__file__ ).parent / filename )
69
37
70
38
try :
71
39
# Start the Celery worker process
@@ -74,19 +42,13 @@ def test_celery_dummy_logs(tmp_path):
74
42
stdout = subprocess .PIPE ,
75
43
stderr = subprocess .PIPE ,
76
44
text = True ,
77
- env = env ,
78
45
cwd = tmp_path ,
46
+ env = env ,
79
47
)
80
48
81
49
stdout , stderr = process .communicate (timeout = 10 )
82
50
logs = stdout + stderr
83
51
84
- expected_messages = [
85
- "No hostname was supplied. Reverting to default 'localhost'" ,
86
- "Connected to memory://localhost//" ,
87
- "Task dummy_celery_app.test_task" ,
88
- ]
89
-
90
52
# Parse the logs as JSON and check the messages field contains the expected messages
91
53
for log_line in logs .splitlines ():
92
54
try :
@@ -95,6 +57,10 @@ def test_celery_dummy_logs(tmp_path):
95
57
for message in expected_messages :
96
58
if message in log_message :
97
59
expected_messages .remove (message )
60
+ for bad_message in unexpected_messages :
61
+ assert bad_message not in log_message , (
62
+ f"Unexpected message found in logs: '{ bad_message } '. Logs are:\n { logs } "
63
+ )
98
64
except json .JSONDecodeError :
99
65
continue
100
66
@@ -107,3 +73,51 @@ def test_celery_dummy_logs(tmp_path):
107
73
pytest .fail ("Celery command not found. Ensure Celery is installed and in PATH." )
108
74
except Exception as e :
109
75
pytest .fail (f"Unexpected error occurred: { e } " )
76
+
77
+
78
+ @pytest .mark .slow
79
+ def test_celery_dummy_logs (tmp_path ):
80
+ command = ["celery" , "--quiet" , "-A" , "dummy_celery_app" , "worker" , "-B" ]
81
+
82
+ expected_messages = [
83
+ "Connected to filesystem://localhost//" ,
84
+ "beat: Starting..." ,
85
+ "Task test_task" ,
86
+ "Scheduler: Sending due task test-task" ,
87
+ "beat: Shutting down..." ,
88
+ ]
89
+
90
+ env = os .environ .copy ()
91
+ env ["CELERY_WORKER_LOG_LEVEL" ] = "INFO"
92
+ env ["CELERY_BEAT_LOG_LEVEL" ] = "INFO"
93
+ assert_command_has_outputs (tmp_path , command , "dummy_celery_app.py" , expected_messages , env = env )
94
+
95
+
96
+ @pytest .mark .slow
97
+ def test_celery_worker_logs_absent (tmp_path ):
98
+ command = [
99
+ "celery" ,
100
+ "--quiet" ,
101
+ "-A" ,
102
+ "dummy_celery_app" ,
103
+ "worker" ,
104
+ "-B" ,
105
+ ]
106
+
107
+ expected_messages = [
108
+ "beat: Starting..." ,
109
+ "Scheduler: Sending due task test-task" ,
110
+ "beat: Shutting down..." ,
111
+ ]
112
+
113
+ unexpected_messages = ["Connected to filesystem://localhost//" , "Task test_task" ]
114
+
115
+ env = os .environ .copy ()
116
+ # test we aren't leaking celery worker logs when set to CRITICAL. They could contain PII
117
+ # or other sensitive data.
118
+ env ["CELERY_WORKER_LOG_LEVEL" ] = "CRITICAL"
119
+ env ["CELERY_BEAT_LOG_LEVEL" ] = "INFO"
120
+
121
+ assert_command_has_outputs (
122
+ tmp_path , command , "dummy_celery_app.py" , expected_messages , unexpected_messages , env = env
123
+ )
0 commit comments