testit-python-commons 3.6.4__tar.gz → 3.6.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/PKG-INFO +1 -1
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/setup.py +1 -1
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/client/api_client.py +18 -1
- testit_python_commons-3.6.5/src/testit_python_commons/utils/__init__.py +3 -0
- testit_python_commons-3.6.5/src/testit_python_commons/utils/html_escape_utils.py +212 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons.egg-info/PKG-INFO +1 -1
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons.egg-info/SOURCES.txt +4 -1
- testit_python_commons-3.6.5/tests/test_html_escape_utils.py +116 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/README.md +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/setup.cfg +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/__init__.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/app_properties.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/client/__init__.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/client/client_configuration.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/client/converter.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/decorators.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/dynamic_methods.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/models/__init__.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/models/adapter_mode.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/models/fixture.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/models/link.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/models/link_type.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/models/outcome_type.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/models/step_result.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/models/test_result.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/models/test_result_with_all_fixture_step_results_model.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/services/__init__.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/services/adapter_manager.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/services/adapter_manager_configuration.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/services/fixture_manager.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/services/fixture_storage.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/services/logger.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/services/plugin_manager.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/services/retry.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/services/step_manager.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/services/step_result_storage.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/services/utils.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/step.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons.egg-info/dependency_links.txt +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons.egg-info/requires.txt +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons.egg-info/top_level.txt +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/tests/test_app_properties.py +0 -0
- {testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/tests/test_dynamic_methods.py +0 -0
|
@@ -21,6 +21,7 @@ from testit_python_commons.client.converter import Converter
|
|
|
21
21
|
from testit_python_commons.models.test_result import TestResult
|
|
22
22
|
from testit_python_commons.services.logger import adapter_logger
|
|
23
23
|
from testit_python_commons.services.retry import retry
|
|
24
|
+
from testit_python_commons.utils.html_escape_utils import HtmlEscapeUtils
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
class ApiClientWorker:
|
|
@@ -57,6 +58,11 @@ class ApiClientWorker:
|
|
|
57
58
|
header_name='Authorization',
|
|
58
59
|
header_value='PrivateToken ' + token)
|
|
59
60
|
|
|
61
|
+
@staticmethod
|
|
62
|
+
def _escape_html_in_model(model):
|
|
63
|
+
"""Apply HTML escaping to all models before sending to API"""
|
|
64
|
+
return HtmlEscapeUtils.escape_html_in_object(model)
|
|
65
|
+
|
|
60
66
|
@adapter_logger
|
|
61
67
|
def create_test_run(self):
|
|
62
68
|
test_run_name = f'TestRun_{datetime.today().strftime("%Y-%m-%dT%H:%M:%S")}' if \
|
|
@@ -65,6 +71,7 @@ class ApiClientWorker:
|
|
|
65
71
|
self.__config.get_project_id(),
|
|
66
72
|
test_run_name
|
|
67
73
|
)
|
|
74
|
+
model = self._escape_html_in_model(model)
|
|
68
75
|
|
|
69
76
|
response = self.__test_run_api.create_empty(create_empty_test_run_api_model=model)
|
|
70
77
|
|
|
@@ -288,6 +295,7 @@ class ApiClientWorker:
|
|
|
288
295
|
logging.debug(f'Autotest "{test_result.get_autotest_name()}" was not found')
|
|
289
296
|
|
|
290
297
|
model = self.__prepare_to_create_autotest(test_result)
|
|
298
|
+
model = self._escape_html_in_model(model)
|
|
291
299
|
|
|
292
300
|
autotest_response = self.__autotest_api.create_auto_test(auto_test_post_model=model)
|
|
293
301
|
|
|
@@ -299,6 +307,7 @@ class ApiClientWorker:
|
|
|
299
307
|
def __create_tests(self, autotests_for_create: typing.List[AutoTestPostModel]):
|
|
300
308
|
logging.debug(f'Creating autotests: "{autotests_for_create}')
|
|
301
309
|
|
|
310
|
+
autotests_for_create = self._escape_html_in_model(autotests_for_create)
|
|
302
311
|
self.__autotest_api.create_multiple(auto_test_post_model=autotests_for_create)
|
|
303
312
|
|
|
304
313
|
logging.debug(f'Autotests were created')
|
|
@@ -308,6 +317,7 @@ class ApiClientWorker:
|
|
|
308
317
|
logging.debug(f'Autotest "{test_result.get_autotest_name()}" was found')
|
|
309
318
|
|
|
310
319
|
model = self.__prepare_to_update_autotest(test_result, autotest)
|
|
320
|
+
model = self._escape_html_in_model(model)
|
|
311
321
|
|
|
312
322
|
self.__autotest_api.update_auto_test(auto_test_put_model=model)
|
|
313
323
|
|
|
@@ -317,6 +327,7 @@ class ApiClientWorker:
|
|
|
317
327
|
def __update_tests(self, autotests_for_update: typing.List[AutoTestPutModel]):
|
|
318
328
|
logging.debug(f'Updating autotests: {autotests_for_update}')
|
|
319
329
|
|
|
330
|
+
autotests_for_update = self._escape_html_in_model(autotests_for_update)
|
|
320
331
|
self.__autotest_api.update_multiple(auto_test_put_model=autotests_for_update)
|
|
321
332
|
|
|
322
333
|
logging.debug(f'Autotests were updated')
|
|
@@ -344,6 +355,7 @@ class ApiClientWorker:
|
|
|
344
355
|
model = Converter.test_result_to_testrun_result_post_model(
|
|
345
356
|
test_result,
|
|
346
357
|
self.__config.get_configuration_id())
|
|
358
|
+
model = self._escape_html_in_model(model)
|
|
347
359
|
|
|
348
360
|
response = self.__test_run_api.set_auto_test_results_for_test_run(
|
|
349
361
|
id=self.__config.get_test_run_id(),
|
|
@@ -358,6 +370,7 @@ class ApiClientWorker:
|
|
|
358
370
|
def __load_test_results(self, test_results: typing.List[AutoTestResultsForTestRunModel]):
|
|
359
371
|
logging.debug(f'Loading test results: {test_results}')
|
|
360
372
|
|
|
373
|
+
test_results = self._escape_html_in_model(test_results)
|
|
361
374
|
self.__test_run_api.set_auto_test_results_for_test_run(
|
|
362
375
|
id=self.__config.get_test_run_id(),
|
|
363
376
|
auto_test_results_for_test_run_model=test_results)
|
|
@@ -379,6 +392,8 @@ class ApiClientWorker:
|
|
|
379
392
|
test_result.get_setup_results())
|
|
380
393
|
model.teardown_results = Converter.step_results_to_auto_test_step_result_update_request(
|
|
381
394
|
test_result.get_teardown_results())
|
|
395
|
+
|
|
396
|
+
model = self._escape_html_in_model(model)
|
|
382
397
|
|
|
383
398
|
try:
|
|
384
399
|
self.__test_results_api.api_v2_test_results_id_put(
|
|
@@ -397,7 +412,9 @@ class ApiClientWorker:
|
|
|
397
412
|
attachment_response = self.__attachments_api.api_v2_attachments_post(
|
|
398
413
|
file=path)
|
|
399
414
|
|
|
400
|
-
|
|
415
|
+
attachment_model = AttachmentPutModel(id=attachment_response.id)
|
|
416
|
+
attachment_model = self._escape_html_in_model(attachment_model)
|
|
417
|
+
attachments.append(attachment_model)
|
|
401
418
|
|
|
402
419
|
logging.debug(f'Attachment "{path}" was uploaded')
|
|
403
420
|
except Exception as exc:
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any, List, Optional, Union
|
|
5
|
+
from datetime import datetime, date, time, timedelta
|
|
6
|
+
from decimal import Decimal
|
|
7
|
+
from uuid import UUID
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class HtmlEscapeUtils:
|
|
11
|
+
"""
|
|
12
|
+
HTML escape utilities for preventing XSS attacks.
|
|
13
|
+
Escapes HTML tags in strings and objects using reflection.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
NO_ESCAPE_HTML_ENV_VAR = "NO_ESCAPE_HTML"
|
|
17
|
+
|
|
18
|
+
# Regex pattern to detect HTML tags (requires at least one non-whitespace character after <)
|
|
19
|
+
# This ensures empty <> brackets are not considered HTML tags
|
|
20
|
+
_HTML_TAG_PATTERN = re.compile(r'<\S.*?(?:>|/>)')
|
|
21
|
+
|
|
22
|
+
# Regex patterns to escape only non-escaped characters
|
|
23
|
+
# Using negative lookbehind to avoid double escaping
|
|
24
|
+
_LESS_THAN_PATTERN = re.compile(r'(?<!\\)<')
|
|
25
|
+
_GREATER_THAN_PATTERN = re.compile(r'(?<!\\)>')
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def escape_html_tags(text: Optional[str]) -> Optional[str]:
|
|
29
|
+
"""
|
|
30
|
+
Escapes HTML tags to prevent XSS attacks.
|
|
31
|
+
First checks if the string contains HTML tags using regex pattern.
|
|
32
|
+
Only performs escaping if HTML tags are detected.
|
|
33
|
+
Escapes all < as \\< and > as \\> only if they are not already escaped.
|
|
34
|
+
Uses regex with negative lookbehind to avoid double escaping.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
text: The text to escape
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Escaped text or original text if escaping is disabled
|
|
41
|
+
"""
|
|
42
|
+
if text is None:
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
# Check if escaping is disabled via environment variable
|
|
46
|
+
no_escape_html = os.environ.get(HtmlEscapeUtils.NO_ESCAPE_HTML_ENV_VAR, "").lower()
|
|
47
|
+
if no_escape_html == "true":
|
|
48
|
+
return text
|
|
49
|
+
|
|
50
|
+
# First check if the string contains HTML tags
|
|
51
|
+
if not HtmlEscapeUtils._HTML_TAG_PATTERN.search(text):
|
|
52
|
+
return text # No HTML tags found, return original string
|
|
53
|
+
|
|
54
|
+
# Use regex with negative lookbehind to escape only non-escaped characters
|
|
55
|
+
result = HtmlEscapeUtils._LESS_THAN_PATTERN.sub(r'\\<', text)
|
|
56
|
+
result = HtmlEscapeUtils._GREATER_THAN_PATTERN.sub(r'\\>', result)
|
|
57
|
+
|
|
58
|
+
return result
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def escape_html_in_object(obj: Any) -> Any:
|
|
62
|
+
"""
|
|
63
|
+
Escapes HTML tags in all string attributes of an object using reflection.
|
|
64
|
+
Also processes list attributes: if list of objects - calls escape_html_in_object_list,
|
|
65
|
+
if list of strings - escapes each string.
|
|
66
|
+
Can be disabled by setting NO_ESCAPE_HTML environment variable to "true".
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
obj: The object to process
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
The processed object with escaped strings
|
|
73
|
+
"""
|
|
74
|
+
if obj is None:
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
# Check if escaping is disabled via environment variable
|
|
78
|
+
no_escape_html = os.environ.get(HtmlEscapeUtils.NO_ESCAPE_HTML_ENV_VAR, "").lower()
|
|
79
|
+
if no_escape_html == "true":
|
|
80
|
+
return obj
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
HtmlEscapeUtils._process_object_attributes(obj)
|
|
84
|
+
except Exception as e:
|
|
85
|
+
# Silently ignore reflection errors
|
|
86
|
+
logging.debug(f"Error processing object attributes: {e}")
|
|
87
|
+
|
|
88
|
+
return obj
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def escape_html_in_object_list(obj_list: Optional[List[Any]]) -> Optional[List[Any]]:
|
|
92
|
+
"""
|
|
93
|
+
Escapes HTML tags in all string attributes of objects in a list using reflection.
|
|
94
|
+
Can be disabled by setting NO_ESCAPE_HTML environment variable to "true".
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
obj_list: The list of objects to process
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
The processed list with escaped strings in all objects
|
|
101
|
+
"""
|
|
102
|
+
if obj_list is None:
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
# Check if escaping is disabled via environment variable
|
|
106
|
+
no_escape_html = os.environ.get(HtmlEscapeUtils.NO_ESCAPE_HTML_ENV_VAR, "").lower()
|
|
107
|
+
if no_escape_html == "true":
|
|
108
|
+
return obj_list
|
|
109
|
+
|
|
110
|
+
for obj in obj_list:
|
|
111
|
+
HtmlEscapeUtils.escape_html_in_object(obj)
|
|
112
|
+
|
|
113
|
+
return obj_list
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def _process_object_attributes(obj: Any) -> None:
|
|
117
|
+
"""
|
|
118
|
+
Process all attributes of an object for HTML escaping.
|
|
119
|
+
"""
|
|
120
|
+
# Handle dictionary-like objects (common in API models)
|
|
121
|
+
if hasattr(obj, '__dict__'):
|
|
122
|
+
for attr_name in dir(obj):
|
|
123
|
+
# Skip private/protected attributes and methods
|
|
124
|
+
if attr_name.startswith('_') or callable(getattr(obj, attr_name, None)):
|
|
125
|
+
continue
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
value = getattr(obj, attr_name)
|
|
129
|
+
HtmlEscapeUtils._process_attribute_value(obj, attr_name, value)
|
|
130
|
+
except Exception as e:
|
|
131
|
+
# Silently ignore attribute errors
|
|
132
|
+
logging.debug(f"Error processing attribute {attr_name}: {e}")
|
|
133
|
+
|
|
134
|
+
# Handle dictionary objects
|
|
135
|
+
elif isinstance(obj, dict):
|
|
136
|
+
for key, value in obj.items():
|
|
137
|
+
if isinstance(value, str):
|
|
138
|
+
obj[key] = HtmlEscapeUtils.escape_html_tags(value)
|
|
139
|
+
elif isinstance(value, list):
|
|
140
|
+
HtmlEscapeUtils._process_list(value)
|
|
141
|
+
elif not HtmlEscapeUtils._is_simple_type(type(value)):
|
|
142
|
+
HtmlEscapeUtils.escape_html_in_object(value)
|
|
143
|
+
|
|
144
|
+
@staticmethod
|
|
145
|
+
def _process_attribute_value(obj: Any, attr_name: str, value: Any) -> None:
|
|
146
|
+
"""
|
|
147
|
+
Process a single attribute value for HTML escaping.
|
|
148
|
+
"""
|
|
149
|
+
if isinstance(value, str):
|
|
150
|
+
# Escape string attributes
|
|
151
|
+
try:
|
|
152
|
+
setattr(obj, attr_name, HtmlEscapeUtils.escape_html_tags(value))
|
|
153
|
+
except AttributeError:
|
|
154
|
+
# Attribute might be read-only
|
|
155
|
+
pass
|
|
156
|
+
elif isinstance(value, list):
|
|
157
|
+
HtmlEscapeUtils._process_list(value)
|
|
158
|
+
elif value is not None and not HtmlEscapeUtils._is_simple_type(type(value)):
|
|
159
|
+
# Process nested objects (but not simple types)
|
|
160
|
+
HtmlEscapeUtils.escape_html_in_object(value)
|
|
161
|
+
|
|
162
|
+
@staticmethod
|
|
163
|
+
def _process_list(lst: List[Any]) -> None:
|
|
164
|
+
"""
|
|
165
|
+
Process a list for HTML escaping.
|
|
166
|
+
"""
|
|
167
|
+
if not lst:
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
first_element = lst[0]
|
|
171
|
+
|
|
172
|
+
if isinstance(first_element, str):
|
|
173
|
+
# List of strings - escape each string
|
|
174
|
+
for i, item in enumerate(lst):
|
|
175
|
+
if isinstance(item, str):
|
|
176
|
+
lst[i] = HtmlEscapeUtils.escape_html_tags(item)
|
|
177
|
+
elif first_element is not None:
|
|
178
|
+
# List of objects - process each object
|
|
179
|
+
for item in lst:
|
|
180
|
+
HtmlEscapeUtils.escape_html_in_object(item)
|
|
181
|
+
|
|
182
|
+
@staticmethod
|
|
183
|
+
def _is_simple_type(obj_type: type) -> bool:
|
|
184
|
+
"""
|
|
185
|
+
Checks if a type is a simple type that doesn't need HTML escaping.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
obj_type: Type to check
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
True if it's a simple type
|
|
192
|
+
"""
|
|
193
|
+
simple_types = {
|
|
194
|
+
# Basic types
|
|
195
|
+
bool, int, float, complex, bytes, bytearray,
|
|
196
|
+
# String type (handled separately)
|
|
197
|
+
str,
|
|
198
|
+
# Date/time types
|
|
199
|
+
datetime, date, time, timedelta,
|
|
200
|
+
# Other common types
|
|
201
|
+
Decimal, UUID,
|
|
202
|
+
# None type
|
|
203
|
+
type(None)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
obj_type in simple_types or
|
|
208
|
+
# Check for enums
|
|
209
|
+
(hasattr(obj_type, '__bases__') and any(
|
|
210
|
+
base.__name__ == 'Enum' for base in obj_type.__bases__
|
|
211
|
+
))
|
|
212
|
+
)
|
|
@@ -35,5 +35,8 @@ src/testit_python_commons/services/retry.py
|
|
|
35
35
|
src/testit_python_commons/services/step_manager.py
|
|
36
36
|
src/testit_python_commons/services/step_result_storage.py
|
|
37
37
|
src/testit_python_commons/services/utils.py
|
|
38
|
+
src/testit_python_commons/utils/__init__.py
|
|
39
|
+
src/testit_python_commons/utils/html_escape_utils.py
|
|
38
40
|
tests/test_app_properties.py
|
|
39
|
-
tests/test_dynamic_methods.py
|
|
41
|
+
tests/test_dynamic_methods.py
|
|
42
|
+
tests/test_html_escape_utils.py
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import unittest
|
|
3
|
+
from testit_python_commons.utils.html_escape_utils import HtmlEscapeUtils
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SampleData:
|
|
7
|
+
def __init__(self, name="Test Name", description="<script>alert('xss')</script>"):
|
|
8
|
+
self.name = name
|
|
9
|
+
self.description = description
|
|
10
|
+
self.tags = ["<tag>", "normal_tag"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestHtmlEscapeUtils(unittest.TestCase):
|
|
14
|
+
|
|
15
|
+
def test_escape_html_tags_basic(self):
|
|
16
|
+
"""Test basic HTML tag escaping"""
|
|
17
|
+
text = "Hello <script>alert('test')</script> world"
|
|
18
|
+
result = HtmlEscapeUtils.escape_html_tags(text)
|
|
19
|
+
expected = "Hello \\<script\\>alert('test')\\</script\\> world"
|
|
20
|
+
self.assertEqual(result, expected)
|
|
21
|
+
|
|
22
|
+
def test_escape_html_tags_no_html_content(self):
|
|
23
|
+
"""Test that strings without HTML tags are returned unchanged"""
|
|
24
|
+
text = "Just plain text without any tags"
|
|
25
|
+
result = HtmlEscapeUtils.escape_html_tags(text)
|
|
26
|
+
self.assertEqual(result, text) # Should be identical object
|
|
27
|
+
|
|
28
|
+
text_with_empty_brackets = "Text with & symbols and other <> but not HTML tags"
|
|
29
|
+
result = HtmlEscapeUtils.escape_html_tags(text_with_empty_brackets)
|
|
30
|
+
# This should remain unchanged because <> is not a valid HTML tag
|
|
31
|
+
self.assertEqual(result, text_with_empty_brackets)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_escape_html_tags_none(self):
|
|
35
|
+
"""Test handling of None input"""
|
|
36
|
+
result = HtmlEscapeUtils.escape_html_tags(None)
|
|
37
|
+
self.assertIsNone(result)
|
|
38
|
+
|
|
39
|
+
def test_escape_html_tags_no_double_escaping(self):
|
|
40
|
+
"""Test that already escaped characters are not double-escaped"""
|
|
41
|
+
text = "Already \\<escaped\\> and <not_escaped>"
|
|
42
|
+
result = HtmlEscapeUtils.escape_html_tags(text)
|
|
43
|
+
expected = "Already \\<escaped\\> and \\<not_escaped\\>"
|
|
44
|
+
self.assertEqual(result, expected)
|
|
45
|
+
|
|
46
|
+
def test_escape_html_tags_various_tags(self):
|
|
47
|
+
"""Test escaping of various HTML tag types"""
|
|
48
|
+
test_cases = [
|
|
49
|
+
("<div>content</div>", "\\<div\\>content\\</div\\>"),
|
|
50
|
+
("<img src='test.jpg'/>", "\\<img src='test.jpg'/\\>"),
|
|
51
|
+
("<br>", "\\<br\\>"),
|
|
52
|
+
("<span class='test'>text</span>", "\\<span class='test'\\>text\\</span\\>"),
|
|
53
|
+
("No tags here", "No tags here"), # Should remain unchanged
|
|
54
|
+
("<>", "<>"), # Empty angle brackets - should remain unchanged
|
|
55
|
+
("< >", "< >"), # Spaced angle brackets - should remain unchanged
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
for input_text, expected in test_cases:
|
|
59
|
+
with self.subTest(input_text=input_text):
|
|
60
|
+
result = HtmlEscapeUtils.escape_html_tags(input_text)
|
|
61
|
+
self.assertEqual(result, expected)
|
|
62
|
+
|
|
63
|
+
def test_escape_html_in_object(self):
|
|
64
|
+
"""Test HTML escaping in object attributes"""
|
|
65
|
+
test_obj = SampleData()
|
|
66
|
+
result = HtmlEscapeUtils.escape_html_in_object(test_obj)
|
|
67
|
+
|
|
68
|
+
self.assertEqual(result.name, "Test Name") # No escaping needed
|
|
69
|
+
self.assertEqual(result.description, "\\<script\\>alert('xss')\\</script\\>") # Escaped
|
|
70
|
+
self.assertEqual(result.tags[0], "\\<tag\\>") # Escaped
|
|
71
|
+
self.assertEqual(result.tags[1], "normal_tag") # No escaping needed
|
|
72
|
+
|
|
73
|
+
def test_escape_html_in_object_list(self):
|
|
74
|
+
"""Test HTML escaping in list of objects"""
|
|
75
|
+
test_list = [
|
|
76
|
+
SampleData("First", "<div>content</div>"),
|
|
77
|
+
SampleData("Second", "<span>more</span>")
|
|
78
|
+
]
|
|
79
|
+
result = HtmlEscapeUtils.escape_html_in_object_list(test_list)
|
|
80
|
+
|
|
81
|
+
self.assertEqual(result[0].description, "\\<div\\>content\\</div\\>")
|
|
82
|
+
self.assertEqual(result[1].description, "\\<span\\>more\\</span\\>")
|
|
83
|
+
|
|
84
|
+
def test_escape_disabled_by_env_var(self):
|
|
85
|
+
"""Test that escaping can be disabled via environment variable"""
|
|
86
|
+
os.environ[HtmlEscapeUtils.NO_ESCAPE_HTML_ENV_VAR] = "true"
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
text = "Hello <script>alert('test')</script> world"
|
|
90
|
+
result = HtmlEscapeUtils.escape_html_tags(text)
|
|
91
|
+
self.assertEqual(result, text) # Should be unchanged
|
|
92
|
+
|
|
93
|
+
test_obj = SampleData()
|
|
94
|
+
result = HtmlEscapeUtils.escape_html_in_object(test_obj)
|
|
95
|
+
self.assertEqual(result.description, "<script>alert('xss')</script>") # Should be unchanged
|
|
96
|
+
finally:
|
|
97
|
+
# Clean up environment variable
|
|
98
|
+
del os.environ[HtmlEscapeUtils.NO_ESCAPE_HTML_ENV_VAR]
|
|
99
|
+
|
|
100
|
+
def test_escape_html_in_dictionary(self):
|
|
101
|
+
"""Test HTML escaping in dictionary objects"""
|
|
102
|
+
test_dict = {
|
|
103
|
+
"name": "Test",
|
|
104
|
+
"description": "<script>alert('xss')</script>",
|
|
105
|
+
"tags": ["<tag>", "normal_tag"]
|
|
106
|
+
}
|
|
107
|
+
result = HtmlEscapeUtils.escape_html_in_object(test_dict)
|
|
108
|
+
|
|
109
|
+
self.assertEqual(result["name"], "Test")
|
|
110
|
+
self.assertEqual(result["description"], "\\<script\\>alert('xss')\\</script\\>")
|
|
111
|
+
self.assertEqual(result["tags"][0], "\\<tag\\>")
|
|
112
|
+
self.assertEqual(result["tags"][1], "normal_tag")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
if __name__ == '__main__':
|
|
116
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/decorators.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/models/link.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{testit_python_commons-3.6.4 → testit_python_commons-3.6.5}/src/testit_python_commons/step.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|