1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-01-06 13:17:54 +03:00
openuds/server/tests/utils/test.py
2024-10-09 17:06:13 +02:00

267 lines
13 KiB
Python

# -*- coding: utf-8 -*-
#
# Copyright (c) 2022 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
Author: Adolfo Gómez, dkmaster at dkmon dot com
"""
# pyright: reportUnknownMemberType=false
import typing
import logging
from django.test import TestCase, TransactionTestCase
from django.test.client import Client, AsyncClient # type: ignore # Pylance does not know about AsyncClient, but it is there
from django.http.response import HttpResponse
from django.conf import settings
from uds.core.environment import Environment
from uds.core.managers.crypto import CryptoManager
logger = logging.getLogger(__name__)
REST_PATH = '/uds/rest/'
class UDSHttpResponse(HttpResponse):
"""
Custom response class to be able to access the response content
"""
def __init__(self, content: bytes, *args: typing.Any, **kwargs: typing.Any) -> None:
super().__init__(content, *args, **kwargs)
def json(self) -> typing.Any:
return super().json()
class UDSClientMixin:
uds_headers: dict[str, str]
ip_version: int = 4
def initialize(self) -> None:
# Ensure only basic middleware are enabled.
settings.MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'uds.middleware.request.GlobalRequestMiddleware',
]
self.uds_headers = {
'User-Agent': 'Testing user agent',
}
# Update settings security options
settings.RSA_KEY = '-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDcANi/08cnpn04\njKW/o2G1k4SIa6dJks8DmT4MQHOWqYC46YSIIPzqGoBPcvkbDSPSFBnByo3HhMY+\nk4JHc9SUwEmHSJWDCHjt7XSXX/ryqH0QQIJtSjk9Bc+GkOU24mnbITiw7ORjp7VN\nvgFdFhjVsZM/NjX/6Y9DoCPC1mGj0O9Dd4MfCsNwUxRhhR6LdrEnpRUSVW0Ksxbz\ncTfpQjdFr86+1BeUbzqN2HDcEGhvioj+0lGPXcOZoRNYU16H7kjLNP+o+rC7f/q/\nfoOYLzDSkmzePbcG+g0Hv7K7fuLus05ZWjupOmJA9hytB1BIF4p5f4ewl05Fx2Zj\nG2LneO2fAgMBAAECggEBANDimOnh2TkDceeMWx+OsAooC3E/zbEkjBudl3UoiNcn\nD0oCpkxeDeT0zpkgz/ZoTnd7kE0Y1e73WQc3JT5UcyXdQLMLLrIgDDnT+Jx1jB5z\n7XLN3UiJbblL2BOrZYbsCJf/fgU2l08rgBBVdJP+lAvps6YUAcd+6gDKfsnSpRhU\nWBHLZde7l6vUJ2OK9ZmHaghF5E8Xx918OSUKFJfGTYL5JLTb/scdl8vQse1quWC1\nk48PPXK10vOFvYWonQpRb2cOK/PPjPXPNWzcQyQY9D1iOeFvRyLqOXYE/ZY+qDe2\nHdPGrkl67yz01nzepkWWg/ZNbMXeZZyOnZm0aXtOxtkCgYEA/Qz3mescgwrt67yh\nFrbXjUqiVf2IpbNt88CUcbY0r1EdTA9OMtOtPYNvfpyRIRfDaZJ1zAdh3CZ2/hTm\ng+VUtseKnUDCi0xIBKX3V2O8sryWt2KStTnTo6JP0T47yXvmaRu5cutgoaD9SK+r\nN5vg1D2gNLmsT8uJh1Bl/yWGC4sCgYEA3pFGgAmiywsvmsddkI+LujoQVTiqkfFg\nMHHsJFOZlhYO83g49Q11pcQ70ukT6e89Ggwy///+z19p8jJ+wGqQWQLsM6eO1utg\nnJ8wMTwk8tOEm9MnWnnWhtG9KWcgkmwOVQiesJdWa1xOqsBKGchUkugmFycKNsiG\nHUbogbJ0OL0CgYBVLIcuxKdNKGGaxlwGVDbLdQKdJQBYncN1ly2f9K9ZD1loH4K3\nsu4N1W6y1Co5VFFO+KAzs4xp2HyW2xwX6xoPh6yNb53L2zombmKJhKWgF8A3K7Or\n0jH9UwXArUzcbZrJaC6MktNss85tJ8vepNYROkjxVkm8dgrtg89BCTVMLwKBgQCW\nSSh+uoL3cdUyQV63h4ZFOIHg2cOrin52F+bpXJ3/z2NHGa30IqOHTGtM7l+o/geX\nOBeT72tC4d2rUlduXEaeJDAUbRcxnnx9JayoAkG8ygDoK3uOR2kJXkTJ2T4QQPCo\nkIp/GaGcGxdviyo+IJyjGijmR1FJTrvotwG22iZKTQKBgQCIh50Dz0/rqZB4Om5g\nLLdZn1C8/lOR8hdK9WUyPHZfJKpQaDOlNdiy9x6xD6+uIQlbNsJhlDbOudHDurfI\nghGbJ1sy1FUloP+V3JAFS88zIwrddcGEso8YMFMCE1fH2/q35XGwZEnUq7ttDaxx\nHmTQ2w37WASIUgCl2GhM25np0Q==\n-----END PRIVATE KEY-----\n'
settings.CERTIFICATE = '-----BEGIN CERTIFICATE-----\nMIICzTCCAjYCCQCOUQEWpuEa3jANBgkqhkiG9w0BAQUFADCBqjELMAkGA1UEBhMC\nRVMxDzANBgNVBAgMBk1hZHJpZDEUMBIGA1UEBwwLQWxjb3Jjw4PCs24xHTAbBgNV\nBAoMFFZpcnR1YWwgQ2FibGUgUy5MLlUuMRQwEgYDVQQLDAtEZXZlbG9wbWVudDEY\nMBYGA1UEAwwPQWRvbGZvIEfDg8KzbWV6MSUwIwYJKoZIhvcNAQkBFhZhZ29tZXpA\ndmlydHVhbGNhYmxlLmVzMB4XDTEyMDYyNTA0MjM0MloXDTEzMDYyNTA0MjM0Mlow\ngaoxCzAJBgNVBAYTAkVTMQ8wDQYDVQQIDAZNYWRyaWQxFDASBgNVBAcMC0FsY29y\nY8ODwrNuMR0wGwYDVQQKDBRWaXJ0dWFsIENhYmxlIFMuTC5VLjEUMBIGA1UECwwL\nRGV2ZWxvcG1lbnQxGDAWBgNVBAMMD0Fkb2xmbyBHw4PCs21lejElMCMGCSqGSIb3\nDQEJARYWYWdvbWV6QHZpcnR1YWxjYWJsZS5lczCBnzANBgkqhkiG9w0BAQEFAAOB\njQAwgYkCgYEA35iGyHS/GVdWk3n9kQ+wsCLR++jd9Vez/s407/natm8YDteKksA0\nMwIvDAX722blm8PUya2NOlnum8KdyUPDOq825XERDlsIA+sTd6lb1c7w44qZ/pb+\n68mhXoRx2VJsu//+zhBkaQ1/KcugeHa4WLRIH35YLxdQDxrXS1eQWccCAwEAATAN\nBgkqhkiG9w0BAQUFAAOBgQAk+fJPpY+XvUsxR2A4SaQ8TGnE2x4PtpwCrCVzKEU9\nW2ugdXvysxkHbib3+JdA6s+lJjHs5HiMZPo/ak8adEKke+d10EU5YcUaJRRUpStY\nqQHziaqOl5Hgi75Kjskq6+tCU0Iui+s9pBg0V6y1AQsCmH2xFs7t1oEOGRFVarfF\n4Q==\n-----END CERTIFICATE-----'
def add_header(self, name: str, value: str) -> None:
self.uds_headers[name] = value
def set_user_agent(self, user_agent: typing.Optional[str] = None) -> None:
user_agent = user_agent or ''
# Add 'HTTP_USER_AGENT' header
self.uds_headers['User-Agent'] = user_agent
def enable_ipv4(self) -> None:
self.ip_version = 4
def enable_ipv6(self) -> None:
self.ip_version = 6
def update_request_kwargs(self, kwargs: dict[str, typing.Any]) -> None:
if self.ip_version == 4:
kwargs['REMOTE_ADDR'] = '127.0.0.1'
elif self.ip_version == 6:
kwargs['REMOTE_ADDR'] = '::1'
kwargs['headers'] = self.uds_headers
def compose_rest_url(self, method: str) -> str:
return f'{REST_PATH}/{method}'
class UDSClient(UDSClientMixin, Client):
def __init__(
self,
enforce_csrf_checks: bool = False,
raise_request_exception: bool = True,
**defaults: typing.Any,
):
UDSClientMixin.initialize(self)
# Instantiate the client and add basic user agent
super().__init__(enforce_csrf_checks, raise_request_exception)
# and required UDS cookie
self.cookies['uds'] = CryptoManager().random_string(48)
def request(self, **request: typing.Any) -> 'UDSHttpResponse':
# Copy request dict
# request = request.copy()
# Add META headers
# request.update(self.uds_headers)
return typing.cast('UDSHttpResponse', super().request(**request))
def get(self, *args: typing.Any, **kwargs: typing.Any) -> 'UDSHttpResponse':
self.update_request_kwargs(kwargs)
return typing.cast('UDSHttpResponse', super().get(*args, **kwargs))
def rest_get(self, method: str, *args: typing.Any, **kwargs: typing.Any) -> 'UDSHttpResponse':
# compose url
return self.get(self.compose_rest_url(method), *args, **kwargs)
def post(self, *args: typing.Any, **kwargs: typing.Any) -> 'UDSHttpResponse':
self.update_request_kwargs(kwargs)
return typing.cast('UDSHttpResponse', super().post(*args, **kwargs))
def rest_post(self, method: str, *args: typing.Any, **kwargs: typing.Any) -> 'UDSHttpResponse':
# compose url
kwargs['content_type'] = kwargs.get('content_type', 'application/json')
return self.post(self.compose_rest_url(method), *args, **kwargs)
def put(self, *args: typing.Any, **kwargs: typing.Any) -> 'UDSHttpResponse':
self.update_request_kwargs(kwargs)
return typing.cast('UDSHttpResponse', super().put(*args, **kwargs))
def rest_put(self, method: str, *args: typing.Any, **kwargs: typing.Any) -> 'UDSHttpResponse':
kwargs['content_type'] = kwargs.get('content_type', 'application/json')
return self.put(self.compose_rest_url(method), *args, **kwargs)
def delete(self, *args: typing.Any, **kwargs: typing.Any) -> 'UDSHttpResponse':
self.update_request_kwargs(kwargs)
kwargs['content_type'] = kwargs.get('content_type', 'application/json')
return typing.cast('UDSHttpResponse', super().delete(*args, **kwargs))
def rest_delete(self, method: str, *args: typing.Any, **kwargs: typing.Any) -> 'UDSHttpResponse':
kwargs['content_type'] = kwargs.get('content_type', 'application/json')
return self.delete(self.compose_rest_url(method), *args, **kwargs)
class UDSAsyncClient(UDSClientMixin, AsyncClient): # type: ignore # Django stubs do not include AsyncClient
def __init__(
self,
enforce_csrf_checks: bool = False,
raise_request_exception: bool = True,
**defaults: typing.Any,
):
# Instantiate the client and add basic user agent
AsyncClient.__init__(self, enforce_csrf_checks, raise_request_exception) # pyright: ignore
UDSClientMixin.initialize(self)
# and required UDS cookie
self.cookies['uds'] = CryptoManager().random_string(48)
async def request(self, **request: typing.Any) -> 'UDSHttpResponse':
# Copy request dict
request = request.copy()
# Add headers
request.update(self.uds_headers)
return await super().request(**request) # pyright: ignore
# pylint: disable=invalid-overridden-method
async def get(self, *args: typing.Any, **kwargs: typing.Any) -> 'UDSHttpResponse':
self.update_request_kwargs(kwargs)
return typing.cast('UDSHttpResponse', await super().get(*args, **kwargs))
async def rest_get(self, method: str, *args: typing.Any, **kwargs: typing.Any) -> 'UDSHttpResponse':
# compose url
return await self.get(self.compose_rest_url(method), *args, **kwargs)
# pylint: disable=invalid-overridden-method
async def post(self, *args: typing.Any, **kwargs: typing.Any) -> 'UDSHttpResponse':
self.update_request_kwargs(kwargs)
return typing.cast('UDSHttpResponse', await super().post(*args, **kwargs))
async def rest_post(self, method: str, *args: typing.Any, **kwargs: typing.Any) -> 'UDSHttpResponse':
kwargs['content_type'] = kwargs.get('content_type', 'application/json')
return await self.post(self.compose_rest_url(method), *args, **kwargs)
# pylint: disable=invalid-overridden-method
async def put(self, *args: typing.Any, **kwargs: typing.Any) -> 'UDSHttpResponse':
kwargs['content_type'] = kwargs.get('content_type', 'application/json')
return typing.cast('UDSHttpResponse', await super().put(*args, **kwargs))
async def rest_put(self, method: str, *args: typing.Any, **kwargs: typing.Any) -> 'UDSHttpResponse':
kwargs['content_type'] = kwargs.get('content_type', 'application/json')
return await self.put(self.compose_rest_url(method), *args, **kwargs)
# pylint: disable=invalid-overridden-method
async def delete(self, *args: typing.Any, **kwargs: typing.Any) -> 'UDSHttpResponse':
self.update_request_kwargs(kwargs)
return typing.cast('UDSHttpResponse', await super().delete(*args, **kwargs))
async def rest_delete(self, method: str, *args: typing.Any, **kwargs: typing.Any) -> 'UDSHttpResponse':
# compose url
return await self.delete(self.compose_rest_url(method), *args, **kwargs)
class UDSTestCaseMixin:
client_class: typing.Type[UDSClient] = UDSClient
async_client_class: typing.Type[UDSAsyncClient] = UDSAsyncClient
client: UDSClient
async_client: UDSAsyncClient
@staticmethod
def add_middleware(middleware: str) -> None:
if middleware not in settings.MIDDLEWARE:
settings.MIDDLEWARE.append(middleware)
@staticmethod
def remove_middleware(middleware: str) -> None:
# Remove middleware from settings, if present
try:
settings.MIDDLEWARE.remove(middleware)
except ValueError:
pass # Not present
class UDSTestCase(UDSTestCaseMixin, TestCase): # pyright: ignore # Overrides superclass client
@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
setupClass(cls) # The one local to this module
def create_environment(self) -> Environment:
return Environment.testing_environment()
class UDSTransactionTestCase(UDSTestCaseMixin, TransactionTestCase): # pyright: ignore # superclass client
@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
setupClass(cls)
# pylint: disable=unused-argument
def setupClass(cls: typing.Union[type[UDSTestCase], type[UDSTransactionTestCase]]) -> None:
# Nothing right now
pass