|
30 | 30 |
|
31 | 31 | from cts import conf |
32 | 32 | from cts import app, db |
| 33 | +import cts.messaging |
33 | 34 | from cts.messaging import _retry_with_backoff, _kafka_send_msg, _umb_send_msg, publish |
34 | 35 | from cts.models import Compose, User, Tag |
35 | 36 | from utils import ModelsBaseTest |
@@ -95,6 +96,14 @@ class TestKafkaSendMessageWhenComposeIsCreated(ModelsBaseTest): |
95 | 96 |
|
96 | 97 | disable_event_handlers = False |
97 | 98 |
|
| 99 | + def setUp(self): |
| 100 | + super(TestKafkaSendMessageWhenComposeIsCreated, self).setUp() |
| 101 | + cts.messaging._kafka_producer = None |
| 102 | + |
| 103 | + def tearDown(self): |
| 104 | + super(TestKafkaSendMessageWhenComposeIsCreated, self).tearDown() |
| 105 | + cts.messaging._kafka_producer = None |
| 106 | + |
98 | 107 | def setup_composes(self): |
99 | 108 | User.create_user(username="odcs") |
100 | 109 | db.session.commit() |
@@ -130,28 +139,45 @@ def test_send_message(self, KafkaProducer): |
130 | 139 | }, |
131 | 140 | ) |
132 | 141 |
|
133 | | - # Verify flush and close were called |
134 | | - mock_producer.flush.assert_called_once() |
135 | | - mock_producer.close.assert_called_once() |
| 142 | + # Producer should not be closed on success (long-lived) |
| 143 | + mock_producer.close.assert_not_called() |
136 | 144 |
|
137 | 145 | @patch.object(conf, "messaging_broker_urls", new=["localhost:9092"]) |
138 | 146 | @patch.object(conf, "messaging_kafka_username", new="test_user") |
139 | 147 | @patch.object(conf, "messaging_kafka_password", new="test_password") |
140 | 148 | @patch("kafka.KafkaProducer") |
141 | 149 | def test_kafka_producer_closed_on_exception(self, KafkaProducer): |
142 | | - """Test that Kafka producer is closed even when send() fails""" |
| 150 | + """Test that producer is closed and recreated on failure""" |
143 | 151 | mock_producer = KafkaProducer.return_value |
144 | 152 | mock_producer.send.side_effect = Exception("Send failed") |
145 | 153 |
|
146 | 154 | msgs = [{"event": "test", "data": "test_data"}] |
147 | 155 |
|
148 | | - with patch("time.sleep"): # Mock sleep for retry backoff |
| 156 | + with patch("time.sleep"): |
149 | 157 | with self.assertRaises(Exception): |
150 | 158 | _kafka_send_msg(msgs) |
151 | 159 |
|
152 | | - # Producer close should be called on every retry attempt (finally block) |
| 160 | + # Producer should be closed on each failed attempt and recreated |
153 | 161 | # Default is 3 retries + 1 initial = 4 attempts |
154 | 162 | self.assertEqual(mock_producer.close.call_count, 4) |
| 163 | + # After all retries exhausted, producer should be reset to None |
| 164 | + self.assertIsNone(cts.messaging._kafka_producer) |
| 165 | + |
| 166 | + @patch.object(conf, "messaging_broker_urls", new=["localhost:9092"]) |
| 167 | + @patch.object(conf, "messaging_kafka_username", new="test_user") |
| 168 | + @patch.object(conf, "messaging_kafka_password", new="test_password") |
| 169 | + @patch("kafka.KafkaProducer") |
| 170 | + def test_kafka_producer_reused_across_calls(self, KafkaProducer): |
| 171 | + """Test that producer is created once and reused""" |
| 172 | + mock_producer = KafkaProducer.return_value |
| 173 | + |
| 174 | + _kafka_send_msg([{"event": "compose-created", "compose": {}}]) |
| 175 | + _kafka_send_msg([{"event": "compose-tagged", "compose": {}}]) |
| 176 | + |
| 177 | + # Producer should only be created once |
| 178 | + KafkaProducer.assert_called_once() |
| 179 | + # But send should be called twice |
| 180 | + self.assertEqual(mock_producer.send.call_count, 2) |
155 | 181 |
|
156 | 182 |
|
157 | 183 | @patch("cts.messaging.publish") |
@@ -510,25 +536,32 @@ def producer_side_effect(*args, **kwargs): |
510 | 536 | class TestKafkaRetries(unittest.TestCase): |
511 | 537 | """Test Kafka-specific retry behavior""" |
512 | 538 |
|
| 539 | + def setUp(self): |
| 540 | + cts.messaging._kafka_producer = None |
| 541 | + |
| 542 | + def tearDown(self): |
| 543 | + cts.messaging._kafka_producer = None |
| 544 | + |
513 | 545 | @patch.object(conf, "messaging_broker_urls", new=["localhost:9092"]) |
514 | 546 | @patch.object(conf, "messaging_kafka_username", new="test_user") |
515 | 547 | @patch.object(conf, "messaging_kafka_password", new="test_password") |
516 | 548 | @patch.object(conf, "messaging_topic_prefix", new="cts.") |
517 | 549 | def test_kafka_send_msg_retries_on_transient_failure(self): |
518 | 550 | """Test that _kafka_send_msg retries on transient failures""" |
519 | | - # Simulate transient failure then success |
520 | | - attempt_count = [0] |
| 551 | + # Simulate send failure on first call, then success |
| 552 | + send_count = [0] |
521 | 553 | mock_producer = Mock() |
522 | 554 |
|
523 | | - def producer_side_effect(*args, **kwargs): |
524 | | - attempt_count[0] += 1 |
525 | | - if attempt_count[0] == 1: |
| 555 | + def send_side_effect(*args, **kwargs): |
| 556 | + send_count[0] += 1 |
| 557 | + if send_count[0] == 1: |
526 | 558 | raise ConnectionError("Transient network error") |
527 | | - return mock_producer |
528 | 559 |
|
529 | | - with patch("time.sleep"): # Mock sleep to speed up test |
530 | | - with patch("kafka.KafkaProducer", side_effect=producer_side_effect): |
531 | | - # Should succeed on second attempt |
| 560 | + mock_producer.send.side_effect = send_side_effect |
| 561 | + |
| 562 | + with patch("time.sleep"): |
| 563 | + with patch("kafka.KafkaProducer", return_value=mock_producer): |
532 | 564 | _kafka_send_msg([{"event": "test", "data": "test"}]) |
533 | 565 |
|
534 | | - self.assertEqual(attempt_count[0], 2) |
| 566 | + # First send fails, producer is closed and reset, second send succeeds |
| 567 | + self.assertEqual(send_count[0], 2) |
0 commit comments