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