File: test_rate_limiter.py

package info (click to toggle)
python-apeye 1.4.1-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 940 kB
  • sloc: python: 2,724; makefile: 10
file content (144 lines) | stat: -rw-r--r-- 3,946 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# stdlib
import json
import logging
import re
import sys
import time
from datetime import datetime

# 3rd party
import pytest
import requests
from pytest_httpserver import HTTPServer
from werkzeug import Request, Response

# this package
from apeye.rate_limiter import HTTPCache, rate_limit

if sys.version_info < (3, 7):
	# 3rd party
	from backports.datetime_fromisoformat import datetime_fromisoformat

else:
	datetime_fromisoformat = datetime.fromisoformat

logging.basicConfig()


@rate_limit(3)
def rate_limited_function():
	print("Inside function")
	return 42


def test_rate_limit(capsys, caplog):
	assert rate_limited_function() == 42
	assert rate_limited_function() == 42
	assert rate_limited_function() == 42

	assert capsys.readouterr().out.splitlines() == ["Inside function"] * 3
	caplog.set_level(logging.DEBUG)

	assert rate_limited_function() == 42
	assert rate_limited_function() == 42
	time.sleep(2)
	assert rate_limited_function() == 42

	assert capsys.readouterr().out.splitlines() == ["Inside function"] * 3

	last_ran_re = re.compile(r"rate_limited_function: Last ran (\d+(\.\d+)?|\d+e-\d+) seconds ago\.")

	print(caplog.record_tuples)

	assert last_ran_re.match(caplog.record_tuples[0][2])
	assert re.match(r"rate_limited_function: Waiting (\d+(\.\d+)?|\d+e-\d+) seconds\.", caplog.record_tuples[1][2])
	assert last_ran_re.match(caplog.record_tuples[2][2])
	assert last_ran_re.match(caplog.record_tuples[3][2])


@pytest.fixture(scope="session")
def testing_http_cache():
	cache = HTTPCache("testing_apeye_http")
	assert cache.clear()
	yield cache

	cache.clear()


@pytest.fixture()
def timeserver(httpserver: HTTPServer):

	def time_handler(request: Request):
		time.sleep(1)

		now = datetime.utcnow()
		headers = {
				"Cache-Control": "max-age=0, private, must-revalidate",
				"Date": now.strftime("%a, %d %B %Y %H:%M:%S GMT"),
				}
		response_json = {
				"abbreviation": "GMT",
				"client_ip": "127.0.0.1",
				"datetime": now.strftime("%Y-%m-%dT%H:%M:%S%z"),
				"day_of_week": now.strftime("%w"),
				"day_of_year": now.strftime("%j"),
				}

		return Response(json.dumps(response_json, indent=4), 200, headers, None, None)

	httpserver.expect_request("/time", method="GET").respond_with_handler(time_handler)

	return httpserver


def test_cache_canary(timeserver: HTTPServer):
	# Proves that worldtimeapi.org returns a different time for two sequential requests.
	session = requests.session()

	# target_url = "http://worldtimeapi.org/api/ip"
	target_url = timeserver.url_for("/time")

	response = session.get(target_url)
	assert response.status_code == 200
	original_time = datetime_fromisoformat(response.json()["datetime"])

	response = session.get(target_url)
	assert response.status_code == 200
	current_time = datetime_fromisoformat(response.json()["datetime"])

	assert current_time > original_time


@pytest.mark.parametrize("run_number", [1, 2])
def test_http_cache(testing_http_cache, capsys, run_number: int, timeserver: HTTPServer):
	session = testing_http_cache.session

	# target_url = "http://worldtimeapi.org/api/ip"
	target_url = timeserver.url_for("/time")

	response = session.get(target_url)
	assert response.status_code == 200
	assert not response.from_cache
	original_time = datetime_fromisoformat(response.json()["datetime"])

	response = session.get(target_url)
	assert response.status_code == 200
	assert response.from_cache
	current_time = datetime_fromisoformat(response.json()["datetime"])

	# If the times have changed the cache has failed.
	assert current_time == original_time

	assert testing_http_cache.cache_dir.is_dir()
	assert testing_http_cache.clear()
	assert not testing_http_cache.cache_dir.is_dir()

	# make a new request
	response = session.get(target_url)
	assert response.status_code == 200
	assert not response.from_cache
	current_time = datetime_fromisoformat(response.json()["datetime"])

	assert current_time > original_time

	assert testing_http_cache.clear()