okay fine

This commit is contained in:
pacnpal
2024-11-03 17:47:26 +00:00
parent 387c4740e7
commit 27f3326e22
10020 changed files with 1935769 additions and 2364 deletions

View File

@@ -0,0 +1,12 @@
from asgiref.testing import ApplicationCommunicator # noqa
from .http import HttpCommunicator # noqa
from .live import ChannelsLiveServerTestCase # noqa
from .websocket import WebsocketCommunicator # noqa
__all__ = [
"ApplicationCommunicator",
"HttpCommunicator",
"ChannelsLiveServerTestCase",
"WebsocketCommunicator",
]

View File

@@ -0,0 +1,56 @@
from urllib.parse import unquote, urlparse
from asgiref.testing import ApplicationCommunicator
class HttpCommunicator(ApplicationCommunicator):
"""
ApplicationCommunicator subclass that has HTTP shortcut methods.
It will construct the scope for you, so you need to pass the application
(uninstantiated) along with HTTP parameters.
This does not support full chunking - for that, just use ApplicationCommunicator
directly.
"""
def __init__(self, application, method, path, body=b"", headers=None):
parsed = urlparse(path)
self.scope = {
"type": "http",
"http_version": "1.1",
"method": method.upper(),
"path": unquote(parsed.path),
"query_string": parsed.query.encode("utf-8"),
"headers": headers or [],
}
assert isinstance(body, bytes)
self.body = body
self.sent_request = False
super().__init__(application, self.scope)
async def get_response(self, timeout=1):
"""
Get the application's response. Returns a dict with keys of
"body", "headers" and "status".
"""
# If we've not sent the request yet, do so
if not self.sent_request:
self.sent_request = True
await self.send_input({"type": "http.request", "body": self.body})
# Get the response start
response_start = await self.receive_output(timeout)
assert response_start["type"] == "http.response.start"
# Get all body parts
response_start["body"] = b""
while True:
chunk = await self.receive_output(timeout)
assert chunk["type"] == "http.response.body"
assert isinstance(chunk["body"], bytes)
response_start["body"] += chunk["body"]
if not chunk.get("more_body", False):
break
# Return structured info
del response_start["type"]
response_start.setdefault("headers", [])
return response_start

View File

@@ -0,0 +1,76 @@
from functools import partial
from daphne.testing import DaphneProcess
from django.contrib.staticfiles.handlers import ASGIStaticFilesHandler
from django.core.exceptions import ImproperlyConfigured
from django.db import connections
from django.test.testcases import TransactionTestCase
from django.test.utils import modify_settings
from channels.routing import get_default_application
def make_application(*, static_wrapper):
# Module-level function for pickle-ability
application = get_default_application()
if static_wrapper is not None:
application = static_wrapper(application)
return application
class ChannelsLiveServerTestCase(TransactionTestCase):
"""
Does basically the same as TransactionTestCase but also launches a
live Daphne server in a separate process, so
that the tests may use another test framework, such as Selenium,
instead of the built-in dummy client.
"""
host = "localhost"
ProtocolServerProcess = DaphneProcess
static_wrapper = ASGIStaticFilesHandler
serve_static = True
@property
def live_server_url(self):
return "http://%s:%s" % (self.host, self._port)
@property
def live_server_ws_url(self):
return "ws://%s:%s" % (self.host, self._port)
def _pre_setup(self):
for connection in connections.all():
if self._is_in_memory_db(connection):
raise ImproperlyConfigured(
"ChannelLiveServerTestCase can not be used with in memory databases"
)
super(ChannelsLiveServerTestCase, self)._pre_setup()
self._live_server_modified_settings = modify_settings(
ALLOWED_HOSTS={"append": self.host}
)
self._live_server_modified_settings.enable()
get_application = partial(
make_application,
static_wrapper=self.static_wrapper if self.serve_static else None,
)
self._server_process = self.ProtocolServerProcess(self.host, get_application)
self._server_process.start()
self._server_process.ready.wait()
self._port = self._server_process.port.value
def _post_teardown(self):
self._server_process.terminate()
self._server_process.join()
self._live_server_modified_settings.disable()
super(ChannelsLiveServerTestCase, self)._post_teardown()
def _is_in_memory_db(self, connection):
"""
Check if DatabaseWrapper holds in memory database.
"""
if connection.vendor == "sqlite":
return connection.is_in_memory_db()

View File

@@ -0,0 +1,102 @@
import json
from urllib.parse import unquote, urlparse
from asgiref.testing import ApplicationCommunicator
class WebsocketCommunicator(ApplicationCommunicator):
"""
ApplicationCommunicator subclass that has WebSocket shortcut methods.
It will construct the scope for you, so you need to pass the application
(uninstantiated) along with the initial connection parameters.
"""
def __init__(self, application, path, headers=None, subprotocols=None):
if not isinstance(path, str):
raise TypeError("Expected str, got {}".format(type(path)))
parsed = urlparse(path)
self.scope = {
"type": "websocket",
"path": unquote(parsed.path),
"query_string": parsed.query.encode("utf-8"),
"headers": headers or [],
"subprotocols": subprotocols or [],
}
super().__init__(application, self.scope)
async def connect(self, timeout=1):
"""
Trigger the connection code.
On an accepted connection, returns (True, <chosen-subprotocol>)
On a rejected connection, returns (False, <close-code>)
"""
await self.send_input({"type": "websocket.connect"})
response = await self.receive_output(timeout)
if response["type"] == "websocket.close":
return (False, response.get("code", 1000))
else:
return (True, response.get("subprotocol", None))
async def send_to(self, text_data=None, bytes_data=None):
"""
Sends a WebSocket frame to the application.
"""
# Make sure we have exactly one of the arguments
assert bool(text_data) != bool(
bytes_data
), "You must supply exactly one of text_data or bytes_data"
# Send the right kind of event
if text_data:
assert isinstance(text_data, str), "The text_data argument must be a str"
await self.send_input({"type": "websocket.receive", "text": text_data})
else:
assert isinstance(
bytes_data, bytes
), "The bytes_data argument must be bytes"
await self.send_input({"type": "websocket.receive", "bytes": bytes_data})
async def send_json_to(self, data):
"""
Sends JSON data as a text frame
"""
await self.send_to(text_data=json.dumps(data))
async def receive_from(self, timeout=1):
"""
Receives a data frame from the view. Will fail if the connection
closes instead. Returns either a bytestring or a unicode string
depending on what sort of frame you got.
"""
response = await self.receive_output(timeout)
# Make sure this is a send message
assert response["type"] == "websocket.send"
# Make sure there's exactly one key in the response
assert ("text" in response) != (
"bytes" in response
), "The response needs exactly one of 'text' or 'bytes'"
# Pull out the right key and typecheck it for our users
if "text" in response:
assert isinstance(response["text"], str), "Text frame payload is not str"
return response["text"]
else:
assert isinstance(
response["bytes"], bytes
), "Binary frame payload is not bytes"
return response["bytes"]
async def receive_json_from(self, timeout=1):
"""
Receives a JSON text frame payload and decodes it
"""
payload = await self.receive_from(timeout)
assert isinstance(payload, str), "JSON data is not a text frame"
return json.loads(payload)
async def disconnect(self, code=1000, timeout=1):
"""
Closes the socket
"""
await self.send_input({"type": "websocket.disconnect", "code": code})
await self.wait(timeout)