Commit 9b2853cdf43d06afbddcda571e49bc4b2cac6c48

Authored by Lucas Kanashiro
2 parents 38820a3b 3739e71b

Merge pull request #97 from colab/tests_accounts

Tests accounts
colab/accounts/forms.py
1 1 # -*- coding: utf-8 -*-
2 2  
3   -from collections import OrderedDict
4   -
5 3 from django import forms
6 4 from django.conf import settings
7   -from django.contrib.auth import authenticate, get_user_model
  5 +from django.contrib.auth import get_user_model
8 6 from django.contrib.auth.forms import ReadOnlyPasswordHashField
9   -from django.contrib.auth.tokens import default_token_generator
10   -from django.contrib.sites.shortcuts import get_current_site
11 7 from django.core.urlresolvers import reverse
12   -from django.template import loader
13   -from django.utils.encoding import force_bytes
14 8 from django.utils.functional import lazy
15   -from django.utils.http import urlsafe_base64_encode
16   -from django.utils.text import capfirst
17 9 from django.utils.translation import ugettext_lazy as _
18 10 from django.utils.safestring import mark_safe
19 11  
... ... @@ -285,236 +277,3 @@ class UserChangeForm(forms.ModelForm):
285 277 # This is done here, rather than on the field, because the
286 278 # field does not have access to the initial value
287 279 return self.initial["password"]
288   -
289   -
290   -class AuthenticationForm(forms.Form):
291   - """
292   - Base class for authenticating users. Extend this to get a form that accepts
293   - username/password logins.
294   - """
295   - username = forms.CharField(max_length=254)
296   - password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
297   -
298   - error_messages = {
299   - 'invalid_login': _("Please enter a correct %(username)s and password. "
300   - "Note that both fields may be case-sensitive."),
301   - 'inactive': _("This account is inactive."),
302   - }
303   -
304   - def __init__(self, request=None, *args, **kwargs):
305   - """
306   - The 'request' parameter is set for custom auth use by subclasses.
307   - The form data comes in via the standard 'data' kwarg.
308   - """
309   - self.request = request
310   - self.user_cache = None
311   - super(AuthenticationForm, self).__init__(*args, **kwargs)
312   -
313   - # Set the label for the "username" field.
314   - UserModel = get_user_model()
315   - self.username_field = UserModel._meta.get_field(
316   - UserModel.USERNAME_FIELD)
317   - if self.fields['username'].label is None:
318   - self.fields['username'].label = capfirst(
319   - self.username_field.verbose_name)
320   -
321   - def clean(self):
322   - username = self.cleaned_data.get('username')
323   - password = self.cleaned_data.get('password')
324   -
325   - if username and password:
326   - self.user_cache = authenticate(username=username,
327   - password=password)
328   - if self.user_cache is None:
329   - raise forms.ValidationError(
330   - self.error_messages['invalid_login'],
331   - code='invalid_login',
332   - params={'username': self.username_field.verbose_name},
333   - )
334   - else:
335   - self.confirm_login_allowed(self.user_cache)
336   -
337   - return self.cleaned_data
338   -
339   - def confirm_login_allowed(self, user):
340   - """
341   - Controls whether the given User may log in. This is a policy setting,
342   - independent of end-user authentication. This default behavior is to
343   - allow login by active users, and reject login by inactive users.
344   - If the given user cannot log in, this method should raise a
345   - ``forms.ValidationError``.
346   - If the given user may log in, this method should return None.
347   - """
348   - if not user.is_active:
349   - raise forms.ValidationError(
350   - self.error_messages['inactive'],
351   - code='inactive',
352   - )
353   -
354   - def get_user_id(self):
355   - if self.user_cache:
356   - return self.user_cache.id
357   - return None
358   -
359   - def get_user(self):
360   - return self.user_cache
361   -
362   -
363   -class PasswordResetForm(forms.Form):
364   - email = forms.EmailField(label=_("Email"), max_length=254)
365   -
366   - def save(self, domain_override=None,
367   - subject_template_name='registration/password_reset_subject.txt',
368   - email_template_name='registration/password_reset_email.html',
369   - use_https=False, token_generator=default_token_generator,
370   - from_email=None, request=None, html_email_template_name=None):
371   - """
372   - Generates a one-use only link for resetting password and sends to the
373   - user.
374   - """
375   - from django.core.mail import send_mail
376   - UserModel = get_user_model()
377   - email = self.cleaned_data["email"]
378   - active_users = UserModel._default_manager.filter(
379   - email__iexact=email, is_active=True)
380   - for user in active_users:
381   - # Make sure that no email is sent to a user that actually has
382   - # a password marked as unusable
383   - if not user.has_usable_password():
384   - continue
385   - if not domain_override:
386   - current_site = get_current_site(request)
387   - site_name = current_site.name
388   - domain = current_site.domain
389   - else:
390   - site_name = domain = domain_override
391   - c = {
392   - 'email': user.email,
393   - 'domain': domain,
394   - 'site_name': site_name,
395   - 'uid': urlsafe_base64_encode(force_bytes(user.pk)),
396   - 'user': user,
397   - 'token': token_generator.make_token(user),
398   - 'protocol': 'https' if use_https else 'http',
399   - }
400   - subject = loader.render_to_string(subject_template_name, c)
401   - # Email subject *must not* contain newlines
402   - subject = ''.join(subject.splitlines())
403   - email = loader.render_to_string(email_template_name, c)
404   -
405   - if html_email_template_name:
406   - html_email = loader.render_to_string(html_email_template_name,
407   - c)
408   - else:
409   - html_email = None
410   - send_mail(subject, email, from_email, [user.email],
411   - html_message=html_email)
412   -
413   -
414   -class SetPasswordForm(forms.Form):
415   - """
416   - A form that lets a user change set their password without entering the old
417   - password
418   - """
419   - error_messages = {
420   - 'password_mismatch': _("The two password fields didn't match."),
421   - }
422   - new_password1 = forms.CharField(label=_("New password"),
423   - widget=forms.PasswordInput)
424   - new_password2 = forms.CharField(label=_("New password confirmation"),
425   - widget=forms.PasswordInput)
426   -
427   - def __init__(self, user, *args, **kwargs):
428   - self.user = user
429   - super(SetPasswordForm, self).__init__(*args, **kwargs)
430   -
431   - def clean_new_password2(self):
432   - password1 = self.cleaned_data.get('new_password1')
433   - password2 = self.cleaned_data.get('new_password2')
434   - if password1 and password2:
435   - if password1 != password2:
436   - raise forms.ValidationError(
437   - self.error_messages['password_mismatch'],
438   - code='password_mismatch',
439   - )
440   - return password2
441   -
442   - def save(self, commit=True):
443   - self.user.set_password(self.cleaned_data['new_password1'])
444   - if commit:
445   - self.user.save()
446   - return self.user
447   -
448   -
449   -class PasswordChangeForm(SetPasswordForm):
450   - """
451   - A form that lets a user change their password by entering their old
452   - password.
453   - """
454   - error_messages = dict(SetPasswordForm.error_messages, **{
455   - 'password_incorrect': _("Your old password was entered incorrectly. "
456   - "Please enter it again."),
457   - })
458   - old_password = forms.CharField(label=_("Old password"),
459   - widget=forms.PasswordInput)
460   -
461   - def clean_old_password(self):
462   - """
463   - Validates that the old_password field is correct.
464   - """
465   - old_password = self.cleaned_data["old_password"]
466   - if not self.user.check_password(old_password):
467   - raise forms.ValidationError(
468   - self.error_messages['password_incorrect'],
469   - code='password_incorrect',
470   - )
471   - return old_password
472   -
473   -PasswordChangeForm.base_fields = OrderedDict(
474   - (k, PasswordChangeForm.base_fields[k])
475   - for k in ['old_password', 'new_password1', 'new_password2']
476   -)
477   -
478   -
479   -class AdminPasswordChangeForm(forms.Form):
480   - """
481   - A form used to change the password of a user in the admin interface.
482   - """
483   - error_messages = {
484   - 'password_mismatch': _("The two password fields didn't match."),
485   - }
486   - password1 = forms.CharField(label=_("Password"),
487   - widget=forms.PasswordInput)
488   - password2 = forms.CharField(label=_("Password (again)"),
489   - widget=forms.PasswordInput)
490   -
491   - def __init__(self, user, *args, **kwargs):
492   - self.user = user
493   - super(AdminPasswordChangeForm, self).__init__(*args, **kwargs)
494   -
495   - def clean_password2(self):
496   - password1 = self.cleaned_data.get('password1')
497   - password2 = self.cleaned_data.get('password2')
498   - if password1 and password2:
499   - if password1 != password2:
500   - raise forms.ValidationError(
501   - self.error_messages['password_mismatch'],
502   - code='password_mismatch',
503   - )
504   - return password2
505   -
506   - def save(self, commit=True):
507   - """
508   - Saves the new password.
509   - """
510   - self.user.set_password(self.cleaned_data["password1"])
511   - if commit:
512   - self.user.save()
513   - return self.user
514   -
515   - def _get_changed_data(self):
516   - data = super(AdminPasswordChangeForm, self).changed_data
517   - for name in self.fields.keys():
518   - if name not in data:
519   - return []
520   - return ['password']
... ...
colab/accounts/tests/test_forms.py
... ... @@ -4,12 +4,16 @@ Objective: Test parameters, and behavior.
4 4 """
5 5  
6 6 import datetime
  7 +from mock import patch
7 8  
8 9 from django.test import TestCase
9 10 from django.core.urlresolvers import reverse
10 11  
11   -from colab.accounts.forms import UserCreationForm, UserChangeForm
  12 +from colab.accounts.forms import UserCreationForm, UserChangeForm,\
  13 + UserUpdateForm, UserForm, get_lists_choices
  14 +from colab.accounts import forms as accounts_forms
12 15 from colab.accounts.models import User
  16 +from colab.accounts.utils import mailman
13 17  
14 18  
15 19 class FormTest(TestCase):
... ... @@ -26,6 +30,9 @@ class FormTest(TestCase):
26 30 user.last_name = "COLAB"
27 31 user.save()
28 32  
  33 + def tearDown(self):
  34 + pass
  35 +
29 36 def create_form_data(self, email, username):
30 37 form_data = {'email': email,
31 38 'first_name': 'colabName',
... ... @@ -36,11 +43,29 @@ class FormTest(TestCase):
36 43 form = UserCreationForm(data=form_data)
37 44 return form
38 45  
  46 + def create_update_form_data(self):
  47 + updated_data = {'username': "colab",
  48 + 'email': 'email@email.com',
  49 + 'last_login': datetime.date.today(),
  50 + 'date_joined': datetime.date.today(),
  51 + 'twitter': 'nick_twitter',
  52 + 'first_name': 'colabName',
  53 + 'last_name': 'secondName',
  54 + }
  55 + initial = {'email': 'email@email.com',
  56 + 'first_name': 'colabName',
  57 + 'last_name': 'secondName',
  58 + 'username': 'colab',
  59 + 'password': '123colab4'}
  60 + form = UserUpdateForm(initial=initial, data=updated_data)
  61 + return form
  62 +
39 63 def create_change_form_data(self, username):
40 64 updated_data = {'username': username,
41 65 'email': 'email@email.com',
42 66 'last_login': datetime.date.today(),
43 67 'date_joined': datetime.date.today()}
  68 +
44 69 initial = {'email': 'email@email.com',
45 70 'first_name': 'colabName',
46 71 'last_name': 'secondName',
... ... @@ -49,10 +74,20 @@ class FormTest(TestCase):
49 74 form = UserChangeForm(initial=initial, data=updated_data)
50 75 return form
51 76  
  77 + def create_user_form_data(self):
  78 + initial = {'email': 'email@email.com',
  79 + 'first_name': 'colabName',
  80 + 'last_name': 'secondName',
  81 + 'username': 'colab',
  82 + 'password': '123colab4'}
  83 + form = UserForm(data=initial)
  84 + return form
  85 +
52 86 def test_already_registered_email(self):
53 87 form = self.create_form_data('usertest@colab.com.br',
54 88 'colab')
55 89 self.assertFalse(form.is_valid())
  90 + self.assertIn('duplicate_email', form.error_messages)
56 91  
57 92 def test_registered_email_message(self):
58 93 form = self.create_form_data('usertest@colab.com.br',
... ... @@ -67,6 +102,12 @@ class FormTest(TestCase):
67 102 'colab123')
68 103 self.assertTrue(form.is_valid())
69 104  
  105 + def test_already_created_username(self):
  106 + form = self.create_form_data('usertest@colab.com.br',
  107 + 'USERtestCoLaB')
  108 + self.assertFalse(form.is_valid())
  109 + self.assertIn('duplicate_username', form.error_messages)
  110 +
70 111 def test_not_valid_username(self):
71 112 form = self.create_form_data('user@email.com',
72 113 'colab!')
... ... @@ -80,5 +121,65 @@ class FormTest(TestCase):
80 121 form = self.create_change_form_data('colab!')
81 122 self.assertFalse(form.is_valid())
82 123  
83   - def tearDown(self):
84   - pass
  124 + @patch.object(accounts_forms, "validate_social_account")
  125 + def test_validate_social_account(self, validate_social_account):
  126 + validate_social_account.return_value = False
  127 +
  128 + form = self.create_update_form_data()
  129 + self.assertFalse(form.is_valid())
  130 + self.assertIn("Social account does not exist", form.errors['twitter'])
  131 +
  132 + def test_required_valid_fields_user_form(self):
  133 + form_data = {
  134 + 'first_name': 'colabName',
  135 + 'last_name': 'secondName',
  136 + 'username': 'colab',
  137 + }
  138 +
  139 + form = UserForm(data=form_data)
  140 +
  141 + self.assertTrue(form.is_valid())
  142 +
  143 + def test_required_empty_fields_user_form(self):
  144 + form_data = {
  145 + 'first_name': '',
  146 + 'last_name': '',
  147 + 'username': '',
  148 + }
  149 +
  150 + form = UserForm(data=form_data)
  151 +
  152 + self.assertFalse(form.is_valid())
  153 +
  154 + self.assertIn('first_name', form.errors)
  155 + self.assertIn('last_name', form.errors)
  156 + self.assertIn('username', form.errors)
  157 +
  158 + def test_blank_required_fields_user_form(self):
  159 + form_data = {
  160 + 'first_name': ' ',
  161 + 'last_name': ' ',
  162 + 'username': ' ',
  163 + }
  164 +
  165 + form = UserForm(data=form_data)
  166 +
  167 + self.assertFalse(form.is_valid())
  168 +
  169 + self.assertIn('first_name', form.errors)
  170 + self.assertIn('last_name', form.errors)
  171 + self.assertIn('username', form.errors)
  172 +
  173 + @patch.object(mailman, "all_lists")
  174 + def test_get_list_choices(self, all_lists):
  175 + all_lists.return_value = [
  176 + {'listname': 'listA', 'description': 'A'},
  177 + {'listname': 'listB', 'description': 'B'},
  178 + {'listname': 'listC', 'description': 'C'},
  179 + {'listname': 'listD', 'description': 'D'},
  180 + ]
  181 + lists = get_lists_choices()
  182 + self.assertEqual(lists, [('listA', u'listA (A)'),
  183 + ('listB', u'listB (B)'),
  184 + ('listC', u'listC (C)'),
  185 + ('listD', u'listD (D)')])
... ...
colab/accounts/tests/test_request.py
... ... @@ -6,6 +6,8 @@ Objective: Test requests.
6 6 from django.test import TestCase, Client
7 7 from django.test.client import RequestFactory
8 8 from colab.accounts.models import User
  9 +from colab.accounts.context_processors import social_network_enabled
  10 +from django.conf import settings
9 11  
10 12  
11 13 class RequestTest(TestCase):
... ... @@ -65,3 +67,26 @@ class RequestTest(TestCase):
65 67 self.assertEqual(302, response.status_code)
66 68 self.assertEqual("http://testserver/account/usertest/subscriptions",
67 69 response.url)
  70 +
  71 +
  72 +class SocialNetworkTest(TestCase):
  73 + """docstring for SocialNetworkTest"""
  74 +
  75 + def setUp(self):
  76 + self.factory = RequestFactory()
  77 + self.client = Client()
  78 +
  79 + def create_user(self):
  80 + self.user_test = User()
  81 + self.user_test.username = "usertest"
  82 + self.user_test.email = "usertest@colab.com.br"
  83 + self.user_test.set_password("1234colab")
  84 + self.user_test.save()
  85 +
  86 + def test_social_network(self):
  87 + self.create_user()
  88 + self.client.login(username="usertest", password='1234colab')
  89 + response = self.client.get('/myaccount/')
  90 + result = social_network_enabled(response)['SOCIAL_NETWORK_ENABLED']
  91 + self.assertTrue(result)
  92 + self.assertTrue(settings.SOCIAL_NETWORK_ENABLED)
... ...
colab/accounts/tests/test_user.py
... ... @@ -43,7 +43,7 @@ class UserTest(TestCase):
43 43 expected_last_name, first_name, last_name):
44 44 data = {'first_name': first_name,
45 45 'last_name': last_name}
46   - self.client.post('/account/usertestcolab/edit', data)
  46 + self.client.post('/account/' + self.user.username + '/edit', data)
47 47 user = User.objects.get(id=1)
48 48 self.assertEqual(expected_first_name, user.first_name)
49 49 self.assertEqual(expected_last_name, user.last_name)
... ... @@ -52,7 +52,7 @@ class UserTest(TestCase):
52 52 data = {'first_name': 'usertestcolab',
53 53 'last_name': 'colab',
54 54 field_name: value}
55   - self.client.post('/account/usertestcolab/edit', data)
  55 + self.client.post('/account/' + self.user.username + '/edit', data)
56 56 user = User.objects.get(id=1)
57 57 self.assertEqual(expected_value, getattr(user, field_name))
58 58  
... ... @@ -77,10 +77,6 @@ class UserTest(TestCase):
77 77 empty_list = ()
78 78 self.assertEqual(empty_list, self.user.mailinglists())
79 79  
80   - def test_update_subscription(self):
81   - pass
82   - # TODO: You should have mailman connection.
83   -
84 80 def test_save(self):
85 81 username_test = "USERtestCoLaB"
86 82  
... ... @@ -374,3 +370,59 @@ class UserTest(TestCase):
374 370 self.authenticate_user()
375 371 self.validate_non_mandatory_fields('bio', '', ' ')
376 372 self.user.delete()
  373 +
  374 + def test_user_without_login(self):
  375 + response = self.client.get("/account/" + self.user.username + "/edit")
  376 + self.assertEqual(response.status_code, 403)
  377 +
  378 + def test_signup_with_post_not_success(self):
  379 + data_user = {
  380 + 'username': 'username',
  381 + 'password1': 'safepassword',
  382 + 'password2': 'safepassword',
  383 + }
  384 + before = User.objects.count()
  385 + self.client.post('/account/register', data=data_user)
  386 + after = User.objects.count()
  387 + self.assertEqual(before, after)
  388 +
  389 + def test_signup_with_post_with_success(self):
  390 + data_user = {
  391 + 'username': 'username',
  392 + 'first_name': 'first name',
  393 + 'last_name': 'last name',
  394 + 'email': 'mail@mail.com',
  395 + 'password1': 'safepassword',
  396 + 'password2': 'safepassword',
  397 + }
  398 + before = User.objects.count()
  399 + self.client.post('/account/register', data=data_user)
  400 + after = User.objects.count()
  401 + self.assertEqual(before + 1, after)
  402 +
  403 + def test_user_logged_in_profile(self):
  404 + self.authenticate_user()
  405 + self.client.get("/account/" + self.user.username)
  406 + self.assertEqual(self.client.session['_auth_user_id'], self.user.id)
  407 +
  408 + def test_user_not_logged_in_profile(self):
  409 + self.client.get("/account/" + self.user.username)
  410 + self.assertEqual(self.client.session, {})
  411 +
  412 + def test_password_changed_message(self):
  413 + self.message_test('Your password was changed.',
  414 + "/account/change-password-done")
  415 +
  416 + def test_password_reset_done_custom_message(self):
  417 + self.message_test("We've emailed you instructions for setting " +
  418 + "your password. You should be receiving them " +
  419 + "shortly.", "/account/password-reset-done/")
  420 +
  421 + def test_password_rest_complete_message(self):
  422 + self.message_test("Your password has been set. You may go ahead and " +
  423 + "log in now.", "/account/password-reset-complete/")
  424 +
  425 + def message_test(self, message, url):
  426 + self.authenticate_user()
  427 + response = self.client.get(url, follow=True)
  428 + self.assertIn(message, response.content)
... ...
colab/accounts/tests/test_user_subscription.py 0 → 100644
... ... @@ -0,0 +1,91 @@
  1 +"""
  2 +Test User Mailing list Subscriptions class.
  3 +Objective: Test parameters, and behavior.
  4 +"""
  5 +
  6 +from mock import patch
  7 +from colab.accounts.models import User
  8 +from django.test import TestCase, Client
  9 +from colab.accounts.utils import mailman
  10 +
  11 +
  12 +class UserSubscriptionTest(TestCase):
  13 + OK = 200
  14 + FORBIDDEN_ACCESS = 403
  15 +
  16 + def setUp(self):
  17 + self.user = self.create_user()
  18 + self.client = Client()
  19 +
  20 + def tearDown(self):
  21 + pass
  22 +
  23 + def create_user(self):
  24 + user = User()
  25 + user.username = "USERtestCoLaB"
  26 + user.set_password("123colab4")
  27 + user.email = "usertest@colab.com.br"
  28 + user.id = 1
  29 + user.twitter = "usertestcolab"
  30 + user.facebook = "usertestcolab"
  31 + user.first_name = "USERtestCoLaB"
  32 + user.last_name = "COLAB"
  33 + user.save()
  34 +
  35 + return user
  36 +
  37 + def authenticate_user(self, user=None, password='123colab4'):
  38 + if not user:
  39 + user = self.user
  40 + user.needs_update = False
  41 + user.save()
  42 + self.client.login(username=user.username,
  43 + password=password)
  44 +
  45 + def test_manage_subscription_logged_in(self):
  46 + self.authenticate_user()
  47 + response = self.client.get("/account/" + self.user.username +
  48 + "/subscriptions")
  49 + self.assertEqual(response.status_code, self.OK)
  50 +
  51 + def test_manage_subscription_without_login(self):
  52 + response = self.client.get("/account/" + self.user.username +
  53 + "/subscriptions")
  54 + self.assertEqual(response.status_code, self.FORBIDDEN_ACCESS)
  55 +
  56 + @patch.object(mailman, 'all_lists')
  57 + @patch.object(mailman, 'mailing_lists')
  58 + def test_context_data_generation(self, mailing_lists, all_lists):
  59 + data_user = {
  60 + 'username': 'username1',
  61 + 'first_name': 'first name1',
  62 + 'last_name': 'last name1',
  63 + 'email': 'mail1@mail.com',
  64 + 'password1': 'safepassword',
  65 + 'password2': 'safepassword',
  66 + }
  67 + self.client.post('/account/register', data=data_user)
  68 + user1 = User.objects.last()
  69 + user1.is_active = True
  70 + user1.save()
  71 + self.authenticate_user(user1, 'safepassword')
  72 +
  73 + mail_lists = [
  74 + {"listname": "name_mock_1", "description": "descript_1"},
  75 + {"listname": "name_mock_2", "description": "descript_2"},
  76 + {"listname": "name_mock_3", "description": "descript_3"},
  77 + ]
  78 + all_lists.return_value = mail_lists
  79 +
  80 + my_mail_lists = [
  81 + "name_mock_1",
  82 + "name_mock_3",
  83 + ]
  84 + mailing_lists.return_value = my_mail_lists
  85 + response = self.client.get("/account/" + user1.username +
  86 + "/subscriptions")
  87 + self.assertEqual(response.status_code, self.OK)
  88 + mailresponse = response.context_data['membership'][user1.email]
  89 + mailresponse = map(lambda x: x[-1], mailresponse)
  90 + expected_value = [True, False, True]
  91 + self.assertEqual(mailresponse, expected_value)
... ...
colab/settings.py
... ... @@ -131,7 +131,7 @@ ATTACHMENTS_FOLDER_PATH = '/mnt/trac/attachments/'
131 131 # the indexes
132 132  
133 133 ORDERING_DATA = {
134   - 'latest': {
  134 + 'latest': {
135 135 'name': _(u'Recent activity'),
136 136 'fields': ('-modified', '-created'),
137 137 },
... ...
tests/run.py
... ... @@ -16,23 +16,34 @@ import coverage
16 16  
17 17 from django.conf import settings
18 18 from django.test.utils import get_runner
  19 +import colab.settings
19 20  
20 21  
21   -def runtests():
  22 +def runtests(test_suites=[]):
22 23 if django.VERSION >= (1, 7, 0):
23 24 django.setup()
24 25  
25 26 test_runner = get_runner(settings)
26   - failures = test_runner(interactive=False, failfast=False).run_tests([])
  27 + failures = test_runner(interactive=False, failfast=False).run_tests(
  28 + test_suites)
27 29 sys.exit(failures)
28 30  
29 31  
30   -def run_with_coverage():
  32 +def run_with_coverage(test_suites=[]):
31 33 if os.path.exists('.coverage'):
32 34 os.remove('.coverage')
33 35 coverage.process_startup()
34   - runtests()
  36 + runtests(test_suites)
35 37  
36 38  
37 39 if __name__ == '__main__':
38   - run_with_coverage()
  40 + all_valid_apps = True
  41 +
  42 + for arg in sys.argv[1:]:
  43 + if arg not in colab.settings.INSTALLED_APPS:
  44 + print arg + " App not found"
  45 + print "Try colab." + arg
  46 + all_valid_apps = False
  47 +
  48 + if all_valid_apps:
  49 + run_with_coverage(sys.argv[1:])
... ...