cledar-sdk 2.0.2__py3-none-any.whl → 2.1.0__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.
Files changed (85) hide show
  1. cledar/__init__.py +1 -0
  2. cledar/kafka/README.md +239 -0
  3. cledar/kafka/__init__.py +42 -0
  4. cledar/kafka/clients/base.py +117 -0
  5. cledar/kafka/clients/consumer.py +138 -0
  6. cledar/kafka/clients/producer.py +97 -0
  7. cledar/kafka/config/schemas.py +262 -0
  8. cledar/kafka/exceptions.py +17 -0
  9. cledar/kafka/handlers/dead_letter.py +88 -0
  10. cledar/kafka/handlers/parser.py +83 -0
  11. cledar/kafka/logger.py +5 -0
  12. cledar/kafka/models/input.py +17 -0
  13. cledar/kafka/models/message.py +14 -0
  14. cledar/kafka/models/output.py +12 -0
  15. cledar/kafka/tests/.env.test.kafka +3 -0
  16. cledar/kafka/tests/README.md +216 -0
  17. cledar/kafka/tests/conftest.py +104 -0
  18. cledar/kafka/tests/integration/__init__.py +1 -0
  19. cledar/kafka/tests/integration/conftest.py +78 -0
  20. cledar/kafka/tests/integration/helpers.py +47 -0
  21. cledar/kafka/tests/integration/test_consumer_integration.py +375 -0
  22. cledar/kafka/tests/integration/test_integration.py +394 -0
  23. cledar/kafka/tests/integration/test_producer_consumer_interaction.py +388 -0
  24. cledar/kafka/tests/integration/test_producer_integration.py +217 -0
  25. cledar/kafka/tests/unit/__init__.py +1 -0
  26. cledar/kafka/tests/unit/test_base_kafka_client.py +391 -0
  27. cledar/kafka/tests/unit/test_config_validation.py +609 -0
  28. cledar/kafka/tests/unit/test_dead_letter_handler.py +443 -0
  29. cledar/kafka/tests/unit/test_error_handling.py +674 -0
  30. cledar/kafka/tests/unit/test_input_parser.py +310 -0
  31. cledar/kafka/tests/unit/test_input_parser_comprehensive.py +489 -0
  32. cledar/kafka/tests/unit/test_utils.py +25 -0
  33. cledar/kafka/tests/unit/test_utils_comprehensive.py +408 -0
  34. cledar/kafka/utils/callbacks.py +28 -0
  35. cledar/kafka/utils/messages.py +39 -0
  36. cledar/kafka/utils/topics.py +15 -0
  37. cledar/kserve/README.md +352 -0
  38. cledar/kserve/__init__.py +5 -0
  39. cledar/kserve/tests/__init__.py +0 -0
  40. cledar/kserve/tests/test_utils.py +64 -0
  41. cledar/kserve/utils.py +30 -0
  42. cledar/logging/README.md +53 -0
  43. cledar/logging/__init__.py +5 -0
  44. cledar/logging/tests/test_universal_plaintext_formatter.py +249 -0
  45. cledar/logging/universal_plaintext_formatter.py +99 -0
  46. cledar/monitoring/README.md +71 -0
  47. cledar/monitoring/__init__.py +5 -0
  48. cledar/monitoring/monitoring_server.py +156 -0
  49. cledar/monitoring/tests/integration/test_monitoring_server_int.py +162 -0
  50. cledar/monitoring/tests/test_monitoring_server.py +59 -0
  51. cledar/nonce/README.md +99 -0
  52. cledar/nonce/__init__.py +5 -0
  53. cledar/nonce/nonce_service.py +62 -0
  54. cledar/nonce/tests/__init__.py +0 -0
  55. cledar/nonce/tests/test_nonce_service.py +136 -0
  56. cledar/redis/README.md +536 -0
  57. cledar/redis/__init__.py +17 -0
  58. cledar/redis/async_example.py +112 -0
  59. cledar/redis/example.py +67 -0
  60. cledar/redis/exceptions.py +25 -0
  61. cledar/redis/logger.py +5 -0
  62. cledar/redis/model.py +14 -0
  63. cledar/redis/redis.py +764 -0
  64. cledar/redis/redis_config_store.py +333 -0
  65. cledar/redis/tests/test_async_integration_redis.py +158 -0
  66. cledar/redis/tests/test_async_redis_service.py +380 -0
  67. cledar/redis/tests/test_integration_redis.py +119 -0
  68. cledar/redis/tests/test_redis_service.py +319 -0
  69. cledar/storage/README.md +529 -0
  70. cledar/storage/__init__.py +6 -0
  71. cledar/storage/constants.py +5 -0
  72. cledar/storage/exceptions.py +79 -0
  73. cledar/storage/models.py +41 -0
  74. cledar/storage/object_storage.py +1274 -0
  75. cledar/storage/tests/conftest.py +18 -0
  76. cledar/storage/tests/test_abfs.py +164 -0
  77. cledar/storage/tests/test_integration_filesystem.py +359 -0
  78. cledar/storage/tests/test_integration_s3.py +453 -0
  79. cledar/storage/tests/test_local.py +384 -0
  80. cledar/storage/tests/test_s3.py +521 -0
  81. {cledar_sdk-2.0.2.dist-info → cledar_sdk-2.1.0.dist-info}/METADATA +1 -1
  82. cledar_sdk-2.1.0.dist-info/RECORD +84 -0
  83. cledar_sdk-2.0.2.dist-info/RECORD +0 -4
  84. {cledar_sdk-2.0.2.dist-info → cledar_sdk-2.1.0.dist-info}/WHEEL +0 -0
  85. {cledar_sdk-2.0.2.dist-info → cledar_sdk-2.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,674 @@
1
+ """
2
+ Comprehensive tests for error handling, retry mechanisms, and edge cases
3
+ in the Kafka service module.
4
+ """
5
+
6
+ import time
7
+ from unittest.mock import MagicMock, patch
8
+
9
+ import pytest
10
+ from confluent_kafka import KafkaError, KafkaException
11
+
12
+ from cledar.kafka.clients.consumer import KafkaConsumer
13
+ from cledar.kafka.clients.producer import KafkaProducer
14
+ from cledar.kafka.config.schemas import KafkaConsumerConfig, KafkaProducerConfig
15
+ from cledar.kafka.exceptions import (
16
+ KafkaConnectionError,
17
+ KafkaConsumerError,
18
+ KafkaConsumerNotConnectedError,
19
+ KafkaProducerNotConnectedError,
20
+ )
21
+ from cledar.kafka.handlers.dead_letter import DeadLetterHandler
22
+ from cledar.kafka.handlers.parser import IncorrectMessageValueError, InputParser
23
+ from cledar.kafka.models.message import KafkaMessage
24
+
25
+
26
+ @pytest.fixture
27
+ def producer_config() -> KafkaProducerConfig:
28
+ """Create producer configuration for error tests."""
29
+ return KafkaProducerConfig(
30
+ kafka_servers="localhost:9092",
31
+ kafka_group_id="error-test-producer",
32
+ kafka_topic_prefix="error-test.",
33
+ kafka_block_buffer_time_sec=5,
34
+ kafka_connection_check_timeout_sec=1,
35
+ kafka_connection_check_interval_sec=30,
36
+ )
37
+
38
+
39
+ @pytest.fixture
40
+ def consumer_config() -> KafkaConsumerConfig:
41
+ """Create consumer configuration for error tests."""
42
+ return KafkaConsumerConfig(
43
+ kafka_servers="localhost:9092",
44
+ kafka_group_id="error-test-consumer",
45
+ kafka_offset="earliest",
46
+ kafka_topic_prefix="error-test.",
47
+ kafka_block_consumer_time_sec=2,
48
+ kafka_connection_check_timeout_sec=1,
49
+ kafka_auto_commit_interval_ms=1000,
50
+ kafka_connection_check_interval_sec=30,
51
+ )
52
+
53
+
54
+ def test_producer_connection_error(producer_config: KafkaProducerConfig) -> None:
55
+ """Test producer connection error handling."""
56
+ with patch("cledar.kafka.clients.producer.Producer") as mock_prod_class:
57
+ mock_prod_instance = MagicMock()
58
+ mock_prod_class.return_value = mock_prod_instance
59
+
60
+ # Mock connection check to fail
61
+ mock_prod_instance.list_topics.side_effect = KafkaException("Connection failed")
62
+
63
+ producer = KafkaProducer(producer_config)
64
+
65
+ with pytest.raises(KafkaConnectionError) as exc_info:
66
+ producer.connect()
67
+
68
+ assert isinstance(exc_info.value.__cause__, KafkaException)
69
+
70
+
71
+ def test_consumer_connection_error(consumer_config: KafkaConsumerConfig) -> None:
72
+ """Test consumer connection error handling."""
73
+ with patch("cledar.kafka.clients.consumer.Consumer") as mock_cons_class:
74
+ mock_cons_instance = MagicMock()
75
+ mock_cons_class.return_value = mock_cons_instance
76
+
77
+ # Mock connection check to fail
78
+ mock_cons_instance.list_topics.side_effect = KafkaException("Connection failed")
79
+
80
+ consumer = KafkaConsumer(consumer_config)
81
+
82
+ with pytest.raises(KafkaConnectionError) as exc_info:
83
+ consumer.connect()
84
+
85
+ assert isinstance(exc_info.value.__cause__, KafkaException)
86
+
87
+
88
+ def test_producer_send_not_connected_error(
89
+ producer_config: KafkaProducerConfig,
90
+ ) -> None:
91
+ """Test producer send when not connected."""
92
+ producer = KafkaProducer(producer_config)
93
+
94
+ with pytest.raises(KafkaProducerNotConnectedError):
95
+ producer.send(topic="test", value="test", key="key")
96
+
97
+
98
+ def test_consumer_consume_not_connected_error(
99
+ consumer_config: KafkaConsumerConfig,
100
+ ) -> None:
101
+ """Test consumer consume when not connected."""
102
+ consumer = KafkaConsumer(consumer_config)
103
+
104
+ with pytest.raises(KafkaConsumerNotConnectedError):
105
+ consumer.consume_next()
106
+
107
+
108
+ def test_consumer_subscribe_not_connected_error(
109
+ consumer_config: KafkaConsumerConfig,
110
+ ) -> None:
111
+ """Test consumer subscribe when not connected."""
112
+ consumer = KafkaConsumer(consumer_config)
113
+
114
+ with pytest.raises(KafkaConsumerNotConnectedError):
115
+ consumer.subscribe(["test-topic"])
116
+
117
+
118
+ def test_consumer_commit_not_connected_error(
119
+ consumer_config: KafkaConsumerConfig,
120
+ ) -> None:
121
+ """Test consumer commit when not connected."""
122
+ consumer = KafkaConsumer(consumer_config)
123
+
124
+ message = KafkaMessage(
125
+ topic="test",
126
+ value="test",
127
+ key="key",
128
+ offset=100,
129
+ partition=0,
130
+ )
131
+
132
+ with pytest.raises(KafkaConsumerNotConnectedError):
133
+ consumer.commit(message)
134
+
135
+
136
+ def test_producer_send_kafka_exception(producer_config: KafkaProducerConfig) -> None:
137
+ """Test producer send with KafkaException."""
138
+ with patch("cledar.kafka.clients.producer.Producer") as mock_prod_class:
139
+ mock_prod_instance = MagicMock()
140
+ mock_prod_class.return_value = mock_prod_instance
141
+
142
+ # Mock connection check to succeed
143
+ mock_prod_instance.list_topics.return_value = None
144
+
145
+ producer = KafkaProducer(producer_config)
146
+ producer.connect()
147
+
148
+ # Mock produce to raise KafkaException
149
+ mock_prod_instance.produce.side_effect = KafkaException("Produce failed")
150
+
151
+ with pytest.raises(KafkaException):
152
+ producer.send(topic="test", value="test", key="key")
153
+
154
+
155
+ def test_consumer_consume_kafka_exception(consumer_config: KafkaConsumerConfig) -> None:
156
+ """Test consumer consume with KafkaException."""
157
+ with patch("cledar.kafka.clients.consumer.Consumer") as mock_cons_class:
158
+ mock_cons_instance = MagicMock()
159
+ mock_cons_class.return_value = mock_cons_instance
160
+
161
+ # Mock connection check to succeed
162
+ mock_cons_instance.list_topics.return_value = None
163
+
164
+ consumer = KafkaConsumer(consumer_config)
165
+ consumer.connect()
166
+
167
+ # Mock poll to raise KafkaException
168
+ mock_cons_instance.poll.side_effect = KafkaException("Poll failed")
169
+
170
+ with pytest.raises(KafkaException):
171
+ consumer.consume_next()
172
+
173
+
174
+ def test_consumer_subscribe_kafka_exception(
175
+ consumer_config: KafkaConsumerConfig,
176
+ ) -> None:
177
+ """Test consumer subscribe with KafkaException."""
178
+ with patch("cledar.kafka.clients.consumer.Consumer") as mock_cons_class:
179
+ mock_cons_instance = MagicMock()
180
+ mock_cons_class.return_value = mock_cons_instance
181
+
182
+ # Mock connection check to succeed
183
+ mock_cons_instance.list_topics.return_value = None
184
+
185
+ consumer = KafkaConsumer(consumer_config)
186
+ consumer.connect()
187
+
188
+ # Mock subscribe to raise KafkaException
189
+ mock_cons_instance.subscribe.side_effect = KafkaException("Subscribe failed")
190
+
191
+ with pytest.raises(KafkaException):
192
+ consumer.subscribe(["test-topic"])
193
+
194
+
195
+ def test_consumer_commit_kafka_exception(consumer_config: KafkaConsumerConfig) -> None:
196
+ """Test consumer commit with KafkaException."""
197
+ with patch("cledar.kafka.clients.consumer.Consumer") as mock_cons_class:
198
+ mock_cons_instance = MagicMock()
199
+ mock_cons_class.return_value = mock_cons_instance
200
+
201
+ # Mock connection check to succeed
202
+ mock_cons_instance.list_topics.return_value = None
203
+
204
+ consumer = KafkaConsumer(consumer_config)
205
+ consumer.connect()
206
+
207
+ # Mock commit to raise KafkaException
208
+ mock_cons_instance.commit.side_effect = KafkaException("Commit failed")
209
+
210
+ message = KafkaMessage(
211
+ topic="test",
212
+ value="test",
213
+ key="key",
214
+ offset=100,
215
+ partition=0,
216
+ )
217
+
218
+ with pytest.raises(KafkaException):
219
+ consumer.commit(message)
220
+
221
+
222
+ def test_producer_buffer_error_retry(producer_config: KafkaProducerConfig) -> None:
223
+ """Test producer buffer error retry mechanism."""
224
+ with patch("cledar.kafka.clients.producer.Producer") as mock_prod_class:
225
+ mock_prod_instance = MagicMock()
226
+ mock_prod_class.return_value = mock_prod_instance
227
+
228
+ # Mock connection check to succeed
229
+ mock_prod_instance.list_topics.return_value = None
230
+
231
+ producer = KafkaProducer(producer_config)
232
+ producer.connect()
233
+
234
+ # Mock produce to raise BufferError first, then succeed
235
+ mock_prod_instance.produce.side_effect = [
236
+ BufferError("Buffer full"),
237
+ None, # Success on retry
238
+ ]
239
+
240
+ # Should not raise exception due to retry mechanism
241
+ producer.send(topic="test", value="test", key="key")
242
+
243
+ # Verify produce was called twice (retry)
244
+ assert mock_prod_instance.produce.call_count == 2
245
+ assert (
246
+ mock_prod_instance.poll.call_count == 1
247
+ ) # Only called once in retry block
248
+
249
+
250
+ def test_producer_buffer_error_retry_failure(
251
+ producer_config: KafkaProducerConfig,
252
+ ) -> None:
253
+ """Test producer buffer error retry mechanism with persistent failure."""
254
+ with patch("cledar.kafka.clients.producer.Producer") as mock_prod_class:
255
+ mock_prod_instance = MagicMock()
256
+ mock_prod_class.return_value = mock_prod_instance
257
+
258
+ # Mock connection check to succeed
259
+ mock_prod_instance.list_topics.return_value = None
260
+
261
+ producer = KafkaProducer(producer_config)
262
+ producer.connect()
263
+
264
+ # Mock produce to always raise BufferError
265
+ mock_prod_instance.produce.side_effect = BufferError("Buffer full")
266
+
267
+ # Should raise BufferError after retry
268
+ with pytest.raises(BufferError):
269
+ producer.send(topic="test", value="test", key="key")
270
+
271
+ # Verify produce was called twice (retry)
272
+ assert mock_prod_instance.produce.call_count == 2
273
+
274
+
275
+ def test_consumer_message_error(consumer_config: KafkaConsumerConfig) -> None:
276
+ """Test consumer message error handling."""
277
+ with patch("cledar.kafka.clients.consumer.Consumer") as mock_cons_class:
278
+ mock_cons_instance = MagicMock()
279
+ mock_cons_class.return_value = mock_cons_instance
280
+
281
+ # Mock connection check to succeed
282
+ mock_cons_instance.list_topics.return_value = None
283
+
284
+ consumer = KafkaConsumer(consumer_config)
285
+ consumer.connect()
286
+
287
+ # Mock poll to return message with error
288
+ mock_message = MagicMock()
289
+ mock_message.error.return_value = KafkaError(1, "Message error")
290
+ mock_cons_instance.poll.return_value = mock_message
291
+
292
+ with pytest.raises(KafkaConsumerError) as exc_info:
293
+ consumer.consume_next()
294
+
295
+ assert isinstance(exc_info.value.args[0], KafkaError)
296
+
297
+
298
+ def test_dead_letter_handler_producer_error(
299
+ producer_config: KafkaProducerConfig,
300
+ ) -> None:
301
+ """Test DeadLetterHandler with producer error."""
302
+ with patch("cledar.kafka.clients.producer.Producer") as mock_prod_class:
303
+ mock_prod_instance = MagicMock()
304
+ mock_prod_class.return_value = mock_prod_instance
305
+
306
+ # Mock connection check to succeed
307
+ mock_prod_instance.list_topics.return_value = None
308
+
309
+ producer = KafkaProducer(producer_config)
310
+ producer.connect()
311
+
312
+ # Mock send to raise KafkaException
313
+ mock_prod_instance.produce.side_effect = KafkaException("DLQ send failed")
314
+
315
+ dlq_handler = DeadLetterHandler(producer=producer, dlq_topic="dlq-topic")
316
+
317
+ message = KafkaMessage(
318
+ topic="test",
319
+ value="test",
320
+ key="key",
321
+ offset=100,
322
+ partition=0,
323
+ )
324
+
325
+ with pytest.raises(KafkaException):
326
+ dlq_handler.handle(message, None)
327
+
328
+
329
+ def test_input_parser_incorrect_message_value_error() -> None:
330
+ """Test InputParser with incorrect message value."""
331
+ from pydantic import BaseModel
332
+
333
+ class SimpleModel(BaseModel):
334
+ id: str
335
+
336
+ parser = InputParser(SimpleModel)
337
+
338
+ message = KafkaMessage(
339
+ topic="test",
340
+ value=None, # None value should cause error
341
+ key="key",
342
+ offset=100,
343
+ partition=0,
344
+ )
345
+
346
+ with pytest.raises(IncorrectMessageValueError):
347
+ parser.parse_message(message)
348
+
349
+
350
+ def test_input_parser_json_validation_error() -> None:
351
+ """Test InputParser with JSON validation error."""
352
+ from pydantic import BaseModel
353
+
354
+ class TestModel(BaseModel):
355
+ id: str
356
+ value: int
357
+
358
+ parser = InputParser(TestModel)
359
+
360
+ message = KafkaMessage(
361
+ topic="test",
362
+ value='{"id": "123", "value": "not-a-number"}', # Invalid type
363
+ key="key",
364
+ offset=100,
365
+ partition=0,
366
+ )
367
+
368
+ from pydantic import ValidationError
369
+
370
+ with pytest.raises(ValidationError): # Pydantic validation error
371
+ parser.parse_message(message)
372
+
373
+
374
+ def test_input_parser_invalid_json_error() -> None:
375
+ """Test InputParser with invalid JSON."""
376
+ from pydantic import BaseModel
377
+
378
+ class TestModel(BaseModel):
379
+ id: str
380
+ value: int
381
+
382
+ parser = InputParser(TestModel)
383
+
384
+ message = KafkaMessage(
385
+ topic="test",
386
+ value='{"id": "123", "value": 42', # Invalid JSON
387
+ key="key",
388
+ offset=100,
389
+ partition=0,
390
+ )
391
+
392
+ with pytest.raises(IncorrectMessageValueError): # JSON parsing error
393
+ parser.parse_message(message)
394
+
395
+
396
+ def test_connection_monitoring_error_handling(
397
+ producer_config: KafkaProducerConfig,
398
+ ) -> None:
399
+ """Test connection monitoring error handling."""
400
+ with patch("cledar.kafka.clients.producer.Producer") as mock_prod_class:
401
+ mock_prod_instance = MagicMock()
402
+ mock_prod_class.return_value = mock_prod_instance
403
+
404
+ # Mock connection check to succeed initially
405
+ mock_prod_instance.list_topics.return_value = None
406
+
407
+ producer = KafkaProducer(producer_config)
408
+ producer.connect()
409
+
410
+ # Mock connection check to fail during monitoring
411
+ mock_prod_instance.list_topics.side_effect = KafkaException("Connection lost")
412
+
413
+ # Start monitoring thread
414
+ producer.start_connection_check_thread()
415
+
416
+ # Let it run for a short time
417
+ time.sleep(0.1)
418
+
419
+ # Stop monitoring
420
+ producer.shutdown()
421
+
422
+ # Should not raise exception, should handle gracefully
423
+
424
+
425
+ def test_connection_monitoring_success_recovery(
426
+ producer_config: KafkaProducerConfig,
427
+ ) -> None:
428
+ """Test connection monitoring success recovery."""
429
+ with patch("cledar.kafka.clients.producer.Producer") as mock_prod_class:
430
+ mock_prod_instance = MagicMock()
431
+ mock_prod_class.return_value = mock_prod_instance
432
+
433
+ # Mock connection check to succeed
434
+ mock_prod_instance.list_topics.return_value = None
435
+
436
+ producer = KafkaProducer(producer_config)
437
+ producer.connect()
438
+
439
+ # Start monitoring thread
440
+ producer.start_connection_check_thread()
441
+
442
+ # Let it run for a short time
443
+ time.sleep(0.1)
444
+
445
+ # Stop monitoring
446
+ producer.shutdown()
447
+
448
+ # Should not raise exception, should handle gracefully
449
+
450
+
451
+ def test_producer_send_with_headers_error(producer_config: KafkaProducerConfig) -> None:
452
+ """Test producer send with headers error handling."""
453
+ with patch("cledar.kafka.clients.producer.Producer") as mock_prod_class:
454
+ mock_prod_instance = MagicMock()
455
+ mock_prod_class.return_value = mock_prod_instance
456
+
457
+ # Mock connection check to succeed
458
+ mock_prod_instance.list_topics.return_value = None
459
+
460
+ producer = KafkaProducer(producer_config)
461
+ producer.connect()
462
+
463
+ # Mock produce to raise KafkaException
464
+ mock_prod_instance.produce.side_effect = KafkaException("Headers error")
465
+
466
+ headers = [("header1", b"value1"), ("header2", b"value2")]
467
+
468
+ with pytest.raises(KafkaException):
469
+ producer.send(topic="test", value="test", key="key", headers=headers)
470
+
471
+
472
+ def test_consumer_consume_with_timeout(consumer_config: KafkaConsumerConfig) -> None:
473
+ """Test consumer consume with timeout."""
474
+ with patch("cledar.kafka.clients.consumer.Consumer") as mock_cons_class:
475
+ mock_cons_instance = MagicMock()
476
+ mock_cons_class.return_value = mock_cons_instance
477
+
478
+ # Mock connection check to succeed
479
+ mock_cons_instance.list_topics.return_value = None
480
+
481
+ consumer = KafkaConsumer(consumer_config)
482
+ consumer.connect()
483
+
484
+ # Mock poll to return None (timeout)
485
+ mock_cons_instance.poll.return_value = None
486
+
487
+ message = consumer.consume_next()
488
+
489
+ assert message is None
490
+
491
+
492
+ def test_consumer_consume_with_partial_message(
493
+ consumer_config: KafkaConsumerConfig,
494
+ ) -> None:
495
+ """Test consumer consume with partial message data."""
496
+ with patch("cledar.kafka.clients.consumer.Consumer") as mock_cons_class:
497
+ mock_cons_instance = MagicMock()
498
+ mock_cons_class.return_value = mock_cons_instance
499
+
500
+ # Mock connection check to succeed
501
+ mock_cons_instance.list_topics.return_value = None
502
+
503
+ consumer = KafkaConsumer(consumer_config)
504
+ consumer.connect()
505
+
506
+ # Mock poll to return message with partial data
507
+ mock_message = MagicMock()
508
+ mock_message.error.return_value = None
509
+ mock_message.topic.return_value = "test-topic"
510
+ mock_message.value.return_value = b"test-value"
511
+ mock_message.key.return_value = None # None key
512
+ mock_message.offset.return_value = 100
513
+ mock_message.partition.return_value = 0
514
+
515
+ mock_cons_instance.poll.return_value = mock_message
516
+
517
+ message = consumer.consume_next()
518
+
519
+ assert message is not None
520
+ assert message.value == "test-value"
521
+ assert message.key is None
522
+
523
+
524
+ def test_producer_send_with_none_values(producer_config: KafkaProducerConfig) -> None:
525
+ """Test producer send with None values."""
526
+ with patch("cledar.kafka.clients.producer.Producer") as mock_prod_class:
527
+ mock_prod_instance = MagicMock()
528
+ mock_prod_class.return_value = mock_prod_instance
529
+
530
+ # Mock connection check to succeed
531
+ mock_prod_instance.list_topics.return_value = None
532
+
533
+ producer = KafkaProducer(producer_config)
534
+ producer.connect()
535
+
536
+ # Should not raise exception with None values
537
+ producer.send(topic="test", value=None, key=None)
538
+
539
+ # Verify produce was called with None values
540
+ mock_prod_instance.produce.assert_called_once()
541
+ call_args = mock_prod_instance.produce.call_args
542
+ assert call_args[1]["value"] is None
543
+ assert call_args[1]["key"] is None
544
+
545
+
546
+ def test_consumer_commit_with_none_message(
547
+ consumer_config: KafkaConsumerConfig,
548
+ ) -> None:
549
+ """Test consumer commit with None message."""
550
+ with patch("cledar.kafka.clients.consumer.Consumer") as mock_cons_class:
551
+ mock_cons_instance = MagicMock()
552
+ mock_cons_class.return_value = mock_cons_instance
553
+
554
+ # Mock connection check to succeed
555
+ mock_cons_instance.list_topics.return_value = None
556
+
557
+ consumer = KafkaConsumer(consumer_config)
558
+ consumer.connect()
559
+
560
+ # Mock poll to return None
561
+ mock_cons_instance.poll.return_value = None
562
+
563
+ message = consumer.consume_next()
564
+
565
+ assert message is None
566
+
567
+ # Should not be able to commit None message
568
+ # (This would be caught by type checking in real usage)
569
+
570
+
571
+ def test_producer_send_with_empty_strings(producer_config: KafkaProducerConfig) -> None:
572
+ """Test producer send with empty strings."""
573
+ with patch("cledar.kafka.clients.producer.Producer") as mock_prod_class:
574
+ mock_prod_instance = MagicMock()
575
+ mock_prod_class.return_value = mock_prod_instance
576
+
577
+ # Mock connection check to succeed
578
+ mock_prod_instance.list_topics.return_value = None
579
+
580
+ producer = KafkaProducer(producer_config)
581
+ producer.connect()
582
+
583
+ # Should not raise exception with empty strings
584
+ producer.send(topic="", value="", key="")
585
+
586
+ # Verify produce was called with empty strings
587
+ mock_prod_instance.produce.assert_called_once()
588
+ call_args = mock_prod_instance.produce.call_args
589
+ assert call_args[1]["value"] == ""
590
+ assert call_args[1]["key"] == ""
591
+
592
+
593
+ def test_consumer_subscribe_with_empty_topic_list(
594
+ consumer_config: KafkaConsumerConfig,
595
+ ) -> None:
596
+ """Test consumer subscribe with empty topic list."""
597
+ with patch("cledar.kafka.clients.consumer.Consumer") as mock_cons_class:
598
+ mock_cons_instance = MagicMock()
599
+ mock_cons_class.return_value = mock_cons_instance
600
+
601
+ # Mock connection check to succeed
602
+ mock_cons_instance.list_topics.return_value = None
603
+
604
+ consumer = KafkaConsumer(consumer_config)
605
+ consumer.connect()
606
+
607
+ # Should not raise exception with empty topic list
608
+ consumer.subscribe([])
609
+
610
+ # Verify subscribe was called with empty list
611
+ mock_cons_instance.subscribe.assert_called_once_with([])
612
+
613
+
614
+ def test_producer_send_with_special_characters(
615
+ producer_config: KafkaProducerConfig,
616
+ ) -> None:
617
+ """Test producer send with special characters."""
618
+ with patch("cledar.kafka.clients.producer.Producer") as mock_prod_class:
619
+ mock_prod_instance = MagicMock()
620
+ mock_prod_class.return_value = mock_prod_instance
621
+
622
+ # Mock connection check to succeed
623
+ mock_prod_instance.list_topics.return_value = None
624
+
625
+ producer = KafkaProducer(producer_config)
626
+ producer.connect()
627
+
628
+ special_topic = "topic-with-special-chars: @#$%^&*()"
629
+ special_value = "value-with-special-chars: @#$%^&*()"
630
+ special_key = "key-with-special-chars: @#$%^&*()"
631
+
632
+ # Should not raise exception with special characters
633
+ producer.send(topic=special_topic, value=special_value, key=special_key)
634
+
635
+ # Verify produce was called with special characters
636
+ mock_prod_instance.produce.assert_called_once()
637
+ call_args = mock_prod_instance.produce.call_args
638
+ assert call_args[1]["value"] == special_value
639
+ assert call_args[1]["key"] == special_key
640
+
641
+
642
+ def test_consumer_consume_with_unicode_characters(
643
+ consumer_config: KafkaConsumerConfig,
644
+ ) -> None:
645
+ """Test consumer consume with unicode characters."""
646
+ with patch("cledar.kafka.clients.consumer.Consumer") as mock_cons_class:
647
+ mock_cons_instance = MagicMock()
648
+ mock_cons_class.return_value = mock_cons_instance
649
+
650
+ # Mock connection check to succeed
651
+ mock_cons_instance.list_topics.return_value = None
652
+
653
+ consumer = KafkaConsumer(consumer_config)
654
+ consumer.connect()
655
+
656
+ # Mock poll to return message with unicode
657
+ unicode_value = "测试消息"
658
+ unicode_key = "测试键"
659
+
660
+ mock_message = MagicMock()
661
+ mock_message.error.return_value = None
662
+ mock_message.topic.return_value = "test-topic"
663
+ mock_message.value.return_value = unicode_value.encode("utf-8")
664
+ mock_message.key.return_value = unicode_key.encode("utf-8")
665
+ mock_message.offset.return_value = 100
666
+ mock_message.partition.return_value = 0
667
+
668
+ mock_cons_instance.poll.return_value = mock_message
669
+
670
+ message = consumer.consume_next()
671
+
672
+ assert message is not None
673
+ assert message.value == unicode_value
674
+ assert message.key == unicode_key