255 lines
9 KiB
Python
255 lines
9 KiB
Python
# Copyright (C) 2010-2024 RhodeCode GmbH
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero General Public License, version 3
|
|
# (only), as published by the Free Software Foundation.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
# This program is dual-licensed. If you wish to learn more about the
|
|
# RhodeCode Enterprise Edition, including its added features, Support services,
|
|
# and proprietary license terms, please see https://rhodecode.com/licenses/
|
|
|
|
import copy
|
|
import mock
|
|
import pytest
|
|
|
|
from rhodecode.lib import helpers
|
|
from rhodecode.lib.utils2 import AttributeDict
|
|
from rhodecode.model.settings import IssueTrackerSettingsModel
|
|
from rhodecode.tests import no_newline_id_generator
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"url, expected_url",
|
|
[
|
|
(r"https://rc.com", '<a href="https://rc.com">https://rc.com</a>'),
|
|
(r"https://rc.com/test", '<a href="https://rc.com/test">https://rc.com/test</a>'),
|
|
(r"https://rc.com/!foo", '<a href="https://rc.com/!foo">https://rc.com/!foo</a>'),
|
|
(r"https://rc.com/&foo", '<a href="https://rc.com/&foo">https://rc.com/&foo</a>'),
|
|
(
|
|
r"https://rc.com/?foo-1&bar=1",
|
|
'<a href="https://rc.com/?foo-1&bar=1">https://rc.com/?foo-1&bar=1</a>',
|
|
),
|
|
(r"https://rc.com?foo-1&bar=1", '<a href="https://rc.com?foo-1&bar=1">https://rc.com?foo-1&bar=1</a>'),
|
|
(r"https://rc.com/#foo", '<a href="https://rc.com/#foo">https://rc.com/#foo</a>'),
|
|
(r"https://rc.com/@foo", '<a href="https://rc.com/@foo">https://rc.com/@foo</a>'),
|
|
],
|
|
)
|
|
def test_urlify_text(url, expected_url):
|
|
assert helpers.urlify_text(url) == expected_url
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"repo_name, commit_id, path, expected_result",
|
|
[
|
|
# Simple case 1
|
|
(
|
|
"repo",
|
|
"commit",
|
|
"a/b",
|
|
'<a href="/repo/files/commit/"><i class="icon-home"></i></a> / <a href="/repo/files/commit/a">a</a> / b',
|
|
),
|
|
# Simple case
|
|
(
|
|
"rX<X",
|
|
"cX<X",
|
|
"pX<X/aX<X/bX<X",
|
|
'<a href="/rX%3CX/files/cX%3CX/"><i class="icon-home"></i></a>'
|
|
" / "
|
|
'<a href="/rX%3CX/files/cX%3CX/pX%3CX">pX<X</a>'
|
|
" / "
|
|
'<a href="/rX%3CX/files/cX%3CX/pX%3CX/aX%3CX">aX<X</a>'
|
|
" / "
|
|
"bX<X",
|
|
),
|
|
# Path with only one segment
|
|
("rX<X", "cX<X", "pX<X", '<a href="/rX%3CX/files/cX%3CX/"><i class="icon-home"></i></a> / pX<X'),
|
|
# Empty path
|
|
("rX<X", "cX<X", "", '<i class="icon-home"></i>'),
|
|
# simple quote
|
|
(
|
|
'rX"X',
|
|
'cX"X',
|
|
'pX"X/aX"X/bX"X',
|
|
'<a href="/rX%22X/files/cX%22X/"><i class="icon-home"></i></a>'
|
|
" / "
|
|
'<a href="/rX%22X/files/cX%22X/pX%22X">pX"X</a>'
|
|
" / "
|
|
'<a href="/rX%22X/files/cX%22X/pX%22X/aX%22X">aX"X</a>'
|
|
" / "
|
|
"bX"X",
|
|
),
|
|
],
|
|
ids=["simple1", "simple2", "one_segment", "empty_path", "simple_quote"],
|
|
)
|
|
def test_files_breadcrumbs_xss(repo_name, commit_id, path, app, expected_result):
|
|
result = helpers.files_breadcrumbs(repo_name, "hg", commit_id, path)
|
|
# Expect it to encode all path fragments properly. This is important
|
|
# because it returns an instance of `literal`.
|
|
if path != "":
|
|
expected_result = expected_result + helpers.files_icon.format(helpers.escape(path))
|
|
assert result == expected_result
|
|
|
|
|
|
def test_format_binary():
|
|
assert helpers.format_byte_size_binary(298489462784) == "278.0 GiB"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"text_string, pattern, expected",
|
|
[
|
|
("No issue here", r"(?:#)(?P<issue_id>\d+)", []),
|
|
("Fix #42", r"(?:#)(?P<issue_id>\d+)", [{"url": "https://r.io/{repo}/i/42", "id": "42"}]),
|
|
(
|
|
"Fix #42, #53",
|
|
r"(?:#)(?P<issue_id>\d+)",
|
|
[{"url": "https://r.io/{repo}/i/42", "id": "42"}, {"url": "https://r.io/{repo}/i/53", "id": "53"}],
|
|
),
|
|
("Fix #42", r"(?:#)?<issue_id>\d+)", []), # Broken regex
|
|
],
|
|
)
|
|
def test_extract_issues(backend, text_string, pattern, expected):
|
|
repo = backend.create_repo()
|
|
config = {
|
|
"123": {
|
|
"uid": "123",
|
|
"pat": pattern,
|
|
"url": r"https://r.io/${repo}/i/${issue_id}",
|
|
"pref": "#",
|
|
"desc": "Test Pattern",
|
|
}
|
|
}
|
|
|
|
def get_settings_mock(self, cache=True):
|
|
return config
|
|
|
|
with mock.patch.object(IssueTrackerSettingsModel, "get_settings", get_settings_mock):
|
|
text, issues, errors = helpers.process_patterns(text_string, repo.repo_name)
|
|
|
|
expected = copy.deepcopy(expected)
|
|
for item in expected:
|
|
item["url"] = item["url"].format(repo=repo.repo_name)
|
|
|
|
assert issues == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"text_string, pattern, link_format, expected_text",
|
|
[
|
|
(
|
|
"Fix #42",
|
|
r"(?:#)(?P<issue_id>\d+)",
|
|
"html",
|
|
'Fix <a class="tooltip issue-tracker-link" href="https://r.io/{repo}/i/42" title="Test Pattern">#42</a>',
|
|
),
|
|
("Fix #42", r"(?:#)(?P<issue_id>\d+)", "markdown", "Fix [#42](https://r.io/{repo}/i/42)"),
|
|
("Fix #42", r"(?:#)(?P<issue_id>\d+)", "rst", "Fix `#42 <https://r.io/{repo}/i/42>`_"),
|
|
("Fix #42", r"(?:#)?<issue_id>\d+)", "html", "Fix #42"), # Broken regex
|
|
],
|
|
)
|
|
def test_process_patterns_repo(backend, text_string, pattern, expected_text, link_format):
|
|
repo = backend.create_repo()
|
|
|
|
def get_settings_mock(self, cache=True):
|
|
return {
|
|
"123": {
|
|
"uid": "123",
|
|
"pat": pattern,
|
|
"url": "https://r.io/${repo}/i/${issue_id}",
|
|
"pref": "#",
|
|
"desc": "Test Pattern",
|
|
}
|
|
}
|
|
|
|
with mock.patch.object(IssueTrackerSettingsModel, "get_settings", get_settings_mock):
|
|
processed_text, issues, error = helpers.process_patterns(text_string, repo.repo_name, link_format)
|
|
|
|
assert processed_text == expected_text.format(repo=repo.repo_name)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"text_string, pattern, expected_text",
|
|
[
|
|
(
|
|
"Fix #42",
|
|
r"(?:#)(?P<issue_id>\d+)",
|
|
'Fix <a class="tooltip issue-tracker-link" href="https://r.io/i/42" title="Test Pattern">#42</a>',
|
|
),
|
|
("Fix #42", r"(?:#)?<issue_id>\d+)", "Fix #42"), # Broken regex
|
|
],
|
|
)
|
|
def test_process_patterns_no_repo(text_string, pattern, expected_text):
|
|
def get_settings_mock(self, cache=True):
|
|
return {
|
|
"123": {
|
|
"uid": "123",
|
|
"pat": pattern,
|
|
"url": "https://r.io/i/${issue_id}",
|
|
"pref": "#",
|
|
"desc": "Test Pattern",
|
|
}
|
|
}
|
|
|
|
with mock.patch.object(IssueTrackerSettingsModel, "get_global_settings", get_settings_mock):
|
|
processed_text, issues, errors = helpers.process_patterns(text_string, "")
|
|
|
|
assert processed_text == expected_text
|
|
|
|
|
|
def test_process_patterns_non_existent_repo_name(backend):
|
|
text_string = "Fix #42"
|
|
pattern = r"(?:#)(?P<issue_id>\d+)"
|
|
expected_text = (
|
|
'Fix <a class="tooltip issue-tracker-link" href="https://r.io/do-not-exist/i/42" title="Test Pattern">#42</a>'
|
|
)
|
|
|
|
def get_settings_mock(self, cache=True):
|
|
return {
|
|
"123": {
|
|
"uid": "123",
|
|
"pat": pattern,
|
|
"url": "https://r.io/${repo}/i/${issue_id}",
|
|
"pref": "#",
|
|
"desc": "Test Pattern",
|
|
}
|
|
}
|
|
|
|
with mock.patch.object(IssueTrackerSettingsModel, "get_global_settings", get_settings_mock):
|
|
processed_text, issues, errors = helpers.process_patterns(text_string, "do-not-exist")
|
|
|
|
assert processed_text == expected_text
|
|
|
|
|
|
def test_get_visual_attr(baseapp):
|
|
from rhodecode.apps._base import TemplateArgs
|
|
|
|
c = TemplateArgs()
|
|
assert None is helpers.get_visual_attr(c, "fakse")
|
|
|
|
# emulate the c.visual behaviour
|
|
c.visual = AttributeDict({})
|
|
assert None is helpers.get_visual_attr(c, "some_var")
|
|
|
|
c.visual.some_var = "foobar"
|
|
assert "foobar" == helpers.get_visual_attr(c, "some_var")
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"test_text, inclusive, expected_text",
|
|
[
|
|
("just a string", False, "just a string"),
|
|
("just a string\n", False, "just a string"),
|
|
("just a string\n next line", False, "just a string..."),
|
|
("just a string\n next line", True, "just a string\n..."),
|
|
],
|
|
ids=no_newline_id_generator,
|
|
)
|
|
def test_chop_at(test_text, inclusive, expected_text):
|
|
assert helpers.chop_at_smart(test_text, "\n", inclusive, "...") == expected_text
|