Projet

Général

Profil

0001-sms-raise-on-recoverable-errors-42230.patch

Nicolas Roche, 29 avril 2020 15:08

Télécharger (12,2 ko)

Voir les différences:

Subject: [PATCH] sms: raise on recoverable errors (#42230)

 passerelle/apps/choosit/models.py |  4 ++--
 passerelle/apps/mobyt/models.py   |  4 ++--
 passerelle/apps/orange/models.py  | 13 +++++++++----
 passerelle/apps/ovh/models.py     |  4 ++--
 passerelle/apps/oxyd/models.py    |  4 ++--
 passerelle/utils/jsonresponse.py  |  5 +++++
 tests/test_orange.py              | 14 +++++++++++++-
 7 files changed, 35 insertions(+), 13 deletions(-)
passerelle/apps/choosit/models.py
1 1
# -*- coding: utf-8 -*-
2 2
import json
3 3
import requests
4 4

  
5 5
from django.utils.six import string_types
6 6
from django.utils.translation import ugettext_lazy as _
7 7
from django.db import models
8 8

  
9
from passerelle.utils.jsonresponse import APIError
9
from passerelle.utils.jsonresponse import APIError, APIRecoverableError
10 10
from passerelle.base.models import SMSResource
11 11

  
12 12

  
13 13
class ChoositSMSGateway(SMSResource):
14 14
    key = models.CharField(verbose_name=_('Key'), max_length=64)
15 15
    default_country_code = models.CharField(verbose_name=_('Default country code'), max_length=3,
16 16
                                            default=u'33')
17 17
    default_trunk_prefix = models.CharField(verbose_name=_('Default trunk prefix'), max_length=2,
......
86 86
                'key': self.key,
87 87
                'recipient': dest,
88 88
                'content': text[:160],
89 89
            }
90 90
            data = {'data': json.dumps(params)}
91 91
            try:
92 92
                r = self.requests.post(self.URL, data=data)
93 93
            except requests.RequestException as e:
94
                results.append('Choosit error: %s' % e)
94
                raise APIRecoverableError('Choosit error: POST failed, %s' % e)
95 95
            else:
96 96
                try:
97 97
                    output = r.json()
98 98
                except ValueError as e:
99 99
                    results.append('Choosit error: bad JSON response')
100 100
                else:
101 101
                    if not isinstance(output, dict):
102 102
                        results.append('Choosit error: JSON response is not a dict %r' % output)
passerelle/apps/mobyt/models.py
1 1
from django.utils.translation import ugettext_lazy as _
2 2
from django.db import models
3 3

  
4 4
import requests
5 5

  
6
from passerelle.utils.jsonresponse import APIError
6
from passerelle.utils.jsonresponse import APIError, APIRecoverableError
7 7
from passerelle.base.models import SMSResource
8 8

  
9 9

  
10 10
class MobytSMSGateway(SMSResource):
11 11
    URL = 'http://multilevel.mobyt.fr/sms/batch.php'
12 12
    MESSAGES_QUALITIES = (
13 13
        ('l', _('sms direct')),
14 14
        ('ll', _('sms low-cost')),
......
61 61
            'rcpt': rcpt,
62 62
            'data': text,
63 63
            'sender': sender,
64 64
            'qty': self.quality,
65 65
        }
66 66
        try:
67 67
            r = self.requests.post(self.URL, data=params)
68 68
        except requests.RequestException as e:
69
            raise APIError('MobyT error: POST failed, %s' % e)
69
            raise APIRecoverableError('MobyT error: POST failed, %s' % e)
70 70
        if r.content[:2] != "OK":
71 71
            raise APIError('MobyT error: response is not "OK"')
72 72
        return None
passerelle/apps/orange/models.py
14 14
#
15 15
# This program is distributed in the hope that it will be useful,
16 16
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17 17
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 18
# GNU Affero General Public License for more details.
19 19
#
20 20
# You should have received a copy of the GNU Affero General Public License
21 21
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
import requests
23

  
22 24
from django.db import models
23 25
from django.utils.translation import ugettext_lazy as _
24 26

  
25 27
from passerelle.base.models import SMSResource
26
from passerelle.utils.jsonresponse import APIError
28
from passerelle.utils.jsonresponse import APIError, APIRecoverableError
27 29

  
28 30
BASE_API = 'https://contact-everyone.orange-business.com/api/v1.2/'
29 31
URL_TOKEN = BASE_API + 'oauth/token'
30 32
URL_GROUPS = BASE_API + 'groups'
31 33
URL_DIFFUSION = BASE_API + 'groups/%s/diffusion-requests'
32 34

  
33 35

  
34 36
class OrangeError(APIError):
......
105 107
            raise OrangeError('Orange fails to send SMS: %s, %s' % (
106 108
                response.status_code, response.text))
107 109
        return get_json(response)
108 110

  
109 111
    def send_msg(self, text, sender, destinations, **kwargs):
110 112
        '''Send a SMS using the Orange provider'''
111 113
        destinations = self.clean_numbers(
112 114
            destinations, self.default_country_code, self.default_trunk_prefix)
113
        access_token = self.get_access_token()
114
        group_id = self.group_id_from_name(access_token)
115
        response = self.diffusion(access_token, group_id, destinations, text)
115
        try:
116
            access_token = self.get_access_token()
117
            group_id = self.group_id_from_name(access_token)
118
            response = self.diffusion(access_token, group_id, destinations, text)
119
        except requests.RequestException as exc:
120
            raise APIRecoverableError('Orange error: POST failed, %s' % exc)
116 121
        return response
passerelle/apps/ovh/models.py
1 1
import requests
2 2

  
3 3
from django.utils.translation import ugettext_lazy as _
4 4
from django.db import models
5 5
from django.utils.encoding import force_text
6 6

  
7
from passerelle.utils.jsonresponse import APIError
7
from passerelle.utils.jsonresponse import APIError, APIRecoverableError
8 8
from passerelle.base.models import SMSResource
9 9

  
10 10

  
11 11
class OVHSMSGateway(SMSResource):
12 12
    URL = 'https://www.ovh.com/cgi-bin/sms/http2sms.cgi'
13 13
    MESSAGES_CLASSES = (
14 14
        (0, _('Message are directly shown to users on phone screen '
15 15
              'at reception. The message is never stored, neither in the '
......
98 98
            'contentType': 'text/json',
99 99
            'class': self.msg_class,
100 100
        }
101 101
        if not kwargs['stop']:
102 102
            params.update({'noStop': 1})
103 103
        try:
104 104
            response = self.requests.post(self.URL, data=params)
105 105
        except requests.RequestException as e:
106
            raise APIError('OVH error: POST failed, %s' % e)
106
            raise APIRecoverableError('OVH error: POST failed, %s' % e)
107 107
        else:
108 108
            try:
109 109
                result = response.json()
110 110
            except ValueError as e:
111 111
                raise APIError('OVH error: bad JSON response')
112 112
            else:
113 113
                if not isinstance(result, dict):
114 114
                    raise APIError('OVH error: bad JSON response %r, it should be a dictionnary' %
passerelle/apps/oxyd/models.py
1 1
import requests
2 2

  
3 3
from django.db import models
4 4
from django.utils.encoding import force_text
5 5

  
6
from passerelle.utils.jsonresponse import APIError
6
from passerelle.utils.jsonresponse import APIError, APIRecoverableError
7 7
from passerelle.base.models import SMSResource
8 8
from django.utils.translation import ugettext_lazy as _
9 9

  
10 10

  
11 11
class OxydSMSGateway(SMSResource):
12 12
    username = models.CharField(verbose_name=_('Username'), max_length=64)
13 13
    password = models.CharField(verbose_name=_('Password'), max_length=64)
14 14
    default_country_code = models.CharField(verbose_name=_('Default country code'), max_length=3,
......
64 64
                'pass': self.password,
65 65
                'num': dest,
66 66
                'sms': text.encode('utf-8'),
67 67
                'flash': '0',
68 68
            }
69 69
            try:
70 70
                r = self.requests.post(self.URL, params)
71 71
            except requests.RequestException as e:
72
                results.append('OXYD error: POST failed, %s' % e)
72
                raise APIRecoverableError('OXYD error: POST failed, %s' % e)
73 73
            else:
74 74
                code = r.content and r.content.split()[0]
75 75
                if force_text(code) != '200':
76 76
                    results.append('OXYD error: response is not 200')
77 77
                else:
78 78
                    results.append(0)
79 79
        if any(results):
80 80
            raise APIError(
passerelle/utils/jsonresponse.py
35 35
    log_error = False
36 36
    http_status = 200
37 37

  
38 38
    def __init__(self, *args, **kwargs):
39 39
        self.__dict__.update(kwargs)
40 40
        super(APIError, self).__init__(*args)
41 41

  
42 42

  
43
class APIRecoverableError(APIError):
44
    ''''Exception to raise when call on remote application should be retry later'''
45
    err = 2
46

  
47

  
43 48
class JSONEncoder(DjangoJSONEncoder):
44 49
    def default(self, o):
45 50
        if isinstance(o, time.struct_time):
46 51
            o = datetime.datetime(*tuple(o)[:6])
47 52
        return super(JSONEncoder, self).default(o)
48 53

  
49 54

  
50 55
class to_json(object):
tests/test_orange.py
11 11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 13
# GNU Affero General Public License for more details.
14 14
#
15 15
# You should have received a copy of the GNU Affero General Public License
16 16
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 17

  
18 18
import json
19
import requests
19 20

  
20 21
import httmock
21 22
import pytest
22 23

  
23 24
from django.contrib.contenttypes.models import ContentType
24 25
from django.utils.encoding import force_text
25 26

  
26 27
from passerelle.apps.orange.models import OrangeSMSGateway, OrangeError
27 28
from passerelle.base.models import ApiUser, AccessRight
28
from passerelle.utils.jsonresponse import APIError
29
from passerelle.utils.jsonresponse import APIError, APIRecoverableError
29 30

  
30 31

  
31 32
NETLOC = 'contact-everyone.orange-business.com'
32 33
JSON_HEADERS = {'content-type': 'application/json'}
33 34
PAYLOAD = {
34 35
    'message': 'hello',
35 36
    'from': '+33699999999',
36 37
    'to': ['+33688888888', '+33677777777'],
......
65 66
@httmock.urlmatch(netloc=NETLOC)
66 67
def response_500(url, request):
67 68
    return httmock.response(500, 'my_error')
68 69

  
69 70
@httmock.urlmatch(netloc=NETLOC)
70 71
def response_invalid_json(url, request):
71 72
    return httmock.response(200, 'not a JSON content')
72 73

  
74
@httmock.urlmatch(netloc=NETLOC)
75
def request_exception(url, request):
76
    raise requests.RequestException('my_error')
77

  
73 78
def setup_access_rights(obj):
74 79
    api = ApiUser.objects.create(username='all',
75 80
                                 keytype='', key='')
76 81
    obj_type = ContentType.objects.get_for_model(obj)
77 82
    AccessRight.objects.create(codename='can_send_messages', apiuser=api,
78 83
                               resource_type=obj_type, resource_pk=obj.pk)
79 84
    return obj
80 85

  
......
168 173
    assert not resp.json['err']
169 174
    assert resp.json['data']['status'] == "I'm ok"
170 175

  
171 176
    # not 201
172 177
    with httmock.HTTMock(response_token_ok, response_group_ok, response_500):
173 178
        resp = app.post_json(url, params=PAYLOAD, status=200)
174 179
    assert resp.json['err']
175 180
    assert resp.json['err_desc'] == 'Orange fails to send SMS: 500, my_error'
181

  
182
    # RequestException
183
    with httmock.HTTMock(request_exception):
184
        resp = app.post_json(url, params=PAYLOAD, status=200)
185
    assert resp.json['err']
186
    assert resp.json['err_desc'] == 'Orange error: POST failed, my_error'
187
    assert 'APIRecoverableError' in resp.json['err_class']
176
-