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 |
51ab5192 | 25 | from mediagoblin.db.models import User, MediaEntry |
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']) |
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 |