Skip to content

Commit

Permalink
Fix generated search URLs for project statistics
Browse files Browse the repository at this point in the history
Search URLs generated for the project statistic page were caught by
Jinja2 autoescaping, which makes them invalid.

A good place to mark them as safe is the view object: It's the immediate
layer below Jinja templates and specifically made to provide strings
included by a Jinja2 template.

Relates to #1920.
  • Loading branch information
haxtibal committed Oct 19, 2024
1 parent a33209b commit daa243f
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ def render_standalone_document_link(
else "/".join((root_prefix, document_link))
)

def render_static_url(self, url: str) -> str:
def render_static_url(self, url: str) -> Markup:
return Markup(self.link_renderer.render_static_url(url))

def render_local_anchor(self, node) -> str:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from dataclasses import dataclass
from datetime import datetime

from markupsafe import Markup

from strictdoc import __version__
from strictdoc.core.document_tree_iterator import DocumentTreeIterator
from strictdoc.core.project_config import ProjectConfig
Expand Down Expand Up @@ -34,19 +36,19 @@ def __init__(
self.is_running_on_server: bool = project_config.is_running_on_server
self.strictdoc_version = __version__

def render_screen(self, jinja_environment: JinjaEnvironment):
def render_screen(self, jinja_environment: JinjaEnvironment) -> Markup:
return jinja_environment.render_template_as_markup(
"screens/project_statistics/index.jinja", view_object=self
)

def render_static_url(self, url: str):
return self.link_renderer.render_static_url(url)
def render_static_url(self, url: str) -> Markup:
return Markup(self.link_renderer.render_static_url(url))

def render_url(self, url: str):
return self.link_renderer.render_url(url)
def render_url(self, url: str) -> Markup:
return Markup(self.link_renderer.render_url(url))

def render_static_url_with_prefix(self, url: str):
return self.link_renderer.render_static_url_with_prefix(url)
def render_static_url_with_prefix(self, url: str) -> Markup:
return Markup(self.link_renderer.render_static_url_with_prefix(url))

def is_empty_tree(self) -> bool:
return self.document_tree_iterator.is_empty_tree()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
<div class="sdoc-table_key_value-section">{{ obj["Section"] }}</div>
{% else %}
{% if view_object.project_config.is_activated_search() and obj["Link"] is defined %}
<a class="sdoc-table_key_value-key" href="{{ obj["Link"] }}">{{ obj["Key"] }}</a>
<a
class="sdoc-table_key_value-key"
data-testid="search-{{ obj["Key"].lower().replace(" ", "-") }}"
href="{{ obj["Link"] }}">{{ obj["Key"] }}
</a>
{% else %}
<div class="sdoc-table_key_value-key">{{ obj["Key"] }}</div>
{% endif %}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
from selenium.webdriver.common.by import By
from seleniumbase import BaseCase

from tests.end2end.helpers.screens.search.search import Screen_SearchResults


class Screen_ProjectStatistics: # pylint: disable=invalid-name
def __init__(self, test_case: BaseCase) -> None:
assert isinstance(test_case, BaseCase)
self.test_case: BaseCase = test_case

def do_click_on_search_link(self, test_id: str) -> Screen_SearchResults:
self.test_case.click_xpath(
f'//a[@data-testid="{test_id}"]',
)
return Screen_SearchResults(self.test_case)

def assert_on_screen(self) -> None:
self.test_case.assert_element(
'//body[@data-viewtype="project-statistics"]',
Expand Down
13 changes: 13 additions & 0 deletions tests/end2end/helpers/screens/search/search.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from selenium.webdriver.common.by import By

from tests.end2end.helpers.screens.screen import Screen


Expand All @@ -7,6 +9,17 @@ def do_click_on_search_requirements(self):
'//a[@data-testid="node.is_requirement"]',
)

def assert_nr_results(self, nr_results: int):
content = (
f"Found {nr_results} results."
if nr_results > 0
else "Nothing matching the query was found."
)
self.test_case.assert_element(
"//div[@class='sdoc-form-success']" f"[contains(., '{content}')]",
by=By.XPATH,
)


class Screen_Search(Screen): # pylint: disable=invalid-name
def do_click_on_search_requirements(self) -> Screen_SearchResults:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[DOCUMENT]
TITLE: Test document

[SECTION]
TITLE: Section title

[REQUIREMENT]
UID: REQ-1
STATUS: Active
TITLE: Requirement title
STATEMENT: Requirement statement.
RATIONALE: Rationale. TBD

[REQUIREMENT]
UID: REQ-2
STATUS: Draft
TITLE: Requirement title
STATEMENT: Requirement statement.

[REQUIREMENT]
UID: REQ-3
STATUS: Other
TITLE: Requirement title
STATEMENT: Requirement statement.

[REQUIREMENT]
STATUS: Backlog
TITLE: Requirement title
STATEMENT: Requirement statement. TBD

[REQUIREMENT]
TITLE: Requirement title
STATEMENT: Requirement statement. TBC

[/SECTION]
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[project]

features = [
"PROJECT_STATISTICS_SCREEN"
"PROJECT_STATISTICS_SCREEN",
"SEARCH"
]
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,36 @@
from tests.end2end.helpers.screens.project_statistics.project_statistics import (
Screen_ProjectStatistics,
)
from tests.end2end.helpers.screens.search.search import Screen_SearchResults
from tests.end2end.server import SDocTestServer

path_to_this_test_file_folder = os.path.dirname(os.path.abspath(__file__))


class Test(E2ECase):
expected_search_results = [
("search-total-sections", 1),
("search-sections-without-any-text", 1),
("search-total-requirements", 5),
("search-requirements-with-no-uid", 2),
(
"search-root-level-requirements-not-connected-to-by-any-requirement",
0,
),
(
"search-non-root-level-requirements-not-connected-to-any-parent-requirement",
4,
),
("search-requirements-with-no-rationale", 4),
("search-requirements-with-no-status", 1),
("search-requirements-with-status-active", 1),
("search-requirements-with-status-draft", 1),
("search-requirements-with-status-backlog", 1),
("search-requirements-with-all-other-statuses", 1),
("search-total-tbd", 2),
("search-total-tbc", 1),
]

def test(self):
with SDocTestServer(
input_path=path_to_this_test_file_folder
Expand All @@ -27,3 +51,12 @@ def test(self):
screen_project_index.do_click_on_project_statistics_link()
)
screen_requirements_coverage.assert_on_screen()

for test_id, nr_results in self.expected_search_results:
screen_search_results: Screen_SearchResults = (
screen_requirements_coverage.do_click_on_search_link(
test_id
)
)
screen_search_results.assert_nr_results(nr_results)
self.go_back()

0 comments on commit daa243f

Please sign in to comment.