You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by he...@apache.org on 2015/05/29 22:40:37 UTC
[15/45] allura git commit: [#7878] Used 2to3 to see what issues would
come up
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tasks/mail_tasks.py
----------------------------------------------------------------------
diff --git a/tasks/mail_tasks.py b/tasks/mail_tasks.py
new file mode 100644
index 0000000..a771f03
--- /dev/null
+++ b/tasks/mail_tasks.py
@@ -0,0 +1,209 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import logging
+import html.parser
+
+from pylons import tmpl_context as c, app_globals as g
+from bson import ObjectId
+
+from allura.lib import helpers as h
+from allura.lib.decorators import task
+from allura.lib import mail_util
+from allura.lib import exceptions as exc
+
+log = logging.getLogger(__name__)
+
+smtp_client = mail_util.SMTPClient()
+
+
+@task
+def route_email(
+ peer, mailfrom, rcpttos, data):
+ '''Route messages according to their destination:
+
+ <topic>@<mount_point>.<subproj2>.<subproj1>.<project>.projects.domain.net
+ gets sent to c.app.handle_message(topic, message)
+ '''
+ try:
+ msg = mail_util.parse_message(data)
+ except: # pragma no cover
+ log.exception('Parse Error: (%r,%r,%r)', peer, mailfrom, rcpttos)
+ return
+ if mail_util.is_autoreply(msg):
+ log.info('Skipping autoreply message: %s', msg['headers'])
+ return
+ mail_user = mail_util.identify_sender(peer, mailfrom, msg['headers'], msg)
+ with h.push_config(c, user=mail_user):
+ log.info('Received email from %s', c.user.username)
+ # For each of the addrs, determine the project/app and route
+ # appropriately
+ for addr in rcpttos:
+ try:
+ userpart, project, app = mail_util.parse_address(addr)
+ with h.push_config(c, project=project, app=app):
+ if not app.has_access(c.user, userpart):
+ log.info('Access denied for %s to mailbox %s',
+ c.user, userpart)
+ elif not c.app.config.options.get('AllowEmailPosting', True):
+ log.info("Posting from email is not enabled")
+ else:
+ if msg['multipart']:
+ msg_hdrs = msg['headers']
+ for part in msg['parts']:
+ if part.get('content_type', '').startswith('multipart/'):
+ continue
+ msg = dict(
+ headers=dict(msg_hdrs, **part['headers']),
+ message_id=part['message_id'],
+ in_reply_to=part['in_reply_to'],
+ references=part['references'],
+ filename=part['filename'],
+ content_type=part['content_type'],
+ payload=part['payload'])
+ c.app.handle_message(userpart, msg)
+ else:
+ c.app.handle_message(userpart, msg)
+ except exc.MailError as e:
+ log.error('Error routing email to %s: %s', addr, e)
+ except:
+ log.exception('Error routing mail to %s', addr)
+
+
+@task
+def sendmail(fromaddr, destinations, text, reply_to, subject,
+ message_id, in_reply_to=None, sender=None, references=None):
+ '''
+ Send an email to the specified list of destinations with respect to the preferred email format specified by user.
+ It is best for broadcast messages.
+
+ :param fromaddr: ObjectId or str(ObjectId) of user, or email address str
+
+ '''
+ from allura import model as M
+ addrs_plain = []
+ addrs_html = []
+ addrs_multi = []
+ if fromaddr is None:
+ fromaddr = g.noreply
+ elif not isinstance(fromaddr, str) or '@' not in fromaddr:
+ log.warning('Looking up user with fromaddr: %s', fromaddr)
+ user = M.User.query.get(_id=ObjectId(fromaddr), disabled=False, pending=False)
+ if not user:
+ log.warning('Cannot find user with ID: %s', fromaddr)
+ fromaddr = g.noreply
+ else:
+ fromaddr = user.email_address_header()
+ # Divide addresses based on preferred email formats
+ for addr in destinations:
+ if mail_util.isvalid(addr):
+ addrs_plain.append(addr)
+ else:
+ try:
+ user = M.User.query.get(_id=ObjectId(addr), disabled=False, pending=False)
+ if not user:
+ log.warning('Cannot find user with ID: %s', addr)
+ continue
+ except:
+ log.exception('Error looking up user with ID: %r' % addr)
+ continue
+ addr = user.email_address_header()
+ if not addr and user.email_addresses:
+ addr = user.email_addresses[0]
+ log.warning(
+ 'User %s has not set primary email address, using %s',
+ user._id, addr)
+ if not addr:
+ log.error(
+ "User %s (%s) has not set any email address, can't deliver",
+ user._id, user.username)
+ continue
+ if user.get_pref('email_format') == 'plain':
+ addrs_plain.append(addr)
+ elif user.get_pref('email_format') == 'html':
+ addrs_html.append(addr)
+ else:
+ addrs_multi.append(addr)
+ htmlparser = html.parser.HTMLParser()
+ plain_msg = mail_util.encode_email_part(htmlparser.unescape(text), 'plain')
+ html_text = g.forge_markdown(email=True).convert(text)
+ html_msg = mail_util.encode_email_part(html_text, 'html')
+ multi_msg = mail_util.make_multipart_message(plain_msg, html_msg)
+ smtp_client.sendmail(
+ addrs_multi, fromaddr, reply_to, subject, message_id,
+ in_reply_to, multi_msg, sender=sender, references=references)
+ smtp_client.sendmail(
+ addrs_plain, fromaddr, reply_to, subject, message_id,
+ in_reply_to, plain_msg, sender=sender, references=references)
+ smtp_client.sendmail(
+ addrs_html, fromaddr, reply_to, subject, message_id,
+ in_reply_to, html_msg, sender=sender, references=references)
+
+
+@task
+def sendsimplemail(
+ fromaddr,
+ toaddr,
+ text,
+ reply_to,
+ subject,
+ message_id,
+ in_reply_to=None,
+ sender=None,
+ references=None,
+ cc=None):
+ '''
+ Send a single mail to the specified address.
+ It is best for single user notifications.
+
+ :param fromaddr: ObjectId or str(ObjectId) of user, or email address str
+ :param toaddr: ObjectId or str(ObjectId) of user, or email address str
+
+ '''
+ from allura import model as M
+ if fromaddr is None:
+ fromaddr = g.noreply
+ elif not isinstance(fromaddr, str) or '@' not in fromaddr:
+ log.warning('Looking up user with fromaddr: %s', fromaddr)
+ user = M.User.query.get(_id=ObjectId(fromaddr), disabled=False, pending=False)
+ if not user:
+ log.warning('Cannot find user with ID: %s', fromaddr)
+ fromaddr = g.noreply
+ else:
+ fromaddr = user.email_address_header()
+
+ if not isinstance(toaddr, str) or '@' not in toaddr:
+ log.warning('Looking up user with toaddr: %s', toaddr)
+ user = M.User.query.get(_id=ObjectId(toaddr), disabled=False, pending=False)
+ if not user:
+ log.warning('Cannot find user with ID: %s', toaddr)
+ toaddr = g.noreply
+ else:
+ toaddr = user.email_address_header()
+
+ htmlparser = html.parser.HTMLParser()
+ plain_msg = mail_util.encode_email_part(htmlparser.unescape(text), 'plain')
+ html_text = g.forge_markdown(email=True).convert(text)
+ html_msg = mail_util.encode_email_part(html_text, 'html')
+ multi_msg = mail_util.make_multipart_message(plain_msg, html_msg)
+ smtp_client.sendmail(
+ [toaddr], fromaddr, reply_to, subject, message_id,
+ in_reply_to, multi_msg, sender=sender, references=references, cc=cc, to=toaddr)
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tasks/notification_tasks.py
----------------------------------------------------------------------
diff --git a/tasks/notification_tasks.py b/tasks/notification_tasks.py
new file mode 100644
index 0000000..2244b46
--- /dev/null
+++ b/tasks/notification_tasks.py
@@ -0,0 +1,29 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from allura.lib.decorators import task
+
+
+@task
+def notify(n_id, ref_id, topic):
+ from allura import model as M
+ M.Mailbox.deliver(n_id, ref_id, topic)
+ M.Mailbox.fire_ready()
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tasks/repo_tasks.py
----------------------------------------------------------------------
diff --git a/tasks/repo_tasks.py b/tasks/repo_tasks.py
new file mode 100644
index 0000000..d747867
--- /dev/null
+++ b/tasks/repo_tasks.py
@@ -0,0 +1,176 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import shutil
+import logging
+import traceback
+
+from pylons import tmpl_context as c, app_globals as g
+from ming.odm import session
+
+from allura.lib.decorators import task
+from allura.lib.repository import RepositoryApp
+
+
+@task
+def init(**kwargs):
+ from allura import model as M
+ c.app.repo.init()
+ M.Notification.post_user(
+ c.user, c.app.repo, 'created',
+ text='Repository %s/%s created' % (
+ c.project.shortname, c.app.config.options.mount_point))
+
+
+@task
+def clone(cloned_from_path, cloned_from_name, cloned_from_url):
+ from allura import model as M
+ try:
+ c.app.repo.init_as_clone(
+ cloned_from_path,
+ cloned_from_name,
+ cloned_from_url)
+ M.Notification.post_user(
+ c.user, c.app.repo, 'created',
+ text='Repository %s/%s created' % (
+ c.project.shortname, c.app.config.options.mount_point))
+ except Exception:
+ g.post_event('repo_clone_task_failed', cloned_from_url,
+ cloned_from_path, traceback.format_exc())
+
+
+@task
+def reclone(*args, **kwargs):
+ from allura import model as M
+ from ming.orm import ThreadLocalORMSession
+ repo = c.app.repo
+ if repo is not None:
+ shutil.rmtree(repo.full_fs_path, ignore_errors=True)
+ M.MergeRequest.query.remove(dict(
+ app_config_id=c.app.config._id))
+ ThreadLocalORMSession.flush_all()
+ clone(*args, **kwargs)
+
+
+@task
+def refresh(**kwargs):
+ from allura import model as M
+ log = logging.getLogger(__name__)
+ # don't create multiple refresh tasks
+ q = {
+ 'task_name': 'allura.tasks.repo_tasks.refresh',
+ 'state': {'$in': ['busy', 'ready']},
+ 'context.app_config_id': c.app.config._id,
+ 'context.project_id': c.project._id,
+ }
+ refresh_tasks_count = M.MonQTask.query.find(q).count()
+ if refresh_tasks_count <= 1: # only this task
+ c.app.repo.refresh()
+ # checking if we have new commits arrived
+ # during refresh and re-queue task if so
+ new_commit_ids = c.app.repo.unknown_commit_ids()
+ if len(new_commit_ids) > 0:
+ refresh.post()
+ log.info('New refresh task is queued due to new commit(s).')
+ else:
+ log.info('Refresh task for %s:%s skipped due to backlog',
+ c.project.shortname, c.app.config.options.mount_point)
+
+
+@task
+def uninstall(**kwargs):
+ from allura import model as M
+ repo = c.app.repo
+ if repo is not None:
+ shutil.rmtree(repo.full_fs_path, ignore_errors=True)
+ repo.delete()
+ M.MergeRequest.query.remove(dict(
+ app_config_id=c.app.config._id))
+ super(RepositoryApp, c.app).uninstall(c.project)
+ from ming.orm import ThreadLocalORMSession
+ ThreadLocalORMSession.flush_all()
+
+
+@task
+def nop():
+ log = logging.getLogger(__name__)
+ log.info('nop')
+
+
+@task
+def reclone_repo(*args, **kwargs):
+ from allura import model as M
+ try:
+ nbhd = M.Neighborhood.query.get(url_prefix='/%s/' % kwargs['prefix'])
+ c.project = M.Project.query.get(
+ shortname=kwargs['shortname'], neighborhood_id=nbhd._id)
+ c.app = c.project.app_instance(kwargs['mount_point'])
+ source_url = c.app.config.options.get('init_from_url')
+ source_path = c.app.config.options.get('init_from_path')
+ c.app.repo.init_as_clone(source_path, None, source_url)
+ M.Notification.post_user(
+ c.user, c.app.repo, 'created',
+ text='Repository %s/%s created' % (
+ c.project.shortname, c.app.config.options.mount_point))
+ except Exception:
+ g.post_event('repo_clone_task_failed', source_url,
+ source_path, traceback.format_exc())
+
+
+@task
+def tarball(revision, path):
+ log = logging.getLogger(__name__)
+ if revision:
+ repo = c.app.repo
+ status = repo.get_tarball_status(revision, path)
+ if status == 'complete':
+ log.info(
+ 'Skipping snapshot for repository: %s:%s rev %s because it is already %s' %
+ (c.project.shortname, c.app.config.options.mount_point, revision, status))
+ else:
+ try:
+ repo.tarball(revision, path)
+ except:
+ log.error(
+ 'Could not create snapshot for repository: %s:%s revision %s path %s' %
+ (c.project.shortname, c.app.config.options.mount_point, revision, path), exc_info=True)
+ raise
+ else:
+ log.warn(
+ 'Skipped creation of snapshot: %s:%s because revision is not specified' %
+ (c.project.shortname, c.app.config.options.mount_point))
+
+
+@task
+def merge(merge_request_id):
+ from allura import model as M
+ mr = M.MergeRequest.query.get(_id=merge_request_id)
+ mr.app.repo.merge(mr)
+ mr.status = 'merged'
+ session(mr).flush(mr)
+
+
+@task
+def can_merge(merge_request_id):
+ from allura import model as M
+ mr = M.MergeRequest.query.get(_id=merge_request_id)
+ result = mr.app.repo.can_merge(mr)
+ mr.set_can_merge_cache(result)
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/templates/__init__.py
----------------------------------------------------------------------
diff --git a/templates/__init__.py b/templates/__init__.py
new file mode 100644
index 0000000..4c0b4ac
--- /dev/null
+++ b/templates/__init__.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""Templates package for the application."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/templates/discussion/__init__.py
----------------------------------------------------------------------
diff --git a/templates/discussion/__init__.py b/templates/discussion/__init__.py
new file mode 100644
index 0000000..144e298
--- /dev/null
+++ b/templates/discussion/__init__.py
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/templates/macro/__init__.py
----------------------------------------------------------------------
diff --git a/templates/macro/__init__.py b/templates/macro/__init__.py
new file mode 100644
index 0000000..144e298
--- /dev/null
+++ b/templates/macro/__init__.py
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/templates/oembed/__init__.py
----------------------------------------------------------------------
diff --git a/templates/oembed/__init__.py b/templates/oembed/__init__.py
new file mode 100644
index 0000000..144e298
--- /dev/null
+++ b/templates/oembed/__init__.py
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/templates/repo/__init__.py
----------------------------------------------------------------------
diff --git a/templates/repo/__init__.py b/templates/repo/__init__.py
new file mode 100644
index 0000000..144e298
--- /dev/null
+++ b/templates/repo/__init__.py
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/templates/widgets/__init__.py
----------------------------------------------------------------------
diff --git a/templates/widgets/__init__.py b/templates/widgets/__init__.py
new file mode 100644
index 0000000..144e298
--- /dev/null
+++ b/templates/widgets/__init__.py
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/__init__.py
----------------------------------------------------------------------
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..14e03bf
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""Unit and functional test suite for allura."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import alluratest.controller
+
+# HACK: prevents test suite from crashing when running under the nose
+# MultiProcessing plugin
+import socket
+socket.setdefaulttimeout(None)
+
+
+class TestController(alluratest.controller.TestController):
+
+ """
+ Base functional test case for the controllers.
+
+ """
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/decorators.py
----------------------------------------------------------------------
diff --git a/tests/decorators.py b/tests/decorators.py
new file mode 100644
index 0000000..635e03a
--- /dev/null
+++ b/tests/decorators.py
@@ -0,0 +1,201 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import sys
+import re
+from functools import wraps
+import contextlib
+
+from ming.orm.ormsession import ThreadLocalORMSession
+from pylons import tmpl_context as c
+from mock import patch
+import tg
+from paste.deploy.converters import asbool
+
+from allura import model as M
+import allura.config.middleware
+
+
+def with_user_project(username):
+ def _with_user_project(func):
+ @wraps(func)
+ def wrapped(*args, **kw):
+ user = M.User.by_username(username)
+ c.user = user
+ n = M.Neighborhood.query.get(name='Users')
+ shortname = 'u/' + username
+ p = M.Project.query.get(shortname=shortname, neighborhood_id=n._id)
+ if not p:
+ n.register_project(shortname, user=user, user_project=True)
+ ThreadLocalORMSession.flush_all()
+ ThreadLocalORMSession.close_all()
+ return func(*args, **kw)
+ return wrapped
+ return _with_user_project
+
+
+@contextlib.contextmanager
+def NullContextManager():
+ yield
+
+
+def with_tool(project_shortname, ep_name, mount_point=None, mount_label=None,
+ ordinal=None, post_install_hook=None, username='test-admin',
+ **override_options):
+ def _with_tool(func):
+ @wraps(func)
+ def wrapped(*args, **kw):
+ c.user = M.User.by_username(username)
+ p = M.Project.query.get(shortname=project_shortname)
+ c.project = p
+ if mount_point and not p.app_instance(mount_point):
+ c.app = p.install_app(
+ ep_name, mount_point, mount_label, ordinal, **override_options)
+ if post_install_hook:
+ post_install_hook(c.app)
+
+ if asbool(tg.config.get('smtp.mock')):
+ smtp_mock = patch('allura.lib.mail_util.smtplib.SMTP')
+ else:
+ smtp_mock = NullContextManager()
+ with smtp_mock:
+ while M.MonQTask.run_ready('setup'):
+ pass
+ ThreadLocalORMSession.flush_all()
+ ThreadLocalORMSession.close_all()
+ elif mount_point:
+ c.app = p.app_instance(mount_point)
+ return func(*args, **kw)
+ return wrapped
+ return _with_tool
+
+with_discussion = with_tool('test', 'Discussion', 'discussion')
+with_link = with_tool('test', 'Link', 'link')
+with_tracker = with_tool('test', 'Tickets', 'bugs')
+with_wiki = with_tool('test', 'Wiki', 'wiki')
+with_url = with_tool('test', 'ShortUrl', 'url')
+
+
+class raises(object):
+
+ '''
+ Test helper in the form of a context manager, to assert that something raises an exception.
+ After completion, the 'exc' attribute can be used to do further inspection of the exception
+ '''
+
+ def __init__(self, ExcType):
+ self.ExcType = ExcType
+ self.exc = None
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_t):
+ if exc_type:
+ self.exc = exc_val
+ if issubclass(exc_type, self.ExcType):
+ # ok
+ return True
+ else:
+ # root exception will be raised, untouched
+ return False
+ else:
+ raise AssertionError('Did not raise %s' % self.ExcType)
+
+
+def without_module(*module_names):
+ def _without_module(func):
+ @wraps(func)
+ def wrapped(*a, **kw):
+ with patch.dict(sys.modules, {m: None for m in module_names}):
+ return func(*a, **kw)
+ return wrapped
+ return _without_module
+
+
+class patch_middleware_config(object):
+
+ '''
+ Context manager that patches the configuration used during middleware
+ setup for Allura
+ '''
+
+ def __init__(self, new_configs):
+ self.new_configs = new_configs
+
+ def __enter__(self):
+ self._make_app = allura.config.middleware.make_app
+
+ def make_app(global_conf, full_stack=True, **app_conf):
+ app_conf.update(self.new_configs)
+ return self._make_app(global_conf, full_stack, **app_conf)
+
+ allura.config.middleware.make_app = make_app
+
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_t):
+ allura.config.middleware.make_app = self._make_app
+ return self
+
+
+@contextlib.contextmanager
+def audits(*messages, **kwargs):
+ """
+ Asserts all the messages exist in audit log
+
+ :param messages: regex strings
+ :param bool user: if this is a user log
+
+ """
+ M.AuditLog.query.remove()
+ yield
+ if kwargs.get('user'):
+ actor = kwargs.get('actor', '.*')
+ ip_addr = kwargs.get('ip_addr', '.*')
+ preamble = '(Done by user: {}\n)?IP Address: {}\n'.format(actor, ip_addr)
+ else:
+ preamble = ''
+ for message in messages:
+ assert M.AuditLog.query.find(dict(
+ message=re.compile(preamble + message))).count(), 'Could not find "%s"' % message
+
+
+@contextlib.contextmanager
+def out_audits(*messages, **kwargs):
+ """
+ Asserts none the messages exist in audit log. "without audits"
+
+ :param messages: list of regex strings
+ :param bool user: if this is a user log
+
+ """
+ M.AuditLog.query.remove()
+ yield
+ if kwargs.get('user'):
+ actor = kwargs.get('actor', '.*')
+ ip_addr = kwargs.get('ip_addr', '.*')
+ preamble = '(Done by user: {}\n)?IP Address: {}\n'.format(actor, ip_addr)
+ else:
+ preamble = ''
+ for message in messages:
+ assert not M.AuditLog.query.find(dict(
+ message=re.compile(preamble + message))).count(), 'Found unexpected: "%s"' % message
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/functional/__init__.py
----------------------------------------------------------------------
diff --git a/tests/functional/__init__.py b/tests/functional/__init__.py
new file mode 100644
index 0000000..ba896c1
--- /dev/null
+++ b/tests/functional/__init__.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""Functional test suite for the controllers of the application."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals