2022-08-17 21:15:32 +03:00
# -*- 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.
"""
2024-02-26 07:50:36 +03:00
Author : Adolfo Gómez , dkmaster at dkmon dot com
2022-08-17 21:15:32 +03:00
"""
2024-02-26 07:50:36 +03:00
# pyright: reportUnknownMemberType=false
2022-08-17 21:15:32 +03:00
import typing
import logging
from django . test import TestCase , TransactionTestCase
2022-12-08 16:44:47 +03:00
from django . test . client import Client , AsyncClient # type: ignore # Pylance does not know about AsyncClient, but it is there
2022-08-25 18:55:51 +03:00
from django . http . response import HttpResponse
2022-08-17 21:15:32 +03:00
from django . conf import settings
2024-02-15 22:06:38 +03:00
from uds . core . environment import Environment
2022-08-17 21:15:32 +03:00
from uds . core . managers . crypto import CryptoManager
logger = logging . getLogger ( __name__ )
2023-01-27 19:02:35 +03:00
REST_PATH = ' /uds/rest/ '
2022-08-25 23:28:57 +03:00
2022-08-25 18:55:51 +03:00
class UDSHttpResponse ( HttpResponse ) :
"""
Custom response class to be able to access the response content
"""
2024-02-26 07:50:36 +03:00
2024-02-21 05:35:36 +03:00
def __init__ ( self , content : bytes , * args : typing . Any , * * kwargs : typing . Any ) - > None :
2022-08-25 18:55:51 +03:00
super ( ) . __init__ ( content , * args , * * kwargs )
2024-02-26 07:50:36 +03:00
2024-01-01 07:16:33 +03:00
def json ( self ) - > typing . Any :
2024-02-21 05:35:36 +03:00
return super ( ) . json ( )
2022-08-25 18:55:51 +03:00
2022-08-25 23:28:57 +03:00
2022-12-08 16:44:47 +03:00
class UDSClientMixin :
2023-12-04 02:04:56 +03:00
uds_headers : dict [ str , str ]
2022-12-09 04:58:17 +03:00
ip_version : int = 4
2022-08-19 01:46:38 +03:00
2024-02-21 05:35:36 +03:00
def initialize ( self ) - > None :
2022-08-17 21:15:32 +03:00
# 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 ' ,
2023-02-06 18:08:04 +03:00
' uds.middleware.request.GlobalRequestMiddleware ' ,
2022-08-17 21:15:32 +03:00
]
2023-04-15 22:21:23 +03:00
self . uds_headers = {
2024-01-12 07:16:49 +03:00
' User-Agent ' : ' Testing user agent ' ,
2023-04-15 22:21:23 +03:00
}
2022-08-17 21:15:32 +03:00
2022-08-25 23:28:57 +03:00
# Update settings security options
settings . RSA_KEY = ' -----BEGIN PRIVATE KEY----- \n MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDcANi/08cnpn04 \n jKW/o2G1k4SIa6dJks8DmT4MQHOWqYC46YSIIPzqGoBPcvkbDSPSFBnByo3HhMY+ \n k4JHc9SUwEmHSJWDCHjt7XSXX/ryqH0QQIJtSjk9Bc+GkOU24mnbITiw7ORjp7VN \n vgFdFhjVsZM/NjX/6Y9DoCPC1mGj0O9Dd4MfCsNwUxRhhR6LdrEnpRUSVW0Ksxbz \n cTfpQjdFr86+1BeUbzqN2HDcEGhvioj+0lGPXcOZoRNYU16H7kjLNP+o+rC7f/q/ \n foOYLzDSkmzePbcG+g0Hv7K7fuLus05ZWjupOmJA9hytB1BIF4p5f4ewl05Fx2Zj \n G2LneO2fAgMBAAECggEBANDimOnh2TkDceeMWx+OsAooC3E/zbEkjBudl3UoiNcn \n D0oCpkxeDeT0zpkgz/ZoTnd7kE0Y1e73WQc3JT5UcyXdQLMLLrIgDDnT+Jx1jB5z \n 7XLN3UiJbblL2BOrZYbsCJf/fgU2l08rgBBVdJP+lAvps6YUAcd+6gDKfsnSpRhU \n WBHLZde7l6vUJ2OK9ZmHaghF5E8Xx918OSUKFJfGTYL5JLTb/scdl8vQse1quWC1 \n k48PPXK10vOFvYWonQpRb2cOK/PPjPXPNWzcQyQY9D1iOeFvRyLqOXYE/ZY+qDe2 \n HdPGrkl67yz01nzepkWWg/ZNbMXeZZyOnZm0aXtOxtkCgYEA/Qz3mescgwrt67yh \n FrbXjUqiVf2IpbNt88CUcbY0r1EdTA9OMtOtPYNvfpyRIRfDaZJ1zAdh3CZ2/hTm \n g+VUtseKnUDCi0xIBKX3V2O8sryWt2KStTnTo6JP0T47yXvmaRu5cutgoaD9SK+r \n N5vg1D2gNLmsT8uJh1Bl/yWGC4sCgYEA3pFGgAmiywsvmsddkI+LujoQVTiqkfFg \n MHHsJFOZlhYO83g49Q11pcQ70ukT6e89Ggwy///+z19p8jJ+wGqQWQLsM6eO1utg \n nJ8wMTwk8tOEm9MnWnnWhtG9KWcgkmwOVQiesJdWa1xOqsBKGchUkugmFycKNsiG \n HUbogbJ0OL0CgYBVLIcuxKdNKGGaxlwGVDbLdQKdJQBYncN1ly2f9K9ZD1loH4K3 \n su4N1W6y1Co5VFFO+KAzs4xp2HyW2xwX6xoPh6yNb53L2zombmKJhKWgF8A3K7Or \n 0jH9UwXArUzcbZrJaC6MktNss85tJ8vepNYROkjxVkm8dgrtg89BCTVMLwKBgQCW \n SSh+uoL3cdUyQV63h4ZFOIHg2cOrin52F+bpXJ3/z2NHGa30IqOHTGtM7l+o/geX \n OBeT72tC4d2rUlduXEaeJDAUbRcxnnx9JayoAkG8ygDoK3uOR2kJXkTJ2T4QQPCo \n kIp/GaGcGxdviyo+IJyjGijmR1FJTrvotwG22iZKTQKBgQCIh50Dz0/rqZB4Om5g \n LLdZn1C8/lOR8hdK9WUyPHZfJKpQaDOlNdiy9x6xD6+uIQlbNsJhlDbOudHDurfI \n ghGbJ1sy1FUloP+V3JAFS88zIwrddcGEso8YMFMCE1fH2/q35XGwZEnUq7ttDaxx \n HmTQ2w37WASIUgCl2GhM25np0Q== \n -----END PRIVATE KEY----- \n '
settings . CERTIFICATE = ' -----BEGIN CERTIFICATE----- \n MIICzTCCAjYCCQCOUQEWpuEa3jANBgkqhkiG9w0BAQUFADCBqjELMAkGA1UEBhMC \n RVMxDzANBgNVBAgMBk1hZHJpZDEUMBIGA1UEBwwLQWxjb3Jjw4PCs24xHTAbBgNV \n BAoMFFZpcnR1YWwgQ2FibGUgUy5MLlUuMRQwEgYDVQQLDAtEZXZlbG9wbWVudDEY \n MBYGA1UEAwwPQWRvbGZvIEfDg8KzbWV6MSUwIwYJKoZIhvcNAQkBFhZhZ29tZXpA \n dmlydHVhbGNhYmxlLmVzMB4XDTEyMDYyNTA0MjM0MloXDTEzMDYyNTA0MjM0Mlow \n gaoxCzAJBgNVBAYTAkVTMQ8wDQYDVQQIDAZNYWRyaWQxFDASBgNVBAcMC0FsY29y \n Y8ODwrNuMR0wGwYDVQQKDBRWaXJ0dWFsIENhYmxlIFMuTC5VLjEUMBIGA1UECwwL \n RGV2ZWxvcG1lbnQxGDAWBgNVBAMMD0Fkb2xmbyBHw4PCs21lejElMCMGCSqGSIb3 \n DQEJARYWYWdvbWV6QHZpcnR1YWxjYWJsZS5lczCBnzANBgkqhkiG9w0BAQEFAAOB \n jQAwgYkCgYEA35iGyHS/GVdWk3n9kQ+wsCLR++jd9Vez/s407/natm8YDteKksA0 \n MwIvDAX722blm8PUya2NOlnum8KdyUPDOq825XERDlsIA+sTd6lb1c7w44qZ/pb+ \n 68mhXoRx2VJsu//+zhBkaQ1/KcugeHa4WLRIH35YLxdQDxrXS1eQWccCAwEAATAN \n BgkqhkiG9w0BAQUFAAOBgQAk+fJPpY+XvUsxR2A4SaQ8TGnE2x4PtpwCrCVzKEU9 \n W2ugdXvysxkHbib3+JdA6s+lJjHs5HiMZPo/ak8adEKke+d10EU5YcUaJRRUpStY \n qQHziaqOl5Hgi75Kjskq6+tCU0Iui+s9pBg0V6y1AQsCmH2xFs7t1oEOGRFVarfF \n 4Q== \n -----END CERTIFICATE----- '
2024-02-21 05:35:36 +03:00
def add_header ( self , name : str , value : str ) - > None :
2023-04-15 22:21:23 +03:00
self . uds_headers [ name ] = value
2022-12-08 16:44:47 +03:00
2024-02-21 05:35:36 +03:00
def set_user_agent ( self , user_agent : typing . Optional [ str ] = None ) - > None :
2022-12-09 04:58:17 +03:00
user_agent = user_agent or ' '
# Add 'HTTP_USER_AGENT' header
2024-01-12 07:16:49 +03:00
self . uds_headers [ ' User-Agent ' ] = user_agent
2022-12-09 04:58:17 +03:00
2024-02-21 05:35:36 +03:00
def enable_ipv4 ( self ) - > None :
2022-12-09 04:58:17 +03:00
self . ip_version = 4
2024-02-21 05:35:36 +03:00
def enable_ipv6 ( self ) - > None :
2022-12-09 04:58:17 +03:00
self . ip_version = 6
2022-12-08 16:44:47 +03:00
2024-01-12 07:16:49 +03:00
def update_request_kwargs ( self , kwargs : dict [ str , typing . Any ] ) - > None :
2022-12-09 04:58:17 +03:00
if self . ip_version == 4 :
kwargs [ ' REMOTE_ADDR ' ] = ' 127.0.0.1 '
elif self . ip_version == 6 :
kwargs [ ' REMOTE_ADDR ' ] = ' ::1 '
2024-02-26 07:50:36 +03:00
2024-01-12 07:16:49 +03:00
kwargs [ ' headers ' ] = self . uds_headers
2023-01-27 19:02:35 +03:00
def compose_rest_url ( self , method : str ) - > str :
return f ' { REST_PATH } / { method } '
2022-12-08 16:44:47 +03:00
class UDSClient ( UDSClientMixin , Client ) :
def __init__ (
self ,
enforce_csrf_checks : bool = False ,
raise_request_exception : bool = True ,
2023-01-27 19:02:35 +03:00
* * defaults : typing . Any ,
2022-12-08 16:44:47 +03:00
) :
UDSClientMixin . initialize ( self )
2022-08-17 21:15:32 +03:00
# Instantiate the client and add basic user agent
2024-02-21 05:35:36 +03:00
super ( ) . __init__ ( enforce_csrf_checks , raise_request_exception )
2022-08-25 23:28:57 +03:00
2022-08-17 21:15:32 +03:00
# and required UDS cookie
2024-01-04 19:39:30 +03:00
self . cookies [ ' uds ' ] = CryptoManager ( ) . random_string ( 48 )
2022-08-19 01:46:38 +03:00
2024-02-21 05:35:36 +03:00
def request ( self , * * request : typing . Any ) - > ' UDSHttpResponse ' :
2022-08-19 01:46:38 +03:00
# Copy request dict
2024-01-12 07:16:49 +03:00
# request = request.copy()
# Add META headers
# request.update(self.uds_headers)
2024-02-21 05:35:36 +03:00
return typing . cast ( ' UDSHttpResponse ' , super ( ) . request ( * * request ) )
2022-08-19 01:46:38 +03:00
2024-02-21 05:35:36 +03:00
def get ( self , * args : typing . Any , * * kwargs : typing . Any ) - > ' UDSHttpResponse ' :
2024-01-12 07:16:49 +03:00
self . update_request_kwargs ( kwargs )
2022-08-25 18:55:51 +03:00
return typing . cast ( ' UDSHttpResponse ' , super ( ) . get ( * args , * * kwargs ) )
2022-08-24 23:16:47 +03:00
2024-02-21 05:35:36 +03:00
def rest_get ( self , method : str , * args : typing . Any , * * kwargs : typing . Any ) - > ' UDSHttpResponse ' :
2023-01-27 19:02:35 +03:00
# compose url
return self . get ( self . compose_rest_url ( method ) , * args , * * kwargs )
2024-02-21 05:35:36 +03:00
def post ( self , * args : typing . Any , * * kwargs : typing . Any ) - > ' UDSHttpResponse ' :
2024-01-12 07:16:49 +03:00
self . update_request_kwargs ( kwargs )
2022-08-25 18:55:51 +03:00
return typing . cast ( ' UDSHttpResponse ' , super ( ) . post ( * args , * * kwargs ) )
2022-08-24 23:16:47 +03:00
2024-02-21 05:35:36 +03:00
def rest_post ( self , method : str , * args : typing . Any , * * kwargs : typing . Any ) - > ' UDSHttpResponse ' :
2023-01-27 19:02:35 +03:00
# compose url
2023-09-06 19:33:00 +03:00
kwargs [ ' content_type ' ] = kwargs . get ( ' content_type ' , ' application/json ' )
2023-01-27 19:02:35 +03:00
return self . post ( self . compose_rest_url ( method ) , * args , * * kwargs )
2024-02-21 05:35:36 +03:00
def put ( self , * args : typing . Any , * * kwargs : typing . Any ) - > ' UDSHttpResponse ' :
2024-01-12 07:16:49 +03:00
self . update_request_kwargs ( kwargs )
2023-01-27 19:02:35 +03:00
return typing . cast ( ' UDSHttpResponse ' , super ( ) . put ( * args , * * kwargs ) )
2024-02-21 05:35:36 +03:00
def rest_put ( self , method : str , * args : typing . Any , * * kwargs : typing . Any ) - > ' UDSHttpResponse ' :
2023-02-02 19:49:49 +03:00
kwargs [ ' content_type ' ] = kwargs . get ( ' content_type ' , ' application/json ' )
2023-01-27 19:02:35 +03:00
return self . put ( self . compose_rest_url ( method ) , * args , * * kwargs )
2024-02-21 05:35:36 +03:00
def delete ( self , * args : typing . Any , * * kwargs : typing . Any ) - > ' UDSHttpResponse ' :
2024-01-12 08:06:29 +03:00
self . update_request_kwargs ( kwargs )
2023-02-02 19:49:49 +03:00
kwargs [ ' content_type ' ] = kwargs . get ( ' content_type ' , ' application/json ' )
2023-01-27 19:02:35 +03:00
return typing . cast ( ' UDSHttpResponse ' , super ( ) . delete ( * args , * * kwargs ) )
2024-02-21 05:35:36 +03:00
def rest_delete ( self , method : str , * args : typing . Any , * * kwargs : typing . Any ) - > ' UDSHttpResponse ' :
2023-02-02 19:49:49 +03:00
kwargs [ ' content_type ' ] = kwargs . get ( ' content_type ' , ' application/json ' )
2023-01-27 19:02:35 +03:00
return self . delete ( self . compose_rest_url ( method ) , * args , * * kwargs )
2022-12-08 16:44:47 +03:00
2024-02-26 07:50:36 +03:00
class UDSAsyncClient ( UDSClientMixin , AsyncClient ) : # type: ignore # Django stubs do not include AsyncClient
2022-12-08 16:44:47 +03:00
def __init__ (
self ,
enforce_csrf_checks : bool = False ,
raise_request_exception : bool = True ,
2023-01-27 19:02:35 +03:00
* * defaults : typing . Any ,
2022-12-08 16:44:47 +03:00
) :
# Instantiate the client and add basic user agent
2024-02-26 07:50:36 +03:00
AsyncClient . __init__ ( self , enforce_csrf_checks , raise_request_exception ) # pyright: ignore
2024-02-21 05:35:36 +03:00
UDSClientMixin . initialize ( self )
2022-12-08 16:44:47 +03:00
# and required UDS cookie
2024-01-04 19:39:30 +03:00
self . cookies [ ' uds ' ] = CryptoManager ( ) . random_string ( 48 )
2022-12-08 16:44:47 +03:00
2024-02-21 05:35:36 +03:00
async def request ( self , * * request : typing . Any ) - > ' UDSHttpResponse ' :
2022-12-08 16:44:47 +03:00
# Copy request dict
request = request . copy ( )
# Add headers
2023-04-15 22:21:23 +03:00
request . update ( self . uds_headers )
2024-02-26 07:50:36 +03:00
return await super ( ) . request ( * * request ) # pyright: ignore
2022-12-08 16:44:47 +03:00
2023-04-15 22:21:23 +03:00
# pylint: disable=invalid-overridden-method
2024-02-21 05:35:36 +03:00
async def get ( self , * args : typing . Any , * * kwargs : typing . Any ) - > ' UDSHttpResponse ' :
2024-01-12 07:16:49 +03:00
self . update_request_kwargs ( kwargs )
2022-12-08 16:44:47 +03:00
return typing . cast ( ' UDSHttpResponse ' , await super ( ) . get ( * args , * * kwargs ) )
2024-02-21 05:35:36 +03:00
async def rest_get ( self , method : str , * args : typing . Any , * * kwargs : typing . Any ) - > ' UDSHttpResponse ' :
2023-01-27 19:02:35 +03:00
# compose url
return await self . get ( self . compose_rest_url ( method ) , * args , * * kwargs )
2023-04-15 22:21:23 +03:00
# pylint: disable=invalid-overridden-method
2024-02-21 05:35:36 +03:00
async def post ( self , * args : typing . Any , * * kwargs : typing . Any ) - > ' UDSHttpResponse ' :
2024-01-12 07:16:49 +03:00
self . update_request_kwargs ( kwargs )
2022-12-08 16:44:47 +03:00
return typing . cast ( ' UDSHttpResponse ' , await super ( ) . post ( * args , * * kwargs ) )
2024-02-21 05:35:36 +03:00
async def rest_post ( self , method : str , * args : typing . Any , * * kwargs : typing . Any ) - > ' UDSHttpResponse ' :
2023-02-02 19:49:49 +03:00
kwargs [ ' content_type ' ] = kwargs . get ( ' content_type ' , ' application/json ' )
2023-01-27 19:02:35 +03:00
return await self . post ( self . compose_rest_url ( method ) , * args , * * kwargs )
2023-04-15 22:21:23 +03:00
# pylint: disable=invalid-overridden-method
2024-02-21 05:35:36 +03:00
async def put ( self , * args : typing . Any , * * kwargs : typing . Any ) - > ' UDSHttpResponse ' :
2023-02-02 19:49:49 +03:00
kwargs [ ' content_type ' ] = kwargs . get ( ' content_type ' , ' application/json ' )
2023-01-27 19:02:35 +03:00
return typing . cast ( ' UDSHttpResponse ' , await super ( ) . put ( * args , * * kwargs ) )
2024-02-21 05:35:36 +03:00
async def rest_put ( self , method : str , * args : typing . Any , * * kwargs : typing . Any ) - > ' UDSHttpResponse ' :
2023-02-02 19:49:49 +03:00
kwargs [ ' content_type ' ] = kwargs . get ( ' content_type ' , ' application/json ' )
2023-01-27 19:02:35 +03:00
return await self . put ( self . compose_rest_url ( method ) , * args , * * kwargs )
2023-04-15 22:21:23 +03:00
# pylint: disable=invalid-overridden-method
2024-02-21 05:35:36 +03:00
async def delete ( self , * args : typing . Any , * * kwargs : typing . Any ) - > ' UDSHttpResponse ' :
2024-01-12 07:16:49 +03:00
self . update_request_kwargs ( kwargs )
2023-01-27 19:02:35 +03:00
return typing . cast ( ' UDSHttpResponse ' , await super ( ) . delete ( * args , * * kwargs ) )
2024-02-21 05:35:36 +03:00
async def rest_delete ( self , method : str , * args : typing . Any , * * kwargs : typing . Any ) - > ' UDSHttpResponse ' :
2023-01-27 19:02:35 +03:00
# compose url
return await self . delete ( self . compose_rest_url ( method ) , * args , * * kwargs )
2022-12-09 04:58:17 +03:00
class UDSTestCaseMixin :
2024-02-21 05:35:36 +03:00
client_class : typing . Type [ UDSClient ] = UDSClient
async_client_class : typing . Type [ UDSAsyncClient ] = UDSAsyncClient
2022-08-17 21:15:32 +03:00
2022-08-19 01:46:38 +03:00
client : UDSClient
2022-12-08 16:44:47 +03:00
async_client : UDSAsyncClient
2022-08-19 01:46:38 +03:00
2022-12-09 04:58:17 +03:00
@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
2024-02-26 07:50:36 +03:00
class UDSTestCase ( UDSTestCaseMixin , TestCase ) : # pyright: ignore # Overrides superclass client
2022-08-29 16:21:12 +03:00
@classmethod
def setUpClass ( cls ) - > None :
super ( ) . setUpClass ( )
2022-12-08 16:44:47 +03:00
setupClass ( cls ) # The one local to this module
2024-02-15 22:06:38 +03:00
def create_environment ( self ) - > Environment :
return Environment . testing_environment ( )
2022-08-25 23:28:57 +03:00
2024-02-26 07:50:36 +03:00
class UDSTransactionTestCase ( UDSTestCaseMixin , TransactionTestCase ) : # pyright: ignore # superclass client
2022-08-29 16:21:12 +03:00
@classmethod
def setUpClass ( cls ) - > None :
super ( ) . setUpClass ( )
setupClass ( cls )
2022-12-08 16:44:47 +03:00
2023-04-15 22:21:23 +03:00
# pylint: disable=unused-argument
2023-12-04 02:51:05 +03:00
def setupClass ( cls : typing . Union [ type [ UDSTestCase ] , type [ UDSTransactionTestCase ] ] ) - > None :
2022-08-29 16:21:12 +03:00
# Nothing right now
pass