fuzzyworld-tools / files / tests / test_blog_comments.py
test_blog_comments.py
| 1 | """Tests for reader comments in server/blog.py: email link, optional form, data-src. Run: python3 -m unittest discover tests""" |
| 2 | import json |
| 3 | import os |
| 4 | import sys |
| 5 | import unittest |
| 6 | |
| 7 | sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) |
| 8 | from test_blog import POST, Tree, blog, needs_markdown # noqa: E402 |
| 9 | |
| 10 | ON = {"comments_site": "writing", "comments_mailbox": "comments@example.org"} |
| 11 | FIRST = "2018/03/02/first-post/index.html" |
| 12 | |
| 13 | |
| 14 | def 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 |
| 22 | class 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 |
| 49 | class 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&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 | |
| 95 | class 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 | |
| 123 | if __name__ == "__main__": |
| 124 | unittest.main() |