You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by he...@apache.org on 2015/05/29 22:40:27 UTC
[05/45] allura git commit: [#7878] Used 2to3 to see what issues would
come up
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/unit/test_app.py
----------------------------------------------------------------------
diff --git a/tests/unit/test_app.py b/tests/unit/test_app.py
new file mode 100644
index 0000000..525f84d
--- /dev/null
+++ b/tests/unit/test_app.py
@@ -0,0 +1,118 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# 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.
+
+from unittest import TestCase
+
+from nose.tools import assert_equal
+
+from allura.app import Application
+from allura import model
+from allura.tests.unit import WithDatabase
+from allura.tests.unit.patches import fake_app_patch
+from allura.tests.unit.factories import create_project, create_app_config
+
+
+class TestApplication(TestCase):
+
+ def test_validate_mount_point(self):
+ app = Application
+ mount_point = '1.2+foo_bar'
+ self.assertIsNone(app.validate_mount_point(mount_point))
+ app.relaxed_mount_points = True
+ self.assertIsNotNone(app.validate_mount_point(mount_point))
+
+ def test_describe_permission(self):
+ class DummyApp(Application):
+ permissions_desc = {
+ 'foo': 'bar',
+ 'post': 'overridden',
+ }
+ f = DummyApp.describe_permission
+ self.assertEqual(f('foo'), 'bar')
+ self.assertEqual(f('post'), 'overridden')
+ self.assertEqual(f('admin'), 'Set permissions.')
+ self.assertEqual(f('does_not_exist'), '')
+
+
+class TestInstall(WithDatabase):
+ patches = [fake_app_patch]
+
+ def test_that_it_creates_a_discussion(self):
+ original_discussion_count = self.discussion_count()
+ install_app()
+ assert self.discussion_count() == original_discussion_count + 1
+
+ def discussion_count(self):
+ return model.Discussion.query.find().count()
+
+
+class TestDefaultDiscussion(WithDatabase):
+ patches = [fake_app_patch]
+
+ def setUp(self):
+ super(TestDefaultDiscussion, self).setUp()
+ install_app()
+ self.discussion = model.Discussion.query.get(
+ shortname='my_mounted_app')
+
+ def test_that_it_has_a_description(self):
+ description = self.discussion.description
+ assert description == 'Forum for my_mounted_app comments'
+
+ def test_that_it_has_a_name(self):
+ assert self.discussion.name == 'my_mounted_app Discussion'
+
+ def test_that_its_shortname_is_taken_from_the_project(self):
+ assert self.discussion.shortname == 'my_mounted_app'
+
+
+class TestAppDefaults(WithDatabase):
+ patches = [fake_app_patch]
+
+ def setUp(self):
+ super(TestAppDefaults, self).setUp()
+ self.app = install_app()
+
+ def test_that_it_has_an_empty_sidebar_menu(self):
+ assert self.app.sidebar_menu() == []
+
+ def test_that_it_denies_access_for_everything(self):
+ assert not self.app.has_access(model.User.anonymous(), 'any.topic')
+
+ def test_default_sitemap(self):
+ assert self.app.sitemap[0].label == 'My Mounted App'
+ assert self.app.sitemap[0].url == '.'
+
+ def test_not_exportable_by_default(self):
+ assert not self.app.exportable
+
+ def test_email_address(self):
+ self.app.url = '/p/project/mount-point/'
+ assert_equal(self.app.email_address, 'mount-point@project.p.in.localhost')
+
+
+def install_app():
+ project = create_project('myproject')
+ app_config = create_app_config(project, 'my_mounted_app')
+ # XXX: Remove project argument to install; it's redundant
+ app = Application(project, app_config)
+ app.install(project)
+ return app
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/unit/test_artifact.py
----------------------------------------------------------------------
diff --git a/tests/unit/test_artifact.py b/tests/unit/test_artifact.py
new file mode 100644
index 0000000..d030fed
--- /dev/null
+++ b/tests/unit/test_artifact.py
@@ -0,0 +1,33 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# 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 unittest
+
+from allura import model as M
+
+
+class TestArtifact(unittest.TestCase):
+
+ def test_translate_query(self):
+ fields = {'name_t': '', 'shortname_s': ''}
+ query = 'name:1 AND shortname:2 AND shortname_name_field:3'
+ q = M.Artifact.translate_query(query, fields)
+ self.assertEqual(q, 'name_t:1 AND shortname_s:2 AND shortname_name_field:3')
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/unit/test_discuss.py
----------------------------------------------------------------------
diff --git a/tests/unit/test_discuss.py b/tests/unit/test_discuss.py
new file mode 100644
index 0000000..4370f09
--- /dev/null
+++ b/tests/unit/test_discuss.py
@@ -0,0 +1,43 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# 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.
+
+from nose.tools import assert_false, assert_true
+
+from allura import model as M
+from allura.tests.unit import WithDatabase
+from allura.tests.unit.patches import fake_app_patch
+
+
+class TestThread(WithDatabase):
+ patches = [fake_app_patch]
+
+ def test_should_update_index(self):
+ p = M.Thread()
+ assert_false(p.should_update_index({}, {}))
+ old = {'num_views': 1}
+ new = {'num_views': 2}
+ assert_false(p.should_update_index(old, new))
+ old = {'num_views': 1, 'a': 1}
+ new = {'num_views': 2, 'a': 1}
+ assert_false(p.should_update_index(old, new))
+ old = {'num_views': 1, 'a': 1}
+ new = {'num_views': 2, 'a': 2}
+ assert_true(p.should_update_index(old, new))
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/unit/test_helpers/__init__.py
----------------------------------------------------------------------
diff --git a/tests/unit/test_helpers/__init__.py b/tests/unit/test_helpers/__init__.py
new file mode 100644
index 0000000..144e298
--- /dev/null
+++ b/tests/unit/test_helpers/__init__.py
@@ -0,0 +1,16 @@
+# 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.
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/unit/test_helpers/test_ago.py
----------------------------------------------------------------------
diff --git a/tests/unit/test_helpers/test_ago.py b/tests/unit/test_helpers/test_ago.py
new file mode 100644
index 0000000..ba1af6d
--- /dev/null
+++ b/tests/unit/test_helpers/test_ago.py
@@ -0,0 +1,68 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# 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.
+
+from mock import patch
+
+from datetime import datetime
+from allura.lib import helpers
+
+
+class TestAgo:
+
+ def setUp(self):
+ self.start_time = datetime(2010, 1, 1, 0, 0, 0)
+
+ def test_that_empty_times_are_phrased_in_minutes(self):
+ self.assertTimeSince('0 minutes ago', 2010, 1, 1, 0, 0, 0)
+
+ def test_that_partial_minutes_are_rounded(self):
+ self.assertTimeSince('less than 1 minute ago', 2010, 1, 1, 0, 0, 29)
+ self.assertTimeSince('1 minute ago', 2010, 1, 1, 0, 0, 31)
+
+ def test_that_minutes_are_rounded(self):
+ self.assertTimeSince('1 minute ago', 2010, 1, 1, 0, 1, 29)
+ self.assertTimeSince('2 minutes ago', 2010, 1, 1, 0, 1, 31)
+
+ def test_that_hours_are_rounded(self):
+ self.assertTimeSince('1 hour ago', 2010, 1, 1, 1, 29, 0)
+ self.assertTimeSince('2 hours ago', 2010, 1, 1, 1, 31, 0)
+
+ def test_that_days_are_rounded(self):
+ self.assertTimeSince('1 day ago', 2010, 1, 2, 11, 0, 0)
+ self.assertTimeSince('2 days ago', 2010, 1, 2, 13, 0, 0)
+
+ def test_that_months_are_rounded(self):
+ self.assertTimeSince('2010-01-01', 2010, 2, 8, 0, 0, 0)
+ self.assertTimeSince('2010-01-01', 2010, 2, 9, 0, 0, 0)
+ self.assertTimeSince('2010-01-01', 2010, 2, 20, 0, 0, 0)
+
+ def test_that_years_are_rounded(self):
+ self.assertTimeSince('2010-01-01', 2011, 6, 1, 0, 0, 0)
+ self.assertTimeSince('2010-01-01', 2011, 8, 1, 0, 0, 0)
+
+ def assertTimeSince(self, time_string, *time_components):
+ assert time_string == self.time_since(*time_components)
+
+ def time_since(self, *time_components):
+ end_time = datetime(*time_components)
+ with patch('allura.lib.helpers.datetime') as datetime_class:
+ datetime_class.utcnow.return_value = end_time
+ return helpers.ago(self.start_time)
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/unit/test_helpers/test_set_context.py
----------------------------------------------------------------------
diff --git a/tests/unit/test_helpers/test_set_context.py b/tests/unit/test_helpers/test_set_context.py
new file mode 100644
index 0000000..1ec6756
--- /dev/null
+++ b/tests/unit/test_helpers/test_set_context.py
@@ -0,0 +1,121 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# 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.
+
+from nose.tools import assert_raises
+from pylons import tmpl_context as c
+from bson import ObjectId
+
+from allura.lib.helpers import set_context
+from allura.lib.exceptions import NoSuchProjectError, NoSuchNeighborhoodError
+from allura.tests.unit import WithDatabase
+from allura.tests.unit import patches
+from allura.tests.unit.factories import (create_project,
+ create_app_config,
+ create_neighborhood)
+
+
+class TestWhenProjectIsFoundAndAppIsNot(WithDatabase):
+
+ def setUp(self):
+ super(TestWhenProjectIsFoundAndAppIsNot, self).setUp()
+ self.myproject = create_project('myproject')
+ set_context('myproject', neighborhood=self.myproject.neighborhood)
+
+ def test_that_it_sets_the_project(self):
+ assert c.project is self.myproject
+
+ def test_that_it_sets_the_app_to_none(self):
+ assert c.app is None, c.app
+
+
+class TestWhenProjectIsFoundInNeighborhood(WithDatabase):
+
+ def setUp(self):
+ super(TestWhenProjectIsFoundInNeighborhood, self).setUp()
+ self.myproject = create_project('myproject')
+ set_context('myproject', neighborhood=self.myproject.neighborhood)
+
+ def test_that_it_sets_the_project(self):
+ assert c.project is self.myproject
+
+ def test_that_it_sets_the_app_to_none(self):
+ assert c.app is None
+
+
+class TestWhenAppIsFoundByID(WithDatabase):
+ patches = [patches.project_app_loading_patch]
+
+ def setUp(self):
+ super(TestWhenAppIsFoundByID, self).setUp()
+ self.myproject = create_project('myproject')
+ self.app_config = create_app_config(self.myproject, 'my_mounted_app')
+ set_context('myproject', app_config_id=self.app_config._id,
+ neighborhood=self.myproject.neighborhood)
+
+ def test_that_it_sets_the_app(self):
+ assert c.app is self.fake_app
+
+ def test_that_it_gets_the_app_by_its_app_config(self):
+ self.project_app_instance_function.assert_called_with(self.app_config)
+
+
+class TestWhenAppIsFoundByMountPoint(WithDatabase):
+ patches = [patches.project_app_loading_patch]
+
+ def setUp(self):
+ super(TestWhenAppIsFoundByMountPoint, self).setUp()
+ self.myproject = create_project('myproject')
+ self.app_config = create_app_config(self.myproject, 'my_mounted_app')
+ set_context('myproject', mount_point='my_mounted_app',
+ neighborhood=self.myproject.neighborhood)
+
+ def test_that_it_sets_the_app(self):
+ assert c.app is self.fake_app
+
+ def test_that_it_gets_the_app_by_its_mount_point(self):
+ self.project_app_instance_function.assert_called_with(
+ 'my_mounted_app')
+
+
+class TestWhenProjectIsNotFound(WithDatabase):
+
+ def test_that_it_raises_an_exception(self):
+ nbhd = create_neighborhood()
+ assert_raises(NoSuchProjectError,
+ set_context,
+ 'myproject',
+ neighborhood=nbhd)
+
+ def test_proper_exception_when_id_lookup(self):
+ create_neighborhood()
+ assert_raises(NoSuchProjectError,
+ set_context,
+ ObjectId(),
+ neighborhood=None)
+
+
+class TestWhenNeighborhoodIsNotFound(WithDatabase):
+
+ def test_that_it_raises_an_exception(self):
+ assert_raises(NoSuchNeighborhoodError,
+ set_context,
+ 'myproject',
+ neighborhood='myneighborhood')
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/unit/test_ldap_auth_provider.py
----------------------------------------------------------------------
diff --git a/tests/unit/test_ldap_auth_provider.py b/tests/unit/test_ldap_auth_provider.py
new file mode 100644
index 0000000..de60939
--- /dev/null
+++ b/tests/unit/test_ldap_auth_provider.py
@@ -0,0 +1,156 @@
+# -*- coding: utf-8 -*-
+
+# 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.
+
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+import calendar
+from datetime import datetime, timedelta
+from bson import ObjectId
+from mock import patch, Mock
+from nose.tools import assert_equal, assert_not_equal, assert_true
+from webob import Request
+from ming.orm.ormsession import ThreadLocalORMSession
+from tg import config
+
+from alluratest.controller import setup_basic_test
+from allura.lib import plugin
+from allura.lib import helpers as h
+from allura import model as M
+
+
+class TestLdapAuthenticationProvider(object):
+
+ def setUp(self):
+ setup_basic_test()
+ self.provider = plugin.LdapAuthenticationProvider(Request.blank('/'))
+
+ def test_password_encoder(self):
+ # Verify salt
+ ep = self.provider._encode_password
+ # Note: OSX uses a crypt library with a known issue relating the hashing algorithms.
+ assert_not_equal(ep('test_pass'), ep('test_pass'))
+ assert_equal(ep('test_pass', '0000'), ep('test_pass', '0000'))
+ # Test password format
+ assert_true(ep('pwd').startswith('{CRYPT}$6$rounds=6000$'))
+
+ @patch('allura.lib.plugin.ldap')
+ def test_set_password(self, ldap):
+ user = Mock(username='test-user')
+ user.__ming__ = Mock()
+ self.provider._encode_password = Mock(return_value='new-pass-hash')
+ ldap.dn.escape_dn_chars = lambda x: x
+
+ dn = 'uid=%s,ou=people,dc=localdomain' % user.username
+ self.provider.set_password(user, 'old-pass', 'new-pass')
+ ldap.initialize.assert_called_once_with('ldaps://localhost/')
+ connection = ldap.initialize.return_value
+ connection.bind_s.called_once_with(dn, 'old-pass')
+ connection.modify_s.assert_called_once_with(
+ dn, [(ldap.MOD_REPLACE, 'userPassword', 'new-pass-hash')])
+ connection.unbind_s.assert_called_once()
+
+ @patch('allura.lib.plugin.ldap')
+ def test_login(self, ldap):
+ params = {
+ 'username': 'test-user',
+ 'password': 'test-password',
+ }
+ self.provider.request.method = 'POST'
+ self.provider.request.body = '&'.join(['%s=%s' % (k,v) for k,v in params.items()])
+ ldap.dn.escape_dn_chars = lambda x: x
+
+ self.provider._login()
+
+ dn = 'uid=%s,ou=people,dc=localdomain' % params['username']
+ ldap.initialize.assert_called_once_with('ldaps://localhost/')
+ connection = ldap.initialize.return_value
+ connection.bind_s.called_once_with(dn, 'test-password')
+ connection.unbind_s.assert_called_once()
+
+ @patch('allura.lib.plugin.ldap')
+ def test_login_autoregister(self, ldap):
+ # covers ldap get_pref too, via the display_name fetch
+ params = {
+ 'username': 'abc32590wr38',
+ 'password': 'test-password',
+ }
+ self.provider.request.method = 'POST'
+ self.provider.request.body = '&'.join(['%s=%s' % (k,v) for k,v in params.items()])
+ ldap.dn.escape_dn_chars = lambda x: x
+ dn = 'uid=%s,ou=people,dc=localdomain' % params['username']
+ conn = ldap.initialize.return_value
+ conn.search_s.return_value = [(dn, {'cn': ['åℒƒ'.encode('utf-8')]})]
+
+ self.provider._login()
+
+ user = M.User.query.get(username=params['username'])
+ assert user
+ assert_equal(user.display_name, 'åℒƒ')
+
+ @patch('allura.lib.plugin.modlist')
+ @patch('allura.lib.plugin.ldap')
+ def test_register_user(self, ldap, modlist):
+ user_doc = {
+ 'username': 'new-user',
+ 'display_name': 'New User',
+ 'password': 'new-password',
+ }
+ ldap.dn.escape_dn_chars = lambda x: x
+ self.provider._encode_password = Mock(return_value='new-password-hash')
+
+ assert_equal(M.User.query.get(username=user_doc['username']), None)
+ with h.push_config(config, **{'auth.ldap.autoregister': 'false'}):
+ self.provider.register_user(user_doc)
+ ThreadLocalORMSession.flush_all()
+ assert_not_equal(M.User.query.get(username=user_doc['username']), None)
+
+ dn = 'uid=%s,ou=people,dc=localdomain' % user_doc['username']
+ ldap.initialize.assert_called_once_with('ldaps://localhost/')
+ connection = ldap.initialize.return_value
+ connection.bind_s.called_once_with(
+ 'cn=admin,dc=localdomain',
+ 'admin-password')
+ connection.add_s.assert_called_once_with(dn, modlist.addModlist.return_value)
+ connection.unbind_s.assert_called_once()
+
+ @patch('allura.lib.plugin.ldap')
+ @patch('allura.lib.plugin.datetime', autospec=True)
+ def test_set_password_sets_last_updated(self, dt_mock, ldap):
+ user = Mock()
+ user.__ming__ = Mock()
+ user.last_password_updated = None
+ self.provider.set_password(user, None, 'new')
+ assert_equal(user.last_password_updated, dt_mock.utcnow.return_value)
+
+ def test_get_last_password_updated_not_set(self):
+ user = Mock()
+ user._id = ObjectId()
+ user.last_password_updated = None
+ upd = self.provider.get_last_password_updated(user)
+ gen_time = datetime.utcfromtimestamp(
+ calendar.timegm(user._id.generation_time.utctimetuple()))
+ assert_equal(upd, gen_time)
+
+ def test_get_last_password_updated(self):
+ user = Mock()
+ user.last_password_updated = datetime(2014, 0o6, 0o4, 13, 13, 13)
+ upd = self.provider.get_last_password_updated(user)
+ assert_equal(upd, user.last_password_updated)
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/unit/test_mixins.py
----------------------------------------------------------------------
diff --git a/tests/unit/test_mixins.py b/tests/unit/test_mixins.py
new file mode 100644
index 0000000..6000654
--- /dev/null
+++ b/tests/unit/test_mixins.py
@@ -0,0 +1,91 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# 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.
+
+from mock import Mock
+from allura.model import VotableArtifact
+
+
+class TestVotableArtifact(object):
+
+ def setUp(self):
+ self.user1 = Mock()
+ self.user1.username = 'test-user'
+ self.user2 = Mock()
+ self.user2.username = 'user2'
+
+ def test_vote_up(self):
+ vote = VotableArtifact()
+
+ vote.vote_up(self.user1)
+ assert vote.votes_up == 1
+ assert vote.votes_up_users == [self.user1.username]
+
+ vote.vote_up(self.user2)
+ assert vote.votes_up == 2
+ assert vote.votes_up_users == [self.user1.username,
+ self.user2.username]
+
+ vote.vote_up(self.user1) # unvote user1
+ assert vote.votes_up == 1
+ assert vote.votes_up_users == [self.user2.username]
+
+ assert vote.votes_down == 0, 'vote_down must be 0 if we voted up only'
+ assert len(vote.votes_down_users) == 0
+
+ def test_vote_down(self):
+ vote = VotableArtifact()
+
+ vote.vote_down(self.user1)
+ assert vote.votes_down == 1
+ assert vote.votes_down_users == [self.user1.username]
+
+ vote.vote_down(self.user2)
+ assert vote.votes_down == 2
+ assert vote.votes_down_users == [self.user1.username,
+ self.user2.username]
+
+ vote.vote_down(self.user1) # unvote user1
+ assert vote.votes_down == 1
+ assert vote.votes_down_users == [self.user2.username]
+
+ assert vote.votes_up == 0, 'vote_up must be 0 if we voted down only'
+ assert len(vote.votes_up_users) == 0
+
+ def test_change_vote(self):
+ vote = VotableArtifact()
+
+ vote.vote_up(self.user1)
+ vote.vote_down(self.user1)
+
+ assert vote.votes_down == 1
+ assert vote.votes_down_users == [self.user1.username]
+ assert vote.votes_up == 0
+ assert len(vote.votes_up_users) == 0
+
+ def test_json(self):
+ vote = VotableArtifact()
+ assert vote.__json__() == {'votes_up': 0, 'votes_down': 0}
+
+ vote.vote_down(self.user1)
+ assert vote.__json__() == {'votes_up': 0, 'votes_down': 1}
+
+ vote.vote_up(self.user2)
+ assert vote.__json__() == {'votes_up': 1, 'votes_down': 1}
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/unit/test_package_path_loader.py
----------------------------------------------------------------------
diff --git a/tests/unit/test_package_path_loader.py b/tests/unit/test_package_path_loader.py
new file mode 100644
index 0000000..8ebad7b
--- /dev/null
+++ b/tests/unit/test_package_path_loader.py
@@ -0,0 +1,232 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# 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.
+
+from unittest import TestCase
+
+import jinja2
+from nose.tools import assert_equal, assert_raises
+import mock
+from tg import config
+
+from allura.lib.package_path_loader import PackagePathLoader
+
+
+class TestPackagePathLoader(TestCase):
+
+ @mock.patch('pkg_resources.resource_filename')
+ @mock.patch('pkg_resources.iter_entry_points')
+ def test_load_paths(self, iter_entry_points, resource_filename):
+ eps = iter_entry_points.return_value.__iter__.return_value = [
+ mock.Mock(ep_name='ep0', module_name='eps.ep0'),
+ mock.Mock(ep_name='ep1', module_name='eps.ep1'),
+ mock.Mock(ep_name='ep2', module_name='eps.ep2'),
+ ]
+ for ep in eps:
+ ep.name = ep.ep_name
+ resource_filename.side_effect = lambda m, r: 'path:' + m
+
+ paths = PackagePathLoader()._load_paths()
+
+ assert_equal(paths, [
+ ['site-theme', None],
+ ['ep0', 'path:eps.ep0'],
+ ['ep1', 'path:eps.ep1'],
+ ['ep2', 'path:eps.ep2'],
+ ['allura', '/'],
+ ])
+ assert_equal(type(paths[0]), list)
+ assert_equal(resource_filename.call_args_list, [
+ mock.call('eps.ep0', ''),
+ mock.call('eps.ep1', ''),
+ mock.call('eps.ep2', ''),
+ ])
+
+ @mock.patch('pkg_resources.iter_entry_points')
+ def test_load_rules(self, iter_entry_points):
+ eps = iter_entry_points.return_value.__iter__.return_value = [
+ mock.Mock(ep_name='ep0', rules=[('>', 'allura')]),
+ mock.Mock(ep_name='ep1', rules=[('=', 'allura')]),
+ mock.Mock(ep_name='ep2', rules=[('<', 'allura')]),
+ ]
+ for ep in eps:
+ ep.name = ep.ep_name
+ ep.load.return_value.template_path_rules = ep.rules
+
+ order_rules, replacement_rules = PackagePathLoader()._load_rules()
+
+ assert_equal(order_rules, [('ep0', 'allura'), ('allura', 'ep2')])
+ assert_equal(replacement_rules, {'allura': 'ep1'})
+
+ eps = iter_entry_points.return_value.__iter__.return_value = [
+ mock.Mock(ep_name='ep0', rules=[('?', 'allura')]),
+ ]
+ for ep in eps:
+ ep.name = ep.ep_name
+ ep.load.return_value.template_path_rules = ep.rules
+ assert_raises(jinja2.TemplateError, PackagePathLoader()._load_rules)
+
+ def test_replace_signposts(self):
+ ppl = PackagePathLoader()
+ ppl._replace_signpost = mock.Mock()
+ paths = [
+ ['site-theme', None],
+ ['ep0', '/ep0'],
+ ['ep1', '/ep1'],
+ ['ep2', '/ep2'],
+ ['allura', '/'],
+ ]
+ rules = {
+ 'allura': 'ep2',
+ 'site-theme': 'ep1',
+ 'foo': 'ep1',
+ 'ep0': 'bar',
+ }
+
+ ppl._replace_signposts(paths, rules)
+
+ assert_equal(paths, [
+ ['site-theme', '/ep1'],
+ ['ep0', '/ep0'],
+ ['allura', '/ep2'],
+ ])
+
+ def test_sort_paths(self):
+ paths = [
+ ['site-theme', None],
+ ['ep0', '/ep0'],
+ ['ep1', '/ep1'],
+ ['ep2', '/ep2'],
+ ['ep3', '/ep3'],
+ ['allura', '/'],
+ ]
+ rules = [
+ ('allura', 'ep0'),
+ ('ep3', 'ep1'),
+ ('ep2', 'ep1'),
+ ('ep4', 'ep1'), # rules referencing missing paths
+ ('ep2', 'ep5'),
+ ]
+
+ PackagePathLoader()._sort_paths(paths, rules)
+
+ assert_equal(paths, [
+ ['site-theme', None],
+ ['ep2', '/ep2'],
+ ['ep3', '/ep3'],
+ ['ep1', '/ep1'],
+ ['allura', '/'],
+ ['ep0', '/ep0'],
+ ])
+
+ def test_init_paths(self):
+ paths = [
+ ['root', '/'],
+ ['none', None],
+ ['tail', '/tail'],
+ ]
+ ppl = PackagePathLoader()
+ ppl._load_paths = mock.Mock(return_value=paths)
+ ppl._load_rules = mock.Mock(return_value=('order_rules', 'repl_rules'))
+ ppl._replace_signposts = mock.Mock()
+ ppl._sort_paths = mock.Mock()
+
+ output = ppl.init_paths()
+
+ ppl._load_paths.assert_called_once_with()
+ ppl._load_rules.assert_called_once_with()
+ ppl._sort_paths.assert_called_once_with(paths, 'order_rules')
+ ppl._replace_signposts.assert_called_once_with(paths, 'repl_rules')
+
+ assert_equal(output, ['/', '/tail'])
+
+ @mock.patch('jinja2.FileSystemLoader')
+ def test_fs_loader(self, FileSystemLoader):
+ ppl = PackagePathLoader()
+ ppl.init_paths = mock.Mock(return_value=['path1', 'path2'])
+ FileSystemLoader.return_value = 'fs_loader'
+
+ output1 = ppl.fs_loader
+ output2 = ppl.fs_loader
+
+ ppl.init_paths.assert_called_once_with()
+ FileSystemLoader.assert_called_once_with(['path1', 'path2'])
+ assert_equal(output1, 'fs_loader')
+ assert output1 is output2
+
+ @mock.patch.dict(config, {'disable_template_overrides': False})
+ @mock.patch('jinja2.FileSystemLoader')
+ def test_get_source(self, fs_loader):
+ ppl = PackagePathLoader()
+ ppl.init_paths = mock.Mock()
+ fs_loader().get_source.return_value = 'fs_load'
+
+ # override exists
+ output = ppl.get_source('env', 'allura.ext.admin:templates/audit.html')
+
+ assert_equal(output, 'fs_load')
+ fs_loader().get_source.assert_called_once_with(
+ 'env', 'override/allura/ext/admin/templates/audit.html')
+
+ fs_loader().get_source.reset_mock()
+ fs_loader().get_source.side_effect = [
+ jinja2.TemplateNotFound('test'), 'fs_load']
+
+ with mock.patch('pkg_resources.resource_filename') as rf:
+ rf.return_value = 'resource'
+ # no override, ':' in template
+ output = ppl.get_source(
+ 'env', 'allura.ext.admin:templates/audit.html')
+ rf.assert_called_once_with(
+ 'allura.ext.admin', 'templates/audit.html')
+
+ assert_equal(output, 'fs_load')
+ assert_equal(fs_loader().get_source.call_count, 2)
+ fs_loader().get_source.assert_called_with('env', 'resource')
+
+ fs_loader().get_source.reset_mock()
+ fs_loader().get_source.side_effect = [
+ jinja2.TemplateNotFound('test'), 'fs_load']
+
+ # no override, ':' not in template
+ output = ppl.get_source('env', 'templates/audit.html')
+
+ assert_equal(output, 'fs_load')
+ assert_equal(fs_loader().get_source.call_count, 2)
+ fs_loader().get_source.assert_called_with(
+ 'env', 'templates/audit.html')
+
+ @mock.patch('jinja2.FileSystemLoader')
+ def test_override_disable(self, fs_loader):
+ ppl = PackagePathLoader()
+ ppl.init_paths = mock.Mock()
+ fs_loader().get_source.side_effect = jinja2.TemplateNotFound('test')
+
+ assert_raises(
+ jinja2.TemplateError,
+ ppl.get_source, 'env', 'allura.ext.admin:templates/audit.html')
+ assert_equal(fs_loader().get_source.call_count, 1)
+ fs_loader().get_source.reset_mock()
+
+ with mock.patch.dict(config, {'disable_template_overrides': False}):
+ assert_raises(
+ jinja2.TemplateError,
+ ppl.get_source, 'env', 'allura.ext.admin:templates/audit.html')
+ assert_equal(fs_loader().get_source.call_count, 2)
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/unit/test_post_model.py
----------------------------------------------------------------------
diff --git a/tests/unit/test_post_model.py b/tests/unit/test_post_model.py
new file mode 100644
index 0000000..b0d8de9
--- /dev/null
+++ b/tests/unit/test_post_model.py
@@ -0,0 +1,56 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# 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.
+
+from pylons import tmpl_context as c
+
+from allura.lib import helpers as h
+from allura import model as M
+from allura.tests.unit import WithDatabase
+from allura.tests.unit import patches
+from allura.tests.unit.factories import create_post
+
+
+class TestPostModel(WithDatabase):
+ patches = [patches.fake_app_patch,
+ patches.disable_notifications_patch]
+
+ def setUp(self):
+ super(TestPostModel, self).setUp()
+ self.post = create_post('mypost')
+
+ def test_that_it_is_pending_by_default(self):
+ assert self.post.status == 'pending'
+
+ def test_that_it_can_be_approved(self):
+ with h.push_config(c, user=M.User()):
+ self.post.approve()
+ assert self.post.status == 'ok'
+
+ def test_activity_extras(self):
+ self.post.text = """\
+This is a **bold thing**, 40 chars here.
+* Here's the first item in our list.
+* And here's the second item."""
+ assert 'allura_id' in self.post.activity_extras
+ summary = self.post.activity_extras['summary']
+ assert summary == "This is a bold thing, 40 chars here. " + \
+ "Here's the first item in our list. " + \
+ "And here's..."
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/unit/test_project.py
----------------------------------------------------------------------
diff --git a/tests/unit/test_project.py b/tests/unit/test_project.py
new file mode 100644
index 0000000..0315e45
--- /dev/null
+++ b/tests/unit/test_project.py
@@ -0,0 +1,105 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# 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 unittest
+from mock import Mock
+
+from allura import model as M
+from allura.app import SitemapEntry
+
+
+class TestProject(unittest.TestCase):
+
+ def test_grouped_navbar_entries(self):
+ p = M.Project()
+ sitemap_entries = [
+ SitemapEntry('bugs', url='bugs url', tool_name='Tickets'),
+ SitemapEntry('wiki', url='wiki url', tool_name='Wiki'),
+ SitemapEntry('discuss', url='discuss url', tool_name='Discussion'),
+ SitemapEntry('subproject', url='subproject url'),
+ SitemapEntry('features', url='features url', tool_name='Tickets'),
+ SitemapEntry('help', url='help url', tool_name='Discussion'),
+ SitemapEntry('support reqs', url='support url',
+ tool_name='Tickets'),
+ ]
+ p.url = Mock(return_value='proj_url/')
+ p.sitemap = Mock(return_value=sitemap_entries)
+ entries = p.grouped_navbar_entries()
+ expected = [
+ ('Tickets \u25be', 'proj_url/_list/tickets', 3),
+ ('wiki', 'wiki url', 0),
+ ('Discussion \u25be', 'proj_url/_list/discussion', 2),
+ ('subproject', 'subproject url', 0),
+ ]
+ expected_ticket_urls = ['bugs url', 'features url', 'support url']
+ actual = [(e.label, e.url, len(e.matching_urls)) for e in entries]
+ self.assertEqual(expected, actual)
+ self.assertEqual(entries[0].matching_urls, expected_ticket_urls)
+
+ def test_grouped_navbar_threshold(self):
+ p = M.Project()
+ sitemap_entries = [
+ SitemapEntry('bugs', url='bugs url', tool_name='Tickets'),
+ SitemapEntry('wiki', url='wiki url', tool_name='Wiki'),
+ SitemapEntry('discuss', url='discuss url', tool_name='Discussion'),
+ SitemapEntry('subproject', url='subproject url'),
+ SitemapEntry('features', url='features url', tool_name='Tickets'),
+ SitemapEntry('help', url='help url', tool_name='Discussion'),
+ SitemapEntry('support reqs', url='support url',
+ tool_name='Tickets'),
+ ]
+ p.url = Mock(return_value='proj_url/')
+ p.sitemap = Mock(return_value=sitemap_entries)
+ p.tool_data['allura'] = {'grouping_threshold': 2}
+ entries = p.grouped_navbar_entries()
+ expected = [
+ ('Tickets \u25be', 'proj_url/_list/tickets', 3),
+ ('wiki', 'wiki url', 0),
+ ('discuss', 'discuss url', 0),
+ ('subproject', 'subproject url', 0),
+ ('help', 'help url', 0),
+ ]
+ expected_ticket_urls = ['bugs url', 'features url', 'support url']
+ actual = [(e.label, e.url, len(e.matching_urls)) for e in entries]
+ self.assertEqual(expected, actual)
+ self.assertEqual(entries[0].matching_urls, expected_ticket_urls)
+
+ def test_social_account(self):
+ p = M.Project()
+ self.assertIsNone(p.social_account('Twitter'))
+
+ p.set_social_account('Twitter', 'http://twitter.com/allura')
+ self.assertEqual(p.social_account('Twitter')
+ .accounturl, 'http://twitter.com/allura')
+ self.assertEqual(p.twitter_handle, 'http://twitter.com/allura')
+
+ def test_should_update_index(self):
+ p = M.Project()
+ self.assertFalse(p.should_update_index({}, {}))
+ old = {'last_updated': 1}
+ new = {'last_updated': 2}
+ self.assertFalse(p.should_update_index(old, new))
+ old = {'last_updated': 1, 'a': 1}
+ new = {'last_updated': 2, 'a': 1}
+ self.assertFalse(p.should_update_index(old, new))
+ old = {'last_updated': 1, 'a': 1}
+ new = {'last_updated': 2, 'a': 2}
+ self.assertTrue(p.should_update_index(old, new))
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/unit/test_repo.py
----------------------------------------------------------------------
diff --git a/tests/unit/test_repo.py b/tests/unit/test_repo.py
new file mode 100644
index 0000000..2197586
--- /dev/null
+++ b/tests/unit/test_repo.py
@@ -0,0 +1,361 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# 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 datetime
+import unittest
+from mock import patch, Mock, MagicMock
+from nose.tools import assert_equal
+from datadiff import tools as dd
+
+from pylons import tmpl_context as c
+
+from allura import model as M
+from allura.controllers.repository import topo_sort
+from allura.model.repository import zipdir, prefix_paths_union
+from allura.model.repo_refresh import (
+ CommitRunDoc,
+ CommitRunBuilder,
+ _group_commits,
+)
+from alluratest.controller import setup_unit_test
+
+
+class TestCommitRunBuilder(unittest.TestCase):
+
+ def setUp(self):
+ setup_unit_test()
+ commits = [
+ M.repository.CommitDoc.make(dict(
+ _id=str(i)))
+ for i in range(10)]
+ for p, com in zip(commits, commits[1:]):
+ p.child_ids = [com._id]
+ com.parent_ids = [p._id]
+ for ci in commits:
+ ci.m.save()
+ self.commits = commits
+
+ def test_single_pass(self):
+ crb = CommitRunBuilder(
+ [ci._id for ci in self.commits])
+ crb.run()
+ self.assertEqual(CommitRunDoc.m.count(), 1)
+
+ def test_two_pass(self):
+ crb = CommitRunBuilder(
+ [ci._id for ci in self.commits[:5]])
+ crb.run()
+ crb = CommitRunBuilder(
+ [ci._id for ci in self.commits[5:]])
+ crb.run()
+ self.assertEqual(CommitRunDoc.m.count(), 2)
+ crb.cleanup()
+ self.assertEqual(CommitRunDoc.m.count(), 1)
+
+ def test_svn_like(self):
+ for ci in self.commits:
+ crb = CommitRunBuilder([ci._id])
+ crb.run()
+ crb.cleanup()
+ self.assertEqual(CommitRunDoc.m.count(), 1)
+
+ def test_reversed(self):
+ for ci in reversed(self.commits):
+ crb = CommitRunBuilder([ci._id])
+ crb.run()
+ crb.cleanup()
+ self.assertEqual(CommitRunDoc.m.count(), 1)
+
+
+class TestTopoSort(unittest.TestCase):
+
+ def test_commit_dates_out_of_order(self):
+ """Commits should be sorted by their parent/child relationships,
+ regardless of the date on the commit.
+ """
+ head_ids = ['dev', 'master']
+ parents = {
+ 'dev': ['dev@{1}'],
+ 'dev@{1}': ['master'],
+ 'master': ['master@{1}'],
+ 'master@{1}': ['master@{2}'],
+ 'master@{2}': ['master@{3}'],
+ 'master@{3}': []}
+ children = {
+ 'master@{3}': ['master@{2}'],
+ 'master@{2}': ['master@{1}'],
+ 'master@{1}': ['master'],
+ 'master': ['dev@{1}'],
+ 'dev@{1}': ['dev'],
+ 'dev': []}
+ dates = {
+ 'dev@{1}': datetime.datetime(2012, 1, 1),
+ 'master@{3}': datetime.datetime(2012, 2, 1),
+ 'master@{2}': datetime.datetime(2012, 3, 1),
+ 'master@{1}': datetime.datetime(2012, 4, 1),
+ 'master': datetime.datetime(2012, 5, 1),
+ 'dev': datetime.datetime(2012, 6, 1)}
+ result = topo_sort(children, parents, dates, head_ids)
+ self.assertEqual(list(result), ['dev', 'dev@{1}', 'master',
+ 'master@{1}', 'master@{2}', 'master@{3}'])
+
+
+def tree(name, id, trees=None, blobs=None):
+ t = Mock(tree_ids=[], blob_ids=[], other_ids=[])
+ t.name = name
+ t.id = id
+ t._id = id
+ if trees is not None:
+ t.tree_ids = trees
+ if blobs is not None:
+ t.blob_ids = blobs
+ return t
+
+
+def blob(name, id):
+ b = Mock()
+ b.name = name
+ b.id = id
+ return b
+
+
+class TestTree(unittest.TestCase):
+
+ @patch('allura.model.repository.Tree.__getitem__')
+ def test_get_obj_by_path(self, getitem):
+ tree = M.repository.Tree()
+ # test with relative path
+ tree.get_obj_by_path('some/path/file.txt')
+ getitem.assert_called_with('some')
+ getitem().__getitem__.assert_called_with('path')
+ getitem().__getitem__().__getitem__.assert_called_with('file.txt')
+ # test with absolute path
+ tree.get_obj_by_path('/some/path/file.txt')
+ getitem.assert_called_with('some')
+ getitem().__getitem__.assert_called_with('path')
+ getitem().__getitem__().__getitem__.assert_called_with('file.txt')
+
+
+class TestBlob(unittest.TestCase):
+
+ def test_pypeline_view(self):
+ blob = M.repository.Blob(Mock(), Mock(), Mock())
+ blob._id = 'blob1'
+ blob.path = Mock(return_value='path')
+ blob.name = 'INSTALL.mdown'
+ blob.extension = '.mdown'
+ assert_equal(blob.has_pypeline_view, True)
+
+
+class TestCommit(unittest.TestCase):
+
+ def test_activity_extras(self):
+ commit = M.repository.Commit()
+ commit.shorthand_id = MagicMock(return_value='abcdef')
+ commit.message = 'commit msg'
+ self.assertIn('allura_id', commit.activity_extras)
+ self.assertEqual(commit.activity_extras['summary'], commit.summary)
+
+ def test_get_path_no_create(self):
+ commit = M.repository.Commit()
+ commit.get_tree = MagicMock()
+ commit.get_path('foo/', create=False)
+ commit.get_tree.assert_called_with(False)
+ commit.get_tree().__getitem__.assert_called_with('foo')
+ commit.get_tree().__getitem__.assert_not_called_with('')
+
+ def test_get_tree_no_create(self):
+ c.model_cache = Mock()
+ c.model_cache.get.return_value = None
+ commit = M.repository.Commit()
+ commit.repo = Mock()
+
+ commit.tree_id = None
+ tree = commit.get_tree(create=False)
+ assert not commit.repo.compute_tree_new.called
+ assert not c.model_cache.get.called
+ assert_equal(tree, None)
+
+ commit.tree_id = 'tree'
+ tree = commit.get_tree(create=False)
+ assert not commit.repo.compute_tree_new.called
+ c.model_cache.get.assert_called_with(M.repository.Tree, dict(_id='tree'))
+ assert_equal(tree, None)
+
+ _tree = Mock()
+ c.model_cache.get.return_value = _tree
+ tree = commit.get_tree(create=False)
+ _tree.set_context.assert_called_with(commit)
+ assert_equal(tree, _tree)
+
+ @patch.object(M.repository.Tree.query, 'get')
+ def test_get_tree_create(self, tree_get):
+ c.model_cache = Mock()
+ c.model_cache.get.return_value = None
+ commit = M.repository.Commit()
+ commit.repo = Mock()
+
+ commit.repo.compute_tree_new.return_value = None
+ commit.tree_id = None
+ tree = commit.get_tree()
+ commit.repo.compute_tree_new.assert_called_once_with(commit)
+ assert not c.model_cache.get.called
+ assert not tree_get.called
+ assert_equal(tree, None)
+
+ commit.repo.compute_tree_new.reset_mock()
+ commit.repo.compute_tree_new.return_value = 'tree'
+ _tree = Mock()
+ c.model_cache.get.return_value = _tree
+ tree = commit.get_tree()
+ commit.repo.compute_tree_new.assert_called_once_with(commit)
+ assert not tree_get.called
+ c.model_cache.get.assert_called_once_with(
+ M.repository.Tree, dict(_id='tree'))
+ _tree.set_context.assert_called_once_with(commit)
+ assert_equal(tree, _tree)
+
+ commit.repo.compute_tree_new.reset_mock()
+ c.model_cache.get.reset_mock()
+ commit.tree_id = 'tree2'
+ tree = commit.get_tree()
+ assert not commit.repo.compute_tree_new.called
+ assert not tree_get.called
+ c.model_cache.get.assert_called_once_with(
+ M.repository.Tree, dict(_id='tree2'))
+ _tree.set_context.assert_called_once_with(commit)
+ assert_equal(tree, _tree)
+
+ commit.repo.compute_tree_new.reset_mock()
+ c.model_cache.get.reset_mock()
+ c.model_cache.get.return_value = None
+ tree_get.return_value = _tree
+ tree = commit.get_tree()
+ c.model_cache.get.assert_called_once_with(
+ M.repository.Tree, dict(_id='tree2'))
+ commit.repo.compute_tree_new.assert_called_once_with(commit)
+ assert_equal(commit.tree_id, 'tree')
+ tree_get.assert_called_once_with(_id='tree')
+ c.model_cache.set.assert_called_once_with(
+ M.repository.Tree, dict(_id='tree'), _tree)
+ _tree.set_context.assert_called_once_with(commit)
+ assert_equal(tree, _tree)
+
+ def test_tree_create(self):
+ commit = M.repository.Commit()
+ commit.get_tree = Mock()
+ tree = commit.tree
+ commit.get_tree.assert_called_with(create=True)
+
+
+class TestZipDir(unittest.TestCase):
+
+ @patch('allura.model.repository.Popen')
+ @patch('allura.model.repository.tg')
+ def test_popen_called(self, tg, popen):
+ from subprocess import PIPE
+ popen.return_value.communicate.return_value = 1, 2
+ popen.return_value.returncode = 0
+ tg.config = {'scm.repos.tarball.zip_binary': '/bin/zip'}
+ src = '/fake/path/to/repo'
+ zipfile = '/fake/zip/file.tmp'
+ zipdir(src, zipfile)
+ popen.assert_called_once_with(
+ ['/bin/zip', '-y', '-q', '-r', zipfile, 'repo'],
+ cwd='/fake/path/to', stdout=PIPE, stderr=PIPE)
+ popen.reset_mock()
+ src = '/fake/path/to/repo/'
+ zipdir(src, zipfile, exclude='file.txt')
+ popen.assert_called_once_with(
+ ['/bin/zip', '-y', '-q', '-r',
+ zipfile, 'repo', '-x', 'file.txt'],
+ cwd='/fake/path/to', stdout=PIPE, stderr=PIPE)
+
+ @patch('allura.model.repository.Popen')
+ @patch('allura.model.repository.tg')
+ def test_exception_logged(self, tg, popen):
+ tg.config = {'scm.repos.tarball.zip_binary': '/bin/zip'}
+ popen.return_value.communicate.return_value = 1, 2
+ popen.return_value.returncode = 1
+ src = '/fake/path/to/repo'
+ zipfile = '/fake/zip/file.tmp'
+ with self.assertRaises(Exception) as cm:
+ zipdir(src, zipfile)
+ emsg = str(cm.exception)
+ self.assertTrue(
+ "Command: "
+ "['/bin/zip', '-y', '-q', '-r', '/fake/zip/file.tmp', 'repo'] "
+ "returned non-zero exit code 1" in emsg)
+ self.assertTrue("STDOUT: 1" in emsg)
+ self.assertTrue("STDERR: 2" in emsg)
+
+
+class TestPrefixPathsUnion(unittest.TestCase):
+
+ def test_disjoint(self):
+ a = set(['a1', 'a2', 'a3'])
+ b = set(['b1', 'b1/foo', 'b2'])
+ self.assertItemsEqual(prefix_paths_union(a, b), [])
+
+ def test_exact(self):
+ a = set(['a1', 'a2', 'a3'])
+ b = set(['b1', 'a2', 'a3'])
+ self.assertItemsEqual(prefix_paths_union(a, b), ['a2', 'a3'])
+
+ def test_prefix(self):
+ a = set(['a1', 'a2', 'a3'])
+ b = set(['b1', 'a2/foo', 'b3/foo'])
+ self.assertItemsEqual(prefix_paths_union(a, b), ['a2'])
+
+
+class TestGroupCommits(object):
+
+ def setUp(self):
+ self.repo = Mock()
+ self.repo.symbolics_for_commit.return_value = ([], [])
+
+ def test_no_branches(self):
+ b, t = _group_commits(self.repo, ['3', '2', '1'])
+ dd.assert_equal(b, {'__default__': ['3', '2', '1']})
+ dd.assert_equal(t, {})
+
+ def test_branches_and_tags(self):
+ self.repo.symbolics_for_commit.side_effect = [
+ (['master'], ['v1.1']),
+ ([], []),
+ ([], []),
+ ]
+ b, t = _group_commits(self.repo, ['3', '2', '1'])
+ dd.assert_equal(b, {'master': ['3', '2', '1']})
+ dd.assert_equal(t, {'v1.1': ['3', '2', '1']})
+
+ def test_multiple_branches(self):
+ self.repo.symbolics_for_commit.side_effect = [
+ (['master'], ['v1.1']),
+ ([], ['v1.0']),
+ (['test1', 'test2'], []),
+ ]
+ b, t = _group_commits(self.repo, ['3', '2', '1'])
+ dd.assert_equal(b, {'master': ['3', '2'],
+ 'test1': ['1'],
+ 'test2': ['1']})
+ dd.assert_equal(t, {'v1.1': ['3'],
+ 'v1.0': ['2', '1']})
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/unit/test_session.py
----------------------------------------------------------------------
diff --git a/tests/unit/test_session.py b/tests/unit/test_session.py
new file mode 100644
index 0000000..b7cb2a3
--- /dev/null
+++ b/tests/unit/test_session.py
@@ -0,0 +1,258 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# 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 pymongo
+import mock
+
+from unittest import TestCase
+
+import allura
+from allura.tests import decorators as td
+from allura.model.session import (
+ IndexerSessionExtension,
+ BatchIndexer,
+ ArtifactSessionExtension,
+ substitute_extensions,
+)
+
+
+def test_extensions_cm():
+ session = mock.Mock(_kwargs=dict(extensions=[]))
+ extension = mock.Mock()
+ with substitute_extensions(session, [extension]) as sess:
+ assert session.flush.call_count == 1
+ assert session.close.call_count == 1
+ assert sess == session
+ assert sess._kwargs['extensions'] == [extension]
+ assert session.flush.call_count == 2
+ assert session.close.call_count == 2
+ assert session._kwargs['extensions'] == []
+
+
+def test_extensions_cm_raises():
+ session = mock.Mock(_kwargs=dict(extensions=[]))
+ extension = mock.Mock()
+ with td.raises(ValueError):
+ with substitute_extensions(session, [extension]) as sess:
+ session.flush.side_effect = AttributeError
+ assert session.flush.call_count == 1
+ assert session.close.call_count == 1
+ assert sess == session
+ assert sess._kwargs['extensions'] == [extension]
+ raise ValueError('test')
+ assert session.flush.call_count == 1
+ assert session.close.call_count == 1
+ assert session._kwargs['extensions'] == []
+
+
+def test_extensions_cm_flush_raises():
+ session = mock.Mock(_kwargs=dict(extensions=[]))
+ extension = mock.Mock()
+ with td.raises(AttributeError):
+ with substitute_extensions(session, [extension]) as sess:
+ session.flush.side_effect = AttributeError
+ assert session.flush.call_count == 1
+ assert session.close.call_count == 1
+ assert sess == session
+ assert sess._kwargs['extensions'] == [extension]
+ assert session.flush.call_count == 2
+ assert session.close.call_count == 1
+ assert session._kwargs['extensions'] == []
+
+
+class TestSessionExtension(TestCase):
+
+ def _mock_indexable(self, **kw):
+ m = mock.Mock(**kw)
+ m.__ming__ = mock.MagicMock()
+ m.index_id.return_value = id(m)
+ return m
+
+
+class TestIndexerSessionExtension(TestSessionExtension):
+
+ def setUp(self):
+ session = mock.Mock()
+ self.ExtensionClass = IndexerSessionExtension
+ self.extension = self.ExtensionClass(session)
+ self.tasks = {'add': mock.Mock(), 'del': mock.Mock()}
+ self.extension.TASKS = mock.Mock()
+ self.extension.TASKS.get.return_value = self.tasks
+
+ def test_flush(self):
+ added = [self._mock_indexable(_id=i) for i in (1, 2, 3)]
+ modified = [self._mock_indexable(_id=i) for i in (4, 5)]
+ deleted = [self._mock_indexable(_id=i) for i in (6, 7)]
+ self.extension.objects_added = added
+ self.extension.objects_modified = modified
+ self.extension.objects_deleted = deleted
+ self.extension.after_flush()
+ self.tasks['add'].post.assert_called_once_with([1, 2, 3, 4, 5])
+ self.tasks['del'].post.assert_called_once_with(list(map(id, deleted)))
+
+ def test_flush_skips_update(self):
+ modified = [self._mock_indexable(_id=i) for i in range(5)]
+ modified[1].should_update_index.return_value = False
+ modified[4].should_update_index.return_value = False
+ self.extension.objects_modified = modified
+ self.extension.after_flush()
+ self.tasks['add'].post.assert_called_once_with([0, 2, 3])
+
+ def test_flush_skips_task_if_all_objects_filtered_out(self):
+ modified = [self._mock_indexable(_id=i) for i in range(5)]
+ for m in modified:
+ m.should_update_index.return_value = False
+ self.extension.objects_modified = modified
+ self.extension.after_flush()
+ assert self.tasks['add'].post.call_count == 0
+
+
+class TestArtifactSessionExtension(TestSessionExtension):
+
+ def setUp(self):
+ session = mock.Mock(disable_index=False)
+ self.ExtensionClass = ArtifactSessionExtension
+ self.extension = self.ExtensionClass(session)
+
+ @mock.patch.object(allura.model.index.Shortlink, 'from_artifact')
+ @mock.patch.object(allura.model.index.ArtifactReference, 'from_artifact')
+ @mock.patch('allura.model.session.index_tasks')
+ def test_flush_skips_update(self, index_tasks, ref_fa, shortlink_fa):
+ modified = [self._mock_indexable(_id=i) for i in range(5)]
+ modified[1].should_update_index.return_value = False
+ modified[4].should_update_index.return_value = False
+ ref_fa.side_effect = lambda obj: mock.Mock(_id=obj._id)
+ self.extension.objects_modified = modified
+ self.extension.after_flush()
+ index_tasks.add_artifacts.post.assert_called_once_with([0, 2, 3])
+
+ @mock.patch('allura.model.session.index_tasks')
+ def test_flush_skips_task_if_all_objects_filtered_out(self, index_tasks):
+ modified = [self._mock_indexable(_id=i) for i in range(5)]
+ for m in modified:
+ m.should_update_index.return_value = False
+ self.extension.objects_modified = modified
+ self.extension.after_flush()
+ assert index_tasks.add_artifacts.post.call_count == 0
+
+
+class TestBatchIndexer(TestCase):
+
+ def setUp(self):
+ session = mock.Mock()
+ self.extcls = BatchIndexer
+ self.ext = self.extcls(session)
+
+ def _mock_indexable(self, **kw):
+ m = mock.Mock(**kw)
+ m.index_id.return_value = id(m)
+ return m
+
+ @mock.patch('allura.model.ArtifactReference.query.find')
+ def test_update_index(self, find):
+ m = self._mock_indexable
+ objs_deleted = [m(_id=i) for i in (1, 2, 3)]
+ arefs = [m(_id=i) for i in (4, 5, 6)]
+ find.return_value = [m(_id=i) for i in (7, 8, 9)]
+ self.ext.update_index(objs_deleted, arefs)
+ self.assertEqual(self.ext.to_delete,
+ set([o.index_id() for o in objs_deleted]))
+ self.assertEqual(self.ext.to_add, set([4, 5, 6]))
+
+ # test deleting something that was previously added
+ objs_deleted += [m(_id=4)]
+ find.return_value = [m(_id=4)]
+ self.ext.update_index(objs_deleted, [])
+ self.assertEqual(self.ext.to_delete,
+ set([o.index_id() for o in objs_deleted]))
+ self.assertEqual(self.ext.to_add, set([5, 6]))
+
+ @mock.patch('allura.model.session.index_tasks')
+ def test_flush(self, index_tasks):
+ objs_deleted = [self._mock_indexable(_id=i) for i in (1, 2, 3)]
+ del_index_ids = set([o.index_id() for o in objs_deleted])
+ self.extcls.to_delete = del_index_ids
+ self.extcls.to_add = set([4, 5, 6])
+ self.ext.flush()
+ index_tasks.del_artifacts.post.assert_called_once_with(
+ list(del_index_ids))
+ index_tasks.add_artifacts.post.assert_called_once_with([4, 5, 6])
+ self.assertEqual(self.ext.to_delete, set())
+ self.assertEqual(self.ext.to_add, set())
+
+ @mock.patch('allura.model.session.index_tasks')
+ def test_flush_chunks_huge_lists(self, index_tasks):
+ self.extcls.to_delete = set(range(100 * 1000 + 1))
+ self.extcls.to_add = set(range(1000 * 1000 + 1))
+ self.ext.flush()
+ self.assertEqual(
+ len(index_tasks.del_artifacts.post.call_args_list[0][0][0]),
+ 100 * 1000)
+ self.assertEqual(
+ len(index_tasks.del_artifacts.post.call_args_list[1][0][0]), 1)
+ self.assertEqual(
+ len(index_tasks.add_artifacts.post.call_args_list[0][0][0]),
+ 1000 * 1000)
+ self.assertEqual(
+ len(index_tasks.add_artifacts.post.call_args_list[1][0][0]), 1)
+ self.assertEqual(self.ext.to_delete, set())
+ self.assertEqual(self.ext.to_add, set())
+
+ @mock.patch('allura.tasks.index_tasks')
+ def test_flush_noop(self, index_tasks):
+ self.ext.flush()
+ self.assertEqual(0, index_tasks.del_artifacts.post.call_count)
+ self.assertEqual(0, index_tasks.add_artifacts.post.call_count)
+ self.assertEqual(self.ext.to_delete, set())
+ self.assertEqual(self.ext.to_add, set())
+
+ @mock.patch('allura.tasks.index_tasks')
+ def test__post_too_large(self, index_tasks):
+ def on_post(chunk):
+ if len(chunk) > 1:
+ e = pymongo.errors.InvalidDocument(
+ "BSON document too large (16906035 bytes) - the connected server supports BSON document sizes up to 16777216 bytes.")
+ # ming injects a 2nd arg with the document, so we do too
+ e.args = e.args + ("doc: {'task_name': 'allura.tasks.index_tasks.add_artifacts', ........",)
+ raise e
+ index_tasks.add_artifacts.post.side_effect = on_post
+ self.ext._post(index_tasks.add_artifacts, list(range(5)))
+ expected = [
+ mock.call([0, 1, 2, 3, 4]),
+ mock.call([0, 1]),
+ mock.call([0]),
+ mock.call([1]),
+ mock.call([2, 3, 4]),
+ mock.call([2]),
+ mock.call([3, 4]),
+ mock.call([3]),
+ mock.call([4])
+ ]
+ self.assertEqual(
+ expected, index_tasks.add_artifacts.post.call_args_list)
+
+ @mock.patch('allura.tasks.index_tasks')
+ def test__post_other_error(self, index_tasks):
+ def on_post(chunk):
+ raise pymongo.errors.InvalidDocument("Cannot encode object...")
+ index_tasks.add_artifacts.post.side_effect = on_post
+ with td.raises(pymongo.errors.InvalidDocument):
+ self.ext._post(index_tasks.add_artifacts, list(range(5)))
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/unit/test_sitemapentry.py
----------------------------------------------------------------------
diff --git a/tests/unit/test_sitemapentry.py b/tests/unit/test_sitemapentry.py
new file mode 100644
index 0000000..2a6353c
--- /dev/null
+++ b/tests/unit/test_sitemapentry.py
@@ -0,0 +1,38 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# 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 unittest
+from mock import Mock
+
+from allura.app import SitemapEntry
+
+
+class TestSitemapEntry(unittest.TestCase):
+
+ def test_matches_url(self):
+ request = Mock(upath_info='/p/project/tool/artifact')
+ s1 = SitemapEntry('tool', url='/p/project/tool')
+ s2 = SitemapEntry('tool2', url='/p/project/tool2')
+ s3 = SitemapEntry('Tool', url='/p/project/_list/tool')
+ s3.matching_urls.append('/p/project/tool')
+ self.assertTrue(s1.matches_url(request))
+ self.assertFalse(s2.matches_url(request))
+ self.assertTrue(s3.matches_url(request))
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/unit/test_solr.py
----------------------------------------------------------------------
diff --git a/tests/unit/test_solr.py b/tests/unit/test_solr.py
new file mode 100644
index 0000000..1b5d899
--- /dev/null
+++ b/tests/unit/test_solr.py
@@ -0,0 +1,238 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# 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 unittest
+
+import mock
+from nose.tools import assert_equal
+from markupsafe import Markup
+
+from allura.lib import helpers as h
+from allura.tests import decorators as td
+from alluratest.controller import setup_basic_test
+from allura.lib.solr import Solr, escape_solr_arg
+from allura.lib.search import search_app, SearchIndexable
+
+
+class TestSolr(unittest.TestCase):
+
+ @mock.patch('allura.lib.solr.pysolr')
+ def test_init(self, pysolr):
+ servers = ['server1', 'server2']
+ solr = Solr(servers, commit=False, commitWithin='10000')
+ calls = [mock.call('server1'), mock.call('server2')]
+ pysolr.Solr.assert_has_calls(calls)
+ assert_equal(len(solr.push_pool), 2)
+
+ pysolr.reset_mock()
+ solr = Solr(servers, 'server3', commit=False, commitWithin='10000')
+ calls = [mock.call('server1'), mock.call('server2'),
+ mock.call('server3')]
+ pysolr.Solr.assert_has_calls(calls)
+ assert_equal(len(solr.push_pool), 2)
+
+ @mock.patch('allura.lib.solr.pysolr')
+ def test_add(self, pysolr):
+ servers = ['server1', 'server2']
+ solr = Solr(servers, commit=False, commitWithin='10000')
+ solr.add('foo', commit=True, commitWithin=None)
+ calls = [mock.call('foo', commit=True, commitWithin=None)] * 2
+ pysolr.Solr().add.assert_has_calls(calls)
+ pysolr.reset_mock()
+ solr.add('bar', somekw='value')
+ calls = [mock.call('bar', commit=False,
+ commitWithin='10000', somekw='value')] * 2
+ pysolr.Solr().add.assert_has_calls(calls)
+
+ @mock.patch('allura.lib.solr.pysolr')
+ def test_delete(self, pysolr):
+ servers = ['server1', 'server2']
+ solr = Solr(servers, commit=False, commitWithin='10000')
+ solr.delete('foo', commit=True)
+ calls = [mock.call('foo', commit=True)] * 2
+ pysolr.Solr().delete.assert_has_calls(calls)
+ pysolr.reset_mock()
+ solr.delete('bar', somekw='value')
+ calls = [mock.call('bar', commit=False, somekw='value')] * 2
+ pysolr.Solr().delete.assert_has_calls(calls)
+
+ @mock.patch('allura.lib.solr.pysolr')
+ def test_commit(self, pysolr):
+ servers = ['server1', 'server2']
+ solr = Solr(servers, commit=False, commitWithin='10000')
+ solr.commit('arg')
+ pysolr.Solr().commit.assert_has_calls([mock.call('arg')] * 2)
+ pysolr.reset_mock()
+ solr.commit('arg', kw='kw')
+ calls = [mock.call('arg', kw='kw')] * 2
+ pysolr.Solr().commit.assert_has_calls(calls)
+
+ @mock.patch('allura.lib.solr.pysolr')
+ def test_search(self, pysolr):
+ servers = ['server1', 'server2']
+ solr = Solr(servers, commit=False, commitWithin='10000')
+ solr.search('foo')
+ solr.query_server.search.assert_called_once_with('foo')
+ pysolr.reset_mock()
+ solr.search('bar', kw='kw')
+ solr.query_server.search.assert_called_once_with('bar', kw='kw')
+
+ @mock.patch('allura.lib.search.search')
+ def test_site_admin_search(self, search):
+ from allura.lib.search import site_admin_search
+ from allura.model import Project, User
+ fq = ['type_s:Project']
+ site_admin_search(Project, 'test', 'shortname', rows=25)
+ search.assert_called_once_with(
+ 'shortname_s:test', fq=fq, ignore_errors=False, rows=25)
+
+ search.reset_mock()
+ site_admin_search(Project, 'shortname:test || shortname:test2', '__custom__')
+ search.assert_called_once_with(
+ 'shortname_s:test || shortname_s:test2', fq=fq, ignore_errors=False)
+
+ fq = ['type_s:User']
+ search.reset_mock()
+ site_admin_search(User, 'test-user', 'username', rows=25)
+ search.assert_called_once_with(
+ 'username_s:test-user', fq=fq, ignore_errors=False, rows=25)
+
+ search.reset_mock()
+ site_admin_search(User, 'username:admin1 || username:root', '__custom__')
+ search.assert_called_once_with(
+ 'username_s:admin1 || username_s:root', fq=fq, ignore_errors=False)
+
+
+class TestSearchIndexable(unittest.TestCase):
+
+ def setUp(self):
+ self.obj = SearchIndexable()
+
+ def test_solarize_empty_index(self):
+ self.obj.index = lambda: None
+ assert_equal(self.obj.solarize(), None)
+
+ def test_solarize_doc_without_text(self):
+ self.obj.index = lambda: dict()
+ assert_equal(self.obj.solarize(), dict(text=''))
+
+ def test_solarize_strips_markdown(self):
+ self.obj.index = lambda: dict(text='# Header')
+ assert_equal(self.obj.solarize(), dict(text='Header'))
+
+ def test_solarize_html_in_text(self):
+ self.obj.index = lambda: dict(text='<script>a(1)</script>')
+ assert_equal(self.obj.solarize(), dict(text='<script>a(1)</script>'))
+ self.obj.index = lambda: dict(text='<script>a(1)</script>')
+ assert_equal(self.obj.solarize(), dict(text='<script>a(1)</script>'))
+
+
+class TestSearch_app(unittest.TestCase):
+
+ def setUp(self):
+ # need to create the "test" project so @td.with_wiki works
+ setup_basic_test()
+
+ @td.with_wiki
+ @mock.patch('allura.lib.search.url')
+ @mock.patch('allura.lib.search.request')
+ def test_basic(self, req, url_fn):
+ req.GET = dict()
+ req.path = '/test/search'
+ url_fn.side_effect = ['the-score-url', 'the-date-url']
+ with h.push_context('test', 'wiki', neighborhood='Projects'):
+ resp = search_app(q='foo bar')
+ assert_equal(resp, dict(
+ q='foo bar',
+ history=None,
+ results=[],
+ count=0,
+ limit=25,
+ page=0,
+ search_error=None,
+ sort_score_url='the-score-url',
+ sort_date_url='the-date-url',
+ sort_field='score',
+ ))
+
+ @td.with_wiki
+ @mock.patch('allura.lib.search.g.solr.search')
+ @mock.patch('allura.lib.search.url')
+ @mock.patch('allura.lib.search.request')
+ def test_escape_solr_text(self, req, url_fn, solr_search):
+ req.GET = dict()
+ req.path = '/test/wiki/search'
+ url_fn.side_effect = ['the-score-url', 'the-date-url']
+ results = mock.Mock(hits=2, docs=[
+ {'id': 123, 'type_s': 'WikiPage Snapshot',
+ 'url_s': '/test/wiki/Foo', 'version_i': 2},
+ {'id': 321, 'type_s': 'Post'},
+ ], highlighting={
+ 123: dict(
+ title='some #ALLURA-HIGHLIGHT-START#Foo#ALLURA-HIGHLIGHT-END# stuff',
+ text='scary <script>alert(1)</script> bar'),
+ 321: dict(title='blah blah',
+ text='less scary but still dangerous <script>alert(1)</script> '
+ 'blah #ALLURA-HIGHLIGHT-START#bar#ALLURA-HIGHLIGHT-END# foo foo'),
+ },
+ )
+ results.__iter__ = lambda self: iter(results.docs)
+ solr_search.return_value = results
+ with h.push_context('test', 'wiki', neighborhood='Projects'):
+ resp = search_app(q='foo bar')
+
+ assert_equal(resp, dict(
+ q='foo bar',
+ history=None,
+ count=2,
+ limit=25,
+ page=0,
+ search_error=None,
+ sort_score_url='the-score-url',
+ sort_date_url='the-date-url',
+ sort_field='score',
+ results=[{
+ 'id': 123,
+ 'type_s': 'WikiPage Snapshot',
+ 'version_i': 2,
+ 'url_s': '/test/wiki/Foo?version=2',
+ # highlighting works
+ 'title_match': Markup('some <strong>Foo</strong> stuff'),
+ # HTML in the solr plaintext results get escaped
+ 'text_match': Markup('scary <script>alert(1)</script> bar'),
+ }, {
+ 'id': 321,
+ 'type_s': 'Post',
+ 'title_match': Markup('blah blah'),
+ # highlighting in text
+ 'text_match': Markup('less scary but still dangerous &lt;script&gt;alert(1)&lt;/script&gt; blah <strong>bar</strong> foo foo'),
+ }]
+ ))
+
+ def test_escape_solr_arg(self):
+ text = 'some: weird "text" with symbols'
+ escaped_text = escape_solr_arg(text)
+ assert_equal(escaped_text, r'some\: weird \"text\" with symbols')
+
+ def test_escape_solr_arg_with_backslash(self):
+ text = 'some: weird "text" with \\ backslash'
+ escaped_text = escape_solr_arg(text)
+ assert_equal(escaped_text, r'some\: weird \"text\" with \\ backslash')
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/version.py
----------------------------------------------------------------------
diff --git a/version.py b/version.py
new file mode 100644
index 0000000..50896f8
--- /dev/null
+++ b/version.py
@@ -0,0 +1,23 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# 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.
+
+__version_info__ = (0, 1)
+__version__ = '.'.join(map(str, __version_info__))