This commit was solely to remove unused imports in the code that I have written
[mediagoblin:mediagoblin.git] / mediagoblin / tests / test_submission.py
1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors.  See AUTHORS.
3 #
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU Affero General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 import sys
18 reload(sys)
19 sys.setdefaultencoding('utf-8')
20
21 import urlparse
22 import os
23 import pytest
24
25 from mediagoblin.tests.tools import fixture_add_user
26 from mediagoblin import mg_globals
27 from mediagoblin.db.models import MediaEntry, User
28 from mediagoblin.tools import template
29 from mediagoblin.media_types.image import ImageMediaManager
30 from mediagoblin.media_types.pdf.processing import check_prerequisites as pdf_check_prerequisites
31
32 from .resources import GOOD_JPG, GOOD_PNG, EVIL_FILE, EVIL_JPG, EVIL_PNG, \
33     BIG_BLUE, GOOD_PDF, GPS_JPG
34
35 GOOD_TAG_STRING = u'yin,yang'
36 BAD_TAG_STRING = unicode('rage,' + 'f' * 26 + 'u' * 26)
37
38 FORM_CONTEXT = ['mediagoblin/submit/start.html', 'submit_form']
39 REQUEST_CONTEXT = ['mediagoblin/user_pages/user.html', 'request']
40
41
42 class TestSubmission:
43     @pytest.fixture(autouse=True)
44     def setup(self, test_app):
45         self.test_app = test_app
46
47         # TODO: Possibly abstract into a decorator like:
48         # @as_authenticated_user('chris')
49         fixture_add_user(privileges=[u'active',u'uploader', u'commenter'])
50
51         self.login()
52
53     def our_user(self):
54         """
55         Fetch the user we're submitting with.  Every .get() or .post()
56         invalidates the session; this is a hacky workaround.
57         """
58         #### FIXME: Pytest collects this as a test and runs this.
59         ####   ... it shouldn't.  At least it passes, but that's
60         ####   totally stupid.
61         ####   Also if we found a way to make this run it should be a
62         ####   property.
63         return User.query.filter(User.username==u'chris').first()
64
65     def login(self):
66         self.test_app.post(
67             '/auth/login/', {
68                 'username': u'chris',
69                 'password': 'toast'})
70
71     def logout(self):
72         self.test_app.get('/auth/logout/')
73
74     def do_post(self, data, *context_keys, **kwargs):
75         url = kwargs.pop('url', '/submit/')
76         do_follow = kwargs.pop('do_follow', False)
77         template.clear_test_template_context()
78         response = self.test_app.post(url, data, **kwargs)
79         if do_follow:
80             response.follow()
81         context_data = template.TEMPLATE_TEST_CONTEXT
82         for key in context_keys:
83             context_data = context_data[key]
84         return response, context_data
85
86     def upload_data(self, filename):
87         return {'upload_files': [('file', filename)]}
88
89     def check_comments(self, request, media_id, count):
90         comments = request.db.MediaComment.query.filter_by(media_entry=media_id)
91         assert count == len(list(comments))
92
93     def test_missing_fields(self):
94         # Test blank form
95         # ---------------
96         response, form = self.do_post({}, *FORM_CONTEXT)
97         assert form.file.errors == [u'You must provide a file.']
98
99         # Test blank file
100         # ---------------
101         response, form = self.do_post({'title': u'test title'}, *FORM_CONTEXT)
102         assert form.file.errors == [u'You must provide a file.']
103
104     def check_url(self, response, path):
105         assert urlparse.urlsplit(response.location)[2] == path
106
107     def check_normal_upload(self, title, filename):
108         response, context = self.do_post({'title': title}, do_follow=True,
109                                          **self.upload_data(filename))
110         self.check_url(response, '/u/{0}/'.format(self.our_user().username))
111         assert 'mediagoblin/user_pages/user.html' in context
112         # Make sure the media view is at least reachable, logged in...
113         url = '/u/{0}/m/{1}/'.format(self.our_user().username,
114                                      title.lower().replace(' ', '-'))
115         self.test_app.get(url)
116         # ... and logged out too.
117         self.logout()
118         self.test_app.get(url)
119
120     def test_normal_jpg(self):
121         self.check_normal_upload(u'Normal upload 1', GOOD_JPG)
122
123     def test_normal_png(self):
124         self.check_normal_upload(u'Normal upload 2', GOOD_PNG)
125
126     @pytest.mark.skipif("not pdf_check_prerequisites()")
127     def test_normal_pdf(self):
128         response, context = self.do_post({'title': u'Normal upload 3 (pdf)'},
129                                          do_follow=True,
130                                          **self.upload_data(GOOD_PDF))
131         self.check_url(response, '/u/{0}/'.format(self.our_user().username))
132         assert 'mediagoblin/user_pages/user.html' in context
133
134     def check_media(self, request, find_data, count=None):
135         media = MediaEntry.query.filter_by(**find_data)
136         if count is not None:
137             assert media.count() == count
138             if count == 0:
139                 return
140         return media[0]
141
142     def test_tags(self):
143         # Good tag string
144         # --------
145         response, request = self.do_post({'title': u'Balanced Goblin 2',
146                                           'tags': GOOD_TAG_STRING},
147                                          *REQUEST_CONTEXT, do_follow=True,
148                                          **self.upload_data(GOOD_JPG))
149         media = self.check_media(request, {'title': u'Balanced Goblin 2'}, 1)
150         assert media.tags[0]['name'] == u'yin'
151         assert media.tags[0]['slug'] == u'yin'
152
153         assert media.tags[1]['name'] == u'yang'
154         assert media.tags[1]['slug'] == u'yang'
155
156         # Test tags that are too long
157         # ---------------
158         response, form = self.do_post({'title': u'Balanced Goblin 2',
159                                        'tags': BAD_TAG_STRING},
160                                       *FORM_CONTEXT,
161                                       **self.upload_data(GOOD_JPG))
162         assert form.tags.errors == [
163                 u'Tags must be shorter than 50 characters.  ' \
164                     'Tags that are too long: ' \
165                     'ffffffffffffffffffffffffffuuuuuuuuuuuuuuuuuuuuuuuuuu']
166
167     def test_delete(self):
168         response, request = self.do_post({'title': u'Balanced Goblin'},
169                                          *REQUEST_CONTEXT, do_follow=True,
170                                          **self.upload_data(GOOD_JPG))
171         media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
172         media_id = media.id
173
174         # render and post to the edit page.
175         edit_url = request.urlgen(
176             'mediagoblin.edit.edit_media',
177             user=self.our_user().username, media_id=media_id)
178         self.test_app.get(edit_url)
179         self.test_app.post(edit_url,
180             {'title': u'Balanced Goblin',
181              'slug': u"Balanced=Goblin",
182              'tags': u''})
183         media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
184         assert media.slug == u"balanced-goblin"
185
186         # Add a comment, so we can test for its deletion later.
187         self.check_comments(request, media_id, 0)
188         comment_url = request.urlgen(
189             'mediagoblin.user_pages.media_post_comment',
190             user=self.our_user().username, media_id=media_id)
191         response = self.do_post({'comment_content': 'i love this test'},
192                                 url=comment_url, do_follow=True)[0]
193         self.check_comments(request, media_id, 1)
194
195         # Do not confirm deletion
196         # ---------------------------------------------------
197         delete_url = request.urlgen(
198             'mediagoblin.user_pages.media_confirm_delete',
199             user=self.our_user().username, media_id=media_id)
200         # Empty data means don't confirm
201         response = self.do_post({}, do_follow=True, url=delete_url)[0]
202         media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
203         media_id = media.id
204
205         # Confirm deletion
206         # ---------------------------------------------------
207         response, request = self.do_post({'confirm': 'y'}, *REQUEST_CONTEXT,
208                                          do_follow=True, url=delete_url)
209         self.check_media(request, {'id': media_id}, 0)
210         self.check_comments(request, media_id, 0)
211
212     def test_evil_file(self):
213         # Test non-suppoerted file with non-supported extension
214         # -----------------------------------------------------
215         response, form = self.do_post({'title': u'Malicious Upload 1'},
216                                       *FORM_CONTEXT,
217                                       **self.upload_data(EVIL_FILE))
218         assert len(form.file.errors) == 1
219         assert 'Sorry, I don\'t support that file type :(' == \
220                 str(form.file.errors[0])
221
222
223     def test_get_media_manager(self):
224         """Test if the get_media_manger function returns sensible things
225         """
226         response, request = self.do_post({'title': u'Balanced Goblin'},
227                                          *REQUEST_CONTEXT, do_follow=True,
228                                          **self.upload_data(GOOD_JPG))
229         media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
230
231         assert media.media_type == u'mediagoblin.media_types.image'
232         assert isinstance(media.media_manager, ImageMediaManager)
233         assert media.media_manager.entry == media
234
235
236     def test_sniffing(self):
237         '''
238         Test sniffing mechanism to assert that regular uploads work as intended
239         '''
240         template.clear_test_template_context()
241         response = self.test_app.post(
242             '/submit/', {
243                 'title': u'UNIQUE_TITLE_PLS_DONT_CREATE_OTHER_MEDIA_WITH_THIS_TITLE'
244                 }, upload_files=[(
245                     'file', GOOD_JPG)])
246
247         response.follow()
248
249         context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/user_pages/user.html']
250
251         request = context['request']
252
253         media = request.db.MediaEntry.query.filter_by(
254             title=u'UNIQUE_TITLE_PLS_DONT_CREATE_OTHER_MEDIA_WITH_THIS_TITLE').first()
255
256         assert media.media_type == 'mediagoblin.media_types.image'
257
258     def check_false_image(self, title, filename):
259         # NOTE: The following 2 tests will ultimately fail, but they
260         #   *will* pass the initial form submission step.  Instead,
261         #   they'll be caught as failures during the processing step.
262         response, context = self.do_post({'title': title}, do_follow=True,
263                                          **self.upload_data(filename))
264         self.check_url(response, '/u/{0}/'.format(self.our_user().username))
265         entry = mg_globals.database.MediaEntry.query.filter_by(title=title).first()
266         assert entry.state == 'failed'
267         assert entry.fail_error == u'mediagoblin.processing:BadMediaFail'
268
269     def test_evil_jpg(self):
270         # Test non-supported file with .jpg extension
271         # -------------------------------------------
272         self.check_false_image(u'Malicious Upload 2', EVIL_JPG)
273
274     def test_evil_png(self):
275         # Test non-supported file with .png extension
276         # -------------------------------------------
277         self.check_false_image(u'Malicious Upload 3', EVIL_PNG)
278
279     def test_media_data(self):
280         self.check_normal_upload(u"With GPS data", GPS_JPG)
281         media = self.check_media(None, {"title": u"With GPS data"}, 1)
282         assert media.media_data.gps_latitude == 59.336666666666666
283
284     def test_processing(self):
285         public_store_dir = mg_globals.global_config[
286             'storage:publicstore']['base_dir']
287
288         data = {'title': u'Big Blue'}
289         response, request = self.do_post(data, *REQUEST_CONTEXT, do_follow=True,
290                                          **self.upload_data(BIG_BLUE))
291         media = self.check_media(request, data, 1)
292         last_size = 1024 ** 3  # Needs to be larger than bigblue.png
293         for key, basename in (('original', 'bigblue.png'),
294                               ('medium', 'bigblue.medium.png'),
295                               ('thumb', 'bigblue.thumbnail.png')):
296             # Does the processed image have a good filename?
297             filename = os.path.join(
298                 public_store_dir,
299                 *media.media_files[key])
300             assert filename.endswith('_' + basename)
301             # Is it smaller than the last processed image we looked at?
302             size = os.stat(filename).st_size
303             assert last_size > size
304             last_size = size