diff options
Diffstat (limited to 'mediagoblin/tests')
55 files changed, 2603 insertions, 632 deletions
diff --git a/mediagoblin/tests/__init__.py b/mediagoblin/tests/__init__.py index 5a3235c6..cf200791 100644 --- a/mediagoblin/tests/__init__.py +++ b/mediagoblin/tests/__init__.py @@ -14,23 +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, suicide_if_bad_celery_environ) - def setup_package(): - suicide_if_bad_celery_environ() 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_plugin_specs.ini b/mediagoblin/tests/appconfig_plugin_specs.ini new file mode 100644 index 00000000..5511cd97 --- /dev/null +++ b/mediagoblin/tests/appconfig_plugin_specs.ini @@ -0,0 +1,21 @@ +[mediagoblin] +direct_remote_path = /mgoblin_static/ +email_sender_address = "notice@mediagoblin.example.org" + +## Uncomment and change to your DB's appropiate setting. +## Default is a local sqlite db "mediagoblin.db". +# sql_engine = postgresql:///gmg + +# set to false to enable sending notices +email_debug_mode = true + +# Set to false to disable registrations +allow_registration = true + +[plugins] +[[mediagoblin.tests.testplugins.pluginspec]] +some_string = "not blork" +some_int = "not an int" + +# this one shouldn't have its own config +[[mediagoblin.tests.testplugins.callables1]] 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 new file mode 100644 index 00000000..dbb0aa0a --- /dev/null +++ b/mediagoblin/tests/conftest.py @@ -0,0 +1,41 @@ +# 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): + """ + py.test fixture to pass sandboxed mediagoblin applications into tests that + want them. + + You could make a local version of this method for your own tests + to override the paste and config files being used by passing them + 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 new file mode 100644 index 00000000..e561c074 --- /dev/null +++ b/mediagoblin/tests/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +usefixtures = tmpdir pt_fixture_enable_testing diff --git a/mediagoblin/tests/resources.py b/mediagoblin/tests/resources.py new file mode 100644 index 00000000..f7b3037d --- /dev/null +++ b/mediagoblin/tests/resources.py @@ -0,0 +1,41 @@ +# 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/>. + + +from pkg_resources import resource_filename + + +def resource(filename): + return resource_filename('mediagoblin.tests', 'test_submission/' + filename) + + +GOOD_JPG = resource('good.jpg') +GOOD_PNG = resource('good.png') +EVIL_FILE = resource('evil') +EVIL_JPG = resource('evil.jpg') +EVIL_PNG = resource('evil.png') +BIG_BLUE = resource('bigblue.png') +GOOD_PDF = resource('good.pdf') + + +def resource_exif(f): + return resource_filename('mediagoblin.tests', 'test_exif/' + f) + + +GOOD_JPG = resource_exif('good.jpg') +EMPTY_JPG = resource_exif('empty.jpg') +BAD_JPG = resource_exif('bad.jpg') +GPS_JPG = resource_exif('has-gps.jpg') diff --git a/mediagoblin/tests/test_api.py b/mediagoblin/tests/test_api.py index 82b1c1b4..89cf1026 100644 --- a/mediagoblin/tests/test_api.py +++ b/mediagoblin/tests/test_api.py @@ -18,40 +18,27 @@ import logging import base64 -from pkg_resources import resource_filename +import pytest from mediagoblin import mg_globals from mediagoblin.tools import template, pluginapi -from mediagoblin.tests.tools import get_app, fixture_add_user +from mediagoblin.tests.tools import fixture_add_user +from .resources import GOOD_JPG, GOOD_PNG, EVIL_FILE, EVIL_JPG, EVIL_PNG, \ + BIG_BLUE _log = logging.getLogger(__name__) -def resource(filename): - ''' - Borrowed from the submission tests - ''' - return resource_filename('mediagoblin.tests', 'test_submission/' + filename) - - -GOOD_JPG = resource('good.jpg') -GOOD_PNG = resource('good.png') -EVIL_FILE = resource('evil') -EVIL_JPG = resource('evil.jpg') -EVIL_PNG = resource('evil.png') -BIG_BLUE = resource('bigblue.png') - class TestAPI(object): - def setUp(self): - self.app = get_app(dump_old_app=False) + def setup(self): self.db = mg_globals.database self.user_password = u'4cc355_70k3N' self.user = fixture_add_user(u'joapi', self.user_password) - def login(self): - self.app.post( + def login(self, test_app): + test_app.post( '/auth/login/', { 'username': self.user.username, 'password': self.user_password}) @@ -65,14 +52,14 @@ class TestAPI(object): self.user.username, self.user_password])))} - def do_post(self, data, **kwargs): + def do_post(self, data, test_app, **kwargs): url = kwargs.pop('url', '/api/submit') do_follow = kwargs.pop('do_follow', False) if not 'headers' in kwargs.keys(): kwargs['headers'] = self.http_auth_headers() - response = self.app.post(url, data, **kwargs) + response = test_app.post(url, data, **kwargs) if do_follow: response.follow() @@ -82,21 +69,22 @@ class TestAPI(object): def upload_data(self, filename): return {'upload_files': [('file', filename)]} - def test_1_test_test_view(self): - self.login() + def test_1_test_test_view(self, test_app): + self.login(test_app) - response = self.app.get( + response = test_app.get( '/api/test', headers=self.http_auth_headers()) assert response.body == \ '{"username": "joapi", "email": "joapi@example.com"}' - def test_2_test_submission(self): - self.login() + def test_2_test_submission(self, test_app): + self.login(test_app) response = self.do_post( {'title': 'Great JPG!'}, + test_app, **self.upload_data(GOOD_JPG)) assert response.status_int == 200 diff --git a/mediagoblin/tests/test_auth.py b/mediagoblin/tests/test_auth.py index a59a319c..61503d32 100644 --- a/mediagoblin/tests/test_auth.py +++ b/mediagoblin/tests/test_auth.py @@ -13,59 +13,17 @@ # # 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 - -from nose.tools import assert_equal +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 setup_fresh_app, get_app, fixture_add_user +from mediagoblin.tests.tools import get_app, fixture_add_user from mediagoblin.tools import template, mail +from mediagoblin.auth import tools as auth_tools -######################## -# 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') - - -@setup_fresh_app def test_register_views(test_app): """ Massive test function that all our registration-related views all work. @@ -102,8 +60,8 @@ def test_register_views(test_app): context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html'] form = context['register_form'] - assert_equal (form.username.errors, [u'Field must be between 3 and 30 characters long.']) - assert_equal (form.password.errors, [u'Field must be between 5 and 1024 characters long.']) + assert form.username.errors == [u'Field must be between 3 and 30 characters long.'] + assert form.password.errors == [u'Field must be between 5 and 1024 characters long.'] ## bad form template.clear_test_template_context() @@ -114,11 +72,11 @@ def test_register_views(test_app): context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html'] form = context['register_form'] - assert_equal (form.username.errors, [u'This field does not take email addresses.']) - assert_equal (form.email.errors, [u'This field requires an email address.']) + assert form.username.errors == [u'This field does not take email addresses.'] + assert form.email.errors == [u'This field requires an email address.'] ## At this point there should be no users in the database ;) - assert_equal(User.query.count(), 0) + assert User.query.count() == 0 # Successful register # ------------------- @@ -131,14 +89,12 @@ def test_register_views(test_app): response.follow() ## Did we redirect to the proper page? Use the right template? - assert_equal( - urlparse.urlsplit(response.location)[2], - '/u/happygirl/') + assert urlparse.urlsplit(response.location)[2] == '/u/happygirl/' 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 @@ -161,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 @@ -191,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 @@ -224,9 +175,7 @@ def test_register_views(test_app): response.follow() ## Did we redirect to the proper page? Use the right template? - assert_equal( - urlparse.urlsplit(response.location)[2], - '/auth/login/') + assert urlparse.urlsplit(response.location)[2] == '/auth/login/' assert 'mediagoblin/auth/login.html' in template.TEMPLATE_TEST_CONTEXT ## Make sure link to change password is sent by email @@ -240,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_equal(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_equal(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() @@ -279,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() @@ -294,20 +224,18 @@ def test_register_views(test_app): # User should be redirected response.follow() - assert_equal( - urlparse.urlsplit(response.location)[2], - '/') + assert urlparse.urlsplit(response.location)[2] == '/' assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT -def test_authentication_views(): +def test_authentication_views(test_app): """ Test logging in and logging out """ - test_app = get_app(dump_old_app=False) # Make a new user test_user = fixture_add_user(active_user=False) + # Get login # --------- test_app.get('/auth/login/') @@ -320,7 +248,6 @@ def test_authentication_views(): 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 # ------------------------- @@ -338,9 +265,7 @@ def test_authentication_views(): 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 # ----------------------- @@ -372,9 +297,7 @@ def test_authentication_views(): # User should be redirected response.follow() - assert_equal( - urlparse.urlsplit(response.location)[2], - '/') + assert urlparse.urlsplit(response.location)[2] == '/' assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT # Make sure user is in the session @@ -389,9 +312,7 @@ def test_authentication_views(): # Should be redirected to index page response.follow() - assert_equal( - urlparse.urlsplit(response.location)[2], - '/') + assert urlparse.urlsplit(response.location)[2] == '/' assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT # Make sure the user is not in the session @@ -407,6 +328,48 @@ def test_authentication_views(): 'username': u'chris', 'password': 'toast', 'next' : '/u/chris/'}) - assert_equal( - urlparse.urlsplit(response.location)[2], - '/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_cache.py b/mediagoblin/tests/test_cache.py deleted file mode 100644 index 48fa1386..00000000 --- a/mediagoblin/tests/test_cache.py +++ /dev/null @@ -1,52 +0,0 @@ -# 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.tests.tools import setup_fresh_app -from mediagoblin import mg_globals - - -DATA_TO_CACHE = { - 'herp': 'derp', - 'lol': 'cats'} - - -def _get_some_data(key): - """ - Stuid function that makes use of some caching. - """ - some_data_cache = mg_globals.cache.get_cache('sum_data') - if some_data_cache.has_key(key): - return some_data_cache.get(key) - - value = DATA_TO_CACHE.get(key) - some_data_cache.put(key, value) - return value - - -@setup_fresh_app -def test_cache_working(test_app): - some_data_cache = mg_globals.cache.get_cache('sum_data') - assert not some_data_cache.has_key('herp') - assert _get_some_data('herp') == 'derp' - assert some_data_cache.get('herp') == 'derp' - # should get the same value again - assert _get_some_data('herp') == 'derp' - - # now we force-change it, but the function should use the cached - # version - some_data_cache.put('herp', 'pred') - assert _get_some_data('herp') == 'pred' 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_collections.py b/mediagoblin/tests/test_collections.py index b19f6362..87782f30 100644 --- a/mediagoblin/tests/test_collections.py +++ b/mediagoblin/tests/test_collections.py @@ -14,17 +14,12 @@ # 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.tests.tools import fixture_add_collection, fixture_add_user, \ - get_app +from mediagoblin.tests.tools import fixture_add_collection, fixture_add_user from mediagoblin.db.models import Collection, User -from mediagoblin.db.base import Session -from nose.tools import assert_equal -def test_user_deletes_collection(): +def test_user_deletes_collection(test_app): # Setup db. - get_app(dump_old_app=False) - user = fixture_add_user() coll = fixture_add_collection(user=user) # Reload into session: @@ -34,4 +29,4 @@ def test_user_deletes_collection(): user.delete() cnt2 = Collection.query.count() - assert_equal(cnt1, cnt2 + 1) + assert cnt1 == cnt2 + 1 diff --git a/mediagoblin/tests/test_csrf_middleware.py b/mediagoblin/tests/test_csrf_middleware.py index e720264c..a272caf6 100644 --- a/mediagoblin/tests/test_csrf_middleware.py +++ b/mediagoblin/tests/test_csrf_middleware.py @@ -14,12 +14,10 @@ # 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.tests.tools import get_app from mediagoblin import mg_globals -def test_csrf_cookie_set(): - test_app = get_app(dump_old_app=False) +def test_csrf_cookie_set(test_app): cookie_name = mg_globals.app_config['csrf_cookie_name'] # get login page @@ -33,11 +31,14 @@ def test_csrf_cookie_set(): assert response.headers.get('Vary', False) == 'Cookie' -def test_csrf_token_must_match(): - # We need a fresh app for this test on webtest < 1.3.6. - # We do not understand why, but it fixes the tests. - # If we require webtest >= 1.3.6, we can switch to a non fresh app here. - test_app = get_app(dump_old_app=True) +# We need a fresh app for this test on webtest < 1.3.6. +# We do not understand why, but it fixes the tests. +# If we require webtest >= 1.3.6, we can switch to a non fresh app here. +# +# ... this comment might be irrelevant post-pytest-fixtures, but I'm not +# removing it yet in case we move to module-level tests :) +# -- cwebber +def test_csrf_token_must_match(test_app): # construct a request with no cookie or form token assert test_app.post('/auth/login/', @@ -67,8 +68,7 @@ def test_csrf_token_must_match(): extra_environ={'gmg.verify_csrf': True}).\ status_int == 200 -def test_csrf_exempt(): - test_app = get_app(dump_old_app=False) +def test_csrf_exempt(test_app): # monkey with the views to decorate a known endpoint import mediagoblin.auth.views from mediagoblin.meddleware.csrf import csrf_exempt diff --git a/mediagoblin/tests/test_edit.py b/mediagoblin/tests/test_edit.py index 7db6eaea..d70d0478 100644 --- a/mediagoblin/tests/test_edit.py +++ b/mediagoblin/tests/test_edit.py @@ -14,35 +14,36 @@ # 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 nose.tools import assert_equal +import urlparse from mediagoblin import mg_globals from mediagoblin.db.models import User -from mediagoblin.tests.tools import get_app, fixture_add_user -from mediagoblin.tools import template -from mediagoblin.auth.lib import bcrypt_check_password +from mediagoblin.tests.tools import fixture_add_user +from mediagoblin import auth +from mediagoblin.tools import template, mail + class TestUserEdit(object): - def setUp(self): - self.app = get_app(dump_old_app=False) + def setup(self): # set up new user self.user_password = u'toast' self.user = fixture_add_user(password = self.user_password) - self.login() - def login(self): - self.app.post( + def login(self, test_app): + test_app.post( '/auth/login/', { 'username': self.user.username, 'password': self.user_password}) - def test_user_deletion(self): + def test_user_deletion(self, test_app): """Delete user via web interface""" + self.login(test_app) + # Make sure user exists assert User.query.filter_by(username=u'chris').first() - res = self.app.post('/edit/account/delete/', {'confirmed': 'y'}) + res = test_app.post('/edit/account/delete/', {'confirmed': 'y'}) # Make sure user has been deleted assert User.query.filter_by(username=u'chris').first() == None @@ -52,84 +53,156 @@ class TestUserEdit(object): #Restore user at end of test self.user = fixture_add_user(password = self.user_password) - self.login() + self.login(test_app) - def test_change_password(self): + def test_change_password(self, test_app): """Test changing password correctly and incorrectly""" + self.login(test_app) + # test that the password can be changed - # template.clear_test_template_context() - res = self.app.post( - '/edit/account/', { + template.clear_test_template_context() + res = test_app.post( + '/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_equal(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() - self.app.post( - '/edit/account/', { + # old_password is wrong + template.clear_test_template_context() + test_app.post( + '/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): + def test_change_bio_url(self, test_app): """Test changing bio and URL""" + self.login(test_app) + # Test if legacy profile editing URL redirects correctly - res = self.app.post( + res = test_app.post( '/edit/profile/', { 'bio': u'I love toast!', 'url': u'http://dustycloud.org/'}, expect_errors=True) # Should redirect to /u/chris/edit/ - assert_equal (res.status_int, 302) + assert res.status_int == 302 assert res.headers['Location'].endswith("/u/chris/edit/") - res = self.app.post( + res = test_app.post( '/u/chris/edit/', { 'bio': u'I love toast!', 'url': u'http://dustycloud.org/'}) test_user = User.query.filter_by(username=u'chris').first() - assert_equal(test_user.bio, u'I love toast!') - assert_equal(test_user.url, u'http://dustycloud.org/') + assert test_user.bio == u'I love toast!' + assert test_user.url == u'http://dustycloud.org/' # change a different user than the logged in (should fail with 403) fixture_add_user(username=u"foo") - res = self.app.post( + res = test_app.post( '/u/foo/edit/', { 'bio': u'I love toast!', 'url': u'http://dustycloud.org/'}, expect_errors=True) - assert_equal(res.status_int, 403) + assert res.status_int == 403 # test changing the bio and the URL inproperly too_long_bio = 150 * 'T' + 150 * 'o' + 150 * 'a' + 150 * 's' + 150* 't' - self.app.post( + test_app.post( '/u/chris/edit/', { # more than 500 characters 'bio': too_long_bio, 'url': 'this-is-no-url'}) # Check form errors - context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/edit/edit_profile.html'] + context = template.TEMPLATE_TEST_CONTEXT[ + 'mediagoblin/edit/edit_profile.html'] form = context['form'] - assert_equal(form.bio.errors, [u'Field must be between 0 and 500 characters long.']) - assert_equal(form.url.errors, [u'This address contains errors']) + assert form.bio.errors == [ + u'Field must be between 0 and 500 characters long.'] + 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 100d17f0..c07e24ae 100644 --- a/mediagoblin/tests/test_exif.py +++ b/mediagoblin/tests/test_exif.py @@ -15,39 +15,20 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import os -import pkg_resources -import Image +try: + from PIL import Image +except ImportError: + import Image from mediagoblin.tools.exif import exif_fix_image_orientation, \ extract_exif, clean_exif, get_gps_data, get_useful +from .resources import GOOD_JPG, EMPTY_JPG, BAD_JPG, GPS_JPG def assert_in(a, b): assert a in b, "%r not in %r" % (a, b) -GOOD_JPG = pkg_resources.resource_filename( - 'mediagoblin.tests', - os.path.join( - 'test_exif', - 'good.jpg')) -EMPTY_JPG = pkg_resources.resource_filename( - 'mediagoblin.tests', - os.path.join( - 'test_exif', - 'empty.jpg')) -BAD_JPG = pkg_resources.resource_filename( - 'mediagoblin.tests', - os.path.join( - 'test_exif', - 'bad.jpg')) -GPS_JPG = pkg_resources.resource_filename( - 'mediagoblin.tests', - os.path.join( - 'test_exif', - 'has-gps.jpg')) - - def test_exif_extraction(): ''' Test EXIF extraction from a good image @@ -67,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_globals.py b/mediagoblin/tests/test_globals.py index 303f89e2..fe3088f8 100644 --- a/mediagoblin/tests/test_globals.py +++ b/mediagoblin/tests/test_globals.py @@ -14,16 +14,16 @@ # 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 nose.tools import assert_raises +import pytest from mediagoblin import mg_globals class TestGlobals(object): - def setUp(self): + def setup(self): self.old_database = mg_globals.database - def tearDown(self): + def teardown(self): mg_globals.database = self.old_database def test_setup_globals(self): @@ -36,7 +36,7 @@ class TestGlobals(object): assert mg_globals.public_store == 'my favorite public_store!' assert mg_globals.queue_store == 'my favorite queue_store!' - assert_raises( + pytest.raises( AssertionError, mg_globals.setup_globals, - no_such_global_foo = "Dummy") + no_such_global_foo="Dummy") diff --git a/mediagoblin/tests/test_http_callback.py b/mediagoblin/tests/test_http_callback.py index 8bee7045..a0511af7 100644 --- a/mediagoblin/tests/test_http_callback.py +++ b/mediagoblin/tests/test_http_callback.py @@ -16,18 +16,21 @@ import json +import pytest from urlparse import urlparse, parse_qs from mediagoblin import mg_globals from mediagoblin.tools import processing -from mediagoblin.tests.tools import get_app, fixture_add_user +from mediagoblin.tests.tools import fixture_add_user from mediagoblin.tests.test_submission import GOOD_PNG from mediagoblin.tests import test_oauth as oauth class TestHTTPCallback(object): - def setUp(self): - self.app = get_app(dump_old_app=False) + @pytest.fixture(autouse=True) + def setup(self, test_app): + self.test_app = test_app + self.db = mg_globals.database self.user_password = u'secret' @@ -36,12 +39,12 @@ class TestHTTPCallback(object): self.login() def login(self): - self.app.post('/auth/login/', { + self.test_app.post('/auth/login/', { 'username': self.user.username, 'password': self.user_password}) def get_access_token(self, client_id, client_secret, code): - response = self.app.get('/oauth/access_token', { + response = self.test_app.get('/oauth/access_token', { 'code': code, 'client_id': client_id, 'client_secret': client_secret}) @@ -52,9 +55,8 @@ class TestHTTPCallback(object): def test_callback(self): ''' Test processing HTTP callback ''' - self.oauth = oauth.TestOAuth() - self.oauth.setUp() + self.oauth.setup(self.test_app) redirect, client_id = self.oauth.test_4_authorize_confidential_client() @@ -69,7 +71,7 @@ class TestHTTPCallback(object): callback_url = 'https://foo.example?secrettestmediagoblinparam' - res = self.app.post('/api/submit?client_id={0}&access_token={1}\ + self.test_app.post('/api/submit?client_id={0}&access_token={1}\ &client_secret={2}'.format( client_id, access_token, diff --git a/mediagoblin/tests/test_messages.py b/mediagoblin/tests/test_messages.py index 4c0f3e2e..22f9e800 100644 --- a/mediagoblin/tests/test_messages.py +++ b/mediagoblin/tests/test_messages.py @@ -14,19 +14,16 @@ # 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.messages import fetch_messages, add_message -from mediagoblin.tests.tools import get_app +from mediagoblin import messages from mediagoblin.tools import template - -def test_messages(): +def test_messages(test_app): """ Added messages should show up in the request.session, fetched messages should be the same as the added ones, and fetching should clear the message list. """ - test_app = get_app(dump_old_app=False) # Aquire a request object test_app.get('/') context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html'] @@ -35,11 +32,19 @@ def test_messages(): # The message queue should be empty assert request.session.get('messages', []) == [] + # First of all, we should clear the messages queue + messages.clear_add_message() # Adding a message should modify the session accordingly - add_message(request, 'herp_derp', 'First!') + messages.add_message(request, 'herp_derp', 'First!') test_msg_queue = [{'text': 'First!', 'level': 'herp_derp'}] - assert request.session['messages'] == test_msg_queue + + # Alternative tests to the following, test divided in two steps: + # assert request.session['messages'] == test_msg_queue + # 1. Tests if add_message worked + assert messages.ADD_MESSAGE_TEST[-1] == test_msg_queue + # 2. Tests if add_message updated session information + assert messages.ADD_MESSAGE_TEST[-1] == request.session['messages'] # fetch_messages should return and empty the queue - assert fetch_messages(request) == test_msg_queue + assert messages.fetch_messages(request) == test_msg_queue assert request.session.get('messages') == [] diff --git a/mediagoblin/tests/test_mgoblin_app.ini b/mediagoblin/tests/test_mgoblin_app.ini index 42d3785a..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,28 +13,24 @@ tags_max_length = 50 # So we can start to test attachments: allow_attachments = 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/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 - -[beaker.cache] -data_dir = %(here)s/test_user_dev/beaker/cache/data -lock_dir = %(here)s/test_user_dev/beaker/cache/lock +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 776affc6..43ad0b6d 100644 --- a/mediagoblin/tests/test_misc.py +++ b/mediagoblin/tests/test_misc.py @@ -14,26 +14,24 @@ # 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 nose.tools import assert_equal - from mediagoblin.db.base import Session from mediagoblin.db.models import User, MediaEntry, MediaComment -from mediagoblin.tests.tools import get_app, \ - fixture_add_user, fixture_media_entry +from mediagoblin.tests.tools import fixture_add_user, fixture_media_entry -def test_404_for_non_existent(): - test_app = get_app(dump_old_app=False) +def test_404_for_non_existent(test_app): res = test_app.get('/does-not-exist/', expect_errors=True) - assert_equal(res.status_int, 404) + assert res.status_int == 404 -def test_user_deletes_other_comments(): +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() @@ -60,11 +58,11 @@ def test_user_deletes_other_comments(): cmt_cnt2 = MediaComment.query.count() # One user deleted - assert_equal(usr_cnt2, usr_cnt1 - 1) + assert usr_cnt2 == usr_cnt1 - 1 # One media gone - assert_equal(med_cnt2, med_cnt1 - 1) + assert med_cnt2 == med_cnt1 - 1 # Three of four comments gone. - assert_equal(cmt_cnt2, cmt_cnt1 - 3) + assert cmt_cnt2 == cmt_cnt1 - 3 User.query.get(user_b.id).delete() @@ -73,17 +71,17 @@ def test_user_deletes_other_comments(): cmt_cnt2 = MediaComment.query.count() # All users gone - assert_equal(usr_cnt2, usr_cnt1 - 2) + assert usr_cnt2 == usr_cnt1 - 2 # All media gone - assert_equal(med_cnt2, med_cnt1 - 2) + assert med_cnt2 == med_cnt1 - 2 # All comments gone - assert_equal(cmt_cnt2, cmt_cnt1 - 4) + assert cmt_cnt2 == cmt_cnt1 - 4 -def test_media_deletes_broken_attachment(): +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_modelmethods.py b/mediagoblin/tests/test_modelmethods.py index 7719bd97..427aa47c 100644 --- a/mediagoblin/tests/test_modelmethods.py +++ b/mediagoblin/tests/test_modelmethods.py @@ -17,13 +17,10 @@ # Maybe not every model needs a test, but some models have special # methods, and so it makes sense to test them here. -from nose.tools import assert_equal - from mediagoblin.db.base import Session from mediagoblin.db.models import MediaEntry -from mediagoblin.tests.tools import get_app, \ - fixture_add_user +from mediagoblin.tests.tools import fixture_add_user import mock @@ -35,8 +32,7 @@ UUID_MOCK = mock.Mock(return_value=FakeUUID()) class TestMediaEntrySlugs(object): - def setUp(self): - self.test_app = get_app(dump_old_app=True) + def _setup(self): self.chris_user = fixture_add_user(u'chris') self.emily_user = fixture_add_user(u'emily') self.existing_entry = self._insert_media_entry_fixture( @@ -57,56 +53,78 @@ class TestMediaEntrySlugs(object): return entry - def test_unique_slug_from_title(self): + def test_unique_slug_from_title(self, test_app): + self._setup() + entry = self._insert_media_entry_fixture(u"Totally unique slug!", save=False) entry.generate_slug() assert entry.slug == u'totally-unique-slug' - def test_old_good_unique_slug(self): + def test_old_good_unique_slug(self, test_app): + self._setup() + entry = self._insert_media_entry_fixture( u"A title here", u"a-different-slug-there", save=False) entry.generate_slug() assert entry.slug == u"a-different-slug-there" - def test_old_weird_slug(self): + def test_old_weird_slug(self, test_app): + self._setup() + entry = self._insert_media_entry_fixture( slug=u"wowee!!!!!", save=False) entry.generate_slug() assert entry.slug == u"wowee" - def test_existing_slug_use_id(self): - entry = self._insert_media_entry_fixture( - u"Beware, I exist!!", this_id=9000, save=False) - entry.generate_slug() - assert entry.slug == u"beware-i-exist-9000" - - @mock.patch('uuid.uuid4', UUID_MOCK) - def test_existing_slug_cant_use_id(self): - # This one grabs the nine thousand slug - self._insert_media_entry_fixture( - slug=u"beware-i-exist-9000") + def test_existing_slug_use_id(self, test_app): + self._setup() entry = self._insert_media_entry_fixture( u"Beware, I exist!!", this_id=9000, save=False) entry.generate_slug() - assert entry.slug == u"beware-i-exist-test" - - @mock.patch('uuid.uuid4', UUID_MOCK) - def test_existing_slug_cant_use_id_extra_junk(self): - # This one grabs the nine thousand slug - self._insert_media_entry_fixture( - slug=u"beware-i-exist-9000") - - # This one grabs makes sure the annoyance doesn't stop - self._insert_media_entry_fixture( - slug=u"beware-i-exist-test") - - entry = self._insert_media_entry_fixture( - u"Beware, I exist!!", this_id=9000, save=False) - entry.generate_slug() - assert entry.slug == u"beware-i-exist-testtest" + assert entry.slug == u"beware-i-exist-9000" - def test_garbage_slug(self): + def test_existing_slug_cant_use_id(self, test_app): + self._setup() + + # Getting tired of dealing with test_app and this mock.patch + # thing conflicting, getting lazy. + @mock.patch('uuid.uuid4', UUID_MOCK) + def _real_test(): + # This one grabs the nine thousand slug + self._insert_media_entry_fixture( + slug=u"beware-i-exist-9000") + + entry = self._insert_media_entry_fixture( + u"Beware, I exist!!", this_id=9000, save=False) + entry.generate_slug() + assert entry.slug == u"beware-i-exist-test" + + _real_test() + + def test_existing_slug_cant_use_id_extra_junk(self, test_app): + self._setup() + + # Getting tired of dealing with test_app and this mock.patch + # thing conflicting, getting lazy. + @mock.patch('uuid.uuid4', UUID_MOCK) + def _real_test(): + # This one grabs the nine thousand slug + self._insert_media_entry_fixture( + slug=u"beware-i-exist-9000") + + # This one grabs makes sure the annoyance doesn't stop + self._insert_media_entry_fixture( + slug=u"beware-i-exist-test") + + entry = self._insert_media_entry_fixture( + u"Beware, I exist!!", this_id=9000, save=False) + entry.generate_slug() + assert entry.slug == u"beware-i-exist-testtest" + + _real_test() + + def test_garbage_slug(self, test_app): """ Titles that sound totally like Q*Bert shouldn't have slugs at all. We'll just reference them by id. @@ -126,13 +144,15 @@ class TestMediaEntrySlugs(object): | |#| |#| |#| |#| \|/ \|/ \|/ \|/ """ + self._setup() + qbert_entry = self._insert_media_entry_fixture( u"@!#?@!", save=False) qbert_entry.generate_slug() assert qbert_entry.slug is None -def test_media_data_init(): +def test_media_data_init(test_app): Session.rollback() Session.remove() media = MediaEntry() @@ -144,4 +164,4 @@ def test_media_data_init(): for obj in Session(): obj_in_session += 1 print repr(obj) - assert_equal(obj_in_session, 0) + assert obj_in_session == 0 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_oauth.py b/mediagoblin/tests/test_oauth.py index 94ba5dab..ea3bd798 100644 --- a/mediagoblin/tests/test_oauth.py +++ b/mediagoblin/tests/test_oauth.py @@ -17,19 +17,22 @@ import json import logging +import pytest from urlparse import parse_qs, urlparse from mediagoblin import mg_globals from mediagoblin.tools import template, pluginapi -from mediagoblin.tests.tools import get_app, fixture_add_user +from mediagoblin.tests.tools import fixture_add_user _log = logging.getLogger(__name__) class TestOAuth(object): - def setUp(self): - self.app = get_app() + @pytest.fixture(autouse=True) + def setup(self, test_app): + self.test_app = test_app + self.db = mg_globals.database self.pman = pluginapi.PluginManager() @@ -40,14 +43,14 @@ class TestOAuth(object): self.login() def login(self): - self.app.post( - '/auth/login/', { - 'username': self.user.username, - 'password': self.user_password}) + self.test_app.post( + '/auth/login/', { + 'username': self.user.username, + 'password': self.user_password}) def register_client(self, name, client_type, description=None, - redirect_uri=''): - return self.app.post( + redirect_uri=''): + return self.test_app.post( '/oauth/client/register', { 'name': name, 'description': description, @@ -59,8 +62,8 @@ class TestOAuth(object): def test_1_public_client_registration_without_redirect_uri(self): ''' Test 'public' OAuth client registration without any redirect uri ''' - response = self.register_client(u'OMGOMGOMG', 'public', - 'OMGOMG Apache License v2') + response = self.register_client( + u'OMGOMGOMG', 'public', 'OMGOMG Apache License v2') ctx = self.get_context('oauth/client/register.html') @@ -70,26 +73,30 @@ class TestOAuth(object): assert response.status_int == 200 # Should display an error - assert ctx['form'].redirect_uri.errors + assert len(ctx['form'].redirect_uri.errors) # Should not pass through assert not client def test_2_successful_public_client_registration(self): ''' Successfully register a public client ''' - self.login() - self.register_client(u'OMGOMG', 'public', 'OMG!', - 'http://foo.example') + uri = 'http://foo.example' + self.register_client( + u'OMGOMG', 'public', 'OMG!', uri) client = self.db.OAuthClient.query.filter( self.db.OAuthClient.name == u'OMGOMG').first() + # redirect_uri should be set + assert client.redirect_uri == uri + # Client should have been registered assert client def test_3_successful_confidential_client_reg(self): ''' Register a confidential OAuth client ''' - response = self.register_client(u'GMOGMO', 'confidential', 'NO GMO!') + response = self.register_client( + u'GMOGMO', 'confidential', 'NO GMO!') assert response.status_int == 302 @@ -103,15 +110,14 @@ class TestOAuth(object): def test_4_authorize_confidential_client(self): ''' Authorize a confidential client as a logged in user ''' - client = self.test_3_successful_confidential_client_reg() client_identifier = client.identifier redirect_uri = 'https://foo.example' - response = self.app.get('/oauth/authorize', { + response = self.test_app.get('/oauth/authorize', { 'client_id': client.identifier, - 'scope': 'admin', + 'scope': 'all', 'redirect_uri': redirect_uri}) # User-agent should NOT be redirected @@ -122,7 +128,7 @@ class TestOAuth(object): form = ctx['form'] # Short for client authorization post reponse - capr = self.app.post( + capr = self.test_app.post( '/oauth/client/authorize', { 'client_id': form.client_id.data, 'allow': 'Allow', @@ -137,6 +143,7 @@ class TestOAuth(object): return authorization_response, client_identifier def get_code_from_redirect_uri(self, uri): + ''' Get the value of ?code= from an URI ''' return parse_qs(urlparse(uri).query)['code'][0] def test_token_endpoint_successful_confidential_request(self): @@ -148,7 +155,7 @@ class TestOAuth(object): client = self.db.OAuthClient.query.filter( self.db.OAuthClient.identifier == unicode(client_id)).first() - token_res = self.app.get('/oauth/access_token?client_id={0}&\ + token_res = self.test_app.get('/oauth/access_token?client_id={0}&\ code={1}&client_secret={2}'.format(client_id, code, client.secret)) assert token_res.status_int == 200 @@ -162,6 +169,11 @@ code={1}&client_secret={2}'.format(client_id, code, client.secret)) assert type(token_data['expires_in']) == int assert token_data['expires_in'] > 0 + # There should be a refresh token provided in the token data + assert len(token_data['refresh_token']) + + return client_id, token_data + def test_token_endpont_missing_id_confidential_request(self): ''' Unsuccessful request against token endpoint, missing client_id ''' code_redirect, client_id = self.test_4_authorize_confidential_client() @@ -171,7 +183,7 @@ code={1}&client_secret={2}'.format(client_id, code, client.secret)) client = self.db.OAuthClient.query.filter( self.db.OAuthClient.identifier == unicode(client_id)).first() - token_res = self.app.get('/oauth/access_token?\ + token_res = self.test_app.get('/oauth/access_token?\ code={0}&client_secret={1}'.format(code, client.secret)) assert token_res.status_int == 200 @@ -181,4 +193,30 @@ code={0}&client_secret={1}'.format(code, client.secret)) assert 'error' in token_data assert not 'access_token' in token_data assert token_data['error'] == 'invalid_request' - assert token_data['error_description'] == 'Missing client_id in request' + assert len(token_data['error_description']) + + def test_refresh_token(self): + ''' Try to get a new access token using the refresh token ''' + # Get an access token and a refresh token + client_id, token_data =\ + self.test_token_endpoint_successful_confidential_request() + + client = self.db.OAuthClient.query.filter( + self.db.OAuthClient.identifier == client_id).first() + + token_res = self.test_app.get('/oauth/access_token', + {'refresh_token': token_data['refresh_token'], + 'client_id': client_id, + 'client_secret': client.secret + }) + + assert token_res.status_int == 200 + + new_token_data = json.loads(token_res.body) + + assert not 'error' in new_token_data + assert 'access_token' in new_token_data + assert 'token_type' in new_token_data + assert 'expires_in' in new_token_data + assert type(new_token_data['expires_in']) == int + assert new_token_data['expires_in'] > 0 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 d7c18642..a9595432 100644 --- a/mediagoblin/tests/test_paste.ini +++ b/mediagoblin/tests/test_paste.ini @@ -6,27 +6,30 @@ 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 -filter-with = beaker -config = %(here)s/test_mgoblin_app.ini +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/ -[filter:beaker] -use = egg:Beaker#beaker_session -cache_dir = %(here)s/test_user_dev/beaker -beaker.session.key = mediagoblin -# beaker.session.secret = somesupersecret -beaker.session.data_dir = %(here)s/test_user_dev/beaker/sessions/data -beaker.session.lock_dir = %(here)s/test_user_dev/beaker/sessions/lock +[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_pdf.py b/mediagoblin/tests/test_pdf.py new file mode 100644 index 00000000..b4d1940a --- /dev/null +++ b/mediagoblin/tests/test_pdf.py @@ -0,0 +1,39 @@ +# 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 tempfile +import shutil +import os +import pytest + +from mediagoblin.media_types.pdf.processing import ( + pdf_info, check_prerequisites, create_pdf_thumb) +from .resources import GOOD_PDF as GOOD + + +@pytest.mark.skipif("not check_prerequisites()") +def test_pdf(): + good_dict = {'pdf_version_major': 1, 'pdf_title': '', + 'pdf_page_size_width': 612, 'pdf_author': '', + 'pdf_keywords': '', 'pdf_pages': 10, + 'pdf_producer': 'dvips + GNU Ghostscript 7.05', + 'pdf_version_minor': 3, + 'pdf_creator': 'LaTeX with hyperref package', + 'pdf_page_size_height': 792} + assert pdf_info(GOOD) == good_dict + temp_dir = tempfile.mkdtemp() + create_pdf_thumb(GOOD, os.path.join(temp_dir, 'good_256_256.png'), 256, 256) + shutil.rmtree(temp_dir) diff --git a/mediagoblin/tests/test_piwigo.py b/mediagoblin/tests/test_piwigo.py new file mode 100644 index 00000000..16ad0111 --- /dev/null +++ b/mediagoblin/tests/test_piwigo.py @@ -0,0 +1,71 @@ +# 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 .tools import fixture_add_user + + +XML_PREFIX = "<?xml version='1.0' encoding='utf-8'?>\n" + + +class Test_PWG(object): + @pytest.fixture(autouse=True) + def setup(self, test_app): + self.test_app = test_app + + fixture_add_user() + + self.username = u"chris" + self.password = "toast" + + def do_post(self, method, params): + params["method"] = method + return self.test_app.post("/api/piwigo/ws.php", params) + + def do_get(self, method, params=None): + if params is None: + params = {} + params["method"] = method + return self.test_app.get("/api/piwigo/ws.php", params) + + def test_session(self): + resp = self.do_post("pwg.session.login", + {"username": u"nouser", "password": "wrong"}) + 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="fail"><err code="999" msg="Invalid username/password"/></rsp>' + + resp = self.do_get("pwg.session.getStatus") + assert resp.body == XML_PREFIX \ + + '<rsp stat="ok"><username>guest</username></rsp>' + + resp = self.do_post("pwg.session.login", + {"username": self.username, "password": self.password}) + assert resp.body == XML_PREFIX + '<rsp stat="ok">1</rsp>' + + resp = self.do_get("pwg.session.getStatus") + assert resp.body == XML_PREFIX \ + + '<rsp stat="ok"><username>chris</username></rsp>' + + self.do_get("pwg.session.logout") + + resp = self.do_get("pwg.session.getStatus") + assert resp.body == XML_PREFIX \ + + '<rsp stat="ok"><username>guest</username></rsp>' diff --git a/mediagoblin/tests/test_pluginapi.py b/mediagoblin/tests/test_pluginapi.py index 315a95da..eae0ce15 100644 --- a/mediagoblin/tests/test_pluginapi.py +++ b/mediagoblin/tests/test_pluginapi.py @@ -14,12 +14,22 @@ # 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 +import pytest +import pkg_resources +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 nose.tools import eq_ +from mediagoblin.tests.tools import get_app +from mediagoblin.tools.common import CollectingPrinter def with_cleanup(*modules_to_delete): @@ -97,7 +107,7 @@ def test_no_plugins(): setup_plugins() # Make sure we didn't load anything. - eq_(len(pman.plugins), 0) + assert len(pman.plugins) == 0 @with_cleanup('mediagoblin.plugins.sampleplugin') @@ -117,14 +127,14 @@ def test_one_plugin(): setup_plugins() # Make sure we only found one plugin - eq_(len(pman.plugins), 1) + assert len(pman.plugins) == 1 # Make sure the plugin is the one we think it is. - eq_(pman.plugins[0], 'mediagoblin.plugins.sampleplugin') + assert pman.plugins[0] == 'mediagoblin.plugins.sampleplugin' # Make sure there was one hook registered - eq_(len(pman.hooks), 1) + assert len(pman.hooks) == 1 # Make sure _setup_plugin_called was called once import mediagoblin.plugins.sampleplugin - eq_(mediagoblin.plugins.sampleplugin._setup_plugin_called, 1) + assert mediagoblin.plugins.sampleplugin._setup_plugin_called == 1 @with_cleanup('mediagoblin.plugins.sampleplugin') @@ -145,14 +155,14 @@ def test_same_plugin_twice(): setup_plugins() # Make sure we only found one plugin - eq_(len(pman.plugins), 1) + assert len(pman.plugins) == 1 # Make sure the plugin is the one we think it is. - eq_(pman.plugins[0], 'mediagoblin.plugins.sampleplugin') + assert pman.plugins[0] == 'mediagoblin.plugins.sampleplugin' # Make sure there was one hook registered - eq_(len(pman.hooks), 1) + assert len(pman.hooks) == 1 # Make sure _setup_plugin_called was called once import mediagoblin.plugins.sampleplugin - eq_(mediagoblin.plugins.sampleplugin._setup_plugin_called, 1) + assert mediagoblin.plugins.sampleplugin._setup_plugin_called == 1 @with_cleanup() @@ -172,4 +182,285 @@ def test_disabled_plugin(): setup_plugins() # Make sure we didn't load the plugin - eq_(len(pman.plugins), 0) + assert len(pman.plugins) == 0 + + +CONFIG_ALL_CALLABLES = [ + ('mediagoblin', {}, []), + ('plugins', {}, [ + ('mediagoblin.tests.testplugins.callables1', {}, []), + ('mediagoblin.tests.testplugins.callables2', {}, []), + ('mediagoblin.tests.testplugins.callables3', {}, []), + ]) + ] + + +@with_cleanup() +def test_hook_handle(): + """ + Test the hook_handle method + """ + cfg = build_config(CONFIG_ALL_CALLABLES) + + mg_globals.app_config = cfg['mediagoblin'] + mg_globals.global_config = cfg + + setup_plugins() + + # Just one hook provided + call_log = [] + assert pluginapi.hook_handle( + "just_one", call_log) == "Called just once" + assert call_log == ["expect this one call"] + + # Nothing provided and unhandled not okay + call_log = [] + pluginapi.hook_handle( + "nothing_handling", call_log) == None + assert call_log == [] + + # Nothing provided and unhandled okay + call_log = [] + assert pluginapi.hook_handle( + "nothing_handling", call_log, unhandled_okay=True) is None + assert call_log == [] + + # Multiple provided, go with the first! + call_log = [] + assert pluginapi.hook_handle( + "multi_handle", call_log) == "the first returns" + assert call_log == ["Hi, I'm the first"] + + # Multiple provided, one has CantHandleIt + call_log = [] + assert pluginapi.hook_handle( + "multi_handle_with_canthandle", + call_log) == "the second returns" + assert call_log == ["Hi, I'm the second"] + + +@with_cleanup() +def test_hook_runall(): + """ + Test the hook_runall method + """ + cfg = build_config(CONFIG_ALL_CALLABLES) + + mg_globals.app_config = cfg['mediagoblin'] + mg_globals.global_config = cfg + + setup_plugins() + + # Just one hook, check results + call_log = [] + assert pluginapi.hook_runall( + "just_one", call_log) == ["Called just once"] + assert call_log == ["expect this one call"] + + # None provided, check results + call_log = [] + assert pluginapi.hook_runall( + "nothing_handling", call_log) == [] + assert call_log == [] + + # Multiple provided, check results + call_log = [] + assert pluginapi.hook_runall( + "multi_handle", call_log) == [ + "the first returns", + "the second returns", + "the third returns", + ] + assert call_log == [ + "Hi, I'm the first", + "Hi, I'm the second", + "Hi, I'm the third"] + + # Multiple provided, one has CantHandleIt, check results + call_log = [] + assert pluginapi.hook_runall( + "multi_handle_with_canthandle", call_log) == [ + "the second returns", + "the third returns", + ] + assert call_log == [ + "Hi, I'm the second", + "Hi, I'm the third"] + + +@with_cleanup() +def test_hook_transform(): + """ + Test the hook_transform method + """ + cfg = build_config(CONFIG_ALL_CALLABLES) + + mg_globals.app_config = cfg['mediagoblin'] + mg_globals.global_config = cfg + + setup_plugins() + + assert pluginapi.hook_transform( + "expand_tuple", (-1, 0)) == (-1, 0, 1, 2, 3) + + +def test_plugin_config(): + """ + Make sure plugins can set up their own config + """ + config, validation_result = read_mediagoblin_config( + pkg_resources.resource_filename( + 'mediagoblin.tests', 'appconfig_plugin_specs.ini')) + + pluginspec_section = config['plugins'][ + 'mediagoblin.tests.testplugins.pluginspec'] + assert pluginspec_section['some_string'] == 'not blork' + assert pluginspec_section['dont_change_me'] == 'still the default' + + # Make sure validation works... this should be an error + assert isinstance( + validation_result[ + 'plugins'][ + 'mediagoblin.tests.testplugins.pluginspec'][ + 'some_int'], + VdtTypeError) + + # 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_processing.py b/mediagoblin/tests/test_processing.py index fe8489aa..591add96 100644 --- a/mediagoblin/tests/test_processing.py +++ b/mediagoblin/tests/test_processing.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -from nose.tools import assert_equal - from mediagoblin import processing class TestProcessing(object): @@ -10,7 +8,7 @@ class TestProcessing(object): result = builder.fill(format) if output is None: return result - assert_equal(output, result) + assert output == result def test_easy_filename_fill(self): self.run_fill('/home/user/foo.TXT', '{basename}bar{ext}', 'foobar.txt') diff --git a/mediagoblin/tests/test_session.py b/mediagoblin/tests/test_session.py new file mode 100644 index 00000000..78d790eb --- /dev/null +++ b/mediagoblin/tests/test_session.py @@ -0,0 +1,30 @@ +# 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 session + +def test_session(): + sess = session.Session() + assert not sess + assert not sess.is_updated() + sess['user_id'] = 27 + assert sess + assert not sess.is_updated() + sess.save() + assert sess.is_updated() + sess.delete() + assert not sess + assert sess.is_updated() diff --git a/mediagoblin/tests/test_storage.py b/mediagoblin/tests/test_storage.py index 61326ae9..f6f1d18f 100644 --- a/mediagoblin/tests/test_storage.py +++ b/mediagoblin/tests/test_storage.py @@ -18,7 +18,7 @@ import os import tempfile -from nose.tools import assert_raises, assert_equal, assert_true +import pytest from werkzeug.utils import secure_filename from mediagoblin import storage @@ -41,10 +41,8 @@ def test_clean_listy_filepath(): assert storage.clean_listy_filepath( ['../../../etc/', 'passwd']) == expected - assert_raises( - storage.InvalidFilepath, - storage.clean_listy_filepath, - ['../../', 'linooks.jpg']) + with pytest.raises(storage.InvalidFilepath): + storage.clean_listy_filepath(['../../', 'linooks.jpg']) class FakeStorageSystem(): @@ -78,10 +76,10 @@ def test_storage_system_from_config(): 'garbage_arg': 'garbage_arg', 'storage_class': 'mediagoblin.tests.test_storage:FakeStorageSystem'}) - assert_equal(this_storage.foobie, 'eiboof') - assert_equal(this_storage.blech, 'hcelb') - assert_equal(unicode(this_storage.__class__), - u'mediagoblin.tests.test_storage.FakeStorageSystem') + assert this_storage.foobie == 'eiboof' + assert this_storage.blech == 'hcelb' + assert unicode(this_storage.__class__) == \ + u'mediagoblin.tests.test_storage.FakeStorageSystem' ########################## @@ -89,7 +87,7 @@ def test_storage_system_from_config(): ########################## def get_tmp_filestorage(mount_url=None, fake_remote=False): - tmpdir = tempfile.mkdtemp() + tmpdir = tempfile.mkdtemp(prefix="test_gmg_storage") if fake_remote: this_storage = FakeRemoteStorage(tmpdir, mount_url) else: @@ -97,6 +95,14 @@ def get_tmp_filestorage(mount_url=None, fake_remote=False): return tmpdir, this_storage +def cleanup_storage(this_storage, tmpdir, *paths): + for p in paths: + while p: + assert this_storage.delete_dir(p) == True + p.pop(-1) + os.rmdir(tmpdir) + + def test_basic_storage__resolve_filepath(): tmpdir, this_storage = get_tmp_filestorage() @@ -108,11 +114,13 @@ def test_basic_storage__resolve_filepath(): assert result == os.path.join( tmpdir, 'etc/passwd') - assert_raises( + pytest.raises( storage.InvalidFilepath, this_storage._resolve_filepath, ['../../', 'etc', 'passwd']) + cleanup_storage(this_storage, tmpdir) + def test_basic_storage_file_exists(): tmpdir, this_storage = get_tmp_filestorage() @@ -126,6 +134,9 @@ def test_basic_storage_file_exists(): assert not this_storage.file_exists(['dir1', 'dir2', 'thisfile.lol']) assert not this_storage.file_exists(['dnedir1', 'dnedir2', 'somefile.lol']) + this_storage.delete_file(['dir1', 'dir2', 'filename.txt']) + cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2']) + def test_basic_storage_get_unique_filepath(): tmpdir, this_storage = get_tmp_filestorage() @@ -146,6 +157,9 @@ def test_basic_storage_get_unique_filepath(): assert len(new_filename) > len('filename.txt') assert new_filename == secure_filename(new_filename) + os.remove(filename) + cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2']) + def test_basic_storage_get_file(): tmpdir, this_storage = get_tmp_filestorage() @@ -182,6 +196,11 @@ def test_basic_storage_get_file(): with this_storage.get_file(['testydir', 'testyfile.txt']) as testyfile: assert testyfile.read() == 'testy file! so testy.' + this_storage.delete_file(filepath) + this_storage.delete_file(new_filepath) + this_storage.delete_file(['testydir', 'testyfile.txt']) + cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2'], ['testydir']) + def test_basic_storage_delete_file(): tmpdir, this_storage = get_tmp_filestorage() @@ -196,19 +215,24 @@ def test_basic_storage_delete_file(): assert os.path.exists( os.path.join(tmpdir, 'dir1/dir2/ourfile.txt')) + assert this_storage.delete_dir(['dir1', 'dir2']) == False this_storage.delete_file(filepath) + assert this_storage.delete_dir(['dir1', 'dir2']) == True assert not os.path.exists( os.path.join(tmpdir, 'dir1/dir2/ourfile.txt')) + cleanup_storage(this_storage, tmpdir, ['dir1']) + def test_basic_storage_url_for_file(): # Not supplying a base_url should actually just bork. tmpdir, this_storage = get_tmp_filestorage() - assert_raises( + pytest.raises( storage.NoWebServing, this_storage.file_url, ['dir1', 'dir2', 'filename.txt']) + cleanup_storage(this_storage, tmpdir) # base_url without domain tmpdir, this_storage = get_tmp_filestorage('/media/') @@ -216,6 +240,7 @@ def test_basic_storage_url_for_file(): ['dir1', 'dir2', 'filename.txt']) expected = '/media/dir1/dir2/filename.txt' assert result == expected + cleanup_storage(this_storage, tmpdir) # base_url with domain tmpdir, this_storage = get_tmp_filestorage( @@ -224,6 +249,7 @@ def test_basic_storage_url_for_file(): ['dir1', 'dir2', 'filename.txt']) expected = 'http://media.example.org/ourmedia/dir1/dir2/filename.txt' assert result == expected + cleanup_storage(this_storage, tmpdir) def test_basic_storage_get_local_path(): @@ -237,10 +263,13 @@ def test_basic_storage_get_local_path(): assert result == expected + cleanup_storage(this_storage, tmpdir) + def test_basic_storage_is_local(): tmpdir, this_storage = get_tmp_filestorage() assert this_storage.local_storage is True + cleanup_storage(this_storage, tmpdir) def test_basic_storage_copy_locally(): @@ -255,9 +284,14 @@ def test_basic_storage_copy_locally(): new_file_dest = os.path.join(dest_tmpdir, 'file2.txt') this_storage.copy_locally(filepath, new_file_dest) + this_storage.delete_file(filepath) assert file(new_file_dest).read() == 'Testing this file' + os.remove(new_file_dest) + os.rmdir(dest_tmpdir) + cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2']) + def _test_copy_local_to_storage_works(tmpdir, this_storage): local_filename = tempfile.mktemp() @@ -267,10 +301,15 @@ def _test_copy_local_to_storage_works(tmpdir, this_storage): this_storage.copy_local_to_storage( local_filename, ['dir1', 'dir2', 'copiedto.txt']) + os.remove(local_filename) + assert file( os.path.join(tmpdir, 'dir1/dir2/copiedto.txt'), 'r').read() == 'haha' + this_storage.delete_file(['dir1', 'dir2', 'copiedto.txt']) + cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2']) + def test_basic_storage_copy_local_to_storage(): tmpdir, this_storage = get_tmp_filestorage() diff --git a/mediagoblin/tests/test_submission.py b/mediagoblin/tests/test_submission.py index fc3d8c83..ac941063 100644 --- a/mediagoblin/tests/test_submission.py +++ b/mediagoblin/tests/test_submission.py @@ -20,28 +20,17 @@ sys.setdefaultencoding('utf-8') import urlparse import os +import pytest -from nose.tools import assert_equal, assert_true -from pkg_resources import resource_filename - -from mediagoblin.tests.tools import get_app, \ - fixture_add_user +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 - -def resource(filename): - return resource_filename('mediagoblin.tests', 'test_submission/' + filename) - +from mediagoblin.media_types.image import ImageMediaManager +from mediagoblin.media_types.pdf.processing import check_prerequisites as pdf_check_prerequisites -GOOD_JPG = resource('good.jpg') -GOOD_PNG = resource('good.png') -EVIL_FILE = resource('evil') -EVIL_JPG = resource('evil.jpg') -EVIL_PNG = resource('evil.png') -BIG_BLUE = resource('bigblue.png') -from .test_exif import GPS_JPG +from .resources import GOOD_JPG, GOOD_PNG, EVIL_FILE, EVIL_JPG, EVIL_PNG, \ + BIG_BLUE, GOOD_PDF, GPS_JPG GOOD_TAG_STRING = u'yin,yang' BAD_TAG_STRING = unicode('rage,' + 'f' * 26 + 'u' * 26) @@ -51,8 +40,9 @@ REQUEST_CONTEXT = ['mediagoblin/user_pages/user.html', 'request'] class TestSubmission: - def setUp(self): - self.test_app = get_app(dump_old_app=False) + @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') @@ -87,28 +77,28 @@ class TestSubmission: return {'upload_files': [('file', filename)]} def check_comments(self, request, media_id, count): - comments = request.db.MediaComment.find({'media_entry': media_id}) - assert_equal(count, len(list(comments))) + comments = request.db.MediaComment.query.filter_by(media_entry=media_id) + assert count == len(list(comments)) def test_missing_fields(self): # Test blank form # --------------- response, form = self.do_post({}, *FORM_CONTEXT) - assert_equal(form.file.errors, [u'You must provide a file.']) + assert form.file.errors == [u'You must provide a file.'] # Test blank file # --------------- response, form = self.do_post({'title': u'test title'}, *FORM_CONTEXT) - assert_equal(form.file.errors, [u'You must provide a file.']) + assert form.file.errors == [u'You must provide a file.'] def check_url(self, response, path): - assert_equal(urlparse.urlsplit(response.location)[2], path) + assert urlparse.urlsplit(response.location)[2] == path def check_normal_upload(self, title, filename): 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)) - assert_true('mediagoblin/user_pages/user.html' in context) + assert 'mediagoblin/user_pages/user.html' in context # Make sure the media view is at least reachable, logged in... url = '/u/{0}/m/{1}/'.format(self.test_user.username, title.lower().replace(' ', '-')) @@ -123,10 +113,18 @@ class TestSubmission: def test_normal_png(self): self.check_normal_upload(u'Normal upload 2', GOOD_PNG) + @pytest.mark.skipif("not pdf_check_prerequisites()") + def test_normal_pdf(self): + response, context = self.do_post({'title': u'Normal upload 3 (pdf)'}, + do_follow=True, + **self.upload_data(GOOD_PDF)) + self.check_url(response, '/u/{0}/'.format(self.test_user.username)) + 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_equal(media.count(), count) + assert media.count() == count if count == 0: return return media[0] @@ -151,10 +149,10 @@ class TestSubmission: 'tags': BAD_TAG_STRING}, *FORM_CONTEXT, **self.upload_data(GOOD_JPG)) - assert_equal(form.tags.errors, [ + assert form.tags.errors == [ u'Tags must be shorter than 50 characters. ' \ 'Tags that are too long: ' \ - 'ffffffffffffffffffffffffffuuuuuuuuuuuuuuuuuuuuuuuuuu']) + 'ffffffffffffffffffffffffffuuuuuuuuuuuuuuuuuuuuuuuuuu'] def test_delete(self): response, request = self.do_post({'title': u'Balanced Goblin'}, @@ -173,7 +171,7 @@ class TestSubmission: 'slug': u"Balanced=Goblin", 'tags': u''}) media = self.check_media(request, {'title': u'Balanced Goblin'}, 1) - assert_equal(media.slug, u"balanced-goblin") + assert media.slug == u"balanced-goblin" # Add a comment, so we can test for its deletion later. self.check_comments(request, media_id, 0) @@ -207,7 +205,7 @@ class TestSubmission: response, form = self.do_post({'title': u'Malicious Upload 1'}, *FORM_CONTEXT, **self.upload_data(EVIL_FILE)) - assert_equal(len(form.file.errors), 1) + assert len(form.file.errors) == 1 assert 'Sorry, I don\'t support that file type :(' == \ str(form.file.errors[0]) @@ -220,8 +218,9 @@ class TestSubmission: **self.upload_data(GOOD_JPG)) media = self.check_media(request, {'title': u'Balanced Goblin'}, 1) - assert_equal(media.media_type, u'mediagoblin.media_types.image') - assert_equal(media.media_manager, img_MEDIA_MANAGER) + assert media.media_type == u'mediagoblin.media_types.image' + assert isinstance(media.media_manager, ImageMediaManager) + assert media.media_manager.entry == media def test_sniffing(self): @@ -241,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' @@ -253,9 +252,9 @@ 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}) - assert_equal(entry.state, 'failed') - assert_equal(entry.fail_error, u'mediagoblin.processing:BadMediaFail') + entry = mg_globals.database.MediaEntry.query.filter_by(title=title).first() + assert entry.state == 'failed' + assert entry.fail_error == u'mediagoblin.processing:BadMediaFail' def test_evil_jpg(self): # Test non-supported file with .jpg extension @@ -270,9 +269,12 @@ class TestSubmission: def test_media_data(self): self.check_normal_upload(u"With GPS data", GPS_JPG) media = self.check_media(None, {"title": u"With GPS data"}, 1) - assert_equal(media.media_data.gps_latitude, 59.336666666666666) + assert media.media_data.gps_latitude == 59.336666666666666 def test_processing(self): + public_store_dir = mg_globals.global_config[ + 'storage:publicstore']['base_dir'] + data = {'title': u'Big Blue'} response, request = self.do_post(data, *REQUEST_CONTEXT, do_follow=True, **self.upload_data(BIG_BLUE)) @@ -282,12 +284,11 @@ class TestSubmission: ('medium', 'bigblue.medium.png'), ('thumb', 'bigblue.thumbnail.png')): # Does the processed image have a good filename? - filename = resource_filename( - 'mediagoblin.tests', - os.path.join('test_user_dev/media/public', - *media.media_files.get(key, []))) - assert_true(filename.endswith('_' + basename)) + filename = os.path.join( + public_store_dir, + *media.media_files[key]) + assert filename.endswith('_' + basename) # Is it smaller than the last processed image we looked at? size = os.stat(filename).st_size - assert_true(last_size > size) + assert last_size > size last_size = size diff --git a/mediagoblin/tests/test_submission/good.pdf b/mediagoblin/tests/test_submission/good.pdf Binary files differnew file mode 100644 index 00000000..ab5db006 --- /dev/null +++ b/mediagoblin/tests/test_submission/good.pdf diff --git a/mediagoblin/tests/test_tags.py b/mediagoblin/tests/test_tags.py index ccb93085..e25cc283 100644 --- a/mediagoblin/tests/test_tags.py +++ b/mediagoblin/tests/test_tags.py @@ -14,17 +14,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/>. -from mediagoblin.tests.tools import get_app from mediagoblin.tools import text -def test_list_of_dicts_conversion(): +def test_list_of_dicts_conversion(test_app): """ When the user adds tags to a media entry, the string from the form is converted into a list of tags, where each tag is stored in the database as a dict. Each tag dict should contain the tag's name and slug. Another function performs the reverse operation when populating a form to edit tags. """ - test_app = get_app(dump_old_app=False) # Leading, trailing, and internal whitespace should be removed and slugified assert text.convert_to_tag_list_of_dicts('sleep , 6 AM, chainsaw! ') == [ {'name': u'sleep', 'slug': u'sleep'}, diff --git a/mediagoblin/tests/test_timesince.py b/mediagoblin/tests/test_timesince.py new file mode 100644 index 00000000..6579eb09 --- /dev/null +++ b/mediagoblin/tests/test_timesince.py @@ -0,0 +1,57 @@ +# 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 datetime import datetime, timedelta + +from mediagoblin.tools.timesince import is_aware, timesince + + +def test_timesince(): + test_time = datetime.now() + + # it should ignore second and microseconds + assert timesince(test_time, test_time + timedelta(microseconds=1)) == "0 minutes" + assert timesince(test_time, test_time + timedelta(seconds=1)) == "0 minutes" + + # test minutes, hours, days, weeks, months and years (singular and plural) + assert timesince(test_time, test_time + timedelta(minutes=1)) == "1 minute" + assert timesince(test_time, test_time + timedelta(minutes=2)) == "2 minutes" + + assert timesince(test_time, test_time + timedelta(hours=1)) == "1 hour" + assert timesince(test_time, test_time + timedelta(hours=2)) == "2 hours" + + assert timesince(test_time, test_time + timedelta(days=1)) == "1 day" + assert timesince(test_time, test_time + timedelta(days=2)) == "2 days" + + assert timesince(test_time, test_time + timedelta(days=7)) == "1 week" + assert timesince(test_time, test_time + timedelta(days=14)) == "2 weeks" + + assert timesince(test_time, test_time + timedelta(days=30)) == "1 month" + assert timesince(test_time, test_time + timedelta(days=60)) == "2 months" + + assert timesince(test_time, test_time + timedelta(days=365)) == "1 year" + assert timesince(test_time, test_time + timedelta(days=730)) == "2 years" + + # okay now we want to test combinations + # e.g. 1 hour, 5 days + assert timesince(test_time, test_time + timedelta(days=5, hours=1)) == "5 days, 1 hour" + + assert timesince(test_time, test_time + timedelta(days=15)) == "2 weeks, 1 day" + + assert timesince(test_time, test_time + timedelta(days=97)) == "3 months, 1 week" + + assert timesince(test_time, test_time + timedelta(days=2250)) == "6 years, 2 months" + diff --git a/mediagoblin/tests/test_util.py b/mediagoblin/tests/test_util.py index e4c04b7a..bc14f528 100644 --- a/mediagoblin/tests/test_util.py +++ b/mediagoblin/tests/test_util.py @@ -104,6 +104,28 @@ def test_locale_to_lower_lower(): assert translate.locale_to_lower_lower('en_us') == 'en-us' +def test_gettext_lazy_proxy(): + from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ + from mediagoblin.tools.translate import pass_to_ugettext, set_thread_locale + proxy = _(u"Password") + orig = u"Password" + + set_thread_locale("es") + p1 = unicode(proxy) + p1_should = pass_to_ugettext(orig) + assert p1_should != orig, "Test useless, string not translated" + assert p1 == p1_should + + set_thread_locale("sv") + p2 = unicode(proxy) + p2_should = pass_to_ugettext(orig) + assert p2_should != orig, "Test broken, string not translated" + assert p2 == p2_should + + assert p1_should != p2_should, "Test broken, same translated string" + assert p1 != p2 + + def test_html_cleaner(): # Remove images result = text.clean_html( diff --git a/mediagoblin/tests/test_workbench.py b/mediagoblin/tests/test_workbench.py index 636c8689..6695618b 100644 --- a/mediagoblin/tests/test_workbench.py +++ b/mediagoblin/tests/test_workbench.py @@ -21,13 +21,18 @@ import tempfile from mediagoblin.tools import workbench from mediagoblin.mg_globals import setup_globals from mediagoblin.decorators import get_workbench -from mediagoblin.tests.test_storage import get_tmp_filestorage +from mediagoblin.tests.test_storage import get_tmp_filestorage, cleanup_storage class TestWorkbench(object): - def setUp(self): + def setup(self): + self.workbench_base = tempfile.mkdtemp(prefix='gmg_workbench_testing') self.workbench_manager = workbench.WorkbenchManager( - os.path.join(tempfile.gettempdir(), u'mgoblin_workbench_testing')) + self.workbench_base) + + def teardown(self): + # If the workbench is empty, this should work. + os.rmdir(self.workbench_base) def test_create_workbench(self): workbench = self.workbench_manager.create() @@ -70,6 +75,8 @@ class TestWorkbench(object): filename = this_workbench.localized_file(this_storage, filepath) assert filename == os.path.join( tmpdir, 'dir1/dir2/ourfile.txt') + this_storage.delete_file(filepath) + cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2']) # with a fake remote file storage tmpdir, this_storage = get_tmp_filestorage(fake_remote=True) @@ -95,6 +102,10 @@ class TestWorkbench(object): assert filename == os.path.join( this_workbench.dir, 'thisfile.text') + this_storage.delete_file(filepath) + cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2']) + this_workbench.destroy() + def test_workbench_decorator(self): """Test @get_workbench decorator and automatic cleanup""" # The decorator needs mg_globals.workbench_manager diff --git a/mediagoblin/tests/testplugins/__init__.py b/mediagoblin/tests/testplugins/__init__.py new file mode 100644 index 00000000..621845ba --- /dev/null +++ b/mediagoblin/tests/testplugins/__init__.py @@ -0,0 +1,15 @@ +# 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/>. diff --git a/mediagoblin/tests/testplugins/callables1/__init__.py b/mediagoblin/tests/testplugins/callables1/__init__.py new file mode 100644 index 00000000..fe801a01 --- /dev/null +++ b/mediagoblin/tests/testplugins/callables1/__init__.py @@ -0,0 +1,43 @@ +# 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/>. + +def setup_plugin(): + pass + + +def just_one(call_log): + call_log.append("expect this one call") + return "Called just once" + + +def multi_handle(call_log): + call_log.append("Hi, I'm the first") + return "the first returns" + +def multi_handle_with_canthandle(call_log): + return None + + +def expand_tuple(this_tuple): + return this_tuple + (1,) + +hooks = { + 'setup': setup_plugin, + 'just_one': just_one, + 'multi_handle': multi_handle, + 'multi_handle_with_canthandle': multi_handle_with_canthandle, + 'expand_tuple': expand_tuple, + } diff --git a/mediagoblin/tests/testplugins/callables2/__init__.py b/mediagoblin/tests/testplugins/callables2/__init__.py new file mode 100644 index 00000000..9d5cf950 --- /dev/null +++ b/mediagoblin/tests/testplugins/callables2/__init__.py @@ -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/>. + +def setup_plugin(): + pass + + +def just_one(call_log): + assert "SHOULD NOT HAPPEN" + +def multi_handle(call_log): + call_log.append("Hi, I'm the second") + return "the second returns" + +def multi_handle_with_canthandle(call_log): + call_log.append("Hi, I'm the second") + return "the second returns" + +def expand_tuple(this_tuple): + return this_tuple + (2,) + +hooks = { + 'setup': setup_plugin, + 'just_one': just_one, + 'multi_handle': multi_handle, + 'multi_handle_with_canthandle': multi_handle_with_canthandle, + 'expand_tuple': expand_tuple, + } diff --git a/mediagoblin/tests/testplugins/callables3/__init__.py b/mediagoblin/tests/testplugins/callables3/__init__.py new file mode 100644 index 00000000..04efc8fc --- /dev/null +++ b/mediagoblin/tests/testplugins/callables3/__init__.py @@ -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/>. + +def setup_plugin(): + pass + + +def just_one(call_log): + assert "SHOULD NOT HAPPEN" + +def multi_handle(call_log): + call_log.append("Hi, I'm the third") + return "the third returns" + +def multi_handle_with_canthandle(call_log): + call_log.append("Hi, I'm the third") + return "the third returns" + +def expand_tuple(this_tuple): + return this_tuple + (3,) + +hooks = { + 'setup': setup_plugin, + 'just_one': just_one, + 'multi_handle': multi_handle, + 'multi_handle_with_canthandle': multi_handle_with_canthandle, + 'expand_tuple': expand_tuple, + } 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/test_tests.py b/mediagoblin/tests/testplugins/modify_context/views.py index d539f1e0..701ec6f9 100644 --- a/mediagoblin/tests/test_tests.py +++ b/mediagoblin/tests/testplugins/modify_context/views.py @@ -14,23 +14,20 @@ # 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 import mg_globals -from mediagoblin.tests.tools import get_app, fixture_add_user -from mediagoblin.db.models import User +from mediagoblin.tools.response import render_to_response -def test_get_app_wipes_db(): - """ - Make sure we get a fresh database on every wipe :) - """ - get_app(dump_old_app=True) - assert User.query.count() == 0 +def specific(request): + return render_to_response( + request, + 'contextplugin/specific.html', + {"something": "orother", + "doubleme": "happy"}) - fixture_add_user() - assert User.query.count() == 1 - get_app(dump_old_app=False) - assert User.query.count() == 1 - - get_app(dump_old_app=True) - assert User.query.count() == 0 +def general(request): + return render_to_response( + request, + 'contextplugin/general.html', + {"lol": "cats", + "doubleme": "joy"}) diff --git a/mediagoblin/tests/testplugins/pluginspec/__init__.py b/mediagoblin/tests/testplugins/pluginspec/__init__.py new file mode 100644 index 00000000..76ca2b1f --- /dev/null +++ b/mediagoblin/tests/testplugins/pluginspec/__init__.py @@ -0,0 +1,22 @@ +# 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/>. + +def setup_plugin(): + pass + +hooks = { + 'setup': setup_plugin, +} diff --git a/mediagoblin/tests/testplugins/pluginspec/config_spec.ini b/mediagoblin/tests/testplugins/pluginspec/config_spec.ini new file mode 100644 index 00000000..5c9c3bd7 --- /dev/null +++ b/mediagoblin/tests/testplugins/pluginspec/config_spec.ini @@ -0,0 +1,4 @@ +[plugin_spec] +some_string = string(default="blork") +some_int = integer(default=50) +dont_change_me = string(default="still the default")
\ No newline at end of file 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 cc4a7add..98361adc 100644 --- a/mediagoblin/tests/tools.py +++ b/mediagoblin/tests/tools.py @@ -19,21 +19,19 @@ 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.open import setup_connection_and_db_from_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 -from mediagoblin.init.celery import setup_celery_app MEDIAGOBLIN_TEST_DB_NAME = u'__mediagoblin_tests__' @@ -41,21 +39,9 @@ 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') -MGOBLIN_APP = None -USER_DEV_DIRECTORIES_TO_SETUP = [ - 'media/public', 'media/queue', - 'beaker/sessions/data', 'beaker/sessions/lock'] -BAD_CELERY_MESSAGE = """\ -Sorry, you *absolutely* must run nosetests with the -mediagoblin.init.celery.from_tests module. Like so: -$ CELERY_CONFIG_MODULE=mediagoblin.init.celery.from_tests ./bin/nosetests""" - - -class BadCeleryEnviron(Exception): pass +USER_DEV_DIRECTORIES_TO_SETUP = ['media/public', 'media/queue'] class TestingMeddleware(BaseMeddleware): @@ -97,41 +83,40 @@ class TestingMeddleware(BaseMeddleware): return -def suicide_if_bad_celery_environ(): - if not os.environ.get('CELERY_CONFIG_MODULE') == \ - 'mediagoblin.init.celery.from_tests': - raise BadCeleryEnviron(BAD_CELERY_MESSAGE) - +def get_app(request, paste_config=None, mgoblin_config=None): + """Create a MediaGoblin app for testing. -def get_app(dump_old_app=True): - suicide_if_bad_celery_environ() - - # Make sure we've turned on testing - testing._activate_testing() - - # Leave this imported as it sets up celery. - from mediagoblin.init.celery import from_tests + Args: + - request: Not an http request, but a pytest fixture request. We + use this to make temporary directories that pytest + automatically cleans up as needed. + - paste_config: particular paste config used by this application. + - mgoblin_config: particular mediagoblin config used by this + application. + """ + paste_config = paste_config or TEST_SERVER_CONFIG + mgoblin_config = mgoblin_config or TEST_APP_CONFIG - global MGOBLIN_APP + # 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('user_dev').strpath - # Just return the old app if that exists and it's okay to set up - # and return - if MGOBLIN_APP and not dump_old_app: - return MGOBLIN_APP + new_paste_config = run_dir.join('paste.ini').strpath + new_mgoblin_config = run_dir.join('mediagoblin.ini').strpath + shutil.copyfile(paste_config, new_paste_config) + shutil.copyfile(mgoblin_config, new_mgoblin_config) Session.rollback() Session.remove() - # Remove and reinstall user_dev directories - if os.path.exists(TEST_USER_DEV): - shutil.rmtree(TEST_USER_DEV) - + # install user_dev directories for directory in USER_DEV_DIRECTORIES_TO_SETUP: - full_dir = os.path.join(TEST_USER_DEV, directory) + full_dir = os.path.join(user_dev_dir, directory) os.makedirs(full_dir) # Get app config - global_config, validation_result = read_mediagoblin_config(TEST_APP_CONFIG) + global_config, validation_result = read_mediagoblin_config(new_mgoblin_config) app_config = global_config['mediagoblin'] # Run database setup/migrations @@ -139,10 +124,7 @@ def get_app(dump_old_app=True): # setup app and return test_app = loadapp( - 'config:' + TEST_SERVER_CONFIG) - - # Re-setup celery - setup_celery_app(app_config, global_config) + 'config:' + new_paste_config) # Insert the TestingMeddleware, which can do some # sanity checks on every request/response. @@ -151,26 +133,10 @@ def get_app(dump_old_app=True): mg_globals.app.meddleware.insert(0, TestingMeddleware(mg_globals.app)) app = TestApp(test_app) - MGOBLIN_APP = app return app -def setup_fresh_app(func): - """ - Decorator to setup a fresh test application for this function. - - Cleans out test buckets and passes in a new, fresh test_app. - """ - @wraps(func) - def wrapper(*args, **kwargs): - test_app = get_app() - testing.clear_test_buckets() - return func(test_app, *args, **kwargs) - - return wrapper - - def install_fixtures_simple(db, fixtures): """ Very simply install fixtures in the database @@ -198,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: @@ -212,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 @@ -228,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 @@ -263,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 + |