fuzzyworld

fuzzyworld-tools / files / tests / test_blog_comments.py

test_blog_comments.py

Blame: the commit that last changed each line · file · history

e9fdc5db 2026-11-01 Pavel Safronov1"""Tests for reader comments in server/blog.py: email link, optional form, data-src. Run: python3 -m unittest discover tests"""
2import json
3import os
4import sys
5import unittest
6
7sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
8from test_blog import POST, Tree, blog, needs_markdown # noqa: E402
9
10ON = {"comments_site": "writing", "comments_mailbox": "comments@example.org"}
11FIRST = "2018/03/02/first-post/index.html"
12
13
14def make(test, **config):
15 t = Tree(test, config)
16 t.file("2018-03-02-first-post.md", "---\npublished: true\n---\n" + POST)
17 t.file("2016-05-06-old-post.md", "---\npublished: true\n---\n# Old\n\nSome old text.\n")
18 return t
19
20
21@needs_markdown
22class CommentsOff(unittest.TestCase):
23 def test_off_by_default_nothing_changes(self):
24 t = make(self)
25 _, out = t.build()
26 everything = "\n".join(t.read(out, r) for r in (FIRST, "blog/index.html", "feed/index.xml"))
27 for word in ("comments-section", "data-src", "mailto:", "comment-form"):
28 self.assertNotIn(word, everything)
29 self.assertFalse(os.path.exists(os.path.join(out, "_comments")))
30
31 def test_imported_comments_markup_is_the_same_off_and_on(self):
32 data = {"/2018/03/02/first-post/": [
33 {"id": 1, "parent": 0, "author": "Eve", "date": "2018-03-03T10:00:00", "text": "Hi"},
34 {"id": 2, "parent": 1, "author": "Bob", "date": "2018-03-04T10:00:00", "text": "reply"}]}
35 pages = []
36 for extra in ({}, ON):
37 t = make(self, comments="comments.json", **extra)
38 t.file("comments.json", json.dumps(data), base=t.dir)
39 _, out = t.build()
40 pages.append(t.read(out, FIRST))
41 off, on = pages
42 tree = off[off.index('<ul class="comments">'):off.index('<p class="meta">Comments ©')]
43 self.assertIn(tree, on)
44 self.assertNotIn("data-src", off)
45 self.assertIn('data-src="/_comments/2018/03/02/first-post.json"', on)
46
47
48@needs_markdown
49class CommentsOn(unittest.TestCase):
50 def test_section_for_every_post_with_data_src_and_mailto(self):
51 t = make(self, **ON)
52 _, out = t.build()
53 page = t.read(out, FIRST)
54 self.assertIn('<section class="comments-section" data-src="/_comments/2018/03/02/first-post.json">', page)
55 self.assertIn('<div class="comments-live"></div>', page)
56 self.assertIn('href="mailto:comments@example.org?subject=Comment%20%5Bwriting%3A%2F2018%2F03%2F02%2Ffirst-post%2F%5D"', page)
57 self.assertIn("Comments are moderated, and your address is never shown.", page)
58 self.assertIn('data-src="/_comments/2016/05/06/old-post.json"', t.read(out, "2016/05/06/old-post/index.html"))
59 self.assertNotIn("<form", page)
60 self.assertNotIn('name="website"', page)
61 self.assertFalse(os.path.exists(os.path.join(out, "_comments")))
62 blog.audit_output(out)
63
64 def test_undated_url_scheme(self):
65 t = make(self, url="/notes/{slug}/", **ON)
66 _, out = t.build()
67 page = t.read(out, "notes/first-post/index.html")
68 self.assertIn('data-src="/_comments/notes/first-post.json"', page)
69 self.assertIn("writing%3A%2Fnotes%2Ffirst-post%2F%5D", page)
70
71 def test_hostile_title_does_not_reach_the_widget_and_values_are_escaped(self):
72 t = Tree(self, {"comments_site": "writing", "comments_mailbox": "a+b&c@example.org",
73 "comments_form": {"action": "https://c.example.org/post.php?a=1&b=2"}})
74 t.file("2018-03-02-x.md", '---\npublished: true\ntitle: "<script>alert(1)</script>"\n---\n# T\n\n## Writing\n\ntext\n')
75 _, out = t.build()
76 page = t.read(out, "2018/03/02/x/index.html")
77 self.assertNotIn("<script>alert", page)
78 self.assertIn("mailto:a%2Bb%26c@example.org?", page)
79 self.assertIn('action="https://c.example.org/post.php?a=1&amp;b=2"', page)
80
81 def test_form_only_with_comments_form(self):
82 t = make(self, comments_form={"action": "https://comments.example.org/post.php"}, **ON)
83 _, out = t.build()
84 page = t.read(out, FIRST)
85 self.assertIn('<form class="comment-form" method="post" action="https://comments.example.org/post.php">', page)
86 self.assertIn('<input type="hidden" name="site" value="writing">', page)
87 self.assertIn('<input type="hidden" name="path" value="/2018/03/02/first-post/">', page)
88 self.assertIn('<input type="hidden" name="elapsed" value="0">', page)
89 self.assertIn('name="website" tabindex="-1" autocomplete="off"', page)
90 self.assertIn('name="name" maxlength="60" required', page)
91 self.assertIn('name="text" rows="5" maxlength="4000" required', page)
92 self.assertIn('type="submit"', page)
93
94
95class CommentsConfig(unittest.TestCase):
96 def bad(self, **config):
97 with self.assertRaises(blog.BlogError):
98 Tree(self, config).cfg()
99
100 def test_validation(self):
101 self.bad(comments_site="writing") # no mailbox
102 self.bad(comments_site="writing", comments_mailbox="not-an-address")
103 self.bad(comments_site="writing", comments_mailbox="a b@example.org")
104 self.bad(comments_site="bad site", comments_mailbox="a@example.org")
105 self.bad(comments_site=5, comments_mailbox="a@example.org")
106 self.bad(comments_mailbox="a@example.org") # mailbox without a site
107 self.bad(comments_form={"action": "https://x.org/p"}) # form without a site
108 for form in ("https://x.org/p", {"action": "http://x.org/p"}, {"action": "https://"}, {"action": 3},
109 {"action": "https://x.org/p q"}, {"action": 'https://x.org/"'}, {"url": "https://x.org/p"},
110 {"action": "https://x.org/p", "extra": 1}, {}):
111 self.bad(comments_form=form, **ON)
112
113 def test_valid_form_is_accepted(self):
114 cfg = Tree(self, {**ON, "comments_form": {"action": "https://x.org/p"}}).cfg()
115 self.assertEqual(cfg["comments_form"], {"action": "https://x.org/p"})
116
117 def test_defaults(self):
118 cfg = Tree(self).cfg()
119 self.assertEqual((cfg["comments_site"], cfg["comments_mailbox"], cfg["comments_form"]), (None, None, None))
120 self.assertEqual(Tree(self, {**ON, "comments_form": None}).cfg()["comments_form"], None)
121
122
123if __name__ == "__main__":
124 unittest.main()