Commit | Line | Data |
---|---|---|
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 | 16 | import json |
57c6473a | 17 | |
3a02813c CAW |
18 | try: |
19 | import mock | |
20 | except ImportError: | |
21 | import unittest.mock as mock | |
5c2ece74 CAW |
22 | import pytest |
23 | ||
967df5ef JT |
24 | from webtest import AppError |
25 | ||
247a3b78 | 26 | from .resources import GOOD_JPG |
57c6473a | 27 | from mediagoblin import mg_globals |
8d75091d | 28 | from mediagoblin.db.models import User, MediaEntry, MediaComment |
5c2ece74 | 29 | from mediagoblin.tests.tools import fixture_add_user |
ee9956c3 | 30 | from mediagoblin.moderation.tools import take_away_privileges |
57c6473a | 31 | |
57c6473a | 32 | class 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" |