api: add an API to request jobs status (#43278)

This commit is contained in:
Nicolas Roche 2020-08-14 19:09:59 +02:00
parent 4180a18f50
commit e2fecd6373
5 changed files with 180 additions and 0 deletions

View File

23
passerelle/api/urls.py Normal file
View File

@ -0,0 +1,23 @@
# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2020 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# 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/>.
from django.conf.urls import url
from .views import JobDetailView
urlpatterns = [
url(r'jobs/(?P<pk>[\w,-]+)/$', JobDetailView.as_view(), name='api-job'),
]

48
passerelle/api/views.py Normal file
View File

@ -0,0 +1,48 @@
# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2020 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# 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/>.
from django.core.exceptions import PermissionDenied
from django.http import Http404
from django.http import JsonResponse
from django.views.generic import DetailView
from passerelle.base.models import Job
from passerelle.utils import is_authorized
class JobDetailView(DetailView):
model = Job
def error(self, message):
return JsonResponse({'err': 1, 'err_desc': message})
def get(self, *args, **kwargs):
try:
job = self.get_object()
except Http404 as exc:
return self.error(str(exc))
if not is_authorized(self.request, job.resource, 'can_access'):
raise PermissionDenied
data = {
'id': job.id,
'resource': job.resource.__class__.__name__,
'parameters': job.parameters,
'status': job.status,
'status_details': job.status_details,
'update_timestamp': job.update_timestamp,
'done_timestamp': job.done_timestamp,
}
return JsonResponse({'err': 0, 'data': data})

View File

@ -7,6 +7,7 @@ from django.contrib.auth.decorators import login_required
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.views.static import serve as static_serve
from .api.urls import urlpatterns as api_urls
from .views import (
HomePageView, ManageView, ManageAddView,
GenericCreateConnectorView, GenericDeleteConnectorView,
@ -37,6 +38,7 @@ urlpatterns = [
decorated_includes(manager_required, include(access_urlpatterns))),
url(r'^manage/',
decorated_includes(manager_required, include(import_export_urlpatterns))),
url('^api/', include(api_urls)),
]
# add patterns from apps

107
tests/test_api.py Normal file
View File

@ -0,0 +1,107 @@
# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2020 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# 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 mock
import pytest
from django.core.urlresolvers import reverse
from django.contrib.contenttypes.models import ContentType
from django.test import override_settings
from passerelle.apps.ovh.models import OVHSMSGateway
from passerelle.base.models import ApiUser, AccessRight, Job
from test_manager import login, simple_user, admin_user
pytestmark = pytest.mark.django_db
API_KEY = '1234'
@pytest.fixture
def connector(db):
connector = OVHSMSGateway.objects.create(
slug='my_connector',
)
apiuser = ApiUser.objects.create(username='me', keytype='API', key=API_KEY)
obj_type = ContentType.objects.get_for_model(OVHSMSGateway)
AccessRight.objects.create(codename='can_access', apiuser=apiuser,
resource_type=obj_type, resource_pk=connector.pk)
return connector
@mock.patch('passerelle.sms.models.SMSResource.send_job')
def test_api_jobs(mocked_send_job, app, connector, simple_user, admin_user):
assert Job.objects.count() == 0
url = reverse('api-job', kwargs={'pk': 22})
# no job
resp = app.get(url, status=200)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'No job found matching the query'
job = connector.add_job('send_job', my_parameter='my_value')
assert Job.objects.count() == 1
url = reverse('api-job', kwargs={'pk': job.id})
# app disabled
with override_settings(PASSERELLE_APP_OVH_ENABLED=False):
resp = app.get(url, status=403)
# access without permission
resp = app.get(url, status=403)
# apiuser access
resp = app.get(url, params={'apikey': API_KEY}, status=200)
assert not resp.json['err']
# logged user access
app = login(app, simple_user.username, password='user')
resp = app.get(url, status=403)
app = login(app, admin_user.username, password='admin')
resp = app.get(url, status=200)
assert not resp.json['err']
# registered job
assert resp.json['data']['id'] == job.id
assert resp.json['data']['resource'] == 'OVHSMSGateway'
assert resp.json['data']['parameters'] == {'my_parameter': 'my_value'}
assert resp.json['data']['status'] == 'registered'
assert resp.json['data']['done_timestamp'] is None
update_timestamp1 = resp.json['data']['update_timestamp']
# completed job
connector.jobs()
assert mocked_send_job.call_args == mock.call(my_parameter='my_value')
resp = app.get(url, status=200)
assert not resp.json['err']
assert resp.json['data']['id'] == job.id
assert resp.json['data']['status'] == 'completed'
assert resp.json['data']['done_timestamp'] is not None
resp.json['data']['update_timestamp'] < update_timestamp1
# failed job
job = connector.add_job('send_job')
assert Job.objects.count() == 2
mocked_send_job.side_effect = Exception('my error message')
connector.jobs()
url = reverse('api-job', kwargs={'pk': job.id})
resp = app.get(url, status=200)
assert not resp.json['err']
assert resp.json['data']['id'] == job.id
assert resp.json['data']['status'] == 'failed'
assert resp.json['data']['status_details'] == {'error_summary': 'Exception: my error message'}
assert resp.json['data']['done_timestamp'] is not None