123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356 |
- import datetime
- import json
- import warnings
- from django.core import mail
- from django.core.signing import BadSignature
- from django.contrib.auth.models import User
- from django.test import TestCase, Client, override_settings
- from freezegun import freeze_time
- import pytz
- from contribmap.models import Contrib
- from contribmap.forms import PublicContribForm
- from contribmap.tokens import ContribTokenManager, URLTokenManager
- class APITestClient(Client):
- def json_get(self, *args, **kwargs):
- """ Annotate the response with a .data containing parsed JSON
- """
- response = super().get(*args, **kwargs)
- response.data = json.loads(response.content.decode('utf-8'))
- return response
- class APITestCase(TestCase):
- def setUp(self):
- super().setUp()
- self.client = APITestClient()
- class TestContrib(TestCase):
- def test_comma_separatedcharfield(self):
- co = Contrib(name='foo', orientations=['SO', 'NE'],
- contrib_type=Contrib.CONTRIB_CONNECT,
- latitude=0.5, longitude=0.5,
- )
- co.save()
- self.assertEqual(
- Contrib.objects.get(name='foo').orientations,
- ['SO', 'NE'])
- co.orientations = ['S']
- co.save()
- class TestContribPrivacy(TestCase):
- def test_always_private_field(self):
- c = Contrib.objects.create(
- name='John',
- phone='010101010101',
- contrib_type=Contrib.CONTRIB_CONNECT,
- latitude=0.5,
- longitude=0.5,
- )
- self.assertEqual(c.get_public_field('phone'), None)
- def test_public_field(self):
- c = Contrib.objects.create(
- name='John',
- phone='010101010101',
- contrib_type=Contrib.CONTRIB_CONNECT,
- privacy_name=True,
- latitude=0.5,
- longitude=0.5,
- )
- self.assertEqual(c.get_public_field('name'), 'John')
- def test_public_callable_field(self):
- c = Contrib.objects.create(
- name='John',
- phone='010101010101',
- orientations=['N'],
- contrib_type=Contrib.CONTRIB_CONNECT,
- privacy_name=True,
- latitude=0.5,
- longitude=0.5,
- )
- self.assertEqual(c.get_public_field('angles'), [[-23, 22]])
- def test_private_field(self):
- c = Contrib.objects.create(
- name='John',
- phone='010101010101',
- contrib_type=Contrib.CONTRIB_CONNECT,
- latitude=0.5,
- longitude=0.5,
- )
- self.assertEqual(c.privacy_name, False)
- self.assertEqual(c.get_public_field('name'), None)
- class TestViews(APITestCase):
- def mk_contrib_post_data(self, *args, **kwargs):
- post_data = {
- 'roof': True,
- 'privacy_place_details': True,
- 'privacy_coordinates': True,
- 'phone': '0202020202',
- 'orientations': ('N', 'NO', 'O', 'SO', 'S', 'SE', 'E', 'NE'),
- 'orientation': 'all',
- 'name': 'JohnCleese',
- 'longitude': -1.553621,
- 'latitude': 47.218371,
- 'floor_total': '2',
- 'floor': 1,
- 'email': 'coucou@example.com',
- 'contrib_type': 'connect',
- 'connect_local': 'on',
- }
- post_data.update(kwargs)
- return post_data
- def test_public_json(self):
- response = self.client.json_get('/map/public.json')
- self.assertEqual(response.status_code, 200)
- self.assertEqual(len(response.data['features']), 0)
- Contrib.objects.create(
- name='John',
- phone='010101010101',
- contrib_type=Contrib.CONTRIB_CONNECT,
- privacy_coordinates=True,
- latitude=0.5,
- longitude=0.5,
- )
- response = self.client.json_get('/map/public.json')
- self.assertEqual(response.status_code, 200)
- self.assertEqual(len(response.data['features']), 1)
- def test_private_json(self):
- self.client.force_login(
- User.objects.create(username='foo', is_staff=False))
- response = self.client.get('/map/private.json')
- self.assertEqual(response.status_code, 403)
- def test_private_json_staff(self):
- self.client.force_login(
- User.objects.create(username='foo', is_staff=True))
- response = self.client.get('/map/private.json')
- self.assertEqual(response.status_code, 200)
- @override_settings(NOTIFICATION_EMAILS=['foo@example.com'])
- def test_add_contrib_sends_moderator_email(self):
- post_data = self.mk_contrib_post_data({'name': 'JohnCleese'})
- del post_data['email']
- response = self.client.post('/map/contribute', post_data)
- self.assertEqual(response.status_code, 302)
- self.assertEqual(len(mail.outbox), 1)
- self.assertIn('JohnCleese', mail.outbox[0].subject)
- self.assertIn('JohnCleese', mail.outbox[0].body)
- self.assertEqual(mail.outbox[0].recipients(), ['foo@example.com'])
- def test_add_contrib_sends_no_author_email(self):
- # Send no email if author did not mentioned an email
- post_data = self.mk_contrib_post_data()
- del post_data['email']
- response = self.client.post('/map/contribute', post_data)
- self.assertEqual(response.status_code, 302)
- self.assertEqual(len(mail.outbox), 0)
- def test_add_contrib_sends_author_email(self):
- # Send no email if author did not mentioned an email
- response = self.client.post(
- '/map/contribute',
- self.mk_contrib_post_data(email='author@example.com'))
- self.assertEqual(response.status_code, 302)
- self.assertEqual(len(mail.outbox), 1)
- class TestManageView(APITestCase):
- def setUp(self):
- self.contrib = Contrib.objects.create(
- name='John',
- phone='010101010101',
- contrib_type=Contrib.CONTRIB_CONNECT,
- privacy_coordinates=True,
- latitude=0.5,
- longitude=0.5,
- )
- self.token = ContribTokenManager().mk_token(self.contrib)
- def test_manage_with_token(self):
- # No token
- response = self.client.get('/map/manage/{}'.format(self.contrib.pk))
- self.assertEqual(response.status_code, 403)
- # Garbage token
- response = self.client.get(
- '/map/manage/{}?token=burp'.format(self.contrib.pk))
- self.assertEqual(response.status_code, 403)
- # Valid token, but for another contrib
- contrib2 = Contrib.objects.create(
- name='John2',
- phone='010101010101',
- contrib_type=Contrib.CONTRIB_CONNECT,
- privacy_coordinates=True,
- latitude=0.5,
- longitude=0.5,
- )
- token2 = ContribTokenManager().mk_token(contrib2)
- response = self.client.get('/map/manage/{}?token={}'.format(
- self.contrib.pk, token2))
- self.assertEqual(response.status_code, 403)
- # Normal legitimate access case
- response = self.client.get(
- '/map/manage/{}?token={}'.format(self.contrib.pk, self.token))
- self.assertEqual(response.status_code, 200)
- # Deleted contrib
- Contrib.objects.all().delete()
- response = self.client.get(
- '/map/manage/{}?token={}'.format(self.contrib.pk, self.token))
- self.assertEqual(response.status_code, 404)
- def test_delete(self):
- response = self.client.post(
- '/map/manage/{}?token={}'.format(self.contrib.pk, self.token),
- {'action': 'delete'})
- self.assertEqual(response.status_code, 302)
- self.assertFalse(Contrib.objects.filter(pk=self.contrib.pk).exists())
- def test_renew(self):
- self.contrib.date = datetime.datetime(2009, 10, 10, tzinfo=pytz.utc)
- self.contrib.expiration_date = datetime.datetime(2010, 10, 10, tzinfo=pytz.utc)
- self.contrib.save()
- with freeze_time('12-12-2100', tz_offset=0):
- response = self.client.post(
- '/map/manage/{}?token={}'.format(self.contrib.pk, self.token),
- {'action': 'renew'})
- self.assertEqual(response.status_code, 200)
- self.contrib = Contrib.objects.get(pk=self.contrib.pk) # refresh
- self.assertEqual(
- self.contrib.expiration_date.date(),
- datetime.date(2101, 12, 12))
- class TestForms(TestCase):
- valid_data = {
- 'roof': True,
- 'privacy_place_details': True,
- 'privacy_coordinates': True,
- 'orientations': ['N'],
- 'orientation': 'all',
- 'name': 'JohnCleese',
- 'longitude': -1.553621,
- 'email': 'foo@example.com',
- 'phone': '0202020202',
- 'latitude': 47.218371,
- 'floor_total': '2',
- 'floor': 1,
- 'contrib_type': 'connect',
- 'connect_local': 'on',
- }
- def test_contact_validation(self):
- no_contact, phone_contact, email_contact, both_contact = [
- self.valid_data.copy() for i in range(4)]
- del phone_contact['email']
- del email_contact['phone']
- del no_contact['phone']
- del no_contact['email']
- both_contact.update(phone_contact)
- both_contact.update(email_contact)
- self.assertFalse(PublicContribForm(no_contact).is_valid())
- self.assertTrue(PublicContribForm(phone_contact).is_valid())
- self.assertTrue(PublicContribForm(email_contact).is_valid())
- self.assertTrue(PublicContribForm(both_contact).is_valid())
- def test_floors_validation(self):
- invalid_floors = self.valid_data.copy()
- invalid_floors['floor'] = 2
- invalid_floors['floor_total'] = 1
- self.assertFalse(PublicContribForm(invalid_floors).is_valid())
- self.assertTrue(PublicContribForm(self.valid_data).is_valid())
- invalid_floors['floor'] = None
- invalid_floors['floor_total'] = None
- self.assertTrue(PublicContribForm(invalid_floors).is_valid())
- def test_share_fields_validation(self):
- data = self.valid_data.copy()
- data['contrib_type'] = 'share'
- self.assertFalse(PublicContribForm(data).is_valid())
- data['access_type'] = 'cable'
- self.assertTrue(PublicContribForm(data).is_valid())
- @override_settings(NOTIFICATION_EMAILS=['foo@example.com'])
- def test_add_contrib_like_a_robot(self):
- robot_data = self.valid_data.copy()
- robot_data['human_field'] = 'should contain no value'
- response = self.client.post('/map/contribute', robot_data)
- self.assertEqual(response.status_code, 403)
- self.assertEqual(len(mail.outbox), 0)
- class TestDataImport(TestCase):
- fixtures = ['bottle_data.yaml']
- @classmethod
- def setUpClass(cls, *args, **kwargs):
- # Silence the warnings about naive datetimes contained in the yaml.
- with warnings.catch_warnings(): # Scope warn catch to this block
- warnings.simplefilter('ignore', RuntimeWarning)
- return super().setUpClass(*args, **kwargs)
- def test_re_save(self):
- for contrib in Contrib.objects.all():
- contrib.full_clean()
- contrib.save()
- class URLTokenManagerTests(TestCase):
- def test_sign_unsign_ok(self):
- input_data = {'foo': 12}
- at = URLTokenManager().sign(input_data)
- output_data = URLTokenManager().unsign(at)
- self.assertEqual(input_data, output_data)
- def test_sign_unsign_wrong_sig(self):
- with self.assertRaises(BadSignature):
- URLTokenManager().unsign(
- b'eyJmb28iOiAxfTpvUFZ1Q3FsSldtQ2htMXJBMmx5VFV0ZWxDLWM')
- class ContribTokenManagerTests(TestCase):
- def test_sign_unsign_ok(self):
- Contrib.objects.create(
- name='John2',
- phone='010101020101',
- contrib_type=Contrib.CONTRIB_CONNECT,
- privacy_coordinates=True,
- latitude=0.1,
- longitude=0.12,
- )
- contrib = Contrib.objects.all().first()
- manager = ContribTokenManager()
- token = manager.mk_token(contrib)
- self.assertEqual(
- manager.get_instance_if_allowed(token, contrib.pk),
- contrib)
|