You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by jo...@apache.org on 2013/08/13 16:07:54 UTC

[1/3] git commit: [#6464] Added tests for GC tracker attachments and comments

Updated Branches:
  refs/heads/cj/6464 ff155b60f -> 243bfb7f0 (forced update)


[#6464] Added tests for GC tracker attachments and comments

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/ad3302b3
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/ad3302b3
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/ad3302b3

Branch: refs/heads/cj/6464
Commit: ad3302b32717eb33dddc6c40f09e92147f9a9308
Parents: eadde65
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Fri Aug 9 16:25:01 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Fri Aug 9 16:59:27 2013 +0000

----------------------------------------------------------------------
 .../forgeimporters/google/__init__.py           | 10 +++-
 .../tests/data/google/test-issue.html           | 38 +++++++++++++
 .../tests/google/test_extractor.py              | 60 ++++++++++++++++++++
 .../forgeimporters/tests/google/test_tracker.py |  4 +-
 4 files changed, 107 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ad3302b3/ForgeImporters/forgeimporters/google/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/__init__.py b/ForgeImporters/forgeimporters/google/__init__.py
index f39fcb1..c50fcea 100644
--- a/ForgeImporters/forgeimporters/google/__init__.py
+++ b/ForgeImporters/forgeimporters/google/__init__.py
@@ -152,8 +152,12 @@ class GoogleCodeProjectExtractor(object):
         return self.page.find(id='hc0').find('span', 'date').get('title')
 
     def get_issue_mod_date(self):
-        last_update = Comment(self.page.findAll('div', 'issuecomment')[-1])
-        return last_update.created_date
+        comments = self.page.findAll('div', 'issuecomment')
+        if comments:
+            last_update = Comment(comments[-1])
+            return last_update.created_date
+        else:
+            return self.get_issue_created_date()
 
     def get_issue_creator(self):
         a = self.page.find(id='hc0').find('a', 'userlink')
@@ -197,7 +201,7 @@ class Comment(object):
     def __init__(self, tag):
         self.author = UserLink(tag.find('span', 'author').find('a', 'userlink'))
         self.created_date = tag.find('span', 'date').get('title')
-        self.body = _as_text(tag.find('pre'))
+        self.body = _as_text(tag.find('pre')).strip()
         self._get_updates(tag)
         self._get_attachments(tag)
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ad3302b3/ForgeImporters/forgeimporters/tests/data/google/test-issue.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/data/google/test-issue.html b/ForgeImporters/forgeimporters/tests/data/google/test-issue.html
index 04667ac..dbef54e 100644
--- a/ForgeImporters/forgeimporters/tests/data/google/test-issue.html
+++ b/ForgeImporters/forgeimporters/tests/data/google/test-issue.html
@@ -282,6 +282,44 @@ That's all
 
 
 </pre>
+<div class="attachments">
+<table cellspacing="3" cellpadding="2" border="0">
+<tr><td width="20">
+<a href="//allura-google-importer.googlecode.com/issues/attachment?aid=70000000&amp;name=at1.txt&amp;token=3REU1M3JUUMt0rJUg7ldcELt6LA%3A1376059941255">
+<img width="15" height="15" src="http://www.gstatic.com/codesite/ph/images/paperclip.gif" border="0" />
+</a>
+</td>
+<td style="min-width:16em" valign="top">
+<b>at1.txt</b>
+<br />
+ 13 bytes
+
+
+ &nbsp; <a href="../../allura-google-importer/issues/attachmentText?id=7&amp;aid=70000000&amp;name=at1.txt&amp;token=3REU1M3JUUMt0rJUg7ldcELt6LA%3A1376059941255" target="_blank">View</a>
+
+ &nbsp; <a href="//allura-google-importer.googlecode.com/issues/attachment?aid=70000000&amp;name=at1.txt&amp;token=3REU1M3JUUMt0rJUg7ldcELt6LA%3A1376059941255">Download</a>
+</td>
+</tr>
+</table>
+<table cellspacing="3" cellpadding="2" border="0">
+<tr><td width="20">
+<a href="//allura-google-importer.googlecode.com/issues/attachment?aid=70000001&amp;name=at2.txt&amp;token=C9Hn4s1-g38hlSggRGo65VZM1ys%3A1376059941255">
+<img width="15" height="15" src="http://www.gstatic.com/codesite/ph/images/paperclip.gif" border="0" />
+</a>
+</td>
+<td style="min-width:16em" valign="top">
+<b>at2.txt</b>
+<br />
+ 13 bytes
+
+
+ &nbsp; <a href="../../allura-google-importer/issues/attachmentText?id=7&amp;aid=70000001&amp;name=at2.txt&amp;token=C9Hn4s1-g38hlSggRGo65VZM1ys%3A1376059941255" target="_blank">View</a>
+
+ &nbsp; <a href="//allura-google-importer.googlecode.com/issues/attachment?aid=70000001&amp;name=at2.txt&amp;token=C9Hn4s1-g38hlSggRGo65VZM1ys%3A1376059941255">Download</a>
+</td>
+</tr>
+</table>
+</div>
 </div>
 <div class="cursor_off vt issuecomment" id="hc1">
 <div style="float:right; margin-right:.3em; text-align:right">

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ad3302b3/ForgeImporters/forgeimporters/tests/google/test_extractor.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_extractor.py b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
index 9d176f6..4468b30 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_extractor.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
@@ -126,6 +126,7 @@ class TestGoogleCodeProjectExtractor(TestCase):
         self.assertEqual(gpe.get_issue_status(), '')
         self.assertEqual(gpe.get_issue_attachments(), [])
         self.assertEqual(list(gpe.iter_comments()), [])
+        self.assertEqual(gpe.get_issue_mod_date(), 'Thu Aug  8 14:56:23 2013')
 
     def test_get_issue_basic_fields(self):
         test_issue = open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/test-issue.html')).read()
@@ -175,3 +176,62 @@ class TestGoogleCodeProjectExtractor(TestCase):
                 'OpSys-Windows',
                 'OpSys-OSX',
             ])
+
+    def test_get_issue_attachments(self):
+        test_issue = open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/test-issue.html')).read()
+        gpe = self._make_extractor(test_issue)
+        attachments = gpe.get_issue_attachments()
+        self.assertEqual(len(attachments), 2)
+        self.assertEqual(attachments[0].filename, 'at1.txt')
+        self.assertEqual(attachments[0].url, 'http://allura-google-importer.googlecode.com/issues/attachment?aid=70000000&name=at1.txt&token=3REU1M3JUUMt0rJUg7ldcELt6LA%3A1376059941255')
+        self.assertIsNone(attachments[0].type)
+        self.assertEqual(attachments[1].filename, 'at2.txt')
+        self.assertEqual(attachments[1].url, 'http://allura-google-importer.googlecode.com/issues/attachment?aid=70000001&name=at2.txt&token=C9Hn4s1-g38hlSggRGo65VZM1ys%3A1376059941255')
+        self.assertIsNone(attachments[1].type)
+
+    def test_iter_comments(self):
+        test_issue = open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/test-issue.html')).read()
+        gpe = self._make_extractor(test_issue)
+        comments = list(gpe.iter_comments())
+        self.assertEqual(len(comments), 4)
+        expected = [
+                {
+                    'author.name': 'john...@gmail.com',
+                    'author.link': 'http://code.google.com/u/101557263855536553789/',
+                    'created_date': 'Thu Aug  8 15:35:15 2013',
+                    'body': 'Test *comment* is a comment',
+                    'updates': {'Status:': 'Started', 'Labels:': '-OpSys-Linux OpSys-Windows'},
+                    'attachments': ['at2.txt'],
+                },
+                {
+                    'author.name': 'john...@gmail.com',
+                    'author.link': 'http://code.google.com/u/101557263855536553789/',
+                    'created_date': 'Thu Aug  8 15:35:34 2013',
+                    'body': 'Another comment',
+                    'updates': {},
+                    'attachments': [],
+                },
+                {
+                    'author.name': 'john...@gmail.com',
+                    'author.link': 'http://code.google.com/u/101557263855536553789/',
+                    'created_date': 'Thu Aug  8 15:36:39 2013',
+                    'body': 'Last comment',
+                    'updates': {},
+                    'attachments': ['at4.txt', 'at1.txt'],
+                },
+                {
+                    'author.name': 'john...@gmail.com',
+                    'author.link': 'http://code.google.com/u/101557263855536553789/',
+                    'created_date': 'Thu Aug  8 15:36:57 2013',
+                    'body': 'Oh, I forgot one',
+                    'updates': {'Labels:': 'OpSys-OSX'},
+                    'attachments': [],
+                },
+            ]
+        for actual, expected in zip(comments, expected):
+            self.assertEqual(actual.author.name, expected['author.name'])
+            self.assertEqual(actual.author.link, expected['author.link'])
+            self.assertEqual(actual.created_date, expected['created_date'])
+            self.assertEqual(actual.body, expected['body'])
+            self.assertEqual(actual.updates, expected['updates'])
+            self.assertEqual([a.filename for a in actual.attachments], expected['attachments'])

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ad3302b3/ForgeImporters/forgeimporters/tests/google/test_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
index 62493bd..a1f0a28 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
@@ -116,7 +116,7 @@ class TestTrackerImporter(TestCase):
             return u
         issue = mock.Mock(
                 get_issue_summary=lambda:'summary',
-                get_issue_description=lambda:'description',
+                get_issue_description=lambda:'my *description* fool',
                 get_issue_status=lambda:'status',
                 get_issue_created_date=lambda:'created_date',
                 get_issue_mod_date=lambda:'mod_date',
@@ -128,7 +128,7 @@ class TestTrackerImporter(TestCase):
             dt.strptime.side_effect = lambda s,f: s
             importer.process_fields(ticket, issue)
             self.assertEqual(ticket.summary, 'summary')
-            self.assertEqual(ticket.description, '*Originally created by:* [cname](clink)\n*Originally owned by:* [oname](olink)\n\ndescription')
+            self.assertEqual(ticket.description, '*Originally created by:* [cname](clink)\n*Originally owned by:* [oname](olink)\n\nmy \*description\* fool')
             self.assertEqual(ticket.status, 'status')
             self.assertEqual(ticket.created_date, 'created_date')
             self.assertEqual(ticket.mod_date, 'mod_date')


[3/3] git commit: [#6464] Fixed test failing due to refactor

Posted by jo...@apache.org.
[#6464] Fixed test failing due to refactor

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/243bfb7f
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/243bfb7f
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/243bfb7f

Branch: refs/heads/cj/6464
Commit: 243bfb7f0f801779647ef470131e556e610c1d87
Parents: 92b6d36
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Tue Aug 13 14:07:38 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Tue Aug 13 14:07:38 2013 +0000

----------------------------------------------------------------------
 ForgeImporters/forgeimporters/tests/google/test_tracker.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/243bfb7f/ForgeImporters/forgeimporters/tests/google/test_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
index 3efd97d..70ea3ad 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
@@ -212,8 +212,8 @@ class TestTrackerImporter(TestCase):
                     'options': set(['foo', 'bar']),
                 },
             }
-        importer.postprocess_custom_fields()
-        self.assertEqual(sorted(c.app.globals.custom_fields, key=itemgetter('name')), [
+        custom_fields = importer.postprocess_custom_fields()
+        self.assertItemsEqual(custom_fields, [
                 {
                     'name': '_foo',
                     'type': 'string',


[2/3] git commit: [#6464] Fixed issue with custom fields not persisting and added tests

Posted by jo...@apache.org.
[#6464] Fixed issue with custom fields not persisting and added tests

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/92b6d367
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/92b6d367
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/92b6d367

Branch: refs/heads/cj/6464
Commit: 92b6d3675b570c1635fc7631205ca5b1af61aa5a
Parents: ad3302b
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Mon Aug 12 22:25:23 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Mon Aug 12 22:25:27 2013 +0000

----------------------------------------------------------------------
 Allura/allura/lib/helpers.py                    |   4 +-
 .../forgeimporters/google/__init__.py           |  19 ++
 ForgeImporters/forgeimporters/google/tracker.py |  65 ++---
 .../tests/google/functional/__init__.py         |  17 ++
 .../tests/google/functional/test_tracker.py     | 260 +++++++++++++++++++
 .../forgeimporters/tests/google/test_tracker.py |  25 +-
 6 files changed, 341 insertions(+), 49 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/92b6d367/Allura/allura/lib/helpers.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index 9f6fbfc..3346901 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -96,7 +96,7 @@ re_preserve_spaces = re.compile(r'''
     ''', re.VERBOSE)
 re_angle_bracket_open = re.compile('<')
 re_angle_bracket_close = re.compile('>')
-md_chars_matcher_all = re.compile(r"([`\*_{}\[\]\(\)#!\\.+-])")
+md_chars_matcher_all = re.compile(r"([`\*_{}\[\]\(\)#!\\\.+-])")
 
 def make_safe_path_portion(ustr, relaxed=True):
     """Return an ascii representation of ``ustr`` that conforms to mount point
@@ -896,7 +896,7 @@ def plain2markdown(text, preserve_multiple_spaces=False, has_html_entities=False
         text = html2text.escape_md_section(text, snob=True)
     except ImportError:
         # fall back to just escaping any MD-special chars
-        text = md_chars_matcher.sub(r"\\\\1", text)
+        text = md_chars_matcher_all.sub(r"\\\1", text)
     # prevent < and > from becoming tags
     text = re_angle_bracket_open.sub('&lt;', text)
     text = re_angle_bracket_close.sub('&gt;', text)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/92b6d367/ForgeImporters/forgeimporters/google/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/__init__.py b/ForgeImporters/forgeimporters/google/__init__.py
index c50fcea..389b9e4 100644
--- a/ForgeImporters/forgeimporters/google/__init__.py
+++ b/ForgeImporters/forgeimporters/google/__init__.py
@@ -29,6 +29,7 @@ import logging
 
 from BeautifulSoup import BeautifulSoup
 
+from allura.lib import helpers as h
 from allura import model as M
 
 
@@ -221,6 +222,24 @@ class Comment(object):
         else:
             self.attachments = []
 
+    @property
+    def annotated_text(self):
+        text = (
+                u'*Originally posted by:* [{author.name}]({author.link})\n'
+                u'\n'
+                u'{body}\n'
+                u'\n'
+                u'{updates}'
+            ).format(
+                author=self.author,
+                body=h.plain2markdown(self.body, True),
+                updates='\n'.join(
+                        '**%s** %s' % (k,v)
+                        for k,v in self.updates.items()
+                    ),
+            )
+        return text
+
 class Attachment(object):
     def __init__(self, tag):
         self.filename = _as_text(tag).strip().split()[0]

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/92b6d367/ForgeImporters/forgeimporters/google/tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tracker.py b/ForgeImporters/forgeimporters/google/tracker.py
index bd3a57f..fefd695 100644
--- a/ForgeImporters/forgeimporters/google/tracker.py
+++ b/ForgeImporters/forgeimporters/google/tracker.py
@@ -19,6 +19,7 @@ from collections import defaultdict
 from datetime import datetime
 
 from pylons import tmpl_context as c
+from pylons import app_globals as g
 from ming.orm import session, ThreadLocalORMSession
 
 from allura import model as M
@@ -44,22 +45,27 @@ class GoogleCodeTrackerImporter(ToolImporter):
 
     def import_tool(self, project, user, project_name, mount_point=None,
             mount_label=None, **kw):
-        c.app = project.install_app('tickets', mount_point, mount_label)
+        app = project.install_app('tickets', mount_point, mount_label)
+        app.globals.open_status_names = 'New Accepted Started'
+        app.globals.closed_status_names = 'Fixed Verified Invalid Duplicate WontFix Done'
         ThreadLocalORMSession.flush_all()
-        c.app.globals.open_status_names = 'New Accepted Started'
-        c.app.globals.closed_status_names = 'Fixed Verified Invalid Duplicate WontFix Done'
         self.custom_fields = {}
         try:
             M.session.artifact_orm_session._get().skip_mod_date = True
-            for issue in GoogleCodeProjectExtractor.iter_issues(project_name):
-                ticket = TM.Ticket.new()
-                self.process_fields(ticket, issue)
-                self.process_labels(ticket, issue)
-                self.process_comments(ticket, issue)
-                session(ticket).flush(ticket)
-                session(ticket).expunge(ticket)
-            self.postprocess_custom_fields()
-            ThreadLocalORMSession.flush_all()
+            with h.push_config(c, user=M.User.anonymous(), app=app):
+                for issue in GoogleCodeProjectExtractor.iter_issues(project_name):
+                    ticket = TM.Ticket.new()
+                    self.process_fields(ticket, issue)
+                    self.process_labels(ticket, issue)
+                    self.process_comments(ticket, issue)
+                    session(ticket).flush(ticket)
+                    session(ticket).expunge(ticket)
+                # app.globals gets expunged every time Ticket.new() is called :-(
+                app.globals = TM.Globals.query.get(app_config_id=app.config._id)
+                app.globals.custom_fields = self.postprocess_custom_fields()
+                ThreadLocalORMSession.flush_all()
+            g.post_event('project_updated')
+            return app
         finally:
             M.session.artifact_orm_session._get().skip_mod_date = False
 
@@ -78,13 +84,18 @@ class GoogleCodeTrackerImporter(ToolImporter):
         ticket.status = issue.get_issue_status()
         ticket.created_date = datetime.strptime(issue.get_issue_created_date(), '%c')
         ticket.mod_date = datetime.strptime(issue.get_issue_mod_date(), '%c')
+        owner = issue.get_issue_owner()
+        if owner:
+            owner_line = '*Originally owned by:* [{owner.name}]({owner.link})\n'.format(owner=owner)
+        else:
+            owner_line = ''
         ticket.description = (
                 u'*Originally created by:* [{creator.name}]({creator.link})\n'
-                '*Originally owned by:* [{owner.name}]({owner.link})\n'
-                '\n'
-                '{body}').format(
+                u'{owner}'
+                u'\n'
+                u'{body}').format(
                     creator=issue.get_issue_creator(),
-                    owner=issue.get_issue_owner(),
+                    owner=owner_line,
                     body=h.plain2markdown(issue.get_issue_description(), True),
                 )
         ticket.add_multiple_attachments(issue.get_issue_attachments())
@@ -106,25 +117,14 @@ class GoogleCodeTrackerImporter(ToolImporter):
     def process_comments(self, ticket, issue):
         for comment in issue.iter_comments():
             p = ticket.discussion_thread.add_post(
-                    text = (
-                        u'*Originally posted by:* [{author.name}]({author.link})\n'
-                        '\n'
-                        '{body}\n'
-                        '\n'
-                        '{updates}').format(
-                            author=comment.author,
-                            body=h.plain2markdown(comment.body, True),
-                            updates='\n'.join(
-                                '**%s** %s' % (k,v)
-                                for k,v in comment.updates.items()
-                            ),
-                    )
+                    text = comment.annotated_text,
+                    ignore_security = True,
+                    timestamp = datetime.strptime(comment.created_date, '%c'),
                 )
-            p.created_date = p.timestamp = datetime.strptime(comment.created_date, '%c')
             p.add_multiple_attachments(comment.attachments)
 
     def postprocess_custom_fields(self):
-        c.app.globals.custom_fields = []
+        custom_fields = []
         for name, field in self.custom_fields.iteritems():
             if field['name'] == '_milestone':
                 field['milestones'] = [{
@@ -137,4 +137,5 @@ class GoogleCodeTrackerImporter(ToolImporter):
                 field['options'] = ' '.join(field['options'])
             else:
                 field['options'] = ''
-            c.app.globals.custom_fields.append(field)
+            custom_fields.append(field)
+        return custom_fields

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/92b6d367/ForgeImporters/forgeimporters/tests/google/functional/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/functional/__init__.py b/ForgeImporters/forgeimporters/tests/google/functional/__init__.py
new file mode 100644
index 0000000..77505f1
--- /dev/null
+++ b/ForgeImporters/forgeimporters/tests/google/functional/__init__.py
@@ -0,0 +1,17 @@
+#       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/incubator-allura/blob/92b6d367/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
new file mode 100644
index 0000000..206ac7c
--- /dev/null
+++ b/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
@@ -0,0 +1,260 @@
+#       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 pkg_resources
+from functools import wraps
+from datetime import datetime
+
+from BeautifulSoup import BeautifulSoup
+import mock
+from ming.orm import ThreadLocalORMSession
+from pylons import tmpl_context as c
+from IPython.testing.decorators import module_not_available, skipif
+
+from alluratest.controller import setup_basic_test
+from allura import model as M
+from forgetracker import model as TM
+from .... import google
+from ....google import tracker
+
+
+def without_html2text(func):
+    @wraps(func)
+    def wrapped(*args, **kw):
+        try:
+            import html2text
+        except ImportError:
+            return func(*args, **kw)
+        else:
+            with mock.patch.object(html2text, 'escape_md_section') as ems:
+                ems.side_effect = ImportError
+                return func(*args, **kw)
+    return wrapped
+
+class TestGCTrackerImporter(TestCase):
+    def _make_extractor(self, html):
+        with mock.patch.object(google, 'urllib2') as urllib2:
+            urllib2.urlopen.return_value = ''
+            extractor = google.GoogleCodeProjectExtractor('my-project', 'project_info')
+        extractor.page = BeautifulSoup(html)
+        extractor.url = "http://test/issue/?id=1"
+        return extractor
+
+    def _make_ticket(self, issue):
+        self.assertIsNone(self.project.app_instance('test-issue'))
+        with mock.patch.object(google, 'urllib2') as urllib2,\
+             mock.patch.object(google.tracker, 'GoogleCodeProjectExtractor') as GPE:
+            urllib2.urlopen = lambda url: mock.Mock(read=lambda: url)
+            GPE.iter_issues.return_value = [issue]
+            gti = google.tracker.GoogleCodeTrackerImporter()
+            gti.import_tool(self.project, self.user, 'test-issue-project', mount_point='test-issue')
+        c.app = self.project.app_instance('test-issue')
+        query = TM.Ticket.query.find({'app_config_id': c.app.config._id})
+        self.assertEqual(query.count(), 1)
+        ticket = query.all()[0]
+        return ticket
+
+    def setUp(self, *a, **kw):
+        super(TestGCTrackerImporter, self).setUp(*a, **kw)
+        setup_basic_test()
+        self.empty_issue = self._make_extractor(open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/empty-issue.html')).read())
+        self.test_issue = self._make_extractor(open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/test-issue.html')).read())
+        c.project = self.project = M.Project.query.get(shortname='test')
+        c.user = self.user = M.User.query.get(username='test-admin')
+
+    def test_empty_issue(self):
+        ticket = self._make_ticket(self.empty_issue)
+        self.assertEqual(ticket.summary, 'Empty Issue')
+        self.assertEqual(ticket.description, '*Originally created by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n\nEmpty')
+        self.assertEqual(ticket.status, '')
+        self.assertEqual(ticket.milestone, '')
+        self.assertEqual(ticket.custom_fields, {})
+
+    @without_html2text
+    def test_issue_basic_fields(self):
+        anon = M.User.anonymous()
+        ticket = self._make_ticket(self.test_issue)
+        self.assertEqual(ticket.reported_by, anon)
+        self.assertIsNone(ticket.assigned_to_id)
+        self.assertEqual(ticket.summary, 'Test Issue')
+        self.assertEqual(ticket.description,
+                '*Originally created by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n'
+                '*Originally owned by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n'
+                '\n'
+                'Test \\*Issue\\* for testing\n'
+                '\n'
+                '&nbsp; 1\\. Test List\n'
+                '&nbsp; 2\\. Item\n'
+                '\n'
+                '\\*\\*Testing\\*\\*\n'
+                '\n'
+                ' \\* Test list 2\n'
+                ' \\* Item\n'
+                '\n'
+                '\\# Test Section\n'
+                '\n'
+                '&nbsp;&nbsp;&nbsp; p = source\\.test\\_issue\\.post\\(\\)\n'
+                '&nbsp;&nbsp;&nbsp; p\\.count = p\\.count \\*5 \\#\\* 6\n'
+                '\n'
+                'That\'s all'
+            )
+        self.assertEqual(ticket.status, 'Started')
+        self.assertEqual(ticket.created_date, datetime(2013, 8, 8, 15, 33, 52))
+        self.assertEqual(ticket.mod_date, datetime(2013, 8, 8, 15, 36, 57))
+        self.assertEqual(ticket.custom_fields, {
+                '_priority': 'Medium',
+                '_opsys': 'All, OSX, Windows',
+                '_component': 'Logic',
+                '_type': 'Defect',
+                '_milestone': 'Release1.0'
+            })
+        self.assertEqual(ticket.labels, ['Performance', 'Security'])
+
+    @skipif(module_not_available('html2text'))
+    def test_html2text_escaping(self):
+        ticket = self._make_ticket(self.test_issue)
+        self.assertEqual(ticket.description,
+                '*Originally created by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n'
+                '*Originally owned by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n'
+                '\n'
+                'Test \\*Issue\\* for testing\n'
+                '\n'
+                '&nbsp; 1. Test List\n'
+                '&nbsp; 2. Item\n'
+                '\n'
+                '\\*\\*Testing\\*\\*\n'
+                '\n'
+                ' \\* Test list 2\n'
+                ' \\* Item\n'
+                '\n'
+                '\\# Test Section\n'
+                '\n'
+                '&nbsp;&nbsp;&nbsp; p = source.test\\_issue.post\\(\\)\n'
+                '&nbsp;&nbsp;&nbsp; p.count = p.count \\*5 \\#\\* 6\n'
+                '\n'
+                'That\'s all'
+            )
+
+    def _assert_attachments(self, actual, *expected):
+        self.assertEqual(actual.count(), len(expected))
+        atts = set((a.filename, a.content_type, a.rfile().read()) for a in actual)
+        self.assertEqual(atts, set(expected))
+
+    def test_attachements(self):
+        ticket = self._make_ticket(self.test_issue)
+        self._assert_attachments(ticket.attachments,
+                ('at1.txt', 'text/plain', 'http://allura-google-importer.googlecode.com/issues/attachment?aid=70000000&name=at1.txt&token=3REU1M3JUUMt0rJUg7ldcELt6LA%3A1376059941255'),
+                ('at2.txt', 'text/plain', 'http://allura-google-importer.googlecode.com/issues/attachment?aid=70000001&name=at2.txt&token=C9Hn4s1-g38hlSggRGo65VZM1ys%3A1376059941255'),
+            )
+
+    @without_html2text
+    def test_comments(self):
+        anon = M.User.anonymous()
+        ticket = self._make_ticket(self.test_issue)
+        actual_comments = ticket.discussion_thread.find_posts()
+        expected_comments = [
+                {
+                    'timestamp': datetime(2013, 8, 8, 15, 35, 15),
+                    'text': (
+                            '*Originally posted by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n'
+                            '\n'
+                            'Test \\*comment\\* is a comment\n'
+                            '\n'
+                            '**Labels:** -OpSys-Linux OpSys-Windows\n'
+                            '**Status:** Started'
+                        ),
+                    'attachments': [
+                            ('at2.txt', 'text/plain', 'http://allura-google-importer.googlecode.com/issues/attachment?aid=60001000&name=at2.txt&token=JOSo4duwaN2FCKZrwYOQ-nx9r7U%3A1376001446667'),
+                        ],
+                },
+                {
+                    'timestamp': datetime(2013, 8, 8, 15, 35, 34),
+                    'text': (
+                            '*Originally posted by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n'
+                            '\n'
+                            'Another comment\n\n'
+                        ),
+                },
+                {
+                    'timestamp': datetime(2013, 8, 8, 15, 36, 39),
+                    'text': (
+                            '*Originally posted by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n'
+                            '\n'
+                            'Last comment\n\n'
+                        ),
+                    'attachments': [
+                            ('at4.txt', 'text/plain', 'http://allura-google-importer.googlecode.com/issues/attachment?aid=60003000&name=at4.txt&token=6Ny2zYHmV6b82dqxyoiH6HUYoC4%3A1376001446667'),
+                            ('at1.txt', 'text/plain', 'http://allura-google-importer.googlecode.com/issues/attachment?aid=60003001&name=at1.txt&token=NS8aMvWsKzTAPuY2kniJG5aLzPg%3A1376001446667'),
+                        ],
+                },
+                {
+                    'timestamp': datetime(2013, 8, 8, 15, 36, 57),
+                    'text': (
+                            '*Originally posted by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n'
+                            '\n'
+                            'Oh, I forgot one\n'
+                            '\n'
+                            '**Labels:** OpSys-OSX'
+                        ),
+                },
+            ]
+        self.assertEqual(len(actual_comments), len(expected_comments))
+        for actual, expected in zip(actual_comments, expected_comments):
+            self.assertEqual(actual.author(), anon)
+            self.assertEqual(actual.timestamp, expected['timestamp'])
+            self.assertEqual(actual.text, expected['text'])
+            if 'attachments' in expected:
+                self._assert_attachments(actual.attachments, *expected['attachments'])
+
+    def test_globals(self):
+        globals = self._make_ticket(self.test_issue).globals
+        self.assertItemsEqual(globals.custom_fields, [
+                {
+                    'label': 'Milestone',
+                    'name': '_milestone',
+                    'type': 'milestone',
+                    'options': '',
+                    'milestones': [
+                            {'name': 'Release1.0', 'due_date': None, 'complete': False},
+                        ],
+                },
+                {
+                    'label': 'Type',
+                    'name': '_type',
+                    'type': 'select',
+                    'options': 'Defect',
+                },
+                {
+                    'label': 'Priority',
+                    'name': '_priority',
+                    'type': 'select',
+                    'options': 'Medium',
+                },
+                {
+                    'label': 'OpSys',
+                    'name': '_opsys',
+                    'type': 'string',
+                    'options': '',
+                },
+                {
+                    'label': 'Component',
+                    'name': '_component',
+                    'type': 'string',
+                    'options': '',
+                },
+            ])

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/92b6d367/ForgeImporters/forgeimporters/tests/google/test_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
index a1f0a28..3efd97d 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
@@ -27,9 +27,10 @@ class TestTrackerImporter(TestCase):
     @mock.patch.object(tracker, 'c')
     @mock.patch.object(tracker, 'ThreadLocalORMSession')
     @mock.patch.object(tracker, 'session')
+    @mock.patch.object(tracker, 'M')
     @mock.patch.object(tracker, 'TM')
     @mock.patch.object(tracker, 'GoogleCodeProjectExtractor')
-    def test_import_tool(self, gpe, TM, session, tlos, c):
+    def test_import_tool(self, gpe, TM, M, session, tlos, c):
         importer = tracker.GoogleCodeTrackerImporter()
         importer.process_fields = mock.Mock()
         importer.process_labels = mock.Mock()
@@ -158,12 +159,14 @@ class TestTrackerImporter(TestCase):
                 mock.Mock(
                     author=_author(1),
                     body='text1',
+                    annotated_text='annotated1',
                     attachments='attachments1',
                     created_date='Mon Jul 15 00:00:00 2013',
                 ),
                 mock.Mock(
                     author=_author(2),
                     body='text2',
+                    annotated_text='annotated2',
                     attachments='attachments2',
                     created_date='Mon Jul 16 00:00:00 2013',
                 ),
@@ -177,24 +180,16 @@ class TestTrackerImporter(TestCase):
         importer = tracker.GoogleCodeTrackerImporter()
         importer.process_comments(ticket, issue)
         self.assertEqual(ticket.discussion_thread.add_post.call_args_list[0], mock.call(
-                text='*Originally posted by:* [author1](author1_link)\n'
-                '\n'
-                'text1\n'
-                '\n'
-                '**Foo:** Bar\n'
-                '**Baz:** Qux'
+                text='annotated1',
+                timestamp=datetime(2013, 7, 15),
+                ignore_security=True,
             ))
-        self.assertEqual(posts[0].created_date, datetime(2013, 7, 15))
-        self.assertEqual(posts[0].timestamp, datetime(2013, 7, 15))
         posts[0].add_multiple_attachments.assert_called_once_with('attachments1')
         self.assertEqual(ticket.discussion_thread.add_post.call_args_list[1], mock.call(
-                text='*Originally posted by:* [author2](author2_link)\n'
-                '\n'
-                'text2\n'
-                '\n'
+                text='annotated2',
+                timestamp=datetime(2013, 7, 16),
+                ignore_security=True,
             ))
-        self.assertEqual(posts[1].created_date, datetime(2013, 7, 16))
-        self.assertEqual(posts[1].timestamp, datetime(2013, 7, 16))
         posts[1].add_multiple_attachments.assert_called_once_with('attachments2')
 
     @mock.patch.object(tracker, 'c')