Projet

Général

Profil

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

Nicolas Roche, 04 février 2021 09:51

Télécharger (27,4 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                      |  87 ++++++++++-
 passerelle/sms/views.py                       |   1 +
 tests/test_sms.py                             | 141 +++++++++++++++++-
 10 files changed, 418 insertions(+), 3 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
        number_regexes = {
129
            'premium_rate': [r'^0033[8]\d{8}$'],
130
            SMSResource.FR_METRO: [r'^0033[67]\d{8}$'],
131
            SMSResource.FR_DOMTOM: [
132
                r'^00262262\d{6}$',   # Réunion, Mayotte, Terres australe/antarctiques
133
                r'^508508\d{6}$',     # Saint-Pierre-et-Miquelon
134
                r'^590590\d{6}$',     # Guadeloupe, Saint-Barthélemy, Saint-Martin
135
                r'^594594\d{6}$',     # Guyane
136
                r'^596596\d{6}$',     # Martinique
137
                r'^00687[67]\d{8}$',  # Nouvelle-Calédonie
138
            ],
139
            SMSResource.BE_: [r'^00324[5-9]\d{7}$']
140
        }
141

  
142
        premium_numbers = set()
143
        if not self.allow_premium_rate:
144
            regex =  re.compile('|'.join(number_regexes['premium_rate']))
145
            premium_numbers = set(dest for dest in destinations if regex.match(dest))
146

  
147
        foreign_numbers = set()
148
        if SMSResource.ALL not in self.authorized:
149
            regexes = []
150
            for country in self.authorized:
151
                regexes += number_regexes[country]
152
            regex = re.compile('|'.join(regexes))
153
            foreign_numbers = set(dest for dest in destinations if not regex.match(dest))
154

  
155
        authorized_numbers = sorted(
156
            set(destinations) - foreign_numbers - premium_numbers,
157
            key=int)
158

  
159
        premium_numbers_string = ", ".join(sorted(premium_numbers, key=int))
160
        foreign_numbers_string = ", ".join(sorted(foreign_numbers - premium_numbers, key=int))
161
        if premium_numbers_string:
162
            logging.warning('unauthorized premium rate phone number: %s',
163
                            premium_numbers_string)
164
        if foreign_numbers_string:
165
            logging.warning('unauthorized foreign phone number: %s',
166
                            foreign_numbers_string)
167
        if len(authorized_numbers) == 0:
168
            raise APIError('no phone number was authorized: %s' % ', '.join(destinations))
169
        warnings = {
170
            'deny premium rate phone numbers': premium_numbers_string,
171
            'deny foreign phone numbers': foreign_numbers_string,
172
        }
173
        return authorized_numbers, warnings
174

  
93 175
    @endpoint(perm='can_send_messages', methods=['post'],
94 176
              description=_('Send a SMS message'),
95 177
              parameters={'nostop': {'description': _('Do not send STOP instruction'), 'example_value': '1'}},
96 178
              post={'request_body': {'schema': {'application/json': SEND_SCHEMA}}})
97 179
    def send(self, request, post_data, nostop=None):
98 180
        post_data['message'] = post_data['message'][:self.max_message_length]
99 181
        post_data['to'] = self.clean_numbers(post_data['to'])
182
        post_data['to'], warnings = self.authorize_numbers(post_data['to'])
100 183
        logging.info('sending SMS to %r from %r', post_data['to'], post_data['from'])
101 184
        stop = nostop is None  # ?nostop in not in query string
102 185
        self.add_job('send_job',
103 186
                     text=post_data['message'], sender=post_data['from'], destinations=post_data['to'],
104 187
                     stop=stop)
105
        return {'err': 0}
188
        return {'err': 0, 'warn': warnings}
106 189

  
107 190
    def send_job(self, *args, **kwargs):
108 191
        self.send_msg(**kwargs)
109 192
        SMSLog.objects.create(appname=self.get_connector_slug(), slug=self.slug)
110 193

  
111 194
    class Meta:
112 195
        abstract = True
113 196

  
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][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
        connector.authorize_numbers([number])
66
    connector.allow_premium_rate = True
67
    connector.save()
68
    assert connector.authorize_numbers([number])[0] == [number]
69

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

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

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

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

  
103
    # Don't raise if authorized destinations are not empty
104
    connector.allow_premium_rate = False
105
    connector.authorized = [SMSResource.FR_METRO]
106
    connector.save()
107
    numbers = [
108
        '0033' + '8' + '12345678',
109
        '0033' + '1' + '12345678',
110
        '0033' + '6' + '12345678',
111
        '596596' + '123456',
112
        '0032' + '45' + '1234567',
113
    ]
114
    authorized_numbers, warnings = connector.authorize_numbers(numbers)
115
    assert authorized_numbers == ['0033612345678']
116
    assert warnings == {
117
        'deny premium rate phone numbers': '0033812345678',
118
        'deny foreign phone numbers': '0032451234567, 0033112345678, 596596123456',
119
    }
120

  
121

  
40 122
@pytest.fixture(params=klasses)
41 123
def connector(request, db):
42 124
    klass = request.param
43 125
    kwargs = getattr(klass, 'TEST_DEFAULTS', {}).get('create_kwargs', {})
44 126
    kwargs.update({
45 127
        'title': klass.__name__,
46 128
        'slug': klass.__name__.lower(),
47 129
        'description': klass.__name__,
......
393 475
    body = json.loads(request.body.decode())
394 476
    assert 'accessRules' in body
395 477
    redirect_url = body['redirection'][len('http://testserver'):]
396 478

  
397 479
    resp = app.get(redirect_url).follow()
398 480
    assert 'Successfuly completed connector configuration' in resp.text
399 481
    connector.refresh_from_db()
400 482
    assert connector.consumer_key == 'xyz'
483

  
484

  
485
@pytest.mark.parametrize('connector', [ChoositSMSGateway], indirect=True)
486
def test_manager(admin_user, app, connector):
487
    app = login(app)
488
    path = '/%s/%s/' % (connector.get_connector_slug(), connector.slug)
489
    resp = app.get(path)
490
    assert '33' in [
491
        x.text for x in resp.html.find('div', {'id': 'description'}).find_all('p')
492
        if x.text.startswith(_('Default country code'))][0]
493
    assert _('All') in [
494
        x.text for x in resp.html.find_all('p')
495
        if x.text.startswith(_('Authorized Countries'))][0]
496
    assert _('no') in [
497
        x.text for x in resp.html.find_all('p')
498
        if x.text.startswith(_('Allow premium rate numbers'))][0]
499

  
500
    path = '/manage/%s/%s/edit' % (connector.get_connector_slug(), connector.slug)
501
    resp = app.get(path)
502
    resp.form['authorized'] = []
503
    resp = resp.form.submit()
504
    assert resp.html.find('div', {'class': 'errornotice'}).p.text == \
505
        'There were errors processing your form.'
506
    assert resp.html.find('div', {'class': 'error'}).text.strip() == 'This field is required.'
507
    resp.form['authorized'] = [SMSResource.FR_METRO, SMSResource.FR_DOMTOM]
508
    resp = resp.form.submit()
509
    resp = resp.follow()
510
    assert _('France mainland (+33 [67])') in [
511
        x.text for x in resp.html.find_all('p')
512
        if x.text.startswith(_('Authorized Countries'))][0]
513

  
514
    path = '/%s/%s/send/' % (connector.get_connector_slug(), connector.slug)
515
    payload = {
516
        'message': 'plop',
517
        'from': '+33699999999',
518
        'to': ['+33688888888'],
519
    }
520
    resp = app.post_json(path, params=payload)
521
    assert resp.json['warn'] == {
522
        'deny premium rate phone numbers': '',
523
        'deny foreign phone numbers': '',
524
    }
525
    with mock.patch.object(type(connector), 'send_msg') as send_function:
526
        send_function.return_value = {}
527
        connector.jobs()
528
    assert SMSLog.objects.count() == 1
529

  
530
    payload['to'][0] = '+33188888888'
531
    SMSLog.objects.all().delete()
532
    app.post_json(path, params=payload)
533
    with mock.patch.object(type(connector), 'send_msg') as send_function:
534
        send_function.return_value = {}
535
        connector.jobs()
536
    assert not SMSLog.objects.count()
537
    assert ResourceLog.objects.filter(levelno=logging.WARNING).count() == 1
538
    assert ResourceLog.objects.filter(levelno=30)[0].extra['exception'] == \
539
        'no phone number was authorized: 0033188888888'
401
-