Projet

Général

Profil

0001-api-record-actions-in-journal-48010.patch

Valentin Deniaud, 20 mai 2021 12:00

Télécharger (28,7 ko)

Voir les différences:

Subject: [PATCH] api: record actions in journal (#48010)

 src/authentic2/api_views.py                   | 39 +++++++++-
 .../apps/journal/migrations/0002_event_api.py | 18 +++++
 src/authentic2/apps/journal/models.py         |  9 ++-
 src/authentic2/apps/journal/search_engine.py  |  9 +++
 .../journal/templates/journal/event_list.html |  2 +-
 src/authentic2/apps/journal/utils.py          |  3 +
 src/authentic2/manager/journal_event_types.py | 45 +++++++-----
 tests/test_api.py                             | 71 +++++++++++++++++--
 tests/test_manager_journal.py                 | 11 ++-
 tests/utils.py                                | 14 ++--
 10 files changed, 189 insertions(+), 32 deletions(-)
 create mode 100644 src/authentic2/apps/journal/migrations/0002_event_api.py
src/authentic2/api_views.py
343 343
    def rpc(self, request, serializer):
344 344
        serializer.user.set_password(serializer.validated_data['new_password'])
345 345
        serializer.user.save()
346
        request.journal.record('manager.user.password.change', form=serializer, api=True)
346 347
        return {'result': 1}, status.HTTP_200_OK
347 348

  
348 349

  
......
778 779
            if not self.request.user.has_perm(perm):
779 780
                raise PermissionDenied(u'You do not have permission %s' % perm)
780 781

  
782
    def perform_create(self, serializer):
783
        super().perform_create(serializer)
784
        self.request.journal.record('manager.user.creation', form=serializer, api=True)
785

  
786
    def perform_update(self, serializer):
787
        super().perform_update(serializer)
788
        attributes = serializer.validated_data.pop('attributes', {})
789
        serializer.validated_data.update(attributes)
790
        self.request.journal.record('manager.user.profile.edit', form=serializer, api=True)
791

  
781 792
    def perform_destroy(self, instance):
782 793
        self.check_perm('custom_user.delete_user', instance.ou)
794
        self.request.journal.record('manager.user.deletion', target_user=instance, api=True)
783 795
        super(UsersAPI, self).perform_destroy(instance)
784 796

  
785 797
    class SynchronizationSerializer(serializers.Serializer):
......
820 832
            )
821 833

  
822 834
        utils.send_password_reset_mail(user, request=request)
835
        request.journal.record('manager.user.password.reset.request', target_user=user, api=True)
823 836
        return Response(status=status.HTTP_204_NO_CONTENT)
824 837

  
825 838
    @action(detail=True, methods=['post'], permission_classes=(DjangoPermission('custom_user.change_user'),))
......
875 888
    def perform_destroy(self, instance):
876 889
        if not self.request.user.has_perm(perm='a2_rbac.delete_role', obj=instance):
877 890
            raise PermissionDenied(u'User %s can\'t create role %s' % (self.request.user, instance))
891
        self.request.journal.record('manager.role.deletion', role=instance, api=True)
878 892
        super(RolesAPI, self).perform_destroy(instance)
879 893

  
894
    def perform_create(self, serializer):
895
        super().perform_create(serializer)
896
        self.request.journal.record('manager.role.creation', role=serializer.instance, api=True)
897

  
898
    def perform_update(self, serializer):
899
        super().perform_update(serializer)
900
        self.request.journal.record('manager.role.edit', role=serializer.instance, form=serializer, api=True)
901

  
880 902

  
881 903
class RolesMembersAPI(UsersAPI):
882 904
    def initial(self, request, *args, **kwargs):
......
919 941
        if not request.user.has_perm('a2_rbac.manage_members_role', obj=self.role):
920 942
            raise PermissionDenied(u'User not allowed to manage role members')
921 943
        self.role.members.add(self.member)
944
        request.journal.record('manager.role.membership.grant', role=self.role, member=self.member, api=True)
922 945
        return Response(
923 946
            {'result': 1, 'detail': _('User successfully added to role')}, status=status.HTTP_201_CREATED
924 947
        )
......
927 950
        if not request.user.has_perm('a2_rbac.manage_members_role', obj=self.role):
928 951
            raise PermissionDenied(u'User not allowed to manage role members')
929 952
        self.role.members.remove(self.member)
953
        request.journal.record(
954
            'manager.role.membership.removal', role=self.role, member=self.member, api=True
955
        )
930 956
        return Response(
931 957
            {'result': 1, 'detail': _('User successfully removed from role')}, status=status.HTTP_200_OK
932 958
        )
......
944 970
        Role = get_role_model()
945 971
        User = get_user_model()
946 972
        self.role = get_object_or_404(Role, uuid=kwargs['role_uuid'])
947
        self.members = []
973
        self.members = set()
948 974

  
949 975
        perm = 'a2_rbac.manage_members_role'
950 976
        authorized = request.user.has_perm(perm, obj=self.role)
......
967 993
                    _("Missing 'uuid' key for dict entry %s " "of the 'data' payload") % entry
968 994
                )
969 995
            try:
970
                self.members.append(User.objects.get(uuid=uuid))
996
                self.members.add(User.objects.get(uuid=uuid))
971 997
            except User.DoesNotExist:
972 998
                raise ValidationError(_('No known user for UUID %s') % entry['uuid'])
973 999

  
......
976 1002

  
977 1003
    def post(self, request, *args, **kwargs):
978 1004
        self.role.members.add(*self.members)
1005
        for member in self.members:
1006
            request.journal.record('manager.role.membership.grant', role=self.role, member=member, api=True)
979 1007
        return Response(
980 1008
            {'result': 1, 'detail': _('Users successfully added to role')}, status=status.HTTP_201_CREATED
981 1009
        )
982 1010

  
983 1011
    def delete(self, request, *args, **kwargs):
984 1012
        self.role.members.remove(*self.members)
1013
        for member in self.members:
1014
            request.journal.record('manager.role.membership.removal', role=self.role, member=member, api=True)
985 1015
        return Response(
986 1016
            {'result': 1, 'detail': _('Users successfully removed from role')}, status=status.HTTP_200_OK
987 1017
        )
988 1018

  
989 1019
    def patch(self, request, *args, **kwargs):
1020
        old_members = set(self.role.members.all())
990 1021
        self.role.members.set(self.members)
1022
        for member in self.members:
1023
            request.journal.record('manager.role.membership.grant', role=self.role, member=member, api=True)
1024
        for member in old_members.difference(self.members):
1025
            request.journal.record('manager.role.membership.removal', role=self.role, member=member, api=True)
991 1026
        return Response(
992 1027
            {'result': 1, 'detail': _('Users successfully assigned to role')}, status=status.HTTP_200_OK
993 1028
        )
src/authentic2/apps/journal/migrations/0002_event_api.py
1
# Generated by Django 2.2.19 on 2021-05-19 15:25
2

  
3
from django.db import migrations, models
4

  
5

  
6
class Migration(migrations.Migration):
7

  
8
    dependencies = [
9
        ('journal', '0001_initial'),
10
    ]
11

  
12
    operations = [
13
        migrations.AddField(
14
            model_name='event',
15
            name='api',
16
            field=models.BooleanField(default=False, verbose_name='API'),
17
        ),
18
    ]
src/authentic2/apps/journal/models.py
85 85
    retention_days = None
86 86

  
87 87
    @classmethod
88
    def record(cls, user=None, session=None, references=None, data=None):
88
    def record(cls, user=None, session=None, references=None, data=None, api=False):
89 89
        event_type = EventType.objects.get_for_name(cls.name)
90 90

  
91
        if user and not isinstance(user, User):
92
            # API user from DRF or OIDC
93
            user = None
94

  
91 95
        Event.objects.create(
92 96
            type=event_type,
93 97
            user=user,
94 98
            session_id=session and session.session_key,
95 99
            references=references or None,  # NULL values take less space
96 100
            data=data or None,  # NULL values take less space
101
            api=api,
97 102
        )
98 103

  
99 104
    @classmethod
......
320 325

  
321 326
    data = JSONField(verbose_name=_('data'), null=True)
322 327

  
328
    api = models.BooleanField(verbose_name=_('API'), default=False)
329

  
323 330
    objects = EventManager.from_queryset(EventQuerySet)()
324 331

  
325 332
    def __init__(self, *args, **kwargs):
src/authentic2/apps/journal/search_engine.py
136 136
You can use <tt>username:john</tt> to find all events related \
137 137
to users whose username is <tt>john</tt>.'''
138 138
    )
139

  
140
    def search_by_api(self, lexem):
141
        yield Q(api=bool(lexem == 'true'))
142

  
143
    search_by_api.documentation = _(
144
        '''\
145
You can use <tt>api:true</tt> to find all events related \
146
to API calls.'''
147
    )
src/authentic2/apps/journal/templates/journal/event_list.html
16 16
                    <tr data-event-id="{{ event.id }}" data-event-cursor="{{ event.cursor }}" data-event-type="{{ event.type.name }}">
17 17
                        <td class="journal-list--timestamp-column">{% block event-timestamp %}{{ event.timestamp }}{% endblock %}</td>
18 18
                        <td class="journal-list--user-column" {% if event.user %}data-user-id="{{ event.user.id }}"{% endif %}>{% block event-user %}{{ event.user.get_full_name|default:"-" }}{% endblock %}</td>
19
                        <td class="journal-list--session-column">{% block event-session %}{{ event.session_id_shortened|default:"-" }}{% endblock %}</td>
19
                        <td class="journal-list--session-column">{% block event-session %}{% if event.api %}API{% else %}{{ event.session_id_shortened|default:"-" }}{% endif %}{% endblock %}</td>
20 20
                        <td class="journal-list--message-column">{% block event-message %}{{ event.message|default:"-" }}{% endblock %}</td>
21 21
                    </tr>
22 22
        {% if forloop.last %}
src/authentic2/apps/journal/utils.py
26 26

  
27 27

  
28 28
def form_to_old_new(form):
29
    if hasattr(form, 'validated_data'):
30
        # form is a DRF serializer
31
        return {'new': {k: _json_value(v) for k, v in form.validated_data.items()}}
29 32
    old = {}
30 33
    new = {}
31 34
    for key in form.changed_data:
src/authentic2/manager/journal_event_types.py
31 31
    label = _('user creation')
32 32

  
33 33
    @classmethod
34
    def record(cls, user, session, form):
35
        super().record(user=user, session=session, references=[form.instance])
34
    def record(cls, user, session, form, api=False):
35
        super().record(user=user, session=session, references=[form.instance], api=api)
36 36

  
37 37
    @classmethod
38 38
    def get_message(cls, event, context):
......
51 51
    label = _('user profile edit')
52 52

  
53 53
    @classmethod
54
    def record(cls, user, session, form):
55
        super().record(user=user, session=session, references=[form.instance], data=form_to_old_new(form))
54
    def record(cls, user, session, form, api=False):
55
        if hasattr(form, 'validated_data'):
56
            attributes = form.validated_data.pop('attributes', {})
57
            form.validated_data.update(attributes)
58
        super().record(
59
            user=user, session=session, references=[form.instance], data=form_to_old_new(form), api=api
60
        )
56 61

  
57 62
    @classmethod
58 63
    def get_message(cls, event, context):
......
96 101
    label = _('user password change')
97 102

  
98 103
    @classmethod
99
    def record(cls, user, session, form):
104
    def record(cls, user, session, form, api=False):
105
        cleaned_data = getattr(form, 'cleaned_data', {})
100 106
        data = {
101
            'generate_password': form.cleaned_data['generate_password'],
102
            'send_mail': form.cleaned_data['send_mail'],
107
            'generate_password': cleaned_data.get('generate_password', False),
108
            'send_mail': cleaned_data.get('send_mail', False),
103 109
        }
104
        super().record(user=user, session=session, references=[form.instance], data=data)
110
        super().record(user=user, session=session, references=[form.instance], data=data, api=api)
105 111

  
106 112
    @classmethod
107 113
    def get_message(cls, event, context):
......
126 132
    label = _('user password reset request')
127 133

  
128 134
    @classmethod
129
    def record(cls, user, session, target_user):
135
    def record(cls, user, session, target_user, api=False):
130 136
        super().record(
131
            user=user, session=session, references=[target_user], data={'email': target_user.email}
137
            user=user, session=session, references=[target_user], data={'email': target_user.email}, api=api
132 138
        )
133 139

  
134 140
    @classmethod
......
219 225
    label = _('user deletion')
220 226

  
221 227
    @classmethod
222
    def record(cls, user, session, target_user):
223
        super().record(user=user, session=session, references=[target_user])
228
    def record(cls, user, session, target_user, api=False):
229
        super().record(user=user, session=session, references=[target_user], api=api)
224 230

  
225 231
    @classmethod
226 232
    def get_message(cls, event, context):
......
259 265

  
260 266
class RoleEventsMixin(EventTypeDefinition):
261 267
    @classmethod
262
    def record(self, user, session, role, references=None, data=None):
268
    def record(self, user, role, session=None, references=None, data=None, api=False):
263 269
        references = references or []
264 270
        references = [role] + references
265 271
        data = data or {}
......
269 275
            session=session,
270 276
            references=references,
271 277
            data=data,
278
            api=api,
272 279
        )
273 280

  
274 281

  
......
291 298
    label = _('role edit')
292 299

  
293 300
    @classmethod
294
    def record(cls, user, session, role, form):
295
        super().record(user=user, session=session, role=role, data=form_to_old_new(form))
301
    def record(cls, user, session, role, form, api=False):
302
        super().record(user=user, session=session, role=role, data=form_to_old_new(form), api=api)
296 303

  
297 304
    @classmethod
298 305
    def get_message(cls, event, context):
......
325 332
    label = _('role membership grant')
326 333

  
327 334
    @classmethod
328
    def record(cls, user, session, role, member):
335
    def record(cls, user, session, role, member, api=False):
329 336
        data = {'member_name': member.get_full_name()}
330
        super().record(user=user, session=session, role=role, references=[member], data=data)
337
        super().record(user=user, session=session, role=role, references=[member], data=data, api=api)
331 338

  
332 339
    @classmethod
333 340
    def get_message(cls, event, context):
......
347 354
    label = _('role membership removal')
348 355

  
349 356
    @classmethod
350
    def record(cls, user, session, role, member):
357
    def record(cls, user, session, role, member, api=False):
351 358
        data = {'member_name': member.get_full_name()}
352
        super().record(user=user, session=session, role=role, references=[member], data=data)
359
        super().record(user=user, session=session, role=role, references=[member], data=data, api=api)
353 360

  
354 361
    @classmethod
355 362
    def get_message(cls, event, context):
tests/test_api.py
42 42
from django_rbac.models import SEARCH_OP
43 43
from django_rbac.utils import get_ou_model, get_role_model
44 44

  
45
from .utils import basic_authorization_header, get_link_from_mail, login
45
from .utils import assert_event, basic_authorization_header, get_link_from_mail, login
46 46

  
47 47
pytestmark = pytest.mark.django_db
48 48

  
......
218 218
    user = User.objects.get(id=simple_user.id)
219 219
    assert user.email_verified
220 220
    assert resp.json['email_verified']
221
    assert_event('manager.user.profile.edit', user=admin, api=True)
221 222

  
222 223
    user.email_verified = True
223 224
    user.email = 'johnny.doeny@foo.bar'
......
304 305
    assert resp.json['email_verified']
305 306
    user = User.objects.get(uuid=resp.json['uuid'])
306 307
    assert user.email_verified
308
    assert_event('manager.user.creation', user=admin, api=True)
307 309

  
308 310

  
309 311
def test_api_users_create_without_email_verified(settings, app, admin):
......
867 869
    elif authorized:
868 870
        assert resp.json['result'] == 1
869 871
        assert resp.json['detail'] == 'User successfully added to role'
872
        assert_event(
873
            'manager.role.membership.grant',
874
            user=api_user if isinstance(api_user, User) else None,
875
            api=True,
876
            role_name=role.name,
877
            member_name=member.get_full_name(),
878
        )
870 879
    else:
871 880
        assert resp.json['result'] == 0
872 881
        assert resp.json['errors'] == 'User not allowed to manage role members'
......
893 902
        assert resp.json['detail'] == 'User successfully removed from role'
894 903
        resp = app.get('/api/roles/{0}/members/{1}/'.format(role.uuid, member.uuid), status=404)
895 904
        assert resp.json == {'result': 0, 'errors': {'detail': 'Not found.'}}
905
        assert_event(
906
            'manager.role.membership.removal',
907
            user=api_user if isinstance(api_user, User) else None,
908
            api=True,
909
            role_name=role.name,
910
            member_name=member.get_full_name(),
911
        )
896 912
    else:
897 913
        assert resp.json['result'] == 0
898 914
        assert resp.json['errors'] == 'User not allowed to manage role members'
......
928 944
        assert resp.json['detail'] == 'Users successfully added to role'
929 945
        for m in [member, member_rando2]:
930 946
            assert m in role.members.all()
947
            assert_event(
948
                'manager.role.membership.grant',
949
                user=api_user if isinstance(api_user, User) else None,
950
                api=True,
951
                role_name=role.name,
952
                member_name=m.get_full_name(),
953
            )
931 954
    else:
932 955
        assert resp.json['result'] == 0
933 956
        assert resp.json['errors'] == 'User not allowed to manage role members'
......
963 986
        assert resp.json['detail'] == 'Users successfully removed from role'
964 987
        for m in [member, member_rando2]:
965 988
            assert m not in role.members.all()
989
            assert_event(
990
                'manager.role.membership.removal',
991
                user=api_user if isinstance(api_user, User) else None,
992
                api=True,
993
                role_name=role.name,
994
                member_name=m.get_full_name(),
995
            )
966 996
    else:
967 997
        assert resp.json['result'] == 0
968 998
        assert resp.json['errors'] == 'User not allowed to manage role members'
969 999

  
970 1000

  
971
def test_api_role_set_members(app, api_user, role, member, member_rando2):
1001
def test_api_role_set_members(app, api_user, role, member, member_rando2, ou_rando):
1002
    user = User.objects.create(
1003
        username='test3', first_name='test3', last_name='test3', email='test3@test.org', ou=ou_rando
1004
    )
972 1005
    app.authorization = ('Basic', (api_user.username, api_user.username))
973 1006

  
974 1007
    authorized = api_user.has_perm('a2_rbac.manage_members_role', role)
......
984 1017

  
985 1018
    payload = {"data": []}
986 1019

  
1020
    role.members.add(user)
987 1021
    for m in [member, member_rando2, member_rando2]:  # test no duplicate
988 1022
        payload['data'].append({"uuid": m.uuid})
989 1023

  
......
999 1033
        assert len(role.members.all()) == 2
1000 1034
        for m in [member, member_rando2]:
1001 1035
            assert m in role.members.all()
1036
            assert_event(
1037
                'manager.role.membership.grant',
1038
                user=api_user if isinstance(api_user, User) else None,
1039
                api=True,
1040
                role_name=role.name,
1041
                member_name=m.get_full_name(),
1042
            )
1043
        assert_event(
1044
            'manager.role.membership.removal',
1045
            user=api_user if isinstance(api_user, User) else None,
1046
            api=True,
1047
            role_name=role.name,
1048
            member_name=user.get_full_name(),
1049
        )
1002 1050
    else:
1003 1051
        assert resp.json['result'] == 0
1004 1052
        assert resp.json['errors'] == 'User not allowed to manage role members'
......
1307 1355
    response = app.post_json(url, params=payload)
1308 1356
    assert response.json['result'] == 1
1309 1357
    assert User.objects.get(username='john.doe').check_password('password2')
1358
    assert_event('manager.user.password.change', user=admin, api=True)
1310 1359

  
1311 1360

  
1312 1361
def test_password_reset(app, ou1, admin, user_ou1, mailoutbox):
......
1328 1377
    mail = mailoutbox[0]
1329 1378
    assert mail.to[0] == email
1330 1379
    assert 'http://testserver/accounts/password/reset/confirm/' in mail.body
1380
    assert_event('manager.user.password.reset.request', user=admin, api=True)
1331 1381

  
1332 1382

  
1333 1383
def test_users_email(app, ou1, admin, user_ou1, mailoutbox):
......
1355 1405
    app.authorization = ('Basic', (admin_ou1.username, admin_ou1.username))
1356 1406
    app.delete('/api/roles/{}/'.format(role_ou1.uuid))
1357 1407
    assert not len(Role.objects.filter(slug='role_ou1'))
1408
    assert_event('manager.role.deletion', user=admin_ou1, api=True, role_name=role_ou1.name)
1358 1409

  
1359 1410

  
1360 1411
def test_api_delete_role_unauthorized(app, simple_user, role_ou1):
......
1370 1421
        'slug': 'updated-role',
1371 1422
    }
1372 1423
    app.patch_json('/api/roles/{}/'.format(role_ou1.uuid), params=role_data)
1424
    assert_event('manager.role.edit', user=admin_ou1, api=True, role_name=role_ou1.name)
1373 1425

  
1374 1426
    # The role API won't change the organizational unit attribute:
1375 1427
    role_ou1.refresh_from_db()
......
1398 1450
    assert role_ou1.name == 'updated-role'
1399 1451
    assert role_ou1.slug == 'updated-role'
1400 1452
    assert role_ou1.ou.slug == 'ou1'
1453
    assert_event('manager.role.edit', user=admin_ou1, api=True, role_name=role_ou1.name)
1401 1454

  
1402 1455

  
1403 1456
def test_api_put_role_unauthorized(app, simple_user, role_ou1, ou1):
......
1415 1468

  
1416 1469
    role_data = {'slug': 'coffee-manager', 'name': 'Coffee Manager', 'ou': 'ou1'}
1417 1470
    resp = app.post_json('/api/roles/', params=role_data)
1471
    assert_event('manager.role.creation', user=admin_ou1, api=True, role_name='Coffee Manager')
1418 1472
    assert isinstance(resp.json, dict)
1419 1473
    uuid = resp.json['uuid']
1420 1474

  
......
1978 2032
    ]
1979 2033

  
1980 2034
    # update with values pass
2035
    del payload['id']
1981 2036
    payload['prefered_color'] = 'blue'
1982
    payload['date'] = '1515-1-15'
1983
    payload['birthdate'] = '1900-2-22'
2037
    payload['date'] = '1515-01-15'
2038
    payload['birthdate'] = '1900-02-22'
1984 2039
    resp = app.put_json(
1985 2040
        '/api/users/{}/'.format(simple_user.uuid), params=payload, headers=headers, status=200
1986 2041
    )
2042
    assert_event('manager.user.profile.edit', user=admin, api=True, new=payload)
1987 2043

  
1988 2044
    # value are properly returned on a get
1989 2045
    resp = app.get('/api/users/{}/'.format(simple_user.uuid), headers=headers, status=200)
......
2355 2411
    assert User.objects.get(username='john.doe').check_password('password2')
2356 2412

  
2357 2413

  
2414
def test_api_users_delete(settings, app, admin, simple_user):
2415
    headers = basic_authorization_header(admin)
2416
    resp = app.delete_json('/api/users/{}/'.format(simple_user.uuid), headers=headers)
2417
    assert not User.objects.filter(pk=simple_user.pk).exists()
2418
    assert_event('manager.user.deletion', user=admin, api=True)
2419

  
2420

  
2358 2421
@pytest.mark.skipif(drf_version.startswith('3.4'), reason='no support for old django rest framework')
2359 2422
def test_api_statistics_list(app, admin):
2360 2423
    OU = get_ou_model()
tests/test_manager_journal.py
79 79
    make("user.login.failure", username="agent")
80 80
    make("user.login", user=user, session=session1, how="password")
81 81
    make("user.password.change", user=user, session=session1)
82
    edit_profile_form = mock.Mock()
82
    edit_profile_form = mock.Mock(spec=["instance", "initial", "changed_data", "cleaned_data"])
83 83
    edit_profile_form.initial = {'email': "user@example.com", 'first_name': "John"}
84 84
    edit_profile_form.changed_data = ["first_name"]
85 85
    edit_profile_form.cleaned_data = {'first_name': "Jane"}
......
973 973
        for p in zip(pq('tbody td.journal-list--user-column'), pq('tbody td.journal-list--message-column'))
974 974
    ] == [['Johnny doe', 'login using password']]
975 975

  
976
    Event.objects.filter(type__name='manager.user.creation').update(api=True)
977
    response.form.set('search', 'api:true')
978
    response = response.form.submit()
979
    assert (
980
        text_content(response.pyquery('tbody tr td.journal-list--message-column')[0]).strip()
981
        == 'creation of user "Johnny doe"'
982
    )
983
    assert text_content(response.pyquery('tbody tr td.journal-list--session-column')[0]).strip() == 'API'
984

  
976 985
    response.form.set('search', '')
977 986
    response.form['event_type'].select(text='Profile changes')
978 987
    response = response.form.submit()
tests/utils.py
264 264
    return ''.join(node.itertext()) if node is not None else ''
265 265

  
266 266

  
267
def assert_event(event_type_name, user=None, session=None, service=None, **data):
268
    qs = Event.objects.filter(type__name=event_type_name)
267
def assert_event(event_type_name, user=None, session=None, service=None, api=False, **data):
268
    qs = Event.objects.filter(type__name=event_type_name, api=api)
269 269
    if user:
270 270
        qs = qs.filter(user=user)
271 271
    else:
......
279 279
    else:
280 280
        qs = qs.exclude(qs._which_references_query(models.Service))
281 281

  
282
    assert qs.count() == 1
282
    count = qs.count()
283
    assert count > 0
283 284

  
284
    if data:
285
    if not data:
286
        assert count == 1
287

  
288
    if data and count == 1:
285 289
        event = qs.get()
286 290
        assert event.data, 'no event.data, should be %s' % data
287 291
        for key, value in data.items():
......
291 295
                event.data.get(key),
292 296
                value,
293 297
            )
298
    elif data and count > 1:
299
        assert qs.filter(**{'data__' + k: v for k, v in data.items()}).count() == 1
294 300

  
295 301

  
296 302
@httmock.HTTMock
297
-