voluptuous-openapi 0.0.4__tar.gz → 0.0.6__tar.gz
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.
- {voluptuous_openapi-0.0.4/voluptuous_openapi.egg-info → voluptuous_openapi-0.0.6}/PKG-INFO +2 -2
- {voluptuous_openapi-0.0.4 → voluptuous_openapi-0.0.6}/setup.py +2 -2
- {voluptuous_openapi-0.0.4 → voluptuous_openapi-0.0.6}/tests/test_lib.py +211 -8
- voluptuous_openapi-0.0.6/tests/test_validation.py +387 -0
- voluptuous_openapi-0.0.6/voluptuous_openapi/__init__.py +444 -0
- {voluptuous_openapi-0.0.4 → voluptuous_openapi-0.0.6/voluptuous_openapi.egg-info}/PKG-INFO +2 -2
- {voluptuous_openapi-0.0.4 → voluptuous_openapi-0.0.6}/voluptuous_openapi.egg-info/SOURCES.txt +1 -0
- voluptuous_openapi-0.0.4/voluptuous_openapi/__init__.py +0 -251
- {voluptuous_openapi-0.0.4 → voluptuous_openapi-0.0.6}/LICENSE +0 -0
- {voluptuous_openapi-0.0.4 → voluptuous_openapi-0.0.6}/MANIFEST.in +0 -0
- {voluptuous_openapi-0.0.4 → voluptuous_openapi-0.0.6}/README.md +0 -0
- {voluptuous_openapi-0.0.4 → voluptuous_openapi-0.0.6}/setup.cfg +0 -0
- {voluptuous_openapi-0.0.4 → voluptuous_openapi-0.0.6}/voluptuous_openapi.egg-info/dependency_links.txt +0 -0
- {voluptuous_openapi-0.0.4 → voluptuous_openapi-0.0.6}/voluptuous_openapi.egg-info/requires.txt +0 -0
- {voluptuous_openapi-0.0.4 → voluptuous_openapi-0.0.6}/voluptuous_openapi.egg-info/top_level.txt +0 -0
- {voluptuous_openapi-0.0.4 → voluptuous_openapi-0.0.6}/voluptuous_openapi.egg-info/zip-safe +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: voluptuous-openapi
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6
|
|
4
4
|
Summary: Convert voluptuous schemas to OpenAPI Schema object
|
|
5
|
-
Home-page:
|
|
5
|
+
Home-page: https://github.com/home-assistant-libs/voluptuous-openapi
|
|
6
6
|
Author: Denis Shulyaka
|
|
7
7
|
Author-email: Shulyaka@gmail.com
|
|
8
8
|
License: Apache License 2.0
|
|
@@ -2,9 +2,9 @@ from setuptools import setup
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="voluptuous-openapi",
|
|
5
|
-
version="0.0.
|
|
5
|
+
version="0.0.6",
|
|
6
6
|
description="Convert voluptuous schemas to OpenAPI Schema object",
|
|
7
|
-
url="
|
|
7
|
+
url="https://github.com/home-assistant-libs/voluptuous-openapi",
|
|
8
8
|
author="Denis Shulyaka",
|
|
9
9
|
author_email="Shulyaka@gmail.com",
|
|
10
10
|
license="Apache License 2.0",
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
|
+
from typing import Any, TypeVar
|
|
2
3
|
|
|
4
|
+
import pytest
|
|
3
5
|
import voluptuous as vol
|
|
4
|
-
from typing import Any, TypeVar
|
|
5
6
|
|
|
6
|
-
from voluptuous_openapi import UNSUPPORTED, convert
|
|
7
|
+
from voluptuous_openapi import UNSUPPORTED, convert, convert_to_voluptuous
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
def test_int_schema():
|
|
@@ -50,11 +51,18 @@ def test_datetime():
|
|
|
50
51
|
|
|
51
52
|
|
|
52
53
|
def test_in():
|
|
53
|
-
assert {"enum": ["beer", "wine"]} == convert(
|
|
54
|
+
assert {"type": "string", "enum": ["beer", "wine"]} == convert(
|
|
55
|
+
vol.Schema(vol.In(["beer", "wine"]))
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_in_integer():
|
|
60
|
+
assert {"type": "integer", "enum": [1, 2]} == convert(vol.Schema(vol.In([1, 2])))
|
|
54
61
|
|
|
55
62
|
|
|
56
63
|
def test_in_dict():
|
|
57
64
|
assert {
|
|
65
|
+
"type": "string",
|
|
58
66
|
"enum": ["en_US", "zh_CN"],
|
|
59
67
|
} == convert(
|
|
60
68
|
vol.Schema(
|
|
@@ -112,6 +120,19 @@ def test_dict():
|
|
|
112
120
|
vol.Schema({})
|
|
113
121
|
)
|
|
114
122
|
|
|
123
|
+
def string(x: str) -> str:
|
|
124
|
+
return x
|
|
125
|
+
|
|
126
|
+
assert {"type": "object", "additionalProperties": {"type": "string"}} == convert(
|
|
127
|
+
vol.Schema({string: string})
|
|
128
|
+
)
|
|
129
|
+
assert {"type": "object", "additionalProperties": True} == convert(
|
|
130
|
+
vol.Schema(object)
|
|
131
|
+
)
|
|
132
|
+
assert {"type": "object", "additionalProperties": True} == convert(
|
|
133
|
+
vol.Schema({string: object})
|
|
134
|
+
)
|
|
135
|
+
|
|
115
136
|
|
|
116
137
|
def test_tuple():
|
|
117
138
|
assert {"type": "array", "items": {"type": "string"}} == convert(vol.Schema(tuple))
|
|
@@ -221,17 +242,39 @@ def test_custom_serializer():
|
|
|
221
242
|
|
|
222
243
|
|
|
223
244
|
def test_constant():
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
assert {"enum": [
|
|
245
|
+
assert {"type": "boolean", "enum": [True]} == convert(vol.Schema(True))
|
|
246
|
+
assert {"type": "boolean", "enum": [False]} == convert(vol.Schema(False))
|
|
247
|
+
assert {"type": "string", "enum": ["Hello"]} == convert(vol.Schema("Hello"))
|
|
248
|
+
assert {"type": "integer", "enum": [1]} == convert(vol.Schema(1))
|
|
249
|
+
assert {"type": "number", "enum": [1.5]} == convert(vol.Schema(1.5))
|
|
250
|
+
assert {
|
|
251
|
+
"type": "object",
|
|
252
|
+
"nullable": True,
|
|
253
|
+
"description": "Must be null",
|
|
254
|
+
} == convert(vol.Schema(None))
|
|
255
|
+
assert {
|
|
256
|
+
"type": "object",
|
|
257
|
+
"nullable": True,
|
|
258
|
+
"description": "Must be null",
|
|
259
|
+
} == convert(vol.Schema(type(None)))
|
|
227
260
|
|
|
228
261
|
|
|
229
262
|
def test_enum():
|
|
230
|
-
class
|
|
263
|
+
class StringEnum(Enum):
|
|
231
264
|
ONE = "one"
|
|
265
|
+
TWO = "two"
|
|
266
|
+
|
|
267
|
+
assert {"type": "string", "enum": ["one", "two"]} == convert(
|
|
268
|
+
vol.Schema(vol.Coerce(StringEnum))
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
class IntEnum(Enum):
|
|
272
|
+
ONE = 1
|
|
232
273
|
TWO = 2
|
|
233
274
|
|
|
234
|
-
assert {"
|
|
275
|
+
assert {"type": "integer", "enum": [1, 2]} == convert(
|
|
276
|
+
vol.Schema(vol.Coerce(IntEnum))
|
|
277
|
+
)
|
|
235
278
|
|
|
236
279
|
|
|
237
280
|
def test_list():
|
|
@@ -256,12 +299,58 @@ def test_any_of():
|
|
|
256
299
|
vol.Any(float, int)
|
|
257
300
|
)
|
|
258
301
|
|
|
302
|
+
assert {"anyOf": [{"type": "number"}, {"type": "integer"}]} == convert(
|
|
303
|
+
vol.Any(float, int, float, int, int)
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
assert {"type": "object", "additionalProperties": True} == convert(
|
|
307
|
+
vol.Any(float, int, object)
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
assert {"type": "integer", "nullable": True, "enum": [1, 2]} == convert(
|
|
311
|
+
vol.Schema(vol.In([1, 2, None]))
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
assert {"type": "integer", "enum": [1, 2, 3]} == convert(
|
|
315
|
+
vol.Schema(vol.Any(1, 2, 3))
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
assert {
|
|
319
|
+
"anyOf": [{"type": "number"}, {"type": "integer"}, {"type": "string"}]
|
|
320
|
+
} == convert(
|
|
321
|
+
vol.Any(
|
|
322
|
+
vol.Any(float, int), vol.Any(int, float), vol.Any(float, vol.Any(int, str))
|
|
323
|
+
)
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
assert {
|
|
327
|
+
"anyOf": [{"type": "number"}, {"type": "integer"}],
|
|
328
|
+
"nullable": True,
|
|
329
|
+
} == convert(vol.Any(vol.Maybe(float), vol.Maybe(int)))
|
|
330
|
+
|
|
259
331
|
|
|
260
332
|
def test_all_of():
|
|
261
333
|
assert {"allOf": [{"minimum": 5}, {"minimum": 10}]} == convert(
|
|
262
334
|
vol.All(vol.Range(min=5), vol.Range(min=10))
|
|
263
335
|
)
|
|
264
336
|
|
|
337
|
+
assert {"type": "string"} == convert(vol.All(object, str))
|
|
338
|
+
|
|
339
|
+
assert {"type": "object", "additionalProperties": {"type": "string"}} == convert(
|
|
340
|
+
vol.All(object, {str: str})
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
assert {"maximum": 10, "minimum": 5, "type": "number"} == convert(
|
|
344
|
+
vol.All(vol.Range(min=5), vol.Range(max=10))
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
assert {"maximum": 10, "minimum": 5, "type": "number"} == convert(
|
|
348
|
+
vol.All(
|
|
349
|
+
vol.All(vol.Range(min=5), float),
|
|
350
|
+
vol.All(vol.All(vol.Range(max=10), float), float),
|
|
351
|
+
)
|
|
352
|
+
)
|
|
353
|
+
|
|
265
354
|
|
|
266
355
|
def test_key_any():
|
|
267
356
|
assert {
|
|
@@ -286,6 +375,24 @@ def test_key_any():
|
|
|
286
375
|
)
|
|
287
376
|
)
|
|
288
377
|
|
|
378
|
+
assert {
|
|
379
|
+
"properties": {
|
|
380
|
+
"conversation_command": {"type": "string"},
|
|
381
|
+
"hours": {"type": "integer"},
|
|
382
|
+
"minutes": {"type": "integer"},
|
|
383
|
+
"name": {"type": "string"},
|
|
384
|
+
"seconds": {"type": "integer"},
|
|
385
|
+
},
|
|
386
|
+
"required": [],
|
|
387
|
+
"type": "object",
|
|
388
|
+
} == convert(
|
|
389
|
+
{
|
|
390
|
+
vol.Required(vol.Any("hours", "minutes", "seconds")): int,
|
|
391
|
+
vol.Optional("name"): str,
|
|
392
|
+
vol.Optional("conversation_command"): str,
|
|
393
|
+
}
|
|
394
|
+
)
|
|
395
|
+
|
|
289
396
|
|
|
290
397
|
def test_function():
|
|
291
398
|
def validator(data):
|
|
@@ -376,3 +483,99 @@ def test_function():
|
|
|
376
483
|
assert {"type": "object", "additionalProperties": {"type": "integer"}} == convert(
|
|
377
484
|
validator_dict_int
|
|
378
485
|
)
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def test_nested_in_list():
|
|
489
|
+
assert {
|
|
490
|
+
"properties": {
|
|
491
|
+
"drink": {
|
|
492
|
+
"type": "array",
|
|
493
|
+
"items": {"type": "string", "enum": ["beer", "wine"]},
|
|
494
|
+
},
|
|
495
|
+
},
|
|
496
|
+
"required": [],
|
|
497
|
+
"type": "object",
|
|
498
|
+
} == convert(vol.Schema({vol.Optional("drink"): [vol.In(["beer", "wine"])]}))
|
|
499
|
+
|
|
500
|
+
assert {"type": "integer", "enum": [1, 2, 3]} == convert(
|
|
501
|
+
vol.Schema(vol.In([1, 2, 3]))
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def test_reverse_int_schema():
|
|
506
|
+
assert convert_to_voluptuous({"type": "integer"}) == int
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def test_reverse_str_schema():
|
|
510
|
+
assert convert_to_voluptuous({"type": "string"}) == str
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def test_reverse_float_schema():
|
|
514
|
+
assert convert_to_voluptuous({"type": "number"}) == float
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def test_reverse_bool_schema():
|
|
518
|
+
assert convert_to_voluptuous({"type": "boolean"}) == bool
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def test_reverse_datetime():
|
|
522
|
+
validator = convert_to_voluptuous(
|
|
523
|
+
{
|
|
524
|
+
"type": "string",
|
|
525
|
+
"format": "date-time",
|
|
526
|
+
}
|
|
527
|
+
)
|
|
528
|
+
validator("2025-01-01T12:32:55.11Z")
|
|
529
|
+
|
|
530
|
+
with pytest.raises(vol.Invalid):
|
|
531
|
+
validator("2021-01-01")
|
|
532
|
+
with pytest.raises(vol.Invalid):
|
|
533
|
+
validator("abc")
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def test_reverse_unknown_type():
|
|
537
|
+
with pytest.raises(ValueError):
|
|
538
|
+
convert_to_voluptuous({})
|
|
539
|
+
|
|
540
|
+
with pytest.raises(ValueError):
|
|
541
|
+
convert_to_voluptuous({"type": "unknown"})
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def test_convert_to_voluptuous_wrong_type() -> None:
|
|
545
|
+
"""Test calling with the wrong type"""
|
|
546
|
+
|
|
547
|
+
with pytest.raises(ValueError):
|
|
548
|
+
convert_to_voluptuous({"oneOf": ["integer"]})
|
|
549
|
+
|
|
550
|
+
with pytest.raises(ValueError):
|
|
551
|
+
convert_to_voluptuous({"oneOf": "integer"})
|
|
552
|
+
|
|
553
|
+
with pytest.raises(ValueError):
|
|
554
|
+
convert_to_voluptuous("a")
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def test_unsupported_features() -> None:
|
|
558
|
+
"""Test converting a mixed aray type."""
|
|
559
|
+
|
|
560
|
+
with pytest.raises(ValueError):
|
|
561
|
+
convert_to_voluptuous({"type": "integer", "multipleOf": 2})
|
|
562
|
+
|
|
563
|
+
with pytest.raises(ValueError):
|
|
564
|
+
convert_to_voluptuous({"type": "array", "items": {"minItems": 1}})
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def test_mixed_type_list() -> None:
|
|
568
|
+
"""Test converting a mixed aray type."""
|
|
569
|
+
validator = convert_to_voluptuous(
|
|
570
|
+
{"type": "array", "items": {"oneOf": [{"type": "string"}, {"type": "integer"}]}}
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
validator(["a", "b"])
|
|
574
|
+
validator([1, 2])
|
|
575
|
+
validator(["a", 1, "b", 2])
|
|
576
|
+
|
|
577
|
+
with pytest.raises(vol.Invalid):
|
|
578
|
+
validator("abc")
|
|
579
|
+
|
|
580
|
+
with pytest.raises(vol.Invalid):
|
|
581
|
+
validator(123)
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
"""Tests for voluptuous schema and openapi schemas that exercise validation code.
|
|
2
|
+
|
|
3
|
+
Each test in this file defines an equivalent schema in both `openapi` and
|
|
4
|
+
`voluptuous` formats. The schema is then converted to the other format and
|
|
5
|
+
validation code is run against all variations of schema types.
|
|
6
|
+
|
|
7
|
+
The motivation is because voluptuous schemas cannot be introspected directly
|
|
8
|
+
and are tested by exercising with both valid and invalid data.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from collections.abc import Callable, Generator
|
|
12
|
+
import datetime
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
import voluptuous as vol
|
|
16
|
+
import openapi_schema_validator
|
|
17
|
+
from typing import Any
|
|
18
|
+
import logging
|
|
19
|
+
|
|
20
|
+
from voluptuous_openapi import convert, convert_to_voluptuous
|
|
21
|
+
from jsonschema.exceptions import ValidationError
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
_LOGGER = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
# Validator type used to represent a validation function for a specific schema type
|
|
27
|
+
Validator = Callable[[Any], Any]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class InvalidFormat(Exception):
|
|
31
|
+
"""Validation exception thrown on invalid input test data."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def voluptuous_validator(schema: vol.Schema) -> Validator:
|
|
35
|
+
"""Create a Validator for a voluptuous schema."""
|
|
36
|
+
|
|
37
|
+
def validator(data: Any) -> Any:
|
|
38
|
+
try:
|
|
39
|
+
_LOGGER.debug("Validating %s with schema %s", data, schema)
|
|
40
|
+
return schema(data)
|
|
41
|
+
except (vol.Invalid, ValueError) as e:
|
|
42
|
+
raise InvalidFormat(str(e))
|
|
43
|
+
|
|
44
|
+
return validator
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def openapi_validator(schema: dict) -> Any:
|
|
48
|
+
"""Create a Validator for an OpenAPI schema."""
|
|
49
|
+
|
|
50
|
+
def validator(data: Any) -> Any:
|
|
51
|
+
try:
|
|
52
|
+
_LOGGER.debug("Validating %s with schema %s", data, schema)
|
|
53
|
+
openapi_schema_validator.validate(data, schema)
|
|
54
|
+
return data
|
|
55
|
+
except ValidationError as e:
|
|
56
|
+
raise InvalidFormat(str(e))
|
|
57
|
+
|
|
58
|
+
return validator
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# Order of id created by `generate_validators`
|
|
62
|
+
TEST_IDS = ["openapi", "voluptuous", "voluptuous_to_openapi", "openapi_to_voluptuous"]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def generate_validators(
|
|
66
|
+
openapi_schema: dict, voluptuous_schema: vol.Schema
|
|
67
|
+
) -> Generator[Validator]:
|
|
68
|
+
"""Create validation functions for the various schema types."""
|
|
69
|
+
|
|
70
|
+
# Native schema validations
|
|
71
|
+
yield openapi_validator(openapi_schema)
|
|
72
|
+
yield voluptuous_validator(voluptuous_schema)
|
|
73
|
+
|
|
74
|
+
# Converted schema validations
|
|
75
|
+
yield openapi_validator(convert(voluptuous_schema))
|
|
76
|
+
yield voluptuous_validator(convert_to_voluptuous(openapi_schema))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@pytest.mark.parametrize(
|
|
80
|
+
"validator",
|
|
81
|
+
generate_validators(
|
|
82
|
+
{"type": "string"},
|
|
83
|
+
str,
|
|
84
|
+
),
|
|
85
|
+
ids=TEST_IDS,
|
|
86
|
+
)
|
|
87
|
+
def test_string(validator: Validator) -> None:
|
|
88
|
+
"""Test string schema."""
|
|
89
|
+
|
|
90
|
+
validator("hello")
|
|
91
|
+
validator("A" * 10)
|
|
92
|
+
validator("A" * 12)
|
|
93
|
+
validator("123")
|
|
94
|
+
# Note voluptuos coerces everything to string but openapi does not,
|
|
95
|
+
# so not validated here.
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@pytest.mark.parametrize(
|
|
99
|
+
"validator",
|
|
100
|
+
generate_validators(
|
|
101
|
+
{"type": "string", "minLength": 1, "maxLength": 10},
|
|
102
|
+
vol.All(str, vol.Length(min=1, max=10)),
|
|
103
|
+
),
|
|
104
|
+
ids=TEST_IDS,
|
|
105
|
+
)
|
|
106
|
+
def test_string_min_max_length(validator: Validator) -> None:
|
|
107
|
+
"""Test string min and max length."""
|
|
108
|
+
|
|
109
|
+
validator("hello")
|
|
110
|
+
validator("A" * 10)
|
|
111
|
+
|
|
112
|
+
with pytest.raises(InvalidFormat):
|
|
113
|
+
validator(123)
|
|
114
|
+
|
|
115
|
+
with pytest.raises(InvalidFormat):
|
|
116
|
+
validator("")
|
|
117
|
+
|
|
118
|
+
with pytest.raises(InvalidFormat):
|
|
119
|
+
validator("A" * 12)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@pytest.mark.parametrize(
|
|
123
|
+
"validator",
|
|
124
|
+
generate_validators(
|
|
125
|
+
{"type": "integer"},
|
|
126
|
+
int,
|
|
127
|
+
),
|
|
128
|
+
ids=TEST_IDS,
|
|
129
|
+
)
|
|
130
|
+
def test_int(validator: Validator) -> None:
|
|
131
|
+
"""Test int schema."""
|
|
132
|
+
|
|
133
|
+
validator(1)
|
|
134
|
+
validator(10)
|
|
135
|
+
validator(0)
|
|
136
|
+
|
|
137
|
+
with pytest.raises(InvalidFormat):
|
|
138
|
+
validator("abc")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@pytest.mark.parametrize(
|
|
142
|
+
"validator",
|
|
143
|
+
generate_validators(
|
|
144
|
+
{"type": "integer", "minimum": 1, "maximum": 10},
|
|
145
|
+
vol.All(int, vol.Range(min=1, max=10)),
|
|
146
|
+
),
|
|
147
|
+
ids=TEST_IDS,
|
|
148
|
+
)
|
|
149
|
+
def test_int_range(validator: Validator) -> None:
|
|
150
|
+
"""Test an int range"""
|
|
151
|
+
|
|
152
|
+
validator(1)
|
|
153
|
+
validator(10)
|
|
154
|
+
|
|
155
|
+
with pytest.raises(InvalidFormat):
|
|
156
|
+
validator(0)
|
|
157
|
+
|
|
158
|
+
with pytest.raises(InvalidFormat):
|
|
159
|
+
validator(11)
|
|
160
|
+
|
|
161
|
+
with pytest.raises(InvalidFormat):
|
|
162
|
+
validator(5.5)
|
|
163
|
+
|
|
164
|
+
with pytest.raises(InvalidFormat):
|
|
165
|
+
validator("abc")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@pytest.mark.parametrize(
|
|
169
|
+
"validator",
|
|
170
|
+
generate_validators(
|
|
171
|
+
{"type": "number"},
|
|
172
|
+
float,
|
|
173
|
+
),
|
|
174
|
+
ids=TEST_IDS,
|
|
175
|
+
)
|
|
176
|
+
def test_float(validator: Validator) -> None:
|
|
177
|
+
"""Test float schema."""
|
|
178
|
+
|
|
179
|
+
validator(1.0)
|
|
180
|
+
validator(5.5)
|
|
181
|
+
validator(10.0)
|
|
182
|
+
|
|
183
|
+
with pytest.raises(InvalidFormat):
|
|
184
|
+
validator("abc")
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@pytest.mark.parametrize(
|
|
188
|
+
"validator",
|
|
189
|
+
generate_validators(
|
|
190
|
+
{"type": "number", "minimum": 1, "maximum": 10},
|
|
191
|
+
vol.All(float, vol.Range(min=1, max=10)),
|
|
192
|
+
),
|
|
193
|
+
ids=TEST_IDS,
|
|
194
|
+
)
|
|
195
|
+
def test_float_range(validator: Validator) -> None:
|
|
196
|
+
"""Test float range schema."""
|
|
197
|
+
|
|
198
|
+
validator(1.0)
|
|
199
|
+
validator(5.5)
|
|
200
|
+
validator(10.0)
|
|
201
|
+
|
|
202
|
+
with pytest.raises(InvalidFormat):
|
|
203
|
+
validator(0.0)
|
|
204
|
+
|
|
205
|
+
with pytest.raises(InvalidFormat):
|
|
206
|
+
validator(10.1)
|
|
207
|
+
|
|
208
|
+
with pytest.raises(InvalidFormat):
|
|
209
|
+
validator("abc")
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
@pytest.mark.parametrize(
|
|
213
|
+
"validator",
|
|
214
|
+
generate_validators(
|
|
215
|
+
{"type": "string", "pattern": r"^\d{3}-\d{2}-\d{4}$"},
|
|
216
|
+
vol.All(str, vol.Match(r"^\d{3}-\d{2}-\d{4}$")),
|
|
217
|
+
),
|
|
218
|
+
ids=TEST_IDS,
|
|
219
|
+
)
|
|
220
|
+
def test_match_pattern(validator: Validator) -> None:
|
|
221
|
+
"""Test matching a regular expression pattern."""
|
|
222
|
+
|
|
223
|
+
validator("555-10-2020")
|
|
224
|
+
|
|
225
|
+
with pytest.raises(InvalidFormat):
|
|
226
|
+
validator("555-1-2020")
|
|
227
|
+
|
|
228
|
+
with pytest.raises(InvalidFormat):
|
|
229
|
+
validator("555")
|
|
230
|
+
|
|
231
|
+
with pytest.raises(InvalidFormat):
|
|
232
|
+
validator("abc")
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@pytest.mark.parametrize(
|
|
236
|
+
"validator",
|
|
237
|
+
generate_validators(
|
|
238
|
+
{"type": "array", "items": {"type": "string"}},
|
|
239
|
+
vol.All([str]),
|
|
240
|
+
),
|
|
241
|
+
ids=TEST_IDS,
|
|
242
|
+
)
|
|
243
|
+
def test_string_list(validator: Validator) -> None:
|
|
244
|
+
"""Test a list of strings."""
|
|
245
|
+
|
|
246
|
+
validator(["a"])
|
|
247
|
+
validator(["a", "b"])
|
|
248
|
+
|
|
249
|
+
with pytest.raises(InvalidFormat):
|
|
250
|
+
validator("abc")
|
|
251
|
+
|
|
252
|
+
with pytest.raises(InvalidFormat):
|
|
253
|
+
validator(123)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@pytest.mark.parametrize(
|
|
257
|
+
"validator",
|
|
258
|
+
generate_validators(
|
|
259
|
+
{
|
|
260
|
+
"type": "object",
|
|
261
|
+
"properties": {"id": {"type": "integer"}, "name": {"type": "string"}},
|
|
262
|
+
"required": ["id"],
|
|
263
|
+
},
|
|
264
|
+
vol.Schema({vol.Required("id"): int, vol.Optional("name"): str}),
|
|
265
|
+
),
|
|
266
|
+
ids=TEST_IDS,
|
|
267
|
+
)
|
|
268
|
+
def test_object(validator: Validator) -> None:
|
|
269
|
+
"""Test an object."""
|
|
270
|
+
validator({"id": 1, "name": "hello"})
|
|
271
|
+
validator({"id": 1})
|
|
272
|
+
|
|
273
|
+
with pytest.raises(InvalidFormat):
|
|
274
|
+
validator({"id": "abc", "name": "hello"})
|
|
275
|
+
|
|
276
|
+
with pytest.raises(InvalidFormat):
|
|
277
|
+
validator({"name": "hello"})
|
|
278
|
+
|
|
279
|
+
with pytest.raises(InvalidFormat):
|
|
280
|
+
validator("abc")
|
|
281
|
+
|
|
282
|
+
with pytest.raises(InvalidFormat):
|
|
283
|
+
validator(123)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
@pytest.mark.parametrize(
|
|
287
|
+
"validator",
|
|
288
|
+
generate_validators(
|
|
289
|
+
{
|
|
290
|
+
"type": "object",
|
|
291
|
+
"properties": {
|
|
292
|
+
"id": {"type": "integer"},
|
|
293
|
+
"content": {
|
|
294
|
+
"type": "object",
|
|
295
|
+
"properties": {
|
|
296
|
+
"name": {"type": "string"},
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
vol.Schema(
|
|
302
|
+
{
|
|
303
|
+
vol.Required("id"): int,
|
|
304
|
+
vol.Optional("content"): vol.Schema({vol.Optional("name"): str}),
|
|
305
|
+
}
|
|
306
|
+
),
|
|
307
|
+
),
|
|
308
|
+
ids=TEST_IDS,
|
|
309
|
+
)
|
|
310
|
+
def test_nested_object(validator: Validator) -> None:
|
|
311
|
+
"""Test an object nested in an object."""
|
|
312
|
+
validator({"id": 1, "content": {"name": "hello"}})
|
|
313
|
+
validator({"id": 1, "content": {}})
|
|
314
|
+
validator({"id": 1})
|
|
315
|
+
|
|
316
|
+
with pytest.raises(InvalidFormat):
|
|
317
|
+
validator({"id": 1, "content": {"name": 1234}})
|
|
318
|
+
|
|
319
|
+
with pytest.raises(InvalidFormat):
|
|
320
|
+
validator(123)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
@pytest.mark.parametrize(
|
|
324
|
+
"validator",
|
|
325
|
+
generate_validators(
|
|
326
|
+
{
|
|
327
|
+
"type": "object",
|
|
328
|
+
"properties": {"id": {"type": "integer"}},
|
|
329
|
+
"additionalProperties": True,
|
|
330
|
+
},
|
|
331
|
+
vol.Schema(
|
|
332
|
+
{vol.Required("id"): int, vol.Optional("name"): str}, extra=vol.ALLOW_EXTRA
|
|
333
|
+
),
|
|
334
|
+
),
|
|
335
|
+
ids=TEST_IDS,
|
|
336
|
+
)
|
|
337
|
+
def test_allow_extra(validator: Validator) -> None:
|
|
338
|
+
"""Test additional properties are allowed."""
|
|
339
|
+
validator({"id": 1})
|
|
340
|
+
validator({"id": 1, "extra-key": "hello"})
|
|
341
|
+
|
|
342
|
+
with pytest.raises(InvalidFormat):
|
|
343
|
+
validator(123)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
@pytest.mark.parametrize(
|
|
347
|
+
"validator",
|
|
348
|
+
generate_validators(
|
|
349
|
+
{
|
|
350
|
+
"type": "object",
|
|
351
|
+
"properties": {"id": {"type": "integer"}},
|
|
352
|
+
"additionalProperties": False,
|
|
353
|
+
},
|
|
354
|
+
vol.Schema({vol.Required("id"): int, vol.Optional("name"): str}),
|
|
355
|
+
),
|
|
356
|
+
ids=TEST_IDS,
|
|
357
|
+
)
|
|
358
|
+
def test_no_extra(validator: Validator) -> None:
|
|
359
|
+
"""Test additional properties are not allowed."""
|
|
360
|
+
validator({"id": 1})
|
|
361
|
+
|
|
362
|
+
# TODO: Note this does not currently fail when converting from openapi to voluptuous because
|
|
363
|
+
# additionalProperties: False is not set. Fix that then uncomment here.
|
|
364
|
+
# with pytest.raises(InvalidFormat):
|
|
365
|
+
# validator({"id": 1, "extra-key": "hello"})
|
|
366
|
+
|
|
367
|
+
with pytest.raises(InvalidFormat):
|
|
368
|
+
validator(123)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
@pytest.mark.parametrize(
|
|
372
|
+
"validator",
|
|
373
|
+
generate_validators(
|
|
374
|
+
{"oneOf": [{"type": "string"}, {"type": "integer"}]},
|
|
375
|
+
vol.Any(str, int),
|
|
376
|
+
),
|
|
377
|
+
ids=TEST_IDS,
|
|
378
|
+
)
|
|
379
|
+
def test_one_of(validator: Validator) -> None:
|
|
380
|
+
"""Test oneOf multiple types."""
|
|
381
|
+
|
|
382
|
+
validator(1)
|
|
383
|
+
validator(10)
|
|
384
|
+
validator("hello")
|
|
385
|
+
|
|
386
|
+
with pytest.raises(InvalidFormat):
|
|
387
|
+
validator(1.4)
|