From 1bceb8dcf8ad380771b8581abe319715f89acc47 Mon Sep 17 00:00:00 2001 From: Johannes Ernst Date: Mon, 16 Dec 2024 11:57:50 -0800 Subject: [PATCH 01/10] Set jinja2 autoescape. Closes #424 --- src/feditest/testruntranscriptserializer/html.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/feditest/testruntranscriptserializer/html.py b/src/feditest/testruntranscriptserializer/html.py index 16a2e06..44ff20b 100644 --- a/src/feditest/testruntranscriptserializer/html.py +++ b/src/feditest/testruntranscriptserializer/html.py @@ -61,7 +61,8 @@ def __init__(self, template_path: str): self.template_path = [ os.path.join(os.path.dirname(__file__), "templates/testplantranscript_default") ] self.jinja2_env = jinja2.Environment( - loader=jinja2.FileSystemLoader(self.template_path) + loader=jinja2.FileSystemLoader(self.template_path), + autoescape=jinja2.select_autoescape() ) self.jinja2_env.filters["regex_sub"] = lambda s, pattern, replacement: re.sub( pattern, replacement, s From a8a6255fb687fb5a4b58cc0eb3bb11c347aed7fb Mon Sep 17 00:00:00 2001 From: Johannes Ernst Date: Mon, 16 Dec 2024 12:15:04 -0800 Subject: [PATCH 02/10] Sanitize app name and app version read from user --- src/feditest/utils.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/feditest/utils.py b/src/feditest/utils.py index 3725d63..d5c9371 100644 --- a/src/feditest/utils.py +++ b/src/feditest/utils.py @@ -445,7 +445,13 @@ def appname_validate(candidate: str) -> str | None: Validate that the provided string is a valid application name. return: string if valid, None otherwise """ - return candidate if len(candidate) > 0 else None + if len(candidate) > 255: + return None + if len(candidate) == 0: + return None + if any(c in candidate for c in ['<', '>', '&'] ): + return None + return candidate def appversion_validate(candidate: str) -> str | None: @@ -453,7 +459,13 @@ def appversion_validate(candidate: str) -> str | None: Validate that the provided string is a valid application version. return: string if value, None otherwise """ - return candidate if len(candidate) > 0 else None + if len(candidate) > 255: + return None + if len(candidate) == 0: + return None + if any(c in candidate for c in ['<', '>', '&'] ): + return None + return candidate def boolean_response_parse_validate(candidate:str) -> bool | None: From 71c362ac4f614d5de598b92fa4c4bd449ce68541 Mon Sep 17 00:00:00 2001 From: Johannes Ernst Date: Mon, 16 Dec 2024 15:42:32 -0800 Subject: [PATCH 03/10] Add validation methods to data structures read from JSON or instantiated locally --- src/feditest/cli/utils.py | 1 + src/feditest/testplan.py | 96 ++++++++++++++++++- src/feditest/utils.py | 13 +++ tests.unit/test_30_create_testplan.py | 1 + ...llback_fediverse_accounts_from_testplan.py | 5 +- .../test_40_report_node_driver_errors.py | 1 + ...40_ubos_mastodon_accounts_from_testplan.py | 5 +- tests.unit/test_50_run_assertion_raises.py | 3 +- tests.unit/test_50_run_exceptions.py | 1 + .../test_50_run_multistep_assertion_raises.py | 1 + tests.unit/test_50_run_not_implemented.py | 1 + tests.unit/test_50_run_passes.py | 1 + tests.unit/test_50_run_skip.py | 1 + tests.unit/test_50_run_testplan_sandbox.py | 1 + 14 files changed, 124 insertions(+), 7 deletions(-) diff --git a/src/feditest/cli/utils.py b/src/feditest/cli/utils.py index 5bba831..8c57276 100644 --- a/src/feditest/cli/utils.py +++ b/src/feditest/cli/utils.py @@ -29,6 +29,7 @@ def create_plan_from_session_and_constellations(args: Namespace) -> TestPlan | N constellations = create_constellations(args) plan = TestPlan(session, constellations, args.name) + plan.properties_validate() plan.simplify() return plan diff --git a/src/feditest/testplan.py b/src/feditest/testplan.py index e7c0d54..76f9f5c 100644 --- a/src/feditest/testplan.py +++ b/src/feditest/testplan.py @@ -10,8 +10,7 @@ import msgspec import feditest -from feditest.utils import hostname_validate, FEDITEST_VERSION - +from feditest.utils import hostname_validate, symbolic_name_validate, FEDITEST_VERSION class InvalidAccountSpecificationException(Exception): """ @@ -143,6 +142,43 @@ class TestPlanConstellationNode(msgspec.Struct): non_existing_accounts: list[dict[str, str | None]] | None = None + def properties_validate(self) -> None: + """ + Validate properties for correctness and safety. + """ + # Unclear whether we can validate the values of the dicts; currently not done. + + if self.parameters: + for par_name, _ in self.parameters.items(): + if not par_name: + raise ValueError('Invalid TestPlanConstellationNode parameter name: cannot be empty') + + if not symbolic_name_validate(par_name): + raise ValueError(f'Invalid TestPlanConstellationNode parameter name: { par_name }') + + if self.accounts: + for account in self.accounts: + if 'role' not in account: + raise ValueError('No role name given in account') + + if not account['role']: + raise ValueError('Invalid TestPlanConstellationNode account role name: cannot be empty') + + if not symbolic_name_validate(account['role']): + raise ValueError(f'Invalid role name in account: "{ account["role" ]}') + + if self.non_existing_accounts: + for non_existing_account in self.non_existing_accounts: + if 'role' not in non_existing_account: + raise ValueError('No role name given in non_existing_account') + + if not non_existing_account['role']: + raise ValueError('Invalid TestPlanConstellationNode non_existing_account role name: cannot be empty') + + if not symbolic_name_validate(non_existing_account['role']): + raise ValueError(f'Invalid role name in non_existing_account: "{ non_existing_account["role" ]}') + + @staticmethod def load(filename: str) -> 'TestPlanConstellationNode': """ @@ -224,6 +260,18 @@ class TestPlanConstellation(msgspec.Struct): roles : dict[str,TestPlanConstellationNode | None] # can be None if used as template name: str | None = None + def properties_validate(self) -> None: + """ + Validate properties for correctness and safety. + """ + for role_name, role_value in self.roles.items(): + if not symbolic_name_validate(role_name): + raise ValueError(f'Invalid TestPlanConstellation role name: { role_name }') + if role_value: + role_value.properties_validate() + + # Not checking self.name: can be any human-readable name + @staticmethod def load(filename: str) -> 'TestPlanConstellation': @@ -288,6 +336,21 @@ class TestPlanTestSpec(msgspec.Struct): skip: str | None = None # if a string is given, it's a reason message why this test spec should be skipped + def properties_validate(self) -> None: + """ + Validate properties for correctness and safety. + """ + # Not checking self.name: can be any human-readable name + + if self.rolemapping: + for role_mapping_key, role_mapping_value in self.rolemapping.items(): + if not symbolic_name_validate(role_mapping_key): + raise ValueError(f'Invalid TestPlanTestSpec role mapping key: { role_mapping_key }') + + if not symbolic_name_validate(role_mapping_value): + raise ValueError(f'Invalid TestPlanTestSpec role mapping value: { role_mapping_value }') + + def __str__(self): return self.name @@ -348,6 +411,16 @@ class TestPlanSessionTemplate(msgspec.Struct): name: str | None = None + def properties_validate(self) -> None: + """ + Validate properties for correctness and safety. + """ + for test in self.tests: + test.properties_validate() + + # Not checking self.name: can be any human-readable name + + @staticmethod def load(filename: str) -> 'TestPlanSessionTemplate': """ @@ -412,6 +485,25 @@ class TestPlan(msgspec.Struct): feditest_version: str = FEDITEST_VERSION + def properties_validate(self) -> None: + """ + Validate properties for correctness and safety. + """ + if self.session_template is None: + raise ValueError('No session_template in TestPlan') + self.session_template.properties_validate() + + for constellation in self.constellations: + constellation.properties_validate() + + # Not checking self.name: can be any human-readable name + if self.type != 'feditest-testplan': + raise ValueError(f'TestPlan type is not feditest-testplan: "{ self.type }".') + + if not symbolic_name_validate(self.feditest_version): + raise ValueError(f'Invalid TestPlan FediTest version: "{ self.feditest_version }".') + + def simplify(self) -> None: """ If possible, simplify this test plan. diff --git a/src/feditest/utils.py b/src/feditest/utils.py index d5c9371..7d3c89e 100644 --- a/src/feditest/utils.py +++ b/src/feditest/utils.py @@ -488,6 +488,19 @@ def boolean_response_parse_validate(candidate:str) -> bool | None: return None +def symbolic_name_validate(candidate_name: str) -> str | None: + """ + Validate a symbolic name, as used, for example, as the name of a role in a TestPlan. + """ + if not candidate_name: + return None + if len(candidate_name) > 255: + return None + if re.fullmatch('[-_a-z-A-Z0-9.]+', candidate_name): + return candidate_name + return None + + def find_first_in_array(array: List[T], condition: Callable[[T], bool]) -> T | None: """ IMHO this should be a python built-in function. The next() workaround confuses me more than I like. diff --git a/tests.unit/test_30_create_testplan.py b/tests.unit/test_30_create_testplan.py index 1c39ba3..c0c0dd6 100644 --- a/tests.unit/test_30_create_testplan.py +++ b/tests.unit/test_30_create_testplan.py @@ -59,6 +59,7 @@ def construct_testplan(constellations: list[TestPlanConstellation], session_temp Helper to put it together. """ test_plan = TestPlan(session_template, constellations, testplan_name) + test_plan.properties_validate() test_plan.simplify() return test_plan diff --git a/tests.unit/test_40_fallback_fediverse_accounts_from_testplan.py b/tests.unit/test_40_fallback_fediverse_accounts_from_testplan.py index c5a35bf..b98caf9 100644 --- a/tests.unit/test_40_fallback_fediverse_accounts_from_testplan.py +++ b/tests.unit/test_40_fallback_fediverse_accounts_from_testplan.py @@ -56,9 +56,10 @@ def test_plan_fixture() -> TestPlan: } ] node1 = TestPlanConstellationNode(node_driver, parameters, plan_accounts, plan_non_existing_accounts) - constellation = TestPlanConstellation( { NODE1_ROLE : node1 }) + constellation = TestPlanConstellation({ NODE1_ROLE : node1 }) session_template = TestPlanSessionTemplate([]) - ret = TestPlan( session_template, [ constellation ] ) + ret = TestPlan(session_template, [ constellation ]) + ret.properties_validate() return ret diff --git a/tests.unit/test_40_report_node_driver_errors.py b/tests.unit/test_40_report_node_driver_errors.py index e668505..fa968fe 100644 --- a/tests.unit/test_40_report_node_driver_errors.py +++ b/tests.unit/test_40_report_node_driver_errors.py @@ -77,6 +77,7 @@ def test_faulty_node_driver_reporting() -> None: }), ] ) + plan.properties_validate() run = TestRun(plan) controller = AutomaticTestRunController(run) diff --git a/tests.unit/test_40_ubos_mastodon_accounts_from_testplan.py b/tests.unit/test_40_ubos_mastodon_accounts_from_testplan.py index e8c91aa..66da2f0 100644 --- a/tests.unit/test_40_ubos_mastodon_accounts_from_testplan.py +++ b/tests.unit/test_40_ubos_mastodon_accounts_from_testplan.py @@ -65,9 +65,10 @@ def the_test_plan() -> TestPlan: } ] node1 = TestPlanConstellationNode(node_driver, parameters, plan_accounts, plan_non_existing_accounts) - constellation = TestPlanConstellation( { NODE1_ROLE : node1 }) + constellation = TestPlanConstellation({ NODE1_ROLE : node1 }) session = TestPlanSessionTemplate([]) - ret = TestPlan( session, [ constellation ] ) + ret = TestPlan(session, [ constellation ]) + ret.properties_validate() return ret diff --git a/tests.unit/test_50_run_assertion_raises.py b/tests.unit/test_50_run_assertion_raises.py index 4d957e4..110f020 100644 --- a/tests.unit/test_50_run_assertion_raises.py +++ b/tests.unit/test_50_run_assertion_raises.py @@ -179,7 +179,8 @@ def the_test_plan() -> TestPlan: constellation = TestPlanConstellation({}, 'No nodes needed') tests = [ TestPlanTestSpec(name) for name in sorted(feditest.all_tests.keys()) if feditest.all_tests.get(name) is not None ] session = TestPlanSessionTemplate(tests, "Test tests that raise various AssertionFailures") - ret = TestPlan( session, [ constellation ] ) + ret = TestPlan(session, [ constellation ]) + ret.properties_validate() return ret diff --git a/tests.unit/test_50_run_exceptions.py b/tests.unit/test_50_run_exceptions.py index 0ae0484..2abce7c 100644 --- a/tests.unit/test_50_run_exceptions.py +++ b/tests.unit/test_50_run_exceptions.py @@ -86,6 +86,7 @@ def test_plan_fixture() -> TestPlan: tests = [ TestPlanTestSpec(name) for name in sorted(feditest.all_tests.keys()) if feditest.all_tests.get(name) is not None ] session = TestPlanSessionTemplate(tests, "Tests buggy tests") ret = TestPlan(session, [ constellation ]) + ret.properties_validate() return ret diff --git a/tests.unit/test_50_run_multistep_assertion_raises.py b/tests.unit/test_50_run_multistep_assertion_raises.py index ababba8..160f68b 100644 --- a/tests.unit/test_50_run_multistep_assertion_raises.py +++ b/tests.unit/test_50_run_multistep_assertion_raises.py @@ -181,6 +181,7 @@ def test_plan_fixture() -> TestPlan: tests = [ TestPlanTestSpec(name) for name in sorted(feditest.all_tests.keys()) if feditest.all_tests.get(name) is not None ] session = TestPlanSessionTemplate(tests, "Test a test whose steps raises multiple AssertionFailures") ret = TestPlan(session, [ constellation ]) + ret.properties_validate() return ret diff --git a/tests.unit/test_50_run_not_implemented.py b/tests.unit/test_50_run_not_implemented.py index 02de338..f2fdc4b 100644 --- a/tests.unit/test_50_run_not_implemented.py +++ b/tests.unit/test_50_run_not_implemented.py @@ -78,6 +78,7 @@ def test_plan_fixture() -> TestPlan: tests = [ TestPlanTestSpec(name) for name in sorted(feditest.all_tests.keys()) if feditest.all_tests.get(name) is not None ] session = TestPlanSessionTemplate(tests, "Test tests that throw NotImplemented errors") ret = TestPlan(session, [ constellation ]) + ret.properties_validate() return ret diff --git a/tests.unit/test_50_run_passes.py b/tests.unit/test_50_run_passes.py index e61b171..fa6d153 100644 --- a/tests.unit/test_50_run_passes.py +++ b/tests.unit/test_50_run_passes.py @@ -59,6 +59,7 @@ def test_plan_fixture() -> TestPlan: tests = [ TestPlanTestSpec(name) for name in sorted(feditest.all_tests.keys()) if feditest.all_tests.get(name) is not None ] session = TestPlanSessionTemplate(tests, "Test a test that passes") ret = TestPlan(session, [ constellation ]) + ret.properties_validate() return ret diff --git a/tests.unit/test_50_run_skip.py b/tests.unit/test_50_run_skip.py index 9f49f33..12c74c8 100644 --- a/tests.unit/test_50_run_skip.py +++ b/tests.unit/test_50_run_skip.py @@ -61,6 +61,7 @@ def the_test_plan() -> TestPlan: tests = [ TestPlanTestSpec(name) for name in sorted(feditest.all_tests.keys()) if feditest.all_tests.get(name) is not None ] session = TestPlanSessionTemplate(tests, "Test a test that wants to be skipped") ret = TestPlan(session, [ constellation ]) + ret.properties_validate() return ret diff --git a/tests.unit/test_50_run_testplan_sandbox.py b/tests.unit/test_50_run_testplan_sandbox.py index 22683d0..6908790 100644 --- a/tests.unit/test_50_run_testplan_sandbox.py +++ b/tests.unit/test_50_run_testplan_sandbox.py @@ -161,6 +161,7 @@ def test_plan_fixture() -> TestPlan: tests = [ TestPlanTestSpec(name) for name in sorted(feditest.all_tests.keys()) if feditest.all_tests.get(name) is not None ] session = TestPlanSessionTemplate(tests, "clientA vs server") ret = TestPlan(session, [ constellation ], "All sandbox tests running clientA against server1") + ret.properties_validate() return ret From 1de9fe5e36d52d809944d639d889a309a358726b Mon Sep 17 00:00:00 2001 From: Johannes Ernst Date: Mon, 16 Dec 2024 15:45:24 -0800 Subject: [PATCH 04/10] Better URLs to UBOS Gears while I see it. --- src/feditest/nodedrivers/ubos.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/feditest/nodedrivers/ubos.py b/src/feditest/nodedrivers/ubos.py index aa2dd96..f84c745 100644 --- a/src/feditest/nodedrivers/ubos.py +++ b/src/feditest/nodedrivers/ubos.py @@ -1,5 +1,5 @@ """ -Nodes managed via UBOS Gears https://ubos.net/ +Nodes managed via UBOS Gears https://ubos.net/docs/gears/ """ from abc import abstractmethod import hashlib @@ -440,10 +440,10 @@ def _provision_node(self, rolename: str, config: NodeConfiguration, account_mana if config._rshcmd: if self._exec_shell('which ubos-admin', config._rshcmd).returncode: - raise OSError(f'{ type(self).__name__ } with an rshcmd requires UBOS Gears on the remote system (see ubos.net).') + raise OSError(f'{ type(self).__name__ } with an rshcmd requires UBOS Gears on the remote system (see https://feditest.org/glossary/ubosgears/).') else: if not shutil.which('ubos-admin'): - raise OSError(f'{ type(self).__name__ } without an rshcmd requires a local system running UBOS Gears (see ubos.net).') + raise OSError(f'{ type(self).__name__ } without an rshcmd requires a local system running UBOS Gears (see https://feditest.org/glossary/ubosgears/).') if account_manager is None: raise RuntimeError(f'No AccountManager set for rolename { rolename } with UbosNodeDriver { self }') From 08ee4f9ffcfe6dcccddc41e9ea3d353a4baf5d5b Mon Sep 17 00:00:00 2001 From: Johannes Ernst Date: Mon, 16 Dec 2024 15:45:59 -0800 Subject: [PATCH 05/10] Sanitize HTML output better --- .../testruntranscriptserializer/html.py | 5 ++--- .../testplantranscript_default/matrix.jinja2 | 4 ++-- .../partials/matrix/matrix.jinja2 | 10 +++++----- .../partials/matrix/metadata.jinja2 | 6 +++--- .../partials/matrix/testresult.jinja2 | 2 +- .../partials/shared/summary.jinja2 | 2 +- .../partials/shared_session/metadata.jinja2 | 2 +- .../partials/shared_session/results.jinja2 | 18 +++++++++--------- .../partials/shared_session/testresult.jinja2 | 4 ++-- .../session_single.jinja2 | 4 ++-- .../session_with_matrix.jinja2 | 4 ++-- 11 files changed, 30 insertions(+), 31 deletions(-) diff --git a/src/feditest/testruntranscriptserializer/html.py b/src/feditest/testruntranscriptserializer/html.py index 44ff20b..c853b94 100644 --- a/src/feditest/testruntranscriptserializer/html.py +++ b/src/feditest/testruntranscriptserializer/html.py @@ -89,11 +89,10 @@ def write(self, transcript: TestRunTranscript, dest: str | None): permit_line_breaks_in_identifier=lambda s: re.sub( r"(\.|::)", r"\1", s ), - local_name_with_tooltip=lambda n: f'{ n.split(".")[-1] }', + local_name_with_tooltip=lambda n: f'{ n.split(".")[-1] }', format_timestamp=lambda ts: ts.strftime("%Y:%m:%d-%H:%M:%S.%fZ") if ts else "", format_duration=lambda s: str(s), # makes it easier to change in the future - len=len, - html_escape=lambda s: html.escape(str(s)) + len=len ) try: diff --git a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/matrix.jinja2 b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/matrix.jinja2 index ad6de61..32714d0 100644 --- a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/matrix.jinja2 +++ b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/matrix.jinja2 @@ -2,11 +2,11 @@ {% include "partials/shared/head.jinja2" %} - {{ transcript.plan.name }} | Feditest + {{ transcript.plan.name | e }} | Feditest
-

Feditest Summary Report: {{ transcript.plan.name }}

+

Feditest Summary Report: {{ transcript.plan.name | e }}

{{ transcript.id }}

{% include "partials/shared/mobile.jinja2" %} diff --git a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/matrix/matrix.jinja2 b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/matrix/matrix.jinja2 index fbbd276..1890414 100644 --- a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/matrix/matrix.jinja2 +++ b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/matrix/matrix.jinja2 @@ -1,6 +1,6 @@ {% macro column_headings(first) %} - {{ first }} + {{ first | e }} {%- for run_session in transcript.sessions %} {%- set constellation = run_session.constellation %} @@ -8,8 +8,8 @@
{%- for role, node in constellation.nodes.items() %} -
{{ role }}
-
{{ node.node_driver }}
+
{{ role | e }}
+
{{ node.node_driver | e }}
{%- endfor %}
@@ -34,9 +34,9 @@ {%- for test_index, ( _, test_meta ) in enumerate(sorted(transcript.test_meta.items())) %} - {{ permit_line_breaks_in_identifier(test_meta.name) }} + {{ permit_line_breaks_in_identifier(test_meta.name) | e }} {%- if test_meta.description %} - {{ test_meta.description }} + {{ test_meta.description | e }} {%- endif %} {%- for session_index, run_session in enumerate(transcript.sessions) %} diff --git a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/matrix/metadata.jinja2 b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/matrix/metadata.jinja2 index fd76e83..c65f825 100644 --- a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/matrix/metadata.jinja2 +++ b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/matrix/metadata.jinja2 @@ -15,14 +15,14 @@ {%- for key in ['username', 'hostname', 'platform'] -%} {%- if getattr(transcript,key) %} - {{ key.capitalize() }} - {{ getattr(transcript, key) }} + {{ key.capitalize() | e }} + {{ getattr(transcript, key) | e }} {%- endif %} {%- endfor %} Feditest version - {{ getattr(transcript, 'feditest_version') }} + {{ getattr(transcript, 'feditest_version') | e }} diff --git a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/matrix/testresult.jinja2 b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/matrix/testresult.jinja2 index 00e1c1c..b557c70 100644 --- a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/matrix/testresult.jinja2 +++ b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/matrix/testresult.jinja2 @@ -1,6 +1,6 @@ {%- if result %} -
{{ result.short_title() }}
+
{{ result.short_title() | e }}
{%- else %} diff --git a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared/summary.jinja2 b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared/summary.jinja2 index e80ef85..dd23375 100644 --- a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared/summary.jinja2 +++ b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared/summary.jinja2 @@ -54,7 +54,7 @@ {%- for spec_level in [ feditest.SpecLevel.SHOULD, feditest.SpecLevel.IMPLIED, feditest.SpecLevel.UNSPECIFIED ] %} - {{ spec_level.formatted_name }} + {{ spec_level.formatted_name | e }} {%- for interop_level in feditest.InteropLevel %}
diff --git a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared_session/metadata.jinja2 b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared_session/metadata.jinja2 index 299284a..887fcd8 100644 --- a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared_session/metadata.jinja2 +++ b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared_session/metadata.jinja2 @@ -16,7 +16,7 @@ {%- if getattr(transcript,key) %} {{ key.capitalize() }} - {{ getattr(transcript, key) }} + {{ getattr(transcript, key) | e }} {%- endif %} {%- endfor %} diff --git a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared_session/results.jinja2 b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared_session/results.jinja2 index e5ff58a..5e14d39 100644 --- a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared_session/results.jinja2 +++ b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared_session/results.jinja2 @@ -2,10 +2,10 @@
{%- for role_name, node in run_session.constellation.nodes.items() %}
-

{{ role_name }}

+

{{ role_name | e }}

{{ local_name_with_tooltip(node.node_driver) }}
-
{{ node.appdata['app'] }}
-
{{ node.appdata['app_version'] or '?'}}
+
{{ node.appdata['app'] | e }}
+
{{ node.appdata['app_version'] or '?' | e }}
{%- if node.parameters %} @@ -16,8 +16,8 @@ {%- for key, value in node.parameters.items() %} - - + + {%- endfor %} @@ -33,9 +33,9 @@ {%- set plan_test_spec = transcript.plan.session_template.tests[run_test.plan_test_index] %} {%- set test_meta = transcript.test_meta[plan_test_spec.name] %}
-

Test: {{ test_meta.name }}

+

Test: {{ test_meta.name | e }}

{%- if test_meta.description %} -
{{ test_meta.description}}
+
{{ test_meta.description | e }}
{%- endif %}

Started {{ format_timestamp(run_test.started) }}, ended {{ format_timestamp(run_test.ended) }} (duration: {{ format_duration(run_test.ended - run_test.started) }})

{%- with result=run_test.worst_result %} @@ -44,9 +44,9 @@ {%- for test_step_index, run_step in enumerate(run_test.run_steps or []) %}
{% set test_step_meta = test_meta.steps[run_step.plan_step_index] %} -
Test step: {{ test_step_meta.name }}
+
Test step: {{ test_step_meta.name | e }}
{%- if test_step_meta.description %} -
{{ test_step_meta.description}}
+
{{ test_step_meta.description | e }}
{%- endif %}

Started {{ format_timestamp(run_test.started) }}, ended {{ format_timestamp(run_test.ended) }} (duration: {{ format_duration(run_test.ended - run_test.started) }})

{%- with result=run_step.result, idmod='step' %} diff --git a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared_session/testresult.jinja2 b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared_session/testresult.jinja2 index 0272b5e..0fcbd54 100644 --- a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared_session/testresult.jinja2 +++ b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared_session/testresult.jinja2 @@ -1,8 +1,8 @@ {%- if result %}
- + -
{{ html_escape(result) }}
+
{{ result | e }}
{%- else %}
diff --git a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/session_single.jinja2 b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/session_single.jinja2 index 97d288a..0c39711 100644 --- a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/session_single.jinja2 +++ b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/session_single.jinja2 @@ -2,11 +2,11 @@ {% include "partials/shared/head.jinja2" %} - {{ transcript.plan.name }} | Feditest + {{ transcript.plan.name | e }} | Feditest
-

Feditest Report: {{ transcript.plan.name }}

+

Feditest Report: {{ transcript.plan.name | e }}

{{ transcript.id }}

{% include "partials/shared/mobile.jinja2" %} diff --git a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/session_with_matrix.jinja2 b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/session_with_matrix.jinja2 index 093ce30..801d9d6 100644 --- a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/session_with_matrix.jinja2 +++ b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/session_with_matrix.jinja2 @@ -2,11 +2,11 @@ {% include "partials/shared/head.jinja2" %} - {{ run_session.name }} | Feditest + {{ run_session.name | e }} | Feditest
-

Feditest Session Report: {{ run_session }}

+

Feditest Session Report: {{ run_session | e }}

{{ transcript.id }} [Summary]

From 681b6b3b2c0390ea465022af1bf5f3743c0be51e Mon Sep 17 00:00:00 2001 From: Johannes Ernst Date: Mon, 16 Dec 2024 15:49:51 -0800 Subject: [PATCH 06/10] Set jinja2 autoescape. Closes #424 (#429) Co-authored-by: Johannes Ernst --- src/feditest/testruntranscriptserializer/html.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/feditest/testruntranscriptserializer/html.py b/src/feditest/testruntranscriptserializer/html.py index 16a2e06..44ff20b 100644 --- a/src/feditest/testruntranscriptserializer/html.py +++ b/src/feditest/testruntranscriptserializer/html.py @@ -61,7 +61,8 @@ def __init__(self, template_path: str): self.template_path = [ os.path.join(os.path.dirname(__file__), "templates/testplantranscript_default") ] self.jinja2_env = jinja2.Environment( - loader=jinja2.FileSystemLoader(self.template_path) + loader=jinja2.FileSystemLoader(self.template_path), + autoescape=jinja2.select_autoescape() ) self.jinja2_env.filters["regex_sub"] = lambda s, pattern, replacement: re.sub( pattern, replacement, s From 8c7d762ef104a448e60e0b49264ed7136da93092 Mon Sep 17 00:00:00 2001 From: Johannes Ernst Date: Mon, 16 Dec 2024 12:15:04 -0800 Subject: [PATCH 07/10] Sanitize app name and app version read from user --- src/feditest/utils.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/feditest/utils.py b/src/feditest/utils.py index 3725d63..d5c9371 100644 --- a/src/feditest/utils.py +++ b/src/feditest/utils.py @@ -445,7 +445,13 @@ def appname_validate(candidate: str) -> str | None: Validate that the provided string is a valid application name. return: string if valid, None otherwise """ - return candidate if len(candidate) > 0 else None + if len(candidate) > 255: + return None + if len(candidate) == 0: + return None + if any(c in candidate for c in ['<', '>', '&'] ): + return None + return candidate def appversion_validate(candidate: str) -> str | None: @@ -453,7 +459,13 @@ def appversion_validate(candidate: str) -> str | None: Validate that the provided string is a valid application version. return: string if value, None otherwise """ - return candidate if len(candidate) > 0 else None + if len(candidate) > 255: + return None + if len(candidate) == 0: + return None + if any(c in candidate for c in ['<', '>', '&'] ): + return None + return candidate def boolean_response_parse_validate(candidate:str) -> bool | None: From 31c87d613efca7a7aca42ff800deee4fed236abd Mon Sep 17 00:00:00 2001 From: Johannes Ernst Date: Mon, 16 Dec 2024 15:42:32 -0800 Subject: [PATCH 08/10] Add validation methods to data structures read from JSON or instantiated locally --- src/feditest/cli/utils.py | 1 + src/feditest/testplan.py | 96 ++++++++++++++++++- src/feditest/utils.py | 13 +++ tests.unit/test_30_create_testplan.py | 1 + ...llback_fediverse_accounts_from_testplan.py | 5 +- .../test_40_report_node_driver_errors.py | 1 + ...40_ubos_mastodon_accounts_from_testplan.py | 5 +- tests.unit/test_50_run_assertion_raises.py | 3 +- tests.unit/test_50_run_exceptions.py | 1 + .../test_50_run_multistep_assertion_raises.py | 1 + tests.unit/test_50_run_not_implemented.py | 1 + tests.unit/test_50_run_passes.py | 1 + tests.unit/test_50_run_skip.py | 1 + tests.unit/test_50_run_testplan_sandbox.py | 1 + 14 files changed, 124 insertions(+), 7 deletions(-) diff --git a/src/feditest/cli/utils.py b/src/feditest/cli/utils.py index 5bba831..8c57276 100644 --- a/src/feditest/cli/utils.py +++ b/src/feditest/cli/utils.py @@ -29,6 +29,7 @@ def create_plan_from_session_and_constellations(args: Namespace) -> TestPlan | N constellations = create_constellations(args) plan = TestPlan(session, constellations, args.name) + plan.properties_validate() plan.simplify() return plan diff --git a/src/feditest/testplan.py b/src/feditest/testplan.py index e7c0d54..76f9f5c 100644 --- a/src/feditest/testplan.py +++ b/src/feditest/testplan.py @@ -10,8 +10,7 @@ import msgspec import feditest -from feditest.utils import hostname_validate, FEDITEST_VERSION - +from feditest.utils import hostname_validate, symbolic_name_validate, FEDITEST_VERSION class InvalidAccountSpecificationException(Exception): """ @@ -143,6 +142,43 @@ class TestPlanConstellationNode(msgspec.Struct): non_existing_accounts: list[dict[str, str | None]] | None = None + def properties_validate(self) -> None: + """ + Validate properties for correctness and safety. + """ + # Unclear whether we can validate the values of the dicts; currently not done. + + if self.parameters: + for par_name, _ in self.parameters.items(): + if not par_name: + raise ValueError('Invalid TestPlanConstellationNode parameter name: cannot be empty') + + if not symbolic_name_validate(par_name): + raise ValueError(f'Invalid TestPlanConstellationNode parameter name: { par_name }') + + if self.accounts: + for account in self.accounts: + if 'role' not in account: + raise ValueError('No role name given in account') + + if not account['role']: + raise ValueError('Invalid TestPlanConstellationNode account role name: cannot be empty') + + if not symbolic_name_validate(account['role']): + raise ValueError(f'Invalid role name in account: "{ account["role" ]}') + + if self.non_existing_accounts: + for non_existing_account in self.non_existing_accounts: + if 'role' not in non_existing_account: + raise ValueError('No role name given in non_existing_account') + + if not non_existing_account['role']: + raise ValueError('Invalid TestPlanConstellationNode non_existing_account role name: cannot be empty') + + if not symbolic_name_validate(non_existing_account['role']): + raise ValueError(f'Invalid role name in non_existing_account: "{ non_existing_account["role" ]}') + + @staticmethod def load(filename: str) -> 'TestPlanConstellationNode': """ @@ -224,6 +260,18 @@ class TestPlanConstellation(msgspec.Struct): roles : dict[str,TestPlanConstellationNode | None] # can be None if used as template name: str | None = None + def properties_validate(self) -> None: + """ + Validate properties for correctness and safety. + """ + for role_name, role_value in self.roles.items(): + if not symbolic_name_validate(role_name): + raise ValueError(f'Invalid TestPlanConstellation role name: { role_name }') + if role_value: + role_value.properties_validate() + + # Not checking self.name: can be any human-readable name + @staticmethod def load(filename: str) -> 'TestPlanConstellation': @@ -288,6 +336,21 @@ class TestPlanTestSpec(msgspec.Struct): skip: str | None = None # if a string is given, it's a reason message why this test spec should be skipped + def properties_validate(self) -> None: + """ + Validate properties for correctness and safety. + """ + # Not checking self.name: can be any human-readable name + + if self.rolemapping: + for role_mapping_key, role_mapping_value in self.rolemapping.items(): + if not symbolic_name_validate(role_mapping_key): + raise ValueError(f'Invalid TestPlanTestSpec role mapping key: { role_mapping_key }') + + if not symbolic_name_validate(role_mapping_value): + raise ValueError(f'Invalid TestPlanTestSpec role mapping value: { role_mapping_value }') + + def __str__(self): return self.name @@ -348,6 +411,16 @@ class TestPlanSessionTemplate(msgspec.Struct): name: str | None = None + def properties_validate(self) -> None: + """ + Validate properties for correctness and safety. + """ + for test in self.tests: + test.properties_validate() + + # Not checking self.name: can be any human-readable name + + @staticmethod def load(filename: str) -> 'TestPlanSessionTemplate': """ @@ -412,6 +485,25 @@ class TestPlan(msgspec.Struct): feditest_version: str = FEDITEST_VERSION + def properties_validate(self) -> None: + """ + Validate properties for correctness and safety. + """ + if self.session_template is None: + raise ValueError('No session_template in TestPlan') + self.session_template.properties_validate() + + for constellation in self.constellations: + constellation.properties_validate() + + # Not checking self.name: can be any human-readable name + if self.type != 'feditest-testplan': + raise ValueError(f'TestPlan type is not feditest-testplan: "{ self.type }".') + + if not symbolic_name_validate(self.feditest_version): + raise ValueError(f'Invalid TestPlan FediTest version: "{ self.feditest_version }".') + + def simplify(self) -> None: """ If possible, simplify this test plan. diff --git a/src/feditest/utils.py b/src/feditest/utils.py index d5c9371..7d3c89e 100644 --- a/src/feditest/utils.py +++ b/src/feditest/utils.py @@ -488,6 +488,19 @@ def boolean_response_parse_validate(candidate:str) -> bool | None: return None +def symbolic_name_validate(candidate_name: str) -> str | None: + """ + Validate a symbolic name, as used, for example, as the name of a role in a TestPlan. + """ + if not candidate_name: + return None + if len(candidate_name) > 255: + return None + if re.fullmatch('[-_a-z-A-Z0-9.]+', candidate_name): + return candidate_name + return None + + def find_first_in_array(array: List[T], condition: Callable[[T], bool]) -> T | None: """ IMHO this should be a python built-in function. The next() workaround confuses me more than I like. diff --git a/tests.unit/test_30_create_testplan.py b/tests.unit/test_30_create_testplan.py index 1c39ba3..c0c0dd6 100644 --- a/tests.unit/test_30_create_testplan.py +++ b/tests.unit/test_30_create_testplan.py @@ -59,6 +59,7 @@ def construct_testplan(constellations: list[TestPlanConstellation], session_temp Helper to put it together. """ test_plan = TestPlan(session_template, constellations, testplan_name) + test_plan.properties_validate() test_plan.simplify() return test_plan diff --git a/tests.unit/test_40_fallback_fediverse_accounts_from_testplan.py b/tests.unit/test_40_fallback_fediverse_accounts_from_testplan.py index c5a35bf..b98caf9 100644 --- a/tests.unit/test_40_fallback_fediverse_accounts_from_testplan.py +++ b/tests.unit/test_40_fallback_fediverse_accounts_from_testplan.py @@ -56,9 +56,10 @@ def test_plan_fixture() -> TestPlan: } ] node1 = TestPlanConstellationNode(node_driver, parameters, plan_accounts, plan_non_existing_accounts) - constellation = TestPlanConstellation( { NODE1_ROLE : node1 }) + constellation = TestPlanConstellation({ NODE1_ROLE : node1 }) session_template = TestPlanSessionTemplate([]) - ret = TestPlan( session_template, [ constellation ] ) + ret = TestPlan(session_template, [ constellation ]) + ret.properties_validate() return ret diff --git a/tests.unit/test_40_report_node_driver_errors.py b/tests.unit/test_40_report_node_driver_errors.py index e668505..fa968fe 100644 --- a/tests.unit/test_40_report_node_driver_errors.py +++ b/tests.unit/test_40_report_node_driver_errors.py @@ -77,6 +77,7 @@ def test_faulty_node_driver_reporting() -> None: }), ] ) + plan.properties_validate() run = TestRun(plan) controller = AutomaticTestRunController(run) diff --git a/tests.unit/test_40_ubos_mastodon_accounts_from_testplan.py b/tests.unit/test_40_ubos_mastodon_accounts_from_testplan.py index e8c91aa..66da2f0 100644 --- a/tests.unit/test_40_ubos_mastodon_accounts_from_testplan.py +++ b/tests.unit/test_40_ubos_mastodon_accounts_from_testplan.py @@ -65,9 +65,10 @@ def the_test_plan() -> TestPlan: } ] node1 = TestPlanConstellationNode(node_driver, parameters, plan_accounts, plan_non_existing_accounts) - constellation = TestPlanConstellation( { NODE1_ROLE : node1 }) + constellation = TestPlanConstellation({ NODE1_ROLE : node1 }) session = TestPlanSessionTemplate([]) - ret = TestPlan( session, [ constellation ] ) + ret = TestPlan(session, [ constellation ]) + ret.properties_validate() return ret diff --git a/tests.unit/test_50_run_assertion_raises.py b/tests.unit/test_50_run_assertion_raises.py index 4d957e4..110f020 100644 --- a/tests.unit/test_50_run_assertion_raises.py +++ b/tests.unit/test_50_run_assertion_raises.py @@ -179,7 +179,8 @@ def the_test_plan() -> TestPlan: constellation = TestPlanConstellation({}, 'No nodes needed') tests = [ TestPlanTestSpec(name) for name in sorted(feditest.all_tests.keys()) if feditest.all_tests.get(name) is not None ] session = TestPlanSessionTemplate(tests, "Test tests that raise various AssertionFailures") - ret = TestPlan( session, [ constellation ] ) + ret = TestPlan(session, [ constellation ]) + ret.properties_validate() return ret diff --git a/tests.unit/test_50_run_exceptions.py b/tests.unit/test_50_run_exceptions.py index 0ae0484..2abce7c 100644 --- a/tests.unit/test_50_run_exceptions.py +++ b/tests.unit/test_50_run_exceptions.py @@ -86,6 +86,7 @@ def test_plan_fixture() -> TestPlan: tests = [ TestPlanTestSpec(name) for name in sorted(feditest.all_tests.keys()) if feditest.all_tests.get(name) is not None ] session = TestPlanSessionTemplate(tests, "Tests buggy tests") ret = TestPlan(session, [ constellation ]) + ret.properties_validate() return ret diff --git a/tests.unit/test_50_run_multistep_assertion_raises.py b/tests.unit/test_50_run_multistep_assertion_raises.py index ababba8..160f68b 100644 --- a/tests.unit/test_50_run_multistep_assertion_raises.py +++ b/tests.unit/test_50_run_multistep_assertion_raises.py @@ -181,6 +181,7 @@ def test_plan_fixture() -> TestPlan: tests = [ TestPlanTestSpec(name) for name in sorted(feditest.all_tests.keys()) if feditest.all_tests.get(name) is not None ] session = TestPlanSessionTemplate(tests, "Test a test whose steps raises multiple AssertionFailures") ret = TestPlan(session, [ constellation ]) + ret.properties_validate() return ret diff --git a/tests.unit/test_50_run_not_implemented.py b/tests.unit/test_50_run_not_implemented.py index 02de338..f2fdc4b 100644 --- a/tests.unit/test_50_run_not_implemented.py +++ b/tests.unit/test_50_run_not_implemented.py @@ -78,6 +78,7 @@ def test_plan_fixture() -> TestPlan: tests = [ TestPlanTestSpec(name) for name in sorted(feditest.all_tests.keys()) if feditest.all_tests.get(name) is not None ] session = TestPlanSessionTemplate(tests, "Test tests that throw NotImplemented errors") ret = TestPlan(session, [ constellation ]) + ret.properties_validate() return ret diff --git a/tests.unit/test_50_run_passes.py b/tests.unit/test_50_run_passes.py index e61b171..fa6d153 100644 --- a/tests.unit/test_50_run_passes.py +++ b/tests.unit/test_50_run_passes.py @@ -59,6 +59,7 @@ def test_plan_fixture() -> TestPlan: tests = [ TestPlanTestSpec(name) for name in sorted(feditest.all_tests.keys()) if feditest.all_tests.get(name) is not None ] session = TestPlanSessionTemplate(tests, "Test a test that passes") ret = TestPlan(session, [ constellation ]) + ret.properties_validate() return ret diff --git a/tests.unit/test_50_run_skip.py b/tests.unit/test_50_run_skip.py index 9f49f33..12c74c8 100644 --- a/tests.unit/test_50_run_skip.py +++ b/tests.unit/test_50_run_skip.py @@ -61,6 +61,7 @@ def the_test_plan() -> TestPlan: tests = [ TestPlanTestSpec(name) for name in sorted(feditest.all_tests.keys()) if feditest.all_tests.get(name) is not None ] session = TestPlanSessionTemplate(tests, "Test a test that wants to be skipped") ret = TestPlan(session, [ constellation ]) + ret.properties_validate() return ret diff --git a/tests.unit/test_50_run_testplan_sandbox.py b/tests.unit/test_50_run_testplan_sandbox.py index 22683d0..6908790 100644 --- a/tests.unit/test_50_run_testplan_sandbox.py +++ b/tests.unit/test_50_run_testplan_sandbox.py @@ -161,6 +161,7 @@ def test_plan_fixture() -> TestPlan: tests = [ TestPlanTestSpec(name) for name in sorted(feditest.all_tests.keys()) if feditest.all_tests.get(name) is not None ] session = TestPlanSessionTemplate(tests, "clientA vs server") ret = TestPlan(session, [ constellation ], "All sandbox tests running clientA against server1") + ret.properties_validate() return ret From 6f38b1dc8e3c96f7ae2067ebc07435cf5ef84381 Mon Sep 17 00:00:00 2001 From: Johannes Ernst Date: Mon, 16 Dec 2024 15:45:24 -0800 Subject: [PATCH 09/10] Better URLs to UBOS Gears while I see it. --- src/feditest/nodedrivers/ubos.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/feditest/nodedrivers/ubos.py b/src/feditest/nodedrivers/ubos.py index aa2dd96..f84c745 100644 --- a/src/feditest/nodedrivers/ubos.py +++ b/src/feditest/nodedrivers/ubos.py @@ -1,5 +1,5 @@ """ -Nodes managed via UBOS Gears https://ubos.net/ +Nodes managed via UBOS Gears https://ubos.net/docs/gears/ """ from abc import abstractmethod import hashlib @@ -440,10 +440,10 @@ def _provision_node(self, rolename: str, config: NodeConfiguration, account_mana if config._rshcmd: if self._exec_shell('which ubos-admin', config._rshcmd).returncode: - raise OSError(f'{ type(self).__name__ } with an rshcmd requires UBOS Gears on the remote system (see ubos.net).') + raise OSError(f'{ type(self).__name__ } with an rshcmd requires UBOS Gears on the remote system (see https://feditest.org/glossary/ubosgears/).') else: if not shutil.which('ubos-admin'): - raise OSError(f'{ type(self).__name__ } without an rshcmd requires a local system running UBOS Gears (see ubos.net).') + raise OSError(f'{ type(self).__name__ } without an rshcmd requires a local system running UBOS Gears (see https://feditest.org/glossary/ubosgears/).') if account_manager is None: raise RuntimeError(f'No AccountManager set for rolename { rolename } with UbosNodeDriver { self }') From 58cd5abc2f9e031d8c7467d4042151566779edb4 Mon Sep 17 00:00:00 2001 From: Johannes Ernst Date: Mon, 16 Dec 2024 15:45:59 -0800 Subject: [PATCH 10/10] Sanitize HTML output better --- .../testruntranscriptserializer/html.py | 5 ++--- .../testplantranscript_default/matrix.jinja2 | 4 ++-- .../partials/matrix/matrix.jinja2 | 10 +++++----- .../partials/matrix/metadata.jinja2 | 6 +++--- .../partials/matrix/testresult.jinja2 | 2 +- .../partials/shared/summary.jinja2 | 2 +- .../partials/shared_session/metadata.jinja2 | 2 +- .../partials/shared_session/results.jinja2 | 18 +++++++++--------- .../partials/shared_session/testresult.jinja2 | 4 ++-- .../session_single.jinja2 | 4 ++-- .../session_with_matrix.jinja2 | 4 ++-- 11 files changed, 30 insertions(+), 31 deletions(-) diff --git a/src/feditest/testruntranscriptserializer/html.py b/src/feditest/testruntranscriptserializer/html.py index 44ff20b..c853b94 100644 --- a/src/feditest/testruntranscriptserializer/html.py +++ b/src/feditest/testruntranscriptserializer/html.py @@ -89,11 +89,10 @@ def write(self, transcript: TestRunTranscript, dest: str | None): permit_line_breaks_in_identifier=lambda s: re.sub( r"(\.|::)", r"\1", s ), - local_name_with_tooltip=lambda n: f'{ n.split(".")[-1] }', + local_name_with_tooltip=lambda n: f'{ n.split(".")[-1] }', format_timestamp=lambda ts: ts.strftime("%Y:%m:%d-%H:%M:%S.%fZ") if ts else "", format_duration=lambda s: str(s), # makes it easier to change in the future - len=len, - html_escape=lambda s: html.escape(str(s)) + len=len ) try: diff --git a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/matrix.jinja2 b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/matrix.jinja2 index ad6de61..32714d0 100644 --- a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/matrix.jinja2 +++ b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/matrix.jinja2 @@ -2,11 +2,11 @@ {% include "partials/shared/head.jinja2" %} - {{ transcript.plan.name }} | Feditest + {{ transcript.plan.name | e }} | Feditest
-

Feditest Summary Report: {{ transcript.plan.name }}

+

Feditest Summary Report: {{ transcript.plan.name | e }}

{{ transcript.id }}

{% include "partials/shared/mobile.jinja2" %} diff --git a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/matrix/matrix.jinja2 b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/matrix/matrix.jinja2 index fbbd276..1890414 100644 --- a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/matrix/matrix.jinja2 +++ b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/matrix/matrix.jinja2 @@ -1,6 +1,6 @@ {% macro column_headings(first) %}
- + {%- for run_session in transcript.sessions %} {%- set constellation = run_session.constellation %} {%- for session_index, run_session in enumerate(transcript.sessions) %} diff --git a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/matrix/metadata.jinja2 b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/matrix/metadata.jinja2 index fd76e83..c65f825 100644 --- a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/matrix/metadata.jinja2 +++ b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/matrix/metadata.jinja2 @@ -15,14 +15,14 @@ {%- for key in ['username', 'hostname', 'platform'] -%} {%- if getattr(transcript,key) %} - - + + {%- endif %} {%- endfor %} - +
{{ key }}{{ value }}{{ key | e }}{{ value | e }}
{{ first }}{{ first | e }} @@ -8,8 +8,8 @@
{%- for role, node in constellation.nodes.items() %} -
{{ role }}
-
{{ node.node_driver }}
+
{{ role | e }}
+
{{ node.node_driver | e }}
{%- endfor %}
@@ -34,9 +34,9 @@ {%- for test_index, ( _, test_meta ) in enumerate(sorted(transcript.test_meta.items())) %}
- {{ permit_line_breaks_in_identifier(test_meta.name) }} + {{ permit_line_breaks_in_identifier(test_meta.name) | e }} {%- if test_meta.description %} - {{ test_meta.description }} + {{ test_meta.description | e }} {%- endif %}
{{ key.capitalize() }}{{ getattr(transcript, key) }}{{ key.capitalize() | e }}{{ getattr(transcript, key) | e }}
Feditest version{{ getattr(transcript, 'feditest_version') }}{{ getattr(transcript, 'feditest_version') | e }}
diff --git a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/matrix/testresult.jinja2 b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/matrix/testresult.jinja2 index 00e1c1c..b557c70 100644 --- a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/matrix/testresult.jinja2 +++ b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/matrix/testresult.jinja2 @@ -1,6 +1,6 @@ {%- if result %} -
{{ result.short_title() }}
+
{{ result.short_title() | e }}
{%- else %} diff --git a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared/summary.jinja2 b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared/summary.jinja2 index e80ef85..dd23375 100644 --- a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared/summary.jinja2 +++ b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared/summary.jinja2 @@ -54,7 +54,7 @@ {%- for spec_level in [ feditest.SpecLevel.SHOULD, feditest.SpecLevel.IMPLIED, feditest.SpecLevel.UNSPECIFIED ] %} - {{ spec_level.formatted_name }} + {{ spec_level.formatted_name | e }} {%- for interop_level in feditest.InteropLevel %}
diff --git a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared_session/metadata.jinja2 b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared_session/metadata.jinja2 index 299284a..887fcd8 100644 --- a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared_session/metadata.jinja2 +++ b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared_session/metadata.jinja2 @@ -16,7 +16,7 @@ {%- if getattr(transcript,key) %} {{ key.capitalize() }} - {{ getattr(transcript, key) }} + {{ getattr(transcript, key) | e }} {%- endif %} {%- endfor %} diff --git a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared_session/results.jinja2 b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared_session/results.jinja2 index e5ff58a..5e14d39 100644 --- a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared_session/results.jinja2 +++ b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared_session/results.jinja2 @@ -2,10 +2,10 @@
{%- for role_name, node in run_session.constellation.nodes.items() %}
-

{{ role_name }}

+

{{ role_name | e }}

{{ local_name_with_tooltip(node.node_driver) }}
-
{{ node.appdata['app'] }}
-
{{ node.appdata['app_version'] or '?'}}
+
{{ node.appdata['app'] | e }}
+
{{ node.appdata['app_version'] or '?' | e }}
{%- if node.parameters %} @@ -16,8 +16,8 @@ {%- for key, value in node.parameters.items() %} - - + + {%- endfor %} @@ -33,9 +33,9 @@ {%- set plan_test_spec = transcript.plan.session_template.tests[run_test.plan_test_index] %} {%- set test_meta = transcript.test_meta[plan_test_spec.name] %}
-

Test: {{ test_meta.name }}

+

Test: {{ test_meta.name | e }}

{%- if test_meta.description %} -
{{ test_meta.description}}
+
{{ test_meta.description | e }}
{%- endif %}

Started {{ format_timestamp(run_test.started) }}, ended {{ format_timestamp(run_test.ended) }} (duration: {{ format_duration(run_test.ended - run_test.started) }})

{%- with result=run_test.worst_result %} @@ -44,9 +44,9 @@ {%- for test_step_index, run_step in enumerate(run_test.run_steps or []) %}
{% set test_step_meta = test_meta.steps[run_step.plan_step_index] %} -
Test step: {{ test_step_meta.name }}
+
Test step: {{ test_step_meta.name | e }}
{%- if test_step_meta.description %} -
{{ test_step_meta.description}}
+
{{ test_step_meta.description | e }}
{%- endif %}

Started {{ format_timestamp(run_test.started) }}, ended {{ format_timestamp(run_test.ended) }} (duration: {{ format_duration(run_test.ended - run_test.started) }})

{%- with result=run_step.result, idmod='step' %} diff --git a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared_session/testresult.jinja2 b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared_session/testresult.jinja2 index 0272b5e..0fcbd54 100644 --- a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared_session/testresult.jinja2 +++ b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/partials/shared_session/testresult.jinja2 @@ -1,8 +1,8 @@ {%- if result %}
- + -
{{ html_escape(result) }}
+
{{ result | e }}
{%- else %}
diff --git a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/session_single.jinja2 b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/session_single.jinja2 index 97d288a..0c39711 100644 --- a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/session_single.jinja2 +++ b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/session_single.jinja2 @@ -2,11 +2,11 @@ {% include "partials/shared/head.jinja2" %} - {{ transcript.plan.name }} | Feditest + {{ transcript.plan.name | e }} | Feditest
-

Feditest Report: {{ transcript.plan.name }}

+

Feditest Report: {{ transcript.plan.name | e }}

{{ transcript.id }}

{% include "partials/shared/mobile.jinja2" %} diff --git a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/session_with_matrix.jinja2 b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/session_with_matrix.jinja2 index 093ce30..801d9d6 100644 --- a/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/session_with_matrix.jinja2 +++ b/src/feditest/testruntranscriptserializer/templates/testplantranscript_default/session_with_matrix.jinja2 @@ -2,11 +2,11 @@ {% include "partials/shared/head.jinja2" %} - {{ run_session.name }} | Feditest + {{ run_session.name | e }} | Feditest
-

Feditest Session Report: {{ run_session }}

+

Feditest Session Report: {{ run_session | e }}

{{ transcript.id }} [Summary]

{{ key }}{{ value }}{{ key | e }}{{ value | e }}