From 1442c6b75ee4cbf16ee51dca773bd74fb660ddfb Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Wed, 6 Oct 2021 22:02:26 +0200 Subject: [PATCH 2/3] tests: separate statistics API tests (#57663) --- tests/api/test_misc.py | 230 -------------------------------- tests/api/test_statistics.py | 252 +++++++++++++++++++++++++++++++++++ 2 files changed, 252 insertions(+), 230 deletions(-) create mode 100644 tests/api/test_statistics.py diff --git a/tests/api/test_misc.py b/tests/api/test_misc.py index afcf9d95..364c57aa 100644 --- a/tests/api/test_misc.py +++ b/tests/api/test_misc.py @@ -31,11 +31,9 @@ from django.urls import reverse from django.utils.encoding import force_text from django.utils.text import slugify from requests.models import Response -from rest_framework import VERSION as drf_version from authentic2.a2_rbac.models import Role from authentic2.a2_rbac.utils import get_default_ou -from authentic2.apps.journal.models import Event, EventType from authentic2.models import Attribute, AttributeValue, AuthorizedRole, Service from authentic2.utils.misc import good_next_url from django_rbac.models import SEARCH_OP @@ -2307,234 +2305,6 @@ def test_api_users_delete(settings, app, admin, simple_user): assert_event('manager.user.deletion', user=admin, api=True) -@pytest.mark.skipif(drf_version.startswith('3.4'), reason='no support for old django rest framework') -def test_api_statistics_list(app, admin): - OU = get_ou_model() - headers = basic_authorization_header(admin) - resp = app.get('/api/statistics/', headers=headers) - assert len(resp.json['data']) == 6 - login_stats = { - 'name': 'Login count by authentication type', - 'url': 'http://testserver/api/statistics/login/', - 'id': 'login', - 'filters': [ - { - "id": "time_interval", - "label": "Time interval", - "options": [ - {"id": "day", "label": "Day"}, - {"id": "month", "label": "Month"}, - {"id": "year", "label": "Year"}, - ], - "required": True, - "default": "month", - }, - {'id': 'service', 'label': 'Service', 'options': []}, - ], - } - assert login_stats in resp.json['data'] - assert { - 'name': 'Login count by service', - 'url': 'http://testserver/api/statistics/service_login/', - 'id': 'service-login', - 'filters': [ - { - "id": "time_interval", - "label": "Time interval", - "options": [ - {"id": "day", "label": "Day"}, - {"id": "month", "label": "Month"}, - {"id": "year", "label": "Year"}, - ], - "required": True, - "default": "month", - }, - ], - } in resp.json['data'] - - service = Service.objects.create(name='Service1', slug='service1', ou=get_default_ou()) - service = Service.objects.create(name='Service2', slug='service2', ou=get_default_ou()) - login_stats['filters'][1]['options'].append({'id': 'service1 default', 'label': 'Service1'}) - login_stats['filters'][1]['options'].append({'id': 'service2 default', 'label': 'Service2'}) - - resp = app.get('/api/statistics/', headers=headers) - assert login_stats in resp.json['data'] - - # adding second ou doesn't change anything - ou = OU.objects.create(name='Second OU', slug='second') - resp = app.get('/api/statistics/', headers=headers) - assert login_stats in resp.json['data'] - - # if there are services in two differents OUs, filter is shown - service.ou = ou - service.save() - login_stats['filters'][1]['options'][1]['id'] = 'service2 second' - login_stats['filters'].append( - { - 'id': 'services_ou', - 'label': 'Services organizational unit', - 'options': [ - {'id': 'default', 'label': 'Default organizational unit'}, - {'id': 'second', 'label': 'Second OU'}, - ], - } - ) - resp = app.get('/api/statistics/', headers=headers) - assert login_stats in resp.json['data'] - - # same goes with users - User.objects.create(username='john.doe', email='john.doe@example.com', ou=ou) - login_stats['filters'].append( - { - 'id': 'users_ou', - 'label': 'Users organizational unit', - 'options': [ - {'id': 'default', 'label': 'Default organizational unit'}, - {'id': 'second', 'label': 'Second OU'}, - ], - } - ) - resp = app.get('/api/statistics/', headers=headers) - assert login_stats in resp.json['data'] - - -@pytest.mark.skipif(drf_version.startswith('3.4'), reason='no support for old django rest framework') -@pytest.mark.parametrize( - 'event_type_name,event_name', [('user.login', 'login'), ('user.registration', 'registration')] -) -def test_api_statistics(app, admin, freezer, event_type_name, event_name): - OU = get_ou_model() - headers = basic_authorization_header(admin) - - resp = app.get('/api/statistics/login/?time_interval=month', headers=headers) - assert resp.json == {"data": {"series": [], "x_labels": []}, "err": 0} - - user = User.objects.create(username='john.doe', email='john.doe@example.com', ou=get_default_ou()) - ou = OU.objects.create(name='Second OU', slug='second') - portal = Service.objects.create(name='portal', slug='portal', ou=ou) - agendas = Service.objects.create(name='agendas', slug='agendas', ou=get_default_ou()) - - method = {'how': 'password-on-https'} - method2 = {'how': 'fc'} - - event_type = EventType.objects.get_for_name(event_type_name) - - freezer.move_to('2020-02-03 12:00') - Event.objects.create(type=event_type, references=[portal], data=method) - Event.objects.create(type=event_type, references=[agendas, user], user=user, data=method) - - freezer.move_to('2020-03-04 13:00') - Event.objects.create(type=event_type, references=[agendas], data=method) - Event.objects.create(type=event_type, references=[portal], data=method2) - - resp = app.get('/api/statistics/%s/?time_interval=month' % event_name, headers=headers) - data = resp.json['data'] - data['series'].sort(key=lambda x: x['label']) - assert data == { - 'x_labels': ['2020-02', '2020-03'], - 'series': [{'label': 'FranceConnect', 'data': [None, 1]}, {'label': 'password', 'data': [2, 1]}], - } - - # default time interval is 'month' - month_data = data - resp = app.get('/api/statistics/%s/' % event_name, headers=headers) - data = resp.json['data'] - data['series'].sort(key=lambda x: x['label']) - assert month_data == data - - resp = app.get( - '/api/statistics/%s/?time_interval=month&services_ou=default' % event_name, headers=headers - ) - data = resp.json['data'] - data['series'].sort(key=lambda x: x['label']) - assert data == { - 'x_labels': ['2020-02', '2020-03'], - 'series': [{'label': 'password', 'data': [1, 1]}], - } - - # legacy way to filter by service OU - services_ou_data = data - resp = app.get('/api/statistics/%s/?time_interval=month&ou=default' % event_name, headers=headers) - data = resp.json['data'] - data['series'].sort(key=lambda x: x['label']) - assert services_ou_data == data - - resp = app.get( - '/api/statistics/%s/?time_interval=month&users_ou=default&service=agendas default' % event_name, - headers=headers, - ) - data = resp.json['data'] - assert data == { - 'x_labels': ['2020-02'], - 'series': [{'label': 'password', 'data': [1]}], - } - - resp = app.get('/api/statistics/%s/?time_interval=month&users_ou=default' % event_name, headers=headers) - data = resp.json['data'] - assert data == {'x_labels': ['2020-02'], 'series': [{'label': 'password', 'data': [1]}]} - - resp = app.get( - '/api/statistics/%s/?time_interval=month&service=agendas default' % event_name, headers=headers - ) - data = resp.json['data'] - assert data == {'x_labels': ['2020-02', '2020-03'], 'series': [{'label': 'password', 'data': [1, 1]}]} - - resp = app.get( - '/api/statistics/%s/?time_interval=month&start=2020-03-01T01:01' % event_name, headers=headers - ) - data = resp.json['data'] - data['series'].sort(key=lambda x: x['label']) - assert data == { - 'x_labels': ['2020-03'], - 'series': [{'label': 'FranceConnect', 'data': [1]}, {'label': 'password', 'data': [1]}], - } - - resp = app.get( - '/api/statistics/%s/?time_interval=month&end=2020-03-01T01:01' % event_name, headers=headers - ) - data = resp.json['data'] - assert data == {'x_labels': ['2020-02'], 'series': [{'label': 'password', 'data': [2]}]} - - resp = app.get('/api/statistics/%s/?time_interval=month&end=2020-03-01' % event_name, headers=headers) - data = resp.json['data'] - assert data == {'x_labels': ['2020-02'], 'series': [{'label': 'password', 'data': [2]}]} - - resp = app.get( - '/api/statistics/%s/?time_interval=year&service=portal second' % event_name, headers=headers - ) - data = resp.json['data'] - data['series'].sort(key=lambda x: x['label']) - assert data == { - 'x_labels': ['2020'], - 'series': [{'label': 'FranceConnect', 'data': [1]}, {'label': 'password', 'data': [1]}], - } - - resp = app.get('/api/statistics/service_%s/?time_interval=month' % event_name, headers=headers) - data = resp.json['data'] - data['series'].sort(key=lambda x: x['label']) - assert data == { - 'x_labels': ['2020-02', '2020-03'], - 'series': [{'label': 'agendas', 'data': [1, 1]}, {'label': 'portal', 'data': [1, 1]}], - } - - resp = app.get('/api/statistics/service_ou_%s/?time_interval=month' % event_name, headers=headers) - data = resp.json['data'] - data['series'].sort(key=lambda x: x['label']) - assert data == { - 'x_labels': ['2020-02', '2020-03'], - 'series': [ - {'label': 'Default organizational unit', 'data': [1, 1]}, - {'label': 'Second OU', 'data': [1, 1]}, - ], - } - - -def test_api_statistics_no_crash_older_drf(app, admin): - headers = basic_authorization_header(admin) - expected_status = 200 if drf_version > '3.9' else 404 - app.get('/api/statistics/login/?time_interval=month', headers=headers, status=expected_status) - - def test_find_duplicates_put(app, admin, settings): app.authorization = ('Basic', (admin.username, admin.username)) app.put_json( diff --git a/tests/api/test_statistics.py b/tests/api/test_statistics.py new file mode 100644 index 00000000..41abd8b4 --- /dev/null +++ b/tests/api/test_statistics.py @@ -0,0 +1,252 @@ +# authentic2 - versatile identity manager +# Copyright (C) 2010-2019 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 . + + +import pytest +from rest_framework import VERSION as drf_version + +from authentic2.a2_rbac.models import OrganizationalUnit as OU +from authentic2.a2_rbac.utils import get_default_ou +from authentic2.apps.journal.models import Event, EventType +from authentic2.custom_user.models import User +from authentic2.models import Service +from tests.utils import basic_authorization_header + + +@pytest.mark.skipif(drf_version.startswith('3.4'), reason='no support for old django rest framework') +def test_api_statistics_list(app, admin): + headers = basic_authorization_header(admin) + resp = app.get('/api/statistics/', headers=headers) + assert len(resp.json['data']) == 6 + login_stats = { + 'name': 'Login count by authentication type', + 'url': 'http://testserver/api/statistics/login/', + 'id': 'login', + 'filters': [ + { + "id": "time_interval", + "label": "Time interval", + "options": [ + {"id": "day", "label": "Day"}, + {"id": "month", "label": "Month"}, + {"id": "year", "label": "Year"}, + ], + "required": True, + "default": "month", + }, + {'id': 'service', 'label': 'Service', 'options': []}, + ], + } + assert login_stats in resp.json['data'] + assert { + 'name': 'Login count by service', + 'url': 'http://testserver/api/statistics/service_login/', + 'id': 'service-login', + 'filters': [ + { + "id": "time_interval", + "label": "Time interval", + "options": [ + {"id": "day", "label": "Day"}, + {"id": "month", "label": "Month"}, + {"id": "year", "label": "Year"}, + ], + "required": True, + "default": "month", + }, + ], + } in resp.json['data'] + + service = Service.objects.create(name='Service1', slug='service1', ou=get_default_ou()) + service = Service.objects.create(name='Service2', slug='service2', ou=get_default_ou()) + login_stats['filters'][1]['options'].append({'id': 'service1 default', 'label': 'Service1'}) + login_stats['filters'][1]['options'].append({'id': 'service2 default', 'label': 'Service2'}) + + resp = app.get('/api/statistics/', headers=headers) + assert login_stats in resp.json['data'] + + # adding second ou doesn't change anything + ou = OU.objects.create(name='Second OU', slug='second') + resp = app.get('/api/statistics/', headers=headers) + assert login_stats in resp.json['data'] + + # if there are services in two differents OUs, filter is shown + service.ou = ou + service.save() + login_stats['filters'][1]['options'][1]['id'] = 'service2 second' + login_stats['filters'].append( + { + 'id': 'services_ou', + 'label': 'Services organizational unit', + 'options': [ + {'id': 'default', 'label': 'Default organizational unit'}, + {'id': 'second', 'label': 'Second OU'}, + ], + } + ) + resp = app.get('/api/statistics/', headers=headers) + assert login_stats in resp.json['data'] + + # same goes with users + User.objects.create(username='john.doe', email='john.doe@example.com', ou=ou) + login_stats['filters'].append( + { + 'id': 'users_ou', + 'label': 'Users organizational unit', + 'options': [ + {'id': 'default', 'label': 'Default organizational unit'}, + {'id': 'second', 'label': 'Second OU'}, + ], + } + ) + resp = app.get('/api/statistics/', headers=headers) + assert login_stats in resp.json['data'] + + +@pytest.mark.skipif(drf_version.startswith('3.4'), reason='no support for old django rest framework') +@pytest.mark.parametrize( + 'event_type_name,event_name', [('user.login', 'login'), ('user.registration', 'registration')] +) +def test_api_statistics(app, admin, freezer, event_type_name, event_name): + headers = basic_authorization_header(admin) + + resp = app.get('/api/statistics/login/?time_interval=month', headers=headers) + assert resp.json == {"data": {"series": [], "x_labels": []}, "err": 0} + + user = User.objects.create(username='john.doe', email='john.doe@example.com', ou=get_default_ou()) + ou = OU.objects.create(name='Second OU', slug='second') + portal = Service.objects.create(name='portal', slug='portal', ou=ou) + agendas = Service.objects.create(name='agendas', slug='agendas', ou=get_default_ou()) + + method = {'how': 'password-on-https'} + method2 = {'how': 'fc'} + + event_type = EventType.objects.get_for_name(event_type_name) + + freezer.move_to('2020-02-03 12:00') + Event.objects.create(type=event_type, references=[portal], data=method) + Event.objects.create(type=event_type, references=[agendas, user], user=user, data=method) + + freezer.move_to('2020-03-04 13:00') + Event.objects.create(type=event_type, references=[agendas], data=method) + Event.objects.create(type=event_type, references=[portal], data=method2) + + resp = app.get('/api/statistics/%s/?time_interval=month' % event_name, headers=headers) + data = resp.json['data'] + data['series'].sort(key=lambda x: x['label']) + assert data == { + 'x_labels': ['2020-02', '2020-03'], + 'series': [{'label': 'FranceConnect', 'data': [None, 1]}, {'label': 'password', 'data': [2, 1]}], + } + + # default time interval is 'month' + month_data = data + resp = app.get('/api/statistics/%s/' % event_name, headers=headers) + data = resp.json['data'] + data['series'].sort(key=lambda x: x['label']) + assert month_data == data + + resp = app.get( + '/api/statistics/%s/?time_interval=month&services_ou=default' % event_name, headers=headers + ) + data = resp.json['data'] + data['series'].sort(key=lambda x: x['label']) + assert data == { + 'x_labels': ['2020-02', '2020-03'], + 'series': [{'label': 'password', 'data': [1, 1]}], + } + + # legacy way to filter by service OU + services_ou_data = data + resp = app.get('/api/statistics/%s/?time_interval=month&ou=default' % event_name, headers=headers) + data = resp.json['data'] + data['series'].sort(key=lambda x: x['label']) + assert services_ou_data == data + + resp = app.get( + '/api/statistics/%s/?time_interval=month&users_ou=default&service=agendas default' % event_name, + headers=headers, + ) + data = resp.json['data'] + assert data == { + 'x_labels': ['2020-02'], + 'series': [{'label': 'password', 'data': [1]}], + } + + resp = app.get('/api/statistics/%s/?time_interval=month&users_ou=default' % event_name, headers=headers) + data = resp.json['data'] + assert data == {'x_labels': ['2020-02'], 'series': [{'label': 'password', 'data': [1]}]} + + resp = app.get( + '/api/statistics/%s/?time_interval=month&service=agendas default' % event_name, headers=headers + ) + data = resp.json['data'] + assert data == {'x_labels': ['2020-02', '2020-03'], 'series': [{'label': 'password', 'data': [1, 1]}]} + + resp = app.get( + '/api/statistics/%s/?time_interval=month&start=2020-03-01T01:01' % event_name, headers=headers + ) + data = resp.json['data'] + data['series'].sort(key=lambda x: x['label']) + assert data == { + 'x_labels': ['2020-03'], + 'series': [{'label': 'FranceConnect', 'data': [1]}, {'label': 'password', 'data': [1]}], + } + + resp = app.get( + '/api/statistics/%s/?time_interval=month&end=2020-03-01T01:01' % event_name, headers=headers + ) + data = resp.json['data'] + assert data == {'x_labels': ['2020-02'], 'series': [{'label': 'password', 'data': [2]}]} + + resp = app.get('/api/statistics/%s/?time_interval=month&end=2020-03-01' % event_name, headers=headers) + data = resp.json['data'] + assert data == {'x_labels': ['2020-02'], 'series': [{'label': 'password', 'data': [2]}]} + + resp = app.get( + '/api/statistics/%s/?time_interval=year&service=portal second' % event_name, headers=headers + ) + data = resp.json['data'] + data['series'].sort(key=lambda x: x['label']) + assert data == { + 'x_labels': ['2020'], + 'series': [{'label': 'FranceConnect', 'data': [1]}, {'label': 'password', 'data': [1]}], + } + + resp = app.get('/api/statistics/service_%s/?time_interval=month' % event_name, headers=headers) + data = resp.json['data'] + data['series'].sort(key=lambda x: x['label']) + assert data == { + 'x_labels': ['2020-02', '2020-03'], + 'series': [{'label': 'agendas', 'data': [1, 1]}, {'label': 'portal', 'data': [1, 1]}], + } + + resp = app.get('/api/statistics/service_ou_%s/?time_interval=month' % event_name, headers=headers) + data = resp.json['data'] + data['series'].sort(key=lambda x: x['label']) + assert data == { + 'x_labels': ['2020-02', '2020-03'], + 'series': [ + {'label': 'Default organizational unit', 'data': [1, 1]}, + {'label': 'Second OU', 'data': [1, 1]}, + ], + } + + +def test_api_statistics_no_crash_older_drf(app, admin): + headers = basic_authorization_header(admin) + expected_status = 200 if drf_version > '3.9' else 404 + app.get('/api/statistics/login/?time_interval=month', headers=headers, status=expected_status) -- 2.33.0