You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by br...@apache.org on 2019/11/13 16:33:43 UTC

[allura] branch db/8340 created (now a91ab3c)

This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a change to branch db/8340
in repository https://gitbox.apache.org/repos/asf/allura.git.


      at a91ab3c  [#8340] some wiki increased coverage

This branch includes the following new commits:

     new 7a50237  [#8340] remove ez_setup file
     new 0d6f0a8  [#8340] test coverage for repo tasks/scripts
     new 589bdfb  [#8340] repo: remove dead code, add some test coverage
     new 1a797b5  [#8340] tracker test coverage and remove dead code
     new 988f4b6  [#8340] various admin/auth tests and dead code removal (url for editing custom groups no longer used)
     new a91ab3c  [#8340] some wiki increased coverage

The 6 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[allura] 06/06: [#8340] some wiki increased coverage

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/8340
in repository https://gitbox.apache.org/repos/asf/allura.git

commit a91ab3c062003a98e66eec97b744775b80d69e55
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Wed Nov 13 11:33:02 2019 -0500

    [#8340] some wiki increased coverage
---
 ForgeWiki/forgewiki/tests/functional/test_root.py | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/ForgeWiki/forgewiki/tests/functional/test_root.py b/ForgeWiki/forgewiki/tests/functional/test_root.py
index d79937a..54fa613 100644
--- a/ForgeWiki/forgewiki/tests/functional/test_root.py
+++ b/ForgeWiki/forgewiki/tests/functional/test_root.py
@@ -773,6 +773,18 @@ class TestRootController(TestController):
         n = M.Notification.query.get(subject="[test:wiki] test-admin removed page bbb")
         assert '222' in n.text
 
+        # view deleted page
+        response = response.click('bbb')
+        assert '(deleted)' in response
+        deletedpath = response.request.path_info
+
+        # undelete it
+        undelete_url = deletedpath + 'undelete'
+        response = self.app.post(undelete_url)
+        assert_equal(response.json, {'location': './edit'})
+        response = self.app.get(deletedpath + 'edit')
+        assert 'Edit bbb' in response
+
     def test_mailto_links(self):
         self.app.get('/wiki/test_mailto/')
         params = {


[allura] 03/06: [#8340] repo: remove dead code, add some test coverage

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/8340
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 589bdfb4d94d2f26cc4511bc20fac9005543df18
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Tue Nov 12 13:29:45 2019 -0500

    [#8340] repo: remove dead code, add some test coverage
---
 Allura/allura/model/repository.py                  | 85 ----------------------
 Allura/allura/tests/model/test_repo.py             | 27 +++++++
 .../forgegit/tests/functional/test_controllers.py  | 27 +++++++
 3 files changed, 54 insertions(+), 85 deletions(-)

diff --git a/Allura/allura/model/repository.py b/Allura/allura/model/repository.py
index 98200fa..203e3ef 100644
--- a/Allura/allura/model/repository.py
+++ b/Allura/allura/model/repository.py
@@ -528,15 +528,6 @@ class Repository(Artifact, ActivityObject):
     def paged_diffs(self, commit_id, start=0, end=None,  onlyChangedFiles=False):
         return self._impl.paged_diffs(commit_id, start, end, onlyChangedFiles)
 
-    def _log(self, rev, skip, limit):
-        head = self.commit(rev)
-        if head is None:
-            return
-        for _id in self.commitlog([head._id], skip, limit):
-            ci = head.query.get(_id=_id)
-            ci.set_context(self)
-            yield ci
-
     def init_as_clone(self, source_path, source_name, source_url):
         self.upstream_repo.name = source_name
         self.upstream_repo.url = source_url
@@ -1158,33 +1149,6 @@ class Commit(RepoObject, ActivityObject):
     def symbolic_ids(self):
         return self.repo.symbolics_for_commit(self)
 
-    def get_parent(self, index=0):
-        '''Get the parent of this commit.
-
-        If there is no parent commit, or if an invalid index is given,
-        returns None.
-        '''
-        try:
-            cache = getattr(c, 'model_cache', '') or ModelCache()
-            ci = cache.get(Commit, dict(_id=self.parent_ids[index]))
-            if not ci:
-                return None
-            ci.set_context(self.repo)
-            return ci
-        except IndexError:
-            return None
-
-    def climb_commit_tree(self, predicate=None):
-        '''
-        Returns a generator that walks up the commit tree along
-        the first-parent ancestory, starting with this commit,
-        optionally filtering by a predicate.'''
-        ancestor = self
-        while ancestor:
-            if predicate is None or predicate(ancestor):
-                yield ancestor
-            ancestor = ancestor.get_parent()
-
     def url(self):
         if self.repo is None:
             self.repo = self.guess_repo()
@@ -1253,13 +1217,6 @@ class Commit(RepoObject, ActivityObject):
                     cur = cur[part]
         return cur
 
-    def has_path(self, path):
-        try:
-            self.get_path(path)
-            return True
-        except KeyError:
-            return False
-
     @LazyProperty
     def changed_paths(self):
         '''
@@ -1352,19 +1309,6 @@ class Tree(RepoObject):
     parent = None
     name = None
 
-    def compute_hash(self):
-        '''Compute a hash based on the contents of the tree.  Note that this
-        hash does not necessarily correspond to any actual DVCS hash.
-        '''
-        lines = (
-            ['tree' + x.name + x.id for x in self.tree_ids]
-            + ['blob' + x.name + x.id for x in self.blob_ids]
-            + [x.type + x.name + x.id for x in self.other_ids])
-        sha_obj = sha1()
-        for line in sorted(lines):
-            sha_obj.update(line)
-        return sha_obj.hexdigest()
-
     def __getitem__(self, name):
         cache = getattr(c, 'model_cache', '') or ModelCache()
         obj = self.by_name[name]
@@ -1578,11 +1522,6 @@ class Blob(object):
     def text(self):
         return self.open().read()
 
-    @classmethod
-    def diff(cls, v0, v1):
-        differ = SequenceMatcher(v0, v1)
-        return differ.get_opcodes()
-
 
 class LastCommit(RepoObject):
 
@@ -1720,8 +1659,6 @@ class ModelCache(object):
         # keyed by query, holds _id
         self._query_cache = defaultdict(OrderedDict)
         self._instance_cache = defaultdict(OrderedDict)  # keyed by _id
-        self._synthetic_ids = defaultdict(set)
-        self._synthetic_id_queries = defaultdict(set)
 
     def _normalize_query(self, query):
         _query = query
@@ -1763,9 +1700,6 @@ class ModelCache(object):
                                                              None)))
             if _id is None:
                 _id = val._model_cache_id = bson.ObjectId()
-                self._synthetic_ids[cls].add(_id)
-            if _id in self._synthetic_ids:
-                self._synthetic_id_queries[cls].add(_query)
             self._query_cache[cls][_query] = _id
             self._instance_cache[cls][_id] = val
         else:
@@ -1815,25 +1749,6 @@ class ModelCache(object):
         key, val = cache.popitem(last=False)
         return val
 
-    def expire_new_instances(self, cls):
-        '''
-        Expire any instances that were "new" or had no _id value.
-
-        If a lot of new instances of a class are being created, it's possible
-        for a query to pull a copy from mongo when a copy keyed by the synthetic
-        ID is still in the cache, potentially causing de-sync between the copies
-        leading to one with missing data overwriting the other.  Clear new
-        instances out of the cache relatively frequently (depending on the query
-        and instance cache sizes) to avoid this.
-        '''
-        for _query in self._synthetic_id_queries[cls]:
-            self._query_cache[cls].pop(_query)
-        self._synthetic_id_queries[cls] = set()
-        for _id in self._synthetic_ids[cls]:
-            instance = self._instance_cache[cls].pop(_id)
-            self._try_flush(instance, expunge=True)
-        self._synthetic_ids[cls] = set()
-
     def num_queries(self, cls=None):
         if cls is None:
             return sum([len(c) for c in self._query_cache.values()])
diff --git a/Allura/allura/tests/model/test_repo.py b/Allura/allura/tests/model/test_repo.py
index 7a4410a..5007d28 100644
--- a/Allura/allura/tests/model/test_repo.py
+++ b/Allura/allura/tests/model/test_repo.py
@@ -94,6 +94,33 @@ class RepoTestBase(unittest.TestCase):
             with mock.patch.dict(config, values, clear=True):
                 self.assertEqual(result, repo.refresh_url())
 
+    def test_clone_command_categories(self):
+        c.app = mock.Mock(**{'config._id': 'deadbeef'})
+        repo = M.repository.Repository(tool='git')
+        cmd_cats = repo.clone_command_categories(anon=False)
+        assert_equal(cmd_cats, [
+            {'key': 'file', 'name': 'File', 'title': u'Filesystem'}
+        ])
+
+        cmd_cats = repo.clone_command_categories(anon=True)
+        assert_equal(cmd_cats, [
+            {'key': 'file', 'name': 'File', 'title': u'Filesystem'}
+        ])
+
+        repo = M.repository.Repository(tool='something-else')  # no "something-else" in config so will use defaults
+        cmd_cats = repo.clone_command_categories(anon=False)
+        assert_equal(cmd_cats, [
+            {'key': 'rw', 'name': 'RW', 'title': 'Read/Write'},
+            {'key': 'ro', 'name': 'RO', 'title': 'Read Only'},
+            {'key': 'https', 'name': 'HTTPS', 'title': 'HTTPS'}
+        ])
+
+        cmd_cats = repo.clone_command_categories(anon=True)
+        assert_equal(cmd_cats, [
+            {'key': 'ro', 'name': 'RO', 'title': 'Read Only'},
+            {'key': 'https_anon', 'name': 'HTTPS', 'title': 'HTTPS'}
+        ])
+
 
 class TestLastCommit(unittest.TestCase):
     def setUp(self):
diff --git a/ForgeGit/forgegit/tests/functional/test_controllers.py b/ForgeGit/forgegit/tests/functional/test_controllers.py
index 1f2aa0c..4b0c079 100644
--- a/ForgeGit/forgegit/tests/functional/test_controllers.py
+++ b/ForgeGit/forgegit/tests/functional/test_controllers.py
@@ -156,6 +156,11 @@ class TestRootController(_TestCase):
              u'parents': [u'6a45885ae7347f1cac5103b0050cc1be6a1496c8'],
              u'message': u'Add README', u'row': 2})
 
+    def test_commit_browser_basic_view(self):
+        resp = self.app.get('/src-git/ci/1e146e67985dcd71c74de79613719bef7bddca4a/basic')
+        resp.mustcontain('Rick')
+        resp.mustcontain('Change README')
+
     def test_log(self):
         resp = self.app.get('/src-git/ci/1e146e67985dcd71c74de79613719bef7bddca4a/log/')
         assert 'Initial commit' in resp
@@ -457,6 +462,15 @@ class TestRootController(_TestCase):
         r = self.app.get('/p/test/src-git/markdown_syntax_dialog')
         assert_in('<h1>Markdown Syntax Guide</h1>', r)
 
+    def test_refresh(self):
+        r = self.app.get('/p/test/src-git/refresh')
+        assert_in('refresh queued', r)
+        assert_equal(1, M.MonQTask.query.find(dict(task_name='allura.tasks.repo_tasks.refresh')).count())
+
+        r = self.app.get('/p/test/src-git/refresh', extra_environ={'HTTP_REFERER': '/p/test/src-git/'}, status=302)
+        assert_in('is being refreshed', self.webflash(r))
+        assert_equal(2, M.MonQTask.query.find(dict(task_name='allura.tasks.repo_tasks.refresh')).count())
+
 
 class TestRestController(_TestCase):
     def test_index(self):
@@ -660,6 +674,13 @@ class TestFork(_TestCase):
         r = self.app.get('/p/test/src-git/merge-requests/1/', status=200)
         assert_commit_details(r)
 
+        r = self.app.get('/p/test/src-git/merge-requests/1/can_merge_task_status')
+        assert_equal(r.json, {'status': 'ready'})
+        r = self.app.get('/p/test/src-git/merge-requests/1/can_merge_result')
+        assert_equal(r.json, {'can_merge': None})
+        r = self.app.get('/p/test/src-git/merge-requests/1/merge_task_status')
+        assert_equal(r.json, {'status': None})
+
     def test_merge_request_detail_noslash(self):
         self._request_merge()
         r = self.app.get('/p/test/src-git/merge-requests/1', status=301)
@@ -686,6 +707,12 @@ class TestFork(_TestCase):
         assert_regexp_matches(r.html.findAll('span')[-2].getText(), r'[0-9]+ seconds? ago')
         assert_regexp_matches(r.html.findAll('span')[-1].getText(), r'[0-9]+ seconds? ago')
 
+        r = self.app.get('/p/test/src-git/merge-requests/?status=rejected')
+        assert 'href="%s/"' % mr_num not in r, r
+
+        r = self.app.get('/p/test/src-git/merge-requests/?status=all')
+        assert 'href="%s/"' % mr_num in r, r
+
     def test_merge_request_update_status(self):
         r, mr_num = self._request_merge()
         r = self.app.post('/p/test/src-git/merge-requests/%s/save' % mr_num,


[allura] 05/06: [#8340] various admin/auth tests and dead code removal (url for editing custom groups no longer used)

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/8340
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 988f4b603f4acbda7e415bfc01c9a0dc8d93e193
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Tue Nov 12 17:57:43 2019 -0500

    [#8340] various admin/auth tests and dead code removal (url for editing custom groups no longer used)
---
 Allura/allura/ext/admin/admin_main.py              | 90 ----------------------
 .../allura/ext/admin/templates/project_group.html  |  4 -
 Allura/allura/ext/admin/widgets.py                 | 17 ----
 Allura/allura/tests/functional/test_admin.py       | 23 +-----
 Allura/allura/tests/functional/test_auth.py        | 22 ++++++
 Allura/allura/tests/functional/test_site_admin.py  |  9 +++
 .../allura/tests/functional/test_trovecategory.py  | 19 ++++-
 7 files changed, 53 insertions(+), 131 deletions(-)

diff --git a/Allura/allura/ext/admin/admin_main.py b/Allura/allura/ext/admin/admin_main.py
index d63b734..b7a9ce8 100644
--- a/Allura/allura/ext/admin/admin_main.py
+++ b/Allura/allura/ext/admin/admin_main.py
@@ -61,7 +61,6 @@ class W:
     label_edit = ffw.LabelEdit()
     group_card = aw.GroupCard()
     permission_card = aw.PermissionCard()
-    group_settings = aw.GroupSettings()
     new_group_settings = aw.NewGroupSettings()
     screenshot_admin = aw.ScreenshotAdmin()
     screenshot_list = ProjectScreenshots(draggable=True)
@@ -1162,57 +1161,11 @@ class GroupsController(BaseController):
         return dict()
 
     @without_trailing_slash
-    @expose()
-    @require_post()
-    @h.vardec
-    def update(self, card=None, **kw):
-        for pr in card:
-            group = M.ProjectRole.query.get(_id=ObjectId(pr['id']))
-            assert group.project == c.project, 'Security violation'
-            user_ids = pr.get('value', [])
-            new_users = pr.get('new', [])
-            if isinstance(user_ids, basestring):
-                user_ids = [user_ids]
-            if isinstance(new_users, basestring):
-                new_users = [new_users]
-            # Handle new users in groups
-            user_added = False
-            for username in new_users:
-                user = M.User.by_username(username.strip())
-                if not user:
-                    flash('User %s not found' % username, 'error')
-                    redirect('.')
-                if not user._id:
-                    continue  # never add anon users to groups
-                M.AuditLog.log('add user %s to %s', username, group.name)
-                M.ProjectRole.by_user(
-                    user, upsert=True).roles.append(group._id)
-                user_added = True
-            # Make sure we aren't removing all users from the Admin group
-            if group.name == u'Admin' and not (user_ids or user_added):
-                flash('You must have at least one user with the Admin role.',
-                      'warning')
-                redirect('.')
-            # Handle users removed from groups
-            user_ids = set(
-                uid and ObjectId(uid)
-                for uid in user_ids)
-            for role in M.ProjectRole.query.find(dict(user_id={'$ne': None}, roles=group._id)):
-                if role.user_id and role.user_id not in user_ids:
-                    role.roles = [
-                        rid for rid in role.roles if rid != group._id]
-                    M.AuditLog.log('remove user %s from %s',
-                                   role.user.username, group.name)
-        g.post_event('project_updated')
-        redirect('.')
-
-    @without_trailing_slash
     @expose('jinja:allura.ext.admin:templates/project_group.html')
     def new(self):
         c.form = W.new_group_settings
         return dict(
             group=None,
-            show_settings=True,
             action="create")
 
     @expose()
@@ -1228,49 +1181,6 @@ class GroupsController(BaseController):
         g.post_event('project_updated')
         redirect('.')
 
-    @expose()
-    def _lookup(self, name, *remainder):
-        return GroupController(name), remainder
-
-
-class GroupController(BaseController):
-    def __init__(self, name):
-        self._group = M.ProjectRole.query.get(_id=ObjectId(name))
-
-    @with_trailing_slash
-    @expose('jinja:allura.ext.admin:templates/project_group.html')
-    def index(self, **kw):
-        if self._group.name in ('Admin', 'Developer', 'Member'):
-            show_settings = False
-            action = None
-        else:
-            show_settings = True
-            action = self._group.settings_href + 'update'
-        c.form = W.group_settings
-        return dict(
-            group=self._group,
-            show_settings=show_settings,
-            action=action)
-
-    @expose()
-    @h.vardec
-    @require_post()
-    @validate(W.group_settings)
-    def update(self, _id=None, delete=None, name=None, **kw):
-        pr = M.ProjectRole.by_name(name)
-        if pr and pr._id != _id._id:
-            flash('%s already exists' % name, 'error')
-            redirect('..')
-        if delete:
-            _id.delete()
-            M.AuditLog.log('delete group %s', _id.name)
-            flash('%s deleted' % name)
-            redirect('..')
-        M.AuditLog.log('update group name %s=>%s', _id.name, name)
-        _id.name = name
-        flash('%s updated' % name)
-        redirect('..')
-
 
 class AuditController(BaseController):
     @with_trailing_slash
diff --git a/Allura/allura/ext/admin/templates/project_group.html b/Allura/allura/ext/admin/templates/project_group.html
index 927b776..a84c095 100644
--- a/Allura/allura/ext/admin/templates/project_group.html
+++ b/Allura/allura/ext/admin/templates/project_group.html
@@ -16,8 +16,4 @@
        specific language governing permissions and limitations
        under the License.
 -#}
-{% if show_settings %}
 {{c.form.display(value=group, action=action)}}
-{% else %}
-<p>No settings available for reserved groups</p>
-{% endif %}
diff --git a/Allura/allura/ext/admin/widgets.py b/Allura/allura/ext/admin/widgets.py
index 4c877e5..d27f8fa 100644
--- a/Allura/allura/ext/admin/widgets.py
+++ b/Allura/allura/ext/admin/widgets.py
@@ -114,23 +114,6 @@ class PermissionCard(CardField):
         return role._id
 
 
-class GroupSettings(ff.CsrfForm):
-    submit_text = None
-
-    @property
-    def hidden_fields(self):
-        f = super(GroupSettings, self).hidden_fields
-        f.append(ew.HiddenField(name='_id', validator=V.Ming(M.ProjectRole)))
-        return f
-
-    class fields(ew_core.NameList):
-        name = ew.InputField(label='Name')
-
-    class buttons(ew_core.NameList):
-        save = ew.SubmitButton(label='Save')
-        delete = ew.SubmitButton(label='Delete Group')
-
-
 class NewGroupSettings(ff.AdminFormResponsive):
     submit_text = 'Save'
 
diff --git a/Allura/allura/tests/functional/test_admin.py b/Allura/allura/tests/functional/test_admin.py
index 004fe73..3b2c4df 100644
--- a/Allura/allura/tests/functional/test_admin.py
+++ b/Allura/allura/tests/functional/test_admin.py
@@ -662,9 +662,6 @@ class TestProjectAdmin(TestController):
         users = dev_holder.find('ul', {'class': 'users'}).findAll(
             'li', {'class': 'deleter'})
         assert 'test-user' in users[0]['data-user']
-        # Make sure we can open role page for builtin role
-        r = self.app.get('/admin/groups/' + developer_id +
-                         '/', validate_chunk=True)
 
     def test_new_admin_subscriptions(self):
         """Newly added admin must be subscribed to all the tools in the project"""
@@ -810,33 +807,21 @@ class TestProjectAdmin(TestController):
         role_holder = r.html.find('table', {'id': 'usergroup_admin'}).findAll('tr')[4]
         assert 'RoleNew1' in str(role_holder)
         role_id = role_holder['data-group']
-        r = self.app.get('/admin/groups/' + role_id + '/', validate_chunk=True)
-        r = self.app.post('/admin/groups/' + str(role_id) + '/update', params={'_id': role_id, 'name': 'Developer'})
-        assert 'error' in self.webflash(r)
-        assert 'already exists' in self.webflash(r)
-
-        with audits('update group name RoleNew1=>rleNew2'):
-            r = self.app.post('/admin/groups/' + str(role_id) + '/update',
-                              params={'_id': role_id, 'name': 'rleNew2'}).follow()
-        assert 'RoleNew1' not in r
-        assert 'rleNew2' in r
 
         # add test-user to role
         role_holder = r.html.find('table', {'id': 'usergroup_admin'}).findAll('tr')[4]
-        rleNew2_id = role_holder['data-group']
-        with audits('add user test-user to rleNew2'):
+        with audits('add user test-user to RoleNew1'):
             r = self.app.post('/admin/groups/add_user', params={
-                'role_id': rleNew2_id,
+                'role_id': role_id,
                 'username': 'test-user'})
 
-        with audits('delete group rleNew2'):
+        with audits('delete group RoleNew1'):
             r = self.app.post('/admin/groups/delete_group', params={
-                'group_name': 'rleNew2'})
+                'group_name': 'RoleNew1'})
         assert 'deleted' in self.webflash(r)
         r = self.app.get('/admin/groups/', status=200)
         roles = [str(t) for t in r.html.findAll('td', {'class': 'group'})]
         assert 'RoleNew1' not in roles
-        assert 'rleNew2' not in roles
 
         # make sure can still access homepage after one of user's roles were
         # deleted
diff --git a/Allura/allura/tests/functional/test_auth.py b/Allura/allura/tests/functional/test_auth.py
index a7c6893..f78760f 100644
--- a/Allura/allura/tests/functional/test_auth.py
+++ b/Allura/allura/tests/functional/test_auth.py
@@ -197,6 +197,28 @@ class TestAuth(TestController):
 
             # changing password covered in TestPasswordExpire
 
+    def test_login_disabled(self):
+        u = M.User.query.get(username='test-user')
+        u.disabled = True
+        r = self.app.get('/auth/', extra_environ={'username': '*anonymous'})
+        f = r.forms[0]
+        encoded = self.app.antispam_field_names(f)
+        f[encoded['username']] = 'test-user'
+        f[encoded['password']] = 'foo'
+        with audits('Failed login', user=True):
+            r = f.submit(extra_environ={'username': '*anonymous'})
+
+    def test_login_pending(self):
+        u = M.User.query.get(username='test-user')
+        u.pending = True
+        r = self.app.get('/auth/', extra_environ={'username': '*anonymous'})
+        f = r.forms[0]
+        encoded = self.app.antispam_field_names(f)
+        f[encoded['username']] = 'test-user'
+        f[encoded['password']] = 'foo'
+        with audits('Failed login', user=True):
+            r = f.submit(extra_environ={'username': '*anonymous'})
+
     def test_logout(self):
         self.app.extra_environ = {'disable_auth_magic': 'True'}
         nav_pattern = ('nav', {'class': 'nav-main'})
diff --git a/Allura/allura/tests/functional/test_site_admin.py b/Allura/allura/tests/functional/test_site_admin.py
index d7eebc6..8b23c1e 100644
--- a/Allura/allura/tests/functional/test_site_admin.py
+++ b/Allura/allura/tests/functional/test_site_admin.py
@@ -137,6 +137,15 @@ class TestSiteAdmin(TestController):
             url, extra_environ=dict(username='*anonymous'), status=302)
         r = self.app.get(url)
         assert 'math.ceil' in r, r
+        assert 'ready' in r, r
+
+        # test resubmit too
+        M.MonQTask.run_ready()
+        r = self.app.get(url)
+        assert 'complete' in r, r
+        r = r.forms['resubmit-task-form'].submit()
+        r = r.follow()
+        assert 'ready' in r, r
 
     def test_task_new(self):
         r = self.app.get('/nf/admin/task_manager/new')
diff --git a/Allura/allura/tests/functional/test_trovecategory.py b/Allura/allura/tests/functional/test_trovecategory.py
index 4bb6eed..346a9f1 100644
--- a/Allura/allura/tests/functional/test_trovecategory.py
+++ b/Allura/allura/tests/functional/test_trovecategory.py
@@ -18,7 +18,7 @@ from bs4 import BeautifulSoup
 import mock
 
 from tg import config
-from nose.tools import assert_equals, assert_true
+from nose.tools import assert_equals, assert_true, assert_in
 from ming.orm import session
 
 from allura import model as M
@@ -133,3 +133,20 @@ class TestTroveCategoryController(TestController):
         </ul>
         """.strip(), 'html.parser')
         assert_equals(str(expected), str(rendered_tree))
+
+    def test_delete(self):
+        self.create_some_cats()
+        session(M.TroveCategory).flush()
+        assert_equals(5, M.TroveCategory.query.find().count())
+
+        r = self.app.get('/categories/1')
+        form = r.forms[0]
+        r = form.submit()
+        assert_in("This category contains at least one sub-category, therefore it can't be removed",
+                  self.webflash(r))
+
+        r = self.app.get('/categories/2')
+        form = r.forms[0]
+        r = form.submit()
+        assert_in("Category removed", self.webflash(r))
+        assert_equals(4, M.TroveCategory.query.find().count())
\ No newline at end of file


[allura] 02/06: [#8340] test coverage for repo tasks/scripts

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/8340
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 0d6f0a86b8c8085da59d0f23a46557d66ef10593
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Tue Nov 12 13:07:09 2019 -0500

    [#8340] test coverage for repo tasks/scripts
---
 Allura/allura/scripts/refresh_last_commits.py |  2 +-
 ForgeGit/forgegit/tests/test_tasks.py         | 54 ++++++++++++++++++++++++++-
 2 files changed, 54 insertions(+), 2 deletions(-)

diff --git a/Allura/allura/scripts/refresh_last_commits.py b/Allura/allura/scripts/refresh_last_commits.py
index 49b2772..84399d8 100644
--- a/Allura/allura/scripts/refresh_last_commits.py
+++ b/Allura/allura/scripts/refresh_last_commits.py
@@ -46,7 +46,7 @@ class RefreshLastCommits(ScriptTask):
                 repo_types.append(repo_type)
             return repo_types
         parser = argparse.ArgumentParser(description='Using existing commit data, '
-                                         'refresh the last commit metadata in MongoDB. Run for all repos (no args), '
+                                         'refresh the "last commit" metadata in MongoDB. Run for all repos (no args), '
                                          'or restrict by neighborhood, project, or code tool mount point.')
         parser.add_argument('--nbhd', action='store', default='', dest='nbhd',
                             help='Restrict update to a particular neighborhood, e.g. /p/.')
diff --git a/ForgeGit/forgegit/tests/test_tasks.py b/ForgeGit/forgegit/tests/test_tasks.py
index 26d1ee5..812a8b4 100644
--- a/ForgeGit/forgegit/tests/test_tasks.py
+++ b/ForgeGit/forgegit/tests/test_tasks.py
@@ -14,18 +14,22 @@
 #       KIND, either express or implied.  See the License for the
 #       specific language governing permissions and limitations
 #       under the License.
-
+import logging
 import unittest
 import mock
+from testfixtures import LogCapture
 
 from ming.orm import ThreadLocalORMSession
 from tg import tmpl_context as c
 
 from alluratest.controller import setup_basic_test, setup_global_objects
+from allura.scripts.refreshrepo import RefreshRepo
+from allura.scripts.refresh_last_commits import RefreshLastCommits
 from allura.lib import helpers as h
 from allura.tasks import repo_tasks
 from allura import model as M
 from forgegit.tests import with_git
+from forgegit.tests.functional.test_controllers import _TestCase as GitRealDataBaseTestCase
 
 
 class TestGitTasks(unittest.TestCase):
@@ -58,3 +62,51 @@ class TestGitTasks(unittest.TestCase):
             M.main_orm_session.flush()
             f.assert_called_with('test_path', None, 'test_url')
             assert ns + 1 == M.Notification.query.find().count()
+
+
+class TestCoreAlluraTasks(GitRealDataBaseTestCase):
+    """
+    Not git-specific things we are testing, but the git tool is a useful standard repo type to use for it
+    """
+
+    def setUp(self):
+        super(TestCoreAlluraTasks, self).setUp()
+        self.setup_with_tools()
+
+    def _assert_logmsg_and_no_warnings_or_errors(self, logs, msg):
+        found_msg = False
+        for r in logs.records:
+            if msg in r.getMessage():
+                found_msg = True
+            if r.levelno > logging.INFO:
+                raise AssertionError('unexpected log {} {}'.format(r.levelname, r.getMessage()))
+        assert found_msg, 'Did not find {} in logs: {}'.format(msg, '\n'.join([str(r) for r in logs.records]))
+
+    def test_refreshrepo(self):
+        opts = RefreshRepo.parser().parse_args(
+            ['--nbhd', '/p/', '--project', 'test', '--clean', '--all', '--repo-types', 'git'])
+        with LogCapture() as logs:
+            RefreshRepo.execute(opts)
+        self._assert_logmsg_and_no_warnings_or_errors(logs, 'Refreshing ALL commits in ')
+
+        # run again with some different params
+        opts = RefreshRepo.parser().parse_args(
+            ['--nbhd', '/p/', '--project', 'test', '--clean-after', '2010-01-01T00:00:00'])
+        with LogCapture() as logs:
+            RefreshRepo.execute(opts)
+        self._assert_logmsg_and_no_warnings_or_errors(logs, 'Refreshing NEW commits in ')
+
+    def test_refresh_last_commits(self):
+        repo = c.app.repo
+        repo.refresh()
+
+        opts = RefreshLastCommits.parser().parse_args(
+            ['--nbhd', '/p/', '--project', 'test', '--clean', '--repo-types', 'git'])
+        with LogCapture() as logs:
+            RefreshLastCommits.execute(opts)
+
+        self._assert_logmsg_and_no_warnings_or_errors(logs, 'Refreshing all last commits ')
+
+        # mostly just making sure nothing errored, but here's at least one thing we can assert:
+        assert repo.status == 'ready'
+


[allura] 01/06: [#8340] remove ez_setup file

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/8340
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 7a502370509f3297f3c95c2aaaf98e000068b3dc
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Mon Nov 11 16:06:30 2019 -0500

    [#8340] remove ez_setup file
---
 Allura/ez_setup/README.txt           |  14 --
 Allura/ez_setup/__init__.py          | 260 -----------------------------------
 Allura/setup.py                      |   7 +-
 AlluraTest/alluratest/test_syntax.py |   1 -
 coverage-report-all.sh               |  41 ------
 5 files changed, 1 insertion(+), 322 deletions(-)

diff --git a/Allura/ez_setup/README.txt b/Allura/ez_setup/README.txt
deleted file mode 100644
index 77c986d..0000000
--- a/Allura/ez_setup/README.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-This directory exists so that Subversion-based projects can share a single
-copy of the ``ez_setup`` bootstrap module for ``setuptools``, and have it
-automatically updated in their projects when ``setuptools`` is updated.
-
-For your convenience, you may use the following svn:externals definition::
-
-    ez_setup svn://svn.eby-sarna.com/svnroot/ez_setup
-
-You can set this by executing this command in your project directory::
-
-    svn propedit svn:externals .
-
-And then adding the line shown above to the file that comes up for editing.
-Then, whenever you update your project, ``ez_setup`` will be updated as well.
diff --git a/Allura/ez_setup/__init__.py b/Allura/ez_setup/__init__.py
deleted file mode 100644
index b8e23b3..0000000
--- a/Allura/ez_setup/__init__.py
+++ /dev/null
@@ -1,260 +0,0 @@
-#!python
-
-#       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.
-
-"""Bootstrap setuptools installation
-
-If you want to use setuptools in your package's setup.py, just include this
-file in the same directory with it, and add this to the top of your setup.py::
-
-    from ez_setup import use_setuptools
-    use_setuptools()
-
-If you want to require a specific version of setuptools, set a download
-mirror, or use an alternate download directory, you can do so by supplying
-the appropriate options to ``use_setuptools()``.
-
-This file can also be run as a script to install or upgrade setuptools.
-"""
-import sys
-DEFAULT_VERSION = "0.6c7"
-DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3]
-
-md5_data = {
-    'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
-    'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
-    'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
-    'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
-    'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
-    'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
-    'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
-    'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
-    'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
-    'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
-    'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
-    'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
-    'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
-    'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
-    'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
-    'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f',
-    'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2',
-    'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc',
-    'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167',
-    'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64',
-    'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d',
-    'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20',
-    'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab',
-    'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53',
-    'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2',
-    'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e',
-    'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372',
-}
-
-import sys
-import os
-
-
-def _validate_md5(egg_name, data):
-    if egg_name in md5_data:
-        from md5 import md5
-        digest = md5(data).hexdigest()
-        if digest != md5_data[egg_name]:
-            print >>sys.stderr, (
-                "md5 validation of %s failed!  (Possible download problem?)"
-                % egg_name
-            )
-            sys.exit(2)
-    return data
-
-
-def use_setuptools(
-    version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
-    download_delay=15
-):
-    """Automatically find/download setuptools and make it available on sys.path
-
-    `version` should be a valid setuptools version number that is available
-    as an egg for download under the `download_base` URL (which should end with
-    a '/').  `to_dir` is the directory where setuptools will be downloaded, if
-    it is not already available.  If `download_delay` is specified, it should
-    be the number of seconds that will be paused before initiating a download,
-    should one be required.  If an older version of setuptools is installed,
-    this routine will print a message to ``sys.stderr`` and raise SystemExit in
-    an attempt to abort the calling script.
-    """
-    try:
-        import setuptools
-        if setuptools.__version__ == '0.0.1':
-            print >>sys.stderr, (
-                "You have an obsolete version of setuptools installed.  Please\n"
-                "remove it from your system entirely before rerunning this script."
-            )
-            sys.exit(2)
-    except ImportError:
-        egg = download_setuptools(
-            version, download_base, to_dir, download_delay)
-        sys.path.insert(0, egg)
-        import setuptools
-        setuptools.bootstrap_install_from = egg
-
-    import pkg_resources
-    try:
-        pkg_resources.require("setuptools>=" + version)
-
-    except pkg_resources.VersionConflict, e:
-        # XXX could we install in a subprocess here?
-        print >>sys.stderr, (
-            "The required version of setuptools (>=%s) is not available, and\n"
-            "can't be installed while this script is running. Please install\n"
-            " a more recent version first.\n\n(Currently using %r)"
-        ) % (version, e.args[0])
-        sys.exit(2)
-
-
-def download_setuptools(
-    version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
-    delay=15
-):
-    """Download setuptools from a specified location and return its filename
-
-    `version` should be a valid setuptools version number that is available
-    as an egg for download under the `download_base` URL (which should end
-    with a '/'). `to_dir` is the directory where the egg will be downloaded.
-    `delay` is the number of seconds to pause before an actual download attempt.
-    """
-    import urllib2
-    import shutil
-    egg_name = "setuptools-%s-py%s.egg" % (version, sys.version[:3])
-    url = download_base + egg_name
-    saveto = os.path.join(to_dir, egg_name)
-    src = dst = None
-    if not os.path.exists(saveto):  # Avoid repeated downloads
-        try:
-            from distutils import log
-            if delay:
-                log.warn("""
----------------------------------------------------------------------------
-This script requires setuptools version %s to run (even to display
-help).  I will attempt to download it for you (from
-%s), but
-you may need to enable firewall access for this script first.
-I will start the download in %d seconds.
-
-(Note: if this machine does not have network access, please obtain the file
-
-   %s
-
-and place it in this directory before rerunning this script.)
----------------------------------------------------------------------------""",
-                         version, download_base, delay, url
-                         )
-                from time import sleep
-                sleep(delay)
-            log.warn("Downloading %s", url)
-            src = urllib2.urlopen(url)
-            # Read/write all in one block, so we don't create a corrupt file
-            # if the download is interrupted.
-            data = _validate_md5(egg_name, src.read())
-            dst = open(saveto, "wb")
-            dst.write(data)
-        finally:
-            if src:
-                src.close()
-            if dst:
-                dst.close()
-    return os.path.realpath(saveto)
-
-
-def main(argv, version=DEFAULT_VERSION):
-    """Install or upgrade setuptools and EasyInstall"""
-
-    try:
-        import setuptools
-    except ImportError:
-        egg = None
-        try:
-            egg = download_setuptools(version, delay=0)
-            sys.path.insert(0, egg)
-            from setuptools.command.easy_install import main
-            return main(list(argv) + [egg])   # we're done here
-        finally:
-            if egg and os.path.exists(egg):
-                os.unlink(egg)
-    else:
-        if setuptools.__version__ == '0.0.1':
-            # tell the user to uninstall obsolete version
-            use_setuptools(version)
-
-    req = "setuptools>=" + version
-    import pkg_resources
-    try:
-        pkg_resources.require(req)
-    except pkg_resources.VersionConflict:
-        try:
-            from setuptools.command.easy_install import main
-        except ImportError:
-            from easy_install import main
-        main(list(argv) + [download_setuptools(delay=0)])
-        sys.exit(0)  # try to force an exit
-    else:
-        if argv:
-            from setuptools.command.easy_install import main
-            main(argv)
-        else:
-            print "Setuptools version", version, "or greater has been installed."
-            print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
-
-
-def update_md5(filenames):
-    """Update our built-in md5 registry"""
-
-    import re
-    from md5 import md5
-
-    for name in filenames:
-        base = os.path.basename(name)
-        f = open(name, 'rb')
-        md5_data[base] = md5(f.read()).hexdigest()
-        f.close()
-
-    data = ["    %r: %r,\n" % it for it in md5_data.items()]
-    data.sort()
-    repl = "".join(data)
-
-    import inspect
-    srcfile = inspect.getsourcefile(sys.modules[__name__])
-    f = open(srcfile, 'rb')
-    src = f.read()
-    f.close()
-
-    match = re.search("\nmd5_data = {\n([^}]+)}", src)
-    if not match:
-        print >>sys.stderr, "Internal error!"
-        sys.exit(2)
-
-    src = src[:match.start(1)] + repl + src[match.end(1):]
-    f = open(srcfile, 'w')
-    f.write(src)
-    f.close()
-
-
-if __name__ == '__main__':
-    if len(sys.argv) > 2 and sys.argv[1] == '--md5update':
-        update_md5(sys.argv[2:])
-    else:
-        main(sys.argv[1:])
diff --git a/Allura/setup.py b/Allura/setup.py
index e9d7878..6f25a17 100644
--- a/Allura/setup.py
+++ b/Allura/setup.py
@@ -17,12 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
-try:
-    from setuptools import setup, find_packages
-except ImportError:
-    from ez_setup import use_setuptools
-    use_setuptools()
-    from setuptools import setup, find_packages
+from setuptools import setup, find_packages
 
 exec open('allura/version.py').read()
 
diff --git a/AlluraTest/alluratest/test_syntax.py b/AlluraTest/alluratest/test_syntax.py
index a77d109..cc4646d 100644
--- a/AlluraTest/alluratest/test_syntax.py
+++ b/AlluraTest/alluratest/test_syntax.py
@@ -57,7 +57,6 @@ def test_no_prints():
         'Allura/allura/command/',
         'Allura/ldap-setup.py',
         'Allura/ldap-userconfig.py',
-        'Allura/ez_setup/',
         'Allura/allura/lib/AsciiDammit.py',
         '/scripts/',
         'ForgeSVN/setup.py',
diff --git a/coverage-report-all.sh b/coverage-report-all.sh
deleted file mode 100755
index c4e7fb6..0000000
--- a/coverage-report-all.sh
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/bin/sh
-#
-#       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.
-#
-# This script calculates global codebase coverage, based on coverage
-# of individual application packages.
-#
-
-DIRS="Allura Forge*"
-EXCLUDES='*/migrations*,*/ez_setup/*,*allura/command/*'
-
-for dir in $DIRS; do
-    if [ ! -f $dir/.coverage ]; then
-        echo "$dir/.coverage not found - please run ./run_test --with-coverage first"
-    else
-        ln -sf $dir/.coverage .coverage.$dir
-    fi
-done
-
-coverage combine
-coverage report --ignore-errors --include='Allura/*,Forge*' --omit=$EXCLUDES
-
-if [ "$1" = "--html" ]; then
-    coverage html --ignore-errors --include='Allura/*,Forge*' --omit=$EXCLUDES -d report.coverage
-    coverage html --ignore-errors --include='Allura/*' --omit=$EXCLUDES -d Allura.coverage
-fi


[allura] 04/06: [#8340] tracker test coverage and remove dead code

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/8340
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 1a797b5e9a89715411515f511bf11abb2b2851ea
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Tue Nov 12 17:00:42 2019 -0500

    [#8340] tracker test coverage and remove dead code
---
 .../forgetracker/templates/tracker/bin.html        |  1 -
 .../forgetracker/templates/tracker/new_bin.html    | 27 -----------------
 .../forgetracker/tests/functional/test_root.py     | 21 +++++++++++++
 ForgeTracker/forgetracker/tests/test_app.py        | 35 ++++++++++++++++++++++
 ForgeTracker/forgetracker/tracker_main.py          | 27 +++--------------
 5 files changed, 60 insertions(+), 51 deletions(-)

diff --git a/ForgeTracker/forgetracker/templates/tracker/bin.html b/ForgeTracker/forgetracker/templates/tracker/bin.html
index 2f33ff3..d919ee6 100644
--- a/ForgeTracker/forgetracker/templates/tracker/bin.html
+++ b/ForgeTracker/forgetracker/templates/tracker/bin.html
@@ -17,7 +17,6 @@
        under the License.
 -#}
 {% extends g.theme.master %}
-{% do g.register_forge_css('css/hilite.css') %}
 {% do g.register_app_css('css/tracker.css') %}
 
 {% block title %}{{c.project.name}} / {{app.config.options.mount_label}} / Saved Searches{% endblock %}
diff --git a/ForgeTracker/forgetracker/templates/tracker/new_bin.html b/ForgeTracker/forgetracker/templates/tracker/new_bin.html
deleted file mode 100644
index 6af78a7..0000000
--- a/ForgeTracker/forgetracker/templates/tracker/new_bin.html
+++ /dev/null
@@ -1,27 +0,0 @@
-{#-
-       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.
--#}
-{% extends g.theme.master %}
-
-{% block title %}{{c.project.name}} / {{c.app.config.options.mount_label}} / New search for {{q}}{% endblock %}
-
-{% block header %}New search for {{q}}{% endblock %}
-
-{% block content %}
-{{c.bin_form.display(value=dict(summary=q,terms=q), action='save_bin')}}
-{% endblock %}
\ No newline at end of file
diff --git a/ForgeTracker/forgetracker/tests/functional/test_root.py b/ForgeTracker/forgetracker/tests/functional/test_root.py
index 5eb9315..4456465 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_root.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_root.py
@@ -354,6 +354,27 @@ class TestFunctionalController(TrackerTestController):
         r = self.app.get('/bugs/milestone_counts')
         assert_equal(r.body, json.dumps(counts))
 
+    def test_bin_counts(self):
+        self.new_ticket(summary='test new')
+        self.new_ticket(summary='test new private', private=True)
+        M.MonQTask.run_ready()
+
+        r = self.app.get('/bugs/bin_counts')
+        assert_equal(r.json, {"bin_counts": [{"count": 2, "label": "Changes"},
+                                             {"count": 0, "label": "Closed Tickets"},
+                                             {"count": 2, "label": "Open Tickets"}]})
+
+        """
+        forgetracker.model.ticket.Globals.bin_count doesn't do a permission check like corresponding milestone_count
+        
+        # Private tickets shouldn't be included in counts if user doesn't
+        # have read access to private tickets.
+        r = self.app.get('/bugs/bin_counts', extra_environ=dict(username='*anonymous'))
+        assert_equal(r.json, {"bin_counts": [{"count": 1, "label": "Changes"},
+                                             {"count": 0, "label": "Closed Tickets"},
+                                             {"count": 1, "label": "Open Tickets"}]})
+        """
+
     def test_milestone_progress(self):
         self.new_ticket(summary='Ticket 1', **{'_milestone': '1.0'})
         self.new_ticket(summary='Ticket 2', **{'_milestone': '1.0',
diff --git a/ForgeTracker/forgetracker/tests/test_app.py b/ForgeTracker/forgetracker/tests/test_app.py
index cfc0596..8c416a9 100644
--- a/ForgeTracker/forgetracker/tests/test_app.py
+++ b/ForgeTracker/forgetracker/tests/test_app.py
@@ -24,6 +24,8 @@ from nose.tools import assert_equal, assert_true
 from tg import tmpl_context as c
 from cgi import FieldStorage
 from cStringIO import StringIO
+
+from alluratest.controller import setup_basic_test
 from ming.orm import ThreadLocalORMSession
 
 from allura import model as M
@@ -32,6 +34,39 @@ from forgetracker import model as TM
 from forgetracker.tests.functional.test_root import TrackerTestController
 
 
+class TestApp(object):
+
+    def setUp(self):
+        setup_basic_test()
+
+    @td.with_tracker
+    def test_inbound_email(self):
+        ticket = TM.Ticket.new()
+        ticket.summary = 'test ticket'
+        ticket.description = 'test description'
+
+        # send a message with no ticket matching it
+        message_id = '123@localhost'
+        message = 'test message'
+        msg = dict(payload=message, message_id=message_id, headers={'Subject': 'test'})
+        c.app.handle_message('1', msg)
+        # message gets added as a post on the ticket
+        post = M.Post.query.get(_id=message_id)
+        assert_equal(post["text"], message)
+
+    @td.with_tracker
+    def test_inbound_email_no_match(self):
+        # send a message with no ticket matching it
+        message_id = '123@localhost'
+        message = 'test message'
+        msg = dict(payload=message, message_id=message_id, headers={'Subject': 'test'})
+        # no ticket matching it
+        c.app.handle_message('6789', msg)
+        # no new message
+        post = M.Post.query.get(_id=message_id)
+        assert_equal(post, None)
+
+
 class TestBulkExport(TrackerTestController):
 
     @td.with_tracker
diff --git a/ForgeTracker/forgetracker/tracker_main.py b/ForgeTracker/forgetracker/tracker_main.py
index f4861a8..5f2f6e8 100644
--- a/ForgeTracker/forgetracker/tracker_main.py
+++ b/ForgeTracker/forgetracker/tracker_main.py
@@ -282,7 +282,9 @@ class ForgeTrackerApp(Application):
         except:
             log.exception('Error getting ticket %s', topic)
             return
-        if ticket.discussion_disabled:
+        if not ticket:
+            log.info('No such ticket num: %s', topic)
+        elif ticket.discussion_disabled:
             log.info('Discussion disabled for ticket %s', ticket.ticket_num)
         else:
             self.handle_artifact_message(ticket, message)
@@ -1150,22 +1152,10 @@ class BinController(BaseController, AdminControllerMixin):
         return dict(bins=self.app.bins, count=count, app=self.app)
 
     @with_trailing_slash
-    @expose('jinja:forgetracker:templates/tracker/bin.html')
-    def bins(self):
-        count = len(self.app.bins)
-        return dict(bins=self.app.bins, count=count, app=self.app)
-
-    @with_trailing_slash
-    @expose('jinja:forgetracker:templates/tracker/new_bin.html')
-    def newbin(self, q=None, **kw):
-        c.bin_form = W.bin_form
-        return dict(q=q or '', bin=bin or '', modelname='Bin', page='New Bin', globals=self.app.globals)
-
-    @with_trailing_slash
     @h.vardec
     @expose('jinja:forgetracker:templates/tracker/bin.html')
     @require_post()
-    @validate(W.bin_form, error_handler=newbin)
+    @validate(W.bin_form, error_handler=index)
     def save_bin(self, **bin_form):
         """Update existing search bin or create a new one.
 
@@ -1204,15 +1194,6 @@ class BinController(BaseController, AdminControllerMixin):
         self.app.globals.invalidate_bin_counts()
         redirect('.')
 
-    @with_trailing_slash
-    @expose()
-    @require_post()
-    @validate(validators=dict(bin=V.Ming(TM.Bin)))
-    def delbin(self, bin=None):
-        require(lambda: bin.app_config_id == self.app.config._id)
-        bin.delete()
-        redirect(request.referer or '/')
-
     @without_trailing_slash
     @h.vardec
     @expose('jinja:forgetracker:templates/tracker/bin.html')