Files
adaptive-multi-protocol-gat…/tests/test_gemtext_renderer.py
T
jwinterm 4767b7965a fix: emit real micron, and validate values interpolated into daemon configs
render_micron was a one-line alias to the Gemtext renderer, so every .mu file
AMPG produced was Gemtext. A Nomad Network browser renders those markers as
raw punctuation. Micron is a different markup: headings are > by depth, links
are inline as `[label`url] rather than standalone lines, emphasis is a toggle
pair, and literal blocks are fenced with `=.

Content is now escaped as well. Micron's control character is the backtick, and
>, <, #, and - are structural at column zero, so a vendor named ">Free Beer"
would have become a section heading on the published page. The line-start check
applies to the emitted line rather than each text chunk, since whether a
character is structural depends on it landing at column zero.

Separately, torrc and tunnels.conf are line-oriented with no quoting or escape
mechanism, so a newline in an interpolated value injects an arbitrary directive:
a tunnel_name of "web\nhost = attacker.example" silently repoints the tunnel.
tunnel_name and keys_file are now validated at the point of interpolation.
_validate_site_id covers the init path only, so a config loaded from disk never
passed through it. There is nothing to escape with here, so these reject rather
than sanitize.

Generated systemd units had no User= and no hardening, so an operator following
the install steps as root got a root-run daemon. Units now carry User=/Group=
when a service account is configured, a visible warning when not, and a baseline
of hardening directives chosen to not break a daemon whose state directory may
live anywhere.

Two tests asserted Gemtext markers inside a .mu file and so had encoded the bug.
2026-07-28 16:03:40 -04:00

86 lines
2.5 KiB
Python

import unittest
from ampg.renderers import render_gemtext, render_micron
class GemtextRendererTest(unittest.TestCase):
def test_converts_basic_html_to_gemtext(self):
html = """
<!doctype html>
<html>
<head><script>ignored()</script></head>
<body>
<h1>Hello</h1>
<p>Read the <a href="about.html">about page</a>.</p>
<ul><li>One</li><li>Two</li></ul>
<img src="logo.png" alt="Logo">
</body>
</html>
"""
gemtext = render_gemtext(
html,
rewrite_link=lambda href: href.replace(".html", ".gmi"),
)
self.assertIn("# Hello", gemtext)
self.assertIn("Read the about page.", gemtext)
self.assertIn("=> about.gmi about page", gemtext)
self.assertIn("- One", gemtext)
self.assertIn("- Two", gemtext)
self.assertIn("=> logo.png Image: Logo", gemtext)
self.assertNotIn("ignored", gemtext)
def test_converts_basic_html_to_micron(self):
html = """
<html>
<body>
<h1>Hello</h1>
<p>Read the <a href="about.html">about page</a>.</p>
<script>ignored()</script>
</body>
</html>
"""
micron = render_micron(
html,
rewrite_link=lambda href: href.replace(".html", ".mu"),
)
# Micron, not Gemtext: headings are `>` by depth and links are inline
# as `[label`url], not standalone "=> url label" lines.
self.assertIn(">Hello", micron)
self.assertNotIn("# Hello", micron)
self.assertIn("`[about page`about.mu]", micron)
self.assertNotIn("=> about.mu", micron)
self.assertNotIn("ignored", micron)
def test_micron_escapes_content_that_would_become_markup(self):
html = (
"<p>&gt;Free Beer</p>"
"<p>price is 3 `BTC`</p>"
"<h2>Sub</h2>"
"<pre>literal `backticks` stay</pre>"
)
micron = render_micron(html)
# A line starting with a structural character must not become a heading.
self.assertIn("\\>Free Beer", micron)
# Backticks in content must not open a formatting run.
self.assertIn("3 \\`BTC\\`", micron)
# Headings and literal blocks still render as real micron.
self.assertIn(">>Sub", micron)
self.assertIn("`=", micron)
self.assertIn("literal `backticks` stay", micron)
def test_micron_emphasis_uses_toggle_pairs(self):
micron = render_micron("<p>a <strong>b</strong> c <em>d</em></p>")
self.assertIn("`!b`!", micron)
self.assertIn("`*d`*", micron)
if __name__ == "__main__":
unittest.main()