Skip to content

Commit

Permalink
livereload.js html src protocol bug, test_create_app_wrong_name bug, …
Browse files Browse the repository at this point in the history
…test_runserver_with_ssl,

pytest-datafiles requirements plugin
  • Loading branch information
zrvku2000 committed Jan 27, 2025
1 parent d3cbee4 commit f90bdad
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 3 deletions.
4 changes: 2 additions & 2 deletions aiohttp_devtools/runserver/serve.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
except ImportError:
static_root_key = None # type: ignore[assignment]

LIVE_RELOAD_HOST_SNIPPET = '\n<script src="http://{}:{}/livereload.js"></script>\n'
LIVE_RELOAD_HOST_SNIPPET = '\n<script src="{}://{}:{}/livereload.js"></script>\n'
LIVE_RELOAD_LOCAL_SNIPPET = b'\n<script src="/livereload.js"></script>\n'

LAST_RELOAD = web.AppKey("LAST_RELOAD", List[float])
Expand Down Expand Up @@ -84,7 +84,7 @@ async def on_prepare(request: web.Request, response: web.StreamResponse) -> None
or request.path.startswith("/_debugtoolbar")
or "text/html" not in response.content_type):
return
lr_snippet = LIVE_RELOAD_HOST_SNIPPET.format(get_host(request), config.aux_port)
lr_snippet = LIVE_RELOAD_HOST_SNIPPET.format(config.protocol, get_host(request), config.aux_port)
dft_logger.debug("appending live reload snippet '%s' to body", lr_snippet)
response.body += lr_snippet.encode()
response.headers[CONTENT_LENGTH] = str(len(response.body))
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ pytest-mock==3.14.0
pytest-sugar==1.0.0
pytest-timeout==2.2.0
pytest-toolbox==0.4
pytest-datafiles==3.0.0
watchfiles==0.21.0
30 changes: 30 additions & 0 deletions tests/test_certs/server.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
-----BEGIN CERTIFICATE-----
MIIFOjCCAyICFCvF0YymuYiohdstQyrHzWdMOa5gMA0GCSqGSIb3DQEBCwUAMFox
CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl
cm5ldCBXaWRnaXRzIFB0eSBMdGQxEzARBgNVBAMMClRlc3RSb290Q0EwHhcNMjUw
MTI2MTIyMzIyWhcNMzUwMTI0MTIyMzIyWjBZMQswCQYDVQQGEwJBVTETMBEGA1UE
CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk
MRIwEAYDVQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
AoICAQDI5oxS4wjneH21uhBj3bnkVQCTntieysO28zMKJdA8M/LVI6NtX9zJGIiR
Oum00ZmN1ESNIgXXscyeeQuFaK7CNc6JFVMOXoUBukWHhdA3IXotAoS0+6Nt8rc2
joyQzembHuA2BQxHhF8gXXKhW6hk0vAjBjpYLGusxuVOgbvBKzL1VXNblSVGaBUm
xUZ9oZnGJw0HeBphDicGjJEokJMDe70vs9wlZdPDxDy/8iyFf+dPtnfCR1v2wzcT
vxI0lRqcf8n5k2cGAZKsE268/PNKbTyR5J7xqRe9hMhnEdCvxkWLhwQcwTKU1a2H
zXii3zZBh0MkcosZ8PmG/JtTSQRKFFGBa7aFh5oVuw//Kdm8qSEqrEeTXB9Us1eB
OS+kFTb/630kEuvLOc1gB3KcLw43AWLc5u5jzxyEcI6yc6wRxQTcxhEIfbj9tLEe
H9aw4nIsSa7lcOZXVboF5i1XrOC+KeAUPAxRjqttjlxAToZtIOtVPIhnjh1iVAP7
g+Y6iGlc1t1jWN2IJrnlf7NyX98Uf5pr98O2NwyCcz0rpZPxHdLNE6/Wk2EugQJ9
fNTEDn9rYW1iw1VMETZ/A53kCOIvse/KxS6aoWq4iPtfgzS3928DN7fZ5wJ5rYuv
pHBzzsFqkY+Oy341s91LIq6ZImTaIWd22KjU1hqdu+2MlCWPTQIDAQABMA0GCSqG
SIb3DQEBCwUAA4ICAQBEpPAoiWFX6st160wz/wJLqlgrr53iQwGyP/CttTE/LNHa
g+bVeJ14fsnwk47+DFxJbWuo3YipVEaIXXqdI2BUgNZLUrfBNGIdq4G0K1KcNeQf
O+Qql5he8LV9TKHj9N6efoZbQFWXixhkJwzb08XVEfWwUt4rDbFWLfEKLMpucRGw
1E5hB/92HuM9yB7ao5sXsMNddvlS4wVLThIw5pr/170nB3uHQXTVnAif0301SMk/
i4wD7wevC9gz+40zbyC2HSsKhS2s+Jjey0/nSack8l5dISMp1XCJweG51Vb8F9Ml
5JZWdlw9J6cbJhw++oOBktLMCmnTiTP67aYlJhgrQeyPQXcg2uYDNlsK5nPqFxWZ
qjdvB6FMI9wS7LJylI9wJHDcG18+U8LrYnDIlotN2OJE7RVP/fYsAHCSswBwl0kH
3y1xIthILUSL1vCUXIZcI6hYSkxGlxTSd4KQoioVHr4/uavIIJxtf1dfkGRvVAEu
vo92OIiXpZ6Rhf4WUXaV6/kB9Jwkj0lJZ2RUw/CSVS8v3g4m4d09TsrhxKKmZxz7
m+vsVopyBewXHHXKcmzI1OO5hL1wyjSx1TIAlr9MW3o3umvSgh3hM3Ildcmni8Xt
tUa95NKvjruz8UJ8gE50TZgsJI4ywT1QSyC55bfv74kKzHysv6zAj19y2CE0Hg==
-----END CERTIFICATE-----
51 changes: 51 additions & 0 deletions tests/test_certs/server.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJJwIBAAKCAgEAyOaMUuMI53h9tboQY9255FUAk57YnsrDtvMzCiXQPDPy1SOj
bV/cyRiIkTrptNGZjdREjSIF17HMnnkLhWiuwjXOiRVTDl6FAbpFh4XQNyF6LQKE
tPujbfK3No6MkM3pmx7gNgUMR4RfIF1yoVuoZNLwIwY6WCxrrMblToG7wSsy9VVz
W5UlRmgVJsVGfaGZxicNB3gaYQ4nBoyRKJCTA3u9L7PcJWXTw8Q8v/IshX/nT7Z3
wkdb9sM3E78SNJUanH/J+ZNnBgGSrBNuvPzzSm08keSe8akXvYTIZxHQr8ZFi4cE
HMEylNWth814ot82QYdDJHKLGfD5hvybU0kEShRRgWu2hYeaFbsP/ynZvKkhKqxH
k1wfVLNXgTkvpBU2/+t9JBLryznNYAdynC8ONwFi3ObuY88chHCOsnOsEcUE3MYR
CH24/bSxHh/WsOJyLEmu5XDmV1W6BeYtV6zgvingFDwMUY6rbY5cQE6GbSDrVTyI
Z44dYlQD+4PmOohpXNbdY1jdiCa55X+zcl/fFH+aa/fDtjcMgnM9K6WT8R3SzROv
1pNhLoECfXzUxA5/a2FtYsNVTBE2fwOd5AjiL7HvysUumqFquIj7X4M0t/dvAze3
2ecCea2Lr6Rwc87BapGPjst+NbPdSyKumSJk2iFndtio1NYanbvtjJQlj00CAwEA
AQKCAgAKPqd9OpKjqyNN9xUK4q2uFR+YZ4tIXbKpS7GYnOEHkOabM9wLoc3Se2vL
bCOq0t1vvBla0RdXLnvuwOFzhikTQkcr+mhn3S4PLn6JMKuzhAOE9BHsYMCuxKfP
ImnMoJN/E43/czZzFy76qYlE7TWjHpacUp77DBjZkLL00+zNJvTMSfU+AFcMRhZ+
CaVUlr8OucMSVG+T73LSBK0KUoUMsmytWBCr34ty+jjW2PSoQiN7jySARb9M0Buo
6B93ivr2bBXSok+ooL/oAn2tKYEGlJd4IR5x2FubkH/fsargq820FciB5uA7csIM
oM+8DoHnyYwE+cpaIk23Mn6BOsH7JffsIYJfdZWfzD3yiC/h2Ha6ohIC8zuYxU2c
kqnV4UdGlXwoFP4Mhhi+DyMQ9L+Ty9RidLbgJ27FMaLkri26REImEhyljCYutvuk
d0vB0gwJW1K0j/2/p5TQnLBkNJlDdfzhoCsb2EWU17Tx2bHiJYlbPbiyyRK9c/Wx
2/i9GfTIz09FllBHhynfqhC7zOFq9VXxHIEsYj2skSiPUi95Doheupz8U2wcsVTm
6/T++I7VxJJBeU+GF91Q9KJVrO0rFxmyolynRHHcnrzNaBVLHGdDWyiglHaI/3Hj
hP2co++b4HhzC+/4cpLPY3+sLrNSdWVCkZ1A+uCwfr5OnWrpwQKCAQEA5wXsdSRm
s2pdU0vZarqtRFq2F+7ZjAHtp6WEGrwcFwjirhCG+ko/K0PSqEci7WiL07/Zsw26
0e+Nq30u4VTz9/DbUFzuFlR3N+/al6MrX1O5mOkrgb7s3YcBNWKmURTv5spP5MAo
uXz4Mv4KMKksO6gTEM9NDGiik6e3CBs2tyYvmCTzhY+PwLAldEQDJZQ6xAK3V7i/
xcMumCS9z5EYcdF0DDx5m7mJGL9BF1OwWd2kAxssGDkihs2ZXvtgzxzT2xdVizZW
3ojS/c2KIrIp6J8tvLMzCIrvkK9rpy+WTNzOiIor/4Fhh35YrAaVO/APnS79kFDB
NY2H8jfY7LJtSQKCAQEA3p7qfkTVJGGO5d7mAR6ZGG/P+soEU6xW4/ybgieAZpM8
5Phd45z89IyataR+8GjOTo+v7hGePcGFhcoZA62kKPejX72gs2bB1x3W/k169M43
TB02lkXuHv/8wxletQAK/L0++aKQccwruK8iw6Yc8AZguoL8CXqlMaRJQiS1emfi
71sRfCSeNJmLCqOIiRkSg0xzBUmN2cP672KKu+fw4JjruJLZy6cGRfn8chKzYDuR
fKc8rS2sRL+L3ufjpWg1+lP1c6DQn2gFDquZ3e20YapRY20nyAObbRH3KvlZLQAM
BQNHMN6eW50mYA3rNMe805nCci6DdE0YSFiatAxl5QKCAQBflgfcAA+uNFgg2sU+
b7a5DX9CL8U7NKEMOGOMXECTF04TDyuJ66ZvVESY87Xz3Mnd9wcwGoIt0pwfVFBN
U0UOVU2o1op8Gr6pGkirbQvJCW9FYVRq/oAquG07lXGTIsKQDy03THqNJLPdBVda
AuUWWdhpoBwVAkYiKcaFSB0/ckFHBiLsJBYqd7dHf8x9g/M8npMVbI+MV9GziaAv
fa1LiooldfArCn07DAb2i93vkNEHp/p6m0k51V+b+Q55I0hU4ja2vuj6cko6UQzS
hjzoztOxu8NlyXaNusckCYB6lPGvdNv3f6TG1vQBWUft4MnVE1g+mesXKVQSWCEc
7kZhAoIBAEx3i5ZJsGiptfrRYHG7/9w789Vx9KCFDueKyiOfy+Pv6TfA9AcN0nlx
nmaMFSog5dRoWIbOuGsAAQweig8QYtXLket96CgXQLfSQRnipTxXZPkZA7oEVTGC
vmCJY1WKqTt9CZeXtkPQXKg4SBmqAkCUAD+wZEAhR4LQqnU0xL1B19pdjpj0vv7U
SsUhvPFSkmBVLyD+zeGiBpyZXYwDtGKBRF6G2pawTWBV6NeKAuEoNOX7T8Uwbf7D
SJkNT81uCTRuCF5qO561jR8n5Fctogr2BLTBNqvmSUnipOK2+WGSpY5HPPnVTdGs
HhVaUpMzlHGeXAL6ZR7aqF+ZR7JWm90CggEAG0OKsZHAIGdeKDZnzu8lQqBWfGCp
9VXz9tcw7EQPn+KZ30FzxZsI5hHvxVKYHmjVDGbKChc0aq2nD8H29ONeDdpQxjSF
7dIZckzz45l3vUZco8b2V2SBgnv7XY4iTefhsbMs9y+cVCTByAXULtmOTZclgep8
Ss0r2tX6kLpfQrCSitk+449dYXqm/pEZGw1+19LEZ8tZNhzTsJOtOddIh0FFJW14
jClxlJvs0iSWfX3ihR/bgXIkxyXJcO/FGRxytek8ngWvkCZm8cFQMqrCNkRN4F60
UKZPPgyCsBX5A1W9QuHdklCCARxCZ8xdkSs8ecb+QrJZvpawskUqSn8KKg==
-----END RSA PRIVATE KEY-----
2 changes: 1 addition & 1 deletion tests/test_runserver_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ async def test_create_app_wrong_name(tmpworkdir):
mktree(tmpworkdir, SIMPLE_APP)
config = Config(app_path='app.py', app_factory_name='missing')
with pytest.raises(AiohttpDevConfigError) as excinfo:
module = config.import_module
module = config.import_module()
config.get_app_factory(module)
assert excinfo.value.args[0] == "Module 'app.py' does not define a 'missing' attribute/class"

Expand Down
109 changes: 109 additions & 0 deletions tests/test_runserver_with_ssl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import asyncio
import json
from unittest import mock

import aiohttp
import pytest
from aiohttp import ClientTimeout
from pytest_toolbox import mktree
import ssl

from aiohttp_devtools.runserver import runserver
from aiohttp_devtools.runserver.config import Config

from aiohttp_devtools.exceptions import AiohttpDevConfigError


from .conftest import forked


async def test_load_invalid_app(tmpworkdir):
mktree(tmpworkdir, {
'invalid': "it's not python file)"
})
with pytest.raises(AiohttpDevConfigError):
Config(app_path='invalid')

async def check_server_running(check_callback, sslcontext):
port_open = False
async with aiohttp.ClientSession(timeout=ClientTimeout(total=1)) as session:
for i in range(50): # pragma: no branch
try:
async with session.get('https://localhost:8443/', ssl=sslcontext):
pass
except OSError:
await asyncio.sleep(0.1)
else:
port_open = True
break
assert port_open
await check_callback(session, sslcontext)
await asyncio.sleep(.25) # TODO(aiohttp 4): Remove this hack

@pytest.mark.filterwarnings(r"ignore:unclosed:ResourceWarning")
@forked
@pytest.mark.datafiles('tests/test_certs', keep_top_dir = True)
def test_start_runserver(datafiles, tmpworkdir, smart_caplog):
mktree(tmpworkdir, {
'app.py': """\
from aiohttp import web
import ssl
async def hello(request):
return web.Response(text='<h1>hello world</h1>', content_type='text/html')
async def has_error(request):
raise ValueError()
def create_app():
app = web.Application()
app.router.add_get('/', hello)
app.router.add_get('/error', has_error)
return app
def get_ssl_context():
ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_context.load_cert_chain('test_certs/server.crt', 'test_certs/server.key')
return ssl_context
""",
'static_dir/foo.js': 'var bar=1;',
})
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
args = runserver(app_path="app.py", static_path="static_dir", bind_address="0.0.0.0", ssl_context_factory_name='get_ssl_context')
aux_app = args["app"]
aux_port = args["port"]
runapp_host = args["host"]
assert isinstance(aux_app, aiohttp.web.Application)
assert aux_port == 8444
assert runapp_host == "0.0.0.0"
for startup in aux_app.on_startup:
loop.run_until_complete(startup(aux_app))
sslcontext = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
async def check_callback(session, sslcontext):
print(session, sslcontext)
async with session.get('https://localhost:8443/', ssl=sslcontext) as r:
assert r.status == 200
assert r.headers['content-type'].startswith('text/html')
text = await r.text()
print(text)
assert '<h1>hello world</h1>' in text
assert '<script src="https://localhost:8444/livereload.js"></script>' in text

async with session.get('https://localhost:8443/error', ssl=sslcontext) as r:
assert r.status == 500
assert 'raise ValueError()' in (await r.text())

try:
loop.run_until_complete(check_server_running(check_callback, sslcontext))
finally:
for shutdown in aux_app.on_shutdown:
loop.run_until_complete(shutdown(aux_app))
loop.run_until_complete(aux_app.cleanup())
assert (
'adev.server.dft INFO: Starting aux server at https://localhost:8444 ◆\n'
'adev.server.dft INFO: serving static files from ./static_dir/ at https://localhost:8444/static/\n'
'adev.server.dft INFO: Starting dev server at https://localhost:8443 ●\n'
) in smart_caplog
loop.run_until_complete(asyncio.sleep(.25)) # TODO(aiohttp 4): Remove this hack


0 comments on commit f90bdad

Please sign in to comment.