| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| |
| import json |
| import os |
| from io import BytesIO |
| import six.moves.urllib.parse |
| import six.moves.urllib.request |
| import six.moves.urllib.error |
| |
| import PIL |
| from mock import patch |
| from tg import config |
| from alluratest.tools import assert_equal, assert_in, assert_not_equal |
| from ming.orm.ormsession import ThreadLocalORMSession, session |
| from paste.httpexceptions import HTTPFound, HTTPMovedPermanently |
| from tg import app_globals as g, tmpl_context as c |
| |
| import allura |
| from allura import model as M |
| from allura.tests import TestController |
| from allura.tests import decorators as td |
| from allura.lib import helpers as h |
| from allura.lib import utils |
| from alluratest.controller import setup_trove_categories |
| |
| |
| class TestNeighborhood(TestController): |
| |
| def setUp(self): |
| super().setUp() |
| self._cleanup_audit_log() |
| |
| def tearDown(self): |
| super().tearDown() |
| self._cleanup_audit_log() |
| |
| def _cleanup_audit_log(self): |
| M.AuditLog.query.remove({}) |
| |
| def test_home_project(self): |
| r = self.app.get('/adobe/wiki/', status=301) |
| assert r.location.endswith('/adobe/wiki/Home/') |
| r = r.follow() |
| assert 'This is the "Adobe" neighborhood' in str(r), str(r) |
| r = self.app.get( |
| '/adobe/admin/', extra_environ=dict(username='test-user'), |
| status=403) |
| |
| def test_redirect(self): |
| r = self.app.post('/adobe/_admin/update', |
| params=dict(redirect='wiki/Home/'), |
| extra_environ=dict(username='root')) |
| r = self.app.get('/adobe/') |
| assert r.location.endswith('/adobe/wiki/Home/') |
| |
| @patch('allura.model.neighborhood.Neighborhood.use_wiki_page_as_root', True) |
| def test_wiki_as_home(self): |
| r = self.app.get('/adobe/', status=200) |
| assert 'This is the "Adobe" neighborhood' in str(r), str(r) |
| |
| def test_admin(self): |
| r = self.app.get('/adobe/_admin/', extra_environ=dict(username='root')) |
| r = self.app.get('/adobe/_admin/overview', |
| extra_environ=dict(username='root')) |
| r = self.app.get('/adobe/_admin/accolades', |
| extra_environ=dict(username='root')) |
| neighborhood = M.Neighborhood.query.get(name='Adobe') |
| neighborhood.features['google_analytics'] = True |
| r = self.app.post('/adobe/_admin/update', |
| params=dict(name='Mozq1', css='', |
| homepage='# MozQ1!', tracking_id='U-123456'), |
| extra_environ=dict(username='root')) |
| r = self.app.post('/adobe/_admin/update', |
| params=dict(name='Mozq1', css='', |
| homepage='# MozQ1!\n[Root]'), |
| extra_environ=dict(username='root')) |
| # make sure project_template is validated as proper json |
| r = self.app.post('/adobe/_admin/update', |
| params=dict(project_template='{'), |
| extra_environ=dict(username='root')) |
| assert 'Invalid JSON' in r |
| |
| def test_admin_overview_audit_log(self): |
| |
| def check_log(message): |
| return M.AuditLog.query.find({'message': message}).count() == 1 |
| |
| nbhd = M.Neighborhood.query.get(name='Projects') |
| nbhd.features['css'] = 'custom' |
| nbhd.features['google_analytics'] = True |
| params = { |
| 'name': 'Pjs', |
| 'redirect': 'http://fake.org/', |
| 'show_title': 'false', |
| 'allow_browse': 'false', |
| 'css': '.class { border: 1px; }', |
| 'homepage': '[Homepage]', |
| 'project_list_url': 'http://fake.org/project_list', |
| 'project_template': '{"name": "template"}', |
| 'tracking_id': 'U-123456', |
| 'prohibited_tools': 'wiki, tickets', |
| 'anchored_tools': 'wiki:Wiki', |
| } |
| |
| self.app.post('/p/_admin/update', params=params, |
| extra_environ=dict(username='root')) |
| |
| assert check_log('change neighborhood name to Pjs') |
| assert check_log('change neighborhood redirect to http://fake.org/') |
| assert check_log('change neighborhood show title to False') |
| assert check_log('change neighborhood allow browse to False') |
| assert check_log('change neighborhood css to .class { border: 1px; }') |
| assert check_log('change neighborhood homepage to [Homepage]') |
| assert check_log('change neighborhood project list url to ' |
| 'http://fake.org/project_list') |
| |
| assert check_log('change neighborhood project template to ' |
| '{"name": "template"}') |
| assert check_log('update neighborhood tracking_id') |
| assert check_log('update neighborhood prohibited tools') |
| assert check_log('update neighborhood anchored tools') |
| |
| # must get as many log records as many values are updated |
| assert M.AuditLog.query.find().count() == len(params) |
| |
| def test_prohibited_tools(self): |
| self.app.post('/p/_admin/update', |
| params=dict(name='Projects', |
| prohibited_tools='wiki, tickets'), |
| extra_environ=dict(username='root')) |
| |
| r = self.app.get('/p/_admin/overview', extra_environ=dict(username='root')) |
| assert 'wiki, tickets' in r |
| |
| c.user = M.User.query.get(username='root') |
| c.project = M.Project.query.get(shortname='test') |
| data = c.project.nav_data(admin_options=True) |
| |
| assert 'Wiki' not in data |
| assert 'Tickets' not in data |
| |
| r = self.app.post('/p/_admin/update', |
| params=dict(name='Projects', |
| prohibited_tools='wiki, test'), |
| extra_environ=dict(username='root')) |
| assert 'error' in self.webflash(r), self.webflash(r) |
| |
| @td.with_wiki |
| def test_anchored_tools(self): |
| neighborhood = M.Neighborhood.query.get(name='Projects') |
| |
| r = self.app.post('/p/_admin/update', |
| params=dict(name='Projects', |
| anchored_tools='wiki:Wiki, tickets:Ticket'), |
| extra_environ=dict(username='root')) |
| assert 'error' not in self.webflash(r) |
| r = self.app.post('/p/_admin/update', |
| params=dict(name='Projects', |
| anchored_tools='w!iki:Wiki, tickets:Ticket'), |
| extra_environ=dict(username='root')) |
| assert 'error' in self.webflash(r) |
| assert_equal(neighborhood.anchored_tools, 'wiki:Wiki, tickets:Ticket') |
| |
| r = self.app.post('/p/_admin/update', |
| params=dict(name='Projects', |
| anchored_tools='wiki:Wiki,'), |
| extra_environ=dict(username='root')) |
| assert 'error' in self.webflash(r) |
| assert_equal(neighborhood.anchored_tools, 'wiki:Wiki, tickets:Ticket') |
| |
| r = self.app.post('/p/_admin/update', |
| params=dict(name='Projects', |
| anchored_tools='badname,'), |
| extra_environ=dict(username='root')) |
| assert 'error' in self.webflash(r) |
| assert_equal(neighborhood.anchored_tools, 'wiki:Wiki, tickets:Ticket') |
| |
| r = self.app.get('/p/test/admin/overview') |
| top_nav = r.html.find(id='top_nav') |
| assert top_nav.find(href='/p/test/wiki/'), top_nav |
| assert top_nav.find(href='/p/test/tickets/'), top_nav |
| |
| c.user = M.User.query.get(username='root') |
| c.project = M.Project.query.get(shortname='test') |
| data = c.project.nav_data(admin_options=True) |
| for tool in data['menu']: |
| if tool['name'].lower() == 'wiki': |
| menu = [name['text'] for name in tool['admin_options']] |
| assert 'Delete' not in menu |
| break |
| |
| def test_show_title(self): |
| r = self.app.get('/adobe/_admin/overview', |
| extra_environ=dict(username='root')) |
| neighborhood = M.Neighborhood.query.get(name='Adobe') |
| # if not set show_title must be True |
| assert neighborhood.show_title |
| # title should be present |
| assert 'class="project_title"' in str(r) |
| r = self.app.post('/adobe/_admin/update', |
| params=dict(name='Mozq1', css='', |
| homepage='# MozQ1!', |
| tracking_id='U-123456', |
| show_title='false'), |
| extra_environ=dict(username='root')) |
| # no title now |
| r = self.app.get('/adobe/', extra_environ=dict(username='root')) |
| assert 'class="project_title"' not in str(r) |
| r = self.app.get('/adobe/wiki/Home/', |
| extra_environ=dict(username='root')) |
| assert 'class="project_title"' not in str(r) |
| |
| # title must be present on project page |
| r = self.app.get('/adobe/adobe-1/admin/', |
| extra_environ=dict(username='root')) |
| assert 'class="project_title"' in str(r) |
| |
| def test_admin_stats_del_count(self): |
| neighborhood = M.Neighborhood.query.get(name='Adobe') |
| proj = M.Project.query.get(neighborhood_id=neighborhood._id) |
| proj.deleted = True |
| ThreadLocalORMSession.flush_all() |
| r = self.app.get('/adobe/_admin/stats/', |
| extra_environ=dict(username='root')) |
| assert 'Deleted: 1' in r |
| assert 'Private: 0' in r |
| |
| def test_admin_stats_priv_count(self): |
| neighborhood = M.Neighborhood.query.get(name='Adobe') |
| proj = M.Project.query.get(neighborhood_id=neighborhood._id) |
| proj.deleted = False |
| proj.private = True |
| ThreadLocalORMSession.flush_all() |
| r = self.app.get('/adobe/_admin/stats/', |
| extra_environ=dict(username='root')) |
| assert 'Deleted: 0' in r |
| assert 'Private: 1' in r |
| |
| def test_admin_stats_adminlist(self): |
| neighborhood = M.Neighborhood.query.get(name='Adobe') |
| proj = M.Project.query.get(neighborhood_id=neighborhood._id) |
| proj.private = False |
| ThreadLocalORMSession.flush_all() |
| r = self.app.get('/adobe/_admin/stats/adminlist', |
| extra_environ=dict(username='root')) |
| pq = M.Project.query.find( |
| dict(neighborhood_id=neighborhood._id, deleted=False)) |
| pq.sort('name') |
| projects = pq.skip(0).limit(int(25)).all() |
| for proj in projects: |
| admin_role = M.ProjectRole.query.get( |
| project_id=proj.root_project._id, name='Admin') |
| if admin_role is None: |
| continue |
| user_role_list = M.ProjectRole.query.find( |
| dict(project_id=proj.root_project._id, name=None)).all() |
| for ur in user_role_list: |
| if ur.user is not None and admin_role._id in ur.roles: |
| assert proj.name in r |
| assert ur.user.username in r |
| |
| def test_icon(self): |
| file_name = 'neo-icon-set-454545-256x350.png' |
| file_path = os.path.join( |
| allura.__path__[0], 'nf', 'allura', 'images', file_name) |
| file_data = open(file_path, 'rb').read() |
| upload = ('icon', file_name, file_data) |
| |
| r = self.app.get('/adobe/_admin/', extra_environ=dict(username='root')) |
| r = self.app.post('/adobe/_admin/update', |
| params=dict(name='Mozq1', css='', |
| homepage='# MozQ1'), |
| extra_environ=dict(username='root'), upload_files=[upload]) |
| r = self.app.get('/adobe/icon') |
| image = PIL.Image.open(BytesIO(r.body)) |
| assert image.size == (48, 48) |
| |
| r = self.app.get('/adobe/icon?foo=bar') |
| |
| def test_google_analytics(self): |
| # analytics allowed |
| neighborhood = M.Neighborhood.query.get(name='Adobe') |
| neighborhood.features['google_analytics'] = True |
| r = self.app.get('/adobe/_admin/overview', |
| extra_environ=dict(username='root')) |
| assert 'Google Analytics ID' in r |
| r = self.app.get('/adobe/adobe-1/admin/overview', |
| extra_environ=dict(username='root')) |
| assert 'Google Analytics ID' in r |
| r = self.app.post('/adobe/_admin/update', |
| params=dict(name='Adobe', css='', |
| homepage='# MozQ1', tracking_id='U-123456'), |
| extra_environ=dict(username='root'), status=302) |
| r = self.app.post('/adobe/adobe-1/admin/update', |
| params=dict(tracking_id='U-654321'), |
| extra_environ=dict(username='root'), status=302) |
| r = self.app.get('/adobe/adobe-1/admin/overview', |
| extra_environ=dict(username='root')) |
| assert "_add_tracking('nbhd', 'U-123456');" in r, r |
| assert "_add_tracking('proj', 'U-654321');" in r |
| # analytics not allowed |
| neighborhood = M.Neighborhood.query.get(name='Adobe') |
| neighborhood.features['google_analytics'] = False |
| r = self.app.get('/adobe/_admin/overview', |
| extra_environ=dict(username='root')) |
| assert 'Google Analytics ID' not in r |
| r = self.app.get('/adobe/adobe-1/admin/overview', |
| extra_environ=dict(username='root')) |
| assert 'Google Analytics ID' not in r |
| r = self.app.get('/adobe/adobe-1/admin/overview', |
| extra_environ=dict(username='root')) |
| assert "_add_tracking('nbhd', 'U-123456');" not in r |
| assert "_add_tracking('proj', 'U-654321');" not in r |
| |
| def test_custom_css(self): |
| test_css = '.test{color:red;}' |
| custom_css = 'Custom CSS' |
| |
| neighborhood = M.Neighborhood.query.get(name='Adobe') |
| neighborhood.css = test_css |
| neighborhood.features['css'] = 'none' |
| r = self.app.get('/adobe/') |
| assert test_css not in r |
| r = self.app.get('/adobe/_admin/overview', |
| extra_environ=dict(username='root')) |
| assert custom_css not in r |
| |
| neighborhood = M.Neighborhood.query.get(name='Adobe') |
| neighborhood.features['css'] = 'picker' |
| r = self.app.get('/adobe/') |
| while isinstance(r.response, HTTPFound) or isinstance(r.response, HTTPMovedPermanently): |
| r = r.follow() |
| assert test_css in r |
| r = self.app.get('/adobe/_admin/overview', |
| extra_environ=dict(username='root')) |
| assert custom_css in r |
| |
| neighborhood = M.Neighborhood.query.get(name='Adobe') |
| neighborhood.features['css'] = 'custom' |
| r = self.app.get('/adobe/') |
| while isinstance(r.response, HTTPFound) or isinstance(r.response, HTTPMovedPermanently): |
| r = r.follow() |
| assert test_css in r |
| r = self.app.get('/adobe/_admin/overview', |
| extra_environ=dict(username='root')) |
| assert custom_css in r |
| |
| def test_picker_css(self): |
| neighborhood = M.Neighborhood.query.get(name='Adobe') |
| neighborhood.features['css'] = 'picker' |
| |
| r = self.app.get('/adobe/_admin/overview', |
| extra_environ=dict(username='root')) |
| assert 'Project title, font' in r |
| assert 'Project title, color' in r |
| assert 'Bar on top' in r |
| assert 'Title bar, background' in r |
| assert 'Title bar, foreground' in r |
| |
| r = self.app.post('/adobe/_admin/update', |
| params={'name': 'Adobe', |
| 'css': '', |
| 'homepage': '', |
| 'css-projecttitlefont': 'arial,sans-serif', |
| 'css-projecttitlecolor': 'green', |
| 'css-barontop': '#555555', |
| 'css-titlebarbackground': '#333', |
| 'css-titlebarcolor': '#444'}, |
| extra_environ=dict(username='root'), upload_files=[]) |
| neighborhood = M.Neighborhood.query.get(name='Adobe') |
| assert '/*projecttitlefont*/.project_title{font-family:arial,sans-serif;}' in neighborhood.css |
| assert '/*projecttitlecolor*/.project_title{color:green;}' in neighborhood.css |
| assert '/*barontop*/.pad h2.colored {background-color:#555555; background-image: none;}' in neighborhood.css |
| assert '/*titlebarbackground*/.pad h2.title{background-color:#333; background-image: none;}' in neighborhood.css |
| assert "/*titlebarcolor*/.pad h2.title, .pad h2.title small a {color:#444;}" in neighborhood.css |
| |
| def test_max_projects(self): |
| # Set max value to unlimit |
| neighborhood = M.Neighborhood.query.get(name='Projects') |
| neighborhood.features['max_projects'] = None |
| r = self.app.post('/p/register', |
| params=dict( |
| project_unixname='maxproject1', project_name='Max project1', |
| project_description='', neighborhood='Projects'), |
| antispam=True, |
| extra_environ=dict(username='root'), status=302) |
| assert '/p/maxproject1/admin' in r.location |
| |
| # Set max value to 0 |
| neighborhood = M.Neighborhood.query.get(name='Projects') |
| neighborhood.features['max_projects'] = 0 |
| r = self.app.post('/p/register', |
| params=dict( |
| project_unixname='maxproject2', project_name='Max project2', |
| project_description='', neighborhood='Projects'), |
| antispam=True, |
| extra_environ=dict(username='root')) |
| while isinstance(r.response, HTTPFound): |
| r = r.follow() |
| assert 'You have exceeded the maximum number of projects' in r |
| |
| def test_project_rate_limit(self): |
| # Set rate limit to unlimit |
| with h.push_config(config, **{'project.rate_limits': '{}'}): |
| r = self.app.post('/p/register', |
| params=dict( |
| project_unixname='rateproject1', project_name='Rate project1', |
| project_description='', neighborhood='Projects'), |
| antispam=True, |
| extra_environ=dict(username='test-user-1'), status=302) |
| assert '/p/rateproject1/admin' in r.location |
| |
| # Set rate limit to 1 in first hour of user account |
| with h.push_config(config, **{'project.rate_limits': '{"3600": 1}'}): |
| r = self.app.post('/p/register', |
| params=dict( |
| project_unixname='rateproject2', project_name='Rate project2', |
| project_description='', neighborhood='Projects'), |
| antispam=True, |
| extra_environ=dict(username='test-user-1')) |
| while isinstance(r.response, HTTPFound): |
| r = r.follow() |
| assert 'Project creation rate limit exceeded. Please try again later.' in r |
| |
| def test_project_rate_limit_admin(self): |
| # Set rate limit to unlimit |
| with h.push_config(config, **{'project.rate_limits': '{}'}): |
| r = self.app.post('/p/register', |
| params=dict( |
| project_unixname='rateproject1', project_name='Rate project1', |
| project_description='', neighborhood='Projects'), |
| antispam=True, |
| extra_environ=dict(username='root'), status=302) |
| assert '/p/rateproject1/admin' in r.location |
| |
| # Set rate limit to 1 in first hour of user account |
| with h.push_config(config, **{'project.rate_limits': '{"3600": 1}'}): |
| r = self.app.post('/p/register', |
| params=dict( |
| project_unixname='rateproject2', project_name='Rate project2', |
| project_description='', neighborhood='Projects'), |
| antispam=True, |
| extra_environ=dict(username='root')) |
| assert '/p/rateproject2/admin' in r.location |
| |
| def test_invite(self): |
| p_nbhd_id = str(M.Neighborhood.query.get(name='Projects')._id) |
| r = self.app.get('/adobe/_moderate/', |
| extra_environ=dict(username='root')) |
| r = self.app.post('/adobe/_moderate/invite', |
| params=dict(pid='adobe-1', invite='on', |
| neighborhood_id=p_nbhd_id), |
| extra_environ=dict(username='root')) |
| r = self.app.get(r.location, extra_environ=dict(username='root')) |
| assert 'error' in r |
| r = self.app.post('/adobe/_moderate/invite', |
| params=dict(pid='no_such_user', |
| invite='on', neighborhood_id=p_nbhd_id), |
| extra_environ=dict(username='root')) |
| r = self.app.get(r.location, extra_environ=dict(username='root')) |
| assert 'error' in r |
| r = self.app.post('/adobe/_moderate/invite', |
| params=dict(pid='test', invite='on', |
| neighborhood_id=p_nbhd_id), |
| extra_environ=dict(username='root')) |
| r = self.app.get(r.location, extra_environ=dict(username='root')) |
| assert 'invited' in r, r |
| assert 'warning' not in r |
| r = self.app.post('/adobe/_moderate/invite', |
| params=dict(pid='test', invite='on', |
| neighborhood_id=p_nbhd_id), |
| extra_environ=dict(username='root')) |
| r = self.app.get(r.location, extra_environ=dict(username='root')) |
| assert 'warning' in r |
| r = self.app.post('/adobe/_moderate/invite', |
| params=dict(pid='test', uninvite='on', |
| neighborhood_id=p_nbhd_id), |
| extra_environ=dict(username='root')) |
| r = self.app.get(r.location, extra_environ=dict(username='root')) |
| assert 'uninvited' in r |
| assert 'warning' not in r |
| r = self.app.post('/adobe/_moderate/invite', |
| params=dict(pid='test', uninvite='on', |
| neighborhood_id=p_nbhd_id), |
| extra_environ=dict(username='root')) |
| r = self.app.get(r.location, extra_environ=dict(username='root')) |
| assert 'warning' in r |
| r = self.app.post('/adobe/_moderate/invite', |
| params=dict(pid='test', invite='on', |
| neighborhood_id=p_nbhd_id), |
| extra_environ=dict(username='root')) |
| r = self.app.get(r.location, extra_environ=dict(username='root')) |
| assert 'invited' in r |
| assert 'warning' not in r |
| |
| def test_evict(self): |
| r = self.app.get('/adobe/_moderate/', |
| extra_environ=dict(username='root')) |
| r = self.app.post('/adobe/_moderate/evict', |
| params=dict(pid='test'), |
| extra_environ=dict(username='root')) |
| r = self.app.get(r.location, extra_environ=dict(username='root')) |
| assert 'error' in r |
| r = self.app.post('/adobe/_moderate/evict', |
| params=dict(pid='adobe-1'), |
| extra_environ=dict(username='root')) |
| r = self.app.get(r.location, extra_environ=dict(username='root')) |
| assert 'adobe-1 evicted to Projects' in r |
| |
| def test_home(self): |
| self.app.get('/adobe/') |
| |
| def test_register(self): |
| r = self.app.get('/adobe/register', status=405) |
| r = self.app.post('/adobe/register', |
| params=dict( |
| project_unixname='', project_name='Nothing', |
| project_description='', neighborhood='Adobe'), |
| antispam=True, |
| extra_environ=dict(username='root')) |
| assert r.html.find('div', {'class': 'error'} |
| ).string == 'Please use 3-15 small letters, numbers, and dashes.' |
| r = self.app.post('/adobe/register', |
| params=dict( |
| project_unixname='mymoz', project_name='My Moz', |
| project_description='', neighborhood='Adobe'), |
| antispam=True, |
| extra_environ=dict(username='*anonymous'), |
| status=302) |
| r = self.app.post('/adobe/register', |
| params=dict( |
| project_unixname='foo.mymoz', project_name='My Moz', |
| project_description='', neighborhood='Adobe'), |
| antispam=True, |
| extra_environ=dict(username='root')) |
| assert r.html.find('div', {'class': 'error'} |
| ).string == 'Please use 3-15 small letters, numbers, and dashes.' |
| r = self.app.post('/p/register', |
| params=dict( |
| project_unixname='test', project_name='Tester', |
| project_description='', neighborhood='Projects'), |
| antispam=True, |
| extra_environ=dict(username='root')) |
| assert r.html.find('div', {'class': 'error'} |
| ).string == 'This project name is taken.' |
| r = self.app.post('/adobe/register', |
| params=dict( |
| project_unixname='mymoz', project_name='My Moz', |
| project_description='', neighborhood='Adobe'), |
| antispam=True, |
| extra_environ=dict(username='root'), |
| status=302) |
| |
| def test_register_private_fails_for_anon(self): |
| r = self.app.post( |
| '/p/register', |
| params=dict( |
| project_unixname='mymoz', |
| project_name='My Moz', |
| project_description='', |
| neighborhood='Projects', |
| private_project='on'), |
| antispam=True, |
| extra_environ=dict(username='*anonymous'), |
| status=302) |
| assert config.get('auth.login_url', '/auth/') in r.location, r.location |
| |
| def test_register_private_fails_for_non_admin(self): |
| self.app.post( |
| '/p/register', |
| params=dict( |
| project_unixname='mymoz', |
| project_name='My Moz', |
| project_description='', |
| neighborhood='Projects', |
| private_project='on'), |
| antispam=True, |
| extra_environ=dict(username='test-user'), |
| status=403) |
| |
| def test_register_private_fails_for_non_private_neighborhood(self): |
| # Turn off private |
| neighborhood = M.Neighborhood.query.get(name='Projects') |
| neighborhood.features['private_projects'] = False |
| r = self.app.get('/p/add_project', extra_environ=dict(username='root')) |
| assert 'private_project' not in r |
| |
| r = self.app.post( |
| '/p/register', |
| params=dict( |
| project_unixname='myprivate1', |
| project_name='My Priv1', |
| project_description='', |
| neighborhood='Projects', |
| private_project='on'), |
| antispam=True, |
| extra_environ=dict(username='root')) |
| cookies = r.headers.getall('Set-Cookie') |
| flash_msg_cookies = list(map(six.moves.urllib.parse.unquote, cookies)) |
| |
| assert any('Internal Error' in cookie for cookie in flash_msg_cookies) |
| |
| proj = M.Project.query.get( |
| shortname='myprivate1', neighborhood_id=neighborhood._id) |
| assert proj is None |
| |
| # Turn on private |
| neighborhood = M.Neighborhood.query.get(name='Projects') |
| neighborhood.features['private_projects'] = True |
| r = self.app.get('/p/add_project', extra_environ=dict(username='root')) |
| assert 'private_project' in r |
| |
| self.app.post( |
| '/p/register', |
| params=dict( |
| project_unixname='myprivate2', |
| project_name='My Priv2', |
| project_description='', |
| neighborhood='Projects', |
| private_project='on'), |
| antispam=True, |
| extra_environ=dict(username='root')) |
| |
| proj = M.Project.query.get( |
| shortname='myprivate2', neighborhood_id=neighborhood._id) |
| assert proj.private |
| |
| def test_register_private_ok(self): |
| r = self.app.post( |
| '/p/register', |
| params=dict( |
| project_unixname='mymoz', |
| project_name='My Moz', |
| project_description='', |
| neighborhood='Projects', |
| private_project='on', |
| tools='wiki'), |
| antispam=True, |
| extra_environ=dict(username='root'), |
| status=302) |
| assert config.get('auth.login_url', |
| '/auth/') not in r.location, r.location |
| r = self.app.get( |
| '/p/mymoz/wiki/', |
| extra_environ=dict(username='root')).follow(extra_environ=dict(username='root'), status=200) |
| r = self.app.get( |
| '/p/mymoz/wiki/', |
| extra_environ=dict(username='*anonymous'), |
| status=302) |
| assert config.get('auth.login_url', '/auth/') in r.location, r.location |
| self.app.get( |
| '/p/mymoz/wiki/', |
| extra_environ=dict(username='test-user'), |
| status=403) |
| |
| def test_project_template(self): |
| setup_trove_categories() |
| icon_url = 'file://' + \ |
| os.path.join(allura.__path__[0], 'nf', 'allura', |
| 'images', 'neo-icon-set-454545-256x350.png') |
| test_groups = [{ |
| "name": "Viewer", # group will be created, all params are valid |
| "permissions": ["read"], |
| "usernames": ["user01"] |
| }, { |
| "name": "", # group won't be created - invalid name |
| "permissions": ["read"], |
| "usernames": ["user01"] |
| }, { |
| "name": "TestGroup1", # group won't be created - invalid perm name |
| "permissions": ["foobar"], |
| "usernames": ["user01"] |
| }, { |
| "name": "TestGroup2", # will be created; 'inspect' perm ignored |
| "permissions": ["read", "inspect"], |
| "usernames": ["user01", "user02"] |
| }, { |
| "name": "TestGroup3", # will be created with no users in group |
| "permissions": ["admin"] |
| }] |
| r = self.app.post('/adobe/_admin/update', params=dict(name='Mozq1', |
| css='', |
| homepage='# MozQ1!\n[Root]', |
| project_template="""{ |
| "private":true, |
| "icon":{ |
| "url":"%s", |
| "filename":"icon.png" |
| }, |
| "tools":{ |
| "wiki":{ |
| "label":"Wiki", |
| "mount_point":"wiki", |
| "options":{ |
| "show_right_bar":false, |
| "show_left_bar":false, |
| "show_discussion":false, |
| "some_url": "http://foo.com/$shortname/" |
| }, |
| "home_text":"My home text!" |
| }, |
| "discussion":{"label":"Discussion","mount_point":"discussion"}, |
| "blog":{"label":"News","mount_point":"news","options":{ |
| "show_discussion":false |
| }}, |
| "admin":{"label":"Admin","mount_point":"admin"} |
| }, |
| "tool_order":["wiki","discussion","news","admin"], |
| "labels":["mmi"], |
| "trove_cats":{ |
| "topic":[247], |
| "developmentstatus":[11] |
| }, |
| "groups": %s |
| }""" % (icon_url, json.dumps(test_groups))), |
| extra_environ=dict(username='root')) |
| r = self.app.post( |
| '/adobe/register', |
| params=dict( |
| project_unixname='testtemp', |
| project_name='Test Template', |
| project_description='', |
| neighborhood='Mozq1', |
| private_project='off'), |
| antispam=True, |
| extra_environ=dict(username='root'), |
| status=302).follow() |
| p = M.Project.query.get(shortname='testtemp') |
| # make sure the correct tools got installed in the right order |
| top_nav = r.html.find('div', {'id': 'top_nav'}).contents[1] |
| assert top_nav.contents[1].contents[1].contents[1]['href'] == '/adobe/testtemp/wiki/' |
| assert 'Wiki' in top_nav.contents[1].contents[1].contents[1].contents[0] |
| assert top_nav.contents[1].contents[3].contents[1]['href'] == '/adobe/testtemp/discussion/' |
| assert 'Discussion' in top_nav.contents[1].contents[3].contents[1].contents[0] |
| assert top_nav.contents[1].contents[5].contents[1]['href'] == '/adobe/testtemp/news/' |
| assert 'News' in top_nav.contents[1].contents[5].contents[1].contents[0] |
| assert top_nav.contents[1].contents[7].contents[1]['href'] == '/adobe/testtemp/admin/' |
| assert 'Admin' in top_nav.contents[1].contents[7].contents[1].contents[0] |
| # make sure project is private |
| r = self.app.get( |
| '/adobe/testtemp/wiki/', |
| extra_environ=dict(username='root')).follow(extra_environ=dict(username='root'), status=200) |
| r = self.app.get( |
| '/adobe/testtemp/wiki/', |
| extra_environ=dict(username='*anonymous'), |
| status=302) |
| # check the labels and trove cats |
| r = self.app.get('/adobe/testtemp/admin/trove') |
| assert 'mmi' in r |
| assert 'Communications » Telephony' in r |
| assert '5 - Production/Stable' in r |
| # check the wiki text |
| r = self.app.get('/adobe/testtemp/wiki/').follow() |
| assert "My home text!" in r |
| # check tool options |
| opts = p.app_config('wiki').options |
| assert_equal(False, opts.show_discussion) |
| assert_equal(False, opts.show_left_bar) |
| assert_equal(False, opts.show_right_bar) |
| assert_equal("http://foo.com/testtemp/", opts.some_url) |
| # check that custom groups/perms/users were setup correctly |
| roles = p.named_roles |
| for group in test_groups: |
| name = group.get('name') |
| permissions = group.get('permissions', []) |
| usernames = group.get('usernames', []) |
| if name in ('Viewer', 'TestGroup2', 'TestGroup3'): |
| role = M.ProjectRole.by_name(name, project=p) |
| # confirm role created in project |
| assert role in roles |
| for perm in permissions: |
| # confirm valid permissions added to role, and invalid |
| # permissions ignored |
| if perm in p.permissions: |
| assert M.ACE.allow(role._id, perm) in p.acl |
| else: |
| assert M.ACE.allow(role._id, perm) not in p.acl |
| # confirm valid users received role |
| for username in usernames: |
| user = M.User.by_username(username) |
| if user and user._id: |
| assert role in M.ProjectRole.by_user( |
| user, project=p).roles |
| # confirm roles with invalid json data are not created |
| if name in ('', 'TestGroup1'): |
| assert name not in roles |
| |
| def test_projects_anchored_tools(self): |
| r = self.app.post('/adobe/_admin/update', params=dict(name='Adobe', |
| css='', |
| homepage='# Adobe!\n[Root]', |
| project_template="""{ |
| "private":true, |
| "tools":{ |
| "wiki":{ |
| "label":"Wiki", |
| "mount_point":"wiki", |
| "options":{ |
| "show_right_bar":false, |
| "show_left_bar":false, |
| "show_discussion":false, |
| "some_url": "http://foo.com/$shortname/" |
| }, |
| "home_text":"My home text!" |
| }, |
| "admin":{"label":"Admin","mount_point":"admin"} |
| }, |
| "tool_order":["wiki","admin"], |
| |
| }"""), |
| extra_environ=dict(username='root')) |
| neighborhood = M.Neighborhood.query.get(name='Adobe') |
| neighborhood.anchored_tools = 'wiki:Wiki' |
| r = self.app.post( |
| '/adobe/register', |
| params=dict( |
| project_unixname='testtemp', |
| project_name='Test Template', |
| project_description='', |
| neighborhood='Adobe', |
| private_project='off'), |
| antispam=True, |
| extra_environ=dict(username='root')) |
| r = self.app.get('/adobe/testtemp/admin/overview') |
| assert r.html.find('div', id='top_nav').find( |
| 'a', href='/adobe/testtemp/wiki/'), r.html |
| assert r.html.find('div', id='top_nav').find( |
| 'a', href='/adobe/testtemp/admin/'), r.html |
| |
| def test_name_check(self): |
| for name in ('My+Moz', 'Te%st!', 'ab', 'a' * 16): |
| r = self.app.get( |
| '/p/check_names?neighborhood=Projects&project_unixname=%s' % name) |
| assert_equal( |
| r.json, |
| {'project_unixname': 'Please use 3-15 small letters, numbers, and dashes.'}) |
| r = self.app.get( |
| '/p/check_names?neighborhood=Projects&project_unixname=mymoz') |
| assert_equal(r.json, {}) |
| r = self.app.get( |
| '/p/check_names?neighborhood=Projects&project_unixname=test') |
| assert_equal(r.json, |
| {'project_unixname': 'This project name is taken.'}) |
| |
| @td.with_tool('test/sub1', 'Wiki', 'wiki') |
| def test_neighborhood_project(self): |
| self.app.get('/adobe/adobe-1/admin/', status=200) |
| self.app.get('/p/test/sub1/wiki/') |
| self.app.get('/p/test/sub1/', status=301) |
| self.app.get('/p/test/no-such-app/', status=301) |
| |
| def test_neighborhood_namespace(self): |
| # p/test exists, so try creating adobe/test |
| self.app.get('/adobe/test/wiki/', status=404) |
| r = self.app.post('/adobe/register', |
| params=dict( |
| project_unixname='test', project_name='Test again', |
| project_description='', neighborhood='Adobe', tools='wiki'), |
| antispam=True, |
| extra_environ=dict(username='root')) |
| assert r.status_int == 302, r.html.find( |
| 'div', {'class': 'error'}).string |
| assert not r.location.endswith('/add_project'), self.webflash(r) |
| r = self.app.get('/adobe/test/wiki/').follow(status=200) |
| |
| def test_neighborhood_awards(self): |
| file_name = 'adobe_icon.png' |
| file_path = os.path.join( |
| allura.__path__[0], 'public', 'nf', 'images', file_name) |
| file_data = open(file_path, 'rb').read() |
| upload = ('icon', file_name, file_data) |
| |
| r = self.app.get('/adobe/_admin/awards', |
| extra_environ=dict(username='root')) |
| r = self.app.post('/adobe/_admin/awards/create', |
| params=dict(short='FOO', full='A basic foo award'), |
| extra_environ=dict(username='root'), upload_files=[upload]) |
| r = self.app.post('/adobe/_admin/awards/create', |
| params=dict(short='BAR', |
| full='A basic bar award with no icon'), |
| extra_environ=dict(username='root')) |
| foo_id = str(M.Award.query.find(dict(short='FOO')).first()._id) |
| bar_id = str(M.Award.query.find(dict(short='BAR')).first()._id) |
| r = self.app.post('/adobe/_admin/awards/%s/update' % bar_id, |
| params=dict(short='BAR2', |
| full='Updated description.'), |
| extra_environ=dict(username='root')).follow().follow() |
| assert 'BAR2' in r |
| assert 'Updated description.' in r |
| r = self.app.get('/adobe/_admin/awards/%s' % |
| foo_id, extra_environ=dict(username='root')) |
| r = self.app.get('/adobe/_admin/awards/%s/icon' % |
| foo_id, extra_environ=dict(username='root')) |
| image = PIL.Image.open(BytesIO(r.body)) |
| assert image.size == (48, 48) |
| self.app.post('/adobe/_admin/awards/grant', |
| params=dict(grant='FOO', recipient='adobe-1', |
| url='http://award.org', comment='Winner!'), |
| extra_environ=dict(username='root')) |
| r = self.app.get('/adobe/_admin/accolades', |
| extra_environ=dict(username='root')) |
| assert_in('Winner!', r) |
| assert_in('http://award.org', r) |
| self.app.get('/adobe/_admin/awards/%s/adobe-1' % |
| foo_id, extra_environ=dict(username='root')) |
| self.app.post('/adobe/_admin/awards/%s/adobe-1/revoke' % foo_id, |
| extra_environ=dict(username='root')) |
| self.app.post('/adobe/_admin/awards/%s/delete' % foo_id, |
| extra_environ=dict(username='root')) |
| |
| def test_add_a_project_link(self): |
| from tg import tmpl_context as c |
| # Install Home tool for all neighborhoods |
| for nb in M.Neighborhood.query.find().all(): |
| p = nb.neighborhood_project |
| with h.push_config(c, user=M.User.query.get()): |
| p.install_app('home', 'home', 'Home', ordinal=0) |
| r = self.app.get('/p/') |
| assert 'Add a Project' in r |
| r = self.app.get('/u/', extra_environ=dict(username='test-user')) |
| assert 'Add a Project' not in r |
| r = self.app.get('/adobe/', extra_environ=dict(username='test-user')) |
| assert 'Add a Project' not in r |
| r = self.app.get('/u/', extra_environ=dict(username='root')) |
| assert 'Add a Project' in r |
| r = self.app.get('/adobe/', extra_environ=dict(username='root')) |
| assert 'Add a Project' in r |
| |
| def test_help(self): |
| r = self.app.get('/p/_admin/help/', |
| extra_environ=dict(username='root')) |
| assert 'macro' in r |
| |
| @td.with_user_project('test-user') |
| def test_profile_tools(self): |
| r = self.app.get('/u/test-user/', |
| extra_environ=dict(username='test-user')).follow() |
| assert r.html.select('div.profile-section.tools a[href="/u/test-user/profile/"]'), r.html |
| |
| def test_user_project_creates_on_demand(self): |
| M.User.register(dict(username='donald-duck'), make_project=False) |
| ThreadLocalORMSession.flush_all() |
| self.app.get('/u/donald-duck/') |
| |
| def test_disabled_user_has_no_user_project(self): |
| M.User.register(dict(username='donald-duck')) |
| self.app.get('/u/donald-duck/') # assert it's there |
| M.User.query.update(dict(username='donald-duck'), |
| {'$set': {'disabled': True}}) |
| self.app.get('/u/donald-duck/', status=404, extra_environ={'username': '*anonymous'}) |
| self.app.get('/u/donald-duck/', status=404, extra_environ={'username': 'test-user'}) |
| self.app.get('/u/donald-duck/', status=301, extra_environ={'username': 'test-admin'}) # site admin user |
| |
| def test_more_projects_link(self): |
| r = self.app.get('/adobe/adobe-1/admin/') |
| link = r.html.find( |
| 'div', {'class': 'neighborhood_title_link'}).find('a') |
| assert 'View More Projects' in str(link) |
| assert link['href'] == '/adobe/' |
| |
| def test_nav_json(self): |
| self.app.get('/p/_nav.json') |
| |
| |
| class TestPhoneVerificationOnProjectRegistration(TestController): |
| def test_phone_verification_fragment_renders(self): |
| self.app.get('/p/phone_verification_fragment', status=200) |
| self.app.get('/adobe/phone_verification_fragment', status=200) |
| |
| def test_verify_phone_no_params(self): |
| with h.push_config(config, **{'project.verify_phone': 'true'}): |
| self.app.get('/p/verify_phone', status=404) |
| |
| def test_verify_phone_error(self): |
| with h.push_config(config, **{'project.verify_phone': 'true'}): |
| r = self.app.get('/p/verify_phone', {'number': '1234567890'}) |
| expected = {'status': 'error', |
| 'error': 'Phone service is not configured'} |
| assert_equal(r.json, expected) |
| rid = r.session.get('phone_verification.request_id') |
| hash = r.session.get('phone_verification.number_hash') |
| assert_equal(rid, None) |
| assert_equal(hash, None) |
| |
| @patch.object(g, 'phone_service', autospec=True) |
| def test_verify_phone(self, phone_service): |
| with h.push_config(config, **{'project.verify_phone': 'true'}): |
| phone_service.verify.return_value = { |
| 'request_id': 'request-id', 'status': 'ok'} |
| r = self.app.get('/p/verify_phone', {'number': '1-555-444-3333'}) |
| phone_service.verify.assert_called_once_with('15554443333') |
| assert_equal(r.json, {'status': 'ok'}) |
| rid = r.session.get('phone_verification.request_id') |
| hash = r.session.get('phone_verification.number_hash') |
| assert_equal(rid, 'request-id') |
| assert_equal(hash, 'f9ac49faef45d18746ced08d001e23b179107940') |
| |
| @patch.object(g, 'phone_service', autospec=True) |
| def test_verify_phone_escapes_error(self, phone_service): |
| phone_service.verify.return_value = { |
| 'status': 'error', |
| 'error': '<script>alert("hacked");</script>', |
| } |
| with h.push_config(config, **{'project.verify_phone': 'true'}): |
| r = self.app.get('/p/verify_phone', {'number': '555-444-3333'}) |
| expected = { |
| 'status': 'error', |
| 'error': '<script>alert("hacked");</script>', |
| } |
| assert_equal(r.json, expected) |
| |
| @patch.object(g, 'phone_service', autospec=True) |
| def test_verify_phone_already_used(self, phone_service): |
| with h.push_config(config, **{'project.verify_phone': 'true'}): |
| u = M.User.register(dict(username='existing-user'), make_project=False) |
| u.set_tool_data('phone_verification', number_hash=utils.phone_number_hash('1-555-444-9999')) |
| session(u).flush(u) |
| phone_service.verify.return_value = {'request_id': 'request-id', 'status': 'ok'} |
| r = self.app.get('/p/verify_phone', {'number': '1-555-444-9999'}) |
| assert_equal(r.json, { |
| 'status': 'error', |
| 'error': 'That phone number has already been used.' |
| }) |
| |
| def test_check_phone_verification_no_params(self): |
| with h.push_config(config, **{'project.verify_phone': 'true'}): |
| self.app.get('/p/check_phone_verification', status=404) |
| |
| @patch.object(g, 'phone_service', autospec=True) |
| def test_check_phone_verification_error(self, phone_service): |
| with h.push_config(config, **{'project.verify_phone': 'true'}): |
| phone_service.check.return_value = {'status': 'error'} |
| req_id = 'request-id' |
| |
| # make request to verify first to initialize session |
| phone_service.verify.return_value = { |
| 'request_id': req_id, 'status': 'ok'} |
| r = self.app.get('/p/verify_phone', {'number': '1234567890'}) |
| |
| r = self.app.get('/p/check_phone_verification', {'pin': '1234'}) |
| assert_equal(r.json, {'status': 'error'}) |
| phone_service.check.assert_called_once_with(req_id, '1234') |
| |
| user = M.User.by_username('test-admin') |
| hash = user.get_tool_data('phone_verification', 'number_hash') |
| assert_equal(hash, None) |
| |
| @patch.object(g, 'phone_service', autospec=True) |
| def test_check_phone_verification_ok(self, phone_service): |
| with h.push_config(config, **{'project.verify_phone': 'true'}): |
| phone_service.check.return_value = {'status': 'ok'} |
| req_id = 'request-id' |
| |
| # make request to verify first to initialize session |
| phone_service.verify.return_value = { |
| 'request_id': req_id, 'status': 'ok'} |
| r = self.app.get('/p/verify_phone', {'number': '11234567890'}) |
| |
| r = self.app.get('/p/check_phone_verification', {'pin': '1234'}) |
| assert_equal(r.json, {'status': 'ok'}) |
| phone_service.check.assert_called_once_with(req_id, '1234') |
| |
| user = M.User.by_username('test-admin') |
| hash = user.get_tool_data('phone_verification', 'number_hash') |
| assert_equal(hash, '54c61c96d5d5aea5254c2d4f41508a938e5501b4') |
| |
| @patch.object(g, 'phone_service', autospec=True) |
| def test_check_phone_verification_escapes_error(self, phone_service): |
| phone_service.check.return_value = { |
| 'status': 'error', |
| 'error': '<script>alert("hacked");</script>', |
| } |
| with h.push_config(config, **{'project.verify_phone': 'true'}): |
| r = self.app.get('/p/check_phone_verification', {'pin': '1234'}) |
| expected = { |
| 'status': 'error', |
| 'error': '<script>alert("hacked");</script>', |
| } |
| assert_equal(r.json, expected) |
| |
| def test_register_phone_not_verified(self): |
| with h.push_config(config, **{'project.verify_phone': 'true'}): |
| r = self.app.post( |
| '/p/register', |
| params=dict( |
| project_unixname='phonetest', |
| project_name='Phone Test', |
| project_description='', |
| neighborhood='Projects'), |
| extra_environ=dict(username='test-user'), |
| antispam=True) |
| overlay = r.html.find('div', {'id': 'phone_verification_overlay'}) |
| assert_not_equal(overlay, None) |
| header = overlay.find('h2') |
| iframe = overlay.find('iframe') |
| assert_equal(header.getText(), 'Phone Verification Required') |
| assert_equal(iframe.get('src'), '/p/phone_verification_fragment') |
| |
| |
| class TestProjectImport(TestController): |
| |
| def test_not_found(self): |
| self.app.get('/p/import_project/asdf/', status=404) |
| self.app.get('/p/import_project/', status=404) |
| |
| # positive tests exist within ForgeImporter package |