mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-10-23 06:12:19 +00:00
test: close coverage gaps found by differential-testing the parser rewrite
Differentially tested the current parser against the pre-PR one over every template shape shipped in config.ini.example and docs (31 shapes x 3 field sets x 4 message states, 372 comparisons) plus 32 adversarial inputs. That found one regression I had introduced: reading a quote immediately after ':' as the start of a quoted argument voided `prefix_if_nonempty:"`, whose unterminated string rejected the whole placeholder and emitted raw template text. An unterminated quote now falls back to greedy parsing, so a literal quote prefix behaves as it always has. Both cases are pinned by tests. Also covered the shorten_url alias in feed formats, which had none: accepted as a function, chains like shorten, falls back to the original on failure, is seen through by shorten_feed_urls so a link is not shortened twice, and does not swallow an unrelated name that merely starts with it. Reset the warn-once flag in the async no-warning test. It asserted warning was never called while a preceding test could already have tripped the flag, so it would have passed vacuously.
This commit is contained in:
1 parent
4c76ee3dbe
commit
4753bf32b3
3 files changed
+78
-3
No files matched your search
@@ -272,11 +272,13 @@ class _TemplateParser:
|
||||
# A quote immediately after ':' opts into the quoted-argument grammar.
|
||||
# Anything else stays greedy, so existing unquoted literals keep their
|
||||
# pipes and their leading/trailing whitespace exactly as written.
|
||||
# An *unterminated* quote is not a quoted argument at all: falling back
|
||||
# to greedy keeps `prefix_if_nonempty:"` prepending a literal quote the
|
||||
# way it always has, rather than voiding the placeholder to raw text.
|
||||
if i < self.n and self.s[i] == '"':
|
||||
value, j = self._parse_quoted_string(i)
|
||||
if value is None:
|
||||
return None, '', j
|
||||
return name, value, j
|
||||
if value is not None:
|
||||
return name, value, j
|
||||
close = self.s.find('}', i)
|
||||
if close == -1:
|
||||
return None, '', self.n
|
||||
|
||||
@@ -138,3 +138,51 @@ class TestFormatFeedMessage:
|
||||
shorten_feed_urls=False,
|
||||
)
|
||||
assert msg == "https://example.com/long"
|
||||
|
||||
|
||||
class TestShortenUrlAlias:
|
||||
"""`shorten_url` is the response-template spelling; feed formats accept it too.
|
||||
|
||||
Without the alias a chain copied from a command response_format hits the unknown
|
||||
-function branch and the long URL goes out unshortened, with no warning.
|
||||
"""
|
||||
|
||||
@patch("modules.feed_format.shorten_url_sync", return_value="https://v.gd/x")
|
||||
def test_shorten_url_is_accepted_as_a_function(self, mock_shorten):
|
||||
out = apply_feed_field_function(
|
||||
"https://example.com/long", "shorten_url", config=object()
|
||||
)
|
||||
assert out == "https://v.gd/x"
|
||||
mock_shorten.assert_called_once()
|
||||
|
||||
@patch("modules.feed_format.shorten_url_sync", return_value="https://v.gd/x")
|
||||
def test_shorten_url_chains_like_shorten(self, _mock_shorten):
|
||||
assert (
|
||||
apply_feed_field_function(
|
||||
"https://example.com/long", "shorten_url|truncate:9", config=object()
|
||||
)
|
||||
== apply_feed_field_function(
|
||||
"https://example.com/long", "shorten|truncate:9", config=object()
|
||||
)
|
||||
)
|
||||
|
||||
@patch("modules.feed_format.shorten_url_sync", return_value="")
|
||||
def test_shorten_url_falls_back_to_the_original(self, _mock_shorten):
|
||||
out = apply_feed_field_function(
|
||||
"https://example.com/long", "shorten_url", config=object()
|
||||
)
|
||||
assert out == "https://example.com/long"
|
||||
|
||||
@patch("modules.feed_format.shorten_url_sync", return_value="https://v.gd/x")
|
||||
def test_explicit_shorten_url_is_not_double_shortened(self, mock_shorten):
|
||||
"""shorten_feed_urls must see through the alias, as it does through `shorten`."""
|
||||
format_feed_message(
|
||||
{"title": "t", "description": "", "link": "https://example.com/long"},
|
||||
"{link|shorten_url}",
|
||||
shorten_feed_urls=True,
|
||||
config=object(),
|
||||
)
|
||||
assert mock_shorten.call_count == 1
|
||||
|
||||
def test_an_unrelated_name_starting_with_shorten_url_is_untouched(self):
|
||||
assert apply_feed_field_function("abc", "shorten_urlish") == "abc"
|
||||
@@ -569,8 +569,12 @@ async def test_async_render_does_not_warn_about_the_event_loop():
|
||||
logger = MagicMock()
|
||||
|
||||
with pytest.MonkeyPatch.context() as mp:
|
||||
import modules.response_template as rt
|
||||
import modules.url_shortener as us
|
||||
mp.setattr(us.requests, "get", lambda *a, **k: resp)
|
||||
# Without this reset the assertion below passes vacuously whenever an
|
||||
# earlier test has already tripped the warn-once flag.
|
||||
mp.setattr(rt, "_warned_blocking_render", False)
|
||||
out = await format_piped_template_async(
|
||||
"{link|shorten_url}",
|
||||
{"link": "https://example.com/long"},
|
||||
@@ -580,3 +584,24 @@ async def test_async_render_does_not_warn_about_the_event_loop():
|
||||
|
||||
assert out == "https://v.gd/abc123"
|
||||
logger.warning.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_a_bare_quote_argument_is_still_a_greedy_literal():
|
||||
"""Regression: opting into quoted args must not void an unterminated quote.
|
||||
|
||||
`prefix_if_nonempty:"` prepends a literal quote character and always has. Reading
|
||||
it as the start of a quoted argument leaves the string unterminated, which would
|
||||
reject the whole placeholder and emit raw template text over RF.
|
||||
"""
|
||||
assert format_piped_template('{d|prefix_if_nonempty:"}', {"d": "12.4km"}) == '"12.4km'
|
||||
assert format_piped_template(
|
||||
'{d|prefix_if_nonempty:"unterminated}', {"d": "12.4km"}
|
||||
) == '"unterminated12.4km'
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_a_quote_after_whitespace_is_a_greedy_literal_not_a_quoted_argument():
|
||||
"""Only a quote *immediately* after ':' opts in, so spacing is preserved."""
|
||||
out = format_piped_template('{d|prefix_if_nonempty: "L" }', {"d": "12.4km"})
|
||||
assert out == ' "L" 12.4km'
|
||||
Reference in new issue
Block a user