werkzeug

Форк
0
/
test_datastructures.py 
1217 строк · 38.6 Кб
1
import io
2
import pickle
3
import tempfile
4
import typing as t
5
from contextlib import contextmanager
6
from copy import copy
7
from copy import deepcopy
8

9
import pytest
10

11
from werkzeug import datastructures as ds
12
from werkzeug import http
13
from werkzeug.exceptions import BadRequestKeyError
14

15

16
class TestNativeItermethods:
17
    def test_basic(self):
18
        class StupidDict:
19
            def keys(self, multi=1):
20
                return iter(["a", "b", "c"] * multi)
21

22
            def values(self, multi=1):
23
                return iter([1, 2, 3] * multi)
24

25
            def items(self, multi=1):
26
                return iter(
27
                    zip(iter(self.keys(multi=multi)), iter(self.values(multi=multi)))
28
                )
29

30
        d = StupidDict()
31
        expected_keys = ["a", "b", "c"]
32
        expected_values = [1, 2, 3]
33
        expected_items = list(zip(expected_keys, expected_values))
34

35
        assert list(d.keys()) == expected_keys
36
        assert list(d.values()) == expected_values
37
        assert list(d.items()) == expected_items
38

39
        assert list(d.keys(2)) == expected_keys * 2
40
        assert list(d.values(2)) == expected_values * 2
41
        assert list(d.items(2)) == expected_items * 2
42

43

44
class _MutableMultiDictTests:
45
    storage_class: t.Type["ds.MultiDict"]
46

47
    def test_pickle(self):
48
        cls = self.storage_class
49

50
        def create_instance(module=None):
51
            if module is None:
52
                d = cls()
53
            else:
54
                old = cls.__module__
55
                cls.__module__ = module
56
                d = cls()
57
                cls.__module__ = old
58
            d.setlist(b"foo", [1, 2, 3, 4])
59
            d.setlist(b"bar", b"foo bar baz".split())
60
            return d
61

62
        for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
63
            d = create_instance()
64
            s = pickle.dumps(d, protocol)
65
            ud = pickle.loads(s)
66
            assert type(ud) == type(d)  # noqa: E721
67
            assert ud == d
68
            alternative = pickle.dumps(create_instance("werkzeug"), protocol)
69
            assert pickle.loads(alternative) == d
70
            ud[b"newkey"] = b"bla"
71
            assert ud != d
72

73
    def test_multidict_dict_interop(self):
74
        # https://github.com/pallets/werkzeug/pull/2043
75
        md = self.storage_class([("a", 1), ("a", 2)])
76
        assert dict(md)["a"] != [1, 2]
77
        assert dict(md)["a"] == 1
78
        assert dict(md) == {**md} == {"a": 1}
79

80
    def test_basic_interface(self):
81
        md = self.storage_class()
82
        assert isinstance(md, dict)
83

84
        mapping = [
85
            ("a", 1),
86
            ("b", 2),
87
            ("a", 2),
88
            ("d", 3),
89
            ("a", 1),
90
            ("a", 3),
91
            ("d", 4),
92
            ("c", 3),
93
        ]
94
        md = self.storage_class(mapping)
95

96
        # simple getitem gives the first value
97
        assert md["a"] == 1
98
        assert md["c"] == 3
99
        with pytest.raises(KeyError):
100
            md["e"]
101
        assert md.get("a") == 1
102

103
        # list getitem
104
        assert md.getlist("a") == [1, 2, 1, 3]
105
        assert md.getlist("d") == [3, 4]
106
        # do not raise if key not found
107
        assert md.getlist("x") == []
108

109
        # simple setitem overwrites all values
110
        md["a"] = 42
111
        assert md.getlist("a") == [42]
112

113
        # list setitem
114
        md.setlist("a", [1, 2, 3])
115
        assert md["a"] == 1
116
        assert md.getlist("a") == [1, 2, 3]
117

118
        # verify that it does not change original lists
119
        l1 = [1, 2, 3]
120
        md.setlist("a", l1)
121
        del l1[:]
122
        assert md["a"] == 1
123

124
        # setdefault, setlistdefault
125
        assert md.setdefault("u", 23) == 23
126
        assert md.getlist("u") == [23]
127
        del md["u"]
128

129
        md.setlist("u", [-1, -2])
130

131
        # delitem
132
        del md["u"]
133
        with pytest.raises(KeyError):
134
            md["u"]
135
        del md["d"]
136
        assert md.getlist("d") == []
137

138
        # keys, values, items, lists
139
        assert list(sorted(md.keys())) == ["a", "b", "c"]
140
        assert list(sorted(md.keys())) == ["a", "b", "c"]
141

142
        assert list(sorted(md.values())) == [1, 2, 3]
143
        assert list(sorted(md.values())) == [1, 2, 3]
144

145
        assert list(sorted(md.items())) == [("a", 1), ("b", 2), ("c", 3)]
146
        assert list(sorted(md.items(multi=True))) == [
147
            ("a", 1),
148
            ("a", 2),
149
            ("a", 3),
150
            ("b", 2),
151
            ("c", 3),
152
        ]
153
        assert list(sorted(md.items())) == [("a", 1), ("b", 2), ("c", 3)]
154
        assert list(sorted(md.items(multi=True))) == [
155
            ("a", 1),
156
            ("a", 2),
157
            ("a", 3),
158
            ("b", 2),
159
            ("c", 3),
160
        ]
161

162
        assert list(sorted(md.lists())) == [("a", [1, 2, 3]), ("b", [2]), ("c", [3])]
163
        assert list(sorted(md.lists())) == [("a", [1, 2, 3]), ("b", [2]), ("c", [3])]
164

165
        # copy method
166
        c = md.copy()
167
        assert c["a"] == 1
168
        assert c.getlist("a") == [1, 2, 3]
169

170
        # copy method 2
171
        c = copy(md)
172
        assert c["a"] == 1
173
        assert c.getlist("a") == [1, 2, 3]
174

175
        # deepcopy method
176
        c = md.deepcopy()
177
        assert c["a"] == 1
178
        assert c.getlist("a") == [1, 2, 3]
179

180
        # deepcopy method 2
181
        c = deepcopy(md)
182
        assert c["a"] == 1
183
        assert c.getlist("a") == [1, 2, 3]
184

185
        # update with a multidict
186
        od = self.storage_class([("a", 4), ("a", 5), ("y", 0)])
187
        md.update(od)
188
        assert md.getlist("a") == [1, 2, 3, 4, 5]
189
        assert md.getlist("y") == [0]
190

191
        # update with a regular dict
192
        md = c
193
        od = {"a": 4, "y": 0}
194
        md.update(od)
195
        assert md.getlist("a") == [1, 2, 3, 4]
196
        assert md.getlist("y") == [0]
197

198
        # pop, poplist, popitem, popitemlist
199
        assert md.pop("y") == 0
200
        assert "y" not in md
201
        assert md.poplist("a") == [1, 2, 3, 4]
202
        assert "a" not in md
203
        assert md.poplist("missing") == []
204

205
        # remaining: b=2, c=3
206
        popped = md.popitem()
207
        assert popped in [("b", 2), ("c", 3)]
208
        popped = md.popitemlist()
209
        assert popped in [("b", [2]), ("c", [3])]
210

211
        # type conversion
212
        md = self.storage_class({"a": "4", "b": ["2", "3"]})
213
        assert md.get("a", type=int) == 4
214
        assert md.getlist("b", type=int) == [2, 3]
215

216
        # repr
217
        md = self.storage_class([("a", 1), ("a", 2), ("b", 3)])
218
        assert "('a', 1)" in repr(md)
219
        assert "('a', 2)" in repr(md)
220
        assert "('b', 3)" in repr(md)
221

222
        # add and getlist
223
        md.add("c", "42")
224
        md.add("c", "23")
225
        assert md.getlist("c") == ["42", "23"]
226
        md.add("c", "blah")
227
        assert md.getlist("c", type=int) == [42, 23]
228

229
        # setdefault
230
        md = self.storage_class()
231
        md.setdefault("x", []).append(42)
232
        md.setdefault("x", []).append(23)
233
        assert md["x"] == [42, 23]
234

235
        # to dict
236
        md = self.storage_class()
237
        md["foo"] = 42
238
        md.add("bar", 1)
239
        md.add("bar", 2)
240
        assert md.to_dict() == {"foo": 42, "bar": 1}
241
        assert md.to_dict(flat=False) == {"foo": [42], "bar": [1, 2]}
242

243
        # popitem from empty dict
244
        with pytest.raises(KeyError):
245
            self.storage_class().popitem()
246

247
        with pytest.raises(KeyError):
248
            self.storage_class().popitemlist()
249

250
        # key errors are of a special type
251
        with pytest.raises(BadRequestKeyError):
252
            self.storage_class()[42]
253

254
        # setlist works
255
        md = self.storage_class()
256
        md["foo"] = 42
257
        md.setlist("foo", [1, 2])
258
        assert md.getlist("foo") == [1, 2]
259

260

261
class _ImmutableDictTests:
262
    storage_class: t.Type[dict]
263

264
    def test_follows_dict_interface(self):
265
        cls = self.storage_class
266

267
        data = {"foo": 1, "bar": 2, "baz": 3}
268
        d = cls(data)
269

270
        assert d["foo"] == 1
271
        assert d["bar"] == 2
272
        assert d["baz"] == 3
273
        assert sorted(d.keys()) == ["bar", "baz", "foo"]
274
        assert "foo" in d
275
        assert "foox" not in d
276
        assert len(d) == 3
277

278
    def test_copies_are_mutable(self):
279
        cls = self.storage_class
280
        immutable = cls({"a": 1})
281
        with pytest.raises(TypeError):
282
            immutable.pop("a")
283

284
        mutable = immutable.copy()
285
        mutable.pop("a")
286
        assert "a" in immutable
287
        assert mutable is not immutable
288
        assert copy(immutable) is immutable
289

290
    def test_dict_is_hashable(self):
291
        cls = self.storage_class
292
        immutable = cls({"a": 1, "b": 2})
293
        immutable2 = cls({"a": 2, "b": 2})
294
        x = {immutable}
295
        assert immutable in x
296
        assert immutable2 not in x
297
        x.discard(immutable)
298
        assert immutable not in x
299
        assert immutable2 not in x
300
        x.add(immutable2)
301
        assert immutable not in x
302
        assert immutable2 in x
303
        x.add(immutable)
304
        assert immutable in x
305
        assert immutable2 in x
306

307

308
class TestImmutableTypeConversionDict(_ImmutableDictTests):
309
    storage_class = ds.ImmutableTypeConversionDict
310

311

312
class TestImmutableMultiDict(_ImmutableDictTests):
313
    storage_class = ds.ImmutableMultiDict
314

315
    def test_multidict_is_hashable(self):
316
        cls = self.storage_class
317
        immutable = cls({"a": [1, 2], "b": 2})
318
        immutable2 = cls({"a": [1], "b": 2})
319
        x = {immutable}
320
        assert immutable in x
321
        assert immutable2 not in x
322
        x.discard(immutable)
323
        assert immutable not in x
324
        assert immutable2 not in x
325
        x.add(immutable2)
326
        assert immutable not in x
327
        assert immutable2 in x
328
        x.add(immutable)
329
        assert immutable in x
330
        assert immutable2 in x
331

332

333
class TestImmutableDict(_ImmutableDictTests):
334
    storage_class = ds.ImmutableDict
335

336

337
class TestImmutableOrderedMultiDict(_ImmutableDictTests):
338
    storage_class = ds.ImmutableOrderedMultiDict
339

340
    def test_ordered_multidict_is_hashable(self):
341
        a = self.storage_class([("a", 1), ("b", 1), ("a", 2)])
342
        b = self.storage_class([("a", 1), ("a", 2), ("b", 1)])
343
        assert hash(a) != hash(b)
344

345

346
class TestMultiDict(_MutableMultiDictTests):
347
    storage_class = ds.MultiDict
348

349
    def test_multidict_pop(self):
350
        def make_d():
351
            return self.storage_class({"foo": [1, 2, 3, 4]})
352

353
        d = make_d()
354
        assert d.pop("foo") == 1
355
        assert not d
356
        d = make_d()
357
        assert d.pop("foo", 32) == 1
358
        assert not d
359
        d = make_d()
360
        assert d.pop("foos", 32) == 32
361
        assert d
362

363
        with pytest.raises(KeyError):
364
            d.pop("foos")
365

366
    def test_multidict_pop_raise_badrequestkeyerror_for_empty_list_value(self):
367
        mapping = [("a", "b"), ("a", "c")]
368
        md = self.storage_class(mapping)
369

370
        md.setlistdefault("empty", [])
371

372
        with pytest.raises(KeyError):
373
            md.pop("empty")
374

375
    def test_multidict_popitem_raise_badrequestkeyerror_for_empty_list_value(self):
376
        mapping = []
377
        md = self.storage_class(mapping)
378

379
        md.setlistdefault("empty", [])
380

381
        with pytest.raises(BadRequestKeyError):
382
            md.popitem()
383

384
    def test_setlistdefault(self):
385
        md = self.storage_class()
386
        assert md.setlistdefault("u", [-1, -2]) == [-1, -2]
387
        assert md.getlist("u") == [-1, -2]
388
        assert md["u"] == -1
389

390
    def test_iter_interfaces(self):
391
        mapping = [
392
            ("a", 1),
393
            ("b", 2),
394
            ("a", 2),
395
            ("d", 3),
396
            ("a", 1),
397
            ("a", 3),
398
            ("d", 4),
399
            ("c", 3),
400
        ]
401
        md = self.storage_class(mapping)
402
        assert list(zip(md.keys(), md.listvalues())) == list(md.lists())
403
        assert list(zip(md, md.listvalues())) == list(md.lists())
404
        assert list(zip(md.keys(), md.listvalues())) == list(md.lists())
405

406
    def test_getitem_raise_badrequestkeyerror_for_empty_list_value(self):
407
        mapping = [("a", "b"), ("a", "c")]
408
        md = self.storage_class(mapping)
409

410
        md.setlistdefault("empty", [])
411

412
        with pytest.raises(KeyError):
413
            md["empty"]
414

415

416
class TestOrderedMultiDict(_MutableMultiDictTests):
417
    storage_class = ds.OrderedMultiDict
418

419
    def test_ordered_interface(self):
420
        cls = self.storage_class
421

422
        d = cls()
423
        assert not d
424
        d.add("foo", "bar")
425
        assert len(d) == 1
426
        d.add("foo", "baz")
427
        assert len(d) == 1
428
        assert list(d.items()) == [("foo", "bar")]
429
        assert list(d) == ["foo"]
430
        assert list(d.items(multi=True)) == [("foo", "bar"), ("foo", "baz")]
431
        del d["foo"]
432
        assert not d
433
        assert len(d) == 0
434
        assert list(d) == []
435

436
        d.update([("foo", 1), ("foo", 2), ("bar", 42)])
437
        d.add("foo", 3)
438
        assert d.getlist("foo") == [1, 2, 3]
439
        assert d.getlist("bar") == [42]
440
        assert list(d.items()) == [("foo", 1), ("bar", 42)]
441

442
        expected = ["foo", "bar"]
443

444
        assert list(d.keys()) == expected
445
        assert list(d) == expected
446
        assert list(d.keys()) == expected
447

448
        assert list(d.items(multi=True)) == [
449
            ("foo", 1),
450
            ("foo", 2),
451
            ("bar", 42),
452
            ("foo", 3),
453
        ]
454
        assert len(d) == 2
455

456
        assert d.pop("foo") == 1
457
        assert d.pop("blafasel", None) is None
458
        assert d.pop("blafasel", 42) == 42
459
        assert len(d) == 1
460
        assert d.poplist("bar") == [42]
461
        assert not d
462

463
        assert d.get("missingkey") is None
464

465
        d.add("foo", 42)
466
        d.add("foo", 23)
467
        d.add("bar", 2)
468
        d.add("foo", 42)
469
        assert d == ds.MultiDict(d)
470
        id = self.storage_class(d)
471
        assert d == id
472
        d.add("foo", 2)
473
        assert d != id
474

475
        d.update({"blah": [1, 2, 3]})
476
        assert d["blah"] == 1
477
        assert d.getlist("blah") == [1, 2, 3]
478

479
        # setlist works
480
        d = self.storage_class()
481
        d["foo"] = 42
482
        d.setlist("foo", [1, 2])
483
        assert d.getlist("foo") == [1, 2]
484
        with pytest.raises(BadRequestKeyError):
485
            d.pop("missing")
486

487
        with pytest.raises(BadRequestKeyError):
488
            d["missing"]
489

490
        # popping
491
        d = self.storage_class()
492
        d.add("foo", 23)
493
        d.add("foo", 42)
494
        d.add("foo", 1)
495
        assert d.popitem() == ("foo", 23)
496
        with pytest.raises(BadRequestKeyError):
497
            d.popitem()
498
        assert not d
499

500
        d.add("foo", 23)
501
        d.add("foo", 42)
502
        d.add("foo", 1)
503
        assert d.popitemlist() == ("foo", [23, 42, 1])
504

505
        with pytest.raises(BadRequestKeyError):
506
            d.popitemlist()
507

508
        # Unhashable
509
        d = self.storage_class()
510
        d.add("foo", 23)
511
        pytest.raises(TypeError, hash, d)
512

513
    def test_iterables(self):
514
        a = ds.MultiDict((("key_a", "value_a"),))
515
        b = ds.MultiDict((("key_b", "value_b"),))
516
        ab = ds.CombinedMultiDict((a, b))
517

518
        assert sorted(ab.lists()) == [("key_a", ["value_a"]), ("key_b", ["value_b"])]
519
        assert sorted(ab.listvalues()) == [["value_a"], ["value_b"]]
520
        assert sorted(ab.keys()) == ["key_a", "key_b"]
521

522
        assert sorted(ab.lists()) == [("key_a", ["value_a"]), ("key_b", ["value_b"])]
523
        assert sorted(ab.listvalues()) == [["value_a"], ["value_b"]]
524
        assert sorted(ab.keys()) == ["key_a", "key_b"]
525

526
    def test_get_description(self):
527
        data = ds.OrderedMultiDict()
528

529
        with pytest.raises(BadRequestKeyError) as exc_info:
530
            data["baz"]
531

532
        assert "baz" not in exc_info.value.get_description()
533
        exc_info.value.show_exception = True
534
        assert "baz" in exc_info.value.get_description()
535

536
        with pytest.raises(BadRequestKeyError) as exc_info:
537
            data.pop("baz")
538

539
        exc_info.value.show_exception = True
540
        assert "baz" in exc_info.value.get_description()
541
        exc_info.value.args = ()
542
        assert "baz" not in exc_info.value.get_description()
543

544

545
class TestTypeConversionDict:
546
    storage_class = ds.TypeConversionDict
547

548
    def test_value_conversion(self):
549
        d = self.storage_class(foo="1")
550
        assert d.get("foo", type=int) == 1
551

552
    def test_return_default_when_conversion_is_not_possible(self):
553
        d = self.storage_class(foo="bar", baz=None)
554
        assert d.get("foo", default=-1, type=int) == -1
555
        assert d.get("baz", default=-1, type=int) == -1
556

557
    def test_propagate_exceptions_in_conversion(self):
558
        d = self.storage_class(foo="bar")
559
        switch = {"a": 1}
560
        with pytest.raises(KeyError):
561
            d.get("foo", type=lambda x: switch[x])
562

563

564
class TestCombinedMultiDict:
565
    storage_class = ds.CombinedMultiDict
566

567
    def test_basic_interface(self):
568
        d1 = ds.MultiDict([("foo", "1")])
569
        d2 = ds.MultiDict([("bar", "2"), ("bar", "3")])
570
        d = self.storage_class([d1, d2])
571

572
        # lookup
573
        assert d["foo"] == "1"
574
        assert d["bar"] == "2"
575
        assert d.getlist("bar") == ["2", "3"]
576

577
        assert sorted(d.items()) == [("bar", "2"), ("foo", "1")]
578
        assert sorted(d.items(multi=True)) == [("bar", "2"), ("bar", "3"), ("foo", "1")]
579
        assert "missingkey" not in d
580
        assert "foo" in d
581

582
        # type lookup
583
        assert d.get("foo", type=int) == 1
584
        assert d.getlist("bar", type=int) == [2, 3]
585

586
        # get key errors for missing stuff
587
        with pytest.raises(KeyError):
588
            d["missing"]
589

590
        # make sure that they are immutable
591
        with pytest.raises(TypeError):
592
            d["foo"] = "blub"
593

594
        # copies are mutable
595
        d = d.copy()
596
        d["foo"] = "blub"
597

598
        # make sure lists merges
599
        md1 = ds.MultiDict((("foo", "bar"), ("foo", "baz")))
600
        md2 = ds.MultiDict((("foo", "blafasel"),))
601
        x = self.storage_class((md1, md2))
602
        assert list(x.lists()) == [("foo", ["bar", "baz", "blafasel"])]
603

604
        # make sure dicts are created properly
605
        assert x.to_dict() == {"foo": "bar"}
606
        assert x.to_dict(flat=False) == {"foo": ["bar", "baz", "blafasel"]}
607

608
    def test_length(self):
609
        d1 = ds.MultiDict([("foo", "1")])
610
        d2 = ds.MultiDict([("bar", "2")])
611
        assert len(d1) == len(d2) == 1
612
        d = self.storage_class([d1, d2])
613
        assert len(d) == 2
614
        d1.clear()
615
        assert len(d1) == 0
616
        assert len(d) == 1
617

618

619
class TestHeaders:
620
    storage_class = ds.Headers
621

622
    def test_basic_interface(self):
623
        headers = self.storage_class()
624
        headers.add("Content-Type", "text/plain")
625
        headers.add("X-Foo", "bar")
626
        assert "x-Foo" in headers
627
        assert "Content-type" in headers
628

629
        with pytest.raises(ValueError):
630
            headers.add("X-Example", "foo\r\n bar")
631

632
        headers["Content-Type"] = "foo/bar"
633
        assert headers["Content-Type"] == "foo/bar"
634
        assert len(headers.getlist("Content-Type")) == 1
635

636
        # list conversion
637
        assert headers.to_wsgi_list() == [("Content-Type", "foo/bar"), ("X-Foo", "bar")]
638
        assert str(headers) == "Content-Type: foo/bar\r\nX-Foo: bar\r\n\r\n"
639
        assert str(self.storage_class()) == "\r\n"
640

641
        # extended add
642
        headers.add("Content-Disposition", "attachment", filename="foo")
643
        assert headers["Content-Disposition"] == "attachment; filename=foo"
644

645
        headers.add("x", "y", z='"')
646
        assert headers["x"] == r'y; z="\""'
647

648
        # string conversion
649
        headers.add("a", 1)
650
        assert headers["a"] == "1"
651

652
    def test_defaults_and_conversion(self):
653
        # defaults
654
        headers = self.storage_class(
655
            [
656
                ("Content-Type", "text/plain"),
657
                ("X-Foo", "bar"),
658
                ("X-Bar", "1"),
659
                ("X-Bar", "2"),
660
            ]
661
        )
662
        assert headers.getlist("x-bar") == ["1", "2"]
663
        assert headers.get("x-Bar") == "1"
664
        assert headers.get("Content-Type") == "text/plain"
665

666
        assert headers.setdefault("X-Foo", "nope") == "bar"
667
        assert headers.setdefault("X-Bar", "nope") == "1"
668
        assert headers.setdefault("X-Baz", "quux") == "quux"
669
        assert headers.setdefault("X-Baz", "nope") == "quux"
670
        headers.pop("X-Baz")
671

672
        # newlines are not allowed in values
673
        with pytest.raises(ValueError):
674
            self.storage_class([("X-Example", "foo\r\n bar")])
675

676
        # type conversion
677
        assert headers.get("x-bar", type=int) == 1
678
        assert headers.getlist("x-bar", type=int) == [1, 2]
679

680
        # list like operations
681
        assert headers[0] == ("Content-Type", "text/plain")
682
        assert headers[:1] == self.storage_class([("Content-Type", "text/plain")])
683
        del headers[:2]
684
        del headers[-1]
685
        assert headers == self.storage_class([("X-Bar", "1")])
686

687
    def test_copying(self):
688
        a = self.storage_class([("foo", "bar")])
689
        b = a.copy()
690
        a.add("foo", "baz")
691
        assert a.getlist("foo") == ["bar", "baz"]
692
        assert b.getlist("foo") == ["bar"]
693

694
    def test_popping(self):
695
        headers = self.storage_class([("a", 1)])
696
        # headers object expect string values. If a non string value
697
        # is passed, it tries converting it to a string
698
        assert headers.pop("a") == "1"
699
        assert headers.pop("b", "2") == "2"
700

701
        with pytest.raises(KeyError):
702
            headers.pop("c")
703

704
    def test_set_arguments(self):
705
        a = self.storage_class()
706
        a.set("Content-Disposition", "useless")
707
        a.set("Content-Disposition", "attachment", filename="foo")
708
        assert a["Content-Disposition"] == "attachment; filename=foo"
709

710
    def test_reject_newlines(self):
711
        h = self.storage_class()
712

713
        for variation in "foo\nbar", "foo\r\nbar", "foo\rbar":
714
            with pytest.raises(ValueError):
715
                h["foo"] = variation
716
            with pytest.raises(ValueError):
717
                h.add("foo", variation)
718
            with pytest.raises(ValueError):
719
                h.add("foo", "test", option=variation)
720
            with pytest.raises(ValueError):
721
                h.set("foo", variation)
722
            with pytest.raises(ValueError):
723
                h.set("foo", "test", option=variation)
724

725
    def test_slicing(self):
726
        # there's nothing wrong with these being native strings
727
        # Headers doesn't care about the data types
728
        h = self.storage_class()
729
        h.set("X-Foo-Poo", "bleh")
730
        h.set("Content-Type", "application/whocares")
731
        h.set("X-Forwarded-For", "192.168.0.123")
732
        h[:] = [(k, v) for k, v in h if k.startswith("X-")]
733
        assert list(h) == [("X-Foo-Poo", "bleh"), ("X-Forwarded-For", "192.168.0.123")]
734

735
    def test_extend(self):
736
        h = self.storage_class([("a", "0"), ("b", "1"), ("c", "2")])
737
        h.extend(ds.Headers([("a", "3"), ("a", "4")]))
738
        assert h.getlist("a") == ["0", "3", "4"]
739
        h.extend(b=["5", "6"])
740
        assert h.getlist("b") == ["1", "5", "6"]
741
        h.extend({"c": "7", "d": ["8", "9"]}, c="10")
742
        assert h.getlist("c") == ["2", "7", "10"]
743
        assert h.getlist("d") == ["8", "9"]
744

745
        with pytest.raises(TypeError):
746
            h.extend({"x": "x"}, {"x": "x"})
747

748
    def test_update(self):
749
        h = self.storage_class([("a", "0"), ("b", "1"), ("c", "2")])
750
        h.update(ds.Headers([("a", "3"), ("a", "4")]))
751
        assert h.getlist("a") == ["3", "4"]
752
        h.update(b=["5", "6"])
753
        assert h.getlist("b") == ["5", "6"]
754
        h.update({"c": "7", "d": ["8", "9"]})
755
        assert h.getlist("c") == ["7"]
756
        assert h.getlist("d") == ["8", "9"]
757
        h.update({"c": "10"}, c="11")
758
        assert h.getlist("c") == ["11"]
759

760
        with pytest.raises(TypeError):
761
            h.extend({"x": "x"}, {"x": "x"})
762

763
    def test_setlist(self):
764
        h = self.storage_class([("a", "0"), ("b", "1"), ("c", "2")])
765
        h.setlist("b", ["3", "4"])
766
        assert h[1] == ("b", "3")
767
        assert h[-1] == ("b", "4")
768
        h.setlist("b", [])
769
        assert "b" not in h
770
        h.setlist("d", ["5"])
771
        assert h["d"] == "5"
772

773
    def test_setlistdefault(self):
774
        h = self.storage_class([("a", "0"), ("b", "1"), ("c", "2")])
775
        assert h.setlistdefault("a", ["3"]) == ["0"]
776
        assert h.setlistdefault("d", ["4", "5"]) == ["4", "5"]
777

778
    def test_to_wsgi_list(self):
779
        h = self.storage_class()
780
        h.set("Key", "Value")
781
        for key, value in h.to_wsgi_list():
782
            assert key == "Key"
783
            assert value == "Value"
784

785
    def test_equality(self):
786
        # test equality, given keys are case insensitive
787
        h1 = self.storage_class()
788
        h1.add("X-Foo", "foo")
789
        h1.add("X-Bar", "bah")
790
        h1.add("X-Bar", "humbug")
791

792
        h2 = self.storage_class()
793
        h2.add("x-foo", "foo")
794
        h2.add("x-bar", "bah")
795
        h2.add("x-bar", "humbug")
796

797
        assert h1 == h2
798

799

800
class TestEnvironHeaders:
801
    storage_class = ds.EnvironHeaders
802

803
    def test_basic_interface(self):
804
        # this happens in multiple WSGI servers because they
805
        # use a vary naive way to convert the headers;
806
        broken_env = {
807
            "HTTP_CONTENT_TYPE": "text/html",
808
            "CONTENT_TYPE": "text/html",
809
            "HTTP_CONTENT_LENGTH": "0",
810
            "CONTENT_LENGTH": "0",
811
            "HTTP_ACCEPT": "*",
812
            "wsgi.version": (1, 0),
813
        }
814
        headers = self.storage_class(broken_env)
815
        assert headers
816
        assert len(headers) == 3
817
        assert sorted(headers) == [
818
            ("Accept", "*"),
819
            ("Content-Length", "0"),
820
            ("Content-Type", "text/html"),
821
        ]
822
        assert not self.storage_class({"wsgi.version": (1, 0)})
823
        assert len(self.storage_class({"wsgi.version": (1, 0)})) == 0
824
        assert 42 not in headers
825

826
    def test_skip_empty_special_vars(self):
827
        env = {"HTTP_X_FOO": "42", "CONTENT_TYPE": "", "CONTENT_LENGTH": ""}
828
        headers = self.storage_class(env)
829
        assert dict(headers) == {"X-Foo": "42"}
830

831
        env = {"HTTP_X_FOO": "42", "CONTENT_TYPE": "", "CONTENT_LENGTH": "0"}
832
        headers = self.storage_class(env)
833
        assert dict(headers) == {"X-Foo": "42", "Content-Length": "0"}
834

835
    def test_return_type_is_str(self):
836
        headers = self.storage_class({"HTTP_FOO": "\xe2\x9c\x93"})
837
        assert headers["Foo"] == "\xe2\x9c\x93"
838
        assert next(iter(headers)) == ("Foo", "\xe2\x9c\x93")
839

840

841
class TestHeaderSet:
842
    storage_class = ds.HeaderSet
843

844
    def test_basic_interface(self):
845
        hs = self.storage_class()
846
        hs.add("foo")
847
        hs.add("bar")
848
        assert "Bar" in hs
849
        assert hs.find("foo") == 0
850
        assert hs.find("BAR") == 1
851
        assert hs.find("baz") < 0
852
        hs.discard("missing")
853
        hs.discard("foo")
854
        assert hs.find("foo") < 0
855
        assert hs.find("bar") == 0
856

857
        with pytest.raises(IndexError):
858
            hs.index("missing")
859

860
        assert hs.index("bar") == 0
861
        assert hs
862
        hs.clear()
863
        assert not hs
864

865

866
class TestImmutableList:
867
    storage_class = ds.ImmutableList
868

869
    def test_list_hashable(self):
870
        data = (1, 2, 3, 4)
871
        store = self.storage_class(data)
872
        assert hash(data) == hash(store)
873
        assert data != store
874

875

876
def make_call_asserter(func=None):
877
    """Utility to assert a certain number of function calls.
878

879
    :param func: Additional callback for each function call.
880

881
    .. code-block:: python
882
        assert_calls, func = make_call_asserter()
883
        with assert_calls(2):
884
            func()
885
            func()
886
    """
887
    calls = [0]
888

889
    @contextmanager
890
    def asserter(count, msg=None):
891
        calls[0] = 0
892
        yield
893
        assert calls[0] == count
894

895
    def wrapped(*args, **kwargs):
896
        calls[0] += 1
897
        if func is not None:
898
            return func(*args, **kwargs)
899

900
    return asserter, wrapped
901

902

903
class TestCallbackDict:
904
    storage_class = ds.CallbackDict
905

906
    def test_callback_dict_reads(self):
907
        assert_calls, func = make_call_asserter()
908
        initial = {"a": "foo", "b": "bar"}
909
        dct = self.storage_class(initial=initial, on_update=func)
910
        with assert_calls(0, "callback triggered by read-only method"):
911
            # read-only methods
912
            dct["a"]
913
            dct.get("a")
914
            pytest.raises(KeyError, lambda: dct["x"])
915
            assert "a" in dct
916
            list(iter(dct))
917
            dct.copy()
918
        with assert_calls(0, "callback triggered without modification"):
919
            # methods that may write but don't
920
            dct.pop("z", None)
921
            dct.setdefault("a")
922

923
    def test_callback_dict_writes(self):
924
        assert_calls, func = make_call_asserter()
925
        initial = {"a": "foo", "b": "bar"}
926
        dct = self.storage_class(initial=initial, on_update=func)
927
        with assert_calls(8, "callback not triggered by write method"):
928
            # always-write methods
929
            dct["z"] = 123
930
            dct["z"] = 123  # must trigger again
931
            del dct["z"]
932
            dct.pop("b", None)
933
            dct.setdefault("x")
934
            dct.popitem()
935
            dct.update([])
936
            dct.clear()
937
        with assert_calls(0, "callback triggered by failed del"):
938
            pytest.raises(KeyError, lambda: dct.__delitem__("x"))
939
        with assert_calls(0, "callback triggered by failed pop"):
940
            pytest.raises(KeyError, lambda: dct.pop("x"))
941

942

943
class TestCacheControl:
944
    def test_repr(self):
945
        cc = ds.RequestCacheControl([("max-age", "0"), ("private", "True")])
946
        assert repr(cc) == "<RequestCacheControl max-age='0' private='True'>"
947

948
    def test_set_none(self):
949
        cc = ds.ResponseCacheControl([("max-age", "0")])
950
        assert cc.no_cache is None
951
        cc.no_cache = None
952
        assert cc.no_cache is None
953
        cc.no_cache = False
954
        assert cc.no_cache is False
955

956
    def test_no_transform(self):
957
        cc = ds.RequestCacheControl([("no-transform", None)])
958
        assert cc.no_transform is True
959
        cc = ds.RequestCacheControl()
960
        assert cc.no_transform is False
961

962
    def test_min_fresh(self):
963
        cc = ds.RequestCacheControl([("min-fresh", "0")])
964
        assert cc.min_fresh == 0
965
        cc = ds.RequestCacheControl([("min-fresh", None)])
966
        assert cc.min_fresh is None
967
        cc = ds.RequestCacheControl()
968
        assert cc.min_fresh is None
969

970
    def test_must_understand(self):
971
        cc = ds.ResponseCacheControl([("must-understand", None)])
972
        assert cc.must_understand is True
973
        cc = ds.ResponseCacheControl()
974
        assert cc.must_understand is False
975

976

977
class TestContentSecurityPolicy:
978
    def test_construct(self):
979
        csp = ds.ContentSecurityPolicy([("font-src", "'self'"), ("media-src", "*")])
980
        assert csp.font_src == "'self'"
981
        assert csp.media_src == "*"
982
        policies = [policy.strip() for policy in csp.to_header().split(";")]
983
        assert "font-src 'self'" in policies
984
        assert "media-src *" in policies
985

986
    def test_properties(self):
987
        csp = ds.ContentSecurityPolicy()
988
        csp.default_src = "* 'self' quart.com"
989
        csp.img_src = "'none'"
990
        policies = [policy.strip() for policy in csp.to_header().split(";")]
991
        assert "default-src * 'self' quart.com" in policies
992
        assert "img-src 'none'" in policies
993

994

995
class TestAccept:
996
    storage_class = ds.Accept
997

998
    def test_accept_basic(self):
999
        accept = self.storage_class(
1000
            [("tinker", 0), ("tailor", 0.333), ("soldier", 0.667), ("sailor", 1)]
1001
        )
1002
        # check __getitem__ on indices
1003
        assert accept[3] == ("tinker", 0)
1004
        assert accept[2] == ("tailor", 0.333)
1005
        assert accept[1] == ("soldier", 0.667)
1006
        assert accept[0], ("sailor", 1)
1007
        # check __getitem__ on string
1008
        assert accept["tinker"] == 0
1009
        assert accept["tailor"] == 0.333
1010
        assert accept["soldier"] == 0.667
1011
        assert accept["sailor"] == 1
1012
        assert accept["spy"] == 0
1013
        # check quality method
1014
        assert accept.quality("tinker") == 0
1015
        assert accept.quality("tailor") == 0.333
1016
        assert accept.quality("soldier") == 0.667
1017
        assert accept.quality("sailor") == 1
1018
        assert accept.quality("spy") == 0
1019
        # check __contains__
1020
        assert "sailor" in accept
1021
        assert "spy" not in accept
1022
        # check index method
1023
        assert accept.index("tinker") == 3
1024
        assert accept.index("tailor") == 2
1025
        assert accept.index("soldier") == 1
1026
        assert accept.index("sailor") == 0
1027
        with pytest.raises(ValueError):
1028
            accept.index("spy")
1029
        # check find method
1030
        assert accept.find("tinker") == 3
1031
        assert accept.find("tailor") == 2
1032
        assert accept.find("soldier") == 1
1033
        assert accept.find("sailor") == 0
1034
        assert accept.find("spy") == -1
1035
        # check to_header method
1036
        assert accept.to_header() == "sailor,soldier;q=0.667,tailor;q=0.333,tinker;q=0"
1037
        # check best_match method
1038
        assert (
1039
            accept.best_match(["tinker", "tailor", "soldier", "sailor"], default=None)
1040
            == "sailor"
1041
        )
1042
        assert (
1043
            accept.best_match(["tinker", "tailor", "soldier"], default=None)
1044
            == "soldier"
1045
        )
1046
        assert accept.best_match(["tinker", "tailor"], default=None) == "tailor"
1047
        assert accept.best_match(["tinker"], default=None) is None
1048
        assert accept.best_match(["tinker"], default="x") == "x"
1049

1050
    def test_accept_wildcard(self):
1051
        accept = self.storage_class([("*", 0), ("asterisk", 1)])
1052
        assert "*" in accept
1053
        assert accept.best_match(["asterisk", "star"], default=None) == "asterisk"
1054
        assert accept.best_match(["star"], default=None) is None
1055

1056
    def test_accept_keep_order(self):
1057
        accept = self.storage_class([("*", 1)])
1058
        assert accept.best_match(["alice", "bob"]) == "alice"
1059
        assert accept.best_match(["bob", "alice"]) == "bob"
1060
        accept = self.storage_class([("alice", 1), ("bob", 1)])
1061
        assert accept.best_match(["alice", "bob"]) == "alice"
1062
        assert accept.best_match(["bob", "alice"]) == "bob"
1063

1064
    def test_accept_wildcard_specificity(self):
1065
        accept = self.storage_class([("asterisk", 0), ("star", 0.5), ("*", 1)])
1066
        assert accept.best_match(["star", "asterisk"], default=None) == "star"
1067
        assert accept.best_match(["asterisk", "star"], default=None) == "star"
1068
        assert accept.best_match(["asterisk", "times"], default=None) == "times"
1069
        assert accept.best_match(["asterisk"], default=None) is None
1070

1071
    def test_accept_equal_quality(self):
1072
        accept = self.storage_class([("a", 1), ("b", 1)])
1073
        assert accept.best == "a"
1074

1075

1076
class TestMIMEAccept:
1077
    @pytest.mark.parametrize(
1078
        ("values", "matches", "default", "expect"),
1079
        [
1080
            ([("text/*", 1)], ["text/html"], None, "text/html"),
1081
            ([("text/*", 1)], ["image/png"], "text/plain", "text/plain"),
1082
            ([("text/*", 1)], ["image/png"], None, None),
1083
            (
1084
                [("*/*", 1), ("text/html", 1)],
1085
                ["image/png", "text/html"],
1086
                None,
1087
                "text/html",
1088
            ),
1089
            (
1090
                [("*/*", 1), ("text/html", 1)],
1091
                ["image/png", "text/plain"],
1092
                None,
1093
                "image/png",
1094
            ),
1095
            (
1096
                [("*/*", 1), ("text/html", 1), ("image/*", 1)],
1097
                ["image/png", "text/html"],
1098
                None,
1099
                "text/html",
1100
            ),
1101
            (
1102
                [("*/*", 1), ("text/html", 1), ("image/*", 1)],
1103
                ["text/plain", "image/png"],
1104
                None,
1105
                "image/png",
1106
            ),
1107
            (
1108
                [("text/html", 1), ("text/html; level=1", 1)],
1109
                ["text/html;level=1"],
1110
                None,
1111
                "text/html;level=1",
1112
            ),
1113
        ],
1114
    )
1115
    def test_mime_accept(self, values, matches, default, expect):
1116
        accept = ds.MIMEAccept(values)
1117
        match = accept.best_match(matches, default=default)
1118
        assert match == expect
1119

1120

1121
class TestLanguageAccept:
1122
    @pytest.mark.parametrize(
1123
        ("values", "matches", "default", "expect"),
1124
        (
1125
            ([("en-us", 1)], ["en"], None, "en"),
1126
            ([("en", 1)], ["en_US"], None, "en_US"),
1127
            ([("en-GB", 1)], ["en-US"], None, None),
1128
            ([("de_AT", 1), ("de", 0.9)], ["en"], None, None),
1129
            ([("de_AT", 1), ("de", 0.9), ("en-US", 0.8)], ["de", "en"], None, "de"),
1130
            ([("de_AT", 0.9), ("en-US", 1)], ["en"], None, "en"),
1131
            ([("en-us", 1)], ["en-us"], None, "en-us"),
1132
            ([("en-us", 1)], ["en-us", "en"], None, "en-us"),
1133
            ([("en-GB", 1)], ["en-US", "en"], "en-US", "en"),
1134
            ([("de_AT", 1)], ["en-US", "en"], "en-US", "en-US"),
1135
            ([("aus-EN", 1)], ["aus"], None, "aus"),
1136
            ([("aus", 1)], ["aus-EN"], None, "aus-EN"),
1137
        ),
1138
    )
1139
    def test_best_match_fallback(self, values, matches, default, expect):
1140
        accept = ds.LanguageAccept(values)
1141
        best = accept.best_match(matches, default=default)
1142
        assert best == expect
1143

1144

1145
class TestFileStorage:
1146
    storage_class = ds.FileStorage
1147

1148
    def test_mimetype_always_lowercase(self):
1149
        file_storage = self.storage_class(content_type="APPLICATION/JSON")
1150
        assert file_storage.mimetype == "application/json"
1151

1152
    @pytest.mark.parametrize("data", [io.StringIO("one\ntwo"), io.BytesIO(b"one\ntwo")])
1153
    def test_bytes_proper_sentinel(self, data):
1154
        # iterate over new lines and don't enter an infinite loop
1155
        storage = self.storage_class(data)
1156
        idx = -1
1157

1158
        for idx, _line in enumerate(storage):
1159
            assert idx < 2
1160

1161
        assert idx == 1
1162

1163
    @pytest.mark.parametrize("stream", (tempfile.SpooledTemporaryFile, io.BytesIO))
1164
    def test_proxy_can_access_stream_attrs(self, stream):
1165
        """``SpooledTemporaryFile`` doesn't implement some of
1166
        ``IOBase``. Ensure that ``FileStorage`` can still access the
1167
        attributes from the backing file object.
1168

1169
        https://github.com/pallets/werkzeug/issues/1344
1170
        https://github.com/python/cpython/pull/3249
1171
        """
1172
        file_storage = self.storage_class(stream=stream())
1173

1174
        for name in ("fileno", "writable", "readable", "seekable"):
1175
            assert hasattr(file_storage, name)
1176

1177
        file_storage.close()
1178

1179
    def test_save_to_pathlib_dst(self, tmp_path):
1180
        src = tmp_path / "src.txt"
1181
        src.write_text("test")
1182
        dst = tmp_path / "dst.txt"
1183

1184
        with src.open("rb") as f:
1185
            storage = self.storage_class(f)
1186
            storage.save(dst)
1187

1188
        assert dst.read_text() == "test"
1189

1190
    def test_save_to_bytes_io(self):
1191
        storage = self.storage_class(io.BytesIO(b"one\ntwo"))
1192
        dst = io.BytesIO()
1193
        storage.save(dst)
1194
        assert dst.getvalue() == b"one\ntwo"
1195

1196
    def test_save_to_file(self, tmp_path):
1197
        path = tmp_path / "file.data"
1198
        storage = self.storage_class(io.BytesIO(b"one\ntwo"))
1199
        with path.open("wb") as dst:
1200
            storage.save(dst)
1201
        with path.open("rb") as src:
1202
            assert src.read() == b"one\ntwo"
1203

1204

1205
@pytest.mark.parametrize("ranges", ([(0, 1), (-5, None)], [(5, None)]))
1206
def test_range_to_header(ranges):
1207
    header = ds.Range("byes", ranges).to_header()
1208
    r = http.parse_range_header(header)
1209
    assert r.ranges == ranges
1210

1211

1212
@pytest.mark.parametrize(
1213
    "ranges", ([(0, 0)], [(None, 1)], [(1, 0)], [(0, 1), (-5, 10)])
1214
)
1215
def test_range_validates_ranges(ranges):
1216
    with pytest.raises(ValueError):
1217
        ds.Range("bytes", ranges)
1218

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.