Projet

Général

Profil

0001-sms-filter-authorized-numbers-39650.patch

Nicolas Roche, 02 février 2021 14:02

Télécharger (26,1 ko)

Voir les différences:

Subject: [PATCH] sms: filter authorized numbers (#39650)

 .../migrations/0010_auto_20210202_1304.py     |  26 ++++
 .../migrations/0009_auto_20210202_1304.py     |  26 ++++
 .../migrations/0009_auto_20210202_1304.py     |  26 ++++
 .../ovh/migrations/0013_auto_20210202_1304.py |  31 +++++
 .../migrations/0009_auto_20210202_1304.py     |  26 ++++
 .../migrations/0002_auto_20210202_1304.py     |  26 ++++
 passerelle/sms/forms.py                       |  31 +++++
 passerelle/sms/models.py                      |  69 +++++++++-
 passerelle/sms/views.py                       |   1 +
 tests/test_sms.py                             | 122 +++++++++++++++++-
 10 files changed, 382 insertions(+), 2 deletions(-)
 create mode 100644 passerelle/apps/choosit/migrations/0010_auto_20210202_1304.py
 create mode 100644 passerelle/apps/mobyt/migrations/0009_auto_20210202_1304.py
 create mode 100644 passerelle/apps/orange/migrations/0009_auto_20210202_1304.py
 create mode 100644 passerelle/apps/ovh/migrations/0013_auto_20210202_1304.py
 create mode 100644 passerelle/apps/oxyd/migrations/0009_auto_20210202_1304.py
 create mode 100644 passerelle/apps/twilio/migrations/0002_auto_20210202_1304.py
passerelle/apps/choosit/migrations/0010_auto_20210202_1304.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.18 on 2021-02-02 12:04
3
from __future__ import unicode_literals
4

  
5
import django.contrib.postgres.fields
6
from django.db import migrations, models
7

  
8

  
9
class Migration(migrations.Migration):
10

  
11
    dependencies = [
12
        ('choosit', '0009_choositsmsgateway_max_message_length'),
13
    ]
14

  
15
    operations = [
16
        migrations.AddField(
17
            model_name='choositsmsgateway',
18
            name='allow_premium_rate',
19
            field=models.BooleanField(default=False, help_text='This option is only applyed to France mainland', verbose_name='Allow premium rate numbers'),
20
        ),
21
        migrations.AddField(
22
            model_name='choositsmsgateway',
23
            name='authorized',
24
            field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('fr-metro', 'France mainland (+33 [67])'), ('fr-domtom', 'France DOM/TOM (+262, etc.)'), ('be', 'Belgian (+32 4[5-9]) '), ('all', 'All')], max_length=32, null=True), default=['all'], size=None, verbose_name='Authorized Countries'),
25
        ),
26
    ]
passerelle/apps/mobyt/migrations/0009_auto_20210202_1304.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.18 on 2021-02-02 12:04
3
from __future__ import unicode_literals
4

  
5
import django.contrib.postgres.fields
6
from django.db import migrations, models
7

  
8

  
9
class Migration(migrations.Migration):
10

  
11
    dependencies = [
12
        ('mobyt', '0008_auto_20200310_1539'),
13
    ]
14

  
15
    operations = [
16
        migrations.AddField(
17
            model_name='mobytsmsgateway',
18
            name='allow_premium_rate',
19
            field=models.BooleanField(default=False, help_text='This option is only applyed to France mainland', verbose_name='Allow premium rate numbers'),
20
        ),
21
        migrations.AddField(
22
            model_name='mobytsmsgateway',
23
            name='authorized',
24
            field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('fr-metro', 'France mainland (+33 [67])'), ('fr-domtom', 'France DOM/TOM (+262, etc.)'), ('be', 'Belgian (+32 4[5-9]) '), ('all', 'All')], max_length=32, null=True), default=['all'], size=None, verbose_name='Authorized Countries'),
25
        ),
26
    ]
passerelle/apps/orange/migrations/0009_auto_20210202_1304.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.18 on 2021-02-02 12:04
3
from __future__ import unicode_literals
4

  
5
import django.contrib.postgres.fields
6
from django.db import migrations, models
7

  
8

  
9
class Migration(migrations.Migration):
10

  
11
    dependencies = [
12
        ('orange', '0008_auto_20200412_1240'),
13
    ]
14

  
15
    operations = [
16
        migrations.AddField(
17
            model_name='orangesmsgateway',
18
            name='allow_premium_rate',
19
            field=models.BooleanField(default=False, help_text='This option is only applyed to France mainland', verbose_name='Allow premium rate numbers'),
20
        ),
21
        migrations.AddField(
22
            model_name='orangesmsgateway',
23
            name='authorized',
24
            field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('fr-metro', 'France mainland (+33 [67])'), ('fr-domtom', 'France DOM/TOM (+262, etc.)'), ('be', 'Belgian (+32 4[5-9]) '), ('all', 'All')], max_length=32, null=True), default=['all'], size=None, verbose_name='Authorized Countries'),
25
        ),
26
    ]
passerelle/apps/ovh/migrations/0013_auto_20210202_1304.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.18 on 2021-02-02 12:04
3
from __future__ import unicode_literals
4

  
5
import django.contrib.postgres.fields
6
from django.db import migrations, models
7

  
8

  
9
class Migration(migrations.Migration):
10

  
11
    dependencies = [
12
        ('ovh', '0012_auto_20201027_1121'),
13
    ]
14

  
15
    operations = [
16
        migrations.AddField(
17
            model_name='ovhsmsgateway',
18
            name='allow_premium_rate',
19
            field=models.BooleanField(default=False, help_text='This option is only applyed to France mainland', verbose_name='Allow premium rate numbers'),
20
        ),
21
        migrations.AddField(
22
            model_name='ovhsmsgateway',
23
            name='authorized',
24
            field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('fr-metro', 'France mainland (+33 [67])'), ('fr-domtom', 'France DOM/TOM (+262, etc.)'), ('be', 'Belgian (+32 4[5-9]) '), ('all', 'All')], max_length=32, null=True), default=['all'], size=None, verbose_name='Authorized Countries'),
25
        ),
26
        migrations.AlterField(
27
            model_name='ovhsmsgateway',
28
            name='alert_emails',
29
            field=django.contrib.postgres.fields.ArrayField(base_field=models.EmailField(blank=True, max_length=254), blank=True, null=True, size=None, verbose_name='Email addresses list to send credit alerts to, separated by comma'),
30
        ),
31
    ]
passerelle/apps/oxyd/migrations/0009_auto_20210202_1304.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.18 on 2021-02-02 12:04
3
from __future__ import unicode_literals
4

  
5
import django.contrib.postgres.fields
6
from django.db import migrations, models
7

  
8

  
9
class Migration(migrations.Migration):
10

  
11
    dependencies = [
12
        ('oxyd', '0008_oxydsmsgateway_max_message_length'),
13
    ]
14

  
15
    operations = [
16
        migrations.AddField(
17
            model_name='oxydsmsgateway',
18
            name='allow_premium_rate',
19
            field=models.BooleanField(default=False, help_text='This option is only applyed to France mainland', verbose_name='Allow premium rate numbers'),
20
        ),
21
        migrations.AddField(
22
            model_name='oxydsmsgateway',
23
            name='authorized',
24
            field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('fr-metro', 'France mainland (+33 [67])'), ('fr-domtom', 'France DOM/TOM (+262, etc.)'), ('be', 'Belgian (+32 4[5-9]) '), ('all', 'All')], max_length=32, null=True), default=['all'], size=None, verbose_name='Authorized Countries'),
25
        ),
26
    ]
passerelle/apps/twilio/migrations/0002_auto_20210202_1304.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.18 on 2021-02-02 12:04
3
from __future__ import unicode_literals
4

  
5
import django.contrib.postgres.fields
6
from django.db import migrations, models
7

  
8

  
9
class Migration(migrations.Migration):
10

  
11
    dependencies = [
12
        ('twilio', '0001_initial'),
13
    ]
14

  
15
    operations = [
16
        migrations.AddField(
17
            model_name='twiliosmsgateway',
18
            name='allow_premium_rate',
19
            field=models.BooleanField(default=False, help_text='This option is only applyed to France mainland', verbose_name='Allow premium rate numbers'),
20
        ),
21
        migrations.AddField(
22
            model_name='twiliosmsgateway',
23
            name='authorized',
24
            field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('fr-metro', 'France mainland (+33 [67])'), ('fr-domtom', 'France DOM/TOM (+262, etc.)'), ('be', 'Belgian (+32 4[5-9]) '), ('all', 'All')], max_length=32, null=True), default=['all'], size=None, verbose_name='Authorized Countries'),
25
        ),
26
    ]
passerelle/sms/forms.py
1
# passerelle - uniform access to multiple data sources and services
2
# Copyright (C) 2020 Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by 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/>.
1 16
from django import forms
2 17
from django.utils.translation import ugettext_lazy as _
3 18

  
19
from passerelle.forms import GenericConnectorForm
20

  
21

  
4 22
class SmsTestSendForm(forms.Form):
5 23
    number = forms.CharField(label=_('To'), max_length=12)
6 24
    sender = forms.CharField(label=_('From'), max_length=12)
7 25
    message = forms.CharField(label=_('Message'), max_length=128)
26

  
27

  
28
class SMSConnectorForm(GenericConnectorForm):
29
    def __init__(self, *args, **kwargs):
30
        from passerelle.sms.models import SMSResource
31

  
32
        super(SMSConnectorForm, self).__init__(*args, **kwargs)
33
        self.fields['authorized'] = forms.MultipleChoiceField(
34
            choices=SMSResource.AUTHORIZED,
35
            widget=forms.CheckboxSelectMultiple,
36
            initial=[SMSResource.ALL],
37
            label=_('Authorized Countries'),
38
        )
passerelle/sms/models.py
11 11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 12
# GNU Affero General Public License for more details.
13 13
#
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16
import logging
17 17
import re
18 18

  
19
from django.contrib.postgres.fields import ArrayField
19 20
from django.db import models
20 21
from django.utils import six
21 22
from django.utils.module_loading import import_string
22 23
from django.utils.translation import ugettext_lazy as _
23 24

  
24 25
from passerelle.base.models import BaseResource
25 26
from passerelle.compat import json_loads
27
from passerelle.sms.forms import SMSConnectorForm
26 28
from passerelle.utils.api import endpoint
27 29
from passerelle.utils.jsonresponse import APIError
28 30

  
31

  
29 32
SEND_SCHEMA = {
30 33
    '$schema': 'http://json-schema.org/draft-04/schema#',
31 34
    "type": "object",
32 35
    'required': ['message', 'from', 'to'],
33 36
    'properties': {
34 37
        'message': {
35 38
            'description': 'String message',
36 39
            'type': 'string',
......
47 50
                'pattern': r'^\+?[-.\s/\d]+$'
48 51
            },
49 52
        },
50 53
    }
51 54
}
52 55

  
53 56

  
54 57
class SMSResource(BaseResource):
58
    manager_form_base_class = SMSConnectorForm
55 59
    category = _('SMS Providers')
56 60
    documentation_url = 'https://doc-publik.entrouvert.com/admin-fonctionnel/les-tutos/configuration-envoi-sms/'
57 61

  
58 62
    _can_send_messages_description = _('Sending messages is limited to the following API users:')
59 63

  
60 64
    default_country_code = models.CharField(verbose_name=_('Default country code'), max_length=3,
61 65
                                            default=u'33')
62 66
    default_trunk_prefix = models.CharField(verbose_name=_('Default trunk prefix'), max_length=2,
63 67
                                            default=u'0')  # Yeah France first !
64
    # FIXME: add regexp field, to check destination and from format
65 68
    max_message_length = models.IntegerField(_('Maximum message length'), default=160)
66 69

  
67 70
    manager_view_template_name = 'passerelle/manage/messages_service_view.html'
68 71

  
72
    FR_METRO = 'fr-metro'
73
    FR_DOMTOM = 'fr-domtom'
74
    BE_ = 'be'
75
    ALL = 'all'
76
    AUTHORIZED = [
77
        (FR_METRO, _('France mainland (+33 [67])')),
78
        (FR_DOMTOM, _('France DOM/TOM (+262, etc.)')),
79
        (BE_, _('Belgian (+32 4[5-9]) ')),
80
        (ALL, _('All')),
81
    ]
82
    authorized = ArrayField(
83
        models.CharField(max_length=32, null=True, choices=AUTHORIZED),
84
        verbose_name=_('Authorized Countries'),
85
        default=[ALL])
86

  
87
    allow_premium_rate = models.BooleanField(
88
        _('Allow premium rate numbers'), default=False,
89
        help_text=_('This option is only applyed to France mainland')
90
    )
91

  
69 92
    @classmethod
70 93
    def get_management_urls(cls):
71 94
        return import_string('passerelle.sms.urls.management_urlpatterns')
72 95

  
96
    def _get_authorized_display(self):
97
        result = []
98
        for key, value in self.AUTHORIZED:
99
            if key in self.authorized:
100
                result.append(str(value))
101
        return ', '.join(result)
102

  
103
    def __init__(self, *args, **kwargs):
104
        super(SMSResource, self).__init__(*args, **kwargs)
105
        self.get_authorized_display = self._get_authorized_display
106

  
73 107
    def clean_numbers(self, destinations):
74 108
        numbers = []
75 109
        for dest in destinations:
76 110
            # most gateways needs the number prefixed by the country code, this is
77 111
            # really unfortunate.
78 112
            dest = dest.strip()
79 113
            number = ''.join(re.findall('[0-9]', dest))
80 114
            if dest.startswith('+'):
......
85 119
            elif number.startswith(self.default_trunk_prefix):
86 120
                number = '00' + self.default_country_code + number[len(self.default_trunk_prefix):]
87 121
            else:
88 122
                raise APIError('phone number %r is unsupported (no international prefix, '
89 123
                               'no local trunk prefix)' % number)
90 124
            numbers.append(number)
91 125
        return numbers
92 126

  
127
    def authorize_numbers(self, destinations):
128
        foreign_numbers = set()
129
        premium_numbers = set()
130
        premium_regex = re.compile(r'^0033[8]\d{8}$')
131
        regexes = []
132
        if SMSResource.ALL not in self.authorized:
133
            if SMSResource.FR_METRO in self.authorized:
134
                regexes.append(r'^0033[67]\d{8}$')    # France
135
            if SMSResource.FR_DOMTOM in self.authorized:
136
                regexes.append(r'^00262262\d{6}$')    # Réunion, Mayotte, Terres australe/antarctiques
137
                regexes.append(r'^508508\d{6}$')      # Saint-Pierre-et-Miquelon
138
                regexes.append(r'^590590\d{6}$')      # Guadeloupe, Saint-Barthélemy, Saint-Martin
139
                regexes.append(r'^594594\d{6}$')      # Guyane
140
                regexes.append(r'^596596\d{6}$')      # Martinique
141
                regexes.append(r'^00687[67]\d{8}$')   # Nouvelle-Calédonie
142
            if SMSResource.BE_ in self.authorized:
143
                regexes.append(r'^00324[5-9]\d{7}$')  # Belgian
144
        country_regex = re.compile('|'.join(regexes))
145

  
146
        if not self.allow_premium_rate:
147
            premium_numbers = set(dest for dest in destinations if premium_regex.match(dest))
148
        if SMSResource.ALL not in self.authorized:
149
            foreign_numbers = set(dest for dest in destinations if not country_regex.match(dest))
150
        for number in list(premium_numbers):
151
            logging.warning('unauthorized premium rate phone number: %s', number)
152
        for number in list(foreign_numbers - premium_numbers):
153
            logging.warning('unauthorized foreign phone number: %s', number)
154
        authorized_numbers = list(set(destinations) - foreign_numbers - premium_numbers)
155
        if len(authorized_numbers) == 0:
156
            raise APIError('no phone number was authorized: %s' % ', '.join(destinations))
157
        return authorized_numbers
158

  
93 159
    @endpoint(perm='can_send_messages', methods=['post'],
94 160
              description=_('Send a SMS message'),
95 161
              parameters={'nostop': {'description': _('Do not send STOP instruction'), 'example_value': '1'}},
96 162
              post={'request_body': {'schema': {'application/json': SEND_SCHEMA}}})
97 163
    def send(self, request, post_data, nostop=None):
98 164
        post_data['message'] = post_data['message'][:self.max_message_length]
99 165
        post_data['to'] = self.clean_numbers(post_data['to'])
166
        post_data['to'] = self.authorize_numbers(post_data['to'])
100 167
        logging.info('sending SMS to %r from %r', post_data['to'], post_data['from'])
101 168
        stop = nostop is None  # ?nostop in not in query string
102 169
        self.add_job('send_job',
103 170
                     text=post_data['message'], sender=post_data['from'], destinations=post_data['to'],
104 171
                     stop=stop)
105 172
        return {'err': 0}
106 173

  
107 174
    def send_job(self, *args, **kwargs):
passerelle/sms/views.py
24 24

  
25 25
    def form_valid(self, form):
26 26
        number = form.cleaned_data['number']
27 27
        sender = form.cleaned_data['sender']
28 28
        message = form.cleaned_data['message']
29 29
        connector = self.get_object()
30 30
        try:
31 31
            number = connector.clean_numbers([number])[0]
32
            number = connector.authorize_numbers([number])[0]
32 33
            connector.send_msg(
33 34
                text=message, sender=sender, destinations=[number], stop=False)
34 35
        except APIError as exc:
35 36
            messages.error(self.request, _('Sending SMS fails: %s' % exc))
36 37
        else:
37 38
            messages.success(self.request, _('An SMS was just sent'))
38 39
        return super(SmsTestSendView, self).form_valid(form)
tests/test_sms.py
1
# passerelle - uniform access to multiple data sources and services
2
# Copyright (C) 2020 Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by 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/>.
1 16
import isodate
2 17
import json
18
import logging
3 19
import mock
4 20
import pytest
5 21
from requests import RequestException
6 22

  
7 23
from django.conf import settings
8 24
from django.contrib.contenttypes.models import ContentType
9 25
from django.urls import reverse
26
from django.utils.translation import ugettext as _
10 27

  
28
from passerelle.apps.choosit.models import ChoositSMSGateway
11 29
from passerelle.apps.ovh.models import OVHSMSGateway
12
from passerelle.base.models import ApiUser, AccessRight, Job
30
from passerelle.base.models import ApiUser, AccessRight, Job, ResourceLog
13 31
from passerelle.sms.models import SMSResource, SMSLog
14 32
from passerelle.utils.jsonresponse import APIError
15 33

  
16 34
from test_manager import login
17 35

  
18 36
import utils
19 37

  
20 38
pytestmark = pytest.mark.django_db
......
32 50
    connector.save()
33 51
    assert connector.clean_numbers(['+ 33 12']) == ['003312']
34 52
    assert connector.clean_numbers(['0 0 33 12']) == ['003312']
35 53
    assert connector.clean_numbers(['1 12']) == ['003212']
36 54
    with pytest.raises(APIError, match='phone number %r is unsupported' % '0123'):
37 55
        connector.clean_numbers(['0123'])
38 56

  
39 57

  
58
def test_authorize_numbers():
59
    connector = OVHSMSGateway()
60

  
61
    # premium-rate
62
    assert connector.allow_premium_rate == False
63
    number = '0033' + '8' + '12345678'
64
    with pytest.raises(APIError, match='no phone number was authorized: %s' % number):
65
        assert connector.authorize_numbers([number])
66
    connector.allow_premium_rate = True
67
    connector.save()
68

  
69
    # All country
70
    assert connector.authorized == [SMSResource.ALL]
71
    number = '0033' + '1' + '12345678'
72
    assert connector.authorize_numbers([number]) == [number]
73
    connector.authorized = [SMSResource.FR_METRO]
74
    connector.save()
75
    with pytest.raises(APIError, match='no phone number was authorized: %s' % number):
76
        connector.authorize_numbers([number])
77

  
78
    # France
79
    number = '0033' + '6' + '12345678'
80
    assert connector.authorize_numbers([number]) == [number]
81
    connector.authorized = [SMSResource.FR_DOMTOM]
82
    connector.save()
83
    with pytest.raises(APIError, match='no phone number was authorized: %s' % number):
84
        connector.authorize_numbers([number])
85

  
86
    # Dom-Tom
87
    number = '596596' + '123456'
88
    assert connector.authorize_numbers([number]) == [number]
89
    connector.authorized = [SMSResource.BE_]
90
    connector.save()
91
    with pytest.raises(APIError, match='no phone number was authorized: %s' % number):
92
        connector.authorize_numbers([number])
93

  
94
    # Belgian
95
    number = '0032' + '45' + '1234567'
96
    assert connector.authorize_numbers([number]) == [number]
97
    connector.authorized = [SMSResource.FR_METRO]
98
    connector.save()
99
    with pytest.raises(APIError, match='no phone number was authorized: %s' % number):
100
        connector.authorize_numbers([number])
101

  
102
    # Don't raise if filtered destination is not empty
103
    numbers = ['0033' + '6' + '12345678', '0032' + '45' + '1234567']
104
    assert len(connector.authorize_numbers(numbers)) == 1
105

  
106

  
40 107
@pytest.fixture(params=klasses)
41 108
def connector(request, db):
42 109
    klass = request.param
43 110
    kwargs = getattr(klass, 'TEST_DEFAULTS', {}).get('create_kwargs', {})
44 111
    kwargs.update({
45 112
        'title': klass.__name__,
46 113
        'slug': klass.__name__.lower(),
47 114
        'description': klass.__name__,
......
393 460
    body = json.loads(request.body.decode())
394 461
    assert 'accessRules' in body
395 462
    redirect_url = body['redirection'][len('http://testserver'):]
396 463

  
397 464
    resp = app.get(redirect_url).follow()
398 465
    assert 'Successfuly completed connector configuration' in resp.text
399 466
    connector.refresh_from_db()
400 467
    assert connector.consumer_key == 'xyz'
468

  
469

  
470
@pytest.mark.parametrize('connector', [ChoositSMSGateway], indirect=True)
471
def test_manager(admin_user, app, connector):
472
    app = login(app)
473
    path = '/%s/%s/' % (connector.get_connector_slug(), connector.slug)
474
    resp = app.get(path)
475
    assert '33' in [
476
        x.text for x in resp.html.find('div', {'id': 'description'}).find_all('p')
477
        if x.text.startswith(_('Default country code'))][0]
478
    assert _('All') in [
479
        x.text for x in resp.html.find_all('p')
480
        if x.text.startswith(_('Authorized Countries'))][0]
481
    assert _('no') in [
482
        x.text for x in resp.html.find_all('p')
483
        if x.text.startswith(_('Allow premium rate numbers'))][0]
484

  
485
    path = '/manage/%s/%s/edit' % (connector.get_connector_slug(), connector.slug)
486
    resp = app.get(path)
487
    resp.form['authorized'] = []
488
    resp = resp.form.submit()
489
    assert resp.html.find('div', {'class': 'errornotice'}).p.text == \
490
        'There were errors processing your form.'
491
    assert resp.html.find('div', {'class': 'error'}).text.strip() == 'This field is required.'
492
    resp.form['authorized'] = [SMSResource.FR_METRO, SMSResource.FR_DOMTOM]
493
    resp = resp.form.submit()
494
    resp = resp.follow()
495
    assert _('France mainland (+33 [67])') in [
496
        x.text for x in resp.html.find_all('p')
497
        if x.text.startswith(_('Authorized Countries'))][0]
498

  
499
    path = '/%s/%s/send/' % (connector.get_connector_slug(), connector.slug)
500
    payload = {
501
        'message': 'plop',
502
        'from': '+33699999999',
503
        'to': ['+33688888888'],
504
    }
505
    app.post_json(path, params=payload)
506
    with mock.patch.object(type(connector), 'send_msg') as send_function:
507
        send_function.return_value = {}
508
        connector.jobs()
509
    assert SMSLog.objects.count() == 1
510

  
511
    payload['to'][0] = '+33188888888'
512
    SMSLog.objects.all().delete()
513
    app.post_json(path, params=payload)
514
    with mock.patch.object(type(connector), 'send_msg') as send_function:
515
        send_function.return_value = {}
516
        connector.jobs()
517
    assert not SMSLog.objects.count()
518
    assert ResourceLog.objects.filter(levelno=logging.WARNING).count() == 1
519
    assert ResourceLog.objects.filter(levelno=30)[0].extra['exception'] == \
520
        'no phone number was authorized: 0033188888888'
401
-