From 3ca60b91700f37fa5a89c53eef000499baba9e89 Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Fri, 9 Jul 2021 19:27:22 +0200
Subject: [PATCH 7/7] toulouse_smart: add update-intervention endpoint (#55230)

---
 passerelle/contrib/toulouse_smart/models.py | 45 ++++++++++++
 tests/test_toulouse_smart.py                | 81 +++++++++++++++++++++
 2 files changed, 126 insertions(+)

diff --git a/passerelle/contrib/toulouse_smart/models.py b/passerelle/contrib/toulouse_smart/models.py
index e0067d8c..7553f1be 100644
--- a/passerelle/contrib/toulouse_smart/models.py
+++ b/passerelle/contrib/toulouse_smart/models.py
@@ -12,25 +12,28 @@
 # GNU Affero General Public License for more details.
 #
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import datetime
 
 import lxml.etree as ET
+from django.conf import settings
 from django.contrib.postgres.fields import JSONField
 from django.db import models
 from django.urls import reverse
+from django.utils.six.moves.urllib import parse as urlparse
 from django.utils.text import slugify
 from django.utils.timezone import now
 from django.utils.translation import ugettext_lazy as _
 from requests import RequestException
 
 from passerelle.base.models import BaseResource, HTTPResource
+from passerelle.base.signature import sign_query
 from passerelle.utils import xml
 from passerelle.utils.api import endpoint
 from passerelle.utils.jsonresponse import APIError
 
 from . import schemas, utils
 
 
 class ToulouseSmartResource(BaseResource, HTTPResource):
@@ -187,16 +190,58 @@ class ToulouseSmartResource(BaseResource, HTTPResource):
         response = self.request(url, json=post_data)
         try:
             result = response.json()
         except ValueError:
             raise APIError('invalid json, got: %s' % response.text)
         self.set('intervention-%s' % result['id'], form_url)
         return {'data': result}
 
+    @endpoint(
+        name='update-intervention',
+        methods=['post'],
+        description=_('Update an intervention status'),
+        perm='can_access',
+        parameters={
+            'id': {'description': _('Intervention identifier')},
+            'trigger': {'description': _('W.C.S trigger')},
+        },
+    )
+    def update_intervention(self, request, id, trigger):
+        try:
+            form_url = self.get('intervention-%s' % id)
+        except KeyError:
+            raise APIError("Cannot find intervention '%s'" % id)
+
+        base_url = '%sjump/trigger/%s' % (form_url, trigger)
+        scheme, netloc, path, params, query, fragment = urlparse.urlparse(base_url)
+        services = settings.KNOWN_SERVICES.get('wcs', {})
+        service = None
+        for service in services.values():
+            remote_url = service.get('url')
+            r_scheme, r_netloc, r_path, r_params, r_query, r_fragment = urlparse.urlparse(remote_url)
+            if r_scheme == scheme and r_netloc == netloc:
+                break
+        else:
+            raise APIError('Cannot find wcs service for %s' % base_url)
+
+        headers = {
+            'Content-Type': 'application/json',
+            'Accept': 'application/json',
+        }
+        query_string = 'orig=%s' % service.get('orig')
+        parameters = sign_query(query_string, key=service.get('secret'))
+        url = '%s?%s' % (base_url, parameters)
+        response = self.requests.post(url, headers=headers)
+        try:
+            result = response.json()
+        except ValueError:
+            raise APIError('failed to post to %s (%s): %s' % (url, response.status_code, response.text))
+        return {'err': result.get('err', 1), 'data': result}
+
 
 class Cache(models.Model):
     resource = models.ForeignKey(
         verbose_name=_('Resource'),
         to=ToulouseSmartResource,
         on_delete=models.CASCADE,
         related_name='cache_entries',
     )
diff --git a/tests/test_toulouse_smart.py b/tests/test_toulouse_smart.py
index d7212f8b..4cb65de2 100644
--- a/tests/test_toulouse_smart.py
+++ b/tests/test_toulouse_smart.py
@@ -40,16 +40,30 @@ def smart(db):
         slug='test',
         description='Test',
         webservice_base_url='https://smart.example.com/',
         basic_auth_username='username',
         basic_auth_password='password',
     )
 
 
+@pytest.fixture
+def wcs_service(settings):
+    wcs_service = {
+        'default': {
+            'title': 'test',
+            'url': 'https://wcs.example.com',
+            'secret': 'xxx',
+            'orig': 'passerelle',
+        },
+    }
+    settings.KNOWN_SERVICES = {'wcs': wcs_service}
+    return wcs_service
+
+
 def mock_response(*path_contents):
     def decorator(func):
         @httmock.urlmatch()
         def error(url, request):
             assert False, 'request to %s' % url.geturl()
 
         def register(path, payload, content, status_code=200):
             @httmock.urlmatch(path=path)
@@ -396,8 +410,75 @@ def test_create_intervention_error_status(app, smart):
 @mock_response(
     ['/v1/type-intervention', None, INTERVENTION_TYPES],
     ['/v1/intervention', CREATE_INTERVENTION_QUERY, 'not json content'],
 )
 def test_create_intervention_error_content(app, smart):
     resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
     assert resp.json['err']
     assert 'invalid json' in resp.json['err_desc']
+
+
+WCS_RESPONSE_SUCCESS = '{"err": 0, "url": null}'
+WCS_RESPONSE_ERROR = '{"err": 1, "err_class": "Page non trouv\u00e9e", "err_desc": null}'
+
+
+@mock_response(
+    ['/v1/type-intervention', None, INTERVENTION_TYPES],
+    ['/v1/intervention', CREATE_INTERVENTION_QUERY, get_json_file('create_intervention')],
+    ['/foo/2/jump/trigger/created', None, WCS_RESPONSE_SUCCESS],
+)
+def test_update_intervention(app, smart, wcs_service):
+    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
+    assert not resp.json['err']
+    intervention_id = resp.json['data']['id']
+    resp = app.post_json(URL + 'update-intervention?id=%s&trigger=created' % intervention_id)
+    assert not resp.json['err']
+    assert resp.json['data'] == json.loads(WCS_RESPONSE_SUCCESS)
+
+
+def test_update_intervention_wrong_id(app, smart):
+    intervention_id = '96bc8712-21a6-4fba-a511-740d6f1bd0bc'
+    resp = app.post_json(URL + 'update-intervention?id=%s&trigger=created' % intervention_id)
+    assert resp.json['err']
+    assert 'Cannot find intervention' in resp.json['err_desc']
+
+
+@mock_response(
+    ['/v1/type-intervention', None, INTERVENTION_TYPES],
+    ['/v1/intervention', CREATE_INTERVENTION_QUERY, get_json_file('create_intervention')],
+)
+def test_update_intervention_wrong_service(app, smart, wcs_service):
+    wcs_service['default']['url'] = 'http://wcs.example.com/smart_form/'
+    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
+    assert not resp.json['err']
+    intervention_id = resp.json['data']['id']
+    resp = app.post_json(URL + 'update-intervention?id=%s&trigger=created' % intervention_id)
+    assert resp.json['err']
+    assert 'Cannot find wcs service' in resp.json['err_desc']
+
+
+@mock_response(
+    ['/v1/type-intervention', None, INTERVENTION_TYPES],
+    ['/v1/intervention', CREATE_INTERVENTION_QUERY, get_json_file('create_intervention')],
+    ['/foo/2/jump/trigger/created', None, None, 500],
+)
+def test_update_intervention_error_wcs(app, smart, wcs_service):
+    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
+    assert not resp.json['err']
+    intervention_id = resp.json['data']['id']
+    resp = app.post_json(URL + 'update-intervention?id=%s&trigger=created' % intervention_id)
+    assert resp.json['err']
+    assert 'failed to post' in resp.json['err_desc']
+
+
+@mock_response(
+    ['/v1/type-intervention', None, INTERVENTION_TYPES],
+    ['/v1/intervention', CREATE_INTERVENTION_QUERY, get_json_file('create_intervention')],
+    ['/foo/2/jump/trigger/created', None, WCS_RESPONSE_ERROR],
+)
+def test_update_intervention_wrong_trigger(app, smart, wcs_service):
+    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
+    assert not resp.json['err']
+    intervention_id = resp.json['data']['id']
+    resp = app.post_json(URL + 'update-intervention?id=%s&trigger=created' % intervention_id)
+    assert resp.json['err']
+    assert resp.json['data'] == json.loads(WCS_RESPONSE_ERROR)
-- 
2.30.2

