tests.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. import os
  4. import ldapdb
  5. from datetime import date
  6. from cStringIO import StringIO
  7. from dateutil.relativedelta import relativedelta
  8. from freezegun import freeze_time
  9. import unittest
  10. from django import db
  11. from django.conf import settings
  12. from django.test import TestCase, Client
  13. from django.core import mail, management
  14. from django.core.exceptions import ValidationError
  15. from coin.members.models import Member, MembershipFee, LdapUser
  16. from coin.validation import chatroom_url_validator
  17. @unittest.skipIf(not settings.LDAP_ACTIVATE, "LDAP disabled")
  18. class LDAPMemberTests(TestCase):
  19. def test_when_creating_member_a_ldapuser_is_also_created_with_same_data(self):
  20. """
  21. Test que lors de la création d'un nouveau membre, une entrée
  22. correspondante est bien créée dans le LDAP et qu'elle contient
  23. les mêmes données.
  24. Cela concerne le nom et le prénom
  25. """
  26. #~ Créé un membre
  27. first_name = 'Gérard'
  28. last_name = 'Majax'
  29. username = MemberTestsUtils.get_random_username()
  30. member = Member(first_name=first_name,
  31. last_name=last_name,
  32. username=username)
  33. member.save()
  34. #~ Récupère l'utilisateur LDAP et fait les tests
  35. ldap_user = LdapUser.objects.get(pk=username)
  36. self.assertEqual(ldap_user.first_name, first_name)
  37. self.assertEqual(ldap_user.last_name, last_name)
  38. self.assertEqual(ldap_user.pk, username)
  39. member.delete()
  40. def test_when_modifiying_member_corresponding_ldap_user_is_also_modified_with_same_data(self):
  41. """
  42. Test que lorsque l'on modifie un membre, l'utilisateur LDAP
  43. correspondant est bien modifié
  44. Cela concerne le no met le prénom
  45. """
  46. #~ Créé un membre
  47. first_name = 'Ronald'
  48. last_name = 'Mac Donald'
  49. username = MemberTestsUtils.get_random_username()
  50. member = Member(first_name=first_name,
  51. last_name=last_name, username=username)
  52. member.save()
  53. #~ Le modifie
  54. new_first_name = 'José'
  55. new_last_name = 'Bové'
  56. member.first_name = new_first_name
  57. member.last_name = new_last_name
  58. member.save()
  59. #~ Récupère l'utilisateur LDAP et fait les tests
  60. ldap_user = LdapUser.objects.get(pk=username)
  61. self.assertEqual(ldap_user.first_name, new_first_name)
  62. self.assertEqual(ldap_user.last_name, new_last_name)
  63. member.delete()
  64. # def test_when_creating_member_corresponding_ldap_user_is_in_coin_ldap_group(self):
  65. # """
  66. # Test que l'utilisateur Ldap fraichement créé est bien dans le group "coin"
  67. # Et que lors de la supression d'un membre, l'utilisateur LDAP correspondant
  68. # est bien retiré du groupe.
  69. # """
  70. # ~ Créé un membre
  71. # username = MemberTestsUtils.get_random_username()
  72. # member = Member(first_name='Canard',
  73. # last_name='WC', username=username)
  74. # member.save()
  75. # ~ Récupère le group "coin" et test que l'utilisateur y est présent
  76. # ldap_group = LdapGroup.objects.get(pk="coin")
  77. # self.assertEqual(username in ldap_group.members, True)
  78. # ~ Supprime l'utilisateur
  79. # member.delete()
  80. # ~ Récupère le group "coin" et test que l'utilisateur n'y est plus
  81. # ldap_group = LdapGroup.objects.get(pk="coin")
  82. # self.assertEqual(username in ldap_group.members, False)
  83. # LdapUser.objects.get(pk=username).delete()
  84. def test_change_password_and_auth(self):
  85. """
  86. Test que la fonction change_password de member fonctionne et permet
  87. l'authentification avec le nouveau mot de passe
  88. """
  89. username = MemberTestsUtils.get_random_username()
  90. password = "1234"
  91. #~ Créé un nouveau membre
  92. member = Member(first_name='Passe-partout',
  93. last_name='Du fort Boyard', username=username)
  94. member.save()
  95. #~ Récupère l'utilisateur LDAP
  96. ldap_user = LdapUser.objects.get(pk=username)
  97. #~ Change son mot de passe
  98. member.set_password(password)
  99. member.save()
  100. #~ Test l'authentification django
  101. c = Client()
  102. self.assertEqual(c.login(username=username, password=password), True)
  103. # Test l'authentification ldap
  104. import ldap
  105. ldap_conn_settings = db.connections['ldap'].settings_dict
  106. l = ldap.initialize(ldap_conn_settings['NAME'])
  107. options = ldap_conn_settings.get('CONNECTION_OPTIONS', {})
  108. for opt, value in options.items():
  109. l.set_option(opt, value)
  110. if ldap_conn_settings.get('TLS', False):
  111. l.start_tls_s()
  112. # Raise "Invalid credentials" exception if auth fail
  113. l.simple_bind_s(ldap_conn_settings['USER'],
  114. ldap_conn_settings['PASSWORD'])
  115. l.unbind_s()
  116. member.delete()
  117. def test_when_creating_member_ldap_display_name_is_well_defined(self):
  118. """
  119. Lors de la création d'un membre, le champ "display_name" du LDAP est
  120. prenom + nom
  121. """
  122. first_name = 'Gérard'
  123. last_name = 'Majax'
  124. username = MemberTestsUtils.get_random_username()
  125. member = Member(first_name=first_name,
  126. last_name=last_name, username=username)
  127. member.save()
  128. #~ Récupère l'utilisateur LDAP
  129. ldap_user = LdapUser.objects.get(pk=username)
  130. self.assertEqual(ldap_user.display_name, '%s %s' %
  131. (first_name, last_name))
  132. member.delete()
  133. def test_when_saving_member_and_ldap_fail_dont_save(self):
  134. """
  135. Test que lors de la sauvegarde d'un membre et que la sauvegarde en LDAP
  136. échoue (ici mauvais mot de passe), rien n'est sauvegardé en base
  137. """
  138. # Fait échouer le LDAP en définissant un mauvais mot de passe
  139. for dbconnection in db.connections.all():
  140. if (type(dbconnection) is
  141. ldapdb.backends.ldap.base.DatabaseWrapper):
  142. dbconnection.settings_dict[
  143. 'PREVIOUSPASSWORD'] = dbconnection.settings_dict['PASSWORD']
  144. dbconnection.settings_dict['PASSWORD'] = 'wrong password test'
  145. # Créé un membre
  146. first_name = 'Du'
  147. last_name = 'Pont'
  148. username = MemberTestsUtils.get_random_username()
  149. member = Member(first_name=first_name,
  150. last_name=last_name, username=username)
  151. # Le sauvegarde en base de donnée
  152. # Le save devrait renvoyer une exception parceque le LDAP échoue
  153. self.assertRaises(Exception, member.save)
  154. # On s'assure, malgré l'exception, que le membre n'est pas en base
  155. with self.assertRaises(Member.DoesNotExist):
  156. Member.objects.get(username=username)
  157. # Restaure le mot de passe pour les tests suivants
  158. for dbconnection in db.connections.all():
  159. if (type(dbconnection) is
  160. ldapdb.backends.ldap.base.DatabaseWrapper):
  161. dbconnection.settings_dict[
  162. 'PASSWORD'] = dbconnection.settings_dict['PREVIOUSPASSWORD']
  163. # def test_when_user_login_member_user_field_is_updated(self):
  164. # """
  165. # Test que lorqu'un utilisateur se connect, le champ user du membre
  166. # correspondant est mis à jour convenablement
  167. # """
  168. # Créé un membre
  169. # first_name = 'Du'
  170. # last_name = 'Pond'
  171. # password = '1234'
  172. # username = MemberTestsUtils.get_random_username()
  173. # member = Member(first_name=first_name,
  174. # last_name=last_name, username=username)
  175. # member.save()
  176. # member.change_password(password)
  177. # Vérifie que user non définit
  178. # self.assertIsNone(member.user)
  179. # Connection
  180. # c = Client()
  181. # self.assertEqual(c.login(username=username, password=password), True)
  182. # Vérifie que user définit
  183. # member = Member.objects.get(username=username)
  184. # self.assertIsNotNone(member.user)
  185. # LdapUser.objects.get(pk=member.username).delete()
  186. class MemberTests(TestCase):
  187. def test_when_creating_member_username_is_well_defined(self):
  188. """
  189. Lors de la création d'un membre, le champ "username", s'il n'est pas
  190. définit doit être généré avec les contraintes suivantes :
  191. premières lettres du prénom + nom le tout en minuscule,
  192. sans caractères accentués et sans espaces.
  193. """
  194. random = os.urandom(4).encode('hex')
  195. first_name = 'Gérard-Étienne'
  196. last_name = 'Majax de la Boétie!B' + random
  197. control = 'gemajaxdelaboetieb' + random
  198. control = control[:30]
  199. member = Member(first_name=first_name, last_name=last_name)
  200. member.save()
  201. self.assertEqual(member.username, control)
  202. member.delete()
  203. def test_when_creating_member_with_username_already_exists_username_is_incr(self):
  204. """
  205. Lors de la création d'un membre, test si le username existe déjà,
  206. renvoi avec un incrément à la fin
  207. """
  208. random = os.urandom(4).encode('hex')
  209. member1 = Member(first_name='Hervé', last_name='DUPOND' + random, email='hdupond@coin.org')
  210. member1.save()
  211. self.assertEqual(member1.username, 'hdupond' + random)
  212. member2 = Member(first_name='Henri', last_name='DUPOND' + random, email='hdupond2@coin.org')
  213. member2.save()
  214. self.assertEqual(member2.username, 'hdupond' + random + '2')
  215. member3 = Member(first_name='Hector', last_name='DUPOND' + random, email='hdupond3@coin.org')
  216. member3.save()
  217. self.assertEqual(member3.username, 'hdupond' + random + '3')
  218. member1.delete()
  219. member2.delete()
  220. member3.delete()
  221. def test_when_creating_legal_entity_organization_name_is_used_for_username(self):
  222. """
  223. Lors de la créatio d'une entreprise, son nom doit être utilisée lors de
  224. la détermination automatique du username
  225. """
  226. random = os.urandom(4).encode('hex')
  227. member = Member(type='legal_entity', organization_name='ILLYSE' + random, email='illyse@coin.org')
  228. member.save()
  229. self.assertEqual(member.username, 'illyse' + random)
  230. member.delete()
  231. def test_when_creating_member_with_nickname_it_is_used_for_username(self):
  232. """
  233. Lors de la création d'une personne, qui a un pseudo, celui-ci est utilisé en priorité
  234. """
  235. random = os.urandom(4).encode('hex')
  236. member = Member(first_name='Richard', last_name='Stallman', nickname='rms' + random, email='illyse@coin.org')
  237. member.save()
  238. self.assertEqual(member.username, 'rms' + random)
  239. member.delete()
  240. def test_member_end_date_of_memberhip(self):
  241. """
  242. Test que end_date_of_membership d'un membre envoi bien la date de fin d'adhésion
  243. """
  244. # Créer un membre
  245. first_name = 'Tin'
  246. last_name = 'Tin'
  247. username = MemberTestsUtils.get_random_username()
  248. member = Member(first_name=first_name,
  249. last_name=last_name, username=username)
  250. member.save()
  251. start_date = date.today()
  252. end_date = start_date + relativedelta(years=+1)
  253. # Créé une cotisation
  254. membershipfee = MembershipFee(member=member, amount=20,
  255. start_date=start_date,
  256. end_date=end_date)
  257. membershipfee.save()
  258. self.assertEqual(member.end_date_of_membership(), end_date)
  259. def test_member_is_paid_up(self):
  260. """
  261. Test l'état "a jour de cotisation" d'un adhérent.
  262. """
  263. # Créé un membre
  264. first_name = 'Capitain'
  265. last_name = 'Haddock'
  266. username = MemberTestsUtils.get_random_username()
  267. member = Member(first_name=first_name,
  268. last_name=last_name, username=username)
  269. member.save()
  270. start_date = date.today()
  271. end_date = start_date + relativedelta(years=+1)
  272. # Test qu'un membre sans cotisation n'est pas à jour
  273. self.assertEqual(member.is_paid_up(), False)
  274. # Créé une cotisation passée
  275. membershipfee = MembershipFee(member=member, amount=20,
  276. start_date=date.today() +
  277. relativedelta(years=-1),
  278. end_date=date.today() + relativedelta(days=-10))
  279. membershipfee.save()
  280. # La cotisation s'étant terminée il y a 10 jours, il ne devrait pas
  281. # être à jour de cotistion
  282. self.assertEqual(member.is_paid_up(), False)
  283. # Créé une cotisation actuelle
  284. membershipfee = MembershipFee(member=member, amount=20,
  285. start_date=date.today() +
  286. relativedelta(days=-10),
  287. end_date=date.today() + relativedelta(days=+10))
  288. membershipfee.save()
  289. # La cotisation se terminant dans 10 jour, il devrait être à jour
  290. # de cotisation
  291. self.assertEqual(member.is_paid_up(), True)
  292. @freeze_time('2016-01-01')
  293. def test_adding_running_fee_set_membership_status(self):
  294. member = Member.objects.create(
  295. first_name='a', last_name='b', username='c',
  296. status=Member.MEMBER_STATUS_PENDING)
  297. # Créé une cotisation passée
  298. MembershipFee.objects.create(
  299. member=member, amount=20,
  300. start_date=date(2015, 12, 12),
  301. end_date=date(2016, 12, 12))
  302. member = Member.objects.get(pk=member.pk)
  303. self.assertEqual(member.status, member.MEMBER_STATUS_MEMBER)
  304. def test_member_cant_be_created_without_names(self):
  305. """
  306. Test qu'un membre ne peut pas être créé sans "noms"
  307. (prenom, nom) ou pseudo ou nom d'organization
  308. """
  309. member = Member(username='blop')
  310. with self.assertRaises(Exception):
  311. member.full_clean()
  312. member.save()
  313. with self.assertRaises(Exception):
  314. member.full_clean()
  315. member.save()
  316. class MemberAdminTests(TestCase):
  317. def setUp(self):
  318. #~ Client web
  319. self.client = Client()
  320. #~ Créé un superuser
  321. self.admin_user_password = '1234'
  322. self.admin_user = Member.objects.create_superuser(
  323. username='test_admin_user',
  324. first_name='test',
  325. last_name='Admin user',
  326. email='i@mail.com',
  327. password=self.admin_user_password)
  328. #~ Connection
  329. self.assertEqual(self.client.login(
  330. username=self.admin_user.username, password=self.admin_user_password), True)
  331. def tearDown(self):
  332. # Supprime le superuser
  333. self.admin_user.delete()
  334. def test_cant_change_username_when_editing(self):
  335. """
  336. Vérifie que dans l'admin Django, le champ username n'est pad modifiable
  337. sur une fiche existante
  338. """
  339. #~ Créé un membre
  340. first_name = 'Gérard'
  341. last_name = 'Majax'
  342. username = MemberTestsUtils.get_random_username()
  343. member = Member(first_name=first_name,
  344. last_name=last_name, username=username)
  345. member.save()
  346. edit_page = self.client.get('/admin/members/member/%i/' % member.id)
  347. self.assertNotContains(edit_page,
  348. '''<input id="id_username" />''',
  349. html=True)
  350. member.delete()
  351. class MemberTestCallForMembershipCommand(TestCase):
  352. def setUp(self):
  353. # Créé un membre
  354. self.username = MemberTestsUtils.get_random_username()
  355. self.member = Member(first_name='Richard', last_name='Stallman',
  356. username=self.username)
  357. self.member.save()
  358. def tearDown(self):
  359. # Supprime le membre
  360. self.member.delete()
  361. MembershipFee.objects.all().delete()
  362. def create_membership_fee(self, end_date):
  363. # Créé une cotisation passée se terminant dans un mois
  364. membershipfee = MembershipFee(member=self.member, amount=20,
  365. start_date=end_date + relativedelta(years=-1),
  366. end_date=end_date)
  367. membershipfee.save()
  368. def create_membership_fee(self, end_date):
  369. # Créé une cotisation se terminant à la date indiquée
  370. membershipfee = MembershipFee(member=self.member, amount=20,
  371. start_date=end_date + relativedelta(years=-1),
  372. end_date=end_date)
  373. membershipfee.save()
  374. return membershipfee
  375. def do_test_email_sent(self, expected_emails = 1, reset_date_last_call = True):
  376. # Vide la outbox
  377. mail.outbox = []
  378. # Call command
  379. management.call_command('call_for_membership_fees', stdout=StringIO())
  380. # Test
  381. self.assertEqual(len(mail.outbox), expected_emails)
  382. # Comme on utilise le même membre, on reset la date de dernier envoi
  383. if reset_date_last_call:
  384. self.member.date_last_call_for_membership_fees_email = None
  385. self.member.save()
  386. def do_test_for_a_end_date(self, end_date, expected_emails=1, reset_date_last_call = True):
  387. # Supprimer toutes les cotisations (au cas ou)
  388. MembershipFee.objects.all().delete()
  389. # Créé la cotisation
  390. membershipfee = self.create_membership_fee(end_date)
  391. self.do_test_email_sent(expected_emails, reset_date_last_call)
  392. membershipfee.delete()
  393. def test_call_email_sent_at_expected_dates(self):
  394. # 1 mois avant la fin, à la fin et chaque mois après la fin pendant 3 mois
  395. self.do_test_for_a_end_date(date.today() + relativedelta(months=+1))
  396. self.do_test_for_a_end_date(date.today())
  397. self.do_test_for_a_end_date(date.today() + relativedelta(months=-1))
  398. self.do_test_for_a_end_date(date.today() + relativedelta(months=-2))
  399. self.do_test_for_a_end_date(date.today() + relativedelta(months=-3))
  400. def test_call_email_not_sent_if_active_membership_fee(self):
  401. # Créé une cotisation se terminant dans un mois
  402. membershipfee = self.create_membership_fee(date.today() + relativedelta(months=+1))
  403. # Un mail devrait être envoyé (ne pas vider date_last_call_for_membership_fees_email)
  404. self.do_test_email_sent(1, False)
  405. # Créé une cotisation enchainant et se terminant dans un an
  406. membershipfee = self.create_membership_fee(date.today() + relativedelta(months=+1, years=+1))
  407. # Pas de mail envoyé
  408. self.do_test_email_sent(0)
  409. def test_date_last_call_for_membership_fees_email(self):
  410. # Créé une cotisation se terminant dans un mois
  411. membershipfee = self.create_membership_fee(date.today() + relativedelta(months=+1))
  412. # Un mail envoyé (ne pas vider date_last_call_for_membership_fees_email)
  413. self.do_test_email_sent(1, False)
  414. # Tente un deuxième envoi, qui devrait être à 0
  415. self.do_test_email_sent(0)
  416. class MemberTestsUtils(object):
  417. @staticmethod
  418. def get_random_username():
  419. """
  420. Renvoi une clé aléatoire pour un utilisateur LDAP
  421. """
  422. return 'coin_test_' + os.urandom(8).encode('hex')
  423. class TestValidators(TestCase):
  424. def test_valid_chatroom(self):
  425. chatroom_url_validator('irc://irc.example.com/#chan')
  426. with self.assertRaises(ValidationError):
  427. chatroom_url_validator('http://#faimaison@irc.geeknode.org')
  428. class MembershipFeeTests(TestCase):
  429. def test_mandatory_start_date(self):
  430. member = Member(first_name='foo', last_name='foo', password='foo', email='foo')
  431. member.save()
  432. # If there is no start_date clean_fields() should raise an
  433. # error but not clean().
  434. membershipfee = MembershipFee(member=member)
  435. self.assertRaises(ValidationError, membershipfee.clean_fields)
  436. self.assertIsNone(membershipfee.clean())
  437. # If there is a start_date, everything is fine.
  438. membershipfee = MembershipFee(member=member, start_date=date.today())
  439. self.assertIsNone(membershipfee.clean_fields())
  440. self.assertIsNone(membershipfee.clean())
  441. member.delete()