import urllib from cStringIO import StringIO from OFS.interfaces import ITraversable from zope.component import getGlobalSiteManager from zope.component import queryAdapter, getMultiAdapter from zope.interface import directlyProvides from zope.viewlet.interfaces import IViewlet, IViewletManager from quintagroup.seoptimizer.browser.interfaces import IPloneSEOLayer from quintagroup.seoptimizer.browser.seo_configlet import ISEOConfigletSchema from quintagroup.canonicalpath.interfaces import ICanonicalLink from quintagroup.canonicalpath.adapters import DefaultCanonicalLinkAdapter from quintagroup.seoptimizer.tests.base import FunctionalTestCase from Products.PloneTestCase.PloneTestCase import portal_owner, \ default_password import re from Products.Five import zcml class TestBugs(FunctionalTestCase): def afterSetUp(self): self.basic_auth = ':'.join((portal_owner, default_password)) self.loginAsPortalOwner() # prepare test document self.portal.invokeFactory('Document', id='my_doc') self.my_doc = self.portal['my_doc'] self.mydoc_path = "/%s" % self.my_doc.absolute_url(1) def set_title(self, title='', title_override=0, comment='', comment_override=0): """ Set seo title """ portal = self.portal fp = portal['front-page'] request = portal.REQUEST view = portal.restrictedTraverse('@@plone') manager = getMultiAdapter((fp, request, view), IViewletManager, name=u'plone.htmlhead') directlyProvides(request, IPloneSEOLayer) viewlet = getMultiAdapter((fp, request, view, manager), IViewlet, name=u'plone.htmlhead.title') form_data = {'seo_title': title, 'seo_title_override:int': title_override, 'seo_html_comment': comment, 'seo_html_comment_override:int': comment_override, 'form.button.Save': "Save", 'form.submitted:int': 1} self.publish(path=fp.absolute_url(1) + '/@@seo-context-properties', basic=self.basic_auth, request_method='POST', stdin=StringIO(urllib.urlencode(form_data))) viewlet.update() seo_title_comment = viewlet.render() return seo_title_comment def test_seo_title(self): """ Test changing title """ title = "New Title" new_title = u'%s' % title seo_title = self.set_title(title=title, title_override=1) self.assertEqual(new_title, seo_title) def test_seo_comment(self): """ Test changing comment """ comment = "New Comment" seo_title_comment = self.set_title(comment=comment, comment_override=1) self.assert_(seo_title_comment.endswith("" % comment)) def test_seo_title_comment(self): """ Test changing title and comment """ title = "New Title" comment = "New Comment" new_title = u'%s\n' % (title, comment) seo_title_comment = self.set_title(title=title, title_override=1, comment=comment, comment_override=1) self.assertEqual(new_title, seo_title_comment) def test_modification_date(self): """ Modification date changing on SEO properties edit """ form_data = {'seo_title': 'New Title', 'seo_title_override:int': 1, 'form.button.Save': "Save", 'form.submitted:int': 1} md_before = self.my_doc.modification_date self.publish(path=self.mydoc_path + '/@@seo-context-properties', basic=self.basic_auth, request_method='POST', stdin=StringIO(urllib.urlencode(form_data))) md_after = self.my_doc.modification_date self.assertNotEqual(md_before, md_after) def test_bug_20_at_plone_org(self): portal = self.portal fp = portal['front-page'] request = portal.REQUEST view = portal.restrictedTraverse('@@plone') manager = getMultiAdapter((fp, request, view), IViewletManager, name=u'plone.htmlhead') viewlet = getMultiAdapter((fp, request, view, manager), IViewlet, name=u'plone.htmlhead.title') viewlet.update() old_title = viewlet.render() # add IPloneSEOLayer directlyProvides(request, IPloneSEOLayer) viewlet = getMultiAdapter((fp, request, view, manager), IViewlet, name=u'plone.htmlhead.title') viewlet.update() new_title = viewlet.render() self.assertEqual(old_title, new_title) def test_bug_22_at_plone_org(self): """If ICanonicalLink adapter is not found for the context object - page rendering should not break, but only canonical link should disappear. """ curl = re.compile(']*href\s*=\s*\"([^\"]*)\"[^>]*>', re.S | re.M) # When adapter registered for the object - canoncal link # present on the page self.assertNotEqual(queryAdapter(self.my_doc, ICanonicalLink), None) res = self.publish(path=self.mydoc_path, basic=self.basic_auth) self.assertNotEqual(curl.search(res.getBody()), None) # Now remove adapter from the registry -> this should : # - not break page on rendering; # - canonical link will be absent on the page gsm = getGlobalSiteManager() gsm.unregisterAdapter(DefaultCanonicalLinkAdapter, [ITraversable, ], ICanonicalLink) self.assertEqual(queryAdapter(self.my_doc, ICanonicalLink), None) res = self.publish(path=self.mydoc_path, basic=self.basic_auth) self.assertEqual(curl.search(res.getBody()), None) # register adapter back in the global site manager gsm.registerAdapter(DefaultCanonicalLinkAdapter, [ITraversable, ], ICanonicalLink) def test_bug_19_23_at_plone_org(self): """overrides.zcml should present in the root of the package""" import quintagroup.seoptimizer try: zcml.load_config('overrides.zcml', quintagroup.seoptimizer) except IOError: self.fail("overrides.zcml removed from the package root") def test_escape_characters_title(self): """Change escape characters in title of SEO properties Bug url http://plone.org/products/plone-seo/issues/31 """ from cgi import escape title = 'New Title' form_data = {'seo_title': title, 'seo_title_override:int': 1, 'form.button.Save': "Save", 'form.submitted:int': 1} self.publish(path=self.mydoc_path + '/@@seo-context-properties', basic=self.basic_auth, request_method='POST', stdin=StringIO(urllib.urlencode(form_data))) html = self.publish(self.mydoc_path, self.basic_auth).getBody() m = re.match('.*\\s*%s\\s*' % escape(title), html, re.S | re.M) self.assert_(m, 'Title is not escaped properly.') def test_escape_characters_comment(self): """Change escape characters in comment of SEO properties """ from cgi import escape comment = 'New comment' form_data = {'seo_title': 'New Title', 'seo_title_override:int': 1, 'seo_html_comment': comment, 'seo_html_comment_override:int': 1, 'form.button.Save': "Save", 'form.submitted:int': 1} self.publish(path=self.mydoc_path + '/@@seo-context-properties', basic=self.basic_auth, request_method='POST', stdin=StringIO(urllib.urlencode(form_data))) html = self.publish(self.mydoc_path, self.basic_auth).getBody() m = re.match('.*' % escape(comment), html, re.S | re.M) self.assert_(m, 'Comment is not escaped properly.') def test_bug_custom_metatags_update(self): # Prepare a page for the test page = self.portal["front-page"] request = self.portal.REQUEST directlyProvides(request, IPloneSEOLayer) seo_context_props = getMultiAdapter((page, request), name="seo-context-properties") # Set default custom meta tag without default value (tag name only) self.gseo = queryAdapter(self.portal, ISEOConfigletSchema) self.gseo.default_custom_metatags = ["test_tag", ] try: # Breakage on updating custom metatag # with seo-context-properties view seo_context_props.updateSEOCustomMetaTagsProperties([]) except IndexError: self.fail("Error in calculating of default tag value, when only "\ "tag name set in default_custom_metatags property of "\ "the configlet.") class TestBug24AtPloneOrg(FunctionalTestCase): def afterSetUp(self): # Add test users: member, editor member_id = 'test_member' editor_id = 'test_editor' test_pswd = 'pswd' uf = self.portal.acl_users uf.userFolderAddUser(member_id, test_pswd, ['Member'], []) uf.userFolderAddUser(editor_id, test_pswd, ['Member', 'Editor'], []) self.member_auth = '%s:%s' % (member_id, test_pswd) self.editor_auth = '%s:%s' % (editor_id, test_pswd) self.portal_url = '/'.join(self.portal.getPhysicalPath()) def test_not_break(self): """Default portal page should not breaks for any user""" # Anonymous resp = self.publish(path=self.portal_url) self.assertEqual(resp.getStatus(), 200) # Member resp = self.publish(path=self.portal_url, basic=self.member_auth) self.assertEqual(resp.getStatus(), 200) # Editor: this fails, althought must pass resp = self.publish(path=self.portal_url, basic=self.editor_auth) self.assertEqual(resp.getStatus(), 200) def test_tab_visibility(self): """Only Editor can view seo tab""" rexp = re.compile(']*' \ 'href="[a-zA-Z0-9\:\/_-]*/@@seo-context-properties"[^>]*>'\ '\s*SEO Properties\s*', re.I | re.S) # Anonymous: NO SEO Properties link res = self.publish(path=self.portal_url).getBody() self.assertEqual(rexp.search(res), None) # Member: NO 'SEO Properties' link res = self.publish(path=self.portal_url, basic=self.member_auth).getBody() self.assertEqual(rexp.search(res), None) # Editor: PRESENT 'SEO Properties' link res = self.publish(path=self.portal_url, basic=self.editor_auth).getBody() self.assertNotEqual(rexp.search(res), None) def test_tab_access(self): """Only Editor can access 'SEO Properties' tab""" test_url = self.portal_url + '/front-page/@@seo-context-properties' # Anonymous: can NOT ACCESS headers = self.publish(path=test_url).headers self.assert_('Unauthorized' in headers.get('bobo-exception-type', ""), "No 'Unauthorized' exception rised for Anonymous on " \ "'@@seo-context-properties' view") # Member: can NOT ACCESS self.publish(path=test_url, basic=self.member_auth).headers self.assert_('Unauthorized' in headers.get('bobo-exception-type', ""), "No 'Unauthorized' exception rised for Member on " \ "'@@seo-context-properties' view") # Editor: CAN Access res = self.publish(path=test_url, basic=self.editor_auth) self.assertEqual(res.status, 200) def test_tab_edit(self): """Editor can change SEO Properties""" test_url = self.portal_url + '/front-page/@@seo-context-properties' form_data = {'seo_title': 'New Title', 'seo_title_override:int': 1, 'form.submitted:int': 1} res = self.publish(path=test_url, basic=self.editor_auth, request_method='POST', stdin=StringIO(urllib.urlencode(form_data))) self.assertNotEqual(res.status, 200) def test_suite(): from unittest import TestSuite, makeSuite suite = TestSuite() suite.addTest(makeSuite(TestBugs)) suite.addTest(makeSuite(TestBug24AtPloneOrg)) return suite