1"""Tests for marshmallow.validate"""
2
3import re
4import pytest
5
6from marshmallow import validate, ValidationError
7
8
9@pytest.mark.parametrize(
10    "valid_url",
11    [
12        "http://example.org",
13        "https://example.org",
14        "ftp://example.org",
15        "ftps://example.org",
16        "http://example.co.jp",
17        "http://www.example.com/a%C2%B1b",
18        "http://www.example.com/~username/",
19        "http://info.example.com/?fred",
20        "http://xn--mgbh0fb.xn--kgbechtv/",
21        "http://example.com/blue/red%3Fand+green",
22        "http://www.example.com/?array%5Bkey%5D=value",
23        "http://xn--rsum-bpad.example.org/",
24        "http://123.45.67.8/",
25        "http://123.45.67.8:8329/",
26        "http://[2001:db8::ff00:42]:8329",
27        "http://[2001::1]:8329",
28        "http://www.example.com:8000/foo",
29        "http://user@example.com",
30        "http://user:pass@example.com",
31    ],
32)
33def test_url_absolute_valid(valid_url):
34    validator = validate.URL(relative=False)
35    assert validator(valid_url) == valid_url
36
37
38@pytest.mark.parametrize(
39    "invalid_url",
40    [
41        "http:///example.com/",
42        "https:///example.com/",
43        "https://example.org\\",
44        "https://example.org\n",
45        "ftp:///example.com/",
46        "ftps:///example.com/",
47        "http//example.org",
48        "http:///",
49        "http:/example.org",
50        "foo://example.org",
51        "../icons/logo.gif",
52        "http://2001:db8::ff00:42:8329",
53        "http://[192.168.1.1]:8329",
54        "abc",
55        "..",
56        "/",
57        " ",
58        "",
59        None,
60    ],
61)
62def test_url_absolute_invalid(invalid_url):
63    validator = validate.URL(relative=False)
64    with pytest.raises(ValidationError):
65        validator(invalid_url)
66
67
68@pytest.mark.parametrize(
69    "valid_url",
70    [
71        "http://example.org",
72        "http://123.45.67.8/",
73        "http://example.com/foo/bar/../baz",
74        "https://example.com/../icons/logo.gif",
75        "http://example.com/./icons/logo.gif",
76        "ftp://example.com/../../../../g",
77        "http://example.com/g?y/./x",
78    ],
79)
80def test_url_relative_valid(valid_url):
81    validator = validate.URL(relative=True)
82    assert validator(valid_url) == valid_url
83
84
85@pytest.mark.parametrize(  # noqa: W605
86    "invalid_url",
87    [
88        "http//example.org",
89        "http://example.org\n",
90        "suppliers.html",
91        "../icons/logo.gif",
92        "icons/logo.gif",
93        "../.../g",
94        "...",
95        "\\",
96        " ",
97        "",
98        None,
99    ],
100)
101def test_url_relative_invalid(invalid_url):
102    validator = validate.URL(relative=True)
103    with pytest.raises(ValidationError):
104        validator(invalid_url)
105
106
107@pytest.mark.parametrize(
108    "valid_url",
109    [
110        "http://example.org",
111        "http://123.45.67.8/",
112        "http://example",
113        "http://example.",
114        "http://example:80",
115        "http://user.name:pass.word@example",
116        "http://example/foo/bar",
117    ],
118)
119def test_url_dont_require_tld_valid(valid_url):
120    validator = validate.URL(require_tld=False)
121    assert validator(valid_url) == valid_url
122
123
124@pytest.mark.parametrize(
125    "invalid_url",
126    [
127        "http//example",
128        "http://example\n",
129        "http://.example.org",
130        "http:///foo/bar",
131        "http:// /foo/bar",
132        "",
133        None,
134    ],
135)
136def test_url_dont_require_tld_invalid(invalid_url):
137    validator = validate.URL(require_tld=False)
138    with pytest.raises(ValidationError):
139        validator(invalid_url)
140
141
142def test_url_custom_scheme():
143    validator = validate.URL()
144    # By default, ws not allowed
145    url = "ws://test.test"
146    with pytest.raises(ValidationError):
147        validator(url)
148
149    validator = validate.URL(schemes={"http", "https", "ws"})
150    assert validator(url) == url
151
152
153def test_url_relative_and_custom_schemes():
154    validator = validate.URL(relative=True)
155    # By default, ws not allowed
156    url = "ws://test.test"
157    with pytest.raises(ValidationError):
158        validator(url)
159
160    validator = validate.URL(relative=True, schemes={"http", "https", "ws"})
161    assert validator(url) == url
162
163
164def test_url_custom_message():
165    validator = validate.URL(error="{input} ain't an URL")
166    with pytest.raises(ValidationError, match="invalid ain't an URL"):
167        validator("invalid")
168
169
170def test_url_repr():
171    assert repr(
172        validate.URL(relative=False, error=None)
173    ) == "<URL(relative=False, error={!r})>".format("Not a valid URL.")
174    assert repr(
175        validate.URL(relative=True, error="foo")
176    ) == "<URL(relative=True, error={!r})>".format("foo")
177
178
179@pytest.mark.parametrize(
180    "valid_email",
181    [
182        "niceandsimple@example.com",
183        "NiCeAnDsImPlE@eXaMpLe.CoM",
184        "very.common@example.com",
185        "a.little.lengthy.but.fine@a.iana-servers.net",
186        "disposable.style.email.with+symbol@example.com",
187        '"very.unusual.@.unusual.com"@example.com',
188        "!#$%&'*+-/=?^_`{}|~@example.org",
189        "niceandsimple@[64.233.160.0]",
190        "niceandsimple@localhost",
191        "josé@blah.com",
192        "δοκ.ιμή@παράδειγμα.δοκιμή",
193    ],
194)
195def test_email_valid(valid_email):
196    validator = validate.Email()
197    assert validator(valid_email) == valid_email
198
199
200@pytest.mark.parametrize(
201    "invalid_email",
202    [
203        "niceandsimple\n@example.com",
204        "NiCeAnDsImPlE@eXaMpLe.CoM\n",
205        'a"b(c)d,e:f;g<h>i[j\\k]l@example.com',
206        'just"not"right@example.com',
207        'this is"not\allowed@example.com',
208        'this\\ still\\"not\\\\allowed@example.com',
209        '"much.more unusual"@example.com',
210        '"very.(),:;<>[]".VERY."very@\\ "very".unusual"@strange.example.com',
211        '" "@example.org',
212        "user@example",
213        "@nouser.com",
214        "example.com",
215        "user",
216        "",
217        None,
218    ],
219)
220def test_email_invalid(invalid_email):
221    validator = validate.Email()
222    with pytest.raises(ValidationError):
223        validator(invalid_email)
224
225
226def test_email_custom_message():
227    validator = validate.Email(error="{input} is not an email addy.")
228    with pytest.raises(ValidationError, match="invalid is not an email addy."):
229        validator("invalid")
230
231
232def test_email_repr():
233    assert repr(validate.Email(error=None)) == "<Email(error={!r})>".format(
234        "Not a valid email address."
235    )
236    assert repr(validate.Email(error="foo")) == "<Email(error={!r})>".format("foo")
237
238
239def test_range_min():
240    assert validate.Range(1, 2)(1) == 1
241    assert validate.Range(0)(1) == 1
242    assert validate.Range()(1) == 1
243    assert validate.Range(min_inclusive=False, max_inclusive=False)(1) == 1
244    assert validate.Range(1, 1)(1) == 1
245
246    with pytest.raises(ValidationError, match="Must be greater than or equal to 2"):
247        validate.Range(2, 3)(1)
248    with pytest.raises(ValidationError, match="Must be greater than or equal to 2"):
249        validate.Range(2)(1)
250    with pytest.raises(ValidationError, match="Must be greater than 1"):
251        validate.Range(1, 2, min_inclusive=False, max_inclusive=True, error=None)(1)
252    with pytest.raises(ValidationError, match="less than 1"):
253        validate.Range(1, 1, min_inclusive=True, max_inclusive=False, error=None)(1)
254
255
256def test_range_max():
257    assert validate.Range(1, 2)(2) == 2
258    assert validate.Range(None, 2)(2) == 2
259    assert validate.Range()(2) == 2
260    assert validate.Range(min_inclusive=False, max_inclusive=False)(2) == 2
261    assert validate.Range(2, 2)(2) == 2
262
263    with pytest.raises(ValidationError, match="less than or equal to 1"):
264        validate.Range(0, 1)(2)
265    with pytest.raises(ValidationError, match="less than or equal to 1"):
266        validate.Range(None, 1)(2)
267    with pytest.raises(ValidationError, match="less than 2"):
268        validate.Range(1, 2, min_inclusive=True, max_inclusive=False, error=None)(2)
269    with pytest.raises(ValidationError, match="greater than 2"):
270        validate.Range(2, 2, min_inclusive=False, max_inclusive=True, error=None)(2)
271
272
273def test_range_custom_message():
274    v = validate.Range(2, 3, error="{input} is not between {min} and {max}")
275    with pytest.raises(ValidationError, match="1 is not between 2 and 3"):
276        v(1)
277
278    v = validate.Range(2, None, error="{input} is less than {min}")
279    with pytest.raises(ValidationError, match="1 is less than 2"):
280        v(1)
281
282    v = validate.Range(None, 3, error="{input} is greater than {max}")
283    with pytest.raises(ValidationError, match="4 is greater than 3"):
284        v(4)
285
286
287def test_range_repr():
288    assert (
289        repr(
290            validate.Range(
291                min=None, max=None, error=None, min_inclusive=True, max_inclusive=True
292            )
293        )
294        == "<Range(min=None, max=None, min_inclusive=True, max_inclusive=True, error=None)>"  # noqa: B950
295    )
296    assert repr(
297        validate.Range(
298            min=1, max=3, error="foo", min_inclusive=False, max_inclusive=False
299        )
300    ) == "<Range(min=1, max=3, min_inclusive=False, max_inclusive=False, error={!r})>".format(  # noqa: B950
301        "foo"
302    )
303
304
305def test_length_min():
306    assert validate.Length(3, 5)("foo") == "foo"
307    assert validate.Length(3, 5)([1, 2, 3]) == [1, 2, 3]
308    assert validate.Length(0)("a") == "a"
309    assert validate.Length(0)([1]) == [1]
310    assert validate.Length()("") == ""
311    assert validate.Length()([]) == []
312    assert validate.Length(1, 1)("a") == "a"
313    assert validate.Length(1, 1)([1]) == [1]
314
315    with pytest.raises(ValidationError):
316        validate.Length(4, 5)("foo")
317    with pytest.raises(ValidationError):
318        validate.Length(4, 5)([1, 2, 3])
319    with pytest.raises(ValidationError):
320        validate.Length(5)("foo")
321    with pytest.raises(ValidationError):
322        validate.Length(5)([1, 2, 3])
323
324
325def test_length_max():
326    assert validate.Length(1, 3)("foo") == "foo"
327    assert validate.Length(1, 3)([1, 2, 3]) == [1, 2, 3]
328    assert validate.Length(None, 1)("a") == "a"
329    assert validate.Length(None, 1)([1]) == [1]
330    assert validate.Length()("") == ""
331    assert validate.Length()([]) == []
332    assert validate.Length(2, 2)("ab") == "ab"
333    assert validate.Length(2, 2)([1, 2]) == [1, 2]
334
335    with pytest.raises(ValidationError):
336        validate.Length(1, 2)("foo")
337    with pytest.raises(ValidationError):
338        validate.Length(1, 2)([1, 2, 3])
339    with pytest.raises(ValidationError):
340        validate.Length(None, 2)("foo")
341    with pytest.raises(ValidationError):
342        validate.Length(None, 2)([1, 2, 3])
343
344
345def test_length_equal():
346    assert validate.Length(equal=3)("foo") == "foo"
347    assert validate.Length(equal=3)([1, 2, 3]) == [1, 2, 3]
348    assert validate.Length(equal=None)("") == ""
349    assert validate.Length(equal=None)([]) == []
350
351    with pytest.raises(ValidationError):
352        validate.Length(equal=2)("foo")
353    with pytest.raises(ValidationError):
354        validate.Length(equal=2)([1, 2, 3])
355
356    with pytest.raises(ValueError):
357        validate.Length(1, None, equal=3)("foo")
358    with pytest.raises(ValueError):
359        validate.Length(None, 5, equal=3)("foo")
360    with pytest.raises(ValueError):
361        validate.Length(1, 5, equal=3)("foo")
362
363
364def test_length_custom_message():
365    v = validate.Length(5, 6, error="{input} is not between {min} and {max}")
366    with pytest.raises(ValidationError, match="foo is not between 5 and 6"):
367        v("foo")
368
369    v = validate.Length(5, None, error="{input} is shorter than {min}")
370    with pytest.raises(ValidationError, match="foo is shorter than 5"):
371        v("foo")
372
373    v = validate.Length(None, 2, error="{input} is longer than {max}")
374    with pytest.raises(ValidationError, match="foo is longer than 2"):
375        v("foo")
376
377    v = validate.Length(None, None, equal=4, error="{input} does not have {equal}")
378    with pytest.raises(ValidationError, match="foo does not have 4"):
379        v("foo")
380
381
382def test_length_repr():
383    assert (
384        repr(validate.Length(min=None, max=None, error=None, equal=None))
385        == "<Length(min=None, max=None, equal=None, error=None)>"
386    )
387    assert repr(
388        validate.Length(min=1, max=3, error="foo", equal=None)
389    ) == "<Length(min=1, max=3, equal=None, error={!r})>".format("foo")
390    assert repr(
391        validate.Length(min=None, max=None, error="foo", equal=5)
392    ) == "<Length(min=None, max=None, equal=5, error={!r})>".format("foo")
393
394
395def test_equal():
396    assert validate.Equal("a")("a") == "a"
397    assert validate.Equal(1)(1) == 1
398    assert validate.Equal([1])([1]) == [1]
399
400    with pytest.raises(ValidationError):
401        validate.Equal("b")("a")
402    with pytest.raises(ValidationError):
403        validate.Equal(2)(1)
404    with pytest.raises(ValidationError):
405        validate.Equal([2])([1])
406
407
408def test_equal_custom_message():
409    v = validate.Equal("a", error="{input} is not equal to {other}.")
410    with pytest.raises(ValidationError, match="b is not equal to a."):
411        v("b")
412
413
414def test_equal_repr():
415    assert repr(
416        validate.Equal(comparable=123, error=None)
417    ) == "<Equal(comparable=123, error={!r})>".format("Must be equal to {other}.")
418    assert repr(
419        validate.Equal(comparable=123, error="foo")
420    ) == "<Equal(comparable=123, error={!r})>".format("foo")
421
422
423def test_regexp_str():
424    assert validate.Regexp(r"a")("a") == "a"
425    assert validate.Regexp(r"\w")("_") == "_"
426    assert validate.Regexp(r"\s")(" ") == " "
427    assert validate.Regexp(r"1")("1") == "1"
428    assert validate.Regexp(r"[0-9]+")("1") == "1"
429    assert validate.Regexp(r"a", re.IGNORECASE)("A") == "A"
430
431    with pytest.raises(ValidationError):
432        validate.Regexp(r"[0-9]+")("a")
433    with pytest.raises(ValidationError):
434        validate.Regexp(r"[a-z]+")("1")
435    with pytest.raises(ValidationError):
436        validate.Regexp(r"a")("A")
437
438
439def test_regexp_compile():
440    assert validate.Regexp(re.compile(r"a"))("a") == "a"
441    assert validate.Regexp(re.compile(r"\w"))("_") == "_"
442    assert validate.Regexp(re.compile(r"\s"))(" ") == " "
443    assert validate.Regexp(re.compile(r"1"))("1") == "1"
444    assert validate.Regexp(re.compile(r"[0-9]+"))("1") == "1"
445    assert validate.Regexp(re.compile(r"a", re.IGNORECASE))("A") == "A"
446    assert validate.Regexp(re.compile(r"a", re.IGNORECASE), re.IGNORECASE)("A") == "A"
447
448    with pytest.raises(ValidationError):
449        validate.Regexp(re.compile(r"[0-9]+"))("a")
450    with pytest.raises(ValidationError):
451        validate.Regexp(re.compile(r"[a-z]+"))("1")
452    with pytest.raises(ValidationError):
453        validate.Regexp(re.compile(r"a"))("A")
454    with pytest.raises(ValidationError):
455        validate.Regexp(re.compile(r"a"), re.IGNORECASE)("A")
456
457
458def test_regexp_custom_message():
459    rex = r"[0-9]+"
460    v = validate.Regexp(rex, error="{input} does not match {regex}")
461    with pytest.raises(ValidationError, match="a does not match"):
462        v("a")
463
464
465def test_regexp_repr():
466    assert repr(
467        validate.Regexp(regex="abc", flags=0, error=None)
468    ) == "<Regexp(regex={!r}, error={!r})>".format(
469        re.compile("abc"), "String does not match expected pattern."
470    )
471    assert repr(
472        validate.Regexp(regex="abc", flags=re.IGNORECASE, error="foo")
473    ) == "<Regexp(regex={!r}, error={!r})>".format(
474        re.compile("abc", re.IGNORECASE), "foo"
475    )
476
477
478def test_predicate():
479    class Dummy:
480        def _true(self):
481            return True
482
483        def _false(self):
484            return False
485
486        def _list(self):
487            return [1, 2, 3]
488
489        def _empty(self):
490            return []
491
492        def _identity(self, arg):
493            return arg
494
495    d = Dummy()
496
497    assert validate.Predicate("_true")(d) == d
498    assert validate.Predicate("_list")(d) == d
499    assert validate.Predicate("_identity", arg=True)(d) == d
500    assert validate.Predicate("_identity", arg=1)(d) == d
501    assert validate.Predicate("_identity", arg="abc")(d) == d
502
503    with pytest.raises(ValidationError, match="Invalid input."):
504        validate.Predicate("_false")(d)
505    with pytest.raises(ValidationError):
506        validate.Predicate("_empty")(d)
507    with pytest.raises(ValidationError):
508        validate.Predicate("_identity", arg=False)(d)
509    with pytest.raises(ValidationError):
510        validate.Predicate("_identity", arg=0)(d)
511    with pytest.raises(ValidationError):
512        validate.Predicate("_identity", arg="")(d)
513
514
515def test_predicate_custom_message():
516    class Dummy:
517        def _false(self):
518            return False
519
520        def __str__(self):
521            return "Dummy"
522
523    d = Dummy()
524    with pytest.raises(ValidationError, match="Dummy._false is invalid!"):
525        validate.Predicate("_false", error="{input}.{method} is invalid!")(d)
526
527
528def test_predicate_repr():
529    assert repr(
530        validate.Predicate(method="foo", error=None)
531    ) == "<Predicate(method={!r}, kwargs={!r}, error={!r})>".format(
532        "foo", {}, "Invalid input."
533    )
534    assert repr(
535        validate.Predicate(method="foo", error="bar", zoo=1)
536    ) == "<Predicate(method={!r}, kwargs={!r}, error={!r})>".format(
537        "foo", {"zoo": 1}, "bar"
538    )
539
540
541def test_noneof():
542    assert validate.NoneOf([1, 2, 3])(4) == 4
543    assert validate.NoneOf("abc")("d") == "d"
544    assert validate.NoneOf("")([]) == []
545    assert validate.NoneOf([])("") == ""
546    assert validate.NoneOf([])([]) == []
547    assert validate.NoneOf([1, 2, 3])(None) is None
548
549    with pytest.raises(ValidationError, match="Invalid input."):
550        validate.NoneOf([1, 2, 3])(3)
551    with pytest.raises(ValidationError):
552        validate.NoneOf("abc")("c")
553    with pytest.raises(ValidationError):
554        validate.NoneOf([1, 2, None])(None)
555    with pytest.raises(ValidationError):
556        validate.NoneOf("")("")
557
558
559def test_noneof_custom_message():
560    with pytest.raises(ValidationError, match="<not valid>"):
561        validate.NoneOf([1, 2], error="<not valid>")(1)
562
563    none_of = validate.NoneOf([1, 2], error="{input} cannot be one of {values}")
564    with pytest.raises(ValidationError, match="1 cannot be one of 1, 2"):
565        none_of(1)
566
567
568def test_noneof_repr():
569    assert repr(
570        validate.NoneOf(iterable=[1, 2, 3], error=None)
571    ) == "<NoneOf(iterable=[1, 2, 3], error={!r})>".format("Invalid input.")
572    assert repr(
573        validate.NoneOf(iterable=[1, 2, 3], error="foo")
574    ) == "<NoneOf(iterable=[1, 2, 3], error={!r})>".format("foo")
575
576
577def test_oneof():
578    assert validate.OneOf([1, 2, 3])(2) == 2
579    assert validate.OneOf("abc")("b") == "b"
580    assert validate.OneOf("")("") == ""
581    assert validate.OneOf(dict(a=0, b=1))("a") == "a"
582    assert validate.OneOf((1, 2, None))(None) is None
583
584    with pytest.raises(ValidationError, match="Must be one of: 1, 2, 3."):
585        validate.OneOf([1, 2, 3])(4)
586    with pytest.raises(ValidationError):
587        validate.OneOf("abc")("d")
588    with pytest.raises(ValidationError):
589        validate.OneOf((1, 2, 3))(None)
590    with pytest.raises(ValidationError):
591        validate.OneOf([])([])
592    with pytest.raises(ValidationError):
593        validate.OneOf(())(())
594    with pytest.raises(ValidationError):
595        validate.OneOf(dict(a=0, b=1))(0)
596    with pytest.raises(ValidationError):
597        validate.OneOf("123")(1)
598
599
600def test_oneof_options():
601    oneof = validate.OneOf([1, 2, 3], ["one", "two", "three"])
602    expected = [("1", "one"), ("2", "two"), ("3", "three")]
603    assert list(oneof.options()) == expected
604
605    oneof = validate.OneOf([1, 2, 3], ["one", "two"])
606    expected = [("1", "one"), ("2", "two"), ("3", "")]
607    assert list(oneof.options()) == expected
608
609    oneof = validate.OneOf([1, 2], ["one", "two", "three"])
610    expected = [("1", "one"), ("2", "two"), ("", "three")]
611    assert list(oneof.options()) == expected
612
613    oneof = validate.OneOf([1, 2])
614    expected = [("1", ""), ("2", "")]
615    assert list(oneof.options()) == expected
616
617
618def test_oneof_text():
619    oneof = validate.OneOf([1, 2, 3], ["one", "two", "three"])
620    assert oneof.choices_text == "1, 2, 3"
621    assert oneof.labels_text == "one, two, three"
622
623    oneof = validate.OneOf([1], ["one"])
624    assert oneof.choices_text == "1"
625    assert oneof.labels_text == "one"
626
627    oneof = validate.OneOf(dict(a=0, b=1))
628    assert ", ".join(sorted(oneof.choices_text.split(", "))) == "a, b"
629    assert oneof.labels_text == ""
630
631
632def test_oneof_custom_message():
633    oneof = validate.OneOf([1, 2, 3], error="{input} is not one of {choices}")
634    expected = "4 is not one of 1, 2, 3"
635    with pytest.raises(ValidationError):
636        oneof(4)
637    assert expected in str(expected)
638
639    oneof = validate.OneOf(
640        [1, 2, 3], ["one", "two", "three"], error="{input} is not one of {labels}"
641    )
642    expected = "4 is not one of one, two, three"
643    with pytest.raises(ValidationError):
644        oneof(4)
645    assert expected in str(expected)
646
647
648def test_oneof_repr():
649    assert repr(
650        validate.OneOf(choices=[1, 2, 3], labels=None, error=None)
651    ) == "<OneOf(choices=[1, 2, 3], labels=[], error={!r})>".format(
652        "Must be one of: {choices}."
653    )
654    assert repr(
655        validate.OneOf(choices=[1, 2, 3], labels=["a", "b", "c"], error="foo")
656    ) == "<OneOf(choices=[1, 2, 3], labels={!r}, error={!r})>".format(
657        ["a", "b", "c"], "foo"
658    )
659
660
661def test_containsonly_in_list():
662    assert validate.ContainsOnly([])([]) == []
663    assert validate.ContainsOnly([1, 2, 3])([1]) == [1]
664    assert validate.ContainsOnly([1, 1, 2])([1, 1]) == [1, 1]
665    assert validate.ContainsOnly([1, 2, 3])([1, 2]) == [1, 2]
666    assert validate.ContainsOnly([1, 2, 3])([2, 1]) == [2, 1]
667    assert validate.ContainsOnly([1, 2, 3])([1, 2, 3]) == [1, 2, 3]
668    assert validate.ContainsOnly([1, 2, 3])([3, 1, 2]) == [3, 1, 2]
669    assert validate.ContainsOnly([1, 2, 3])([2, 3, 1]) == [2, 3, 1]
670    assert validate.ContainsOnly([1, 2, 3])([1, 2, 3, 1]) == [1, 2, 3, 1]
671    assert validate.ContainsOnly([1, 2, 3])([]) == []
672
673    with pytest.raises(
674        ValidationError,
675        match="One or more of the choices you made was not in: 1, 2, 3.",
676    ):
677        validate.ContainsOnly([1, 2, 3])([4])
678    with pytest.raises(ValidationError):
679        validate.ContainsOnly([])([1])
680
681
682def test_contains_only_unhashable_types():
683    assert validate.ContainsOnly([[1], [2], [3]])([[1]]) == [[1]]
684    assert validate.ContainsOnly([[1], [1], [2]])([[1], [1]]) == [[1], [1]]
685    assert validate.ContainsOnly([[1], [2], [3]])([[1], [2]]) == [[1], [2]]
686    assert validate.ContainsOnly([[1], [2], [3]])([[2], [1]]) == [[2], [1]]
687    assert validate.ContainsOnly([[1], [2], [3]])([[1], [2], [3]]) == [[1], [2], [3]]
688    assert validate.ContainsOnly([[1], [2], [3]])([[3], [1], [2]]) == [[3], [1], [2]]
689    assert validate.ContainsOnly([[1], [2], [3]])([[2], [3], [1]]) == [[2], [3], [1]]
690    assert validate.ContainsOnly([[1], [2], [3]])([]) == []
691
692    with pytest.raises(ValidationError):
693        validate.ContainsOnly([[1], [2], [3]])([[4]])
694    with pytest.raises(ValidationError):
695        validate.ContainsOnly([])([1])
696
697
698def test_containsonly_in_tuple():
699    assert validate.ContainsOnly(())(()) == ()
700    assert validate.ContainsOnly((1, 2, 3))((1,)) == (1,)
701    assert validate.ContainsOnly((1, 1, 2))((1, 1)) == (1, 1)
702    assert validate.ContainsOnly((1, 2, 3))((1, 2)) == (1, 2)
703    assert validate.ContainsOnly((1, 2, 3))((2, 1)) == (2, 1)
704    assert validate.ContainsOnly((1, 2, 3))((1, 2, 3)) == (1, 2, 3)
705    assert validate.ContainsOnly((1, 2, 3))((3, 1, 2)) == (3, 1, 2)
706    assert validate.ContainsOnly((1, 2, 3))((2, 3, 1)) == (2, 3, 1)
707    assert validate.ContainsOnly((1, 2, 3))(()) == tuple()
708    with pytest.raises(ValidationError):
709        validate.ContainsOnly((1, 2, 3))((4,))
710    with pytest.raises(ValidationError):
711        validate.ContainsOnly(())((1,))
712
713
714def test_contains_only_in_string():
715    assert validate.ContainsOnly("")("") == ""
716    assert validate.ContainsOnly("abc")("a") == "a"
717    assert validate.ContainsOnly("aab")("aa") == "aa"
718    assert validate.ContainsOnly("abc")("ab") == "ab"
719    assert validate.ContainsOnly("abc")("ba") == "ba"
720    assert validate.ContainsOnly("abc")("abc") == "abc"
721    assert validate.ContainsOnly("abc")("cab") == "cab"
722    assert validate.ContainsOnly("abc")("bca") == "bca"
723    assert validate.ContainsOnly("abc")("") == ""
724
725    with pytest.raises(ValidationError):
726        validate.ContainsOnly("abc")("d")
727    with pytest.raises(ValidationError):
728        validate.ContainsOnly("")("a")
729
730
731def test_containsonly_custom_message():
732    containsonly = validate.ContainsOnly(
733        [1, 2, 3], error="{input} is not one of {choices}"
734    )
735    expected = "4, 5 is not one of 1, 2, 3"
736    with pytest.raises(ValidationError):
737        containsonly([4, 5])
738    assert expected in str(expected)
739
740    containsonly = validate.ContainsOnly(
741        [1, 2, 3], ["one", "two", "three"], error="{input} is not one of {labels}"
742    )
743    expected = "4, 5 is not one of one, two, three"
744    with pytest.raises(ValidationError):
745        containsonly([4, 5])
746    assert expected in str(expected)
747
748
749def test_containsonly_repr():
750    assert repr(
751        validate.ContainsOnly(choices=[1, 2, 3], labels=None, error=None)
752    ) == "<ContainsOnly(choices=[1, 2, 3], labels=[], error={!r})>".format(
753        "One or more of the choices you made was not in: {choices}."
754    )
755    assert repr(
756        validate.ContainsOnly(choices=[1, 2, 3], labels=["a", "b", "c"], error="foo")
757    ) == "<ContainsOnly(choices=[1, 2, 3], labels={!r}, error={!r})>".format(
758        ["a", "b", "c"], "foo"
759    )
760
761
762def test_containsnoneof_error_message():
763    with pytest.raises(
764        ValidationError, match="One or more of the choices you made was in: 1"
765    ):
766        validate.ContainsNoneOf([1])([1])
767
768    with pytest.raises(
769        ValidationError, match="One or more of the choices you made was in: 1, 2, 3"
770    ):
771        validate.ContainsNoneOf([1, 2, 3])([1])
772
773    with pytest.raises(
774        ValidationError, match="One or more of the choices you made was in: one, two"
775    ):
776        validate.ContainsNoneOf(["one", "two"])(["one"])
777
778    with pytest.raises(
779        ValidationError, match="One or more of the choices you made was in: @, !, &, ?"
780    ):
781        validate.ContainsNoneOf("@!&?")("@")
782
783
784def test_containsnoneof_in_list():
785    assert validate.ContainsNoneOf([])([]) == []
786    assert validate.ContainsNoneOf([])([1, 2, 3]) == [1, 2, 3]
787    assert validate.ContainsNoneOf([4])([1, 2, 3]) == [1, 2, 3]
788    assert validate.ContainsNoneOf([2])([1, 3, 4]) == [1, 3, 4]
789    assert validate.ContainsNoneOf([1, 2, 3])([4]) == [4]
790    assert validate.ContainsNoneOf([4])([1, 1, 1, 1]) == [1, 1, 1, 1]
791
792    with pytest.raises(ValidationError):
793        validate.ContainsNoneOf([1])([1, 2, 3])
794
795    with pytest.raises(ValidationError):
796        validate.ContainsNoneOf([1, 1, 1])([1, 2, 3])
797
798    with pytest.raises(ValidationError):
799        validate.ContainsNoneOf([1, 2])([1, 2])
800
801    with pytest.raises(ValidationError):
802        validate.ContainsNoneOf([1])([1, 1, 1, 1])
803
804
805def test_containsnoneof_unhashable_types():
806    assert validate.ContainsNoneOf([[1], [2], [3]])([]) == []
807    assert validate.ContainsNoneOf([[1], [2], [3]])([[4]]) == [[4]]
808    assert validate.ContainsNoneOf([[1], [2], [3]])([[4], [4]]) == [[4], [4]]
809    assert validate.ContainsNoneOf([[1], [2], [3]])([[4], [5]]) == [[4], [5]]
810
811    with pytest.raises(ValidationError):
812        validate.ContainsNoneOf([[1], [2], [3]])([[1]])
813
814    with pytest.raises(ValidationError):
815        validate.ContainsNoneOf([[1], [2], [3]])([[1], [2]])
816
817    with pytest.raises(ValidationError):
818        validate.ContainsNoneOf([[1], [2], [3]])([[2], [1]])
819
820    with pytest.raises(ValidationError):
821        validate.ContainsNoneOf([[1], [2], [3]])([[1], [2], [3]])
822
823    with pytest.raises(ValidationError):
824        validate.ContainsNoneOf([[1], [2], [3]])([[3], [2], [1]])
825
826    with pytest.raises(ValidationError):
827        validate.ContainsNoneOf([[1], [2], [3]])([[2], [3], [1]])
828
829
830def test_containsnoneof_in_tuple():
831    assert validate.ContainsNoneOf(())(()) == ()
832    assert validate.ContainsNoneOf(())((1, 2, 3)) == (1, 2, 3)
833    assert validate.ContainsNoneOf((4,))((1, 2, 3)) == (1, 2, 3)
834    assert validate.ContainsNoneOf((2,))((1, 3, 4)) == (1, 3, 4)
835    assert validate.ContainsNoneOf((1, 2, 3))((4,)) == (4,)
836    assert validate.ContainsNoneOf((4,))((1, 1, 1, 1)) == (1, 1, 1, 1)
837
838    with pytest.raises(ValidationError):
839        validate.ContainsNoneOf((1,))((1, 2, 3))
840
841    with pytest.raises(ValidationError):
842        validate.ContainsNoneOf((1, 1, 1))((1, 2, 3))
843
844    with pytest.raises(ValidationError):
845        validate.ContainsNoneOf((1, 2))((1, 2))
846
847    with pytest.raises(ValidationError):
848        validate.ContainsNoneOf((1,))((1, 1, 1, 1))
849
850
851def test_containsnoneof_in_string():
852    assert validate.ContainsNoneOf("")("") == ""
853    assert validate.ContainsNoneOf("")("abc") == "abc"
854    assert validate.ContainsNoneOf("d")("abc") == "abc"
855    assert validate.ContainsNoneOf("b")("acd") == "acd"
856    assert validate.ContainsNoneOf("abc")("d") == "d"
857    assert validate.ContainsNoneOf("d")("aaaa") == "aaaa"
858
859    with pytest.raises(ValidationError):
860        validate.ContainsNoneOf("a")("abc")
861
862    with pytest.raises(ValidationError):
863        validate.ContainsNoneOf("aaa")("abc")
864
865    with pytest.raises(ValidationError):
866        validate.ContainsNoneOf("ab")("ab")
867
868    with pytest.raises(ValidationError):
869        validate.ContainsNoneOf("a")("aaaa")
870
871
872def test_containsnoneof_custom_message():
873    validator = validate.ContainsNoneOf(
874        [1, 2, 3], error="{input} was in the banned list: {values}"
875    )
876    expected = "1 was in the banned list: 1, 2, 3"
877    with pytest.raises(ValidationError, match=expected):
878        validator([1])
879
880
881def test_containsnoneof_mixing_types():
882    with pytest.raises(ValidationError):
883        validate.ContainsNoneOf("abc")(["a"])
884
885    with pytest.raises(ValidationError):
886        validate.ContainsNoneOf(["a", "b", "c"])("a")
887
888    with pytest.raises(ValidationError):
889        validate.ContainsNoneOf((1, 2, 3))([1])
890
891    with pytest.raises(ValidationError):
892        validate.ContainsNoneOf([1, 2, 3])((1,))
893
894
895def is_even(value):
896    if value % 2 != 0:
897        raise ValidationError("Not an even value.")
898
899
900def test_and():
901    validator = validate.And(validate.Range(min=0), is_even)
902    assert validator(2)
903    with pytest.raises(ValidationError) as excinfo:
904        validator(-1)
905    errors = excinfo.value.messages
906    assert errors == ["Must be greater than or equal to 0.", "Not an even value."]
907
908    validator_with_composition = validate.And(validator, validate.Range(max=6))
909    assert validator_with_composition(4)
910    with pytest.raises(ValidationError) as excinfo:
911        validator_with_composition(7)
912
913    errors = excinfo.value.messages
914    assert errors == ["Not an even value.", "Must be less than or equal to 6."]
915