Fix #927 - Clean up federation code after Elrond's review
[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
51ab5192 25from mediagoblin.db.models import User, MediaEntry
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'])
ee9956c3 38
51ab5192
JT
39 def _activity_to_feed(self, test_app, activity, headers=None):
40 """ Posts an activity to the user's feed """
41 if headers:
42 headers.setdefault("Content-Type", "application/json")
43 else:
44 headers = {"Content-Type": "application/json"}
45
a14d90c2
JT
46 with mock.patch("mediagoblin.decorators.oauth_required",
47 new_callable=self.mocked_oauth_required):
51ab5192
JT
48 response = test_app.post(
49 "/api/user/{0}/feed".format(self.user.username),
50 json.dumps(activity),
51 headers=headers
52 )
53
54 return response, json.loads(response.body)
55
56 def _upload_image(self, test_app, image):
57 """ Uploads and image to MediaGoblin via pump.io API """
58 data = open(image, "rb").read()
59 headers = {
60 "Content-Type": "image/jpeg",
61 "Content-Length": str(len(data))
62 }
63
64
a14d90c2
JT
65 with mock.patch("mediagoblin.decorators.oauth_required",
66 new_callable=self.mocked_oauth_required):
51ab5192
JT
67 response = test_app.post(
68 "/api/user/{0}/uploads".format(self.user.username),
69 data,
70 headers=headers
71 )
72 image = json.loads(response.body)
73
74 return response, image
75
76 def _post_image_to_feed(self, test_app, image):
77 """ Posts an already uploaded image to feed """
78 activity = {
79 "verb": "post",
80 "object": image,
81 }
82
83 return self._activity_to_feed(test_app, activity)
84
85
967df5ef
JT
86 def mocked_oauth_required(self, *args, **kwargs):
87 """ Mocks mediagoblin.decorator.oauth_required to always validate """
88
89 def fake_controller(controller, request, *args, **kwargs):
90 request.user = User.query.filter_by(id=self.user.id).first()
91 return controller(request, *args, **kwargs)
92
93 def oauth_required(c):
94 return lambda *args, **kwargs: fake_controller(c, *args, **kwargs)
95
96 return oauth_required
97
ee9956c3
JT
98 def test_can_post_image(self, test_app):
99 """ Tests that an image can be posted to the API """
100 # First request we need to do is to upload the image
51ab5192 101 response, image = self._upload_image(test_app, GOOD_JPG)
ee9956c3 102
51ab5192
JT
103 # I should have got certain things back
104 assert response.status_code == 200
ee9956c3 105
51ab5192
JT
106 assert "id" in image
107 assert "fullImage" in image
108 assert "url" in image["fullImage"]
109 assert "url" in image
110 assert "author" in image
111 assert "published" in image
112 assert "updated" in image
113 assert image["objectType"] == "image"
247a3b78 114
51ab5192
JT
115 # Check that we got the response we're expecting
116 response, _ = self._post_image_to_feed(test_app, image)
117 assert response.status_code == 200
57c6473a 118
51ab5192
JT
119 def test_upload_image_with_filename(self, test_app):
120 """ Tests that you can upload an image with filename and description """
121 response, data = self._upload_image(test_app, GOOD_JPG)
122 response, data = self._post_image_to_feed(test_app, data)
ee9956c3 123
51ab5192
JT
124 image = data["object"]
125
126 # Now we need to add a title and description
127 title = "My image ^_^"
128 description = "This is my super awesome image :D"
129 license = "CC-BY-SA"
130
131 image["displayName"] = title
132 image["content"] = description
133 image["license"] = license
134
135 activity = {"verb": "update", "object": image}
136
a14d90c2
JT
137 with mock.patch("mediagoblin.decorators.oauth_required",
138 new_callable=self.mocked_oauth_required):
ee9956c3
JT
139 response = test_app.post(
140 "/api/user/{0}/feed".format(self.user.username),
51ab5192
JT
141 json.dumps(activity),
142 headers={"Content-Type": "application/json"}
ee9956c3
JT
143 )
144
51ab5192
JT
145 image = json.loads(response.body)["object"]
146
147 # Check everything has been set on the media correctly
148 media = MediaEntry.query.filter_by(id=image["id"]).first()
149 assert media.title == title
150 assert media.description == description
151 assert media.license == license
152
153 # Check we're being given back everything we should on an update
154 assert image["id"] == media.id
155 assert image["displayName"] == title
156 assert image["content"] == description
157 assert image["license"] == license
158
ee9956c3
JT
159
160 def test_only_uploaders_post_image(self, test_app):
161 """ Test that only uploaders can upload images """
162 # Remove uploader permissions from user
163 take_away_privileges(self.user.username, u"uploader")
164
165 # Now try and upload a image
166 data = open(GOOD_JPG, "rb").read()
167 headers = {
168 "Content-Type": "image/jpeg",
169 "Content-Length": str(len(data)),
170 }
171
a14d90c2
JT
172 with mock.patch("mediagoblin.decorators.oauth_required",
173 new_callable=self.mocked_oauth_required):
967df5ef 174 with pytest.raises(AppError) as excinfo:
a14d90c2 175 test_app.post(
967df5ef
JT
176 "/api/user/{0}/uploads".format(self.user.username),
177 data,
178 headers=headers
179 )
57c6473a 180
ee9956c3 181 # Assert that we've got a 403
967df5ef 182 assert "403 FORBIDDEN" in excinfo.value.message
51ab5192 183
3c8bd177
JT
184 def test_object_endpoint(self, test_app):
185 """ Tests that object can be looked up at endpoint """
186 # Post an image
187 response, data = self._upload_image(test_app, GOOD_JPG)
188 response, data = self._post_image_to_feed(test_app, data)
189
190 # Now lookup image to check that endpoint works.
191 image = data["object"]
192
193 assert "links" in image
194 assert "self" in image["links"]
195
196 # Get URI and strip testing host off
197 object_uri = image["links"]["self"]["href"]
198 object_uri = object_uri.replace("http://localhost:80", "")
199
a14d90c2
JT
200 with mock.patch("mediagoblin.decorators.oauth_required",
201 new_callable=self.mocked_oauth_required):
3c8bd177
JT
202 request = test_app.get(object_uri)
203
204 image = json.loads(request.body)
205 entry = MediaEntry.query.filter_by(id=image["id"]).first()
206
207 assert request.status_code == 200
208 assert entry.id == image["id"]
209
210 assert "image" in image
211 assert "fullImage" in image
212 assert "pump_io" in image
213 assert "links" in image
51ab5192
JT
214
215 def test_post_comment(self, test_app):
216 """ Tests that I can post an comment media """
217 # Upload some media to comment on
218 response, data = self._upload_image(test_app, GOOD_JPG)
219 response, data = self._post_image_to_feed(test_app, data)
220
221 content = "Hai this is a comment on this lovely picture ^_^"
222
223 activity = {
224 "verb": "post",
225 "object": {
226 "objectType": "comment",
227 "content": content,
228 "inReplyTo": data["object"],
229 }
230 }
231
232 response, comment_data = self._activity_to_feed(test_app, activity)
233 assert response.status_code == 200
234
235 # Find the objects in the database
236 media = MediaEntry.query.filter_by(id=data["object"]["id"]).first()
237 comment = media.get_comments()[0]
238
239 # Tests that it matches in the database
240 assert comment.author == self.user.id
241 assert comment.content == content
242
243 # Test that the response is what we should be given
244 assert comment.id == comment_data["object"]["id"]
245 assert comment.content == comment_data["object"]["content"]
246
247 def test_profile(self, test_app):
248 """ Tests profile endpoint """
249 uri = "/api/user/{0}/profile".format(self.user.username)
a14d90c2
JT
250 with mock.patch("mediagoblin.decorators.oauth_required",
251 new_callable=self.mocked_oauth_required):
51ab5192
JT
252 response = test_app.get(uri)
253 profile = json.loads(response.body)
254
255 assert response.status_code == 200
256
257 assert profile["preferredUsername"] == self.user.username
258 assert profile["objectType"] == "person"
259
260 assert "links" in profile
8ac7a653 261
5e5d4458
JT
262 def test_whoami_without_login(self, test_app):
263 """ Test that whoami endpoint returns error when not logged in """
264 with pytest.raises(AppError) as excinfo:
265 response = test_app.get("/api/whoami")
266
267 assert "401 UNAUTHORIZED" in excinfo.value.message