unit/test/test_asgi_application.py
Andrei Zeliankou c183bd8749 Tests: get rid of classes in test files.
Class usage came from the unittest framework and it was always redundant
after migration to the pytest.  This commit removes classes from files
containing tests to make them more readable and understandable.
2023-06-14 18:20:09 +01:00

476 lines
11 KiB
Python

import re
import time
import pytest
from packaging import version
from unit.applications.lang.python import ApplicationPython
prerequisites = {
'modules': {'python': lambda v: version.parse(v) >= version.parse('3.5')}
}
client = ApplicationPython(load_module='asgi')
def test_asgi_application_variables(date_to_sec_epoch, sec_epoch):
client.load('variables')
body = 'Test body string.'
resp = client.http(
f"""POST / HTTP/1.1
Host: localhost
Content-Length: {len(body)}
Custom-Header: blah
Custom-hEader: Blah
Content-Type: text/html
Connection: close
custom-header: BLAH
{body}""".encode(),
raw=True,
)
assert resp['status'] == 200, 'status'
headers = resp['headers']
header_server = headers.pop('Server')
assert re.search(r'Unit/[\d\.]+', header_server), 'server header'
date = headers.pop('Date')
assert date[-4:] == ' GMT', 'date header timezone'
assert abs(date_to_sec_epoch(date) - sec_epoch) < 5, 'date header'
assert headers == {
'Connection': 'close',
'content-length': str(len(body)),
'content-type': 'text/html',
'request-method': 'POST',
'request-uri': '/',
'http-host': 'localhost',
'http-version': '1.1',
'custom-header': 'blah, Blah, BLAH',
'asgi-version': '3.0',
'asgi-spec-version': '2.1',
'scheme': 'http',
}, 'headers'
assert resp['body'] == body, 'body'
def test_asgi_application_ipv6():
client.load('empty')
assert 'success' in client.conf(
{"[::1]:7080": {"pass": "applications/empty"}}, 'listeners'
)
assert client.get(sock_type='ipv6')['status'] == 200
def test_asgi_application_unix(temp_dir):
client.load('empty')
addr = f'{temp_dir}/sock'
assert 'success' in client.conf(
{f"unix:{addr}": {"pass": "applications/empty"}}, 'listeners'
)
assert client.get(sock_type='unix', addr=addr)['status'] == 200
def test_asgi_application_query_string():
client.load('query_string')
resp = client.get(url='/?var1=val1&var2=val2')
assert (
resp['headers']['query-string'] == 'var1=val1&var2=val2'
), 'query-string header'
def test_asgi_application_prefix():
client.load('prefix', prefix='/api/rest')
def set_prefix(prefix):
client.conf(f'"{prefix}"', 'applications/prefix/prefix')
def check_prefix(url, prefix):
resp = client.get(url=url)
assert resp['status'] == 200
assert resp['headers']['prefix'] == prefix
check_prefix('/ap', 'NULL')
check_prefix('/api', 'NULL')
check_prefix('/api/', 'NULL')
check_prefix('/api/res', 'NULL')
check_prefix('/api/restful', 'NULL')
check_prefix('/api/rest', '/api/rest')
check_prefix('/api/rest/', '/api/rest')
check_prefix('/api/rest/get', '/api/rest')
check_prefix('/api/rest/get/blah', '/api/rest')
set_prefix('/api/rest/')
check_prefix('/api/rest', '/api/rest')
check_prefix('/api/restful', 'NULL')
check_prefix('/api/rest/', '/api/rest')
check_prefix('/api/rest/blah', '/api/rest')
set_prefix('/app')
check_prefix('/ap', 'NULL')
check_prefix('/app', '/app')
check_prefix('/app/', '/app')
check_prefix('/application/', 'NULL')
set_prefix('/')
check_prefix('/', 'NULL')
check_prefix('/app', 'NULL')
def test_asgi_application_query_string_space():
client.load('query_string')
resp = client.get(url='/ ?var1=val1&var2=val2')
assert (
resp['headers']['query-string'] == 'var1=val1&var2=val2'
), 'query-string space'
resp = client.get(url='/ %20?var1=val1&var2=val2')
assert (
resp['headers']['query-string'] == 'var1=val1&var2=val2'
), 'query-string space 2'
resp = client.get(url='/ %20 ?var1=val1&var2=val2')
assert (
resp['headers']['query-string'] == 'var1=val1&var2=val2'
), 'query-string space 3'
resp = client.get(url='/blah %20 blah? var1= val1 & var2=val2')
assert (
resp['headers']['query-string'] == ' var1= val1 & var2=val2'
), 'query-string space 4'
def test_asgi_application_query_string_empty():
client.load('query_string')
resp = client.get(url='/?')
assert resp['status'] == 200, 'query string empty status'
assert resp['headers']['query-string'] == '', 'query string empty'
def test_asgi_application_query_string_absent():
client.load('query_string')
resp = client.get()
assert resp['status'] == 200, 'query string absent status'
assert resp['headers']['query-string'] == '', 'query string absent'
@pytest.mark.skip('not yet')
def test_asgi_application_server_port():
client.load('server_port')
assert (
client.get()['headers']['Server-Port'] == '7080'
), 'Server-Port header'
@pytest.mark.skip('not yet')
def test_asgi_application_working_directory_invalid():
client.load('empty')
assert 'success' in client.conf(
'"/blah"', 'applications/empty/working_directory'
), 'configure invalid working_directory'
assert client.get()['status'] == 500, 'status'
def test_asgi_application_204_transfer_encoding():
client.load('204_no_content')
assert (
'Transfer-Encoding' not in client.get()['headers']
), '204 header transfer encoding'
def test_asgi_application_shm_ack_handle():
# Minimum possible limit
shm_limit = 10 * 1024 * 1024
client.load('mirror', limits={"shm": shm_limit})
# Should exceed shm_limit
max_body_size = 12 * 1024 * 1024
assert 'success' in client.conf(
f'{{"http":{{"max_body_size": {max_body_size} }}}}',
'settings',
)
assert client.get()['status'] == 200, 'init'
body = '0123456789AB' * 1024 * 1024 # 12 Mb
resp = client.post(body=body, read_buffer_size=1024 * 1024)
assert resp['body'] == body, 'keep-alive 1'
def test_asgi_keepalive_body():
client.load('mirror')
assert client.get()['status'] == 200, 'init'
body = '0123456789' * 500
(resp, sock) = client.post(
headers={
'Host': 'localhost',
'Connection': 'keep-alive',
},
start=True,
body=body,
read_timeout=1,
)
assert resp['body'] == body, 'keep-alive 1'
body = '0123456789'
resp = client.post(sock=sock, body=body)
assert resp['body'] == body, 'keep-alive 2'
def test_asgi_keepalive_reconfigure():
client.load('mirror')
assert client.get()['status'] == 200, 'init'
body = '0123456789'
conns = 3
socks = []
for i in range(conns):
(resp, sock) = client.post(
headers={
'Host': 'localhost',
'Connection': 'keep-alive',
},
start=True,
body=body,
read_timeout=1,
)
assert resp['body'] == body, 'keep-alive open'
client.load('mirror', processes=i + 1)
socks.append(sock)
for i in range(conns):
(resp, sock) = client.post(
headers={
'Host': 'localhost',
'Connection': 'keep-alive',
},
start=True,
sock=socks[i],
body=body,
read_timeout=1,
)
assert resp['body'] == body, 'keep-alive request'
client.load('mirror', processes=i + 1)
for i in range(conns):
resp = client.post(sock=socks[i], body=body)
assert resp['body'] == body, 'keep-alive close'
client.load('mirror', processes=i + 1)
def test_asgi_keepalive_reconfigure_2():
client.load('mirror')
assert client.get()['status'] == 200, 'init'
body = '0123456789'
(resp, sock) = client.post(
headers={
'Host': 'localhost',
'Connection': 'keep-alive',
},
start=True,
body=body,
read_timeout=1,
)
assert resp['body'] == body, 'reconfigure 2 keep-alive 1'
client.load('empty')
assert client.get()['status'] == 200, 'init'
(resp, sock) = client.post(start=True, sock=sock, body=body)
assert resp['status'] == 200, 'reconfigure 2 keep-alive 2'
assert resp['body'] == '', 'reconfigure 2 keep-alive 2 body'
assert 'success' in client.conf(
{"listeners": {}, "applications": {}}
), 'reconfigure 2 clear configuration'
resp = client.get(sock=sock)
assert resp == {}, 'reconfigure 2 keep-alive 3'
def test_asgi_keepalive_reconfigure_3():
client.load('empty')
assert client.get()['status'] == 200, 'init'
sock = client.http(
b"""GET / HTTP/1.1
""",
raw=True,
no_recv=True,
)
assert client.get()['status'] == 200
assert 'success' in client.conf(
{"listeners": {}, "applications": {}}
), 'reconfigure 3 clear configuration'
resp = client.http(
b"""Host: localhost
Connection: close
""",
sock=sock,
raw=True,
)
assert resp['status'] == 200, 'reconfigure 3'
def test_asgi_process_switch():
client.load('delayed', processes=2)
client.get(
headers={
'Host': 'localhost',
'Content-Length': '0',
'X-Delay': '5',
'Connection': 'close',
},
no_recv=True,
)
headers_delay_1 = {
'Connection': 'close',
'Host': 'localhost',
'Content-Length': '0',
'X-Delay': '1',
}
client.get(headers=headers_delay_1, no_recv=True)
time.sleep(0.5)
for _ in range(10):
client.get(headers=headers_delay_1, no_recv=True)
client.get(headers=headers_delay_1)
def test_asgi_application_loading_error(skip_alert):
skip_alert(r'Python failed to import module "blah"')
client.load('empty', module="blah")
assert client.get()['status'] == 503, 'loading error'
def test_asgi_application_threading(wait_for_record):
"""wait_for_record() timeouts after 5s while every thread works at
least 3s. So without releasing GIL test should fail.
"""
client.load('threading')
for _ in range(10):
client.get(no_recv=True)
assert (
wait_for_record(r'\(5\) Thread: 100', wait=50) is not None
), 'last thread finished'
def test_asgi_application_threads():
client.load('threads', threads=2)
socks = []
for _ in range(2):
sock = client.get(
headers={
'Host': 'localhost',
'X-Delay': '3',
'Connection': 'close',
},
no_recv=True,
)
socks.append(sock)
time.sleep(1.0) # required to avoid greedy request reading
threads = set()
for sock in socks:
resp = client.recvall(sock).decode('utf-8')
client.log_in(resp)
resp = client._resp_to_dict(resp)
assert resp['status'] == 200, 'status'
threads.add(resp['headers']['x-thread'])
sock.close()
assert len(socks) == len(threads), 'threads differs'
def test_asgi_application_legacy():
client.load('legacy')
resp = client.get(
headers={
'Host': 'localhost',
'Content-Length': '0',
'Connection': 'close',
},
)
assert resp['status'] == 200, 'status'
def test_asgi_application_legacy_force():
client.load('legacy_force', protocol='asgi')
resp = client.get(
headers={
'Host': 'localhost',
'Content-Length': '0',
'Connection': 'close',
},
)
assert resp['status'] == 200, 'status'