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:
agessaman committed 2026-09-07 16:16:33 -07:00
1 parent 4c76ee3dbe
commit 4753bf32b3
3 files changed
+78 -3

No files matched your search

+5 -3
View File
@@ -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
+48
View File
@@ -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"
+25
View File
@@ -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'