aboutsummaryrefslogtreecommitdiffstats
path: root/mediagoblin/tests
diff options
context:
space:
mode:
Diffstat (limited to 'mediagoblin/tests')
-rw-r--r--mediagoblin/tests/__init__.py12
-rw-r--r--mediagoblin/tests/appconfig_context_modified.ini27
-rw-r--r--mediagoblin/tests/appconfig_static_plugin.ini27
-rw-r--r--mediagoblin/tests/auth_configs/__init__.py0
-rw-r--r--mediagoblin/tests/auth_configs/authentication_disabled_appconfig.ini25
-rw-r--r--mediagoblin/tests/auth_configs/openid_appconfig.ini41
-rw-r--r--mediagoblin/tests/conftest.py28
-rw-r--r--mediagoblin/tests/pytest.ini2
-rw-r--r--mediagoblin/tests/test_auth.py153
-rw-r--r--mediagoblin/tests/test_basic_auth.py59
-rw-r--r--mediagoblin/tests/test_celery_setup.py2
-rw-r--r--mediagoblin/tests/test_edit.py91
-rw-r--r--mediagoblin/tests/test_exif.py375
-rw-r--r--mediagoblin/tests/test_mgoblin_app.ini19
-rw-r--r--mediagoblin/tests/test_misc.py8
-rw-r--r--mediagoblin/tests/test_notifications.py151
-rw-r--r--mediagoblin/tests/test_openid.py373
-rw-r--r--mediagoblin/tests/test_paste.ini14
-rw-r--r--mediagoblin/tests/test_piwigo.py8
-rw-r--r--mediagoblin/tests/test_pluginapi.py141
-rw-r--r--mediagoblin/tests/test_submission.py14
-rw-r--r--mediagoblin/tests/testplugins/modify_context/__init__.py55
-rw-r--r--mediagoblin/tests/testplugins/modify_context/templates/contextplugin/general.html5
-rw-r--r--mediagoblin/tests/testplugins/modify_context/templates/contextplugin/specific.html6
-rw-r--r--mediagoblin/tests/testplugins/modify_context/views.py33
-rw-r--r--mediagoblin/tests/testplugins/staticstuff/__init__.py36
-rw-r--r--mediagoblin/tests/testplugins/staticstuff/static/css/bunnify.css4
-rw-r--r--mediagoblin/tests/testplugins/staticstuff/views.py28
-rw-r--r--mediagoblin/tests/tools.py106
29 files changed, 1638 insertions, 205 deletions
diff --git a/mediagoblin/tests/__init__.py b/mediagoblin/tests/__init__.py
index 7a88281e..cf200791 100644
--- a/mediagoblin/tests/__init__.py
+++ b/mediagoblin/tests/__init__.py
@@ -14,21 +14,9 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-import os
-import shutil
-
-from mediagoblin import mg_globals
-from mediagoblin.tests.tools import TEST_USER_DEV
-
def setup_package():
import warnings
from sqlalchemy.exc import SAWarning
warnings.simplefilter("error", SAWarning)
-
-
-def teardown_package():
- # Remove and reinstall user_dev directories
- if os.path.exists(TEST_USER_DEV):
- shutil.rmtree(TEST_USER_DEV)
diff --git a/mediagoblin/tests/appconfig_context_modified.ini b/mediagoblin/tests/appconfig_context_modified.ini
new file mode 100644
index 00000000..cc6721f5
--- /dev/null
+++ b/mediagoblin/tests/appconfig_context_modified.ini
@@ -0,0 +1,27 @@
+[mediagoblin]
+direct_remote_path = /test_static/
+email_sender_address = "notice@mediagoblin.example.org"
+email_debug_mode = true
+
+#Runs with an in-memory sqlite db for speed.
+sql_engine = "sqlite://"
+run_migrations = true
+
+# Celery shouldn't be set up by the application as it's setup via
+# mediagoblin.init.celery.from_celery
+celery_setup_elsewhere = true
+
+[storage:publicstore]
+base_dir = %(here)s/user_dev/media/public
+base_url = /mgoblin_media/
+
+[storage:queuestore]
+base_dir = %(here)s/user_dev/media/queue
+
+[celery]
+CELERY_ALWAYS_EAGER = true
+CELERY_RESULT_DBURI = "sqlite:///%(here)s/user_dev/celery.db"
+BROKER_HOST = "sqlite:///%(here)s/user_dev/kombu.db"
+
+[plugins]
+[[mediagoblin.tests.testplugins.modify_context]]
diff --git a/mediagoblin/tests/appconfig_static_plugin.ini b/mediagoblin/tests/appconfig_static_plugin.ini
new file mode 100644
index 00000000..5ce5c5bd
--- /dev/null
+++ b/mediagoblin/tests/appconfig_static_plugin.ini
@@ -0,0 +1,27 @@
+[mediagoblin]
+direct_remote_path = /test_static/
+email_sender_address = "notice@mediagoblin.example.org"
+email_debug_mode = true
+
+#Runs with an in-memory sqlite db for speed.
+sql_engine = "sqlite://"
+run_migrations = true
+
+# Celery shouldn't be set up by the application as it's setup via
+# mediagoblin.init.celery.from_celery
+celery_setup_elsewhere = true
+
+[storage:publicstore]
+base_dir = %(here)s/user_dev/media/public
+base_url = /mgoblin_media/
+
+[storage:queuestore]
+base_dir = %(here)s/user_dev/media/queue
+
+[celery]
+CELERY_ALWAYS_EAGER = true
+CELERY_RESULT_DBURI = "sqlite:///%(here)s/user_dev/celery.db"
+BROKER_HOST = "sqlite:///%(here)s/user_dev/kombu.db"
+
+[plugins]
+[[mediagoblin.tests.testplugins.staticstuff]]
diff --git a/mediagoblin/tests/auth_configs/__init__.py b/mediagoblin/tests/auth_configs/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/mediagoblin/tests/auth_configs/__init__.py
diff --git a/mediagoblin/tests/auth_configs/authentication_disabled_appconfig.ini b/mediagoblin/tests/auth_configs/authentication_disabled_appconfig.ini
new file mode 100644
index 00000000..a64e9e40
--- /dev/null
+++ b/mediagoblin/tests/auth_configs/authentication_disabled_appconfig.ini
@@ -0,0 +1,25 @@
+[mediagoblin]
+direct_remote_path = /test_static/
+email_sender_address = "notice@mediagoblin.example.org"
+email_debug_mode = true
+
+# TODO: Switch to using an in-memory database
+sql_engine = "sqlite:///%(here)s/user_dev/mediagoblin.db"
+
+# Celery shouldn't be set up by the application as it's setup via
+# mediagoblin.init.celery.from_celery
+celery_setup_elsewhere = true
+
+[storage:publicstore]
+base_dir = %(here)s/user_dev/media/public
+base_url = /mgoblin_media/
+
+[storage:queuestore]
+base_dir = %(here)s/user_dev/media/queue
+
+[celery]
+CELERY_ALWAYS_EAGER = true
+CELERY_RESULT_DBURI = "sqlite:///%(here)s/user_dev/celery.db"
+BROKER_HOST = "sqlite:///%(here)s/user_dev/kombu.db"
+
+[plugins]
diff --git a/mediagoblin/tests/auth_configs/openid_appconfig.ini b/mediagoblin/tests/auth_configs/openid_appconfig.ini
new file mode 100644
index 00000000..c2bd82fd
--- /dev/null
+++ b/mediagoblin/tests/auth_configs/openid_appconfig.ini
@@ -0,0 +1,41 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+[mediagoblin]
+direct_remote_path = /test_static/
+email_sender_address = "notice@mediagoblin.example.org"
+email_debug_mode = true
+
+# TODO: Switch to using an in-memory database
+sql_engine = "sqlite:///%(here)s/user_dev/mediagoblin.db"
+
+# Celery shouldn't be set up by the application as it's setup via
+# mediagoblin.init.celery.from_celery
+celery_setup_elsewhere = true
+
+[storage:publicstore]
+base_dir = %(here)s/user_dev/media/public
+base_url = /mgoblin_media/
+
+[storage:queuestore]
+base_dir = %(here)s/user_dev/media/queue
+
+[celery]
+CELERY_ALWAYS_EAGER = true
+CELERY_RESULT_DBURI = "sqlite:///%(here)s/user_dev/celery.db"
+BROKER_HOST = "sqlite:///%(here)s/user_dev/kombu.db"
+
+[plugins]
+[[mediagoblin.plugins.openid]]
diff --git a/mediagoblin/tests/conftest.py b/mediagoblin/tests/conftest.py
index 25f495d6..dbb0aa0a 100644
--- a/mediagoblin/tests/conftest.py
+++ b/mediagoblin/tests/conftest.py
@@ -1,7 +1,25 @@
-from mediagoblin.tests import tools
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
+from mediagoblin.tests import tools
+from mediagoblin.tools.testing import _activate_testing
+
+
@pytest.fixture()
def test_app(request):
"""
@@ -13,3 +31,11 @@ def test_app(request):
in differently to get_app.
"""
return tools.get_app(request)
+
+
+@pytest.fixture()
+def pt_fixture_enable_testing():
+ """
+ py.test fixture to enable testing mode in tools.
+ """
+ _activate_testing()
diff --git a/mediagoblin/tests/pytest.ini b/mediagoblin/tests/pytest.ini
index d4aa2d69..e561c074 100644
--- a/mediagoblin/tests/pytest.ini
+++ b/mediagoblin/tests/pytest.ini
@@ -1,2 +1,2 @@
[pytest]
-usefixtures = tmpdir \ No newline at end of file
+usefixtures = tmpdir pt_fixture_enable_testing
diff --git a/mediagoblin/tests/test_auth.py b/mediagoblin/tests/test_auth.py
index 755727f9..61503d32 100644
--- a/mediagoblin/tests/test_auth.py
+++ b/mediagoblin/tests/test_auth.py
@@ -13,54 +13,15 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
import urlparse
-import datetime
+import pkg_resources
+import pytest
from mediagoblin import mg_globals
-from mediagoblin.auth import lib as auth_lib
from mediagoblin.db.models import User
-from mediagoblin.tests.tools import fixture_add_user
+from mediagoblin.tests.tools import get_app, fixture_add_user
from mediagoblin.tools import template, mail
-
-
-########################
-# Test bcrypt auth funcs
-########################
-
-def test_bcrypt_check_password():
- # Check known 'lollerskates' password against check function
- assert auth_lib.bcrypt_check_password(
- 'lollerskates',
- '$2a$12$PXU03zfrVCujBhVeICTwtOaHTUs5FFwsscvSSTJkqx/2RQ0Lhy/nO')
-
- assert not auth_lib.bcrypt_check_password(
- 'notthepassword',
- '$2a$12$PXU03zfrVCujBhVeICTwtOaHTUs5FFwsscvSSTJkqx/2RQ0Lhy/nO')
-
- # Same thing, but with extra fake salt.
- assert not auth_lib.bcrypt_check_password(
- 'notthepassword',
- '$2a$12$ELVlnw3z1FMu6CEGs/L8XO8vl0BuWSlUHgh0rUrry9DUXGMUNWwl6',
- '3><7R45417')
-
-
-def test_bcrypt_gen_password_hash():
- pw = 'youwillneverguessthis'
-
- # Normal password hash generation, and check on that hash
- hashed_pw = auth_lib.bcrypt_gen_password_hash(pw)
- assert auth_lib.bcrypt_check_password(
- pw, hashed_pw)
- assert not auth_lib.bcrypt_check_password(
- 'notthepassword', hashed_pw)
-
- # Same thing, extra salt.
- hashed_pw = auth_lib.bcrypt_gen_password_hash(pw, '3><7R45417')
- assert auth_lib.bcrypt_check_password(
- pw, hashed_pw, '3><7R45417')
- assert not auth_lib.bcrypt_check_password(
- 'notthepassword', hashed_pw, '3><7R45417')
+from mediagoblin.auth import tools as auth_tools
def test_register_views(test_app):
@@ -132,8 +93,8 @@ def test_register_views(test_app):
assert 'mediagoblin/user_pages/user.html' in template.TEMPLATE_TEST_CONTEXT
## Make sure user is in place
- new_user = mg_globals.database.User.find_one(
- {'username': u'happygirl'})
+ new_user = mg_globals.database.User.query.filter_by(
+ username=u'happygirl').first()
assert new_user
assert new_user.status == u'needs_email_verification'
assert new_user.email_verified == False
@@ -156,24 +117,19 @@ def test_register_views(test_app):
assert path == u'/auth/verify_email/'
parsed_get_params = urlparse.parse_qs(get_params)
- ### user should have these same parameters
- assert parsed_get_params['userid'] == [
- unicode(new_user.id)]
- assert parsed_get_params['token'] == [
- new_user.verification_key]
-
## Try verifying with bs verification key, shouldn't work
template.clear_test_template_context()
response = test_app.get(
- "/auth/verify_email/?userid=%s&token=total_bs" % unicode(
- new_user.id))
+ "/auth/verify_email/?token=total_bs")
response.follow()
- context = template.TEMPLATE_TEST_CONTEXT[
- 'mediagoblin/user_pages/user.html']
+
+ # Correct redirect?
+ assert urlparse.urlsplit(response.location)[2] == '/'
+
# assert context['verification_successful'] == True
# TODO: Would be good to test messages here when we can do so...
- new_user = mg_globals.database.User.find_one(
- {'username': u'happygirl'})
+ new_user = mg_globals.database.User.query.filter_by(
+ username=u'happygirl').first()
assert new_user
assert new_user.status == u'needs_email_verification'
assert new_user.email_verified == False
@@ -186,8 +142,8 @@ def test_register_views(test_app):
'mediagoblin/user_pages/user.html']
# assert context['verification_successful'] == True
# TODO: Would be good to test messages here when we can do so...
- new_user = mg_globals.database.User.find_one(
- {'username': u'happygirl'})
+ new_user = mg_globals.database.User.query.filter_by(
+ username=u'happygirl').first()
assert new_user
assert new_user.status == u'active'
assert new_user.email_verified == True
@@ -233,35 +189,17 @@ def test_register_views(test_app):
path = urlparse.urlsplit(email_context['verification_url'])[2]
get_params = urlparse.urlsplit(email_context['verification_url'])[3]
- assert path == u'/auth/forgot_password/verify/'
parsed_get_params = urlparse.parse_qs(get_params)
-
- # user should have matching parameters
- new_user = mg_globals.database.User.find_one({'username': u'happygirl'})
- assert parsed_get_params['userid'] == [unicode(new_user.id)]
- assert parsed_get_params['token'] == [new_user.fp_verification_key]
-
- ### The forgotten password token should be set to expire in ~ 10 days
- # A few ticks have expired so there are only 9 full days left...
- assert (new_user.fp_token_expire - datetime.datetime.now()).days == 9
+ assert path == u'/auth/forgot_password/verify/'
## Try using a bs password-changing verification key, shouldn't work
template.clear_test_template_context()
response = test_app.get(
- "/auth/forgot_password/verify/?userid=%s&token=total_bs" % unicode(
- new_user.id), status=404)
- assert response.status.split()[0] == u'404' # status="404 NOT FOUND"
+ "/auth/forgot_password/verify/?token=total_bs")
+ response.follow()
- ## Try using an expired token to change password, shouldn't work
- template.clear_test_template_context()
- new_user = mg_globals.database.User.find_one({'username': u'happygirl'})
- real_token_expiration = new_user.fp_token_expire
- new_user.fp_token_expire = datetime.datetime.now()
- new_user.save()
- response = test_app.get("%s?%s" % (path, get_params), status=404)
- assert response.status.split()[0] == u'404' # status="404 NOT FOUND"
- new_user.fp_token_expire = real_token_expiration
- new_user.save()
+ # Correct redirect?
+ assert urlparse.urlsplit(response.location)[2] == '/'
## Verify step 1 of password-change works -- can see form to change password
template.clear_test_template_context()
@@ -272,7 +210,6 @@ def test_register_views(test_app):
template.clear_test_template_context()
response = test_app.post(
'/auth/forgot_password/verify/', {
- 'userid': parsed_get_params['userid'],
'password': 'iamveryveryhappy',
'token': parsed_get_params['token']})
response.follow()
@@ -298,6 +235,7 @@ def test_authentication_views(test_app):
# Make a new user
test_user = fixture_add_user(active_user=False)
+
# Get login
# ---------
test_app.get('/auth/login/')
@@ -310,7 +248,6 @@ def test_authentication_views(test_app):
context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
form = context['login_form']
assert form.username.errors == [u'This field is required.']
- assert form.password.errors == [u'This field is required.']
# Failed login - blank user
# -------------------------
@@ -328,9 +265,7 @@ def test_authentication_views(test_app):
response = test_app.post(
'/auth/login/', {
'username': u'chris'})
- context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
- form = context['login_form']
- assert form.password.errors == [u'This field is required.']
+ assert 'mediagoblin/auth/login.html' in template.TEMPLATE_TEST_CONTEXT
# Failed login - bad user
# -----------------------
@@ -394,3 +329,47 @@ def test_authentication_views(test_app):
'password': 'toast',
'next' : '/u/chris/'})
assert urlparse.urlsplit(response.location)[2] == '/u/chris/'
+
+
+@pytest.fixture()
+def authentication_disabled_app(request):
+ return get_app(
+ request,
+ mgoblin_config=pkg_resources.resource_filename(
+ 'mediagoblin.tests.auth_configs',
+ 'authentication_disabled_appconfig.ini'))
+
+
+def test_authentication_disabled_app(authentication_disabled_app):
+ # app.auth should = false
+ assert mg_globals.app.auth is False
+
+ # Try to visit register page
+ template.clear_test_template_context()
+ response = authentication_disabled_app.get('/auth/register/')
+ response.follow()
+
+ # Correct redirect?
+ assert urlparse.urlsplit(response.location)[2] == '/'
+ assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
+
+ # Try to vist login page
+ template.clear_test_template_context()
+ response = authentication_disabled_app.get('/auth/login/')
+ response.follow()
+
+ # Correct redirect?
+ assert urlparse.urlsplit(response.location)[2] == '/'
+ assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
+
+ ## Test check_login_simple should return None
+ assert auth_tools.check_login_simple('test', 'simple') is None
+
+ # Try to visit the forgot password page
+ template.clear_test_template_context()
+ response = authentication_disabled_app.get('/auth/register/')
+ response.follow()
+
+ # Correct redirect?
+ assert urlparse.urlsplit(response.location)[2] == '/'
+ assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
diff --git a/mediagoblin/tests/test_basic_auth.py b/mediagoblin/tests/test_basic_auth.py
new file mode 100644
index 00000000..cdd80fca
--- /dev/null
+++ b/mediagoblin/tests/test_basic_auth.py
@@ -0,0 +1,59 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+from mediagoblin.plugins.basic_auth import tools as auth_tools
+from mediagoblin.tools.testing import _activate_testing
+
+_activate_testing()
+
+
+########################
+# Test bcrypt auth funcs
+########################
+
+
+def test_bcrypt_check_password():
+ # Check known 'lollerskates' password against check function
+ assert auth_tools.bcrypt_check_password(
+ 'lollerskates',
+ '$2a$12$PXU03zfrVCujBhVeICTwtOaHTUs5FFwsscvSSTJkqx/2RQ0Lhy/nO')
+
+ assert not auth_tools.bcrypt_check_password(
+ 'notthepassword',
+ '$2a$12$PXU03zfrVCujBhVeICTwtOaHTUs5FFwsscvSSTJkqx/2RQ0Lhy/nO')
+
+ # Same thing, but with extra fake salt.
+ assert not auth_tools.bcrypt_check_password(
+ 'notthepassword',
+ '$2a$12$ELVlnw3z1FMu6CEGs/L8XO8vl0BuWSlUHgh0rUrry9DUXGMUNWwl6',
+ '3><7R45417')
+
+
+def test_bcrypt_gen_password_hash():
+ pw = 'youwillneverguessthis'
+
+ # Normal password hash generation, and check on that hash
+ hashed_pw = auth_tools.bcrypt_gen_password_hash(pw)
+ assert auth_tools.bcrypt_check_password(
+ pw, hashed_pw)
+ assert not auth_tools.bcrypt_check_password(
+ 'notthepassword', hashed_pw)
+
+ # Same thing, extra salt.
+ hashed_pw = auth_tools.bcrypt_gen_password_hash(pw, '3><7R45417')
+ assert auth_tools.bcrypt_check_password(
+ pw, hashed_pw, '3><7R45417')
+ assert not auth_tools.bcrypt_check_password(
+ 'notthepassword', hashed_pw, '3><7R45417')
diff --git a/mediagoblin/tests/test_celery_setup.py b/mediagoblin/tests/test_celery_setup.py
index 5530c6f2..0184436a 100644
--- a/mediagoblin/tests/test_celery_setup.py
+++ b/mediagoblin/tests/test_celery_setup.py
@@ -48,7 +48,7 @@ def test_setup_celery_from_config():
assert isinstance(fake_celery_module.CELERYD_ETA_SCHEDULER_PRECISION, float)
assert fake_celery_module.CELERY_RESULT_PERSISTENT is True
assert fake_celery_module.CELERY_IMPORTS == [
- 'foo.bar.baz', 'this.is.an.import', 'mediagoblin.processing.task']
+ 'foo.bar.baz', 'this.is.an.import', 'mediagoblin.processing.task', 'mediagoblin.notifications.task']
assert fake_celery_module.CELERY_RESULT_BACKEND == 'database'
assert fake_celery_module.CELERY_RESULT_DBURI == (
'sqlite:///' +
diff --git a/mediagoblin/tests/test_edit.py b/mediagoblin/tests/test_edit.py
index cda2607f..d70d0478 100644
--- a/mediagoblin/tests/test_edit.py
+++ b/mediagoblin/tests/test_edit.py
@@ -14,13 +14,14 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-import pytest
+import urlparse
from mediagoblin import mg_globals
from mediagoblin.db.models import User
from mediagoblin.tests.tools import fixture_add_user
-from mediagoblin.tools import template
-from mediagoblin.auth.lib import bcrypt_check_password
+from mediagoblin import auth
+from mediagoblin.tools import template, mail
+
class TestUserEdit(object):
def setup(self):
@@ -60,32 +61,34 @@ class TestUserEdit(object):
self.login(test_app)
# test that the password can be changed
- # template.clear_test_template_context()
+ template.clear_test_template_context()
res = test_app.post(
- '/edit/account/', {
+ '/edit/password/', {
'old_password': 'toast',
'new_password': '123456',
- 'wants_comment_notification': 'y'
})
+ res.follow()
+
+ # Did we redirect to the correct page?
+ assert urlparse.urlsplit(res.location)[2] == '/edit/account/'
- # Check for redirect on success
- assert res.status_int == 302
# test_user has to be fetched again in order to have the current values
test_user = User.query.filter_by(username=u'chris').first()
- assert bcrypt_check_password('123456', test_user.pw_hash)
+ assert auth.check_password('123456', test_user.pw_hash)
# Update current user passwd
self.user_password = '123456'
# test that the password cannot be changed if the given
- # old_password is wrong template.clear_test_template_context()
+ # old_password is wrong
+ template.clear_test_template_context()
test_app.post(
- '/edit/account/', {
+ '/edit/password/', {
'old_password': 'toast',
'new_password': '098765',
})
test_user = User.query.filter_by(username=u'chris').first()
- assert not bcrypt_check_password('098765', test_user.pw_hash)
+ assert not auth.check_password('098765', test_user.pw_hash)
def test_change_bio_url(self, test_app):
@@ -138,4 +141,68 @@ class TestUserEdit(object):
assert form.url.errors == [
u'This address contains errors']
+ def test_email_change(self, test_app):
+ self.login(test_app)
+
+ # Test email already in db
+ template.clear_test_template_context()
+ test_app.post(
+ '/edit/account/', {
+ 'new_email': 'chris@example.com',
+ 'password': 'toast'})
+
+ # Check form errors
+ context = template.TEMPLATE_TEST_CONTEXT[
+ 'mediagoblin/edit/edit_account.html']
+ assert context['form'].new_email.errors == [
+ u'Sorry, a user with that email address already exists.']
+
+ # Test successful email change
+ template.clear_test_template_context()
+ res = test_app.post(
+ '/edit/account/', {
+ 'new_email': 'new@example.com',
+ 'password': 'toast'})
+ res.follow()
+
+ # Correct redirect?
+ assert urlparse.urlsplit(res.location)[2] == '/u/chris/'
+
+ # Make sure we get email verification and try verifying
+ assert len(mail.EMAIL_TEST_INBOX) == 1
+ message = mail.EMAIL_TEST_INBOX.pop()
+ assert message['To'] == 'new@example.com'
+ email_context = template.TEMPLATE_TEST_CONTEXT[
+ 'mediagoblin/edit/verification.txt']
+ assert email_context['verification_url'] in \
+ message.get_payload(decode=True)
+
+ path = urlparse.urlsplit(email_context['verification_url'])[2]
+ assert path == u'/edit/verify_email/'
+
+ ## Try verifying with bs verification key, shouldn't work
+ template.clear_test_template_context()
+ res = test_app.get(
+ "/edit/verify_email/?token=total_bs")
+ res.follow()
+
+ # Correct redirect?
+ assert urlparse.urlsplit(res.location)[2] == '/'
+
+ # Email shouldn't be saved
+ email_in_db = mg_globals.database.User.query.filter_by(
+ email='new@example.com').first()
+ email = User.query.filter_by(username='chris').first().email
+ assert email_in_db is None
+ assert email == 'chris@example.com'
+
+ # Verify email activation works
+ template.clear_test_template_context()
+ get_params = urlparse.urlsplit(email_context['verification_url'])[3]
+ res = test_app.get('%s?%s' % (path, get_params))
+ res.follow()
+
+ # New email saved?
+ email = User.query.filter_by(username='chris').first().email
+ assert email == 'new@example.com'
# test changing the url inproperly
diff --git a/mediagoblin/tests/test_exif.py b/mediagoblin/tests/test_exif.py
index 824de3c2..c07e24ae 100644
--- a/mediagoblin/tests/test_exif.py
+++ b/mediagoblin/tests/test_exif.py
@@ -48,63 +48,324 @@ def test_exif_extraction():
assert gps == {}
# Do we have the "useful" tags?
- assert useful == {
- 'EXIF Flash': {
- 'field_type': 3,
- 'printable': u'Flash did not fire',
- 'field_offset': 380,
- 'tag': 37385,
- 'values': [0],
- 'field_length': 2},
- 'EXIF ExposureTime': {
- 'field_type': 5,
- 'printable': '1/125',
- 'field_offset': 700,
- 'tag': 33434,
- 'values': [[1, 125]],
- 'field_length': 8},
- 'EXIF FocalLength': {
- 'field_type': 5,
- 'printable': '18',
- 'field_offset': 780,
- 'tag': 37386,
- 'values': [[18, 1]],
- 'field_length': 8},
- 'Image Model': {
- 'field_type': 2,
- 'printable': 'NIKON D80',
- 'field_offset': 152,
- 'tag': 272,
- 'values': 'NIKON D80',
- 'field_length': 10},
- 'Image Make': {
- 'field_type': 2,
- 'printable': 'NIKON CORPORATION',
- 'field_offset': 134,
- 'tag': 271,
- 'values': 'NIKON CORPORATION',
- 'field_length': 18},
- 'EXIF ExposureMode': {
- 'field_type': 3,
- 'printable': 'Manual Exposure',
- 'field_offset': 584,
- 'tag': 41986,
- 'values': [1],
- 'field_length': 2},
- 'EXIF ISOSpeedRatings': {
- 'field_type': 3,
- 'printable': '100',
- 'field_offset': 260,
- 'tag': 34855,
- 'values': [100],
- 'field_length': 2},
- 'EXIF FNumber': {
- 'field_type': 5,
- 'printable': '10',
- 'field_offset': 708,
- 'tag': 33437,
- 'values': [[10, 1]],
- 'field_length': 8}}
+ assert useful == {'EXIF CVAPattern': {'field_length': 8,
+ 'field_offset': 26224,
+ 'field_type': 7,
+ 'printable': u'[0, 2, 0, 2, 1, 2, 0, 1]',
+ 'tag': 41730,
+ 'values': [0, 2, 0, 2, 1, 2, 0, 1]},
+ 'EXIF ColorSpace': {'field_length': 2,
+ 'field_offset': 476,
+ 'field_type': 3,
+ 'printable': u'sRGB',
+ 'tag': 40961,
+ 'values': [1]},
+ 'EXIF ComponentsConfiguration': {'field_length': 4,
+ 'field_offset': 308,
+ 'field_type': 7,
+ 'printable': u'YCbCr',
+ 'tag': 37121,
+ 'values': [1, 2, 3, 0]},
+ 'EXIF CompressedBitsPerPixel': {'field_length': 8,
+ 'field_offset': 756,
+ 'field_type': 5,
+ 'printable': u'4',
+ 'tag': 37122,
+ 'values': [[4, 1]]},
+ 'EXIF Contrast': {'field_length': 2,
+ 'field_offset': 656,
+ 'field_type': 3,
+ 'printable': u'Soft',
+ 'tag': 41992,
+ 'values': [1]},
+ 'EXIF CustomRendered': {'field_length': 2,
+ 'field_offset': 572,
+ 'field_type': 3,
+ 'printable': u'Normal',
+ 'tag': 41985,
+ 'values': [0]},
+ 'EXIF DateTimeDigitized': {'field_length': 20,
+ 'field_offset': 736,
+ 'field_type': 2,
+ 'printable': u'2011:06:22 12:20:33',
+ 'tag': 36868,
+ 'values': u'2011:06:22 12:20:33'},
+ 'EXIF DateTimeOriginal': {'field_length': 20,
+ 'field_offset': 716,
+ 'field_type': 2,
+ 'printable': u'2011:06:22 12:20:33',
+ 'tag': 36867,
+ 'values': u'2011:06:22 12:20:33'},
+ 'EXIF DigitalZoomRatio': {'field_length': 8,
+ 'field_offset': 26232,
+ 'field_type': 5,
+ 'printable': u'1',
+ 'tag': 41988,
+ 'values': [[1, 1]]},
+ 'EXIF ExifImageLength': {'field_length': 2,
+ 'field_offset': 500,
+ 'field_type': 3,
+ 'printable': u'2592',
+ 'tag': 40963,
+ 'values': [2592]},
+ 'EXIF ExifImageWidth': {'field_length': 2,
+ 'field_offset': 488,
+ 'field_type': 3,
+ 'printable': u'3872',
+ 'tag': 40962,
+ 'values': [3872]},
+ 'EXIF ExifVersion': {'field_length': 4,
+ 'field_offset': 272,
+ 'field_type': 7,
+ 'printable': u'0221',
+ 'tag': 36864,
+ 'values': [48, 50, 50, 49]},
+ 'EXIF ExposureBiasValue': {'field_length': 8,
+ 'field_offset': 764,
+ 'field_type': 10,
+ 'printable': u'0',
+ 'tag': 37380,
+ 'values': [[0, 1]]},
+ 'EXIF ExposureMode': {'field_length': 2,
+ 'field_offset': 584,
+ 'field_type': 3,
+ 'printable': u'Manual Exposure',
+ 'tag': 41986,
+ 'values': [1]},
+ 'EXIF ExposureProgram': {'field_length': 2,
+ 'field_offset': 248,
+ 'field_type': 3,
+ 'printable': u'Manual',
+ 'tag': 34850,
+ 'values': [1]},
+ 'EXIF ExposureTime': {'field_length': 8,
+ 'field_offset': 700,
+ 'field_type': 5,
+ 'printable': u'1/125',
+ 'tag': 33434,
+ 'values': [[1, 125]]},
+ 'EXIF FNumber': {'field_length': 8,
+ 'field_offset': 708,
+ 'field_type': 5,
+ 'printable': u'10',
+ 'tag': 33437,
+ 'values': [[10, 1]]},
+ 'EXIF FileSource': {'field_length': 1,
+ 'field_offset': 536,
+ 'field_type': 7,
+ 'printable': u'Digital Camera',
+ 'tag': 41728,
+ 'values': [3]},
+ 'EXIF Flash': {'field_length': 2,
+ 'field_offset': 380,
+ 'field_type': 3,
+ 'printable': u'Flash did not fire',
+ 'tag': 37385,
+ 'values': [0]},
+ 'EXIF FlashPixVersion': {'field_length': 4,
+ 'field_offset': 464,
+ 'field_type': 7,
+ 'printable': u'0100',
+ 'tag': 40960,
+ 'values': [48, 49, 48, 48]},
+ 'EXIF FocalLength': {'field_length': 8,
+ 'field_offset': 780,
+ 'field_type': 5,
+ 'printable': u'18',
+ 'tag': 37386,
+ 'values': [[18, 1]]},
+ 'EXIF FocalLengthIn35mmFilm': {'field_length': 2,
+ 'field_offset': 620,
+ 'field_type': 3,
+ 'printable': u'27',
+ 'tag': 41989,
+ 'values': [27]},
+ 'EXIF GainControl': {'field_length': 2,
+ 'field_offset': 644,
+ 'field_type': 3,
+ 'printable': u'None',
+ 'tag': 41991,
+ 'values': [0]},
+ 'EXIF ISOSpeedRatings': {'field_length': 2,
+ 'field_offset': 260,
+ 'field_type': 3,
+ 'printable': u'100',
+ 'tag': 34855,
+ 'values': [100]},
+ 'EXIF InteroperabilityOffset': {'field_length': 4,
+ 'field_offset': 512,
+ 'field_type': 4,
+ 'printable': u'26240',
+ 'tag': 40965,
+ 'values': [26240]},
+ 'EXIF LightSource': {'field_length': 2,
+ 'field_offset': 368,
+ 'field_type': 3,
+ 'printable': u'Unknown',
+ 'tag': 37384,
+ 'values': [0]},
+ 'EXIF MaxApertureValue': {'field_length': 8,
+ 'field_offset': 772,
+ 'field_type': 5,
+ 'printable': u'18/5',
+ 'tag': 37381,
+ 'values': [[18, 5]]},
+ 'EXIF MeteringMode': {'field_length': 2,
+ 'field_offset': 356,
+ 'field_type': 3,
+ 'printable': u'Pattern',
+ 'tag': 37383,
+ 'values': [5]},
+ 'EXIF Saturation': {'field_length': 2,
+ 'field_offset': 668,
+ 'field_type': 3,
+ 'printable': u'Normal',
+ 'tag': 41993,
+ 'values': [0]},
+ 'EXIF SceneCaptureType': {'field_length': 2,
+ 'field_offset': 632,
+ 'field_type': 3,
+ 'printable': u'Standard',
+ 'tag': 41990,
+ 'values': [0]},
+ 'EXIF SceneType': {'field_length': 1,
+ 'field_offset': 548,
+ 'field_type': 7,
+ 'printable': u'Directly Photographed',
+ 'tag': 41729,
+ 'values': [1]},
+ 'EXIF SensingMethod': {'field_length': 2,
+ 'field_offset': 524,
+ 'field_type': 3,
+ 'printable': u'One-chip color area',
+ 'tag': 41495,
+ 'values': [2]},
+ 'EXIF Sharpness': {'field_length': 2,
+ 'field_offset': 680,
+ 'field_type': 3,
+ 'printable': u'Normal',
+ 'tag': 41994,
+ 'values': [0]},
+ 'EXIF SubSecTime': {'field_length': 3,
+ 'field_offset': 428,
+ 'field_type': 2,
+ 'printable': u'10',
+ 'tag': 37520,
+ 'values': u'10'},
+ 'EXIF SubSecTimeDigitized': {'field_length': 3,
+ 'field_offset': 452,
+ 'field_type': 2,
+ 'printable': u'10',
+ 'tag': 37522,
+ 'values': u'10'},
+ 'EXIF SubSecTimeOriginal': {'field_length': 3,
+ 'field_offset': 440,
+ 'field_type': 2,
+ 'printable': u'10',
+ 'tag': 37521,
+ 'values': u'10'},
+ 'EXIF SubjectDistanceRange': {'field_length': 2,
+ 'field_offset': 692,
+ 'field_type': 3,
+ 'printable': u'0',
+ 'tag': 41996,
+ 'values': [0]},
+ 'EXIF WhiteBalance': {'field_length': 2,
+ 'field_offset': 596,
+ 'field_type': 3,
+ 'printable': u'Auto',
+ 'tag': 41987,
+ 'values': [0]},
+ 'Image DateTime': {'field_length': 20,
+ 'field_offset': 194,
+ 'field_type': 2,
+ 'printable': u'2011:06:22 12:20:33',
+ 'tag': 306,
+ 'values': u'2011:06:22 12:20:33'},
+ 'Image ExifOffset': {'field_length': 4,
+ 'field_offset': 126,
+ 'field_type': 4,
+ 'printable': u'214',
+ 'tag': 34665,
+ 'values': [214]},
+ 'Image Make': {'field_length': 18,
+ 'field_offset': 134,
+ 'field_type': 2,
+ 'printable': u'NIKON CORPORATION',
+ 'tag': 271,
+ 'values': u'NIKON CORPORATION'},
+ 'Image Model': {'field_length': 10,
+ 'field_offset': 152,
+ 'field_type': 2,
+ 'printable': u'NIKON D80',
+ 'tag': 272,
+ 'values': u'NIKON D80'},
+ 'Image Orientation': {'field_length': 2,
+ 'field_offset': 42,
+ 'field_type': 3,
+ 'printable': u'Rotated 90 CCW',
+ 'tag': 274,
+ 'values': [6]},
+ 'Image ResolutionUnit': {'field_length': 2,
+ 'field_offset': 78,
+ 'field_type': 3,
+ 'printable': u'Pixels/Inch',
+ 'tag': 296,
+ 'values': [2]},
+ 'Image Software': {'field_length': 15,
+ 'field_offset': 178,
+ 'field_type': 2,
+ 'printable': u'Shotwell 0.9.3',
+ 'tag': 305,
+ 'values': u'Shotwell 0.9.3'},
+ 'Image XResolution': {'field_length': 8,
+ 'field_offset': 162,
+ 'field_type': 5,
+ 'printable': u'300',
+ 'tag': 282,
+ 'values': [[300, 1]]},
+ 'Image YCbCrPositioning': {'field_length': 2,
+ 'field_offset': 114,
+ 'field_type': 3,
+ 'printable': u'Co-sited',
+ 'tag': 531,
+ 'values': [2]},
+ 'Image YResolution': {'field_length': 8,
+ 'field_offset': 170,
+ 'field_type': 5,
+ 'printable': u'300',
+ 'tag': 283,
+ 'values': [[300, 1]]},
+ 'Thumbnail Compression': {'field_length': 2,
+ 'field_offset': 26280,
+ 'field_type': 3,
+ 'printable': u'JPEG (old-style)',
+ 'tag': 259,
+ 'values': [6]},
+ 'Thumbnail ResolutionUnit': {'field_length': 2,
+ 'field_offset': 26316,
+ 'field_type': 3,
+ 'printable': u'Pixels/Inch',
+ 'tag': 296,
+ 'values': [2]},
+ 'Thumbnail XResolution': {'field_length': 8,
+ 'field_offset': 26360,
+ 'field_type': 5,
+ 'printable': u'300',
+ 'tag': 282,
+ 'values': [[300, 1]]},
+ 'Thumbnail YCbCrPositioning': {'field_length': 2,
+ 'field_offset': 26352,
+ 'field_type': 3,
+ 'printable': u'Co-sited',
+ 'tag': 531,
+ 'values': [2]},
+ 'Thumbnail YResolution': {'field_length': 8,
+ 'field_offset': 26368,
+ 'field_type': 5,
+ 'printable': u'300',
+ 'tag': 283,
+ 'values': [[300, 1]]}}
def test_exif_image_orientation():
diff --git a/mediagoblin/tests/test_mgoblin_app.ini b/mediagoblin/tests/test_mgoblin_app.ini
index 2e876812..535cf1c1 100644
--- a/mediagoblin/tests/test_mgoblin_app.ini
+++ b/mediagoblin/tests/test_mgoblin_app.ini
@@ -3,8 +3,9 @@ direct_remote_path = /test_static/
email_sender_address = "notice@mediagoblin.example.org"
email_debug_mode = true
-# TODO: Switch to using an in-memory database
-sql_engine = "sqlite:///%(here)s/test_user_dev/mediagoblin.db"
+#Runs with an in-memory sqlite db for speed.
+sql_engine = "sqlite://"
+run_migrations = true
# tag parsing
tags_max_length = 50
@@ -12,22 +13,24 @@ tags_max_length = 50
# So we can start to test attachments:
allow_attachments = True
-media_types = mediagoblin.media_types.image, mediagoblin.media_types.pdf
-
[storage:publicstore]
-base_dir = %(here)s/test_user_dev/media/public
+base_dir = %(here)s/user_dev/media/public
base_url = /mgoblin_media/
[storage:queuestore]
-base_dir = %(here)s/test_user_dev/media/queue
+base_dir = %(here)s/user_dev/media/queue
[celery]
CELERY_ALWAYS_EAGER = true
-CELERY_RESULT_DBURI = "sqlite:///%(here)s/test_user_dev/celery.db"
-BROKER_HOST = "sqlite:///%(here)s/test_user_dev/kombu.db"
+CELERY_RESULT_DBURI = "sqlite:///%(here)s/user_dev/celery.db"
+BROKER_HOST = "sqlite:///%(here)s/user_dev/kombu.db"
[plugins]
[[mediagoblin.plugins.api]]
[[mediagoblin.plugins.oauth]]
[[mediagoblin.plugins.httpapiauth]]
[[mediagoblin.plugins.piwigo]]
+[[mediagoblin.plugins.basic_auth]]
+[[mediagoblin.plugins.openid]]
+[[mediagoblin.media_types.image]]
+[[mediagoblin.media_types.pdf]]
diff --git a/mediagoblin/tests/test_misc.py b/mediagoblin/tests/test_misc.py
index 755d863f..43ad0b6d 100644
--- a/mediagoblin/tests/test_misc.py
+++ b/mediagoblin/tests/test_misc.py
@@ -28,8 +28,10 @@ def test_user_deletes_other_comments(test_app):
user_a = fixture_add_user(u"chris_a")
user_b = fixture_add_user(u"chris_b")
- media_a = fixture_media_entry(uploader=user_a.id, save=False)
- media_b = fixture_media_entry(uploader=user_b.id, save=False)
+ media_a = fixture_media_entry(uploader=user_a.id, save=False,
+ expunge=False, fake_upload=False)
+ media_b = fixture_media_entry(uploader=user_b.id, save=False,
+ expunge=False, fake_upload=False)
Session.add(media_a)
Session.add(media_b)
Session.flush()
@@ -79,7 +81,7 @@ def test_user_deletes_other_comments(test_app):
def test_media_deletes_broken_attachment(test_app):
user_a = fixture_add_user(u"chris_a")
- media = fixture_media_entry(uploader=user_a.id, save=False)
+ media = fixture_media_entry(uploader=user_a.id, save=False, expunge=False)
media.attachment_files.append(dict(
name=u"some name",
filepath=[u"does", u"not", u"exist"],
diff --git a/mediagoblin/tests/test_notifications.py b/mediagoblin/tests/test_notifications.py
new file mode 100644
index 00000000..d52b8d5a
--- /dev/null
+++ b/mediagoblin/tests/test_notifications.py
@@ -0,0 +1,151 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import pytest
+
+import urlparse
+
+from mediagoblin.tools import template, mail
+
+from mediagoblin.db.models import Notification, CommentNotification, \
+ CommentSubscription
+from mediagoblin.db.base import Session
+
+from mediagoblin.notifications import mark_comment_notification_seen
+
+from mediagoblin.tests.tools import fixture_add_comment, \
+ fixture_media_entry, fixture_add_user, \
+ fixture_comment_subscription
+
+
+class TestNotifications:
+ @pytest.fixture(autouse=True)
+ def setup(self, test_app):
+ self.test_app = test_app
+
+ # TODO: Possibly abstract into a decorator like:
+ # @as_authenticated_user('chris')
+ self.test_user = fixture_add_user()
+
+ self.current_user = None
+
+ self.login()
+
+ def login(self, username=u'chris', password=u'toast'):
+ response = self.test_app.post(
+ '/auth/login/', {
+ 'username': username,
+ 'password': password})
+
+ response.follow()
+
+ assert urlparse.urlsplit(response.location)[2] == '/'
+ assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
+
+ ctx = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
+
+ assert Session.merge(ctx['request'].user).username == username
+
+ self.current_user = ctx['request'].user
+
+ def logout(self):
+ self.test_app.get('/auth/logout/')
+ self.current_user = None
+
+ @pytest.mark.parametrize('wants_email', [True, False])
+ def test_comment_notification(self, wants_email):
+ '''
+ Test
+ - if a notification is created when posting a comment on
+ another users media entry.
+ - that the comment data is consistent and exists.
+
+ '''
+ user = fixture_add_user('otherperson', password='nosreprehto',
+ wants_comment_notification=wants_email)
+
+ user_id = user.id
+
+ media_entry = fixture_media_entry(uploader=user.id, state=u'processed')
+
+ media_entry_id = media_entry.id
+
+ subscription = fixture_comment_subscription(media_entry)
+
+ subscription_id = subscription.id
+
+ media_uri_id = '/u/{0}/m/{1}/'.format(user.username,
+ media_entry.id)
+ media_uri_slug = '/u/{0}/m/{1}/'.format(user.username,
+ media_entry.slug)
+
+ self.test_app.post(
+ media_uri_id + 'comment/add/',
+ {
+ 'comment_content': u'Test comment #42'
+ }
+ )
+
+ notifications = Notification.query.filter_by(
+ user_id=user.id).all()
+
+ assert len(notifications) == 1
+
+ notification = notifications[0]
+
+ assert type(notification) == CommentNotification
+ assert notification.seen == False
+ assert notification.user_id == user.id
+ assert notification.subject.get_author.id == self.test_user.id
+ assert notification.subject.content == u'Test comment #42'
+
+ if wants_email == True:
+ assert mail.EMAIL_TEST_MBOX_INBOX == [
+ {'from': 'notice@mediagoblin.example.org',
+ 'message': 'Content-Type: text/plain; \
+charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: \
+base64\nSubject: GNU MediaGoblin - chris commented on your \
+post\nFrom: notice@mediagoblin.example.org\nTo: \
+otherperson@example.com\n\nSGkgb3RoZXJwZXJzb24sCmNocmlzIGNvbW1lbnRlZCBvbiB5b3VyIHBvc3QgKGh0dHA6Ly9sb2Nh\nbGhvc3Q6ODAvdS9vdGhlcnBlcnNvbi9tL3NvbWUtdGl0bGUvYy8xLyNjb21tZW50KSBhdCBHTlUg\nTWVkaWFHb2JsaW4KClRlc3QgY29tbWVudCAjNDIKCkdOVSBNZWRpYUdvYmxpbg==\n',
+ 'to': [u'otherperson@example.com']}]
+ else:
+ assert mail.EMAIL_TEST_MBOX_INBOX == []
+
+ # Save the ids temporarily because of DetachedInstanceError
+ notification_id = notification.id
+ comment_id = notification.subject.id
+
+ self.logout()
+ self.login('otherperson', 'nosreprehto')
+
+ self.test_app.get(media_uri_slug + '/c/{0}/'.format(comment_id))
+
+ notification = Notification.query.filter_by(id=notification_id).first()
+
+ assert notification.seen == True
+
+ self.test_app.get(media_uri_slug + '/notifications/silence/')
+
+ subscription = CommentSubscription.query.filter_by(id=subscription_id)\
+ .first()
+
+ assert subscription.notify == False
+
+ notifications = Notification.query.filter_by(
+ user_id=user_id).all()
+
+ # User should not have been notified
+ assert len(notifications) == 1
diff --git a/mediagoblin/tests/test_openid.py b/mediagoblin/tests/test_openid.py
new file mode 100644
index 00000000..23a2290e
--- /dev/null
+++ b/mediagoblin/tests/test_openid.py
@@ -0,0 +1,373 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import urlparse
+import pkg_resources
+import pytest
+import mock
+
+openid_consumer = pytest.importorskip(
+ "openid.consumer.consumer")
+
+from mediagoblin import mg_globals
+from mediagoblin.db.base import Session
+from mediagoblin.db.models import User
+from mediagoblin.plugins.openid.models import OpenIDUserURL
+from mediagoblin.tests.tools import get_app, fixture_add_user
+from mediagoblin.tools import template
+
+# App with plugin enabled
+@pytest.fixture()
+def openid_plugin_app(request):
+ return get_app(
+ request,
+ mgoblin_config=pkg_resources.resource_filename(
+ 'mediagoblin.tests.auth_configs',
+ 'openid_appconfig.ini'))
+
+
+class TestOpenIDPlugin(object):
+ def _setup(self, openid_plugin_app, value=True, edit=False, delete=False):
+ if value:
+ response = openid_consumer.SuccessResponse(mock.Mock(), mock.Mock())
+ if edit or delete:
+ response.identity_url = u'http://add.myopenid.com'
+ else:
+ response.identity_url = u'http://real.myopenid.com'
+ self._finish_verification = mock.Mock(return_value=response)
+ else:
+ self._finish_verification = mock.Mock(return_value=False)
+
+ @mock.patch('mediagoblin.plugins.openid.views._response_email', mock.Mock(return_value=None))
+ @mock.patch('mediagoblin.plugins.openid.views._response_nickname', mock.Mock(return_value=None))
+ @mock.patch('mediagoblin.plugins.openid.views._finish_verification', self._finish_verification)
+ def _setup_start(self, openid_plugin_app, edit, delete):
+ if edit:
+ self._start_verification = mock.Mock(return_value=openid_plugin_app.post(
+ '/edit/openid/finish/'))
+ elif delete:
+ self._start_verification = mock.Mock(return_value=openid_plugin_app.post(
+ '/edit/openid/delete/finish/'))
+ else:
+ self._start_verification = mock.Mock(return_value=openid_plugin_app.post(
+ '/auth/openid/login/finish/'))
+ _setup_start(self, openid_plugin_app, edit, delete)
+
+ def test_bad_login(self, openid_plugin_app):
+ """ Test that attempts to login with invalid paramaters"""
+
+ # Test GET request for auth/register page
+ res = openid_plugin_app.get('/auth/register/').follow()
+
+ # Make sure it redirected to the correct place
+ assert urlparse.urlsplit(res.location)[2] == '/auth/openid/login/'
+
+ # Test GET request for auth/login page
+ res = openid_plugin_app.get('/auth/login/')
+ res.follow()
+
+ # Correct redirect?
+ assert urlparse.urlsplit(res.location)[2] == '/auth/openid/login/'
+
+ # Test GET request for auth/openid/register page
+ res = openid_plugin_app.get('/auth/openid/register/')
+ res.follow()
+
+ # Correct redirect?
+ assert urlparse.urlsplit(res.location)[2] == '/auth/openid/login/'
+
+ # Test GET request for auth/openid/login/finish page
+ res = openid_plugin_app.get('/auth/openid/login/finish/')
+ res.follow()
+
+ # Correct redirect?
+ assert urlparse.urlsplit(res.location)[2] == '/auth/openid/login/'
+
+ # Test GET request for auth/openid/login page
+ res = openid_plugin_app.get('/auth/openid/login/')
+
+ # Correct place?
+ assert 'mediagoblin/plugins/openid/login.html' in template.TEMPLATE_TEST_CONTEXT
+
+ # Try to login with an empty form
+ template.clear_test_template_context()
+ openid_plugin_app.post(
+ '/auth/openid/login/', {})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/login.html']
+ form = context['login_form']
+ assert form.openid.errors == [u'This field is required.']
+
+ # Try to login with wrong form values
+ template.clear_test_template_context()
+ openid_plugin_app.post(
+ '/auth/openid/login/', {
+ 'openid': 'not_a_url.com'})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/login.html']
+ form = context['login_form']
+ assert form.openid.errors == [u'Please enter a valid url.']
+
+ # Should be no users in the db
+ assert User.query.count() == 0
+
+ # Phony OpenID URl
+ template.clear_test_template_context()
+ openid_plugin_app.post(
+ '/auth/openid/login/', {
+ 'openid': 'http://phoney.myopenid.com/'})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/login.html']
+ form = context['login_form']
+ assert form.openid.errors == [u'Sorry, the OpenID server could not be found']
+
+ def test_login(self, openid_plugin_app):
+ """Tests that test login and registion with openid"""
+ # Test finish_login redirects correctly when response = False
+ self._setup(openid_plugin_app, False)
+
+ @mock.patch('mediagoblin.plugins.openid.views._finish_verification', self._finish_verification)
+ @mock.patch('mediagoblin.plugins.openid.views._start_verification', self._start_verification)
+ def _test_non_response():
+ template.clear_test_template_context()
+ res = openid_plugin_app.post(
+ '/auth/openid/login/', {
+ 'openid': 'http://phoney.myopenid.com/'})
+ res.follow()
+
+ # Correct Place?
+ assert urlparse.urlsplit(res.location)[2] == '/auth/openid/login/'
+ assert 'mediagoblin/plugins/openid/login.html' in template.TEMPLATE_TEST_CONTEXT
+ _test_non_response()
+
+ # Test login with new openid
+ # Need to clear_test_template_context before calling _setup
+ template.clear_test_template_context()
+ self._setup(openid_plugin_app)
+
+ @mock.patch('mediagoblin.plugins.openid.views._finish_verification', self._finish_verification)
+ @mock.patch('mediagoblin.plugins.openid.views._start_verification', self._start_verification)
+ def _test_new_user():
+ openid_plugin_app.post(
+ '/auth/openid/login/', {
+ 'openid': u'http://real.myopenid.com'})
+
+ # Right place?
+ assert 'mediagoblin/auth/register.html' in template.TEMPLATE_TEST_CONTEXT
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
+ register_form = context['register_form']
+
+ # Register User
+ res = openid_plugin_app.post(
+ '/auth/openid/register/', {
+ 'openid': register_form.openid.data,
+ 'username': u'chris',
+ 'email': u'chris@example.com'})
+ res.follow()
+
+ # Correct place?
+ assert urlparse.urlsplit(res.location)[2] == '/u/chris/'
+ assert 'mediagoblin/user_pages/user.html' in template.TEMPLATE_TEST_CONTEXT
+
+ # No need to test if user is in logged in and verification email
+ # awaits, since openid uses the register_user function which is
+ # tested in test_auth
+
+ # Logout User
+ openid_plugin_app.get('/auth/logout')
+
+ # Get user and detach from session
+ test_user = mg_globals.database.User.query.filter_by(
+ username=u'chris').first()
+ Session.expunge(test_user)
+
+ # Log back in
+ # Could not get it to work by 'POST'ing to /auth/openid/login/
+ template.clear_test_template_context()
+ res = openid_plugin_app.post(
+ '/auth/openid/login/finish/', {
+ 'openid': u'http://real.myopenid.com'})
+ res.follow()
+
+ assert urlparse.urlsplit(res.location)[2] == '/'
+ assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
+
+ # Make sure user is in the session
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
+ session = context['request'].session
+ assert session['user_id'] == unicode(test_user.id)
+
+ _test_new_user()
+
+ # Test register with empty form
+ template.clear_test_template_context()
+ openid_plugin_app.post(
+ '/auth/openid/register/', {})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
+ register_form = context['register_form']
+
+ assert register_form.openid.errors == [u'This field is required.']
+ assert register_form.email.errors == [u'This field is required.']
+ assert register_form.username.errors == [u'This field is required.']
+
+ # Try to register with existing username and email
+ template.clear_test_template_context()
+ openid_plugin_app.post(
+ '/auth/openid/register/', {
+ 'openid': 'http://real.myopenid.com',
+ 'email': 'chris@example.com',
+ 'username': 'chris'})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
+ register_form = context['register_form']
+
+ assert register_form.username.errors == [u'Sorry, a user with that name already exists.']
+ assert register_form.email.errors == [u'Sorry, a user with that email address already exists.']
+ assert register_form.openid.errors == [u'Sorry, an account is already registered to that OpenID.']
+
+ def test_add_delete(self, openid_plugin_app):
+ """Test adding and deleting openids"""
+ # Add user
+ test_user = fixture_add_user(password='')
+ openid = OpenIDUserURL()
+ openid.openid_url = 'http://real.myopenid.com'
+ openid.user_id = test_user.id
+ openid.save()
+
+ # Log user in
+ template.clear_test_template_context()
+ self._setup(openid_plugin_app)
+
+ @mock.patch('mediagoblin.plugins.openid.views._finish_verification', self._finish_verification)
+ @mock.patch('mediagoblin.plugins.openid.views._start_verification', self._start_verification)
+ def _login_user():
+ openid_plugin_app.post(
+ '/auth/openid/login/finish/', {
+ 'openid': u'http://real.myopenid.com'})
+
+ _login_user()
+
+ # Try and delete only OpenID url
+ template.clear_test_template_context()
+ res = openid_plugin_app.post(
+ '/edit/openid/delete/', {
+ 'openid': 'http://real.myopenid.com'})
+ assert 'mediagoblin/plugins/openid/delete.html' in template.TEMPLATE_TEST_CONTEXT
+
+ # Add OpenID to user
+ # Empty form
+ template.clear_test_template_context()
+ res = openid_plugin_app.post(
+ '/edit/openid/', {})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/add.html']
+ form = context['form']
+ assert form.openid.errors == [u'This field is required.']
+
+ # Try with a bad url
+ template.clear_test_template_context()
+ openid_plugin_app.post(
+ '/edit/openid/', {
+ 'openid': u'not_a_url.com'})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/add.html']
+ form = context['form']
+ assert form.openid.errors == [u'Please enter a valid url.']
+
+ # Try with a url that's already registered
+ template.clear_test_template_context()
+ openid_plugin_app.post(
+ '/edit/openid/', {
+ 'openid': 'http://real.myopenid.com'})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/add.html']
+ form = context['form']
+ assert form.openid.errors == [u'Sorry, an account is already registered to that OpenID.']
+
+ # Test adding openid to account
+ # Need to clear_test_template_context before calling _setup
+ template.clear_test_template_context()
+ self._setup(openid_plugin_app, edit=True)
+
+ # Need to remove openid_url from db because it was added at setup
+ openid = OpenIDUserURL.query.filter_by(
+ openid_url=u'http://add.myopenid.com')
+ openid.delete()
+
+ @mock.patch('mediagoblin.plugins.openid.views._finish_verification', self._finish_verification)
+ @mock.patch('mediagoblin.plugins.openid.views._start_verification', self._start_verification)
+ def _test_add():
+ # Successful add
+ template.clear_test_template_context()
+ res = openid_plugin_app.post(
+ '/edit/openid/', {
+ 'openid': u'http://add.myopenid.com'})
+ res.follow()
+
+ # Correct place?
+ assert urlparse.urlsplit(res.location)[2] == '/edit/account/'
+ assert 'mediagoblin/edit/edit_account.html' in template.TEMPLATE_TEST_CONTEXT
+
+ # OpenID Added?
+ new_openid = mg_globals.database.OpenIDUserURL.query.filter_by(
+ openid_url=u'http://add.myopenid.com').first()
+ assert new_openid
+
+ _test_add()
+
+ # Test deleting openid from account
+ # Need to clear_test_template_context before calling _setup
+ template.clear_test_template_context()
+ self._setup(openid_plugin_app, delete=True)
+
+ # Need to add OpenID back to user because it was deleted during
+ # patch
+ openid = OpenIDUserURL()
+ openid.openid_url = 'http://add.myopenid.com'
+ openid.user_id = test_user.id
+ openid.save()
+
+ @mock.patch('mediagoblin.plugins.openid.views._finish_verification', self._finish_verification)
+ @mock.patch('mediagoblin.plugins.openid.views._start_verification', self._start_verification)
+ def _test_delete(self, test_user):
+ # Delete openid from user
+ # Create another user to test deleting OpenID that doesn't belong to them
+ new_user = fixture_add_user(username='newman')
+ openid = OpenIDUserURL()
+ openid.openid_url = 'http://realfake.myopenid.com/'
+ openid.user_id = new_user.id
+ openid.save()
+
+ # Try and delete OpenID url that isn't the users
+ template.clear_test_template_context()
+ res = openid_plugin_app.post(
+ '/edit/openid/delete/', {
+ 'openid': 'http://realfake.myopenid.com/'})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/delete.html']
+ form = context['form']
+ assert form.openid.errors == [u'That OpenID is not registered to this account.']
+
+ # Delete OpenID
+ # Kind of weird to POST to delete/finish
+ template.clear_test_template_context()
+ res = openid_plugin_app.post(
+ '/edit/openid/delete/finish/', {
+ 'openid': u'http://add.myopenid.com'})
+ res.follow()
+
+ # Correct place?
+ assert urlparse.urlsplit(res.location)[2] == '/edit/account/'
+ assert 'mediagoblin/edit/edit_account.html' in template.TEMPLATE_TEST_CONTEXT
+
+ # OpenID deleted?
+ new_openid = mg_globals.database.OpenIDUserURL.query.filter_by(
+ openid_url=u'http://add.myopenid.com').first()
+ assert not new_openid
+
+ _test_delete(self, test_user)
diff --git a/mediagoblin/tests/test_paste.ini b/mediagoblin/tests/test_paste.ini
index 91ecbb84..a9595432 100644
--- a/mediagoblin/tests/test_paste.ini
+++ b/mediagoblin/tests/test_paste.ini
@@ -6,6 +6,8 @@ use = egg:Paste#urlmap
/ = mediagoblin
/mgoblin_media/ = publicstore_serve
/test_static/ = mediagoblin_static
+/theme_static/ = theme_static
+/plugin_static/ = plugin_static
[app:mediagoblin]
use = egg:mediagoblin#app
@@ -13,12 +15,22 @@ config = %(here)s/mediagoblin.ini
[app:publicstore_serve]
use = egg:Paste#static
-document_root = %(here)s/test_user_dev/media/public
+document_root = %(here)s/user_dev/media/public
[app:mediagoblin_static]
use = egg:Paste#static
document_root = %(here)s/mediagoblin/static/
+[app:theme_static]
+use = egg:Paste#static
+document_root = %(here)s/user_dev/theme_static/
+cache_max_age = 86400
+
+[app:plugin_static]
+use = egg:Paste#static
+document_root = %(here)s/user_dev/plugin_static/
+cache_max_age = 86400
+
[celery]
CELERY_ALWAYS_EAGER = true
diff --git a/mediagoblin/tests/test_piwigo.py b/mediagoblin/tests/test_piwigo.py
index 18f95057..16ad0111 100644
--- a/mediagoblin/tests/test_piwigo.py
+++ b/mediagoblin/tests/test_piwigo.py
@@ -44,11 +44,13 @@ class Test_PWG(object):
def test_session(self):
resp = self.do_post("pwg.session.login",
{"username": u"nouser", "password": "wrong"})
- assert resp.body == XML_PREFIX + '<rsp stat="ok">0</rsp>'
+ assert resp.body == XML_PREFIX \
+ + '<rsp stat="fail"><err code="999" msg="Invalid username/password"/></rsp>'
resp = self.do_post("pwg.session.login",
{"username": self.username, "password": "wrong"})
- assert resp.body == XML_PREFIX + '<rsp stat="ok">0</rsp>'
+ assert resp.body == XML_PREFIX \
+ + '<rsp stat="fail"><err code="999" msg="Invalid username/password"/></rsp>'
resp = self.do_get("pwg.session.getStatus")
assert resp.body == XML_PREFIX \
@@ -56,7 +58,7 @@ class Test_PWG(object):
resp = self.do_post("pwg.session.login",
{"username": self.username, "password": self.password})
- assert resp.body == XML_PREFIX + '<rsp stat="ok">1</rsp>'
+ assert resp.body == XML_PREFIX + '<rsp stat="ok">1</rsp>'
resp = self.do_get("pwg.session.getStatus")
assert resp.body == XML_PREFIX \
diff --git a/mediagoblin/tests/test_pluginapi.py b/mediagoblin/tests/test_pluginapi.py
index 809b5ce9..eae0ce15 100644
--- a/mediagoblin/tests/test_pluginapi.py
+++ b/mediagoblin/tests/test_pluginapi.py
@@ -14,6 +14,8 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import os
+import json
import sys
from configobj import ConfigObj
@@ -24,7 +26,10 @@ from validate import VdtTypeError
from mediagoblin import mg_globals
from mediagoblin.init.plugins import setup_plugins
from mediagoblin.init.config import read_mediagoblin_config
+from mediagoblin.gmg_commands.assetlink import link_plugin_assets
from mediagoblin.tools import pluginapi
+from mediagoblin.tests.tools import get_app
+from mediagoblin.tools.common import CollectingPrinter
def with_cleanup(*modules_to_delete):
@@ -323,3 +328,139 @@ def test_plugin_config():
# the callables thing shouldn't really have anything though.
assert len(config['plugins'][
'mediagoblin.tests.testplugins.callables1']) == 0
+
+
+@pytest.fixture()
+def context_modified_app(request):
+ """
+ Get a MediaGoblin app fixture using appconfig_context_modified.ini
+ """
+ return get_app(
+ request,
+ mgoblin_config=pkg_resources.resource_filename(
+ 'mediagoblin.tests', 'appconfig_context_modified.ini'))
+
+
+def test_modify_context(context_modified_app):
+ """
+ Test that we can modify both the view/template specific and
+ global contexts for templates.
+ """
+ # Specific thing passed into a page
+ result = context_modified_app.get("/modify_context/specific/")
+ assert result.body.strip() == """Specific page!
+
+specific thing: in yer specificpage
+global thing: globally appended!
+something: orother
+doubleme: happyhappy"""
+
+ # General test, should have global context variable only
+ result = context_modified_app.get("/modify_context/")
+ assert result.body.strip() == """General page!
+
+global thing: globally appended!
+lol: cats
+doubleme: joyjoy"""
+
+
+@pytest.fixture()
+def static_plugin_app(request):
+ """
+ Get a MediaGoblin app fixture using appconfig_static_plugin.ini
+ """
+ return get_app(
+ request,
+ mgoblin_config=pkg_resources.resource_filename(
+ 'mediagoblin.tests', 'appconfig_static_plugin.ini'))
+
+
+def test_plugin_assetlink(static_plugin_app):
+ """
+ Test that the assetlink command works correctly
+ """
+ linked_assets_dir = mg_globals.app_config['plugin_linked_assets_dir']
+ plugin_link_dir = os.path.join(
+ linked_assets_dir.rstrip(os.path.sep),
+ 'staticstuff')
+
+ plugin_statics = pluginapi.hook_runall("static_setup")
+ assert len(plugin_statics) == 1
+ plugin_static = plugin_statics[0]
+
+ def run_assetlink():
+ printer = CollectingPrinter()
+
+ link_plugin_assets(
+ plugin_static, linked_assets_dir, printer)
+
+ return printer
+
+ # it shouldn't exist yet
+ assert not os.path.lexists(plugin_link_dir)
+
+ # link dir doesn't exist, link it
+ result = run_assetlink().collection[0]
+ assert result == \
+ 'Linked asset directory for plugin "staticstuff":\n %s\nto:\n %s\n' % (
+ plugin_static.file_path.rstrip(os.path.sep),
+ plugin_link_dir)
+ assert os.path.lexists(plugin_link_dir)
+ assert os.path.islink(plugin_link_dir)
+ assert os.path.realpath(plugin_link_dir) == plugin_static.file_path
+
+ # link dir exists, leave it alone
+ # (and it should exist still since we just ran it..)
+ result = run_assetlink().collection[0]
+ assert result == 'Skipping "staticstuff"; already set up.\n'
+ assert os.path.lexists(plugin_link_dir)
+ assert os.path.islink(plugin_link_dir)
+ assert os.path.realpath(plugin_link_dir) == plugin_static.file_path
+
+ # link dir exists, is a symlink to somewhere else (re-link)
+ junk_file_path = os.path.join(
+ linked_assets_dir.rstrip(os.path.sep),
+ 'junk.txt')
+ with file(junk_file_path, 'w') as junk_file:
+ junk_file.write('barf')
+
+ os.unlink(plugin_link_dir)
+ os.symlink(junk_file_path, plugin_link_dir)
+
+ result = run_assetlink().combined_string
+ assert result == """Old link found for "staticstuff"; removing.
+Linked asset directory for plugin "staticstuff":
+ %s
+to:
+ %s
+""" % (plugin_static.file_path.rstrip(os.path.sep), plugin_link_dir)
+ assert os.path.lexists(plugin_link_dir)
+ assert os.path.islink(plugin_link_dir)
+ assert os.path.realpath(plugin_link_dir) == plugin_static.file_path
+
+ # link dir exists, but is a non-symlink
+ os.unlink(plugin_link_dir)
+ with file(plugin_link_dir, 'w') as clobber_file:
+ clobber_file.write('clobbered!')
+
+ result = run_assetlink().collection[0]
+ assert result == 'Could not link "staticstuff": %s exists and is not a symlink\n' % (
+ plugin_link_dir)
+
+ with file(plugin_link_dir, 'r') as clobber_file:
+ assert clobber_file.read() == 'clobbered!'
+
+
+def test_plugin_staticdirect(static_plugin_app):
+ """
+ Test that the staticdirect utilities pull up the right things
+ """
+ result = json.loads(
+ static_plugin_app.get('/staticstuff/').body)
+
+ assert len(result) == 2
+
+ assert result['mgoblin_bunny_pic'] == '/test_static/images/bunny_pic.png'
+ assert result['plugin_bunny_css'] == \
+ '/plugin_static/staticstuff/css/bunnify.css'
+
diff --git a/mediagoblin/tests/test_submission.py b/mediagoblin/tests/test_submission.py
index 162b2d19..ac941063 100644
--- a/mediagoblin/tests/test_submission.py
+++ b/mediagoblin/tests/test_submission.py
@@ -26,7 +26,7 @@ from mediagoblin.tests.tools import fixture_add_user
from mediagoblin import mg_globals
from mediagoblin.db.models import MediaEntry
from mediagoblin.tools import template
-from mediagoblin.media_types.image import MEDIA_MANAGER as img_MEDIA_MANAGER
+from mediagoblin.media_types.image import ImageMediaManager
from mediagoblin.media_types.pdf.processing import check_prerequisites as pdf_check_prerequisites
from .resources import GOOD_JPG, GOOD_PNG, EVIL_FILE, EVIL_JPG, EVIL_PNG, \
@@ -77,7 +77,7 @@ class TestSubmission:
return {'upload_files': [('file', filename)]}
def check_comments(self, request, media_id, count):
- comments = request.db.MediaComment.find({'media_entry': media_id})
+ comments = request.db.MediaComment.query.filter_by(media_entry=media_id)
assert count == len(list(comments))
def test_missing_fields(self):
@@ -122,7 +122,7 @@ class TestSubmission:
assert 'mediagoblin/user_pages/user.html' in context
def check_media(self, request, find_data, count=None):
- media = MediaEntry.find(find_data)
+ media = MediaEntry.query.filter_by(**find_data)
if count is not None:
assert media.count() == count
if count == 0:
@@ -219,7 +219,7 @@ class TestSubmission:
media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
assert media.media_type == u'mediagoblin.media_types.image'
- assert isinstance(media.media_manager, img_MEDIA_MANAGER)
+ assert isinstance(media.media_manager, ImageMediaManager)
assert media.media_manager.entry == media
@@ -240,8 +240,8 @@ class TestSubmission:
request = context['request']
- media = request.db.MediaEntry.find_one({
- u'title': u'UNIQUE_TITLE_PLS_DONT_CREATE_OTHER_MEDIA_WITH_THIS_TITLE'})
+ media = request.db.MediaEntry.query.filter_by(
+ title=u'UNIQUE_TITLE_PLS_DONT_CREATE_OTHER_MEDIA_WITH_THIS_TITLE').first()
assert media.media_type == 'mediagoblin.media_types.image'
@@ -252,7 +252,7 @@ class TestSubmission:
response, context = self.do_post({'title': title}, do_follow=True,
**self.upload_data(filename))
self.check_url(response, '/u/{0}/'.format(self.test_user.username))
- entry = mg_globals.database.MediaEntry.find_one({'title': title})
+ entry = mg_globals.database.MediaEntry.query.filter_by(title=title).first()
assert entry.state == 'failed'
assert entry.fail_error == u'mediagoblin.processing:BadMediaFail'
diff --git a/mediagoblin/tests/testplugins/modify_context/__init__.py b/mediagoblin/tests/testplugins/modify_context/__init__.py
new file mode 100644
index 00000000..164e66c1
--- /dev/null
+++ b/mediagoblin/tests/testplugins/modify_context/__init__.py
@@ -0,0 +1,55 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.tools import pluginapi
+import pkg_resources
+
+
+def append_to_specific_context(context):
+ context['specific_page_append'] = 'in yer specificpage'
+ return context
+
+def append_to_global_context(context):
+ context['global_append'] = 'globally appended!'
+ return context
+
+def double_doubleme(context):
+ if 'doubleme' in context:
+ context['doubleme'] = context['doubleme'] * 2
+ return context
+
+
+def setup_plugin():
+ routes = [
+ ('modify_context.specific_page',
+ '/modify_context/specific/',
+ 'mediagoblin.tests.testplugins.modify_context.views:specific'),
+ ('modify_context.general_page',
+ '/modify_context/',
+ 'mediagoblin.tests.testplugins.modify_context.views:general')]
+
+ pluginapi.register_routes(routes)
+ pluginapi.register_template_path(
+ pkg_resources.resource_filename(
+ 'mediagoblin.tests.testplugins.modify_context', 'templates'))
+
+
+hooks = {
+ 'setup': setup_plugin,
+ ('modify_context.specific_page',
+ 'contextplugin/specific.html'): append_to_specific_context,
+ 'template_global_context': append_to_global_context,
+ 'template_context_prerender': double_doubleme}
diff --git a/mediagoblin/tests/testplugins/modify_context/templates/contextplugin/general.html b/mediagoblin/tests/testplugins/modify_context/templates/contextplugin/general.html
new file mode 100644
index 00000000..9cf96d3e
--- /dev/null
+++ b/mediagoblin/tests/testplugins/modify_context/templates/contextplugin/general.html
@@ -0,0 +1,5 @@
+General page!
+
+global thing: {{ global_append }}
+lol: {{ lol }}
+doubleme: {{ doubleme }}
diff --git a/mediagoblin/tests/testplugins/modify_context/templates/contextplugin/specific.html b/mediagoblin/tests/testplugins/modify_context/templates/contextplugin/specific.html
new file mode 100644
index 00000000..5b1b4c4a
--- /dev/null
+++ b/mediagoblin/tests/testplugins/modify_context/templates/contextplugin/specific.html
@@ -0,0 +1,6 @@
+Specific page!
+
+specific thing: {{ specific_page_append }}
+global thing: {{ global_append }}
+something: {{ something }}
+doubleme: {{ doubleme }}
diff --git a/mediagoblin/tests/testplugins/modify_context/views.py b/mediagoblin/tests/testplugins/modify_context/views.py
new file mode 100644
index 00000000..701ec6f9
--- /dev/null
+++ b/mediagoblin/tests/testplugins/modify_context/views.py
@@ -0,0 +1,33 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.tools.response import render_to_response
+
+
+def specific(request):
+ return render_to_response(
+ request,
+ 'contextplugin/specific.html',
+ {"something": "orother",
+ "doubleme": "happy"})
+
+
+def general(request):
+ return render_to_response(
+ request,
+ 'contextplugin/general.html',
+ {"lol": "cats",
+ "doubleme": "joy"})
diff --git a/mediagoblin/tests/testplugins/staticstuff/__init__.py b/mediagoblin/tests/testplugins/staticstuff/__init__.py
new file mode 100644
index 00000000..a2591646
--- /dev/null
+++ b/mediagoblin/tests/testplugins/staticstuff/__init__.py
@@ -0,0 +1,36 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.tools.staticdirect import PluginStatic
+from mediagoblin.tools import pluginapi
+from pkg_resources import resource_filename
+
+def setup_plugin():
+ routes = [
+ ('staticstuff.static_demo',
+ '/staticstuff/',
+ 'mediagoblin.tests.testplugins.staticstuff.views:static_demo')]
+
+ pluginapi.register_routes(routes)
+
+
+hooks = {
+ 'setup': setup_plugin,
+ 'static_setup': lambda: PluginStatic(
+ 'staticstuff',
+ resource_filename(
+ 'mediagoblin.tests.testplugins.staticstuff',
+ 'static'))}
diff --git a/mediagoblin/tests/testplugins/staticstuff/static/css/bunnify.css b/mediagoblin/tests/testplugins/staticstuff/static/css/bunnify.css
new file mode 100644
index 00000000..1294ab8a
--- /dev/null
+++ b/mediagoblin/tests/testplugins/staticstuff/static/css/bunnify.css
@@ -0,0 +1,4 @@
+body {
+ background-color: #5edcf1;
+ color: #eb8add;
+} \ No newline at end of file
diff --git a/mediagoblin/tests/testplugins/staticstuff/views.py b/mediagoblin/tests/testplugins/staticstuff/views.py
new file mode 100644
index 00000000..34a5e8cb
--- /dev/null
+++ b/mediagoblin/tests/testplugins/staticstuff/views.py
@@ -0,0 +1,28 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import json
+
+from werkzeug import Response
+
+
+def static_demo(request):
+ return Response(json.dumps({
+ # this does not exist, but we'll pretend it does ;)
+ 'mgoblin_bunny_pic': request.staticdirect(
+ 'images/bunny_pic.png'),
+ 'plugin_bunny_css': request.staticdirect(
+ 'css/bunnify.css', 'staticstuff')}))
diff --git a/mediagoblin/tests/tools.py b/mediagoblin/tests/tools.py
index 794ed940..98361adc 100644
--- a/mediagoblin/tests/tools.py
+++ b/mediagoblin/tests/tools.py
@@ -15,23 +15,22 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-import sys
import os
import pkg_resources
import shutil
-from functools import wraps
from paste.deploy import loadapp
from webtest import TestApp
from mediagoblin import mg_globals
-from mediagoblin.db.models import User, MediaEntry, Collection
+from mediagoblin.db.models import User, MediaEntry, Collection, MediaComment, \
+ CommentSubscription, CommentNotification
from mediagoblin.tools import testing
from mediagoblin.init.config import read_mediagoblin_config
from mediagoblin.db.base import Session
from mediagoblin.meddleware import BaseMeddleware
-from mediagoblin.auth.lib import bcrypt_gen_password_hash
+from mediagoblin.auth import gen_password_hash
from mediagoblin.gmg_commands.dbupdate import run_dbupdate
@@ -40,8 +39,6 @@ TEST_SERVER_CONFIG = pkg_resources.resource_filename(
'mediagoblin.tests', 'test_paste.ini')
TEST_APP_CONFIG = pkg_resources.resource_filename(
'mediagoblin.tests', 'test_mgoblin_app.ini')
-TEST_USER_DEV = pkg_resources.resource_filename(
- 'mediagoblin.tests', 'test_user_dev')
USER_DEV_DIRECTORIES_TO_SETUP = ['media/public', 'media/queue']
@@ -103,7 +100,7 @@ def get_app(request, paste_config=None, mgoblin_config=None):
# This is the directory we're copying the paste/mgoblin config stuff into
run_dir = request.config._tmpdirhandler.mktemp(
'mgoblin_app', numbered=True)
- user_dev_dir = run_dir.mkdir('test_user_dev').strpath
+ user_dev_dir = run_dir.mkdir('user_dev').strpath
new_paste_config = run_dir.join('paste.ini').strpath
new_mgoblin_config = run_dir.join('mediagoblin.ini').strpath
@@ -167,13 +164,13 @@ def assert_db_meets_expected(db, expected):
for collection_name, collection_data in expected.iteritems():
collection = db[collection_name]
for expected_document in collection_data:
- document = collection.find_one({'id': expected_document['id']})
+ document = collection.query.filter_by(id=expected_document['id']).first()
assert document is not None # make sure it exists
assert document == expected_document # make sure it matches
def fixture_add_user(username=u'chris', password=u'toast',
- active_user=True):
+ active_user=True, wants_comment_notification=True):
# Reuse existing user or create a new one
test_user = User.query.filter_by(username=username).first()
if test_user is None:
@@ -181,11 +178,13 @@ def fixture_add_user(username=u'chris', password=u'toast',
test_user.username = username
test_user.email = username + u'@example.com'
if password is not None:
- test_user.pw_hash = bcrypt_gen_password_hash(password)
+ test_user.pw_hash = gen_password_hash(password)
if active_user:
test_user.email_verified = True
test_user.status = u'active'
+ test_user.wants_comment_notification = wants_comment_notification
+
test_user.save()
# Reload
@@ -197,19 +196,79 @@ def fixture_add_user(username=u'chris', password=u'toast',
return test_user
+def fixture_comment_subscription(entry, notify=True, send_email=None):
+ if send_email is None:
+ uploader = User.query.filter_by(id=entry.uploader).first()
+ send_email = uploader.wants_comment_notification
+
+ cs = CommentSubscription(
+ media_entry_id=entry.id,
+ user_id=entry.uploader,
+ notify=notify,
+ send_email=send_email)
+
+ cs.save()
+
+ cs = CommentSubscription.query.filter_by(id=cs.id).first()
+
+ Session.expunge(cs)
+
+ return cs
+
+
+def fixture_add_comment_notification(entry_id, subject_id, user_id,
+ seen=False):
+ cn = CommentNotification(user_id=user_id,
+ seen=seen,
+ subject_id=subject_id)
+ cn.save()
+
+ cn = CommentNotification.query.filter_by(id=cn.id).first()
+
+ Session.expunge(cn)
+
+ return cn
+
+
def fixture_media_entry(title=u"Some title", slug=None,
- uploader=None, save=True, gen_slug=True):
+ uploader=None, save=True, gen_slug=True,
+ state=u'unprocessed', fake_upload=True,
+ expunge=True):
+ """
+ Add a media entry for testing purposes.
+
+ Caution: if you're adding multiple entries with fake_upload=True,
+ make sure you save between them... otherwise you'll hit an
+ IntegrityError from multiple newly-added-MediaEntries adding
+ FileKeynames at once. :)
+ """
+ if uploader is None:
+ uploader = fixture_add_user().id
+
entry = MediaEntry()
entry.title = title
entry.slug = slug
- entry.uploader = uploader or fixture_add_user().id
+ entry.uploader = uploader
entry.media_type = u'image'
+ entry.state = state
+
+ if fake_upload:
+ entry.media_files = {'thumb': ['a', 'b', 'c.jpg'],
+ 'medium': ['d', 'e', 'f.png'],
+ 'original': ['g', 'h', 'i.png']}
+ entry.media_type = u'mediagoblin.media_types.image'
if gen_slug:
entry.generate_slug()
+
if save:
entry.save()
+ if expunge:
+ entry = MediaEntry.query.filter_by(id=entry.id).first()
+
+ Session.expunge(entry)
+
return entry
@@ -232,3 +291,26 @@ def fixture_add_collection(name=u"My first Collection", user=None):
Session.expunge(coll)
return coll
+
+def fixture_add_comment(author=None, media_entry=None, comment=None):
+ if author is None:
+ author = fixture_add_user().id
+
+ if media_entry is None:
+ media_entry = fixture_media_entry().id
+
+ if comment is None:
+ comment = \
+ 'Auto-generated test comment by user #{0} on media #{0}'.format(
+ author, media_entry)
+
+ comment = MediaComment(author=author,
+ media_entry=media_entry,
+ content=comment)
+
+ comment.save()
+
+ Session.expunge(comment)
+
+ return comment
+