Document both migrations, comment out old migration
[mediagoblin.git] / mediagoblin / tests / test_api.py
CommitLineData
57c6473a
JW
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/>.
ee9956c3 16import json
57c6473a 17
247a3b78 18import mock
8ac7a653 19import pytest
247a3b78 20
967df5ef
JT
21from webtest import AppError
22
247a3b78 23from .resources import GOOD_JPG
8ac7a653 24from mediagoblin import mg_globals
8d75091d 25from mediagoblin.db.models import User, MediaEntry, MediaComment
ee9956c3
JT
26from mediagoblin.tests.tools import fixture_add_user
27from mediagoblin.moderation.tools import take_away_privileges
ee9956c3 28
57c6473a 29class TestAPI(object):
a14d90c2 30 """ Test mediagoblin's pump.io complient APIs """
247a3b78 31
ee9956c3
JT
32 @pytest.fixture(autouse=True)
33 def setup(self, test_app):
34 self.test_app = test_app
57c6473a 35 self.db = mg_globals.database
51ab5192 36
5e5d4458 37 self.user = fixture_add_user(privileges=[u'active', u'uploader', u'commenter'])
8917ffb1
JT
38 self.other_user = fixture_add_user(
39 username="otheruser",
40 privileges=[u'active', u'uploader', u'commenter']
41 )
ee9956c3 42
51ab5192
JT
43 def _activity_to_feed(self, test_app, activity, headers=None):
44 """ Posts an activity to the user's feed """
45 if headers:
46 headers.setdefault("Content-Type", "application/json")
47 else:
48 headers = {"Content-Type": "application/json"}
49
a14d90c2
JT
50 with mock.patch("mediagoblin.decorators.oauth_required",
51 new_callable=self.mocked_oauth_required):
51ab5192
JT
52 response = test_app.post(
53 "/api/user/{0}/feed".format(self.user.username),
54 json.dumps(activity),
55 headers=headers
56 )
57
58 return response, json.loads(response.body)
59
60 def _upload_image(self, test_app, image):
61 """ Uploads and image to MediaGoblin via pump.io API """
62 data = open(image, "rb").read()
63 headers = {
64 "Content-Type": "image/jpeg",
65 "Content-Length": str(len(data))
66 }
67
68
a14d90c2
JT
69 with mock.patch("mediagoblin.decorators.oauth_required",
70 new_callable=self.mocked_oauth_required):
51ab5192
JT
71 response = test_app.post(
72 "/api/user/{0}/uploads".format(self.user.username),
73 data,
74 headers=headers
75 )
76 image = json.loads(response.body)
77
78 return response, image
79
80 def _post_image_to_feed(self, test_app, image):
81 """ Posts an already uploaded image to feed """
82 activity = {
83 "verb": "post",
84 "object": image,
85 }
86
87 return self._activity_to_feed(test_app, activity)
88
89
967df5ef
JT
90 def mocked_oauth_required(self, *args, **kwargs):
91 """ Mocks mediagoblin.decorator.oauth_required to always validate """
92
93 def fake_controller(controller, request, *args, **kwargs):
94 request.user = User.query.filter_by(id=self.user.id).first()
95 return controller(request, *args, **kwargs)
96
97 def oauth_required(c):
98 return lambda *args, **kwargs: fake_controller(c, *args, **kwargs)
99
100 return oauth_required
101
ee9956c3
JT
102 def test_can_post_image(self, test_app):
103 """ Tests that an image can be posted to the API """
104 # First request we need to do is to upload the image
51ab5192 105 response, image = self._upload_image(test_app, GOOD_JPG)
ee9956c3 106
51ab5192
JT
107 # I should have got certain things back
108 assert response.status_code == 200
ee9956c3 109
51ab5192
JT
110 assert "id" in image
111 assert "fullImage" in image
112 assert "url" in image["fullImage"]
113 assert "url" in image
114 assert "author" in image
115 assert "published" in image
116 assert "updated" in image
117 assert image["objectType"] == "image"
247a3b78 118
51ab5192
JT
119 # Check that we got the response we're expecting
120 response, _ = self._post_image_to_feed(test_app, image)
121 assert response.status_code == 200
8d75091d 122
8917ffb1
JT
123 def test_unable_to_upload_as_someone_else(self, test_app):
124 """ Test that can't upload as someoen else """
125 data = open(GOOD_JPG, "rb").read()
126 headers = {
127 "Content-Type": "image/jpeg",
128 "Content-Length": str(len(data))
129 }
8d75091d 130
8917ffb1
JT
131 with mock.patch("mediagoblin.decorators.oauth_required",
132 new_callable=self.mocked_oauth_required):
8d75091d 133
8917ffb1
JT
134 # Will be self.user trying to upload as self.other_user
135 with pytest.raises(AppError) as excinfo:
136 test_app.post(
137 "/api/user/{0}/uploads".format(self.other_user.username),
138 data,
139 headers=headers
140 )
8d75091d 141
8917ffb1 142 assert "403 FORBIDDEN" in excinfo.value.message
8d75091d 143
8917ffb1
JT
144 def test_unable_to_post_feed_as_someone_else(self, test_app):
145 """ Tests that can't post an image to someone else's feed """
146 response, data = self._upload_image(test_app, GOOD_JPG)
8d75091d 147
8917ffb1
JT
148 activity = {
149 "verb": "post",
150 "object": data
151 }
8d75091d 152
8917ffb1
JT
153 headers = {
154 "Content-Type": "application/json",
155 }
8d75091d 156
8917ffb1
JT
157 with mock.patch("mediagoblin.decorators.oauth_required",
158 new_callable=self.mocked_oauth_required):
159 with pytest.raises(AppError) as excinfo:
160 test_app.post(
161 "/api/user/{0}/feed".format(self.other_user.username),
162 json.dumps(activity),
163 headers=headers
164 )
8d75091d
JT
165
166 assert "403 FORBIDDEN" in excinfo.value.message
167
168 def test_only_able_to_update_own_image(self, test_app):
169 """ Test's that the uploader is the only person who can update an image """
170 response, data = self._upload_image(test_app, GOOD_JPG)
171 response, data = self._post_image_to_feed(test_app, data)
172
173 activity = {
174 "verb": "update",
175 "object": data["object"],
176 }
177
178 headers = {
179 "Content-Type": "application/json",
180 }
181
182 # Lets change the image uploader to be self.other_user, this is easier
183 # than uploading the image as someone else as the way self.mocked_oauth_required
184 # and self._upload_image.
185 media = MediaEntry.query.filter_by(id=data["object"]["id"]).first()
186 media.uploader = self.other_user.id
187 media.save()
188
189 # Now lets try and edit the image as self.user, this should produce a 403 error.
190 with mock.patch("mediagoblin.decorators.oauth_required",
191 new_callable=self.mocked_oauth_required):
192 with pytest.raises(AppError) as excinfo:
193 test_app.post(
194 "/api/user/{0}/feed".format(self.user.username),
195 json.dumps(activity),
196 headers=headers
197 )
198
8917ffb1 199 assert "403 FORBIDDEN" in excinfo.value.message
57c6473a 200
51ab5192
JT
201 def test_upload_image_with_filename(self, test_app):
202 """ Tests that you can upload an image with filename and description """
203 response, data = self._upload_image(test_app, GOOD_JPG)
204 response, data = self._post_image_to_feed(test_app, data)
ee9956c3 205
51ab5192
JT
206 image = data["object"]
207
208 # Now we need to add a title and description
209 title = "My image ^_^"
210 description = "This is my super awesome image :D"
211 license = "CC-BY-SA"
212
213 image["displayName"] = title
214 image["content"] = description
215 image["license"] = license
216
217 activity = {"verb": "update", "object": image}
218
a14d90c2
JT
219 with mock.patch("mediagoblin.decorators.oauth_required",
220 new_callable=self.mocked_oauth_required):
ee9956c3
JT
221 response = test_app.post(
222 "/api/user/{0}/feed".format(self.user.username),
51ab5192
JT
223 json.dumps(activity),
224 headers={"Content-Type": "application/json"}
ee9956c3
JT
225 )
226
51ab5192
JT
227 image = json.loads(response.body)["object"]
228
229 # Check everything has been set on the media correctly
230 media = MediaEntry.query.filter_by(id=image["id"]).first()
231 assert media.title == title
232 assert media.description == description
233 assert media.license == license
234
235 # Check we're being given back everything we should on an update
236 assert image["id"] == media.id
237 assert image["displayName"] == title
238 assert image["content"] == description
239 assert image["license"] == license
240
ee9956c3
JT
241
242 def test_only_uploaders_post_image(self, test_app):
243 """ Test that only uploaders can upload images """
244 # Remove uploader permissions from user
245 take_away_privileges(self.user.username, u"uploader")
246
247 # Now try and upload a image
248 data = open(GOOD_JPG, "rb").read()
249 headers = {
250 "Content-Type": "image/jpeg",
251 "Content-Length": str(len(data)),
252 }
253
a14d90c2
JT
254 with mock.patch("mediagoblin.decorators.oauth_required",
255 new_callable=self.mocked_oauth_required):
967df5ef 256 with pytest.raises(AppError) as excinfo:
a14d90c2 257 test_app.post(
967df5ef
JT
258 "/api/user/{0}/uploads".format(self.user.username),
259 data,
260 headers=headers
261 )
57c6473a 262
ee9956c3 263 # Assert that we've got a 403
967df5ef 264 assert "403 FORBIDDEN" in excinfo.value.message
51ab5192 265
3c8bd177
JT
266 def test_object_endpoint(self, test_app):
267 """ Tests that object can be looked up at endpoint """
268 # Post an image
269 response, data = self._upload_image(test_app, GOOD_JPG)
270 response, data = self._post_image_to_feed(test_app, data)
271
272 # Now lookup image to check that endpoint works.
273 image = data["object"]
274
275 assert "links" in image
276 assert "self" in image["links"]
277
278 # Get URI and strip testing host off
279 object_uri = image["links"]["self"]["href"]
280 object_uri = object_uri.replace("http://localhost:80", "")
281
a14d90c2
JT
282 with mock.patch("mediagoblin.decorators.oauth_required",
283 new_callable=self.mocked_oauth_required):
3c8bd177
JT
284 request = test_app.get(object_uri)
285
286 image = json.loads(request.body)
287 entry = MediaEntry.query.filter_by(id=image["id"]).first()
288
289 assert request.status_code == 200
290 assert entry.id == image["id"]
291
292 assert "image" in image
293 assert "fullImage" in image
294 assert "pump_io" in image
295 assert "links" in image
51ab5192
JT
296
297 def test_post_comment(self, test_app):
298 """ Tests that I can post an comment media """
299 # Upload some media to comment on
300 response, data = self._upload_image(test_app, GOOD_JPG)
301 response, data = self._post_image_to_feed(test_app, data)
302
303 content = "Hai this is a comment on this lovely picture ^_^"
304
305 activity = {
306 "verb": "post",
307 "object": {
308 "objectType": "comment",
309 "content": content,
310 "inReplyTo": data["object"],
311 }
312 }
313
314 response, comment_data = self._activity_to_feed(test_app, activity)
315 assert response.status_code == 200
316
317 # Find the objects in the database
318 media = MediaEntry.query.filter_by(id=data["object"]["id"]).first()
319 comment = media.get_comments()[0]
320
321 # Tests that it matches in the database
322 assert comment.author == self.user.id
323 assert comment.content == content
324
325 # Test that the response is what we should be given
326 assert comment.id == comment_data["object"]["id"]
327 assert comment.content == comment_data["object"]["content"]
8d75091d 328
8917ffb1
JT
329 def test_unable_to_post_comment_as_someone_else(self, test_app):
330 """ Tests that you're unable to post a comment as someone else. """
331 # Upload some media to comment on
332 response, data = self._upload_image(test_app, GOOD_JPG)
333 response, data = self._post_image_to_feed(test_app, data)
8d75091d 334
8917ffb1
JT
335 activity = {
336 "verb": "post",
337 "object": {
338 "objectType": "comment",
339 "content": "comment commenty comment ^_^",
340 "inReplyTo": data["object"],
341 }
342 }
8d75091d 343
8917ffb1
JT
344 headers = {
345 "Content-Type": "application/json",
346 }
8d75091d 347
8917ffb1
JT
348 with mock.patch("mediagoblin.decorators.oauth_required",
349 new_callable=self.mocked_oauth_required):
350 with pytest.raises(AppError) as excinfo:
351 test_app.post(
352 "/api/user/{0}/feed".format(self.other_user.username),
353 json.dumps(activity),
354 headers=headers
355 )
8d75091d 356
8917ffb1
JT
357 assert "403 FORBIDDEN" in excinfo.value.message
358
8d75091d
JT
359 def test_unable_to_update_someone_elses_comment(self, test_app):
360 """ Test that you're able to update someoen elses comment. """
361 # Upload some media to comment on
362 response, data = self._upload_image(test_app, GOOD_JPG)
363 response, data = self._post_image_to_feed(test_app, data)
364
365 activity = {
366 "verb": "post",
367 "object": {
368 "objectType": "comment",
369 "content": "comment commenty comment ^_^",
370 "inReplyTo": data["object"],
371 }
372 }
373
374 headers = {
375 "Content-Type": "application/json",
376 }
377
378 # Post the comment.
379 response, comment_data = self._activity_to_feed(test_app, activity)
380
381 # change who uploaded the comment as it's easier than changing
382 comment_id = comment_data["object"]["id"]
383 comment = MediaComment.query.filter_by(id=comment_id).first()
384 comment.author = self.other_user.id
385
386 # Update the comment as someone else.
387 comment_data["object"]["content"] = "Yep"
388 activity = {
389 "verb": "update",
390 "object": comment_data["object"]
391 }
392
393 with mock.patch("mediagoblin.decorators.oauth_required",
394 new_callable=self.mocked_oauth_required):
395 with pytest.raises(AppError) as excinfo:
396 test_app.post(
397 "/api/user/{0}/feed".format(self.user.username),
398 json.dumps(activity),
399 headers=headers
400 )
401
402 assert "403 FORBIDDEN" in excinfo.value.message
51ab5192
JT
403
404 def test_profile(self, test_app):
405 """ Tests profile endpoint """
406 uri = "/api/user/{0}/profile".format(self.user.username)
a14d90c2
JT
407 with mock.patch("mediagoblin.decorators.oauth_required",
408 new_callable=self.mocked_oauth_required):
51ab5192
JT
409 response = test_app.get(uri)
410 profile = json.loads(response.body)
411
412 assert response.status_code == 200
413
414 assert profile["preferredUsername"] == self.user.username
415 assert profile["objectType"] == "person"
416
417 assert "links" in profile
8ac7a653 418
5e5d4458
JT
419 def test_whoami_without_login(self, test_app):
420 """ Test that whoami endpoint returns error when not logged in """
421 with pytest.raises(AppError) as excinfo:
422 response = test_app.get("/api/whoami")
423
424 assert "401 UNAUTHORIZED" in excinfo.value.message