2022-12-18 08:29:58 +00:00
|
|
|
try: # no cov
|
|
|
|
from ujson import dumps
|
|
|
|
except ModuleNotFoundError: # no cov
|
|
|
|
from json import dumps # type: ignore
|
2022-09-18 15:17:23 +01:00
|
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
from unittest.mock import Mock, patch
|
2022-12-18 08:29:58 +00:00
|
|
|
from urllib.error import URLError
|
2022-09-18 15:17:23 +01:00
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
2022-12-18 08:29:58 +00:00
|
|
|
from sanic_testing import TestManager
|
|
|
|
|
|
|
|
from sanic.cli.inspector_client import InspectorClient
|
|
|
|
from sanic.helpers import Default
|
2022-09-18 15:17:23 +01:00
|
|
|
from sanic.log import Colors
|
2022-12-18 08:29:58 +00:00
|
|
|
from sanic.worker.inspector import Inspector
|
2022-09-18 15:17:23 +01:00
|
|
|
|
|
|
|
|
|
|
|
DATA = {
|
|
|
|
"info": {
|
|
|
|
"packages": ["foo"],
|
|
|
|
},
|
|
|
|
"extra": {
|
|
|
|
"more": "data",
|
|
|
|
},
|
|
|
|
"workers": {"Worker-Name": {"some": "state"}},
|
|
|
|
}
|
2022-12-18 08:29:58 +00:00
|
|
|
FULL_SERIALIZED = dumps({"result": DATA})
|
|
|
|
OUT_SERIALIZED = dumps(DATA)
|
|
|
|
|
|
|
|
|
|
|
|
class FooInspector(Inspector):
|
|
|
|
async def foo(self, bar):
|
|
|
|
return f"bar is {bar}"
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
def publisher():
|
|
|
|
publisher = Mock()
|
|
|
|
return publisher
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
def inspector(publisher):
|
|
|
|
inspector = FooInspector(
|
|
|
|
publisher, {}, {}, "localhost", 9999, "", Default(), Default()
|
|
|
|
)
|
|
|
|
inspector(False)
|
|
|
|
return inspector
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
def http_client(inspector):
|
|
|
|
manager = TestManager(inspector.app)
|
|
|
|
return manager.test_client
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("command", ("info",))
|
|
|
|
@patch("sanic.cli.inspector_client.sys.stdout.write")
|
|
|
|
def test_send_inspect(write, urlopen, command: str):
|
|
|
|
urlopen.read.return_value = FULL_SERIALIZED.encode()
|
|
|
|
InspectorClient("localhost", 9999, False, False, None).do(command)
|
|
|
|
write.assert_called()
|
|
|
|
write.reset_mock()
|
|
|
|
InspectorClient("localhost", 9999, False, True, None).do(command)
|
|
|
|
write.assert_called_with(OUT_SERIALIZED + "\n")
|
|
|
|
|
|
|
|
|
|
|
|
@patch("sanic.cli.inspector_client.sys")
|
|
|
|
def test_send_inspect_conn_refused(sys: Mock, urlopen):
|
|
|
|
urlopen.side_effect = URLError("")
|
|
|
|
InspectorClient("localhost", 9999, False, False, None).do("info")
|
2022-09-18 15:17:23 +01:00
|
|
|
|
|
|
|
message = (
|
|
|
|
f"{Colors.RED}Could not connect to inspector at: "
|
2022-12-18 08:29:58 +00:00
|
|
|
f"{Colors.YELLOW}http://localhost:9999{Colors.END}\n"
|
2022-09-18 15:17:23 +01:00
|
|
|
"Either the application is not running, or it did not start "
|
2022-12-18 08:29:58 +00:00
|
|
|
"an inspector instance.\n<urlopen error >\n"
|
2022-09-18 15:17:23 +01:00
|
|
|
)
|
2022-12-18 08:29:58 +00:00
|
|
|
sys.exit.assert_called_once_with(1)
|
|
|
|
sys.stderr.write.assert_called_once_with(message)
|
|
|
|
|
|
|
|
|
|
|
|
def test_run_inspector_reload(publisher, http_client):
|
|
|
|
_, response = http_client.post("/reload")
|
|
|
|
assert response.status == 200
|
|
|
|
publisher.send.assert_called_once_with("__ALL_PROCESSES__:")
|
|
|
|
|
|
|
|
|
2022-12-18 12:09:17 +00:00
|
|
|
def test_run_inspector_reload_zero_downtime(publisher, http_client):
|
|
|
|
_, response = http_client.post("/reload", json={"zero_downtime": True})
|
|
|
|
assert response.status == 200
|
|
|
|
publisher.send.assert_called_once_with("__ALL_PROCESSES__::STARTUP_FIRST")
|
|
|
|
|
|
|
|
|
2022-12-18 08:29:58 +00:00
|
|
|
def test_run_inspector_shutdown(publisher, http_client):
|
|
|
|
_, response = http_client.post("/shutdown")
|
|
|
|
assert response.status == 200
|
|
|
|
publisher.send.assert_called_once_with("__TERMINATE__")
|
|
|
|
|
|
|
|
|
|
|
|
def test_run_inspector_scale(publisher, http_client):
|
|
|
|
_, response = http_client.post("/scale", json={"replicas": 4})
|
|
|
|
assert response.status == 200
|
|
|
|
publisher.send.assert_called_once_with("__SCALE__:4")
|
|
|
|
|
|
|
|
|
|
|
|
def test_run_inspector_arbitrary(http_client):
|
|
|
|
_, response = http_client.post("/foo", json={"bar": 99})
|
|
|
|
assert response.status == 200
|
|
|
|
assert response.json == {"meta": {"action": "foo"}, "result": "bar is 99"}
|
2022-09-18 15:17:23 +01:00
|
|
|
|
|
|
|
|
|
|
|
def test_state_to_json():
|
|
|
|
now = datetime.now()
|
|
|
|
now_iso = now.isoformat()
|
|
|
|
app_info = {"app": "hello"}
|
|
|
|
worker_state = {"Test": {"now": now, "nested": {"foo": now}}}
|
2022-12-18 08:29:58 +00:00
|
|
|
inspector = Inspector(
|
|
|
|
Mock(), app_info, worker_state, "", 0, "", Default(), Default()
|
|
|
|
)
|
|
|
|
state = inspector._state_to_json()
|
2022-09-18 15:17:23 +01:00
|
|
|
|
|
|
|
assert state == {
|
|
|
|
"info": app_info,
|
|
|
|
"workers": {"Test": {"now": now_iso, "nested": {"foo": now_iso}}},
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-12-18 08:29:58 +00:00
|
|
|
def test_run_inspector_authentication():
|
|
|
|
inspector = Inspector(
|
|
|
|
Mock(), {}, {}, "", 0, "super-secret", Default(), Default()
|
|
|
|
)(False)
|
|
|
|
manager = TestManager(inspector.app)
|
|
|
|
_, response = manager.test_client.get("/")
|
|
|
|
assert response.status == 401
|
|
|
|
_, response = manager.test_client.get(
|
|
|
|
"/", headers={"Authorization": "Bearer super-secret"}
|
|
|
|
)
|
|
|
|
assert response.status == 200
|