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 | |
247a3b78 | 18 | import mock |
8ac7a653 | 19 | import pytest |
247a3b78 | 20 | |
967df5ef JT |
21 | from webtest import AppError |
22 | ||
247a3b78 | 23 | from .resources import GOOD_JPG |
8ac7a653 | 24 | from mediagoblin import mg_globals |
8d75091d | 25 | from mediagoblin.db.models import User, MediaEntry, MediaComment |
ee9956c3 JT |
26 | from mediagoblin.tests.tools import fixture_add_user |
27 | from mediagoblin.moderation.tools import take_away_privileges | |
ee9956c3 | 28 | |
57c6473a | 29 | class 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 |