cledar-sdk 2.0.2__py3-none-any.whl → 2.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cledar/__init__.py +0 -0
- cledar/kafka/README.md +239 -0
- cledar/kafka/__init__.py +40 -0
- cledar/kafka/clients/base.py +98 -0
- cledar/kafka/clients/consumer.py +110 -0
- cledar/kafka/clients/producer.py +80 -0
- cledar/kafka/config/schemas.py +178 -0
- cledar/kafka/exceptions.py +22 -0
- cledar/kafka/handlers/dead_letter.py +82 -0
- cledar/kafka/handlers/parser.py +49 -0
- cledar/kafka/logger.py +3 -0
- cledar/kafka/models/input.py +13 -0
- cledar/kafka/models/message.py +10 -0
- cledar/kafka/models/output.py +8 -0
- cledar/kafka/tests/.env.test.kafka +3 -0
- cledar/kafka/tests/README.md +216 -0
- cledar/kafka/tests/conftest.py +104 -0
- cledar/kafka/tests/integration/__init__.py +1 -0
- cledar/kafka/tests/integration/conftest.py +78 -0
- cledar/kafka/tests/integration/helpers.py +47 -0
- cledar/kafka/tests/integration/test_consumer_integration.py +375 -0
- cledar/kafka/tests/integration/test_integration.py +394 -0
- cledar/kafka/tests/integration/test_producer_consumer_interaction.py +388 -0
- cledar/kafka/tests/integration/test_producer_integration.py +217 -0
- cledar/kafka/tests/unit/__init__.py +1 -0
- cledar/kafka/tests/unit/test_base_kafka_client.py +391 -0
- cledar/kafka/tests/unit/test_config_validation.py +609 -0
- cledar/kafka/tests/unit/test_dead_letter_handler.py +443 -0
- cledar/kafka/tests/unit/test_error_handling.py +674 -0
- cledar/kafka/tests/unit/test_input_parser.py +310 -0
- cledar/kafka/tests/unit/test_input_parser_comprehensive.py +489 -0
- cledar/kafka/tests/unit/test_utils.py +25 -0
- cledar/kafka/tests/unit/test_utils_comprehensive.py +408 -0
- cledar/kafka/utils/callbacks.py +19 -0
- cledar/kafka/utils/messages.py +28 -0
- cledar/kafka/utils/topics.py +2 -0
- cledar/kserve/README.md +352 -0
- cledar/kserve/__init__.py +3 -0
- cledar/kserve/tests/__init__.py +0 -0
- cledar/kserve/tests/test_utils.py +64 -0
- cledar/kserve/utils.py +27 -0
- cledar/logging/README.md +53 -0
- cledar/logging/__init__.py +3 -0
- cledar/logging/tests/test_universal_plaintext_formatter.py +249 -0
- cledar/logging/universal_plaintext_formatter.py +94 -0
- cledar/monitoring/README.md +71 -0
- cledar/monitoring/__init__.py +3 -0
- cledar/monitoring/monitoring_server.py +112 -0
- cledar/monitoring/tests/integration/test_monitoring_server_int.py +162 -0
- cledar/monitoring/tests/test_monitoring_server.py +59 -0
- cledar/nonce/README.md +99 -0
- cledar/nonce/__init__.py +3 -0
- cledar/nonce/nonce_service.py +36 -0
- cledar/nonce/tests/__init__.py +0 -0
- cledar/nonce/tests/test_nonce_service.py +136 -0
- cledar/redis/README.md +536 -0
- cledar/redis/__init__.py +15 -0
- cledar/redis/async_example.py +111 -0
- cledar/redis/example.py +37 -0
- cledar/redis/exceptions.py +22 -0
- cledar/redis/logger.py +3 -0
- cledar/redis/model.py +10 -0
- cledar/redis/redis.py +525 -0
- cledar/redis/redis_config_store.py +252 -0
- cledar/redis/tests/test_async_integration_redis.py +158 -0
- cledar/redis/tests/test_async_redis_service.py +380 -0
- cledar/redis/tests/test_integration_redis.py +119 -0
- cledar/redis/tests/test_redis_service.py +319 -0
- cledar/storage/README.md +529 -0
- cledar/storage/__init__.py +4 -0
- cledar/storage/constants.py +3 -0
- cledar/storage/exceptions.py +50 -0
- cledar/storage/models.py +19 -0
- cledar/storage/object_storage.py +955 -0
- cledar/storage/tests/conftest.py +18 -0
- cledar/storage/tests/test_abfs.py +164 -0
- cledar/storage/tests/test_integration_filesystem.py +359 -0
- cledar/storage/tests/test_integration_s3.py +453 -0
- cledar/storage/tests/test_local.py +384 -0
- cledar/storage/tests/test_s3.py +521 -0
- {cledar_sdk-2.0.2.dist-info → cledar_sdk-2.0.3.dist-info}/METADATA +1 -1
- cledar_sdk-2.0.3.dist-info/RECORD +84 -0
- cledar_sdk-2.0.2.dist-info/RECORD +0 -4
- {cledar_sdk-2.0.2.dist-info → cledar_sdk-2.0.3.dist-info}/WHEEL +0 -0
- {cledar_sdk-2.0.2.dist-info → cledar_sdk-2.0.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Comprehensive tests for utils modules covering topics, callbacks, and messages
|
|
3
|
+
utilities.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
from unittest.mock import MagicMock, patch
|
|
8
|
+
|
|
9
|
+
from confluent_kafka import KafkaError
|
|
10
|
+
|
|
11
|
+
from cledar.kafka.utils.callbacks import delivery_callback
|
|
12
|
+
from cledar.kafka.utils.messages import (
|
|
13
|
+
consumer_not_connected_msg,
|
|
14
|
+
extract_id_from_value,
|
|
15
|
+
)
|
|
16
|
+
from cledar.kafka.utils.topics import build_topic
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_build_topic_with_prefix() -> None:
|
|
20
|
+
"""Test building topic name with prefix."""
|
|
21
|
+
result = build_topic("test-topic", "prefix.")
|
|
22
|
+
assert result == "prefix.test-topic"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_build_topic_without_prefix() -> None:
|
|
26
|
+
"""Test building topic name without prefix."""
|
|
27
|
+
result = build_topic("test-topic", None)
|
|
28
|
+
assert result == "test-topic"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_build_topic_with_empty_prefix() -> None:
|
|
32
|
+
"""Test building topic name with empty prefix."""
|
|
33
|
+
result = build_topic("test-topic", "")
|
|
34
|
+
assert result == "test-topic"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_build_topic_with_empty_topic_name() -> None:
|
|
38
|
+
"""Test building topic name with empty topic name."""
|
|
39
|
+
result = build_topic("", "prefix.")
|
|
40
|
+
assert result == "prefix."
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_build_topic_with_special_characters() -> None:
|
|
44
|
+
"""Test building topic name with special characters."""
|
|
45
|
+
special_topic = "topic-with-special-chars-@#$%"
|
|
46
|
+
result = build_topic(special_topic, "prefix.")
|
|
47
|
+
assert result == f"prefix.{special_topic}"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_build_topic_with_unicode() -> None:
|
|
51
|
+
"""Test building topic name with unicode characters."""
|
|
52
|
+
unicode_topic = "topic-with-unicode-测试"
|
|
53
|
+
result = build_topic(unicode_topic, "prefix.")
|
|
54
|
+
assert result == f"prefix.{unicode_topic}"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_build_topic_with_numbers() -> None:
|
|
58
|
+
"""Test building topic name with numbers."""
|
|
59
|
+
numeric_topic = "topic123"
|
|
60
|
+
result = build_topic(numeric_topic, "prefix.")
|
|
61
|
+
assert result == f"prefix.{numeric_topic}"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_build_topic_with_underscores() -> None:
|
|
65
|
+
"""Test building topic name with underscores."""
|
|
66
|
+
underscore_topic = "topic_with_underscores"
|
|
67
|
+
result = build_topic(underscore_topic, "prefix.")
|
|
68
|
+
assert result == f"prefix.{underscore_topic}"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_build_topic_with_dots_in_topic() -> None:
|
|
72
|
+
"""Test building topic name with dots."""
|
|
73
|
+
dotted_topic = "topic.with.dots"
|
|
74
|
+
result = build_topic(dotted_topic, "prefix.")
|
|
75
|
+
assert result == f"prefix.{dotted_topic}"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_build_topic_with_dots_in_prefix() -> None:
|
|
79
|
+
"""Test building topic name with dots in prefix."""
|
|
80
|
+
result = build_topic("test-topic", "prefix.with.dots.")
|
|
81
|
+
assert result == "prefix.with.dots.test-topic"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_build_topic_with_spaces() -> None:
|
|
85
|
+
"""Test building topic name with spaces."""
|
|
86
|
+
spaced_topic = "topic with spaces"
|
|
87
|
+
result = build_topic(spaced_topic, "prefix.")
|
|
88
|
+
assert result == f"prefix.{spaced_topic}"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_build_topic_with_long_names() -> None:
|
|
92
|
+
"""Test building topic name with long names."""
|
|
93
|
+
long_topic = "a" * 1000 # Very long topic name
|
|
94
|
+
long_prefix = "b" * 1000 # Very long prefix
|
|
95
|
+
|
|
96
|
+
result = build_topic(long_topic, long_prefix)
|
|
97
|
+
assert result == long_prefix + long_topic
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_build_topic_multiple_calls() -> None:
|
|
101
|
+
"""Test building multiple topic names."""
|
|
102
|
+
topics = ["topic1", "topic2", "topic3"]
|
|
103
|
+
prefix = "prefix."
|
|
104
|
+
|
|
105
|
+
results = [build_topic(topic, prefix) for topic in topics]
|
|
106
|
+
|
|
107
|
+
assert results == ["prefix.topic1", "prefix.topic2", "prefix.topic3"]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@patch("cledar.kafka.utils.callbacks.logger")
|
|
111
|
+
def test_delivery_callback_success(mock_logger: MagicMock) -> None:
|
|
112
|
+
"""Test delivery callback with successful delivery."""
|
|
113
|
+
mock_error = None
|
|
114
|
+
mock_message = MagicMock()
|
|
115
|
+
mock_message.topic.return_value = "test-topic"
|
|
116
|
+
mock_message.value.return_value = b"test-value"
|
|
117
|
+
|
|
118
|
+
delivery_callback(mock_error, mock_message)
|
|
119
|
+
|
|
120
|
+
mock_logger.debug.assert_called_once()
|
|
121
|
+
call_args = mock_logger.debug.call_args
|
|
122
|
+
assert call_args[1]["extra"]["topic"] == "test-topic"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@patch("cledar.kafka.utils.callbacks.logger")
|
|
126
|
+
def test_delivery_callback_error(mock_logger: MagicMock) -> None:
|
|
127
|
+
"""Test delivery callback with error."""
|
|
128
|
+
mock_error = KafkaError(1, "Test error")
|
|
129
|
+
mock_message = MagicMock()
|
|
130
|
+
mock_message.topic.return_value = "test-topic"
|
|
131
|
+
|
|
132
|
+
delivery_callback(mock_error, mock_message)
|
|
133
|
+
|
|
134
|
+
mock_logger.error.assert_called_once()
|
|
135
|
+
call_args = mock_logger.error.call_args
|
|
136
|
+
assert call_args[1]["extra"]["error"] == mock_error
|
|
137
|
+
assert call_args[1]["extra"]["topic"] == "test-topic"
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@patch("cledar.kafka.utils.callbacks.logger")
|
|
141
|
+
def test_delivery_callback_with_none_topic(mock_logger: MagicMock) -> None:
|
|
142
|
+
"""Test delivery callback with None topic."""
|
|
143
|
+
mock_error = None
|
|
144
|
+
mock_message = MagicMock()
|
|
145
|
+
mock_message.topic.return_value = None
|
|
146
|
+
|
|
147
|
+
delivery_callback(mock_error, mock_message)
|
|
148
|
+
|
|
149
|
+
mock_logger.debug.assert_called_once()
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@patch("cledar.kafka.utils.callbacks.logger")
|
|
153
|
+
def test_delivery_callback_with_exception_in_topic(mock_logger: MagicMock) -> None:
|
|
154
|
+
"""Test delivery callback with exception in topic method."""
|
|
155
|
+
mock_error = None
|
|
156
|
+
mock_message = MagicMock()
|
|
157
|
+
mock_message.topic.side_effect = Exception("Topic error")
|
|
158
|
+
|
|
159
|
+
delivery_callback(mock_error, mock_message)
|
|
160
|
+
|
|
161
|
+
mock_logger.debug.assert_called_once()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@patch("cledar.kafka.utils.callbacks.logger")
|
|
165
|
+
def test_delivery_callback_multiple_calls(mock_logger: MagicMock) -> None:
|
|
166
|
+
"""Test delivery callback with multiple calls."""
|
|
167
|
+
mock_error = None
|
|
168
|
+
mock_message = MagicMock()
|
|
169
|
+
mock_message.topic.return_value = "test-topic"
|
|
170
|
+
|
|
171
|
+
# Call multiple times
|
|
172
|
+
delivery_callback(mock_error, mock_message)
|
|
173
|
+
delivery_callback(mock_error, mock_message)
|
|
174
|
+
|
|
175
|
+
assert mock_logger.debug.call_count == 2
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@patch("cledar.kafka.utils.callbacks.logger")
|
|
179
|
+
def test_delivery_callback_with_real_kafka_error(mock_logger: MagicMock) -> None:
|
|
180
|
+
"""Test delivery callback with real Kafka error."""
|
|
181
|
+
mock_error = KafkaError(1, "Broker: Unknown topic or partition")
|
|
182
|
+
mock_message = MagicMock()
|
|
183
|
+
mock_message.topic.return_value = "test-topic"
|
|
184
|
+
|
|
185
|
+
delivery_callback(mock_error, mock_message)
|
|
186
|
+
|
|
187
|
+
mock_logger.error.assert_called_once()
|
|
188
|
+
call_args = mock_logger.error.call_args
|
|
189
|
+
assert call_args[1]["extra"]["error"] == mock_error
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@patch("cledar.kafka.utils.messages.logger")
|
|
193
|
+
def test_extract_id_from_value_valid_json(mock_logger: MagicMock) -> None:
|
|
194
|
+
"""Test extracting ID from valid JSON."""
|
|
195
|
+
valid_json = '{"id": "123", "name": "test"}'
|
|
196
|
+
result = extract_id_from_value(valid_json)
|
|
197
|
+
|
|
198
|
+
assert result == "123"
|
|
199
|
+
mock_logger.error.assert_not_called()
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@patch("cledar.kafka.utils.messages.logger")
|
|
203
|
+
def test_extract_id_from_value_missing_id_field(mock_logger: MagicMock) -> None:
|
|
204
|
+
"""Test extracting ID from JSON without id field."""
|
|
205
|
+
json_without_id = '{"name": "test", "value": 42}'
|
|
206
|
+
result = extract_id_from_value(json_without_id)
|
|
207
|
+
|
|
208
|
+
assert result == "<unknown_id>"
|
|
209
|
+
mock_logger.error.assert_not_called()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
@patch("cledar.kafka.utils.messages.logger")
|
|
213
|
+
def test_extract_id_from_value_none_value(mock_logger: MagicMock) -> None:
|
|
214
|
+
"""Test extracting ID from None value."""
|
|
215
|
+
result = extract_id_from_value(None)
|
|
216
|
+
|
|
217
|
+
assert result == "<unknown_id>"
|
|
218
|
+
mock_logger.error.assert_not_called()
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@patch("cledar.kafka.utils.messages.logger")
|
|
222
|
+
def test_extract_id_from_value_invalid_json(mock_logger: MagicMock) -> None:
|
|
223
|
+
"""Test extracting ID from invalid JSON."""
|
|
224
|
+
invalid_json = '{"id": "123", "name": "test"' # Missing closing brace
|
|
225
|
+
result = extract_id_from_value(invalid_json)
|
|
226
|
+
|
|
227
|
+
assert result == "<unknown_id>"
|
|
228
|
+
mock_logger.error.assert_called_once()
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@patch("cledar.kafka.utils.messages.logger")
|
|
232
|
+
def test_extract_id_from_value_empty_string(mock_logger: MagicMock) -> None:
|
|
233
|
+
"""Test extracting ID from empty string."""
|
|
234
|
+
result = extract_id_from_value("")
|
|
235
|
+
|
|
236
|
+
assert result == "<unknown_id>"
|
|
237
|
+
mock_logger.error.assert_called_once()
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@patch("cledar.kafka.utils.messages.logger")
|
|
241
|
+
def test_extract_id_from_value_non_json_string(mock_logger: MagicMock) -> None:
|
|
242
|
+
"""Test extracting ID from non-JSON string."""
|
|
243
|
+
non_json = "This is not JSON"
|
|
244
|
+
result = extract_id_from_value(non_json)
|
|
245
|
+
|
|
246
|
+
assert result == "<unknown_id>"
|
|
247
|
+
mock_logger.error.assert_called_once()
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@patch("cledar.kafka.utils.messages.logger")
|
|
251
|
+
def test_extract_id_from_value_id_is_null(mock_logger: MagicMock) -> None:
|
|
252
|
+
"""Test extracting ID when id field is null."""
|
|
253
|
+
json_with_null_id = '{"id": null, "name": "test"}'
|
|
254
|
+
result = extract_id_from_value(json_with_null_id)
|
|
255
|
+
|
|
256
|
+
assert result == "None" # Should convert to string
|
|
257
|
+
mock_logger.error.assert_not_called()
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@patch("cledar.kafka.utils.messages.logger")
|
|
261
|
+
def test_extract_id_from_value_id_is_number(mock_logger: MagicMock) -> None:
|
|
262
|
+
"""Test extracting ID when id field is a number."""
|
|
263
|
+
json_with_numeric_id = '{"id": 123, "name": "test"}'
|
|
264
|
+
result = extract_id_from_value(json_with_numeric_id)
|
|
265
|
+
|
|
266
|
+
assert result == "123" # Should convert to string
|
|
267
|
+
mock_logger.error.assert_not_called()
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@patch("cledar.kafka.utils.messages.logger")
|
|
271
|
+
def test_extract_id_from_value_id_is_boolean(mock_logger: MagicMock) -> None:
|
|
272
|
+
"""Test extracting ID when id field is a boolean."""
|
|
273
|
+
json_with_boolean_id = '{"id": true, "name": "test"}'
|
|
274
|
+
result = extract_id_from_value(json_with_boolean_id)
|
|
275
|
+
|
|
276
|
+
assert result == "True" # Should convert to string
|
|
277
|
+
mock_logger.error.assert_not_called()
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@patch("cledar.kafka.utils.messages.logger")
|
|
281
|
+
def test_extract_id_from_value_id_is_array(mock_logger: MagicMock) -> None:
|
|
282
|
+
"""Test extracting ID when id field is an array."""
|
|
283
|
+
json_with_array_id = '{"id": [1, 2, 3], "name": "test"}'
|
|
284
|
+
result = extract_id_from_value(json_with_array_id)
|
|
285
|
+
|
|
286
|
+
assert result == "[1, 2, 3]" # Should convert to string
|
|
287
|
+
mock_logger.error.assert_not_called()
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@patch("cledar.kafka.utils.messages.logger")
|
|
291
|
+
def test_extract_id_from_value_id_is_object(mock_logger: MagicMock) -> None:
|
|
292
|
+
"""Test extracting ID when id field is an object."""
|
|
293
|
+
json_with_object_id = '{"id": {"sub": "value"}, "name": "test"}'
|
|
294
|
+
result = extract_id_from_value(json_with_object_id)
|
|
295
|
+
|
|
296
|
+
assert result == "{'sub': 'value'}" # Should convert to string
|
|
297
|
+
mock_logger.error.assert_not_called()
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@patch("cledar.kafka.utils.messages.logger")
|
|
301
|
+
def test_extract_id_from_value_empty_json(mock_logger: MagicMock) -> None:
|
|
302
|
+
"""Test extracting ID from empty JSON object."""
|
|
303
|
+
empty_json = "{}"
|
|
304
|
+
result = extract_id_from_value(empty_json)
|
|
305
|
+
|
|
306
|
+
assert result == "<unknown_id>"
|
|
307
|
+
mock_logger.error.assert_not_called()
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
@patch("cledar.kafka.utils.messages.logger")
|
|
311
|
+
def test_extract_id_from_value_nested_json(mock_logger: MagicMock) -> None:
|
|
312
|
+
"""Test extracting ID from nested JSON."""
|
|
313
|
+
nested_json = '{"data": {"id": "123", "nested": {"value": 42}}}'
|
|
314
|
+
result = extract_id_from_value(nested_json)
|
|
315
|
+
|
|
316
|
+
assert result == "<unknown_id>" # Only looks for top-level "id"
|
|
317
|
+
mock_logger.error.assert_not_called()
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
@patch("cledar.kafka.utils.messages.logger")
|
|
321
|
+
def test_extract_id_from_value_with_special_characters(mock_logger: MagicMock) -> None:
|
|
322
|
+
"""Test extracting ID with special characters."""
|
|
323
|
+
special_json = '{"id": "test@#$%^&*()", "name": "test"}'
|
|
324
|
+
result = extract_id_from_value(special_json)
|
|
325
|
+
|
|
326
|
+
assert result == "test@#$%^&*()"
|
|
327
|
+
mock_logger.error.assert_not_called()
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
@patch("cledar.kafka.utils.messages.logger")
|
|
331
|
+
def test_extract_id_from_value_with_unicode(mock_logger: MagicMock) -> None:
|
|
332
|
+
"""Test extracting ID with unicode characters."""
|
|
333
|
+
unicode_json = '{"id": "测试", "name": "test"}'
|
|
334
|
+
result = extract_id_from_value(unicode_json)
|
|
335
|
+
|
|
336
|
+
assert result == "测试"
|
|
337
|
+
mock_logger.error.assert_not_called()
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
@patch("cledar.kafka.utils.messages.logger")
|
|
341
|
+
def test_extract_id_from_value_with_whitespace(mock_logger: MagicMock) -> None:
|
|
342
|
+
"""Test extracting ID with whitespace."""
|
|
343
|
+
whitespace_json = '{"id": " test ", "name": "test"}'
|
|
344
|
+
result = extract_id_from_value(whitespace_json)
|
|
345
|
+
|
|
346
|
+
assert result == " test " # Preserves whitespace
|
|
347
|
+
mock_logger.error.assert_not_called()
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
@patch("cledar.kafka.utils.messages.logger")
|
|
351
|
+
def test_extract_id_from_value_large_json(mock_logger: MagicMock) -> None:
|
|
352
|
+
"""Test extracting ID from large JSON."""
|
|
353
|
+
large_data = {"key" + str(i): "value" + str(i) for i in range(1000)}
|
|
354
|
+
large_json = json.dumps({"id": "123", "data": large_data})
|
|
355
|
+
result = extract_id_from_value(large_json)
|
|
356
|
+
|
|
357
|
+
assert result == "123"
|
|
358
|
+
mock_logger.error.assert_not_called()
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def test_consumer_not_connected_msg_constant() -> None:
|
|
362
|
+
"""Test that consumer_not_connected_msg is a constant."""
|
|
363
|
+
assert (
|
|
364
|
+
consumer_not_connected_msg
|
|
365
|
+
== "KafkaConsumer is not connected. Call connect first."
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def test_consumer_not_connected_msg_immutable() -> None:
|
|
370
|
+
"""Test that consumer_not_connected_msg is a constant."""
|
|
371
|
+
# The constant should always have the same value
|
|
372
|
+
assert (
|
|
373
|
+
consumer_not_connected_msg
|
|
374
|
+
== "KafkaConsumer is not connected. Call connect first."
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
# Test that it's a string constant
|
|
378
|
+
assert isinstance(consumer_not_connected_msg, str)
|
|
379
|
+
assert len(consumer_not_connected_msg) > 0
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
@patch("cledar.kafka.utils.messages.logger")
|
|
383
|
+
def test_extract_id_from_value_multiple_calls(mock_logger: MagicMock) -> None:
|
|
384
|
+
"""Test extracting ID with multiple calls."""
|
|
385
|
+
json1 = '{"id": "123", "name": "test1"}'
|
|
386
|
+
json2 = '{"id": "456", "name": "test2"}'
|
|
387
|
+
|
|
388
|
+
result1 = extract_id_from_value(json1)
|
|
389
|
+
result2 = extract_id_from_value(json2)
|
|
390
|
+
|
|
391
|
+
assert result1 == "123"
|
|
392
|
+
assert result2 == "456"
|
|
393
|
+
mock_logger.error.assert_not_called()
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
@patch("cledar.kafka.utils.messages.logger")
|
|
397
|
+
def test_extract_id_from_value_json_decode_error_logging(
|
|
398
|
+
mock_logger: MagicMock,
|
|
399
|
+
) -> None:
|
|
400
|
+
"""Test that JSON decode errors are properly logged."""
|
|
401
|
+
invalid_json = '{"id": "123", "name": "test"' # Missing closing brace
|
|
402
|
+
|
|
403
|
+
result = extract_id_from_value(invalid_json)
|
|
404
|
+
|
|
405
|
+
assert result == "<unknown_id>"
|
|
406
|
+
mock_logger.error.assert_called_once()
|
|
407
|
+
error_call = mock_logger.error.call_args
|
|
408
|
+
assert "Decoding for id failed" in error_call[0][0]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from confluent_kafka import KafkaError, Message
|
|
2
|
+
|
|
3
|
+
from ..logger import logger
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def delivery_callback(error: KafkaError, msg: Message) -> None:
|
|
7
|
+
try:
|
|
8
|
+
if msg is None:
|
|
9
|
+
logger.warning("Callback received a None message.")
|
|
10
|
+
else:
|
|
11
|
+
topic = msg.topic()
|
|
12
|
+
except Exception as exc:
|
|
13
|
+
logger.warning(f"Failed to extract topic from message: {exc}")
|
|
14
|
+
topic = None
|
|
15
|
+
|
|
16
|
+
if error:
|
|
17
|
+
logger.error("Message failed delivery.", extra={"error": error, "topic": topic})
|
|
18
|
+
else:
|
|
19
|
+
logger.debug("Message delivered.", extra={"topic": topic})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from ..logger import logger
|
|
4
|
+
|
|
5
|
+
_UNKNOWN_ID_PLACEHOLDER = "<unknown_id>"
|
|
6
|
+
_ID_FIELD_KEY = "id"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def extract_id_from_value(value: str | None) -> str:
|
|
10
|
+
msg_id: str = _UNKNOWN_ID_PLACEHOLDER
|
|
11
|
+
if value is None:
|
|
12
|
+
return msg_id
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
parsed_value = json.loads(value).get(_ID_FIELD_KEY, _UNKNOWN_ID_PLACEHOLDER)
|
|
16
|
+
except (json.JSONDecodeError, TypeError, AttributeError) as e:
|
|
17
|
+
logger.error(f"Decoding for id failed. {e}")
|
|
18
|
+
return _UNKNOWN_ID_PLACEHOLDER
|
|
19
|
+
|
|
20
|
+
msg_id = (
|
|
21
|
+
str(parsed_value)
|
|
22
|
+
if parsed_value != _UNKNOWN_ID_PLACEHOLDER
|
|
23
|
+
else _UNKNOWN_ID_PLACEHOLDER
|
|
24
|
+
)
|
|
25
|
+
return msg_id
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
consumer_not_connected_msg = "KafkaConsumer is not connected. Call connect first."
|