quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

test_01_basic.py (13004B)


      1 #!/usr/bin/env python3
      2 # -*- coding: utf-8 -*-
      3 #***************************************************************************
      4 #                                  _   _ ____  _
      5 #  Project                     ___| | | |  _ \| |
      6 #                             / __| | | | |_) | |
      7 #                            | (__| |_| |  _ <| |___
      8 #                             \___|\___/|_| \_\_____|
      9 #
     10 # Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
     11 #
     12 # This software is licensed as described in the file COPYING, which
     13 # you should have received as part of this distribution. The terms
     14 # are also available at https://curl.se/docs/copyright.html.
     15 #
     16 # You may opt to use, copy, modify, merge, publish, distribute and/or sell
     17 # copies of the Software, and permit persons to whom the Software is
     18 # furnished to do so, under the terms of the COPYING file.
     19 #
     20 # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
     21 # KIND, either express or implied.
     22 #
     23 # SPDX-License-Identifier: curl
     24 #
     25 ###########################################################################
     26 #
     27 import logging
     28 import pytest
     29 
     30 from testenv import Env
     31 from testenv import CurlClient
     32 
     33 
     34 log = logging.getLogger(__name__)
     35 
     36 
     37 class TestBasic:
     38 
     39     # simple http: GET
     40     def test_01_01_http_get(self, env: Env, httpd):
     41         curl = CurlClient(env=env)
     42         url = f'http://{env.domain1}:{env.http_port}/data.json'
     43         r = curl.http_get(url=url)
     44         r.check_response(http_status=200)
     45         assert r.json['server'] == env.domain1
     46 
     47     # simple https: GET, any http version
     48     @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason="curl without SSL")
     49     def test_01_02_https_get(self, env: Env, httpd):
     50         curl = CurlClient(env=env)
     51         url = f'https://{env.domain1}:{env.https_port}/data.json'
     52         r = curl.http_get(url=url)
     53         r.check_response(http_status=200)
     54         assert r.json['server'] == env.domain1
     55 
     56     # simple https: GET, h2 wanted and got
     57     @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason="curl without SSL")
     58     def test_01_03_h2_get(self, env: Env, httpd):
     59         curl = CurlClient(env=env)
     60         url = f'https://{env.domain1}:{env.https_port}/data.json'
     61         r = curl.http_get(url=url, extra_args=['--http2'])
     62         r.check_response(http_status=200, protocol='HTTP/2')
     63         assert r.json['server'] == env.domain1
     64 
     65     # simple https: GET, h2 unsupported, fallback to h1
     66     @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason="curl without SSL")
     67     def test_01_04_h2_unsupported(self, env: Env, httpd):
     68         curl = CurlClient(env=env)
     69         url = f'https://{env.domain2}:{env.https_port}/data.json'
     70         r = curl.http_get(url=url, extra_args=['--http2'])
     71         r.check_response(http_status=200, protocol='HTTP/1.1')
     72         assert r.json['server'] == env.domain2
     73 
     74     # simple h3: GET, want h3 and get it
     75     @pytest.mark.skipif(condition=not Env.have_h3(), reason="h3 not supported")
     76     def test_01_05_h3_get(self, env: Env, httpd, nghttpx):
     77         curl = CurlClient(env=env)
     78         url = f'https://{env.domain1}:{env.h3_port}/data.json'
     79         r = curl.http_get(url=url, extra_args=['--http3-only'])
     80         r.check_response(http_status=200, protocol='HTTP/3')
     81         assert r.json['server'] == env.domain1
     82 
     83     # simple download, check connect/handshake timings
     84     @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason="curl without SSL")
     85     @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
     86     def test_01_06_timings(self, env: Env, httpd, nghttpx, proto):
     87         if proto == 'h3' and not env.have_h3():
     88             pytest.skip("h3 not supported")
     89         curl = CurlClient(env=env)
     90         url = f'https://{env.authority_for(env.domain1, proto)}/data.json'
     91         r = curl.http_download(urls=[url], alpn_proto=proto, with_stats=True)
     92         r.check_stats(http_status=200, count=1,
     93                       remote_port=env.port_for(alpn_proto=proto),
     94                       remote_ip='127.0.0.1')
     95         # there are cases where time_connect is reported as 0
     96         assert r.stats[0]['time_connect'] >= 0, f'{r.stats[0]}'
     97         assert r.stats[0]['time_appconnect'] > 0, f'{r.stats[0]}'
     98 
     99     # simple https: HEAD
    100     @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
    101     @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason="curl without SSL")
    102     def test_01_07_head(self, env: Env, httpd, nghttpx, proto):
    103         if proto == 'h3' and not env.have_h3():
    104             pytest.skip("h3 not supported")
    105         curl = CurlClient(env=env)
    106         url = f'https://{env.authority_for(env.domain1, proto)}/data.json'
    107         r = curl.http_download(urls=[url], with_stats=True, with_headers=True,
    108                                extra_args=['-I'])
    109         r.check_stats(http_status=200, count=1, exitcode=0,
    110                       remote_port=env.port_for(alpn_proto=proto),
    111                       remote_ip='127.0.0.1')
    112         # got the Content-Length: header, but did not download anything
    113         assert r.responses[0]['header']['content-length'] == '30', f'{r.responses[0]}'
    114         assert r.stats[0]['size_download'] == 0, f'{r.stats[0]}'
    115 
    116     # http: GET for HTTP/2, see Upgrade:, 101 switch
    117     def test_01_08_h2_upgrade(self, env: Env, httpd):
    118         curl = CurlClient(env=env)
    119         url = f'http://{env.domain1}:{env.http_port}/data.json'
    120         r = curl.http_get(url=url, extra_args=['--http2'])
    121         r.check_exit_code(0)
    122         assert len(r.responses) == 2, f'{r.responses}'
    123         assert r.responses[0]['status'] == 101, f'{r.responses[0]}'
    124         assert r.responses[1]['status'] == 200, f'{r.responses[1]}'
    125         assert r.responses[1]['protocol'] == 'HTTP/2', f'{r.responses[1]}'
    126         assert r.json['server'] == env.domain1
    127 
    128     # http: GET for HTTP/2 with prior knowledge
    129     def test_01_09_h2_prior_knowledge(self, env: Env, httpd):
    130         curl = CurlClient(env=env)
    131         url = f'http://{env.domain1}:{env.http_port}/data.json'
    132         r = curl.http_get(url=url, extra_args=['--http2-prior-knowledge'])
    133         r.check_exit_code(0)
    134         assert len(r.responses) == 1, f'{r.responses}'
    135         assert r.response['status'] == 200, f'{r.responsw}'
    136         assert r.response['protocol'] == 'HTTP/2', f'{r.response}'
    137         assert r.json['server'] == env.domain1
    138 
    139     # http: strip TE header in HTTP/2 requests
    140     def test_01_10_te_strip(self, env: Env, httpd):
    141         curl = CurlClient(env=env)
    142         url = f'https://{env.authority_for(env.domain1, "h2")}/data.json'
    143         r = curl.http_get(url=url, extra_args=['--http2', '-H', 'TE: gzip'])
    144         r.check_exit_code(0)
    145         assert len(r.responses) == 1, f'{r.responses}'
    146         assert r.responses[0]['status'] == 200, f'{r.responses[1]}'
    147         assert r.responses[0]['protocol'] == 'HTTP/2', f'{r.responses[1]}'
    148 
    149     # http: large response headers
    150     # send 48KB+ sized response headers to check we handle that correctly
    151     # larger than 64KB headers expose a bug in Apache HTTP/2 that is not
    152     # RSTing the stream correctly when its internal limits are exceeded.
    153     @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
    154     def test_01_11_large_resp_headers(self, env: Env, httpd, proto):
    155         if proto == 'h3' and not env.have_h3():
    156             pytest.skip("h3 not supported")
    157         curl = CurlClient(env=env)
    158         url = f'https://{env.authority_for(env.domain1, proto)}' \
    159             f'/curltest/tweak?x-hd={48 * 1024}'
    160         r = curl.http_get(url=url, alpn_proto=proto, extra_args=[])
    161         r.check_exit_code(0)
    162         assert len(r.responses) == 1, f'{r.responses}'
    163         assert r.responses[0]['status'] == 200, f'{r.responses}'
    164 
    165     # http: response headers larger than what curl buffers for
    166     @pytest.mark.skipif(condition=not Env.httpd_is_at_least('2.4.64'),
    167                         reason='httpd must be at least 2.4.64')
    168     @pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
    169     def test_01_12_xlarge_resp_headers(self, env: Env, httpd, configures_httpd, proto):
    170         httpd.set_extra_config('base', [
    171             f'H2MaxHeaderBlockLen {130 * 1024}',
    172         ])
    173         httpd.reload_if_config_changed()
    174         curl = CurlClient(env=env)
    175         url = f'https://{env.authority_for(env.domain1, proto)}' \
    176             f'/curltest/tweak?x-hd={128 * 1024}'
    177         r = curl.http_get(url=url, alpn_proto=proto, extra_args=[])
    178         r.check_exit_code(0)
    179         assert len(r.responses) == 1, f'{r.responses}'
    180         assert r.responses[0]['status'] == 200, f'{r.responses}'
    181 
    182     # http: 1 response header larger than what curl buffers for
    183     @pytest.mark.skipif(condition=not Env.httpd_is_at_least('2.4.64'),
    184                         reason='httpd must be at least 2.4.64')
    185     @pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
    186     def test_01_13_megalarge_resp_headers(self, env: Env, httpd, configures_httpd, proto):
    187         httpd.set_extra_config('base', [
    188             'LogLevel http2:trace2',
    189             f'H2MaxHeaderBlockLen {130 * 1024}',
    190         ])
    191         httpd.reload_if_config_changed()
    192         curl = CurlClient(env=env)
    193         url = f'https://{env.authority_for(env.domain1, proto)}' \
    194             f'/curltest/tweak?x-hd1={128 * 1024}'
    195         r = curl.http_get(url=url, alpn_proto=proto, extra_args=[])
    196         if proto == 'h2':
    197             r.check_exit_code(16)  # CURLE_HTTP2
    198         else:
    199             r.check_exit_code(100)  # CURLE_TOO_LARGE
    200 
    201     # http: several response headers, together > 256 KB
    202     # nghttp2 error -905: Too many CONTINUATION frames following a HEADER frame
    203     @pytest.mark.skipif(condition=not Env.httpd_is_at_least('2.4.64'),
    204                         reason='httpd must be at least 2.4.64')
    205     @pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
    206     def test_01_14_gigalarge_resp_headers(self, env: Env, httpd, configures_httpd, proto):
    207         httpd.set_extra_config('base', [
    208             'LogLevel http2:trace2',
    209             f'H2MaxHeaderBlockLen {1024 * 1024}',
    210         ])
    211         httpd.reload_if_config_changed()
    212         curl = CurlClient(env=env)
    213         url = f'https://{env.authority_for(env.domain1, proto)}' \
    214             f'/curltest/tweak?x-hd={256 * 1024}'
    215         r = curl.http_get(url=url, alpn_proto=proto, extra_args=[])
    216         if proto == 'h2':
    217             r.check_exit_code(16)  # CURLE_HTTP2
    218         else:
    219             r.check_exit_code(0)   # 1.1 can do
    220 
    221     # http: one response header > 256 KB
    222     @pytest.mark.skipif(condition=not Env.httpd_is_at_least('2.4.64'),
    223                         reason='httpd must be at least 2.4.64')
    224     @pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
    225     def test_01_15_gigalarge_resp_headers(self, env: Env, httpd, configures_httpd, proto):
    226         httpd.set_extra_config('base', [
    227             'LogLevel http2:trace2',
    228             f'H2MaxHeaderBlockLen {1024 * 1024}',
    229         ])
    230         httpd.reload_if_config_changed()
    231         curl = CurlClient(env=env)
    232         url = f'https://{env.authority_for(env.domain1, proto)}' \
    233             f'/curltest/tweak?x-hd1={256 * 1024}'
    234         r = curl.http_get(url=url, alpn_proto=proto, extra_args=[])
    235         if proto == 'h2':
    236             r.check_exit_code(16)  # CURLE_HTTP2
    237         else:
    238             r.check_exit_code(100)  # CURLE_TOO_LARGE
    239 
    240     # http: invalid request headers, GET, issue #16998
    241     @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
    242     def test_01_16_inv_req_get(self, env: Env, httpd, nghttpx, proto):
    243         if proto == 'h3' and not env.have_h3():
    244             pytest.skip("h3 not supported")
    245         curl = CurlClient(env=env)
    246         url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo'
    247         r = curl.http_get(url=url, alpn_proto=proto, extra_args=[
    248             '-H', "a: a\x0ab"
    249         ])
    250         # on h1, request is sent, h2/h3 reject
    251         if proto == 'http/1.1':
    252             r.check_exit_code(0)
    253         else:
    254             r.check_exit_code(43)
    255 
    256     # http: special handling of TE request header
    257     @pytest.mark.parametrize("te_in, te_out", [
    258         pytest.param('trailers', 'trailers', id='trailers'),
    259         pytest.param('chunked', None, id='chunked'),
    260         pytest.param('gzip, trailers', 'trailers', id='gzip+trailers'),
    261         pytest.param('gzip ;q=0.2;x="y,x", trailers', 'trailers', id='gzip+q+x+trailers'),
    262         pytest.param('gzip ;x="trailers", chunks', None, id='gzip+x+chunks'),
    263     ])
    264     def test_01_17_TE(self, env: Env, httpd, te_in, te_out):
    265         proto = 'h2'
    266         curl = CurlClient(env=env)
    267         url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo'
    268         r = curl.http_download(urls=[url], alpn_proto=proto, with_stats=True,
    269                                with_headers=True,
    270                                extra_args=['-H', f'TE: {te_in}'])
    271         r.check_response(200)
    272         if te_out is not None:
    273             assert r.responses[0]['header']['request-te'] == te_out, f'{r.responses[0]}'
    274         else:
    275             assert 'request-te' not in r.responses[0]['header'], f'{r.responses[0]}'