"""
Tests for the comments plugin,
contrib/plugins/comments/plugins/comments.py.
"""
__author__ = 'Ryan Barrett <pyblosxom@ryanb.org>'
__url__ = 'http://pyblosxom.sourceforge.net/wiki/index.php/Framework_for_testing_plugins'
from plugins.tests.test_base import PluginTest
from plugins.comments.plugins import comments
import cgi, cPickle, os, cStringIO, sys
class TestComments(PluginTest):
"""Test class for the comments plugin.
"""
def setUp(self):
PluginTest.setUp(self, comments)
# add comment templates
self.renderer.flavour.update({'comment-story': 'comment-story',
'comment': 'comment'})
# inject the frozen time module
comments.time = self.frozen_time
# populate with default config vars
comments.cb_start(self.args)
def tearDown(self):
PluginTest.tearDown(self)
def comment_path(self):
"""Returns the comment path that would currently be created."""
filename = '%s-%0.1f.%s' % (self.entry_name, self.timestamp,
self.config['comment_draft_ext'])
return os.path.join(self.config['comment_dir'], filename)
def comment(self, title='title', author='author', body='body', url=None,
email=None, ipaddress=None, encoding=None, preview=None,
**kwargs):
"""Posts a comment with the given contents."""
# set the encoding in the config. it should default to utf-8
if encoding:
self.config['blog_encoding'] = encoding
# build up the form data and post the comment
args = [(arg, vars()[arg])
for arg in ('title', 'author', 'body', 'url', 'email', 'preview')
if vars()[arg] is not None]
self.set_form_data(dict(args))
comments.cb_prepare(self.args)
def check_comment_file(self, title='title', author='author', body='body',
url='', email='', encoding=None, ipaddress='',
expected_title=None, expected_author=None,
expected_body=None, expected_url=None, preview=None,
delete_datadir=True):
"""Posts a comment and checks its contents on disk."""
self.comment(title, author, body, url, email, ipaddress, encoding,
preview)
if encoding is None:
encoding = 'utf-8'
if expected_title is None:
expected_title = title
if expected_author is None:
expected_author = author
if expected_body is None:
expected_body = body
if expected_url is None:
expected_url = url
# check the files in the comments directory
files = os.listdir(self.config['comment_dir'])
self.assert_(os.path.basename(self.comment_path()) in files)
self.assert_(comments.LATEST_PICKLE_FILE in files)
# check the coment file's contents
expected_lines = [
'<?xml version="1.0" encoding="%s"?>\n' % encoding,
'<item>\n',
'<description>%s</description>\n' % cgi.escape(expected_body),
'<pubDate>%0.1f</pubDate>\n' % self.timestamp,
'<author>%s</author>\n' % cgi.escape(expected_author),
'<title>%s</title>\n' % cgi.escape(expected_title),
'<source></source>\n',
'<link>%s</link>\n' % cgi.escape(expected_url),
'<w3cdate>%s</w3cdate>\n' % self.timestamp_w3c,
'<date>%s</date>\n' % self.timestamp_date,
'<ipaddress>%s</ipaddress>\n' % ipaddress,
'</item>\n',
]
if email:
expected_lines.insert(-1, '<email>%s</email>\n' % email)
file = open(self.comment_path())
actual_lines = file.readlines()
file.close()
expected_lines.sort()
actual_lines.sort()
for expected, actual in zip(expected_lines, actual_lines):
self.assertEquals(expected, actual)
if delete_datadir:
self.delete_datadir()
def check_comment_output(self, expected, delete_datadir=True, **kwargs):
"""Posts a comment and checks its rendered output.
Note that this deletes the datadir before it posts the
comment!
"""
self.data['display_comment_default'] = True
self.comment(**kwargs)
comments.cb_story(self.args)
self.args['template'] = ''
comments.cb_story_end(self.args)
self.assertEquals(expected, self.args['template'])
if delete_datadir:
self.delete_datadir()
def test_cb_start(self):
"""cb_start() should set defaults for some config variables."""
self.config = self.config_base
comments.cb_start(self.args)
self.assertEquals(os.path.join(self.datadir, 'comments'),
self.config['comment_dir'])
self.assertEquals('cmt', self.config['comment_ext'])
self.assertEquals('cmt', self.config['comment_draft_ext'])
self.assertEquals(0, self.config['comment_nofollow'])
def test_verify_installation(self):
"""verify_installation should check the comment dir and smtp
config."""
# comment_dir must exist
assert not os.path.exists('/not/a/directory')
self.config['comment_dir'] = '/not/a/directory'
self.assertEquals(0, comments.verify_installation(self.request))
del self.config['comment_dir']
# either all smtp config variables must be defined, or none
smtp_vars = ['comment_smtp_server', 'comment_smtp_from',
'comment_smtp_to']
for smtp_var in smtp_vars:
[self.config.pop(var, '') for var in smtp_vars]
self.config[smtp_var] = 'abc'
self.assertEquals(0, comments.verify_installation(self.request))
del self.config[smtp_vars[-1]]
self.assertEquals(1, comments.verify_installation(self.request))
def test_cb_handle(self):
"""cb_handle() should intercept requests for /comments.js."""
self.assertEquals(None, comments.cb_handle(self.args))
self.request.add_http({'PATH_INFO': '/not_comments.js'})
self.assertEquals(None, comments.cb_handle(self.args))
self.request.add_http({'PATH_INFO': '/comments.js'})
self.assertEquals(True, comments.cb_handle(self.args))
response = self.request.get_response()
self.assertEquals('text/javascript',
response.get_headers()['Content-Type'])
out = cStringIO.StringIO()
response.send_body(out)
self.assert_(out.getvalue().startswith(
'/* AJAX comment support for pyblosxom'))
def test_cb_prepare_showcomments(self):
"""cb_prepare() should set display_comment_default to show
comments."""
# default is to not show comments
del self.data['bl_type']
comments.cb_prepare(self.args)
self.assertEquals(False, self.data['display_comment_default'])
# show them if the bl_type config var is 'file'
self.data['bl_type'] = 'db'
comments.cb_prepare(self.args)
self.assertEquals(False, self.data['display_comment_default'])
self.data['bl_type'] = 'file'
comments.cb_prepare(self.args)
self.assertEquals(True, self.data['display_comment_default'])
# or if the query string has showcomments=yes
del self.data['bl_type']
self.request.add_http({'QUERY_STRING': 'x=yes&showcomments=no7&y=no'})
comments.cb_prepare(self.args)
self.assertEquals(False, self.data['display_comment_default'])
self.request.add_http({'QUERY_STRING': 'x=yes&showcomments=yes&y=no'})
comments.cb_prepare(self.args)
self.assertEquals(True, self.data['display_comment_default'])
def test_cb_prepare_new_comment(self):
"""A new comment should be packaged in XML and stored in a new file."""
self.check_comment_file(title='title', author='author', body='body')
# url is optional. try setting it.
self.check_comment_file(url='http://home/')
# previewed comments shouldn't be stored
self.comment(preview='yes')
self.assert_(not os.path.exists(self.comment_path()))
def test_cb_prepare_encoding(self):
"""If the blog_encoding config var is set, it should be used."""
self.check_comment_file(encoding='us-ascii')
def test_cb_prepare_massage_link(self):
"""User-provided URLs should be scrubbed and linkified if
necessary."""
# html control characters should be stripped
self.check_comment_file(url='<script arg=\'val"ue"\'>',
expected_url='http://script arg=value')
# http:// should only be added if there isn't already a protocol
self.check_comment_file(url='xmpp:me@jabber.org')
def test_cb_prepare_nofollow(self):
"""Nofollow support should add rel="nofollow" to links in the
body."""
body = '<a href="/dest">x</a>'
# default is off
self.assert_(self.config['comment_nofollow'] == False)
self.check_comment_file(body=body)
# turned on
self.config['comment_nofollow'] = True
nofollow_body = '<a rel="nofollow" href="/dest">x</a>'
self.check_comment_file(body=body, expected_body=nofollow_body)
def test_cb_prepare_email(self):
"""User-provided URLs should be scrubbed and linkified if necessary."""
self.check_comment_file(email='a@b.c')
def test_cb_prepare_ipaddress(self):
"""If provided, IP address should be recorded."""
ipaddress = '12.34.56.78'
self.request.add_http({'REMOTE_ADDR': ipaddress})
self.check_comment_file(ipaddress=ipaddress)
def test_cb_reject(self):
"""Comments should be filtered with cb_comment_reject()."""
# try rejecting the comment, with and without a message
for return_value, msg in ((True, 'Comment rejected.'),
((True, 'bad!'), 'bad!')
):
self.inject_callback('comment_reject', lambda: return_value)
self.comment()
self.assert_(not os.path.exists(self.comment_path()))
self.assertEquals(True, self.data['rejected'])
self.assertEquals(msg, self.data['comment_message'])
del self.data['rejected']
# try accepting the comment, with and without a message
for return_value in [False, (False, 'ok')]:
self.inject_callback('comment_reject', lambda: return_value)
self.check_comment_file()
self.assert_('rejected' not in self.data)
self.assertEquals('Comment submitted. Thanks!',
self.data['comment_message'])
def test_cb_prepare_latest_pickle(self):
"""The "latest" file should contain the last comment's timestamp."""
self.comment()
latest_path = os.path.join(self.config['comment_dir'],
comments.LATEST_PICKLE_FILE)
timestamp = cPickle.load(open(latest_path))
self.assertEquals(self.timestamp, timestamp)
def test_cb_prepare_draft(self):
"""For draft support, comment_draft_ext should override comment_ext."""
self.config['comment_draft_ext'] = 'draft'
self.check_comment_file()
def test_cb_head(self):
"""cb_head() should expand template variables in single-entry lists."""
template = self.args['template']
# only expand if we have a comment-head template
self.assert_('comment-head' not in self.renderer.flavour)
self.assertEquals(template, comments.cb_head(self.args))
self.assert_(not self.entry.has_key('title'))
# don't expand if we're displaying more than one entry
self.renderer.flavour['comment-head'] = ''
self.renderer.set_content([self.entry, self.entry])
self.assertEquals(template, comments.cb_head(self.args))
self.assert_(not self.entry.has_key('title'))
# we have comment-head and only one entry. expand!
class MockEntry(dict):
"""Intercepts __getitem__ and records the key."""
def __getitem__(self, key):
self.key = key
mock_entry = MockEntry()
self.renderer.set_content([self.entry])
self.args['entry'] = {'entry_list': [mock_entry]}
self.assertEquals(template, comments.cb_head(self.args))
self.assertEquals('title', mock_entry.key)
def test_cb_renderer(self):
"""cb_renderer() should return an AjaxRenderer for ajax
requests."""
self.assert_(not isinstance(comments.cb_renderer(self.args),
comments.AjaxRenderer))
self.set_form_data({'ajax': 'true'})
self.assert_(isinstance(comments.cb_renderer(self.args),
comments.AjaxRenderer))
def test_ajax_renderer(self):
"""AjaxRenderer should only output previewed and posted
comments."""
def should_output(template_name):
renderer = comments.AjaxRenderer(self.request, self.data)
return renderer._should_output(self.entry,
template_name)
# a comment preview
self.set_form_data({'ajax': 'preview'})
self.assertEquals(True, should_output('comment-preview'))
self.assertEquals(False, should_output('story'))
# a comment that was just posted
self.set_form_data({'ajax': 'post'})
self.assertEquals(False, should_output('story'))
self.entry['cmt_time'] = self.timestamp
self.assert_('cmt_time' not in self.data)
self.assertEquals(False, should_output('comment'))
self.data['cmt_time'] = self.timestamp
self.assertEquals(True, should_output('comment'))
def test_num_comments(self):
"""cb_story() should count the number of comments."""
self.data['display_comment_default'] = True
def check_num_comments(expected):
if self.entry.has_key("num_comments"):
self.entry["num_comments"] = None
comments.cb_story(self.args)
self.assertEquals(expected, self.entry['num_comments'])
check_num_comments(0)
self.comment()
check_num_comments(1)
self.frozen_time.timestamp += 1
self.comment()
check_num_comments(2)
def test_when_to_render_comments(self):
"""cb_story[_end]() should only render comment templates when
appropriate.
"""
def check_no_comments(expected):
args_template = self.args['template']
comments.cb_story(self.args)
self.assertEquals(expected, self.args['template'])
comments.cb_story_end(self.args)
self.assertEquals(expected, self.args['template'])
self.args['template'] = args_template
# with no comment-story template, there's nothing
del self.renderer.flavour['comment-story']
self.renderer.set_content([self.entry])
check_no_comments('template starts:')
# with a comment-story template, we show the template
# once
self.renderer.flavour['comment-story'] = 'comment-story'
self.renderer.set_content([self.entry, self.entry])
check_no_comments('template starts:comment-story')
self.renderer.set_content([self.entry])
self.data['display_comment_default'] = False
check_no_comments('template starts:comment-story')
self.data['display_comment_default'] = True
self.entry['nocomments'] = True
check_no_comments('template starts:')
def test_cb_story_comment_story_template(self):
"""Check that cb_story() appends the comment-story template.."""
self.data['display_comment_default'] = True
self.assert_(self.renderer.flavour['comment-story'] == 'comment-story')
comments.cb_story(self.args)
self.assertEquals('template starts:comment-story',
self.args['template'])
def test_cb_story_end_renders_comments(self):
self.comment()
# """Check that cb_story_end() renders comments."""
self.data['display_comment_default'] = True
# no comments. check both cb_story_end's return value and
# args['template'].
self.args['template'] = 'foo'
self.assertEquals('foo', comments.cb_story_end(self.args))
self.assertEquals('foo', self.args['template'])
# one comment
self.renderer.flavour['comment'] = '$cmt_time '
expected = '%s ' % self.timestamp
self.check_comment_output(expected, delete_datadir=False)
# two comments
self.frozen_time.timestamp += 1
expected += '%s ' % self.frozen_time.timestamp
self.check_comment_output(expected)
def test_template_variables(self):
"""Check the comment template variables."""
self.data['display_comment_default'] = True
# these will sent in the form data
args = {
'body': 'body=with"chars',
'author': 'author',
'title': 'title',
'url': 'http://snarfed.org/',
'email': 'pyblosxom@ryanb.org',
}
# these will be used as template variables, prefixed with $cmt_
vars = dict(args)
for old, new in [('body', 'description'), ('url', 'link')]:
vars[new] = vars[old]
del vars[old]
vars.update({
# these are generated by pyblosxom
'time': str(self.timestamp),
'w3cdate': self.timestamp_w3c,
'date': self.timestamp_date,
'pubDate': self.timestamp_asc,
'description': 'body=with"chars',
})
# these depends on the fact that dict.keys() and dict.values() return
# items in the same order. so far, python guarantees this.
def make_template():
return '\n'.join('$cmt_%s' % name for name in vars.keys())
def make_expected():
return '\n'.join(vars.values())
# a normal comment
self.renderer.flavour['comment'] = 'comment:\n' + make_template()
self.check_comment_output('comment:\n' + make_expected(), **args)
# a previewed comment
self.renderer.flavour['comment-preview'] = 'preview:\n' + make_template()
args['preview'] = 'yes'
self.check_comment_output('preview:\n' + make_expected(), **args)
# a rejected comment
del args['preview']
self.inject_callback('comment_reject', lambda: (True, 'foo'))
vars['description'] = '<span class="error">foo</span>'
self.renderer.flavour['comment'] = 'comment:\n' + make_template()
self.check_comment_output('comment:\n' + make_expected(), **args)
def test_optionally_linked_author(self):
"""Test the cmt_optionally_linked_author template variable."""
self.renderer.flavour['comment'] = '$cmt_optionally_linked_author'
self.assert_(self.config['comment_nofollow'] == False)
self.check_comment_output('me', author='me', url='')
self.check_comment_output('<a href="http://home">me</a>',
author='me', url='home')
self.config['comment_nofollow'] = True
self.check_comment_output('me', author='me', url='')
self.check_comment_output('<a rel="nofollow" href="http://home">me</a>',
author='me', url='home')
|