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,489 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Comprehensive tests for InputParser covering JSON parsing, message validation,
|
|
3
|
+
error handling, and edge cases.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
from pydantic import BaseModel, ValidationError
|
|
11
|
+
|
|
12
|
+
from cledar.kafka.handlers.parser import IncorrectMessageValueError, InputParser
|
|
13
|
+
from cledar.kafka.models.message import KafkaMessage
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SimpleModel(BaseModel):
|
|
17
|
+
"""Simple test model."""
|
|
18
|
+
|
|
19
|
+
id: str
|
|
20
|
+
name: str
|
|
21
|
+
value: int
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class OptionalModel(BaseModel):
|
|
25
|
+
"""Model with optional fields."""
|
|
26
|
+
|
|
27
|
+
id: str
|
|
28
|
+
name: str | None = None
|
|
29
|
+
value: int | None = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class NestedModel(BaseModel):
|
|
33
|
+
"""Model with nested structure."""
|
|
34
|
+
|
|
35
|
+
id: str
|
|
36
|
+
data: dict[str, Any]
|
|
37
|
+
metadata: dict[str, str] | None = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ComplexModel(BaseModel):
|
|
41
|
+
"""Complex model with various field types."""
|
|
42
|
+
|
|
43
|
+
id: str
|
|
44
|
+
count: int
|
|
45
|
+
active: bool
|
|
46
|
+
tags: list[str]
|
|
47
|
+
config: dict[str, Any]
|
|
48
|
+
nested: NestedModel | None = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest.fixture
|
|
52
|
+
def simple_parser() -> InputParser[SimpleModel]:
|
|
53
|
+
"""Create a parser for SimpleModel."""
|
|
54
|
+
return InputParser(SimpleModel)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@pytest.fixture
|
|
58
|
+
def optional_parser() -> InputParser[OptionalModel]:
|
|
59
|
+
"""Create a parser for OptionalModel."""
|
|
60
|
+
return InputParser(OptionalModel)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@pytest.fixture
|
|
64
|
+
def nested_parser() -> InputParser[NestedModel]:
|
|
65
|
+
"""Create a parser for NestedModel."""
|
|
66
|
+
return InputParser(NestedModel)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@pytest.fixture
|
|
70
|
+
def complex_parser() -> InputParser[ComplexModel]:
|
|
71
|
+
"""Create a parser for ComplexModel."""
|
|
72
|
+
return InputParser(ComplexModel)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@pytest.fixture
|
|
76
|
+
def valid_simple_json() -> str:
|
|
77
|
+
"""Valid JSON for SimpleModel."""
|
|
78
|
+
return '{"id": "123", "name": "test", "value": 42}'
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@pytest.fixture
|
|
82
|
+
def valid_nested_json() -> str:
|
|
83
|
+
"""Valid JSON for NestedModel."""
|
|
84
|
+
return (
|
|
85
|
+
'{"id": "456", "data": {"key": "value", "number": 123}, '
|
|
86
|
+
'"metadata": {"type": "test"}}'
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@pytest.fixture
|
|
91
|
+
def valid_complex_json() -> str:
|
|
92
|
+
"""Valid JSON for ComplexModel."""
|
|
93
|
+
return json.dumps(
|
|
94
|
+
{
|
|
95
|
+
"id": "789",
|
|
96
|
+
"count": 10,
|
|
97
|
+
"active": True,
|
|
98
|
+
"tags": ["tag1", "tag2"],
|
|
99
|
+
"config": {"setting": "value", "enabled": True},
|
|
100
|
+
"nested": {
|
|
101
|
+
"id": "nested-123",
|
|
102
|
+
"data": {"inner": "data"},
|
|
103
|
+
"metadata": {"type": "nested"},
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@pytest.fixture
|
|
110
|
+
def sample_message() -> KafkaMessage:
|
|
111
|
+
"""Create a sample KafkaMessage."""
|
|
112
|
+
return KafkaMessage(
|
|
113
|
+
topic="test-topic",
|
|
114
|
+
value='{"id": "123", "name": "test", "value": 42}',
|
|
115
|
+
key="test-key",
|
|
116
|
+
offset=100,
|
|
117
|
+
partition=0,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_init() -> None:
|
|
122
|
+
"""Test InputParser initialization."""
|
|
123
|
+
parser = InputParser(SimpleModel)
|
|
124
|
+
assert parser.model == SimpleModel
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_parse_json_valid(
|
|
128
|
+
simple_parser: InputParser[SimpleModel], valid_simple_json: str
|
|
129
|
+
) -> None:
|
|
130
|
+
"""Test parsing valid JSON."""
|
|
131
|
+
result = simple_parser.parse_json(valid_simple_json)
|
|
132
|
+
|
|
133
|
+
assert isinstance(result, SimpleModel)
|
|
134
|
+
assert result.id == "123"
|
|
135
|
+
assert result.name == "test"
|
|
136
|
+
assert result.value == 42
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_parse_json_invalid_json(simple_parser: InputParser[SimpleModel]) -> None:
|
|
140
|
+
"""Test parsing invalid JSON."""
|
|
141
|
+
invalid_json = '{"id": "123", "name": "test", "value": 42' # Missing closing brace
|
|
142
|
+
|
|
143
|
+
with pytest.raises(IncorrectMessageValueError):
|
|
144
|
+
simple_parser.parse_json(invalid_json)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_parse_json_missing_required_field(
|
|
148
|
+
simple_parser: InputParser[SimpleModel],
|
|
149
|
+
) -> None:
|
|
150
|
+
"""Test parsing JSON with missing required field."""
|
|
151
|
+
incomplete_json = '{"id": "123", "name": "test"}' # Missing 'value' field
|
|
152
|
+
|
|
153
|
+
with pytest.raises(ValidationError):
|
|
154
|
+
simple_parser.parse_json(incomplete_json)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_parse_json_wrong_type(simple_parser: InputParser[SimpleModel]) -> None:
|
|
158
|
+
"""Test parsing JSON with wrong field type."""
|
|
159
|
+
wrong_type_json = '{"id": "123", "name": "test", "value": "not-a-number"}'
|
|
160
|
+
|
|
161
|
+
with pytest.raises(ValidationError):
|
|
162
|
+
simple_parser.parse_json(wrong_type_json)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def test_parse_json_extra_fields(simple_parser: InputParser[SimpleModel]) -> None:
|
|
166
|
+
"""Test parsing JSON with extra fields (should be ignored by default)."""
|
|
167
|
+
extra_fields_json = '{"id": "123", "name": "test", "value": 42, "extra": "ignored"}'
|
|
168
|
+
|
|
169
|
+
result = simple_parser.parse_json(extra_fields_json)
|
|
170
|
+
|
|
171
|
+
assert isinstance(result, SimpleModel)
|
|
172
|
+
assert result.id == "123"
|
|
173
|
+
assert result.name == "test"
|
|
174
|
+
assert result.value == 42
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_parse_json_empty_string(simple_parser: InputParser[SimpleModel]) -> None:
|
|
178
|
+
"""Test parsing empty string."""
|
|
179
|
+
with pytest.raises(IncorrectMessageValueError):
|
|
180
|
+
simple_parser.parse_json("")
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def test_parse_json_null_values(optional_parser: InputParser[OptionalModel]) -> None:
|
|
184
|
+
"""Test parsing JSON with null values for optional fields."""
|
|
185
|
+
null_json = '{"id": "123", "name": null, "value": null}'
|
|
186
|
+
|
|
187
|
+
result = optional_parser.parse_json(null_json)
|
|
188
|
+
|
|
189
|
+
assert isinstance(result, OptionalModel)
|
|
190
|
+
assert result.id == "123"
|
|
191
|
+
assert result.name is None
|
|
192
|
+
assert result.value is None
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def test_parse_json_nested_structure(
|
|
196
|
+
nested_parser: InputParser[NestedModel], valid_nested_json: str
|
|
197
|
+
) -> None:
|
|
198
|
+
"""Test parsing JSON with nested structure."""
|
|
199
|
+
result = nested_parser.parse_json(valid_nested_json)
|
|
200
|
+
|
|
201
|
+
assert isinstance(result, NestedModel)
|
|
202
|
+
assert result.id == "456"
|
|
203
|
+
assert result.data == {"key": "value", "number": 123}
|
|
204
|
+
assert result.metadata == {"type": "test"}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def test_parse_json_complex_structure(
|
|
208
|
+
complex_parser: InputParser[ComplexModel], valid_complex_json: str
|
|
209
|
+
) -> None:
|
|
210
|
+
"""Test parsing JSON with complex structure."""
|
|
211
|
+
result = complex_parser.parse_json(valid_complex_json)
|
|
212
|
+
|
|
213
|
+
assert isinstance(result, ComplexModel)
|
|
214
|
+
assert result.id == "789"
|
|
215
|
+
assert result.count == 10
|
|
216
|
+
assert result.active is True
|
|
217
|
+
assert result.tags == ["tag1", "tag2"]
|
|
218
|
+
assert result.config == {"setting": "value", "enabled": True}
|
|
219
|
+
assert result.nested is not None
|
|
220
|
+
assert result.nested.id == "nested-123"
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def test_parse_message_valid(
|
|
224
|
+
simple_parser: InputParser[SimpleModel], sample_message: KafkaMessage
|
|
225
|
+
) -> None:
|
|
226
|
+
"""Test parsing a valid KafkaMessage."""
|
|
227
|
+
result = simple_parser.parse_message(sample_message)
|
|
228
|
+
|
|
229
|
+
assert result.key == sample_message.key
|
|
230
|
+
assert result.value == sample_message.value
|
|
231
|
+
assert result.topic == sample_message.topic
|
|
232
|
+
assert result.offset == sample_message.offset
|
|
233
|
+
assert result.partition == sample_message.partition
|
|
234
|
+
|
|
235
|
+
assert isinstance(result.payload, SimpleModel)
|
|
236
|
+
assert result.payload.id == "123"
|
|
237
|
+
assert result.payload.name == "test"
|
|
238
|
+
assert result.payload.value == 42
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def test_parse_message_none_value(simple_parser: InputParser[SimpleModel]) -> None:
|
|
242
|
+
"""Test parsing a message with None value."""
|
|
243
|
+
message = KafkaMessage(
|
|
244
|
+
topic="test-topic",
|
|
245
|
+
value=None,
|
|
246
|
+
key="test-key",
|
|
247
|
+
offset=100,
|
|
248
|
+
partition=0,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
with pytest.raises(IncorrectMessageValueError):
|
|
252
|
+
simple_parser.parse_message(message)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def test_parse_message_empty_value(simple_parser: InputParser[SimpleModel]) -> None:
|
|
256
|
+
"""Test parsing a message with empty string value."""
|
|
257
|
+
message = KafkaMessage(
|
|
258
|
+
topic="test-topic",
|
|
259
|
+
value="",
|
|
260
|
+
key="test-key",
|
|
261
|
+
offset=100,
|
|
262
|
+
partition=0,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
with pytest.raises(IncorrectMessageValueError):
|
|
266
|
+
simple_parser.parse_message(message)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def test_parse_message_invalid_json_value(
|
|
270
|
+
simple_parser: InputParser[SimpleModel],
|
|
271
|
+
) -> None:
|
|
272
|
+
"""Test parsing a message with invalid JSON value."""
|
|
273
|
+
message = KafkaMessage(
|
|
274
|
+
topic="test-topic",
|
|
275
|
+
value='{"id": "123", "name": "test", "value": 42', # Invalid JSON
|
|
276
|
+
key="test-key",
|
|
277
|
+
offset=100,
|
|
278
|
+
partition=0,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
with pytest.raises(IncorrectMessageValueError):
|
|
282
|
+
simple_parser.parse_message(message)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def test_parse_message_missing_required_field(
|
|
286
|
+
simple_parser: InputParser[SimpleModel],
|
|
287
|
+
) -> None:
|
|
288
|
+
"""Test parsing a message with missing required field."""
|
|
289
|
+
message = KafkaMessage(
|
|
290
|
+
topic="test-topic",
|
|
291
|
+
value='{"id": "123", "name": "test"}', # Missing 'value' field
|
|
292
|
+
key="test-key",
|
|
293
|
+
offset=100,
|
|
294
|
+
partition=0,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
with pytest.raises(ValidationError):
|
|
298
|
+
simple_parser.parse_message(message)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def test_parse_message_with_special_characters(
|
|
302
|
+
simple_parser: InputParser[SimpleModel],
|
|
303
|
+
) -> None:
|
|
304
|
+
"""Test parsing a message with special characters."""
|
|
305
|
+
special_json = (
|
|
306
|
+
'{"id": "123", "name": "test with special chars: \\n\\t\\"\'", "value": 42}'
|
|
307
|
+
)
|
|
308
|
+
message = KafkaMessage(
|
|
309
|
+
topic="test-topic",
|
|
310
|
+
value=special_json,
|
|
311
|
+
key="test-key",
|
|
312
|
+
offset=100,
|
|
313
|
+
partition=0,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
result = simple_parser.parse_message(message)
|
|
317
|
+
|
|
318
|
+
assert result.payload.name == "test with special chars: \n\t\"'"
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def test_parse_message_with_unicode(simple_parser: InputParser[SimpleModel]) -> None:
|
|
322
|
+
"""Test parsing a message with unicode characters."""
|
|
323
|
+
unicode_json = '{"id": "123", "name": "测试名称", "value": 42}'
|
|
324
|
+
message = KafkaMessage(
|
|
325
|
+
topic="test-topic",
|
|
326
|
+
value=unicode_json,
|
|
327
|
+
key="test-key",
|
|
328
|
+
offset=100,
|
|
329
|
+
partition=0,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
result = simple_parser.parse_message(message)
|
|
333
|
+
|
|
334
|
+
assert result.payload.name == "测试名称"
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def test_parse_message_with_large_data(nested_parser: InputParser[NestedModel]) -> None:
|
|
338
|
+
"""Test parsing a message with large data."""
|
|
339
|
+
large_data = {"key" + str(i): "value" + str(i) for i in range(1000)}
|
|
340
|
+
large_json = json.dumps(
|
|
341
|
+
{"id": "123", "data": large_data, "metadata": {"type": "large"}}
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
message = KafkaMessage(
|
|
345
|
+
topic="test-topic",
|
|
346
|
+
value=large_json,
|
|
347
|
+
key="test-key",
|
|
348
|
+
offset=100,
|
|
349
|
+
partition=0,
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
result = nested_parser.parse_message(message)
|
|
353
|
+
|
|
354
|
+
assert len(result.payload.data) == 1000
|
|
355
|
+
assert result.payload.data["key0"] == "value0"
|
|
356
|
+
assert result.payload.data["key999"] == "value999"
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def test_parse_message_with_none_key(simple_parser: InputParser[SimpleModel]) -> None:
|
|
360
|
+
"""Test parsing a message with None key."""
|
|
361
|
+
message = KafkaMessage(
|
|
362
|
+
topic="test-topic",
|
|
363
|
+
value='{"id": "123", "name": "test", "value": 42}',
|
|
364
|
+
key=None,
|
|
365
|
+
offset=100,
|
|
366
|
+
partition=0,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
result = simple_parser.parse_message(message)
|
|
370
|
+
|
|
371
|
+
assert result.key is None
|
|
372
|
+
assert result.payload.id == "123"
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def test_parse_message_with_none_offset_partition(
|
|
376
|
+
simple_parser: InputParser[SimpleModel],
|
|
377
|
+
) -> None:
|
|
378
|
+
"""Test parsing a message with None offset and partition."""
|
|
379
|
+
message = KafkaMessage(
|
|
380
|
+
topic="test-topic",
|
|
381
|
+
value='{"id": "123", "name": "test", "value": 42}',
|
|
382
|
+
key="test-key",
|
|
383
|
+
offset=None,
|
|
384
|
+
partition=None,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
result = simple_parser.parse_message(message)
|
|
388
|
+
|
|
389
|
+
assert result.offset is None
|
|
390
|
+
assert result.partition is None
|
|
391
|
+
assert result.payload.id == "123"
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def test_incorrect_message_value_error_message(
|
|
395
|
+
simple_parser: InputParser[SimpleModel],
|
|
396
|
+
) -> None:
|
|
397
|
+
"""Test that IncorrectMessageValueError is raised for None value."""
|
|
398
|
+
message = KafkaMessage(
|
|
399
|
+
topic="test-topic",
|
|
400
|
+
value=None,
|
|
401
|
+
key="test-key",
|
|
402
|
+
offset=100,
|
|
403
|
+
partition=0,
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
with pytest.raises(IncorrectMessageValueError):
|
|
407
|
+
simple_parser.parse_message(message)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def test_parser_with_different_models() -> None:
|
|
411
|
+
"""Test that parsers work with different model types."""
|
|
412
|
+
simple_parser = InputParser(SimpleModel)
|
|
413
|
+
optional_parser = InputParser(OptionalModel)
|
|
414
|
+
|
|
415
|
+
simple_json = '{"id": "123", "name": "test", "value": 42}'
|
|
416
|
+
optional_json = '{"id": "456"}'
|
|
417
|
+
|
|
418
|
+
simple_result = simple_parser.parse_json(simple_json)
|
|
419
|
+
optional_result = optional_parser.parse_json(optional_json)
|
|
420
|
+
|
|
421
|
+
assert isinstance(simple_result, SimpleModel)
|
|
422
|
+
assert isinstance(optional_result, OptionalModel)
|
|
423
|
+
assert simple_result.id == "123"
|
|
424
|
+
assert optional_result.id == "456"
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def test_parse_json_with_boolean_values(
|
|
428
|
+
complex_parser: InputParser[ComplexModel],
|
|
429
|
+
) -> None:
|
|
430
|
+
"""Test parsing JSON with boolean values."""
|
|
431
|
+
boolean_json = (
|
|
432
|
+
'{"id": "123", "count": 5, "active": true, "tags": ["tag1"], '
|
|
433
|
+
'"config": {"enabled": false}}'
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
result = complex_parser.parse_json(boolean_json)
|
|
437
|
+
|
|
438
|
+
assert result.active is True
|
|
439
|
+
assert result.config["enabled"] is False
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def test_parse_json_with_numeric_types(
|
|
443
|
+
complex_parser: InputParser[ComplexModel],
|
|
444
|
+
) -> None:
|
|
445
|
+
"""Test parsing JSON with various numeric types."""
|
|
446
|
+
numeric_json = (
|
|
447
|
+
'{"id": "123", "count": 42, "active": true, "tags": ["tag1"], '
|
|
448
|
+
'"config": {"float": 3.14, "negative": -10}}'
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
result = complex_parser.parse_json(numeric_json)
|
|
452
|
+
|
|
453
|
+
assert result.count == 42
|
|
454
|
+
assert result.config["float"] == 3.14
|
|
455
|
+
assert result.config["negative"] == -10
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def test_parse_json_with_array_types(complex_parser: InputParser[ComplexModel]) -> None:
|
|
459
|
+
"""Test parsing JSON with array types."""
|
|
460
|
+
array_json = (
|
|
461
|
+
'{"id": "123", "count": 3, "active": true, "tags": ["tag1", "tag2", "tag3"], '
|
|
462
|
+
'"config": {"empty_array": []}}'
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
result = complex_parser.parse_json(array_json)
|
|
466
|
+
|
|
467
|
+
assert result.tags == ["tag1", "tag2", "tag3"]
|
|
468
|
+
assert result.config["empty_array"] == []
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def test_parse_message_preserves_all_fields(
|
|
472
|
+
simple_parser: InputParser[SimpleModel],
|
|
473
|
+
) -> None:
|
|
474
|
+
"""Test that parse_message preserves all KafkaMessage fields."""
|
|
475
|
+
message = KafkaMessage(
|
|
476
|
+
topic="custom-topic",
|
|
477
|
+
value='{"id": "123", "name": "test", "value": 42}',
|
|
478
|
+
key="custom-key",
|
|
479
|
+
offset=999,
|
|
480
|
+
partition=5,
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
result = simple_parser.parse_message(message)
|
|
484
|
+
|
|
485
|
+
assert result.topic == "custom-topic"
|
|
486
|
+
assert result.key == "custom-key"
|
|
487
|
+
assert result.offset == 999
|
|
488
|
+
assert result.partition == 5
|
|
489
|
+
assert result.value == message.value
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from cledar.kafka.utils.messages import extract_id_from_value
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_extract_id_from_value_correct_value() -> None:
|
|
5
|
+
input_value = '{"id": "3"}'
|
|
6
|
+
expected_id = "3"
|
|
7
|
+
extracted_id = extract_id_from_value(input_value)
|
|
8
|
+
|
|
9
|
+
assert expected_id == extracted_id, "Extracted and expected id doesn't match"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_extract_id_from_value_incorrect_value() -> None:
|
|
13
|
+
input_value = "s"
|
|
14
|
+
expected_id = "<unknown_id>"
|
|
15
|
+
extracted_id = extract_id_from_value(input_value)
|
|
16
|
+
|
|
17
|
+
assert expected_id == extracted_id, "Extracted and expected id doesn't match"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_extract_id_from_value_none_value() -> None:
|
|
21
|
+
input_value = None
|
|
22
|
+
expected_id = "<unknown_id>"
|
|
23
|
+
extracted_id = extract_id_from_value(input_value)
|
|
24
|
+
|
|
25
|
+
assert expected_id == extracted_id, "Extracted and expected id doesn't match"
|