Commit | Line | Data |
---|---|---|
c5678c1a | 1 | # GNU MediaGoblin -- federated, autonomous media hosting |
cf29e8a8 | 2 | # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. |
c5678c1a CAW |
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/>. | |
16 | ||
17 | ||
1e03504e | 18 | import os |
c5678c1a | 19 | import pkg_resources |
1e03504e JK |
20 | import shutil |
21 | ||
386c9c7c | 22 | import six |
c5678c1a | 23 | |
623bee73 | 24 | from paste.deploy import loadapp |
c5678c1a CAW |
25 | from webtest import TestApp |
26 | ||
91b89bde | 27 | from mediagoblin import mg_globals |
64a456a4 JT |
28 | from mediagoblin.db.models import User, LocalUser, MediaEntry, Collection, TextComment, \ |
29 | CommentSubscription, Notification, Privilege, Report, Client, \ | |
30 | RequestToken, AccessToken, Activity, Generator, Comment | |
152a3bfa | 31 | from mediagoblin.tools import testing |
421129b6 | 32 | from mediagoblin.init.config import read_mediagoblin_config |
39dc3bf8 | 33 | from mediagoblin.db.base import Session |
56dc1c9d | 34 | from mediagoblin.meddleware import BaseMeddleware |
fa723291 | 35 | from mediagoblin.auth import gen_password_hash |
d693f6bd | 36 | from mediagoblin.gmg_commands.dbupdate import run_dbupdate |
f751d346 | 37 | from mediagoblin.tools.crypto import random_string |
c5678c1a | 38 | |
dfd66b78 | 39 | from datetime import datetime |
40 | ||
c5678c1a | 41 | |
cfd2cbf3 | 42 | MEDIAGOBLIN_TEST_DB_NAME = u'__mediagoblin_tests__' |
623bee73 | 43 | TEST_SERVER_CONFIG = pkg_resources.resource_filename( |
5c441e75 | 44 | 'mediagoblin.tests', 'test_paste.ini') |
c5678c1a | 45 | TEST_APP_CONFIG = pkg_resources.resource_filename( |
623bee73 | 46 | 'mediagoblin.tests', 'test_mgoblin_app.ini') |
6588acc1 | 47 | |
c5678c1a | 48 | |
9e1fa239 | 49 | USER_DEV_DIRECTORIES_TO_SETUP = ['media/public', 'media/queue'] |
c5678c1a | 50 | |
c5678c1a | 51 | |
56dc1c9d | 52 | class TestingMeddleware(BaseMeddleware): |
33d11e99 | 53 | """ |
ce5ae8da | 54 | Meddleware for the Unit tests |
a11aa2d1 | 55 | |
33d11e99 E |
56 | It might make sense to perform some tests on all |
57 | requests/responses. Or prepare them in a special | |
58 | manner. For example all html responses could be tested | |
59 | for being valid html *after* being rendered. | |
60 | ||
61 | This module is getting inserted at the front of the | |
ce5ae8da | 62 | meddleware list, which means: requests are handed here |
33d11e99 E |
63 | first, responses last. So this wraps up the "normal" |
64 | app. | |
65 | ||
66 | If you need to add a test, either add it directly to | |
67 | the appropiate process_request or process_response, or | |
68 | create a new method and call it from process_*. | |
69 | """ | |
70 | ||
33d11e99 E |
71 | def process_response(self, request, response): |
72 | # All following tests should be for html only! | |
74af60bb | 73 | if getattr(response, 'content_type', None) != "text/html": |
33d11e99 E |
74 | # Get out early |
75 | return | |
76 | ||
77 | # If the template contains a reference to | |
78 | # /mgoblin_static/ instead of using | |
79 | # /request.staticdirect(), error out here. | |
80 | # This could probably be implemented as a grep on | |
81 | # the shipped templates easier... | |
82 | if response.text.find("/mgoblin_static/") >= 0: | |
83 | raise AssertionError( | |
84 | "Response HTML contains reference to /mgoblin_static/ " | |
85 | "instead of staticdirect. Request was for: " | |
86 | + request.full_path) | |
87 | ||
88 | return | |
89 | ||
90 | ||
5c2ece74 CAW |
91 | def get_app(request, paste_config=None, mgoblin_config=None): |
92 | """Create a MediaGoblin app for testing. | |
93 | ||
94 | Args: | |
95 | - request: Not an http request, but a pytest fixture request. We | |
96 | use this to make temporary directories that pytest | |
97 | automatically cleans up as needed. | |
98 | - paste_config: particular paste config used by this application. | |
99 | - mgoblin_config: particular mediagoblin config used by this | |
100 | application. | |
101 | """ | |
6588acc1 CAW |
102 | paste_config = paste_config or TEST_SERVER_CONFIG |
103 | mgoblin_config = mgoblin_config or TEST_APP_CONFIG | |
104 | ||
5c2ece74 CAW |
105 | # This is the directory we're copying the paste/mgoblin config stuff into |
106 | run_dir = request.config._tmpdirhandler.mktemp( | |
107 | 'mgoblin_app', numbered=True) | |
491029bc | 108 | user_dev_dir = run_dir.mkdir('user_dev').strpath |
5c2ece74 CAW |
109 | |
110 | new_paste_config = run_dir.join('paste.ini').strpath | |
111 | new_mgoblin_config = run_dir.join('mediagoblin.ini').strpath | |
112 | shutil.copyfile(paste_config, new_paste_config) | |
113 | shutil.copyfile(mgoblin_config, new_mgoblin_config) | |
114 | ||
9c768866 BS |
115 | Session.rollback() |
116 | Session.remove() | |
117 | ||
5c2ece74 | 118 | # install user_dev directories |
c5678c1a | 119 | for directory in USER_DEV_DIRECTORIES_TO_SETUP: |
5c2ece74 | 120 | full_dir = os.path.join(user_dev_dir, directory) |
c5678c1a CAW |
121 | os.makedirs(full_dir) |
122 | ||
123 | # Get app config | |
5c2ece74 | 124 | global_config, validation_result = read_mediagoblin_config(new_mgoblin_config) |
623bee73 | 125 | app_config = global_config['mediagoblin'] |
c5678c1a | 126 | |
d693f6bd | 127 | # Run database setup/migrations |
8b7343c6 CAW |
128 | # @@: The *only* test that doesn't pass if we remove this is in |
129 | # test_persona.py... why? | |
30520c92 | 130 | run_dbupdate(app_config, global_config) |
c5678c1a CAW |
131 | |
132 | # setup app and return | |
0a791a94 | 133 | test_app = loadapp( |
5c2ece74 | 134 | 'config:' + new_paste_config) |
623bee73 | 135 | |
ce5ae8da | 136 | # Insert the TestingMeddleware, which can do some |
91b89bde | 137 | # sanity checks on every request/response. |
34b0874d E |
138 | # Doing it this way is probably not the cleanest way. |
139 | # We'll fix it, when we have plugins! | |
ce5ae8da | 140 | mg_globals.app.meddleware.insert(0, TestingMeddleware(mg_globals.app)) |
91b89bde | 141 | |
623bee73 | 142 | app = TestApp(test_app) |
623bee73 | 143 | return app |
3aa4c668 CAW |
144 | |
145 | ||
85663692 CAW |
146 | def install_fixtures_simple(db, fixtures): |
147 | """ | |
148 | Very simply install fixtures in the database | |
149 | """ | |
386c9c7c | 150 | for collection_name, collection_fixtures in six.iteritems(fixtures): |
85663692 CAW |
151 | collection = db[collection_name] |
152 | for fixture in collection_fixtures: | |
153 | collection.insert(fixture) | |
154 | ||
155 | ||
a5cf95c5 | 156 | def fixture_add_user(username=u'chris', password=u'toast', |
e1561d04 | 157 | privileges=[], wants_comment_notification=True): |
4fc0a289 | 158 | # Reuse existing user or create a new one |
b4997540 | 159 | test_user = LocalUser.query.filter(LocalUser.username==username).first() |
4fc0a289 | 160 | if test_user is None: |
d88fcb03 | 161 | test_user = LocalUser() |
9754802d E |
162 | test_user.username = username |
163 | test_user.email = username + u'@example.com' | |
164 | if password is not None: | |
fa723291 | 165 | test_user.pw_hash = gen_password_hash(password) |
2d7b6bde | 166 | test_user.wants_comment_notification = wants_comment_notification |
e1561d04 | 167 | for privilege in privileges: |
168 | query = Privilege.query.filter(Privilege.privilege_name==privilege) | |
169 | if query.count(): | |
170 | test_user.all_privileges.append(query.one()) | |
2d7b6bde | 171 | |
9754802d | 172 | test_user.save() |
b4997540 JT |
173 | |
174 | # Reload - The `with_polymorphic` needs to be there to eagerly load | |
175 | # the attributes on the LocalUser as this can't be done post detachment. | |
176 | user_query = LocalUser.query.with_polymorphic(LocalUser) | |
177 | test_user = user_query.filter(LocalUser.username==username).first() | |
37ef4c66 E |
178 | |
179 | # ... and detach from session: | |
37ef4c66 E |
180 | Session.expunge(test_user) |
181 | ||
9754802d | 182 | return test_user |
cd75b228 E |
183 | |
184 | ||
2d7b6bde JW |
185 | def fixture_comment_subscription(entry, notify=True, send_email=None): |
186 | if send_email is None: | |
0f3bf8d4 JT |
187 | actor = LocalUser.query.filter_by(id=entry.actor).first() |
188 | send_email = actor.wants_comment_notification | |
2d7b6bde JW |
189 | |
190 | cs = CommentSubscription( | |
191 | media_entry_id=entry.id, | |
0f3bf8d4 | 192 | user_id=entry.actor, |
2d7b6bde JW |
193 | notify=notify, |
194 | send_email=send_email) | |
195 | ||
196 | cs.save() | |
197 | ||
198 | cs = CommentSubscription.query.filter_by(id=cs.id).first() | |
199 | ||
200 | Session.expunge(cs) | |
201 | ||
202 | return cs | |
203 | ||
204 | ||
64a456a4 | 205 | def fixture_add_comment_notification(entry, subject, user, |
2d7b6bde | 206 | seen=False): |
64a456a4 JT |
207 | cn = Notification( |
208 | user_id=user, | |
209 | seen=seen, | |
210 | ) | |
211 | cn.obj = subject | |
2d7b6bde JW |
212 | cn.save() |
213 | ||
64a456a4 | 214 | cn = Notification.query.filter_by(id=cn.id).first() |
2d7b6bde JW |
215 | |
216 | Session.expunge(cn) | |
217 | ||
218 | return cn | |
219 | ||
220 | ||
e9b4e500 | 221 | def fixture_media_entry(title=u"Some title", slug=None, |
2d7b6bde JW |
222 | uploader=None, save=True, gen_slug=True, |
223 | state=u'unprocessed', fake_upload=True, | |
224 | expunge=True): | |
c3de34d4 CAW |
225 | """ |
226 | Add a media entry for testing purposes. | |
227 | ||
228 | Caution: if you're adding multiple entries with fake_upload=True, | |
229 | make sure you save between them... otherwise you'll hit an | |
230 | IntegrityError from multiple newly-added-MediaEntries adding | |
231 | FileKeynames at once. :) | |
232 | """ | |
2d7b6bde JW |
233 | if uploader is None: |
234 | uploader = fixture_add_user().id | |
235 | ||
e9b4e500 E |
236 | entry = MediaEntry() |
237 | entry.title = title | |
238 | entry.slug = slug | |
0f3bf8d4 | 239 | entry.actor = uploader |
e9b4e500 | 240 | entry.media_type = u'image' |
2d7b6bde JW |
241 | entry.state = state |
242 | ||
243 | if fake_upload: | |
244 | entry.media_files = {'thumb': ['a', 'b', 'c.jpg'], | |
245 | 'medium': ['d', 'e', 'f.png'], | |
246 | 'original': ['g', 'h', 'i.png']} | |
247 | entry.media_type = u'mediagoblin.media_types.image' | |
c121a7d3 | 248 | |
e9b4e500 E |
249 | if gen_slug: |
250 | entry.generate_slug() | |
2d7b6bde | 251 | |
e9b4e500 E |
252 | if save: |
253 | entry.save() | |
254 | ||
2d7b6bde JW |
255 | if expunge: |
256 | entry = MediaEntry.query.filter_by(id=entry.id).first() | |
257 | ||
258 | Session.expunge(entry) | |
259 | ||
e9b4e500 E |
260 | return entry |
261 | ||
262 | ||
0f3bf8d4 JT |
263 | def fixture_add_collection(name=u"My first Collection", user=None, |
264 | collection_type=Collection.USER_DEFINED_TYPE): | |
cd75b228 E |
265 | if user is None: |
266 | user = fixture_add_user() | |
0f3bf8d4 JT |
267 | coll = Collection.query.filter_by( |
268 | actor=user.id, | |
269 | title=name, | |
270 | type=collection_type | |
271 | ).first() | |
cd75b228 E |
272 | if coll is not None: |
273 | return coll | |
274 | coll = Collection() | |
0f3bf8d4 | 275 | coll.actor = user.id |
cd75b228 | 276 | coll.title = name |
0f3bf8d4 | 277 | coll.type = collection_type |
cd75b228 E |
278 | coll.generate_slug() |
279 | coll.save() | |
280 | ||
281 | # Reload | |
282 | Session.refresh(coll) | |
283 | ||
284 | # ... and detach from session: | |
285 | Session.expunge(coll) | |
286 | ||
287 | return coll | |
491029bc | 288 | |
2d7b6bde JW |
289 | def fixture_add_comment(author=None, media_entry=None, comment=None): |
290 | if author is None: | |
291 | author = fixture_add_user().id | |
292 | ||
293 | if media_entry is None: | |
64a456a4 | 294 | media_entry = fixture_media_entry() |
2d7b6bde JW |
295 | |
296 | if comment is None: | |
297 | comment = \ | |
298 | 'Auto-generated test comment by user #{0} on media #{0}'.format( | |
299 | author, media_entry) | |
300 | ||
64a456a4 JT |
301 | text_comment = TextComment( |
302 | actor=author, | |
303 | content=comment | |
304 | ) | |
305 | text_comment.save() | |
2d7b6bde | 306 | |
64a456a4 JT |
307 | comment_link = Comment() |
308 | comment_link.target = media_entry | |
309 | comment_link.comment = text_comment | |
310 | comment_link.save() | |
2d7b6bde | 311 | |
64a456a4 | 312 | Session.expunge(comment_link) |
2d7b6bde | 313 | |
64a456a4 | 314 | return text_comment |
2d7b6bde | 315 | |
dfd66b78 | 316 | def fixture_add_comment_report(comment=None, reported_user=None, |
317 | reporter=None, created=None, report_content=None): | |
318 | if comment is None: | |
319 | comment = fixture_add_comment() | |
320 | ||
321 | if reported_user is None: | |
322 | reported_user = fixture_add_user() | |
323 | ||
324 | if reporter is None: | |
325 | reporter = fixture_add_user() | |
326 | ||
327 | if created is None: | |
328 | created=datetime.now() | |
329 | ||
330 | if report_content is None: | |
331 | report_content = \ | |
6acf4ee6 | 332 | 'Auto-generated test report' |
dfd66b78 | 333 | |
64a456a4 JT |
334 | comment_report = Report() |
335 | comment_report.obj = comment | |
336 | comment_report.reported_user = reported_user | |
337 | comment_report.reporter = reporter | |
338 | comment_report.created = created | |
339 | comment_report.report_content = report_content | |
340 | comment_report.obj = comment | |
dfd66b78 | 341 | comment_report.save() |
342 | ||
343 | Session.expunge(comment_report) | |
344 | ||
345 | return comment_report | |
5ddc85e0 JT |
346 | |
347 | def fixture_add_activity(obj, verb="post", target=None, generator=None, actor=None): | |
348 | if generator is None: | |
349 | generator = Generator( | |
350 | name="GNU MediaGoblin", | |
351 | object_type="service" | |
352 | ) | |
353 | generator.save() | |
354 | ||
355 | if actor is None: | |
356 | actor = fixture_add_user() | |
357 | ||
358 | activity = Activity( | |
359 | verb=verb, | |
360 | actor=actor.id, | |
361 | generator=generator.id, | |
362 | ) | |
363 | ||
364 | activity.set_object(obj) | |
365 | ||
366 | if target is not None: | |
367 | activity.set_target(target) | |
368 | ||
369 | activity.save() | |
0f3bf8d4 | 370 | return activity |