Convenience functions for callable hooks
[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
24 from pkg_resources import resource_filename
25
26 from mediagoblin.tests.tools import fixture_add_user
27 from mediagoblin import mg_globals
28 from mediagoblin.db.models import MediaEntry
29 from mediagoblin.tools import template
30 from mediagoblin.media_types.image import MEDIA_MANAGER as img_MEDIA_MANAGER
31
32 def resource(filename):
33 return resource_filename('mediagoblin.tests', 'test_submission/' + filename)
34
35
36 GOOD_JPG = resource('good.jpg')
37 GOOD_PNG = resource('good.png')
38 EVIL_FILE = resource('evil')
39 EVIL_JPG = resource('evil.jpg')
40 EVIL_PNG = resource('evil.png')
41 BIG_BLUE = resource('bigblue.png')
42 from .test_exif import GPS_JPG
43
44 GOOD_TAG_STRING = u'yin,yang'
45 BAD_TAG_STRING = unicode('rage,' + 'f' * 26 + 'u' * 26)
46
47 FORM_CONTEXT = ['mediagoblin/submit/start.html', 'submit_form']
48 REQUEST_CONTEXT = ['mediagoblin/user_pages/user.html', 'request']
49
50
51 class TestSubmission:
52 def _setup(self, test_app):
53 self.test_app = test_app
54
55 # TODO: Possibly abstract into a decorator like:
56 # @as_authenticated_user('chris')
57 test_user = fixture_add_user()
58
59 self.test_user = test_user
60
61 self.login()
62
63 def login(self):
64 self.test_app.post(
65 '/auth/login/', {
66 'username': u'chris',
67 'password': 'toast'})
68
69 def logout(self):
70 self.test_app.get('/auth/logout/')
71
72 def do_post(self, data, *context_keys, **kwargs):
73 url = kwargs.pop('url', '/submit/')
74 do_follow = kwargs.pop('do_follow', False)
75 template.clear_test_template_context()
76 response = self.test_app.post(url, data, **kwargs)
77 if do_follow:
78 response.follow()
79 context_data = template.TEMPLATE_TEST_CONTEXT
80 for key in context_keys:
81 context_data = context_data[key]
82 return response, context_data
83
84 def upload_data(self, filename):
85 return {'upload_files': [('file', filename)]}
86
87 def check_comments(self, request, media_id, count):
88 comments = request.db.MediaComment.find({'media_entry': media_id})
89 assert count == len(list(comments))
90
91 def test_missing_fields(self, test_app):
92 self._setup(test_app)
93
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.test_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.test_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, test_app):
121 self._setup(test_app)
122 self.check_normal_upload(u'Normal upload 1', GOOD_JPG)
123
124 def test_normal_png(self, test_app):
125 self._setup(test_app)
126 self.check_normal_upload(u'Normal upload 2', GOOD_PNG)
127
128 def check_media(self, request, find_data, count=None):
129 media = MediaEntry.find(find_data)
130 if count is not None:
131 assert media.count() == count
132 if count == 0:
133 return
134 return media[0]
135
136 def test_tags(self, test_app):
137 self._setup(test_app)
138
139 # Good tag string
140 # --------
141 response, request = self.do_post({'title': u'Balanced Goblin 2',
142 'tags': GOOD_TAG_STRING},
143 *REQUEST_CONTEXT, do_follow=True,
144 **self.upload_data(GOOD_JPG))
145 media = self.check_media(request, {'title': u'Balanced Goblin 2'}, 1)
146 assert media.tags[0]['name'] == u'yin'
147 assert media.tags[0]['slug'] == u'yin'
148
149 assert media.tags[1]['name'] == u'yang'
150 assert media.tags[1]['slug'] == u'yang'
151
152 # Test tags that are too long
153 # ---------------
154 response, form = self.do_post({'title': u'Balanced Goblin 2',
155 'tags': BAD_TAG_STRING},
156 *FORM_CONTEXT,
157 **self.upload_data(GOOD_JPG))
158 assert form.tags.errors == [
159 u'Tags must be shorter than 50 characters. ' \
160 'Tags that are too long: ' \
161 'ffffffffffffffffffffffffffuuuuuuuuuuuuuuuuuuuuuuuuuu']
162
163 def test_delete(self, test_app):
164 self._setup(test_app)
165
166 response, request = self.do_post({'title': u'Balanced Goblin'},
167 *REQUEST_CONTEXT, do_follow=True,
168 **self.upload_data(GOOD_JPG))
169 media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
170 media_id = media.id
171
172 # render and post to the edit page.
173 edit_url = request.urlgen(
174 'mediagoblin.edit.edit_media',
175 user=self.test_user.username, media_id=media_id)
176 self.test_app.get(edit_url)
177 self.test_app.post(edit_url,
178 {'title': u'Balanced Goblin',
179 'slug': u"Balanced=Goblin",
180 'tags': u''})
181 media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
182 assert media.slug == u"balanced-goblin"
183
184 # Add a comment, so we can test for its deletion later.
185 self.check_comments(request, media_id, 0)
186 comment_url = request.urlgen(
187 'mediagoblin.user_pages.media_post_comment',
188 user=self.test_user.username, media_id=media_id)
189 response = self.do_post({'comment_content': 'i love this test'},
190 url=comment_url, do_follow=True)[0]
191 self.check_comments(request, media_id, 1)
192
193 # Do not confirm deletion
194 # ---------------------------------------------------
195 delete_url = request.urlgen(
196 'mediagoblin.user_pages.media_confirm_delete',
197 user=self.test_user.username, media_id=media_id)
198 # Empty data means don't confirm
199 response = self.do_post({}, do_follow=True, url=delete_url)[0]
200 media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
201 media_id = media.id
202
203 # Confirm deletion
204 # ---------------------------------------------------
205 response, request = self.do_post({'confirm': 'y'}, *REQUEST_CONTEXT,
206 do_follow=True, url=delete_url)
207 self.check_media(request, {'id': media_id}, 0)
208 self.check_comments(request, media_id, 0)
209
210 def test_evil_file(self, test_app):
211 self._setup(test_app)
212
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, test_app):
224 """Test if the get_media_manger function returns sensible things
225 """
226 self._setup(test_app)
227
228 response, request = self.do_post({'title': u'Balanced Goblin'},
229 *REQUEST_CONTEXT, do_follow=True,
230 **self.upload_data(GOOD_JPG))
231 media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
232
233 assert media.media_type == u'mediagoblin.media_types.image'
234 assert media.media_manager == img_MEDIA_MANAGER
235
236
237 def test_sniffing(self, test_app):
238 '''
239 Test sniffing mechanism to assert that regular uploads work as intended
240 '''
241 self._setup(test_app)
242
243 template.clear_test_template_context()
244 response = self.test_app.post(
245 '/submit/', {
246 'title': u'UNIQUE_TITLE_PLS_DONT_CREATE_OTHER_MEDIA_WITH_THIS_TITLE'
247 }, upload_files=[(
248 'file', GOOD_JPG)])
249
250 response.follow()
251
252 context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/user_pages/user.html']
253
254 request = context['request']
255
256 media = request.db.MediaEntry.find_one({
257 u'title': u'UNIQUE_TITLE_PLS_DONT_CREATE_OTHER_MEDIA_WITH_THIS_TITLE'})
258
259 assert media.media_type == 'mediagoblin.media_types.image'
260
261 def check_false_image(self, title, filename):
262 # NOTE: The following 2 tests will ultimately fail, but they
263 # *will* pass the initial form submission step. Instead,
264 # they'll be caught as failures during the processing step.
265 response, context = self.do_post({'title': title}, do_follow=True,
266 **self.upload_data(filename))
267 self.check_url(response, '/u/{0}/'.format(self.test_user.username))
268 entry = mg_globals.database.MediaEntry.find_one({'title': title})
269 assert entry.state == 'failed'
270 assert entry.fail_error == u'mediagoblin.processing:BadMediaFail'
271
272 def test_evil_jpg(self, test_app):
273 self._setup(test_app)
274
275 # Test non-supported file with .jpg extension
276 # -------------------------------------------
277 self.check_false_image(u'Malicious Upload 2', EVIL_JPG)
278
279 def test_evil_png(self, test_app):
280 self._setup(test_app)
281
282 # Test non-supported file with .png extension
283 # -------------------------------------------
284 self.check_false_image(u'Malicious Upload 3', EVIL_PNG)
285
286 def test_media_data(self, test_app):
287 self._setup(test_app)
288
289 self.check_normal_upload(u"With GPS data", GPS_JPG)
290 media = self.check_media(None, {"title": u"With GPS data"}, 1)
291 assert media.media_data.gps_latitude == 59.336666666666666
292
293 def test_processing(self, test_app):
294 self._setup(test_app)
295
296 public_store_dir = mg_globals.global_config[
297 'storage:publicstore']['base_dir']
298
299 data = {'title': u'Big Blue'}
300 response, request = self.do_post(data, *REQUEST_CONTEXT, do_follow=True,
301 **self.upload_data(BIG_BLUE))
302 media = self.check_media(request, data, 1)
303 last_size = 1024 ** 3 # Needs to be larger than bigblue.png
304 for key, basename in (('original', 'bigblue.png'),
305 ('medium', 'bigblue.medium.png'),
306 ('thumb', 'bigblue.thumbnail.png')):
307 # Does the processed image have a good filename?
308 filename = os.path.join(
309 public_store_dir,
310 *media.media_files.get(key, []))
311 assert filename.endswith('_' + basename)
312 # Is it smaller than the last processed image we looked at?
313 size = os.stat(filename).st_size
314 assert last_size > size
315 last_size = size