Completed
Push — master ( 3b5e94...ee223a )
by Ali-Akber
10s
created

FlaskExtTests.test_retry_after()   A

Complexity

Conditions 4

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
c 1
b 0
f 0
dl 0
loc 19
rs 9.2
1
"""
2
3
"""
4
import json
5
import logging
6
import time
7
import unittest
8
9
import hiro
10
import mock
11
import redis
12
import datetime
13
from flask import Flask, Blueprint, request, current_app, make_response
14
from flask.ext import restful
15
from flask.ext.restful import Resource
16
from flask.views import View, MethodView
17
from limits.errors import ConfigurationError
18
from limits.storage import MemcachedStorage
19
from limits.strategies import MovingWindowRateLimiter
20
21
from flask.ext.limiter.extension import C, Limiter, HEADERS
22
from flask.ext.limiter.util import get_remote_address, get_ipaddr
23
24
25
class FlaskExtTests(unittest.TestCase):
26
    def setUp(self):
27
        redis.Redis().flushall()
28
29
    def build_app(self, config={}, **limiter_args):
30
        app = Flask(__name__)
31
        for k, v in config.items():
32
            app.config.setdefault(k, v)
33
        limiter_args.setdefault('key_func', get_remote_address)
34
        limiter = Limiter(app, **limiter_args)
35
        mock_handler = mock.Mock()
36
        mock_handler.level = logging.INFO
37
        limiter.logger.addHandler(mock_handler)
38
        return app, limiter
39
40
    def test_invalid_strategy(self):
41
        app = Flask(__name__)
42
        app.config.setdefault(C.STRATEGY, "fubar")
43
        self.assertRaises(ConfigurationError, Limiter, app, key_func=get_remote_address)
44
45
    def test_invalid_storage_string(self):
46
        app = Flask(__name__)
47
        app.config.setdefault(C.STORAGE_URL, "fubar://localhost:1234")
48
        self.assertRaises(ConfigurationError, Limiter, app, key_func=get_remote_address)
49
50
    def test_constructor_arguments_over_config(self):
51
        app = Flask(__name__)
52
        app.config.setdefault(C.STRATEGY, "fixed-window-elastic-expiry")
53
        limiter = Limiter(strategy='moving-window', key_func=get_remote_address)
54
        limiter.init_app(app)
55
        app.config.setdefault(C.STORAGE_URL, "redis://localhost:6379")
56
        self.assertEqual(type(limiter._limiter), MovingWindowRateLimiter)
57
        limiter = Limiter(storage_uri='memcached://localhost:11211', key_func=get_remote_address)
58
        limiter.init_app(app)
59
        self.assertEqual(type(limiter._storage), MemcachedStorage)
60
61 View Code Duplication
    def test_error_message(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
62
        app, limiter = self.build_app({
63
            C.GLOBAL_LIMITS: "1 per day"
64
        })
65
66
        @app.route("/")
67
        def null():
68
            return ""
69
70
        with app.test_client() as cli:
71
            cli.get("/")
72
            self.assertTrue("1 per 1 day" in cli.get("/").data.decode())
73
74
            @app.errorhandler(429)
75
            def ratelimit_handler(e):
76
                return make_response('{"error" : "rate limit %s"}' % str(e.description), 429)
77
78
            self.assertEqual({'error': 'rate limit 1 per 1 day'}, json.loads(cli.get("/").data.decode()))
79
80
    def test_reset(self):
81
        app, limiter = self.build_app({
82
            C.GLOBAL_LIMITS: "1 per day"
83
        })
84
85
        @app.route("/")
86
        def null():
87
            return "Hello Reset"
88
89
        with app.test_client() as cli:
90
            cli.get("/")
91
            self.assertTrue("1 per 1 day" in cli.get("/").data.decode())
92
            limiter.reset()
93
            self.assertEqual("Hello Reset", cli.get("/").data.decode())
94
            self.assertTrue("1 per 1 day" in cli.get("/").data.decode())
95
96
    def test_swallow_error(self):
97
        app, limiter = self.build_app({
98
            C.GLOBAL_LIMITS: "1 per day",
99
            C.SWALLOW_ERRORS: True
100
        })
101
102
        @app.route("/")
103
        def null():
104
            return "ok"
105
106
        with app.test_client() as cli:
107
            with mock.patch("limits.strategies.FixedWindowRateLimiter.hit") as hit:
108
                def raiser(*a, **k):
109
                    raise Exception
110
111
                hit.side_effect = raiser
112
                self.assertTrue("ok" in cli.get("/").data.decode())
113
114 View Code Duplication
    def test_no_swallow_error(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
115
        app, limiter = self.build_app({
116
            C.GLOBAL_LIMITS: "1 per day",
117
        })
118
119
        @app.route("/")
120
        def null():
121
            return "ok"
122
123
        @app.errorhandler(500)
124
        def e500(e):
125
            return str(e), 500
126
127
        with app.test_client() as cli:
128
            with mock.patch("limits.strategies.FixedWindowRateLimiter.hit") as hit:
129
                def raiser(*a, **k):
130
                    raise Exception("underlying")
131
132
                hit.side_effect = raiser
133
                self.assertEqual(500, cli.get("/").status_code)
134
                self.assertEqual("underlying", cli.get("/").data.decode())
135
136 View Code Duplication
    def test_combined_rate_limits(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
137
        app, limiter = self.build_app({
138
            C.GLOBAL_LIMITS: "1 per hour; 10 per day"
139
        })
140
141
        @app.route("/t1")
142
        @limiter.limit("100 per hour;10/minute")
143
        def t1():
144
            return "t1"
145
146
        @app.route("/t2")
147
        def t2():
148
            return "t2"
149
150
        with hiro.Timeline().freeze() as timeline:
151
            with app.test_client() as cli:
152
                self.assertEqual(200, cli.get("/t1").status_code)
153
                self.assertEqual(200, cli.get("/t2").status_code)
154
                self.assertEqual(429, cli.get("/t2").status_code)
155
156 View Code Duplication
    def test_key_func(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
157
        app, limiter = self.build_app()
158
159
        @app.route("/t1")
160
        @limiter.limit("100 per minute", lambda: "test")
161
        def t1():
162
            return "test"
163
164
        with hiro.Timeline().freeze() as timeline:
165
            with app.test_client() as cli:
166
                for i in range(0, 100):
167
                    self.assertEqual(200,
168
                                     cli.get("/t1", headers={"X_FORWARDED_FOR": "127.0.0.2"}).status_code
169
                                     )
170
                self.assertEqual(429, cli.get("/t1").status_code)
171
172
    def test_multiple_decorators(self):
173
        app, limiter = self.build_app(key_func=get_ipaddr)
174
175
        @app.route("/t1")
176
        @limiter.limit("100 per minute", lambda: "test")  # effectively becomes a limit for all users
177
        @limiter.limit("50/minute")  # per ip as per default key_func
178
        def t1():
179
            return "test"
180
181
        with hiro.Timeline().freeze() as timeline:
182
            with app.test_client() as cli:
183
                for i in range(0, 100):
184
                    self.assertEqual(200 if i < 50 else 429,
185
                                     cli.get("/t1", headers={"X_FORWARDED_FOR": "127.0.0.2"}).status_code
186
                                     )
187
                for i in range(50):
188
                    self.assertEqual(200, cli.get("/t1").status_code)
189
                self.assertEqual(429, cli.get("/t1").status_code)
190
                self.assertEqual(429, cli.get("/t1", headers={"X_FORWARDED_FOR": "127.0.0.3"}).status_code)
191
192 View Code Duplication
    def test_logging(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
193
        app = Flask(__name__)
194
        limiter = Limiter(app, key_func=get_remote_address)
195
        mock_handler = mock.Mock()
196
        mock_handler.level = logging.INFO
197
        limiter.logger.addHandler(mock_handler)
198
199
        @app.route("/t1")
200
        @limiter.limit("1/minute")
201
        def t1():
202
            return "test"
203
204
        with app.test_client() as cli:
205
            self.assertEqual(200, cli.get("/t1").status_code)
206
            self.assertEqual(429, cli.get("/t1").status_code)
207
        self.assertEqual(mock_handler.handle.call_count, 1)
208
209 View Code Duplication
    def test_reuse_logging(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
210
        app = Flask(__name__)
211
        app_handler = mock.Mock()
212
        app_handler.level = logging.INFO
213
        app.logger.addHandler(app_handler)
214
        limiter = Limiter(app, key_func=get_remote_address)
215
        for handler in app.logger.handlers:
216
            limiter.logger.addHandler(handler)
217
218
        @app.route("/t1")
219
        @limiter.limit("1/minute")
220
        def t1():
221
            return "42"
222
223
        with app.test_client() as cli:
224
            cli.get("/t1")
225
            cli.get("/t1")
226
227
        self.assertEqual(app_handler.handle.call_count, 1)
228
229 View Code Duplication
    def test_exempt_routes(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
230
        app, limiter = self.build_app(default_limits=["1/minute"])
231
232
        @app.route("/t1")
233
        def t1():
234
            return "test"
235
236
        @app.route("/t2")
237
        @limiter.exempt
238
        def t2():
239
            return "test"
240
241
        with app.test_client() as cli:
242
            self.assertEqual(cli.get("/t1").status_code, 200)
243
            self.assertEqual(cli.get("/t1").status_code, 429)
244
            self.assertEqual(cli.get("/t2").status_code, 200)
245
            self.assertEqual(cli.get("/t2").status_code, 200)
246
247 View Code Duplication
    def test_blueprint(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
248
        app, limiter = self.build_app(default_limits=["1/minute"])
249
        bp = Blueprint("main", __name__)
250
251
        @bp.route("/t1")
252
        def t1():
253
            return "test"
254
255
        @bp.route("/t2")
256
        @limiter.limit("10 per minute")
257
        def t2():
258
            return "test"
259
260
        app.register_blueprint(bp)
261
262
        with app.test_client() as cli:
263
            self.assertEqual(cli.get("/t1").status_code, 200)
264
            self.assertEqual(cli.get("/t1").status_code, 429)
265
            for i in range(0, 10):
266
                self.assertEqual(cli.get("/t2").status_code, 200)
267
            self.assertEqual(cli.get("/t2").status_code, 429)
268
269
    def test_register_blueprint(self):
270
        app, limiter = self.build_app(default_limits=["1/minute"])
271
        bp_1 = Blueprint("bp1", __name__)
272
        bp_2 = Blueprint("bp2", __name__)
273
        bp_3 = Blueprint("bp3", __name__)
274
        bp_4 = Blueprint("bp4", __name__)
275
276
        @bp_1.route("/t1")
277
        def t1():
278
            return "test"
279
280
        @bp_1.route("/t2")
281
        def t2():
282
            return "test"
283
284
        @bp_2.route("/t3")
285
        def t3():
286
            return "test"
287
288
        @bp_3.route("/t4")
289
        def t4():
290
            return "test"
291
292
        @bp_4.route("/t5")
293
        def t4():
294
            return "test"
295
296
        def dy_limit():
297
            return "1/second"
298
299
        app.register_blueprint(bp_1)
300
        app.register_blueprint(bp_2)
301
        app.register_blueprint(bp_3)
302
        app.register_blueprint(bp_4)
303
304
        limiter.limit("1/second")(bp_1)
305
        limiter.exempt(bp_3)
306
        limiter.limit(dy_limit)(bp_4)
307
308
        with hiro.Timeline().freeze() as timeline:
309
            with app.test_client() as cli:
310
                self.assertEqual(cli.get("/t1").status_code, 200)
311
                self.assertEqual(cli.get("/t1").status_code, 429)
312
                timeline.forward(1)
313
                self.assertEqual(cli.get("/t1").status_code, 200)
314
                self.assertEqual(cli.get("/t2").status_code, 200)
315
                self.assertEqual(cli.get("/t2").status_code, 429)
316
                timeline.forward(1)
317
                self.assertEqual(cli.get("/t2").status_code, 200)
318
319
                self.assertEqual(cli.get("/t3").status_code, 200)
320
                for i in range(0, 10):
321
                    timeline.forward(1)
322
                    self.assertEqual(cli.get("/t3").status_code, 429)
323
324
                for i in range(0, 10):
325
                    self.assertEqual(cli.get("/t4").status_code, 200)
326
327
                self.assertEqual(cli.get("/t5").status_code, 200)
328
                self.assertEqual(cli.get("/t5").status_code, 429)
329
330 View Code Duplication
    def test_disabled_flag(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
331
        app, limiter = self.build_app(
332
                config={C.ENABLED: False},
333
                default_limits=["1/minute"]
334
        )
335
336
        @app.route("/t1")
337
        def t1():
338
            return "test"
339
340
        @app.route("/t2")
341
        @limiter.limit("10 per minute")
342
        def t2():
343
            return "test"
344
345
        with app.test_client() as cli:
346
            self.assertEqual(cli.get("/t1").status_code, 200)
347
            self.assertEqual(cli.get("/t1").status_code, 200)
348
            for i in range(0, 10):
349
                self.assertEqual(cli.get("/t2").status_code, 200)
350
            self.assertEqual(cli.get("/t2").status_code, 200)
351
352
    def test_fallback_to_memory_config(self):
353
        _, limiter = self.build_app(
354
                config={C.ENABLED: True},
355
                default_limits=["5/minute"],
356
                storage_uri="redis://localhost:6379",
357
                in_memory_fallback=["1/minute"]
358
        )
359
        self.assertEqual(len(limiter._in_memory_fallback), 1)
360
361
        _, limiter = self.build_app(
362
                config={C.ENABLED: True, C.IN_MEMORY_FALLBACK: "1/minute"},
363
                default_limits=["5/minute"],
364
                storage_uri="redis://localhost:6379",
365
        )
366
        self.assertEqual(len(limiter._in_memory_fallback), 1)
367
368
    def test_fallback_to_memory_backoff_check(self):
369
        app, limiter = self.build_app(
370
                config={C.ENABLED: True},
371
                default_limits=["5/minute"],
372
                storage_uri="redis://localhost:6379",
373
                in_memory_fallback=["1/minute"]
374
        )
375
376
        @app.route("/t1")
377
        def t1():
378
            return "test"
379
380
        with app.test_client() as cli:
381
            def raiser(*a):
382
                raise Exception("redis dead")
383
384
            with hiro.Timeline() as timeline:
385
                with mock.patch(
386
                        "redis.client.Redis.execute_command"
387
                ) as exec_command:
388
                    exec_command.side_effect = raiser
389
                    self.assertEqual(cli.get("/t1").status_code, 200)
390
                    self.assertEqual(cli.get("/t1").status_code, 429)
391
                    timeline.forward(1)
392
                    self.assertEqual(cli.get("/t1").status_code, 429)
393
                    timeline.forward(2)
394
                    self.assertEqual(cli.get("/t1").status_code, 429)
395
                    timeline.forward(4)
396
                    self.assertEqual(cli.get("/t1").status_code, 429)
397
                    timeline.forward(8)
398
                    self.assertEqual(cli.get("/t1").status_code, 429)
399
                    timeline.forward(16)
400
                    self.assertEqual(cli.get("/t1").status_code, 429)
401
                    timeline.forward(32)
402
                    self.assertEqual(cli.get("/t1").status_code, 200)
403
                # redis back to normal, but exponential backoff will only
404
                # result in it being marked after pow(2,0) seconds and next
405
                # check
406
                self.assertEqual(cli.get("/t1").status_code, 429)
407
                timeline.forward(1)
408
                self.assertEqual(cli.get("/t1").status_code, 200)
409
                self.assertEqual(cli.get("/t1").status_code, 200)
410
                self.assertEqual(cli.get("/t1").status_code, 200)
411
                self.assertEqual(cli.get("/t1").status_code, 200)
412
                self.assertEqual(cli.get("/t1").status_code, 200)
413
                self.assertEqual(cli.get("/t1").status_code, 429)
414
415
    def test_fallback_to_memory(self):
416
        app, limiter = self.build_app(
417
                config={C.ENABLED: True},
418
                default_limits=["5/minute"],
419
                storage_uri="redis://localhost:6379",
420
                in_memory_fallback=["1/minute"]
421
        )
422
423
        @app.route("/t1")
424
        def t1():
425
            return "test"
426
427
        @app.route("/t2")
428
        @limiter.limit("3 per minute")
429
        def t2():
430
            return "test"
431
432
        with app.test_client() as cli:
433
            self.assertEqual(cli.get("/t1").status_code, 200)
434
            self.assertEqual(cli.get("/t1").status_code, 200)
435
            self.assertEqual(cli.get("/t1").status_code, 200)
436
            self.assertEqual(cli.get("/t1").status_code, 200)
437
            self.assertEqual(cli.get("/t1").status_code, 200)
438
            self.assertEqual(cli.get("/t1").status_code, 429)
439
            self.assertEqual(cli.get("/t2").status_code, 200)
440
            self.assertEqual(cli.get("/t2").status_code, 200)
441
            self.assertEqual(cli.get("/t2").status_code, 200)
442
            self.assertEqual(cli.get("/t2").status_code, 429)
443
444
            def raiser(*a):
445
                raise Exception("redis dead")
446
447
            with mock.patch(
448
                    "redis.client.Redis.execute_command"
449
            ) as exec_command:
450
                exec_command.side_effect = raiser
451
                self.assertEqual(cli.get("/t1").status_code, 200)
452
                self.assertEqual(cli.get("/t1").status_code, 429)
453
                self.assertEqual(cli.get("/t2").status_code, 200)
454
                self.assertEqual(cli.get("/t2").status_code, 429)
455
            # redis back to normal, go back to regular limits
456
            with hiro.Timeline() as timeline:
457
                timeline.forward(1)
458
                limiter._storage.storage.flushall()
459
                self.assertEqual(cli.get("/t2").status_code, 200)
460
                self.assertEqual(cli.get("/t2").status_code, 200)
461
                self.assertEqual(cli.get("/t2").status_code, 200)
462
                self.assertEqual(cli.get("/t2").status_code, 429)
463
464
    def test_decorated_dynamic_limits(self):
465
        app, limiter = self.build_app({"X": "2 per second"}, default_limits=["1/second"])
466
467
        def request_context_limit():
468
            limits = {
469
                "127.0.0.1": "10 per minute",
470
                "127.0.0.2": "1 per minute"
471
            }
472
            remote_addr = (request.access_route and request.access_route[0]) or request.remote_addr or '127.0.0.1'
473
            limit = limits.setdefault(remote_addr, '1 per minute')
474
            return limit
475
476
        @app.route("/t1")
477
        @limiter.limit("20/day")
478
        @limiter.limit(lambda: current_app.config.get("X"))
479
        @limiter.limit(request_context_limit)
480
        def t1():
481
            return "42"
482
483
        @app.route("/t2")
484
        @limiter.limit(lambda: current_app.config.get("X"))
485
        def t2():
486
            return "42"
487
488
        R1 = {"X_FORWARDED_FOR": "127.0.0.1, 127.0.0.0"}
489
        R2 = {"X_FORWARDED_FOR": "127.0.0.2"}
490
491
        with app.test_client() as cli:
492
            with hiro.Timeline().freeze() as timeline:
493
                for i in range(0, 10):
494
                    self.assertEqual(cli.get("/t1", headers=R1).status_code, 200)
495
                    timeline.forward(1)
496
                self.assertEqual(cli.get("/t1", headers=R1).status_code, 429)
497
                self.assertEqual(cli.get("/t1", headers=R2).status_code, 200)
498
                self.assertEqual(cli.get("/t1", headers=R2).status_code, 429)
499
                timeline.forward(60)
500
                self.assertEqual(cli.get("/t1", headers=R2).status_code, 200)
501
                self.assertEqual(cli.get("/t2").status_code, 200)
502
                self.assertEqual(cli.get("/t2").status_code, 200)
503
                self.assertEqual(cli.get("/t2").status_code, 429)
504
                timeline.forward(1)
505
                self.assertEqual(cli.get("/t2").status_code, 200)
506
507 View Code Duplication
    def test_invalid_decorated_dynamic_limits(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
508
        app = Flask(__name__)
509
        app.config.setdefault("X", "2 per sec")
510
        limiter = Limiter(app, default_limits=["1/second"], key_func=get_remote_address)
511
        mock_handler = mock.Mock()
512
        mock_handler.level = logging.INFO
513
        limiter.logger.addHandler(mock_handler)
514
515
        @app.route("/t1")
516
        @limiter.limit(lambda: current_app.config.get("X"))
517
        def t1():
518
            return "42"
519
520
        with app.test_client() as cli:
521
            with hiro.Timeline().freeze() as timeline:
522
                self.assertEqual(cli.get("/t1").status_code, 200)
523
                self.assertEqual(cli.get("/t1").status_code, 429)
524
        # 2 for invalid limit, 1 for warning.
525
        self.assertEqual(mock_handler.handle.call_count, 3)
526
        self.assertTrue("failed to load ratelimit" in mock_handler.handle.call_args_list[0][0][0].msg)
527
        self.assertTrue("failed to load ratelimit" in mock_handler.handle.call_args_list[1][0][0].msg)
528
        self.assertTrue("exceeded at endpoint" in mock_handler.handle.call_args_list[2][0][0].msg)
529
530 View Code Duplication
    def test_invalid_decorated_static_limits(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
531
        app = Flask(__name__)
532
        limiter = Limiter(app, default_limits=["1/second"], key_func=get_remote_address)
533
        mock_handler = mock.Mock()
534
        mock_handler.level = logging.INFO
535
        limiter.logger.addHandler(mock_handler)
536
537
        @app.route("/t1")
538
        @limiter.limit("2/sec")
539
        def t1():
540
            return "42"
541
542
        with app.test_client() as cli:
543
            with hiro.Timeline().freeze() as timeline:
544
                self.assertEqual(cli.get("/t1").status_code, 200)
545
                self.assertEqual(cli.get("/t1").status_code, 429)
546
        self.assertTrue("failed to configure" in mock_handler.handle.call_args_list[0][0][0].msg)
547
        self.assertTrue("exceeded at endpoint" in mock_handler.handle.call_args_list[1][0][0].msg)
548
549 View Code Duplication
    def test_invalid_decorated_static_limit_blueprint(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
550
        app = Flask(__name__)
551
        limiter = Limiter(app, default_limits=["1/second"], key_func=get_remote_address)
552
        mock_handler = mock.Mock()
553
        mock_handler.level = logging.INFO
554
        limiter.logger.addHandler(mock_handler)
555
        bp = Blueprint("bp1", __name__)
556
557
        @bp.route("/t1")
558
        def t1():
559
            return "42"
560
561
        limiter.limit("2/sec")(bp)
562
        app.register_blueprint(bp)
563
564
        with app.test_client() as cli:
565
            with hiro.Timeline().freeze() as timeline:
566
                self.assertEqual(cli.get("/t1").status_code, 200)
567
                self.assertEqual(cli.get("/t1").status_code, 429)
568
        self.assertTrue("failed to configure" in mock_handler.handle.call_args_list[0][0][0].msg)
569
        self.assertTrue("exceeded at endpoint" in mock_handler.handle.call_args_list[1][0][0].msg)
570
571 View Code Duplication
    def test_invalid_decorated_dynamic_limits_blueprint(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
572
        app = Flask(__name__)
573
        app.config.setdefault("X", "2 per sec")
574
        limiter = Limiter(app, default_limits=["1/second"], key_func=get_remote_address)
575
        mock_handler = mock.Mock()
576
        mock_handler.level = logging.INFO
577
        limiter.logger.addHandler(mock_handler)
578
        bp = Blueprint("bp1", __name__)
579
580
        @bp.route("/t1")
581
        def t1():
582
            return "42"
583
584
        limiter.limit(lambda: current_app.config.get("X"))(bp)
585
        app.register_blueprint(bp)
586
587
        with app.test_client() as cli:
588
            with hiro.Timeline().freeze() as timeline:
589
                self.assertEqual(cli.get("/t1").status_code, 200)
590
                self.assertEqual(cli.get("/t1").status_code, 429)
591
        self.assertEqual(mock_handler.handle.call_count, 3)
592
        self.assertTrue("failed to load ratelimit" in mock_handler.handle.call_args_list[0][0][0].msg)
593
        self.assertTrue("failed to load ratelimit" in mock_handler.handle.call_args_list[1][0][0].msg)
594
        self.assertTrue("exceeded at endpoint" in mock_handler.handle.call_args_list[2][0][0].msg)
595
596
    def test_multiple_apps(self):
597
        app1 = Flask(__name__)
598
        app2 = Flask(__name__)
599
600
        limiter = Limiter(default_limits=["1/second"], key_func=get_remote_address)
601
        limiter.init_app(app1)
602
        limiter.init_app(app2)
603
604
        @app1.route("/ping")
605
        def ping():
606
            return "PONG"
607
608
        @app1.route("/slowping")
609
        @limiter.limit("1/minute")
610
        def slow_ping():
611
            return "PONG"
612
613
        @app2.route("/ping")
614
        @limiter.limit("2/second")
615
        def ping_2():
616
            return "PONG"
617
618
        @app2.route("/slowping")
619
        @limiter.limit("2/minute")
620
        def slow_ping_2():
621
            return "PONG"
622
623
        with hiro.Timeline().freeze() as timeline:
624
            with app1.test_client() as cli:
625
                self.assertEqual(cli.get("/ping").status_code, 200)
626
                self.assertEqual(cli.get("/ping").status_code, 429)
627
                timeline.forward(1)
628
                self.assertEqual(cli.get("/ping").status_code, 200)
629
                self.assertEqual(cli.get("/slowping").status_code, 200)
630
                timeline.forward(59)
631
                self.assertEqual(cli.get("/slowping").status_code, 429)
632
                timeline.forward(1)
633
                self.assertEqual(cli.get("/slowping").status_code, 200)
634
            with app2.test_client() as cli:
635
                self.assertEqual(cli.get("/ping").status_code, 200)
636
                self.assertEqual(cli.get("/ping").status_code, 200)
637
                self.assertEqual(cli.get("/ping").status_code, 429)
638
                timeline.forward(1)
639
                self.assertEqual(cli.get("/ping").status_code, 200)
640
                self.assertEqual(cli.get("/slowping").status_code, 200)
641
                timeline.forward(59)
642
                self.assertEqual(cli.get("/slowping").status_code, 200)
643
                self.assertEqual(cli.get("/slowping").status_code, 429)
644
                timeline.forward(1)
645
                self.assertEqual(cli.get("/slowping").status_code, 200)
646
647
    def test_headers_no_breach(self):
648
        app = Flask(__name__)
649
        limiter = Limiter(
650
            app, default_limits=["10/minute"], headers_enabled=True,
651
            key_func=get_remote_address
652
        )
653
654
        @app.route("/t1")
655
        def t1():
656
            return "test"
657
658
        @app.route("/t2")
659
        @limiter.limit("2/second; 5 per minute; 10/hour")
660
        def t2():
661
            return "test"
662
663
        with hiro.Timeline().freeze():
664
            with app.test_client() as cli:
665
                resp = cli.get("/t1")
666
                self.assertEqual(
667
                        resp.headers.get('X-RateLimit-Limit'),
668
                        '10'
669
                )
670
                self.assertEqual(
671
                        resp.headers.get('X-RateLimit-Remaining'),
672
                        '9'
673
                )
674
                self.assertEqual(
675
                        resp.headers.get('X-RateLimit-Reset'),
676
                        str(int(time.time() + 61))
677
                )
678
                self.assertEqual(
679
                        resp.headers.get('Retry-After'),
680
                        str(60)
681
                )
682
                resp = cli.get("/t2")
683
                self.assertEqual(
684
                        resp.headers.get('X-RateLimit-Limit'),
685
                        '2'
686
                )
687
                self.assertEqual(
688
                        resp.headers.get('X-RateLimit-Remaining'),
689
                        '1'
690
                )
691
                self.assertEqual(
692
                        resp.headers.get('X-RateLimit-Reset'),
693
                        str(int(time.time() + 2))
694
                )
695
696
                self.assertEqual(
697
                    resp.headers.get('Retry-After'),
698
                    str(1)
699
                )
700
701 View Code Duplication
    def test_headers_breach(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
702
        app = Flask(__name__)
703
        limiter = Limiter(
704
            app, default_limits=["10/minute"],
705
            headers_enabled=True, key_func=get_remote_address
706
        )
707
708
        @app.route("/t1")
709
        @limiter.limit("2/second; 10 per minute; 20/hour")
710
        def t():
711
            return "test"
712
713
        with hiro.Timeline().freeze() as timeline:
714
            with app.test_client() as cli:
715
                for i in range(11):
716
                    resp = cli.get("/t1")
717
                    timeline.forward(1)
718
719
                self.assertEqual(
720
                        resp.headers.get('X-RateLimit-Limit'),
721
                        '10'
722
                )
723
                self.assertEqual(
724
                        resp.headers.get('X-RateLimit-Remaining'),
725
                        '0'
726
                )
727
                self.assertEqual(
728
                        resp.headers.get('X-RateLimit-Reset'),
729
                        str(int(time.time() + 50))
730
                )
731
                self.assertEqual(
732
                        resp.headers.get('Retry-After'),
733
                        str(int(50))
734
                )
735
736
    def test_retry_after(self):
737
        app = Flask(__name__)
738
        _ = Limiter(
739
            app, default_limits=["1/minute"],
740
            headers_enabled=True, key_func=get_remote_address
741
        )
742
743
        @app.route("/t1")
744
        def t():
745
            return "test"
746
747
        with hiro.Timeline().freeze() as timeline:
748
            with app.test_client() as cli:
749
                resp = cli.get("/t1")
750
                retry_after = int(resp.headers.get('Retry-After'))
751
                self.assertTrue(retry_after > 0)
752
                timeline.forward(retry_after)
753
                resp = cli.get("/t1")
754
                self.assertEqual(resp.status_code, 200)
755
756
    def test_custom_headers_from_setter(self):
757
        app = Flask(__name__)
758
        limiter = Limiter(
759
            app, default_limits=["10/minute"], headers_enabled=True,
760
            key_func=get_remote_address, retry_after='http-date'
761
        )
762
        limiter._header_mapping[HEADERS.RESET] = 'X-Reset'
763
        limiter._header_mapping[HEADERS.LIMIT] = 'X-Limit'
764
        limiter._header_mapping[HEADERS.REMAINING] = 'X-Remaining'
765
766
        @app.route("/t1")
767
        @limiter.limit("2/second; 10 per minute; 20/hour")
768
        def t():
769
            return "test"
770
771
        with hiro.Timeline().freeze(0) as timeline:
772
            with app.test_client() as cli:
773
                for i in range(11):
774 View Code Duplication
                    resp = cli.get("/t1")
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
775
                    timeline.forward(1)
776
777
                self.assertEqual(
778
                        resp.headers.get('X-Limit'),
779
                        '10'
780
                )
781
                self.assertEqual(
782
                        resp.headers.get('X-Remaining'),
783
                        '0'
784
                )
785
                self.assertEqual(
786
                        resp.headers.get('X-Reset'),
787
                        str(int(time.time() + 50))
788
                )
789
                self.assertEqual(
790
                    resp.headers.get('Retry-After'),
791
                    'Thu, 01 Jan 1970 00:01:01 GMT'
792
                )
793
794
    def test_custom_headers_from_config(self):
795
        app = Flask(__name__)
796
        app.config.setdefault(C.HEADER_LIMIT, "X-Limit")
797
        app.config.setdefault(C.HEADER_REMAINING, "X-Remaining")
798
        app.config.setdefault(C.HEADER_RESET, "X-Reset")
799
        limiter = Limiter(
800
            app, default_limits=["10/minute"], headers_enabled=True,
801
            key_func=get_remote_address
802
        )
803
804
805
        @app.route("/t1")
806
        @limiter.limit("2/second; 10 per minute; 20/hour")
807
        def t():
808
            return "test"
809
810
        with hiro.Timeline().freeze() as timeline:
811
            with app.test_client() as cli:
812
                for i in range(11):
813
                    resp = cli.get("/t1")
814
                    timeline.forward(1)
815
816
                self.assertEqual(
817
                        resp.headers.get('X-Limit'),
818
                        '10'
819
                )
820
                self.assertEqual(
821
                        resp.headers.get('X-Remaining'),
822
                        '0'
823
                )
824
                self.assertEqual(
825
                        resp.headers.get('X-Reset'),
826
                        str(int(time.time() + 50))
827
                )
828
829
    def test_named_shared_limit(self):
830
        app, limiter = self.build_app()
831
        shared_limit_a = limiter.shared_limit("1/minute", scope='a')
832
        shared_limit_b = limiter.shared_limit("1/minute", scope='b')
833
834
        @app.route("/t1")
835
        @shared_limit_a
836
        def route1():
837
            return "route1"
838
839
        @app.route("/t2")
840
        @shared_limit_a
841
        def route2():
842
            return "route2"
843
844
        @app.route("/t3")
845
        @shared_limit_b
846
        def route3():
847
            return "route3"
848
849
        with hiro.Timeline().freeze() as timeline:
850
            with app.test_client() as cli:
851
                self.assertEqual(200, cli.get("/t1").status_code)
852
                self.assertEqual(200, cli.get("/t3").status_code)
853
                self.assertEqual(429, cli.get("/t2").status_code)
854
855 View Code Duplication
    def test_application_shared_limit(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
856
        app, limiter = self.build_app(application_limits=["2/minute"])
857
        @app.route("/t1")
858
        def t1():
859
            return "route1"
860
861
        @app.route("/t2")
862
        def t2():
863
            return "route2"
864
865
        with hiro.Timeline().freeze():
866
            with app.test_client() as cli:
867
                self.assertEqual(200, cli.get("/t1").status_code)
868
                self.assertEqual(200, cli.get("/t2").status_code)
869
                self.assertEqual(429, cli.get("/t1").status_code)
870
871 View Code Duplication
    def test_dynamic_shared_limit(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
872
        app, limiter = self.build_app()
873
        fn_a = mock.Mock()
874
        fn_b = mock.Mock()
875
        fn_a.return_value = "foo"
876
        fn_b.return_value = "bar"
877
878
        dy_limit_a = limiter.shared_limit("1/minute", scope=fn_a)
879
        dy_limit_b = limiter.shared_limit("1/minute", scope=fn_b)
880
881
        @app.route("/t1")
882
        @dy_limit_a
883
        def t1():
884
            return "route1"
885
886
        @app.route("/t2")
887
        @dy_limit_a
888
        def t2():
889
            return "route2"
890
891
        @app.route("/t3")
892
        @dy_limit_b
893
        def t3():
894
            return "route3"
895
896
        with hiro.Timeline().freeze():
897
            with app.test_client() as cli:
898
                self.assertEqual(200, cli.get("/t1").status_code)
899
                self.assertEqual(200, cli.get("/t3").status_code)
900
                self.assertEqual(429, cli.get("/t2").status_code)
901
                self.assertEqual(429, cli.get("/t3").status_code)
902
                self.assertEqual(2, fn_a.call_count)
903
                self.assertEqual(2, fn_b.call_count)
904
                fn_b.assert_called_with("t3")
905
                fn_a.assert_has_calls([mock.call("t1"), mock.call("t2")])
906
907 View Code Duplication
    def test_conditional_limits(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
908
        """Test that the conditional activation of the limits work."""
909
        app = Flask(__name__)
910
        limiter = Limiter(app, key_func=get_remote_address)
911
912
        @app.route("/limited")
913
        @limiter.limit("1 per day")
914
        def limited_route():
915
            return "passed"
916
917
        @app.route("/unlimited")
918
        @limiter.limit("1 per day", exempt_when=lambda: True)
919
        def never_limited_route():
920
            return "should always pass"
921
922
        is_exempt = False
923
924
        @app.route("/conditional")
925
        @limiter.limit("1 per day", exempt_when=lambda: is_exempt)
926
        def conditionally_limited_route():
927
            return "conditional"
928
929
        with app.test_client() as cli:
930
            self.assertEqual(cli.get("/limited").status_code, 200)
931
            self.assertEqual(cli.get("/limited").status_code, 429)
932
933
            self.assertEqual(cli.get("/unlimited").status_code, 200)
934
            self.assertEqual(cli.get("/unlimited").status_code, 200)
935
936
            self.assertEqual(cli.get("/conditional").status_code, 200)
937
            self.assertEqual(cli.get("/conditional").status_code, 429)
938
            is_exempt = True
939
            self.assertEqual(cli.get("/conditional").status_code, 200)
940
            is_exempt = False
941
            self.assertEqual(cli.get("/conditional").status_code, 429)
942
943
    def test_conditional_shared_limits(self):
944
        """Test that conditional shared limits work."""
945
        app = Flask(__name__)
946
        limiter = Limiter(app, key_func=get_remote_address)
947
948
        @app.route("/limited")
949
        @limiter.shared_limit("1 per day", "test_scope")
950
        def limited_route():
951
            return "passed"
952
953
        @app.route("/unlimited")
954
        @limiter.shared_limit("1 per day", "test_scope", exempt_when=lambda: True)
955
        def never_limited_route():
956
            return "should always pass"
957
958
        is_exempt = False
959
960
        @app.route("/conditional")
961
        @limiter.shared_limit("1 per day", "test_scope", exempt_when=lambda: is_exempt)
962
        def conditionally_limited_route():
963
            return "conditional"
964
965
        with app.test_client() as cli:
966
            self.assertEqual(cli.get("/unlimited").status_code, 200)
967
            self.assertEqual(cli.get("/unlimited").status_code, 200)
968
969
            self.assertEqual(cli.get("/limited").status_code, 200)
970
            self.assertEqual(cli.get("/limited").status_code, 429)
971
972
            self.assertEqual(cli.get("/conditional").status_code, 429)
973
            is_exempt = True
974
            self.assertEqual(cli.get("/conditional").status_code, 200)
975
            is_exempt = False
976
            self.assertEqual(cli.get("/conditional").status_code, 429)
977
978
    def test_whitelisting(self):
979
980
        app = Flask(__name__)
981
        limiter = Limiter(
982
            app, default_limits=["1/minute"], headers_enabled=True,
983
            key_func=get_remote_address
984
        )
985
986
        @app.route("/")
987
        def t():
988
            return "test"
989
990
        @limiter.request_filter
991
        def w():
992
            if request.headers.get("internal", None) == "true":
993
                return True
994
            return False
995
996
        with hiro.Timeline().freeze() as timeline:
997
            with app.test_client() as cli:
998
                self.assertEqual(cli.get("/").status_code, 200)
999
                self.assertEqual(cli.get("/").status_code, 429)
1000
                timeline.forward(60)
1001
                self.assertEqual(cli.get("/").status_code, 200)
1002
1003
                for i in range(0, 10):
1004
                    self.assertEqual(
1005
                            cli.get("/", headers={"internal": "true"}).status_code,
1006
                            200
1007
                    )
1008
1009
    def test_pluggable_views(self):
1010
        app, limiter = self.build_app(
1011
                default_limits=["1/hour"]
1012
        )
1013
1014
        class Va(View):
1015
            methods = ['GET', 'POST']
1016
            decorators = [limiter.limit("2/second")]
1017
1018
            def dispatch_request(self):
1019
                return request.method.lower()
1020
1021
        class Vb(View):
1022
            methods = ['GET']
1023
            decorators = [limiter.limit("1/second, 3/minute")]
1024
1025
            def dispatch_request(self):
1026
                return request.method.lower()
1027
1028
        class Vc(View):
1029
            methods = ['GET']
1030
1031
            def dispatch_request(self):
1032
                return request.method.lower()
1033
1034
        app.add_url_rule("/a", view_func=Va.as_view("a"))
1035
        app.add_url_rule("/b", view_func=Vb.as_view("b"))
1036
        app.add_url_rule("/c", view_func=Vc.as_view("c"))
1037
        with hiro.Timeline().freeze() as timeline:
1038
            with app.test_client() as cli:
1039
                self.assertEqual(200, cli.get("/a").status_code)
1040
                self.assertEqual(200, cli.get("/a").status_code)
1041
                self.assertEqual(429, cli.post("/a").status_code)
1042
                self.assertEqual(200, cli.get("/b").status_code)
1043
                timeline.forward(1)
1044
                self.assertEqual(200, cli.get("/b").status_code)
1045
                timeline.forward(1)
1046
                self.assertEqual(200, cli.get("/b").status_code)
1047
                timeline.forward(1)
1048
                self.assertEqual(429, cli.get("/b").status_code)
1049
                self.assertEqual(200, cli.get("/c").status_code)
1050
                self.assertEqual(429, cli.get("/c").status_code)
1051
1052
    def test_pluggable_method_views(self):
1053
        app, limiter = self.build_app(
1054
                default_limits=["1/hour"]
1055
        )
1056
1057
        class Va(MethodView):
1058
            decorators = [limiter.limit("2/second")]
1059
1060
            def get(self):
1061
                return request.method.lower()
1062
1063
            def post(self):
1064
                return request.method.lower()
1065
1066
        class Vb(MethodView):
1067
            decorators = [limiter.limit("1/second, 3/minute")]
1068
1069
            def get(self):
1070
                return request.method.lower()
1071
1072
        class Vc(MethodView):
1073
            def get(self):
1074
                return request.method.lower()
1075
1076
        class Vd(MethodView):
1077
            decorators = [limiter.limit("1/minute", methods=['get'])]
1078
1079
            def get(self):
1080
                return request.method.lower()
1081
1082
            def post(self):
1083
                return request.method.lower()
1084
1085
        app.add_url_rule("/a", view_func=Va.as_view("a"))
1086
        app.add_url_rule("/b", view_func=Vb.as_view("b"))
1087
        app.add_url_rule("/c", view_func=Vc.as_view("c"))
1088
        app.add_url_rule("/d", view_func=Vd.as_view("d"))
1089
1090
        with hiro.Timeline().freeze() as timeline:
1091
            with app.test_client() as cli:
1092
                self.assertEqual(200, cli.get("/a").status_code)
1093
                self.assertEqual(200, cli.get("/a").status_code)
1094
                self.assertEqual(429, cli.get("/a").status_code)
1095
                self.assertEqual(429, cli.post("/a").status_code)
1096
                self.assertEqual(200, cli.get("/b").status_code)
1097
                timeline.forward(1)
1098
                self.assertEqual(200, cli.get("/b").status_code)
1099
                timeline.forward(1)
1100
                self.assertEqual(200, cli.get("/b").status_code)
1101
                timeline.forward(1)
1102
                self.assertEqual(429, cli.get("/b").status_code)
1103
                self.assertEqual(200, cli.get("/c").status_code)
1104
                self.assertEqual(429, cli.get("/c").status_code)
1105
                self.assertEqual(200, cli.get("/d").status_code)
1106
                self.assertEqual(429, cli.get("/d").status_code)
1107
                self.assertEqual(200, cli.post("/d").status_code)
1108
                self.assertEqual(200, cli.post("/d").status_code)
1109
1110
    def test_flask_restful_resource(self):
1111
        app, limiter = self.build_app(
1112
                default_limits=["1/hour"]
1113
        )
1114
        api = restful.Api(app)
1115
1116
        class Va(Resource):
1117
            decorators = [limiter.limit("2/second")]
1118
1119
            def get(self):
1120
                return request.method.lower()
1121
1122
            def post(self):
1123
                return request.method.lower()
1124
1125
        class Vb(Resource):
1126
            decorators = [limiter.limit("1/second, 3/minute")]
1127
1128
            def get(self):
1129
                return request.method.lower()
1130
1131
        class Vc(Resource):
1132
            def get(self):
1133
                return request.method.lower()
1134
1135 View Code Duplication
        api.add_resource(Va, "/a")
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1136
        api.add_resource(Vb, "/b")
1137
        api.add_resource(Vc, "/c")
1138
1139
        with hiro.Timeline().freeze() as timeline:
1140
            with app.test_client() as cli:
1141
                self.assertEqual(200, cli.get("/a").status_code)
1142
                self.assertEqual(200, cli.get("/a").status_code)
1143
                self.assertEqual(429, cli.get("/a").status_code)
1144
                self.assertEqual(429, cli.post("/a").status_code)
1145
                self.assertEqual(200, cli.get("/b").status_code)
1146
                timeline.forward(1)
1147
                self.assertEqual(200, cli.get("/b").status_code)
1148
                timeline.forward(1)
1149
                self.assertEqual(200, cli.get("/b").status_code)
1150 View Code Duplication
                timeline.forward(1)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1151
                self.assertEqual(429, cli.get("/b").status_code)
1152
                self.assertEqual(200, cli.get("/c").status_code)
1153
                self.assertEqual(429, cli.get("/c").status_code)
1154
1155
    def test_separate_method_limits(self):
1156
        app, limiter = self.build_app()
1157
1158
        @limiter.limit("1/second", per_method=True)
1159
        @app.route("/", methods=["GET", "POST"])
1160
        def root():
1161
            return "root"
1162
1163
        with hiro.Timeline():
1164
            with app.test_client() as cli:
1165
                self.assertEqual(200, cli.get("/").status_code)
1166
                self.assertEqual(429, cli.get("/").status_code)
1167
                self.assertEqual(200, cli.post("/").status_code)
1168
                self.assertEqual(429, cli.post("/").status_code)
1169
1170
    def test_explicit_method_limits(self):
1171
        app, limiter = self.build_app()
1172
1173
        @limiter.limit("1/second", methods=["GET"])
1174
        @app.route("/", methods=["GET", "POST"])
1175
        def root():
1176
            return "root"
1177
1178
        with hiro.Timeline():
1179
            with app.test_client() as cli:
1180
                self.assertEqual(200, cli.get("/").status_code)
1181
                self.assertEqual(429, cli.get("/").status_code)
1182
                self.assertEqual(200, cli.post("/").status_code)
1183
                self.assertEqual(200, cli.post("/").status_code)
1184
1185
    def test_no_auto_check(self):
1186
        app, limiter = self.build_app(auto_check=False)
1187
1188
        @limiter.limit("1/second", per_method=True)
1189
        @app.route("/", methods=["GET", "POST"])
1190
        def root():
1191
            return "root"
1192
1193
        with hiro.Timeline().freeze():
1194
            with app.test_client() as cli:
1195
                self.assertEqual(200, cli.get("/").status_code)
1196
                self.assertEqual(200, cli.get("/").status_code)
1197
1198
        # attach before_request to perform check
1199
        @app.before_request
1200
        def _():
1201
            limiter.check()
1202
1203
        with hiro.Timeline().freeze():
1204
            with app.test_client() as cli:
1205
                self.assertEqual(200, cli.get("/").status_code)
1206
                self.assertEqual(429, cli.get("/").status_code)
1207
1208
    def test_custom_error_message(self):
1209
        app, limiter = self.build_app()
1210
1211
        @app.errorhandler(429)
1212
        def ratelimit_handler(e):
1213
            return make_response(
1214
                    e.description
1215
                    , 429
1216
            )
1217
1218
        l1 = lambda: "1/second"
1219
        e1 = lambda: "dos"
1220
1221
        @limiter.limit("1/second", error_message="uno")
1222
        @app.route("/t1")
1223
        def t1():
1224
            return "1"
1225
1226
        @limiter.limit(l1, error_message=e1)
1227
        @app.route("/t2")
1228
        def t2():
1229
            return "2"
1230
1231
        s1 = limiter.shared_limit("1/second", scope='error_message', error_message="tres")
1232
1233
        @app.route("/t3")
1234
        @s1
1235
        def t3():
1236
            return "3"
1237
1238
        with hiro.Timeline().freeze():
1239
            with app.test_client() as cli:
1240
                cli.get("/t1")
1241
                resp = cli.get("/t1")
1242
                self.assertEqual(429, resp.status_code)
1243
                self.assertEqual(resp.data, b'uno')
1244
                cli.get("/t2")
1245
                resp = cli.get("/t2")
1246
                self.assertEqual(429, resp.status_code)
1247
                self.assertEqual(resp.data, b'dos')
1248
                cli.get("/t3")
1249
                resp = cli.get("/t3")
1250
                self.assertEqual(429, resp.status_code)
1251
                self.assertEqual(resp.data, b'tres')
1252
1253
    def test_custom_key_prefix(self):
1254
        app1, limiter1 = self.build_app(key_prefix="moo", storage_uri="redis://localhost:6379")
1255
        app2, limiter2 = self.build_app(key_prefix="cow", storage_uri="redis://localhost:6379")
1256
1257
        @app1.route("/test")
1258
        @limiter1.limit("1/day")
1259
        def t1():
1260
            return "app1 test"
1261
1262
        @app2.route("/test")
1263
        @limiter2.limit("1/day")
1264
        def t1():
1265
            return "app1 test"
1266
1267
        with app1.test_client() as cli:
1268
            resp = cli.get("/test")
1269
            self.assertEqual(200, resp.status_code)
1270
            resp = cli.get("/test")
1271
            self.assertEqual(429, resp.status_code)
1272
        with app2.test_client() as cli:
1273
            resp = cli.get("/test")
1274
            self.assertEqual(200, resp.status_code)
1275
            resp = cli.get("/test")
1276
            self.assertEqual(429, resp.status_code)
1277