everysk-lib 1.10.3__cp312-cp312-win_amd64.whl → 1.11.0__cp312-cp312-win_amd64.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.
@@ -0,0 +1,1154 @@
1
+ ###############################################################################
2
+ #
3
+ # (C) Copyright 2025 EVERYSK TECHNOLOGIES
4
+ #
5
+ # This is an unpublished work containing confidential and proprietary
6
+ # information of EVERYSK TECHNOLOGIES. Disclosure, use, or reproduction
7
+ # without authorization of EVERYSK TECHNOLOGIES is prohibited.
8
+ #
9
+ ###############################################################################
10
+ # ruff: noqa: E501, ERA001, RUF012
11
+
12
+ import re
13
+ from collections.abc import Iterator
14
+ from sys import maxsize
15
+ from zoneinfo import ZoneInfo
16
+
17
+ from everysk.core.datetime import Date, DateTime
18
+ from everysk.core.exceptions import ReadonlyError, RequiredError
19
+ from everysk.core.mapping import fields
20
+ from everysk.core.mapping.base import BaseMapping
21
+ from everysk.core.unittests import TestCase, mock
22
+
23
+
24
+ class BaseMappingFieldTestCase(TestCase):
25
+ def test_init_field_type(self) -> None:
26
+ field = fields.BaseMappingField(field_type=int)
27
+ self.assertEqual(field.field_type, int)
28
+
29
+ def test_init_field_name(self) -> None:
30
+ field = fields.BaseMappingField(field_name='test_field')
31
+ self.assertEqual(field.field_name, 'test_field')
32
+
33
+ def test_init_choices(self) -> None:
34
+ field = fields.BaseMappingField()
35
+ self.assertSetEqual(field.choices, set())
36
+ field = fields.BaseMappingField(choices={1, 2, 3})
37
+ self.assertSetEqual(field.choices, {1, 2, 3})
38
+
39
+ def test_init_default(self) -> None:
40
+ field = fields.BaseMappingField(default=10)
41
+ self.assertEqual(field.default, 10)
42
+
43
+ @mock.patch.object(fields.BaseMappingField, 'clean_value', return_value='cleaned_value')
44
+ def test_init_default_cleaned(self, clean_value: mock.MagicMock) -> None:
45
+ field = fields.BaseMappingField(default='raw_value', field_type=str)
46
+ self.assertEqual(field.default, 'cleaned_value')
47
+ clean_value.assert_called_once_with('raw_value')
48
+
49
+ @mock.patch.object(fields.BaseMappingField, 'validate', return_value='validated_value')
50
+ def test_init_default_validated(self, validate: mock.MagicMock) -> None:
51
+ field = fields.BaseMappingField(default='raw_value', field_type=str)
52
+ self.assertEqual(field.default, 'raw_value')
53
+ validate.assert_called_once_with('raw_value')
54
+
55
+ def test_init_default_empty_dict(self) -> None:
56
+ with self.assertRaises(ValueError) as context:
57
+ _ = fields.BaseMappingField(default={})
58
+ self.assertEqual(
59
+ str(context.exception), 'Default value cannot be a dict, list, or set unless the field is readonly.'
60
+ )
61
+
62
+ def test_init_default_empty_list(self) -> None:
63
+ with self.assertRaises(ValueError) as context:
64
+ _ = fields.BaseMappingField(default=[])
65
+ self.assertEqual(
66
+ str(context.exception), 'Default value cannot be a dict, list, or set unless the field is readonly.'
67
+ )
68
+
69
+ def test_init_default_empty_set(self) -> None:
70
+ with self.assertRaises(ValueError) as context:
71
+ _ = fields.BaseMappingField(default=set())
72
+ self.assertEqual(
73
+ str(context.exception), 'Default value cannot be a dict, list, or set unless the field is readonly.'
74
+ )
75
+
76
+ def test_init_readonly(self) -> None:
77
+ field = fields.BaseMappingField()
78
+ self.assertFalse(field.readonly)
79
+ field = fields.BaseMappingField(readonly=True)
80
+ self.assertTrue(field.readonly)
81
+
82
+ def test_init_required(self) -> None:
83
+ field = fields.BaseMappingField()
84
+ self.assertFalse(field.required)
85
+ field = fields.BaseMappingField(required=True)
86
+ self.assertTrue(field.required)
87
+
88
+ def test_init_extra_keys(self) -> None:
89
+ field = fields.BaseMappingField(extra_key1='value1', extra_key2='value2')
90
+ self.assertEqual(field.extra_key1, 'value1')
91
+ self.assertEqual(field.extra_key2, 'value2')
92
+
93
+
94
+ class BaseMappingObjectTestCase:
95
+ ## Class used to test object functionality with different fields
96
+ ## use it as inheritance on every field test case and set the attributes below
97
+ # field: fields.BaseMappingField = fields.BaseMappingField # noqa:
98
+ # choices: set = {'default', 'updated'}
99
+ # value_invalid_type: int = 123
100
+ # value_init: str = 'default'
101
+ # value_update: str = 'updated'
102
+
103
+ def test_init_without_value(self) -> None:
104
+ class TestField(BaseMapping):
105
+ __validate_fields__: bool = True
106
+ f1 = self.field()
107
+
108
+ obj = TestField()
109
+ self.assertEqual(obj.f1, Undefined)
110
+ self.assertEqual(obj['f1'], Undefined)
111
+
112
+ def test_choices(self) -> None:
113
+ if self.choices is None:
114
+ return
115
+
116
+ class TestField(BaseMapping):
117
+ __validate_fields__: bool = True
118
+ f1 = self.field(choices=self.choices)
119
+
120
+ obj = TestField(f1=self.value_init)
121
+ obj.f1 = self.value_update
122
+ self.assertEqual(obj.f1, self.value_update)
123
+ self.assertEqual(obj['f1'], self.value_update)
124
+
125
+ def test_instance(self) -> None:
126
+ class TestField(BaseMapping):
127
+ __validate_fields__: bool = True
128
+ f1 = self.field()
129
+
130
+ TestField(f1=self.value_init)
131
+
132
+ def test_instance_invalid_type(self) -> None:
133
+ class TestField(BaseMapping):
134
+ __validate_fields__: bool = True
135
+ f1 = self.field()
136
+
137
+ with self.assertRaises(TypeError) as context:
138
+ TestField(f1=self.value_invalid_type)
139
+
140
+ self.assertEqual(
141
+ str(context.exception),
142
+ f'Field f1 must be of type {self.field.field_type.__name__}, got {type(self.value_invalid_type).__name__}.',
143
+ )
144
+
145
+ def test_invalid_choice(self) -> None:
146
+ if self.choices is None:
147
+ return
148
+
149
+ class TestField(BaseMapping):
150
+ __validate_fields__: bool = True
151
+ f1 = self.field(choices=self.choices)
152
+
153
+ with self.assertRaises(ValueError) as context:
154
+ _ = TestField(f1='invalid_choice')
155
+ self.assertEqual(
156
+ str(context.exception), f"The value 'invalid_choice' for field 'f1' must be in this list {self.choices}."
157
+ )
158
+ obj = TestField()
159
+ with self.assertRaises(ValueError) as context:
160
+ obj.f1 = 'invalid_choice'
161
+ self.assertEqual(
162
+ str(context.exception), f"The value 'invalid_choice' for field 'f1' must be in this list {self.choices}."
163
+ )
164
+
165
+ with self.assertRaises(ValueError) as context:
166
+ obj['f1'] = 'invalid_choice'
167
+ self.assertEqual(
168
+ str(context.exception), f"The value 'invalid_choice' for field 'f1' must be in this list {self.choices}."
169
+ )
170
+
171
+ def test_readonly_field(self) -> None:
172
+ class TestField(BaseMapping):
173
+ __validate_fields__: bool = True
174
+ f1 = self.field(readonly=True)
175
+
176
+ with self.assertRaises(ReadonlyError) as context:
177
+ obj = TestField(f1=self.value_init)
178
+ self.assertEqual(str(context.exception), 'Field f1 is readonly.')
179
+
180
+ obj = TestField()
181
+ with self.assertRaises(ReadonlyError) as context:
182
+ obj.f1 = self.value_update
183
+ self.assertEqual(str(context.exception), 'Field f1 is readonly.')
184
+
185
+ with self.assertRaises(ReadonlyError) as context:
186
+ obj['f1'] = self.value_update
187
+ self.assertEqual(str(context.exception), 'Field f1 is readonly.')
188
+
189
+ def test_required_field(self) -> None:
190
+ class TestField(BaseMapping):
191
+ __validate_fields__: bool = True
192
+ f1 = self.field(required=True)
193
+
194
+ with self.assertRaises(RequiredError) as context:
195
+ _ = TestField()
196
+ self.assertEqual(str(context.exception), 'Field f1 is required.')
197
+
198
+ def test_value_init(self) -> None:
199
+ class TestField(BaseMapping):
200
+ __validate_fields__: bool = True
201
+ f1 = self.field()
202
+
203
+ obj = TestField(f1=self.value_init)
204
+ self.assertEqual(obj.f1, self.value_init)
205
+ self.assertEqual(obj['f1'], self.value_init)
206
+
207
+ def test_value_update_attribute(self) -> None:
208
+ class TestField(BaseMapping):
209
+ __validate_fields__: bool = True
210
+ f1 = self.field()
211
+
212
+ obj = TestField(f1=self.value_init)
213
+ obj.f1 = self.value_update
214
+ self.assertEqual(obj.f1, self.value_update)
215
+ self.assertEqual(obj['f1'], self.value_update)
216
+
217
+ def test_value_update_key(self) -> None:
218
+ class TestField(BaseMapping):
219
+ __validate_fields__: bool = True
220
+ f1 = self.field()
221
+
222
+ obj = TestField(f1=self.value_init)
223
+ obj['f1'] = self.value_update
224
+ self.assertEqual(obj.f1, self.value_update)
225
+ self.assertEqual(obj['f1'], self.value_update)
226
+
227
+
228
+ class BoolFieldTestCase(TestCase, BaseMappingObjectTestCase):
229
+ field: fields.BoolField = fields.BoolField
230
+ choices: set = {True, False}
231
+ value_invalid_type: int = 123
232
+ value_init: bool = True
233
+ value_update: bool = False
234
+
235
+ def test_attr_type(self) -> None:
236
+ field = fields.BoolField()
237
+ self.assertEqual(field.field_type, bool)
238
+
239
+ def test_clean_value_true(self) -> None:
240
+ field = fields.BoolField()
241
+ self.assertTrue(field.clean_value(1))
242
+ self.assertTrue(field.clean_value(True)) # noqa: FBT003
243
+ self.assertTrue(field.clean_value('y'))
244
+ self.assertTrue(field.clean_value('yes'))
245
+ self.assertTrue(field.clean_value('on'))
246
+
247
+ def test_clean_value_false(self) -> None:
248
+ field = fields.BoolField()
249
+ self.assertFalse(field.clean_value(0))
250
+ self.assertFalse(field.clean_value(False)) # noqa: FBT003
251
+ self.assertFalse(field.clean_value('n'))
252
+ self.assertFalse(field.clean_value('no'))
253
+ self.assertFalse(field.clean_value('off'))
254
+
255
+ def test_invalid_clean_value(self) -> None:
256
+ field = fields.BoolField()
257
+ self.assertEqual(field.clean_value('invalid'), 'invalid')
258
+
259
+
260
+ class ChoiceFieldTestCase(TestCase, BaseMappingObjectTestCase):
261
+ field: fields.ChoiceField = fields.ChoiceField
262
+ choices: set = {'a', 'b', 'c'}
263
+ value_invalid_type: int = 123
264
+ value_init: str = 'a'
265
+ value_update: str = 'b'
266
+
267
+ def test_attr_type(self) -> None:
268
+ field = fields.StrField()
269
+ self.assertEqual(field.field_type, str)
270
+
271
+ def test_min_size_default_value(self) -> None:
272
+ field = fields.StrField()
273
+ self.assertEqual(field.min_size, 0)
274
+
275
+ def test_max_size_default_value(self) -> None:
276
+ field = fields.StrField()
277
+ self.assertEqual(field.max_size, maxsize)
278
+
279
+ def test_validate_min_size(self) -> None:
280
+ field = fields.StrField(min_size=2)
281
+ with self.assertRaises(ValueError) as context:
282
+ field.validate('a')
283
+ self.assertEqual(str(context.exception), "The size for field 'None' must be greater than or equal to 2.")
284
+
285
+ def test_validate_max_size(self) -> None:
286
+ field = fields.StrField(max_size=2)
287
+ with self.assertRaises(ValueError) as context:
288
+ field.validate('abc')
289
+ self.assertEqual(str(context.exception), "The size for field 'None' must be less than or equal to 2.")
290
+
291
+ def test_validate_regex(self) -> None:
292
+ field = fields.StrField(regex=r'^[a-z]+$')
293
+ with self.assertRaises(ValueError) as context:
294
+ field.validate('abc123')
295
+ self.assertEqual(
296
+ str(context.exception), "The value 'abc123' for field 'None' must match with this regex: ^[a-z]+$"
297
+ )
298
+
299
+
300
+ class DateFieldTestCase(TestCase, BaseMappingObjectTestCase):
301
+ field: fields.DateField = fields.DateField
302
+ choices: set = {Date(2026, 1, 1), Date(2026, 1, 2)}
303
+ value_invalid_type: int = 123
304
+ value_init: Date = Date(2026, 1, 1)
305
+ value_update: Date = Date(2026, 1, 2)
306
+
307
+ def test_attr_type(self) -> None:
308
+ field = fields.DateField()
309
+ self.assertEqual(field.field_type, Date)
310
+
311
+ def test_clean_value_str_isoformat(self) -> None:
312
+ field = fields.DateField()
313
+ self.assertEqual(field.clean_value('2026-01-01'), Date(2026, 1, 1))
314
+
315
+ def test_clean_value_str_everysk(self) -> None:
316
+ field = fields.DateField()
317
+ self.assertEqual(field.clean_value('20260101'), Date(2026, 1, 1))
318
+
319
+ def test_clean_value_date(self) -> None:
320
+ field = fields.DateField()
321
+ self.assertEqual(field.clean_value(Date(2026, 1, 1)), Date(2026, 1, 1))
322
+
323
+ def test_invalid_isoformat(self) -> None:
324
+ field = fields.DateField()
325
+ with self.assertRaises(ValueError) as context:
326
+ field.clean_value('01-01-2026')
327
+ self.assertEqual(str(context.exception), "Invalid isoformat string: '01-01-2026'")
328
+
329
+ def test_invalid_everysk_format(self) -> None:
330
+ field = fields.DateField()
331
+ self.assertEqual(field.clean_value('2026/01/01'), '2026/01/01')
332
+
333
+ def test_min_date_validation(self) -> None:
334
+ field = fields.DateField(min_date=Date(2026, 1, 1))
335
+ with self.assertRaises(ValueError) as context:
336
+ field.validate(Date(2025, 12, 31))
337
+ self.assertEqual(
338
+ str(context.exception),
339
+ "The value '2025-12-31' for field 'None' must be greater than or equal to 2026-01-01.",
340
+ )
341
+
342
+ def test_max_date_validation(self) -> None:
343
+ field = fields.DateField(max_date=Date(2026, 12, 31))
344
+ with self.assertRaises(ValueError) as context:
345
+ field.validate(Date(2027, 1, 1))
346
+ self.assertEqual(
347
+ str(context.exception), "The value '2027-01-01' for field 'None' must be less than or equal to 2026-12-31."
348
+ )
349
+
350
+
351
+ class DateTimeFieldTestCase(TestCase, BaseMappingObjectTestCase):
352
+ field: fields.DateTimeField = fields.DateTimeField
353
+ choices: set = {DateTime(2026, 1, 1), DateTime(2026, 1, 2)}
354
+ value_invalid_type: int = 123
355
+ value_init: DateTime = DateTime(2026, 1, 1)
356
+ value_update: DateTime = DateTime(2026, 1, 2)
357
+
358
+ def test_attr_type(self) -> None:
359
+ field = fields.DateTimeField()
360
+ self.assertEqual(field.field_type, DateTime)
361
+
362
+ def test_clean_value_str_isoformat(self) -> None:
363
+ field = fields.DateTimeField()
364
+ self.assertEqual(field.clean_value('2026-01-01T12:30:45'), DateTime(2026, 1, 1, 12, 30, 45))
365
+
366
+ def test_clean_value_str_isoformat_tz(self) -> None:
367
+ field = fields.DateTimeField()
368
+ self.assertEqual(
369
+ field.clean_value('2026-01-01T12:30:45+00:00'), DateTime(2026, 1, 1, 12, 30, 45, tzinfo=ZoneInfo('UTC'))
370
+ )
371
+
372
+ def test_clean_value_str_everysk(self) -> None:
373
+ field = fields.DateTimeField()
374
+ self.assertEqual(field.clean_value('2026-01-01 13:00:00'), DateTime(2026, 1, 1, 13, 0, 0))
375
+
376
+ def test_clean_value_str_everysk_tz(self) -> None:
377
+ field = fields.DateTimeField()
378
+ self.assertEqual(
379
+ field.clean_value('2026-01-01 13:00:00+00:00'), DateTime(2026, 1, 1, 13, 0, 0, tzinfo=ZoneInfo('UTC'))
380
+ )
381
+
382
+ def test_clean_value_date(self) -> None:
383
+ field = fields.DateTimeField()
384
+ self.assertEqual(field.clean_value(Date(2026, 1, 1)), DateTime(2026, 1, 1, 0, 0, 0))
385
+
386
+ def test_clean_value_datetime(self) -> None:
387
+ field = fields.DateTimeField()
388
+ self.assertEqual(field.clean_value(DateTime(2026, 1, 1, 12, 30, 45)), DateTime(2026, 1, 1, 12, 30, 45))
389
+
390
+ def test_force_time(self) -> None:
391
+ # Default is None so time is not changed
392
+ field = fields.DateTimeField()
393
+ self.assertEqual(field.clean_value(DateTime(2026, 1, 1, 12, 30, 45)), DateTime(2026, 1, 1, 12, 30, 45))
394
+ self.assertEqual(field.clean_value('2026-01-01T13:00:00'), DateTime(2026, 1, 1, 13, 0, 0))
395
+ self.assertEqual(field.clean_value('2026-01-01 13:00:00'), DateTime(2026, 1, 1, 13, 0, 0))
396
+ self.assertEqual(field.clean_value('2026-01-01'), DateTime(2026, 1, 1, 0, 0, 0))
397
+
398
+ def test_force_time_first_minute(self) -> None:
399
+ field = fields.DateTimeField(force_time='FIRST_MINUTE')
400
+ self.assertEqual(field.clean_value(DateTime(2026, 1, 1, 8, 15, 0)), DateTime(2026, 1, 1, 0, 0, 0))
401
+ self.assertEqual(field.clean_value('2026-01-01T13:00:00'), DateTime(2026, 1, 1, 0, 0, 0))
402
+ self.assertEqual(field.clean_value('2026-01-01 13:00:00'), DateTime(2026, 1, 1, 0, 0, 0))
403
+ self.assertEqual(field.clean_value('2026-01-01'), DateTime(2026, 1, 1, 0, 0, 0))
404
+
405
+ def test_force_time_last_minute(self) -> None:
406
+ field = fields.DateTimeField(force_time='LAST_MINUTE')
407
+ self.assertEqual(field.clean_value(DateTime(2026, 1, 1, 8, 15, 0)), DateTime(2026, 1, 1, 23, 59, 59, 999999))
408
+ self.assertEqual(field.clean_value('2026-01-01T13:00:00'), DateTime(2026, 1, 1, 23, 59, 59, 999999))
409
+ self.assertEqual(field.clean_value('2026-01-01 13:00:00'), DateTime(2026, 1, 1, 23, 59, 59, 999999))
410
+ self.assertEqual(field.clean_value('2026-01-01'), DateTime(2026, 1, 1, 23, 59, 59, 999999))
411
+
412
+ def test_force_time_midday(self) -> None:
413
+ field = fields.DateTimeField(force_time='MIDDAY')
414
+ self.assertEqual(field.clean_value(DateTime(2026, 1, 1, 8, 15, 0)), DateTime(2026, 1, 1, 12, 0, 0))
415
+ self.assertEqual(field.clean_value('2026-01-01T13:00:00'), DateTime(2026, 1, 1, 12, 0, 0))
416
+ self.assertEqual(field.clean_value('2026-01-01 13:00:00'), DateTime(2026, 1, 1, 12, 0, 0))
417
+ self.assertEqual(field.clean_value('2026-01-01'), DateTime(2026, 1, 1, 12, 0, 0))
418
+
419
+ def test_force_time_now(self) -> None:
420
+ fixed_now = DateTime(2026, 1, 1, 15, 45, 30)
421
+
422
+ with mock.patch('everysk.core.mapping.fields.DateTime.now', return_value=fixed_now):
423
+ field = fields.DateTimeField(force_time='NOW')
424
+ self.assertEqual(field.clean_value(DateTime(2026, 1, 1, 8, 15, 0)), fixed_now)
425
+ self.assertEqual(field.clean_value('2026-01-01T13:00:00'), fixed_now)
426
+ self.assertEqual(field.clean_value('2026-01-01 13:00:00'), fixed_now)
427
+ self.assertEqual(field.clean_value('2026-01-01'), fixed_now)
428
+
429
+ def test_invalid_isoformat(self) -> None:
430
+ field = fields.DateTimeField()
431
+ self.assertEqual(field.clean_value('01-01-2026 12:30:45'), '01-01-2026 12:30:45')
432
+
433
+ def test_invalid_everysk_format(self) -> None:
434
+ field = fields.DateTimeField()
435
+ self.assertEqual(field.clean_value('2026/01/01 12:30:45'), '2026/01/01 12:30:45')
436
+
437
+ def test_min_date_validation(self) -> None:
438
+ field = fields.DateTimeField(min_date=DateTime(2026, 1, 1, 0, 0, 0))
439
+ with self.assertRaises(ValueError) as context:
440
+ field.validate(DateTime(2025, 12, 31, 23, 59, 59))
441
+ self.assertEqual(
442
+ str(context.exception),
443
+ "The value '2025-12-31 23:59:59+00:00' for field 'None' must be greater than or equal to 2026-01-01 00:00:00+00:00.",
444
+ )
445
+
446
+ def test_max_date_validation(self) -> None:
447
+ field = fields.DateTimeField(max_date=DateTime(2026, 12, 31, 23, 59, 59))
448
+ with self.assertRaises(ValueError) as context:
449
+ field.validate(DateTime(2027, 1, 1, 0, 0, 0))
450
+ self.assertEqual(
451
+ str(context.exception),
452
+ "The value '2027-01-01 00:00:00+00:00' for field 'None' must be less than or equal to 2026-12-31 23:59:59+00:00.",
453
+ )
454
+
455
+
456
+ class DictFieldTestCase(TestCase, BaseMappingObjectTestCase):
457
+ field: fields.DictField = fields.DictField
458
+ choices: set = None
459
+ value_invalid_type: int = 123
460
+ value_init: dict = {'a': 1}
461
+ value_update: dict = {'b': 2}
462
+
463
+ def setUp(self) -> None:
464
+ self._field = fields.DictField(readonly=True)
465
+ self._value = self._field.clean_value({'key': 'value'})
466
+
467
+ def test_attr_type(self) -> None:
468
+ field = fields.DictField()
469
+ self.assertEqual(field.field_type, dict)
470
+
471
+ def test_clean_value_readonly(self) -> None:
472
+ self.assertEqual(self._value, {'key': 'value'})
473
+ self.assertIsInstance(self._value, fields.DictField.ReadonlyDict)
474
+
475
+ def test_clean_value_readonly_clear(self) -> None:
476
+ with self.assertRaises(ReadonlyError):
477
+ self._value.clear()
478
+
479
+ def test_clean_value_readonly_del(self) -> None:
480
+ with self.assertRaises(ReadonlyError):
481
+ del self._value['key']
482
+
483
+ def test_clean_value_readonly_pop(self) -> None:
484
+ with self.assertRaises(ReadonlyError):
485
+ self._value.pop('key')
486
+
487
+ def test_clean_value_readonly_popitem(self) -> None:
488
+ with self.assertRaises(ReadonlyError):
489
+ self._value.popitem()
490
+
491
+ def test_clean_value_readonly_set(self) -> None:
492
+ with self.assertRaises(ReadonlyError):
493
+ self._value['key'] = 'new_value'
494
+
495
+ def test_clean_value_readonly_setdefault(self) -> None:
496
+ with self.assertRaises(ReadonlyError):
497
+ self._value.setdefault('key', 'new_value')
498
+
499
+ def test_clean_value_readonly_update(self) -> None:
500
+ with self.assertRaises(ReadonlyError):
501
+ self._value.update({'new_key': 'new_value'})
502
+
503
+
504
+ class EmailFieldTestCase(TestCase, BaseMappingObjectTestCase):
505
+ field: fields.EmailField = fields.EmailField
506
+ choices: set = {'a@b.c', 'd@e.f'}
507
+ value_invalid_type: int = 123
508
+ value_init: str = 'a@b.c'
509
+ value_update: str = 'd@e.f'
510
+
511
+ def test_attr_type(self) -> None:
512
+ field = fields.EmailField()
513
+ self.assertEqual(field.field_type, str)
514
+
515
+ def test_validate_valid_email(self) -> None:
516
+ field = fields.EmailField()
517
+ field.validate('fscariott@everysk.com')
518
+
519
+ def test_validate_invalid_email(self) -> None:
520
+ field = fields.EmailField()
521
+ with self.assertRaises(ValueError) as context:
522
+ field.validate('invalid-email')
523
+ self.assertEqual(str(context.exception), 'Key None must be an e-mail.')
524
+
525
+ def test_validate_invalid_email_max_size(self) -> None:
526
+ field = fields.EmailField()
527
+ long_email = 'a' * 315 + '@everysk.com' # total length = 321
528
+ with self.assertRaises(ValueError) as context:
529
+ field.validate(long_email)
530
+ self.assertEqual(str(context.exception), "The size for field 'None' must be less than or equal to 320.")
531
+
532
+ def test_validate_invalid_email_min_size(self) -> None:
533
+ field = fields.EmailField()
534
+ short_email = 'a@b' # total length = 3
535
+ with self.assertRaises(ValueError) as context:
536
+ field.validate(short_email)
537
+ self.assertEqual(str(context.exception), "The size for field 'None' must be greater than or equal to 5.")
538
+
539
+ def test_validate_invalid_email_more_at(self) -> None:
540
+ field = fields.EmailField()
541
+ with self.assertRaises(ValueError) as context:
542
+ field.validate('fscariott@everysk.com@teste')
543
+ self.assertEqual(str(context.exception), 'Key None must be an e-mail.')
544
+
545
+
546
+ class FloatFieldTestCase(TestCase, BaseMappingObjectTestCase):
547
+ field: fields.FloatField = fields.FloatField
548
+ choices: set = {1.23, 4.56}
549
+ value_invalid_type: str = 'invalid'
550
+ value_init: float = 1.23
551
+ value_update: float = 4.56
552
+
553
+ def test_attr_type(self) -> None:
554
+ field = fields.FloatField()
555
+ self.assertEqual(field.field_type, float)
556
+
557
+ def test_clean_value_int(self) -> None:
558
+ field = fields.FloatField()
559
+ self.assertEqual(field.clean_value(123), 123.0)
560
+
561
+ def test_clean_value_str(self) -> None:
562
+ field = fields.FloatField()
563
+ self.assertEqual(field.clean_value('123.45'), 123.45)
564
+
565
+ def test_clean_value_invalid_str(self) -> None:
566
+ field = fields.FloatField()
567
+ self.assertEqual(field.clean_value('invalid'), 'invalid')
568
+
569
+ def test_validate_max_value(self) -> None:
570
+ field = fields.FloatField(max_value=100.0)
571
+ with self.assertRaises(ValueError) as context:
572
+ field.validate(150.0)
573
+ self.assertEqual(
574
+ str(context.exception), "The value '150.0' for field 'None' must be less than or equal to 100.0."
575
+ )
576
+
577
+ def test_validate_min_value(self) -> None:
578
+ field = fields.FloatField(min_value=10.0)
579
+ with self.assertRaises(ValueError) as context:
580
+ field.validate(5.0)
581
+ self.assertEqual(
582
+ str(context.exception), "The value '5.0' for field 'None' must be greater than or equal to 10.0."
583
+ )
584
+
585
+
586
+ class IntFieldTestCase(TestCase, BaseMappingObjectTestCase):
587
+ field: fields.IntField = fields.IntField
588
+ choices: set = {1, 2}
589
+ value_invalid_type: str = 'invalid'
590
+ value_init: int = 1
591
+ value_update: int = 2
592
+
593
+ def test_attr_type(self) -> None:
594
+ field = fields.IntField()
595
+ self.assertEqual(field.field_type, int)
596
+
597
+ def test_clean_value_str(self) -> None:
598
+ field = fields.IntField()
599
+ self.assertEqual(field.clean_value('123'), 123)
600
+
601
+ def test_clean_value_invalid_str(self) -> None:
602
+ field = fields.IntField()
603
+ self.assertEqual(field.clean_value('invalid'), 'invalid')
604
+
605
+ def test_validate_max_value(self) -> None:
606
+ field = fields.IntField(max_value=100)
607
+ with self.assertRaises(ValueError) as context:
608
+ field.validate(150)
609
+ self.assertEqual(str(context.exception), "The value '150' for field 'None' must be less than or equal to 100.")
610
+
611
+ def test_validate_min_value(self) -> None:
612
+ field = fields.IntField(min_value=10)
613
+ with self.assertRaises(ValueError) as context:
614
+ field.validate(5)
615
+ self.assertEqual(str(context.exception), "The value '5' for field 'None' must be greater than or equal to 10.")
616
+
617
+
618
+ class IteratorFieldTestCase(TestCase, BaseMappingObjectTestCase):
619
+ field: fields.IteratorField = fields.IteratorField
620
+ choices: set = None
621
+ value_invalid_type: str = 'invalid'
622
+ value_init: Iterator = iter([1])
623
+ value_update: Iterator = iter([2])
624
+
625
+ def test_attr_type(self) -> None:
626
+ field = fields.IteratorField()
627
+ self.assertEqual(field.field_type, Iterator)
628
+
629
+ def test_clean_value_list(self) -> None:
630
+ field = fields.IteratorField()
631
+ value = field.clean_value([1, 2, 3])
632
+ self.assertIsInstance(value, Iterator)
633
+ self.assertListEqual(list(value), [1, 2, 3])
634
+
635
+ def test_clean_value_set(self) -> None:
636
+ field = fields.IteratorField()
637
+ value = field.clean_value({3, 1, 2})
638
+ self.assertIsInstance(value, Iterator)
639
+ self.assertListEqual(sorted(value), [1, 2, 3])
640
+
641
+ def test_clean_value_str(self) -> None:
642
+ field = fields.IteratorField()
643
+ value = field.clean_value('a,b,c')
644
+ self.assertIsInstance(value, Iterator)
645
+ self.assertListEqual(list(value), ['a', 'b', 'c'])
646
+
647
+ def test_clean_value_str_separator(self) -> None:
648
+ field = fields.IteratorField(separator=';')
649
+ value = field.clean_value('a;b;c')
650
+ self.assertIsInstance(value, Iterator)
651
+ self.assertListEqual(list(value), ['a', 'b', 'c'])
652
+
653
+ def test_clean_value_tuple(self) -> None:
654
+ field = fields.IteratorField()
655
+ value = field.clean_value((1, 2, 3))
656
+ self.assertIsInstance(value, Iterator)
657
+ self.assertListEqual(list(value), [1, 2, 3])
658
+
659
+
660
+ class ListFieldTestCase(TestCase, BaseMappingObjectTestCase):
661
+ field: fields.ListField = fields.ListField
662
+ choices: set = {1, 2}
663
+ value_invalid_type: str = 'invalid'
664
+ value_init: list = [1]
665
+ value_update: list = [2]
666
+
667
+ def test_attr_type(self) -> None:
668
+ field = fields.ListField()
669
+ self.assertEqual(field.field_type, list)
670
+
671
+ def test_clean_value_readonly(self) -> None:
672
+ field = fields.ListField(readonly=True)
673
+ value = field.clean_value([1, 2, 3])
674
+ self.assertIsInstance(value, fields.ListField.ReadonlyList)
675
+ self.assertListEqual(value, [1, 2, 3])
676
+
677
+ def test_clean_value_readonly_append(self) -> None:
678
+ field = fields.ListField(readonly=True)
679
+ value = field.clean_value([1, 2, 3])
680
+ with self.assertRaises(fields.ReadonlyError):
681
+ value.append(4)
682
+
683
+ def test_clean_value_readonly_clear(self) -> None:
684
+ field = fields.ListField(readonly=True)
685
+ value = field.clean_value([1, 2, 3])
686
+ with self.assertRaises(fields.ReadonlyError):
687
+ value.clear()
688
+
689
+ def test_clean_value_readonly_extend(self) -> None:
690
+ field = fields.ListField(readonly=True)
691
+ value = field.clean_value([1, 2, 3])
692
+ with self.assertRaises(fields.ReadonlyError):
693
+ value.extend([4, 5])
694
+
695
+ def test_clean_value_readonly_insert(self) -> None:
696
+ field = fields.ListField(readonly=True)
697
+ value = field.clean_value([1, 2, 3])
698
+ with self.assertRaises(fields.ReadonlyError):
699
+ value.insert(1, 4)
700
+
701
+ def test_clean_value_readonly_pop(self) -> None:
702
+ field = fields.ListField(readonly=True)
703
+ value = field.clean_value([1, 2, 3])
704
+ with self.assertRaises(fields.ReadonlyError):
705
+ value.pop()
706
+
707
+ def test_clean_value_readonly_remove(self) -> None:
708
+ field = fields.ListField(readonly=True)
709
+ value = field.clean_value([1, 2, 3])
710
+ with self.assertRaises(fields.ReadonlyError):
711
+ value.remove(2)
712
+
713
+ def test_clean_value_readonly_reverse(self) -> None:
714
+ field = fields.ListField(readonly=True)
715
+ value = field.clean_value([1, 2, 3])
716
+ with self.assertRaises(fields.ReadonlyError):
717
+ value.reverse()
718
+
719
+ def test_clean_value_readonly_sort(self) -> None:
720
+ field = fields.ListField(readonly=True)
721
+ value = field.clean_value([1, 2, 3])
722
+ with self.assertRaises(fields.ReadonlyError):
723
+ value.sort()
724
+
725
+ def test_clean_value_set(self) -> None:
726
+ field = fields.ListField()
727
+ value = field.clean_value({3, 1, 2})
728
+ self.assertIsInstance(value, list)
729
+ self.assertListEqual(sorted(value), [1, 2, 3])
730
+
731
+ def test_clean_value_str(self) -> None:
732
+ field = fields.ListField()
733
+ value = field.clean_value('a,b,c')
734
+ self.assertIsInstance(value, list)
735
+ self.assertListEqual(value, ['a', 'b', 'c'])
736
+
737
+ def test_clean_value_str_separator(self) -> None:
738
+ field = fields.ListField(separator=';')
739
+ value = field.clean_value('a;b;c')
740
+ self.assertIsInstance(value, list)
741
+ self.assertListEqual(value, ['a', 'b', 'c'])
742
+
743
+ def test_clean_value_tuple(self) -> None:
744
+ field = fields.ListField()
745
+ value = field.clean_value((1, 2, 3))
746
+ self.assertIsInstance(value, list)
747
+ self.assertListEqual(value, [1, 2, 3])
748
+
749
+ def test_invalid_choice_list(self) -> None:
750
+ class TestField(BaseMapping):
751
+ __validate_fields__: bool = True
752
+ f1 = self.field(choices=self.choices)
753
+
754
+ with self.assertRaises(ValueError) as context:
755
+ _ = TestField(f1=[3])
756
+ self.assertEqual(str(context.exception), f"The value '[3]' for field 'f1' must be in this list {self.choices}.")
757
+ obj = TestField()
758
+ with self.assertRaises(ValueError) as context:
759
+ obj.f1 = [3]
760
+ self.assertEqual(str(context.exception), f"The value '[3]' for field 'f1' must be in this list {self.choices}.")
761
+
762
+ with self.assertRaises(ValueError) as context:
763
+ obj['f1'] = [3]
764
+ self.assertEqual(str(context.exception), f"The value '[3]' for field 'f1' must be in this list {self.choices}.")
765
+
766
+ def test_invalid_choice_list_values(self) -> None:
767
+ class TestField(BaseMapping):
768
+ __validate_fields__: bool = True
769
+ f1 = self.field(choices=self.choices)
770
+
771
+ with self.assertRaises(ValueError) as context:
772
+ _ = TestField(f1=[1, 3])
773
+ self.assertEqual(
774
+ str(context.exception), f"The value '[1, 3]' for field 'f1' must be in this list {self.choices}."
775
+ )
776
+ obj = TestField()
777
+ with self.assertRaises(ValueError) as context:
778
+ obj.f1 = [1, 3]
779
+ self.assertEqual(
780
+ str(context.exception), f"The value '[1, 3]' for field 'f1' must be in this list {self.choices}."
781
+ )
782
+
783
+ with self.assertRaises(ValueError) as context:
784
+ obj['f1'] = [1, 3]
785
+ self.assertEqual(
786
+ str(context.exception), f"The value '[1, 3]' for field 'f1' must be in this list {self.choices}."
787
+ )
788
+
789
+ def test_max_size_default_value(self) -> None:
790
+ field = fields.ListField()
791
+ self.assertEqual(field.max_size, maxsize // 8)
792
+
793
+ def test_min_size_default_value(self) -> None:
794
+ field = fields.ListField()
795
+ self.assertEqual(field.min_size, 0)
796
+
797
+ def test_validate_max_size(self) -> None:
798
+ field = fields.ListField(max_size=2)
799
+ with self.assertRaises(ValueError) as context:
800
+ field.validate([1, 2, 3])
801
+ self.assertEqual(str(context.exception), "The size for field 'None' must be less than or equal to 2.")
802
+
803
+ def test_validate_min_size(self) -> None:
804
+ field = fields.ListField(min_size=2)
805
+ with self.assertRaises(ValueError) as context:
806
+ field.validate([1])
807
+ self.assertEqual(str(context.exception), "The size for field 'None' must be greater than or equal to 2.")
808
+
809
+
810
+ class SetFieldTestCase(TestCase, BaseMappingObjectTestCase):
811
+ field: fields.SetField = fields.SetField
812
+ choices: set = {1, 2}
813
+ value_invalid_type: str = 'invalid'
814
+ value_init: set = {1}
815
+ value_update: set = {2}
816
+
817
+ def test_attr_type(self) -> None:
818
+ field = fields.SetField()
819
+ self.assertEqual(field.field_type, set)
820
+
821
+ def test_clean_value_list(self) -> None:
822
+ field = fields.SetField()
823
+ value = field.clean_value([1, 2, 2, 3])
824
+ self.assertIsInstance(value, set)
825
+ self.assertSetEqual(value, {1, 2, 3})
826
+
827
+ def test_clean_value_readonly(self) -> None:
828
+ field = fields.SetField(readonly=True)
829
+ value = field.clean_value({1, 2, 3})
830
+ self.assertIsInstance(value, fields.SetField.ReadonlySet)
831
+ self.assertSetEqual(value, {1, 2, 3})
832
+
833
+ def test_clean_value_readonly_add(self) -> None:
834
+ field = fields.SetField(readonly=True)
835
+ value = field.clean_value({1, 2, 3})
836
+ with self.assertRaises(fields.ReadonlyError):
837
+ value.add(4)
838
+
839
+ def test_clean_value_readonly_clear(self) -> None:
840
+ field = fields.SetField(readonly=True)
841
+ value = field.clean_value({1, 2, 3})
842
+ with self.assertRaises(fields.ReadonlyError):
843
+ value.clear()
844
+
845
+ def test_clean_value_readonly_discard(self) -> None:
846
+ field = fields.SetField(readonly=True)
847
+ value = field.clean_value({1, 2, 3})
848
+ with self.assertRaises(fields.ReadonlyError):
849
+ value.discard(2)
850
+
851
+ def test_clean_value_readonly_pop(self) -> None:
852
+ field = fields.SetField(readonly=True)
853
+ value = field.clean_value({1, 2, 3})
854
+ with self.assertRaises(fields.ReadonlyError):
855
+ value.pop()
856
+
857
+ def test_clean_value_readonly_remove(self) -> None:
858
+ field = fields.SetField(readonly=True)
859
+ value = field.clean_value({1, 2, 3})
860
+ with self.assertRaises(fields.ReadonlyError):
861
+ value.remove(2)
862
+
863
+ def test_clean_value_readonly_update(self) -> None:
864
+ field = fields.SetField(readonly=True)
865
+ value = field.clean_value({1, 2, 3})
866
+ with self.assertRaises(fields.ReadonlyError):
867
+ value.update({4})
868
+
869
+ def test_clean_value_str(self) -> None:
870
+ field = fields.SetField()
871
+ value = field.clean_value('a,b,a,c')
872
+ self.assertIsInstance(value, set)
873
+ self.assertSetEqual(value, {'a', 'b', 'c'})
874
+
875
+ def test_clean_value_str_separator(self) -> None:
876
+ field = fields.SetField(separator=';')
877
+ value = field.clean_value('a;b;a;c')
878
+ self.assertIsInstance(value, set)
879
+ self.assertSetEqual(value, {'a', 'b', 'c'})
880
+
881
+ def test_clean_value_tuple(self) -> None:
882
+ field = fields.SetField()
883
+ value = field.clean_value((1, 2, 2, 3))
884
+ self.assertIsInstance(value, set)
885
+ self.assertSetEqual(value, {1, 2, 3})
886
+
887
+ def test_invalid_choice_set(self) -> None:
888
+ class TestField(BaseMapping):
889
+ __validate_fields__: bool = True
890
+ f1 = self.field(choices=self.choices)
891
+
892
+ with self.assertRaises(ValueError) as context:
893
+ _ = TestField(f1={3})
894
+ self.assertEqual(
895
+ str(context.exception), f"The value '{{3}}' for field 'f1' must be in this list {self.choices}."
896
+ )
897
+ obj = TestField()
898
+ with self.assertRaises(ValueError) as context:
899
+ obj.f1 = {3}
900
+ self.assertEqual(
901
+ str(context.exception), f"The value '{{3}}' for field 'f1' must be in this list {self.choices}."
902
+ )
903
+
904
+ with self.assertRaises(ValueError) as context:
905
+ obj['f1'] = {3}
906
+ self.assertEqual(
907
+ str(context.exception), f"The value '{{3}}' for field 'f1' must be in this list {self.choices}."
908
+ )
909
+
910
+ def test_invalid_choice_set_values(self) -> None:
911
+ class TestField(BaseMapping):
912
+ __validate_fields__: bool = True
913
+ f1 = self.field(choices=self.choices)
914
+
915
+ with self.assertRaises(ValueError) as context:
916
+ _ = TestField(f1={1, 3})
917
+ self.assertEqual(
918
+ str(context.exception), f"The value '{{1, 3}}' for field 'f1' must be in this list {self.choices}."
919
+ )
920
+ obj = TestField()
921
+ with self.assertRaises(ValueError) as context:
922
+ obj.f1 = {1, 3}
923
+ self.assertEqual(
924
+ str(context.exception), f"The value '{{1, 3}}' for field 'f1' must be in this list {self.choices}."
925
+ )
926
+
927
+ with self.assertRaises(ValueError) as context:
928
+ obj['f1'] = {1, 3}
929
+ self.assertEqual(
930
+ str(context.exception), f"The value '{{1, 3}}' for field 'f1' must be in this list {self.choices}."
931
+ )
932
+
933
+ def test_max_size_default_value(self) -> None:
934
+ field = fields.SetField()
935
+ self.assertEqual(field.max_size, maxsize // 8)
936
+
937
+ def test_min_size_default_value(self) -> None:
938
+ field = fields.SetField()
939
+ self.assertEqual(field.min_size, 0)
940
+
941
+ def test_validate_min_size(self) -> None:
942
+ field = fields.SetField(min_size=2)
943
+ with self.assertRaises(ValueError) as context:
944
+ field.validate({1})
945
+ self.assertEqual(str(context.exception), "The size for field 'None' must be greater than or equal to 2.")
946
+
947
+ def test_validate_max_size(self) -> None:
948
+ field = fields.SetField(max_size=2)
949
+ with self.assertRaises(ValueError) as context:
950
+ field.validate({1, 2, 3})
951
+ self.assertEqual(str(context.exception), "The size for field 'None' must be less than or equal to 2.")
952
+
953
+
954
+ class StrFieldTestCase(TestCase, BaseMappingObjectTestCase):
955
+ field: fields.StrField = fields.StrField
956
+ choices: set = {'a', 'b', 'c'}
957
+ value_invalid_type: int = 123
958
+ value_init: str = 'a'
959
+ value_update: str = 'b'
960
+
961
+ def test_attr_type(self) -> None:
962
+ field = fields.StrField()
963
+ self.assertEqual(field.field_type, str)
964
+
965
+ def test_min_size_default_value(self) -> None:
966
+ field = fields.StrField()
967
+ self.assertEqual(field.min_size, 0)
968
+
969
+ def test_max_size_default_value(self) -> None:
970
+ field = fields.StrField()
971
+ self.assertEqual(field.max_size, maxsize)
972
+
973
+ def test_validate_min_size(self) -> None:
974
+ field = fields.StrField(min_size=2)
975
+ with self.assertRaises(ValueError) as context:
976
+ field.validate('a')
977
+ self.assertEqual(str(context.exception), "The size for field 'None' must be greater than or equal to 2.")
978
+
979
+ def test_validate_max_size(self) -> None:
980
+ field = fields.StrField(max_size=2)
981
+ with self.assertRaises(ValueError) as context:
982
+ field.validate('abc')
983
+ self.assertEqual(str(context.exception), "The size for field 'None' must be less than or equal to 2.")
984
+
985
+ def test_validate_regex(self) -> None:
986
+ field = fields.StrField(regex=r'^[a-z]+$')
987
+ with self.assertRaises(ValueError) as context:
988
+ field.validate('abc123')
989
+ self.assertEqual(
990
+ str(context.exception), "The value 'abc123' for field 'None' must match with this regex: ^[a-z]+$"
991
+ )
992
+
993
+
994
+ class RegexFieldTestCase(TestCase, BaseMappingObjectTestCase):
995
+ field: fields.RegexField = fields.RegexField
996
+ choices: set = None
997
+ value_invalid_type: int = 123
998
+ value_init: re.Pattern = re.compile('a')
999
+ value_update: re.Pattern = re.compile('b')
1000
+
1001
+ def test_attr_type(self) -> None:
1002
+ field = fields.RegexField()
1003
+ self.assertEqual(field.field_type, re.Pattern)
1004
+
1005
+ def test_clean_value(self) -> None:
1006
+ field = fields.RegexField()
1007
+ value = field.clean_value(r'^[a-z]+$')
1008
+ self.assertIsInstance(value, re.Pattern)
1009
+ self.assertEqual(value.pattern, r'^[a-z]+$')
1010
+
1011
+
1012
+ class TupleFieldTestCase(TestCase, BaseMappingObjectTestCase):
1013
+ field: fields.TupleField = fields.TupleField
1014
+ choices: set = (1, 2)
1015
+ value_invalid_type: int = 123
1016
+ value_init: tuple = (1,)
1017
+ value_update: tuple = (2,)
1018
+
1019
+ def test_attr_type(self) -> None:
1020
+ field = fields.TupleField()
1021
+ self.assertEqual(field.field_type, tuple)
1022
+
1023
+ def test_clean_value_list(self) -> None:
1024
+ field = fields.TupleField()
1025
+ value = field.clean_value([1, 2, 3])
1026
+ self.assertIsInstance(value, tuple)
1027
+ self.assertTupleEqual(value, (1, 2, 3))
1028
+
1029
+ def test_clean_value_set(self) -> None:
1030
+ field = fields.TupleField()
1031
+ value = field.clean_value({3, 1, 2})
1032
+ self.assertIsInstance(value, tuple)
1033
+ self.assertTupleEqual(tuple(sorted(value)), (1, 2, 3))
1034
+
1035
+ def test_clean_value_str(self) -> None:
1036
+ field = fields.TupleField()
1037
+ value = field.clean_value('a,b,c')
1038
+ self.assertIsInstance(value, tuple)
1039
+ self.assertTupleEqual(value, ('a', 'b', 'c'))
1040
+
1041
+ def test_clean_value_str_separator(self) -> None:
1042
+ field = fields.TupleField(separator=';')
1043
+ value = field.clean_value('a;b;c')
1044
+ self.assertIsInstance(value, tuple)
1045
+ self.assertTupleEqual(value, ('a', 'b', 'c'))
1046
+
1047
+ def test_invalid_choice_tuple(self) -> None:
1048
+ class TestField(BaseMapping):
1049
+ __validate_fields__: bool = True
1050
+ f1 = self.field(choices=self.choices)
1051
+
1052
+ with self.assertRaises(ValueError) as context:
1053
+ _ = TestField(f1=(3,))
1054
+ self.assertEqual(
1055
+ str(context.exception), f"The value '(3,)' for field 'f1' must be in this list {self.choices}."
1056
+ )
1057
+ obj = TestField()
1058
+ with self.assertRaises(ValueError) as context:
1059
+ obj.f1 = (3,)
1060
+ self.assertEqual(
1061
+ str(context.exception), f"The value '(3,)' for field 'f1' must be in this list {self.choices}."
1062
+ )
1063
+
1064
+ with self.assertRaises(ValueError) as context:
1065
+ obj['f1'] = (3,)
1066
+ self.assertEqual(
1067
+ str(context.exception), f"The value '(3,)' for field 'f1' must be in this list {self.choices}."
1068
+ )
1069
+
1070
+ def test_invalid_choice_tuple_values(self) -> None:
1071
+ class TestField(BaseMapping):
1072
+ __validate_fields__: bool = True
1073
+ f1 = self.field(choices=self.choices)
1074
+
1075
+ with self.assertRaises(ValueError) as context:
1076
+ _ = TestField(f1=(1, 3))
1077
+ self.assertEqual(
1078
+ str(context.exception), f"The value '(1, 3)' for field 'f1' must be in this list {self.choices}."
1079
+ )
1080
+ obj = TestField()
1081
+ with self.assertRaises(ValueError) as context:
1082
+ obj.f1 = (1, 3)
1083
+ self.assertEqual(
1084
+ str(context.exception), f"The value '(1, 3)' for field 'f1' must be in this list {self.choices}."
1085
+ )
1086
+
1087
+ with self.assertRaises(ValueError) as context:
1088
+ obj['f1'] = (1, 3)
1089
+ self.assertEqual(
1090
+ str(context.exception), f"The value '(1, 3)' for field 'f1' must be in this list {self.choices}."
1091
+ )
1092
+
1093
+ def test_min_size_default_value(self) -> None:
1094
+ field = fields.TupleField()
1095
+ self.assertEqual(field.min_size, 0)
1096
+
1097
+ def test_max_size_default_value(self) -> None:
1098
+ field = fields.TupleField()
1099
+ self.assertEqual(field.max_size, maxsize // 8)
1100
+
1101
+ def test_validate_min_size(self) -> None:
1102
+ field = fields.TupleField(min_size=2)
1103
+ with self.assertRaises(ValueError) as context:
1104
+ field.validate((1,))
1105
+ self.assertEqual(str(context.exception), "The size for field 'None' must be greater than or equal to 2.")
1106
+
1107
+ def test_validate_max_size(self) -> None:
1108
+ field = fields.TupleField(max_size=2)
1109
+ with self.assertRaises(ValueError) as context:
1110
+ field.validate((1, 2, 3))
1111
+ self.assertEqual(str(context.exception), "The size for field 'None' must be less than or equal to 2.")
1112
+
1113
+
1114
+ class URLFieldTestCase(TestCase, BaseMappingObjectTestCase):
1115
+ field: fields.URLField = fields.URLField
1116
+ choices: set = {'http://example.com', 'https://example.com'}
1117
+ value_invalid_type: int = 123
1118
+ value_init: str = 'http://example.com'
1119
+ value_update: str = 'https://example.com'
1120
+
1121
+ def test_attr_type(self) -> None:
1122
+ field = fields.URLField()
1123
+ self.assertEqual(field.field_type, str)
1124
+
1125
+ def test_validate_http_url(self) -> None:
1126
+ field = fields.URLField()
1127
+ field.validate('http://exampe.com/path?var=1&var=2')
1128
+
1129
+ def test_validate_https_url(self) -> None:
1130
+ field = fields.URLField()
1131
+ field.validate('https://exampe.com/path?var=1&var=2')
1132
+
1133
+ def test_validate_localhost_url(self) -> None:
1134
+ field = fields.URLField()
1135
+ field.validate('http://localhost:8080/path?var=1&var=2')
1136
+
1137
+ def test_validate_invalid_scheme(self) -> None:
1138
+ field = fields.URLField()
1139
+ with self.assertRaises(ValueError) as context:
1140
+ field.validate('tp://example.com/path?var=1&var=2')
1141
+ self.assertEqual(str(context.exception), 'Key None must be an URL.')
1142
+
1143
+ def test_validate_invalid_domain(self) -> None:
1144
+ field = fields.URLField()
1145
+ with self.assertRaises(ValueError) as context:
1146
+ field.validate('http://example com/path?var=1&var=2')
1147
+ self.assertEqual(str(context.exception), 'Key None must be an URL.')
1148
+
1149
+ def test_validate_invalid_query(self) -> None:
1150
+ field = fields.URLField()
1151
+ with self.assertRaises(ValueError) as context:
1152
+ field.validate('http://localhost:8080/path?var=1 var=2')
1153
+
1154
+ self.assertEqual(str(context.exception), 'Key None must be an URL.')