[1958] | 1 | import urllib |
---|
[1493] | 2 | from cStringIO import StringIO |
---|
[2205] | 3 | |
---|
[2237] | 4 | from OFS.interfaces import ITraversable |
---|
| 5 | |
---|
[2205] | 6 | from zope.component import providedBy |
---|
[2237] | 7 | from zope.component import getGlobalSiteManager |
---|
| 8 | from zope.component import queryAdapter, getMultiAdapter |
---|
[2205] | 9 | from zope.interface import directlyProvides |
---|
| 10 | from zope.viewlet.interfaces import IViewlet, IViewletManager |
---|
| 11 | from zope.publisher.browser import TestRequest |
---|
| 12 | |
---|
| 13 | from quintagroup.seoptimizer.browser.interfaces import IPloneSEOLayer |
---|
[2440] | 14 | from quintagroup.seoptimizer.browser.views import PROP_CUSTOM_PREFIX |
---|
| 15 | from quintagroup.seoptimizer.browser.seo_configlet import ISEOConfigletSchema |
---|
[2237] | 16 | from quintagroup.canonicalpath.interfaces import ICanonicalLink |
---|
| 17 | from quintagroup.canonicalpath.adapters import DefaultCanonicalLinkAdapter |
---|
[1877] | 18 | from base import * |
---|
[1493] | 19 | |
---|
[2205] | 20 | |
---|
[1493] | 21 | class TestBugs(FunctionalTestCase): |
---|
| 22 | |
---|
| 23 | def afterSetUp(self): |
---|
[1894] | 24 | self.basic_auth = ':'.join((portal_owner,default_password)) |
---|
| 25 | self.loginAsPortalOwner() |
---|
| 26 | # prepare test document |
---|
| 27 | my_doc = self.portal.invokeFactory('Document', id='my_doc') |
---|
| 28 | self.my_doc = self.portal['my_doc'] |
---|
[2237] | 29 | self.mydoc_path = "/%s" % self.my_doc.absolute_url(1) |
---|
[1493] | 30 | |
---|
| 31 | def test_modification_date(self): |
---|
| 32 | """ Modification date changing on SEO properties edit """ |
---|
[1894] | 33 | form_data = {'seo_title': 'New Title', |
---|
| 34 | 'seo_title_override:int': 1, |
---|
[3012] | 35 | 'form.button.Save': "Save", |
---|
[1894] | 36 | 'form.submitted:int': 1} |
---|
[1493] | 37 | |
---|
[1894] | 38 | md_before = self.my_doc.modification_date |
---|
[2237] | 39 | self.publish(path=self.mydoc_path+'/@@seo-context-properties', |
---|
[1894] | 40 | basic=self.basic_auth, request_method='POST', |
---|
| 41 | stdin=StringIO(urllib.urlencode(form_data))) |
---|
| 42 | md_after = self.my_doc.modification_date |
---|
| 43 | |
---|
[1493] | 44 | self.assertNotEqual(md_before, md_after) |
---|
| 45 | |
---|
[2205] | 46 | def test_bug_20_at_plone_org(self): |
---|
| 47 | portal = self.portal |
---|
| 48 | fp = portal['front-page'] |
---|
| 49 | request = portal.REQUEST |
---|
| 50 | view = portal.restrictedTraverse('@@plone') |
---|
[1894] | 51 | |
---|
[2205] | 52 | manager = getMultiAdapter((fp, request, view), IViewletManager, |
---|
| 53 | name=u'plone.htmlhead') |
---|
| 54 | viewlet = getMultiAdapter((fp, request, view, manager), IViewlet, |
---|
| 55 | name=u'plone.htmlhead.title') |
---|
| 56 | viewlet.update() |
---|
| 57 | old_title = viewlet.render() |
---|
| 58 | |
---|
| 59 | # add IPloneSEOLayer |
---|
| 60 | directlyProvides(request, IPloneSEOLayer) |
---|
| 61 | |
---|
| 62 | viewlet = getMultiAdapter((fp, request, view, manager), IViewlet, |
---|
| 63 | name=u'plone.htmlhead.title') |
---|
| 64 | viewlet.update() |
---|
| 65 | new_title = viewlet.render() |
---|
| 66 | |
---|
| 67 | self.assertEqual(old_title, new_title) |
---|
| 68 | |
---|
[2237] | 69 | def test_bug_22_at_plone_org(self): |
---|
| 70 | """If ICanonicalLink adapter is not found for the context object |
---|
[2239] | 71 | - page rendering should not break, but only canonical link |
---|
[2237] | 72 | should disappear. |
---|
| 73 | """ |
---|
| 74 | curl = re.compile('<link\srel\s*=\s*"canonical"\s+' \ |
---|
| 75 | '[^>]*href\s*=\s*\"([^\"]*)\"[^>]*>', re.S|re.M) |
---|
| 76 | # When adapter registered for the object - canoncal link present on the page |
---|
| 77 | self.assertNotEqual( queryAdapter(self.my_doc, ICanonicalLink), None) |
---|
[2205] | 78 | |
---|
[2237] | 79 | res = self.publish(path=self.mydoc_path, basic=self.basic_auth) |
---|
| 80 | self.assertNotEqual(curl.search(res.getBody()), None) |
---|
[2205] | 81 | |
---|
[2237] | 82 | # Now remove adapter from the registry -> this should : |
---|
| 83 | # - not break page on rendering; |
---|
| 84 | # - canonical link will be absent on the page |
---|
| 85 | gsm = getGlobalSiteManager() |
---|
| 86 | gsm.unregisterAdapter(DefaultCanonicalLinkAdapter, [ITraversable,], |
---|
| 87 | ICanonicalLink) |
---|
| 88 | self.assertEqual( queryAdapter(self.my_doc, ICanonicalLink), None) |
---|
| 89 | |
---|
| 90 | res = self.publish(path=self.mydoc_path, basic=self.basic_auth) |
---|
| 91 | self.assertEqual(curl.search(res.getBody()), None) |
---|
| 92 | |
---|
| 93 | # register adapter back in the global site manager |
---|
| 94 | gsm.registerAdapter(DefaultCanonicalLinkAdapter, [ITraversable,], |
---|
| 95 | ICanonicalLink) |
---|
[2239] | 96 | |
---|
| 97 | def test_bug_19_23_at_plone_org(self): |
---|
| 98 | """overrides.zcml should present in the root of the package""" |
---|
| 99 | import quintagroup.seoptimizer |
---|
| 100 | try: |
---|
| 101 | zcml.load_config('overrides.zcml', quintagroup.seoptimizer) |
---|
| 102 | except IOError: |
---|
| 103 | self.fail("overrides.zcml removed from the package root") |
---|
| 104 | |
---|
[3125] | 105 | def test_escape_characters_title(self): |
---|
| 106 | """Change escape characters in title of SEO properties |
---|
| 107 | Bug url http://plone.org/products/plone-seo/issues/31 |
---|
| 108 | """ |
---|
| 109 | from cgi import escape |
---|
| 110 | title = 'New <i>Title</i>' |
---|
| 111 | form_data = {'seo_title': title, |
---|
| 112 | 'seo_title_override:int': 1, |
---|
| 113 | 'form.button.Save': "Save", |
---|
| 114 | 'form.submitted:int': 1} |
---|
| 115 | |
---|
| 116 | res = self.publish(path=self.mydoc_path+'/@@seo-context-properties', |
---|
| 117 | basic=self.basic_auth, request_method='POST', |
---|
| 118 | stdin=StringIO(urllib.urlencode(form_data))) |
---|
| 119 | html = self.publish(self.mydoc_path, self.basic_auth).getBody() |
---|
| 120 | m = re.match('.*<title>\\s*%s\\s*</title>' % escape(title), html, re.S|re.M) |
---|
| 121 | self.assert_(m, 'Title is not escaped properly.') |
---|
| 122 | |
---|
| 123 | def test_escape_characters_comment(self): |
---|
| 124 | """Change escape characters in comment of SEO properties |
---|
| 125 | """ |
---|
| 126 | from cgi import escape |
---|
| 127 | comment = 'New <i>comment</i>' |
---|
| 128 | form_data = {'seo_title': 'New Title', |
---|
| 129 | 'seo_title_override:int': 1, |
---|
| 130 | 'seo_html_comment': comment, |
---|
| 131 | 'seo_html_comment_override:int': 1, |
---|
| 132 | 'form.button.Save': "Save", |
---|
| 133 | 'form.submitted:int': 1} |
---|
| 134 | |
---|
| 135 | res = self.publish(path=self.mydoc_path+'/@@seo-context-properties', |
---|
| 136 | basic=self.basic_auth, request_method='POST', |
---|
| 137 | stdin=StringIO(urllib.urlencode(form_data))) |
---|
| 138 | html = self.publish(self.mydoc_path, self.basic_auth).getBody() |
---|
| 139 | m = re.match('.*<!--\\s*%s\\s*-->' % escape(comment), html, re.S|re.M) |
---|
| 140 | self.assert_(m, 'Comment is not escaped properly.') |
---|
| 141 | |
---|
[2440] | 142 | def test_bug_custom_metatags_update(self): |
---|
| 143 | # Prepare a page for the test |
---|
| 144 | page = self.portal["front-page"] |
---|
| 145 | request = self.portal.REQUEST |
---|
| 146 | directlyProvides(request, IPloneSEOLayer) |
---|
| 147 | seo_context_props = getMultiAdapter((page, request), name="seo-context-properties") |
---|
| 148 | # Set default custom meta tag without default value (tag name only) |
---|
| 149 | self.gseo = queryAdapter(self.portal, ISEOConfigletSchema) |
---|
| 150 | self.gseo.default_custom_metatags = ["test_tag",] |
---|
| 151 | try: |
---|
| 152 | # Breakage on updating custom metatag with seo-context-properties view |
---|
| 153 | seo_context_props.updateSEOCustomMetaTagsProperties([]) |
---|
| 154 | except IndexError: |
---|
| 155 | self.fail("Error in calculating of default tag value, when only tag name set "\ |
---|
| 156 | "in default_custom_metatags property of the configlet.") |
---|
[2247] | 157 | |
---|
[2440] | 158 | |
---|
[2252] | 159 | class TestBug24AtPloneOrg(FunctionalTestCase): |
---|
| 160 | |
---|
| 161 | def afterSetUp(self): |
---|
| 162 | # Add test users: member, editor |
---|
[2247] | 163 | member_id = 'test_member' |
---|
| 164 | editor_id = 'test_editor' |
---|
| 165 | test_pswd = 'pswd' |
---|
| 166 | uf = self.portal.acl_users |
---|
| 167 | uf.userFolderAddUser(member_id, test_pswd, |
---|
| 168 | ['Member'], []) |
---|
| 169 | uf.userFolderAddUser(editor_id, test_pswd, |
---|
| 170 | ['Member','Editor'], []) |
---|
| 171 | |
---|
[2252] | 172 | self.member_auth = '%s:%s'%(member_id, test_pswd) |
---|
| 173 | self.editor_auth = '%s:%s'%(editor_id, test_pswd) |
---|
| 174 | self.portal_url = '/'.join(self.portal.getPhysicalPath()) |
---|
[2247] | 175 | |
---|
[2252] | 176 | def test_not_break(self): |
---|
| 177 | """Default portal page should not breaks for any user""" |
---|
| 178 | # Anonymous |
---|
| 179 | resp = self.publish(path=self.portal_url) |
---|
[2247] | 180 | self.assertEqual(resp.getStatus(), 200) |
---|
[2252] | 181 | # Member |
---|
| 182 | resp = self.publish(path=self.portal_url, basic=self.member_auth) |
---|
| 183 | self.assertEqual(resp.getStatus(), 200) |
---|
| 184 | # Editor: this fails, althought must pass |
---|
| 185 | resp = self.publish(path=self.portal_url, basic=self.editor_auth) |
---|
| 186 | self.assertEqual(resp.getStatus(), 200) |
---|
[2237] | 187 | |
---|
[2252] | 188 | def test_tab_visibility(self): |
---|
| 189 | """Only Editor can view seo tab""" |
---|
| 190 | rexp = re.compile('<a\s+[^>]*' \ |
---|
| 191 | 'href="[a-zA-Z0-9\:\/_-]*/@@seo-context-properties"[^>]*>'\ |
---|
| 192 | '\s*SEO Properties\s*</a>', re.I|re.S) |
---|
| 193 | # Anonymous: NO SEO Properties link |
---|
| 194 | res = self.publish(path=self.portal_url).getBody() |
---|
| 195 | self.assertEqual(rexp.search(res), None) |
---|
| 196 | # Member: NO 'SEO Properties' link |
---|
| 197 | res = self.publish(path=self.portal_url, basic=self.member_auth).getBody() |
---|
| 198 | self.assertEqual(rexp.search(res), None) |
---|
| 199 | # Editor: PRESENT 'SEO Properties' link |
---|
| 200 | res = self.publish(path=self.portal_url, basic=self.editor_auth).getBody() |
---|
| 201 | self.assertNotEqual(rexp.search(res), None) |
---|
[2249] | 202 | |
---|
[2252] | 203 | def test_tab_access(self): |
---|
| 204 | """Only Editor can access 'SEO Properties' tab""" |
---|
| 205 | test_url = self.portal_url + '/front-page/@@seo-context-properties' |
---|
| 206 | # Anonymous: can NOT ACCESS |
---|
| 207 | headers = self.publish(path=test_url).headers |
---|
| 208 | self.assertEqual( headers.get('bobo-exception-type',""), 'Unauthorized', |
---|
| 209 | "No 'Unauthorized' exception rised for Anonymous on '@@seo-context-properties' view") |
---|
| 210 | # Member: can NOT ACCESS |
---|
| 211 | status = self.publish(path=test_url, basic=self.member_auth).headers |
---|
| 212 | self.assertEqual( headers.get('bobo-exception-type',""), 'Unauthorized', |
---|
| 213 | "No 'Unauthorized' exception rised for Member on '@@seo-context-properties' view") |
---|
| 214 | # Editor: CAN Access |
---|
| 215 | res = self.publish(path=test_url, basic=self.editor_auth) |
---|
| 216 | self.assertEqual(res.status, 200) |
---|
[2249] | 217 | |
---|
[2252] | 218 | |
---|
| 219 | def test_tab_edit(self): |
---|
| 220 | """Editor can change SEO Properties""" |
---|
| 221 | test_url = self.portal_url + '/front-page/@@seo-context-properties' |
---|
| 222 | form_data = {'seo_title': 'New Title', |
---|
| 223 | 'seo_title_override:int': 1, |
---|
| 224 | 'form.submitted:int': 1} |
---|
| 225 | res = self.publish(path=test_url, basic=self.editor_auth, |
---|
| 226 | request_method='POST', stdin=StringIO(urllib.urlencode(form_data))) |
---|
| 227 | self.assertNotEqual(res.status, 200) |
---|
| 228 | |
---|
| 229 | |
---|
[1493] | 230 | def test_suite(): |
---|
| 231 | from unittest import TestSuite, makeSuite |
---|
| 232 | suite = TestSuite() |
---|
| 233 | suite.addTest(makeSuite(TestBugs)) |
---|
[2252] | 234 | suite.addTest(makeSuite(TestBug24AtPloneOrg)) |
---|
[1493] | 235 | return suite |
---|
[2247] | 236 | |
---|