commit bfcb6e5182fea6d01b69423df770ad4d8be0eb04
Author: Jun Omae <jun66j5@gmail.com>
Date:   Wed Dec 7 04:59:36 2016 +0900

    (#12981) make compatible with Trac 1.2 and add unit tests

diff --git a/autocompleteusers/autocompleteusers.py b/autocompleteusers/autocompleteusers.py
index 732b3aa..36b2f87 100644
--- a/autocompleteusers/autocompleteusers.py
+++ b/autocompleteusers/autocompleteusers.py
@@ -10,6 +10,7 @@ import fnmatch
 
 from trac.config import ListOption
 from trac.core import Component, implements
+from trac.util.text import obfuscate_email_address
 from trac.web.api import IRequestFilter, IRequestHandler
 from trac.web.chrome import Chrome, ITemplateProvider, ITemplateStreamFilter
 from trac.web.chrome import add_script, add_stylesheet
@@ -130,12 +131,14 @@ class AutocompleteUsers(Component):
         # returning users without session data, but there currently seems
         # to be no other way to handle this.
         query = req.args.get('q', '').lower()
-        db = self.env.get_db_cnx()
+        db = self.env.get_read_db()
         cursor = db.cursor()
-        cursor.execute("""SELECT DISTINCT username FROM permission""")
-        usernames = [user[0] for user in self.env.get_known_users()]
-        return sorted([row[0] for row in cursor if not row[0] in usernames
-                       and row[0].lower().startswith(query)])
+        cursor.execute("SELECT DISTINCT username FROM permission")
+        perm_users = [row[0] for row in cursor]
+        known_users = set(item[0] for item in self.env.get_known_users())
+        return sorted(user for user in perm_users
+                      if user not in known_users and
+                      user.lower().startswith(query))
 
     def _get_users(self, req):
         # instead of known_users, could be
@@ -147,11 +150,13 @@ class AutocompleteUsers(Component):
         query = req.args.get('q', '').lower()
 
         # user names, email addresses, full names
+        can_view = Chrome(self.env).show_email_addresses or \
+            'EMAIL_VIEW' in req.perm
         users = []
         for user_data in self.env.get_known_users():
-            user_data = [user is not None
-                         and Chrome(self.env).format_author(req, user) or ''
-                         for user in user_data]
+            user_data = [value or '' for value in user_data]
+            if not can_view and user_data[EMAIL]:
+                user_data[EMAIL] = obfuscate_email_address(user_data[EMAIL])
             for index, field in enumerate((USER, EMAIL, NAME)):  # ordered by how they appear
                 value = user_data[field].lower()
 
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_autocompleteusers.py b/tests/test_autocompleteusers.py
new file mode 100644
index 0000000..2d27c7f
--- /dev/null
+++ b/tests/test_autocompleteusers.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2016 Jun Omae <jun66j5@gmail.com>
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution.
+
+from trac.test import EnvironmentStub, MockRequest
+from trac.web.api import RequestDone
+
+from autocompleteusers.autocompleteusers import AutocompleteUsers
+
+
+class Test(object):
+
+    def setup_method(self, method):
+        self.env = EnvironmentStub(default_data=True)
+        self.mod = AutocompleteUsers(self.env)
+
+    def teardown_method(self, method):
+        self.env.reset_db()
+
+    if hasattr(EnvironmentStub, 'insert_users'):
+        def _insert_user(self, username, name, email):
+            self.env.insert_users([(username, name, email)])
+    else:
+        def _insert_user(self, username, name, email):
+            self.env.known_users.append((username, name, email))
+
+    def _insert_test_users(self):
+        self._insert_user('alice', u'Alïcé', 'alice@example.org')
+        self._insert_user('charlie', None, 'charlie@example.org')
+        self._insert_user('bob', u'Böb', None)
+
+    def _process_request(self, req):
+        assert self.mod.match_request(req)
+        try:
+            self.mod.process_request(req)
+            raise AssertionError('not raising RequestDone')
+        except RequestDone:
+            return req.response_sent.getvalue().decode('utf-8')
+
+    def test_no_users(self):
+        req = MockRequest(self.env, path_info='/subjects', args={'users': '1'})
+        assert '' == self._process_request(req)
+
+    def test_users(self):
+        self._insert_test_users()
+        req = MockRequest(self.env, path_info='/subjects', args={'users': '1'})
+        assert self._process_request(req) == \
+            u'alice|<alice@example.org> |Alïcé\n' \
+            u'bob||Böb\n' \
+            u'charlie|<charlie@example.org> |'
+        req = MockRequest(self.env, path_info='/subjects',
+                          args={'users': '1', 'q': 'cH'})
+        assert self._process_request(req) == u'charlie|<charlie@example.org> |'
+
+    def test_users_without_email_view(self):
+        self._insert_test_users()
+        req = MockRequest(self.env, path_info='/subjects', args={'users': '1'})
+        req.perm = {}  # no permissions
+        assert self._process_request(req) == \
+            u'alice|<alice@…> |Alïcé\n' \
+            u'bob||Böb\n' \
+            u'charlie|<charlie@…> |'
+        req = MockRequest(self.env, path_info='/subjects',
+                          args={'users': '1', 'q': 'B'})
+        assert self._process_request(req) == u'bob||Böb'
+
+    def test_users_with_groups(self):
+        self._insert_test_users()
+        req = MockRequest(self.env, path_info='/subjects',
+                          args={'users': '1', 'groups': '1'})
+        assert self._process_request(req) == \
+            u'alice|<alice@example.org> |Alïcé\n' \
+            u'bob||Böb\n' \
+            u'charlie|<charlie@example.org> |\n' \
+            u'anonymous||group\n' \
+            u'authenticated||group'
+        req = MockRequest(self.env, path_info='/subjects',
+                          args={'users': '1', 'groups': '1', 'q': 'An'})
+        assert self._process_request(req) == u'anonymous||group'
diff --git a/tox.ini b/tox.ini
index de39dda..755d4f5 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,8 +1,8 @@
 [tox]
-envlist = py26, py27
+envlist = {py26,py27}-trac{1.0,1.2}
 indexserver =
-    testrun = http://pypi.testrun.org
-    pypi = http://pypi.python.org/simple
+    testrun = https://pypi.testrun.org
+    pypi = https://pypi.python.org/simple
 
 [testenv]
 deps =
@@ -11,7 +11,8 @@ deps =
     :pypi:pytest-pep8
     :pypi:pytest-flakes
     :pypi:pytest-capturelog
-    :pypi:trac
+    trac1.0: :pypi:trac>=1.0,<1.1
+    trac1.2: :pypi:trac>=1.2,<1.3
 
 commands =
-    py.test -v --pep8 --flakes --clearcache --doctest-modules autocompleteusers
+    py.test -v --pep8 --flakes --cache-clear --doctest-modules autocompleteusers tests
