Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / tests / functional / test_root.py @ 011743be

History | View | Annotate | Download (22.2 KB)

1
# -*- coding: utf-8 -*-
2
# Copyright (C) 2006-2020 CS GROUP - France
3
# License: GNU GPL v2 <http://www.gnu.org/licenses/gpl-2.0.html>
4

    
5
"""
6
Functional test suite for the root controller.
7

8
This is an example of how functional tests can be written for controllers.
9

10
As opposed to a unit-test, which test a small unit of functionality,
11
functional tests exercise the whole application and its WSGI stack.
12

13
Please read http://pythonpaste.org/webtest/ for more information.
14

15
"""
16
from nose.tools import assert_true, assert_false, assert_equal
17
from datetime import datetime
18
from calendar import timegm
19
import transaction
20

    
21
from vigilo.models.session import DBSession
22
from vigilo.models.demo import functions
23
from vigilo.models.tables import EventHistory, CorrEvent, User, \
24
                            Permission, Host, UserGroup, DataPermission
25
from vigiboard.tests import TestController
26
from tg import config
27

    
28
def populate_DB():
29
    """ Peuple la base de données. """
30
    # On ajoute un groupe d'hôtes et un groupe de services.
31
    supitemmanagers = functions.add_supitemgroup(u'managersgroup')
32

    
33
    usergroup = UserGroup.by_group_name(u'users_with_access')
34
    DBSession.add(DataPermission(
35
        group=supitemmanagers,
36
        usergroup=usergroup,
37
        access=u'w',
38
    ))
39
    DBSession.flush()
40

    
41
    # On crée un 2 hôtes, et on les ajoute au groupe d'hôtes.
42
    host1 = functions.add_host(u'host1')
43
    host2 = functions.add_host(u'host2')
44
    supitemmanagers.supitems.append(host1)
45
    supitemmanagers.supitems.append(host2)
46
    DBSession.flush()
47

    
48
    # On crée 2 services de bas niveau, et on les ajoute au groupe de services.
49
    service1 = functions.add_lowlevelservice(host1, u'service1')
50
    service2 = functions.add_lowlevelservice(host2, u'service2')
51
    supitemmanagers.supitems.append(service1)
52
    supitemmanagers.supitems.append(service2)
53
    DBSession.flush()
54

    
55
    return ([host1, host2], [service1, service2])
56

    
57
def add_correvent_caused_by(supitem, timestamp,
58
        correvent_status=CorrEvent.ACK_NONE, event_status=u"WARNING"):
59
    """
60
    Ajoute dans la base de données un évènement corrélé causé
61
    par un incident survenu sur l'item passé en paramètre.
62
    Génère un historique pour les tests.
63
    """
64

    
65
    # Ajout d'un événement brut et d'un événement corrélé.
66
    event = functions.add_event(supitem, event_status, u'foo')
67
    aggregate = functions.add_correvent([event], status=correvent_status)
68
    return aggregate.idcorrevent
69

    
70

    
71
class TestRootController(TestController):
72
    """ Classe de test du root controller """
73
    def setUp(self):
74
        super(TestRootController, self).setUp()
75
        perm = Permission.by_permission_name(u'vigiboard-access')
76
        perm2 = Permission.by_permission_name(u'vigiboard-update')
77

    
78
        user = User(
79
            user_name=u'access',
80
            fullname=u'',
81
            email=u'user.has@access',
82
        )
83
        usergroup = UserGroup(group_name=u'users_with_access')
84
        usergroup.permissions.append(perm)
85
        usergroup.permissions.append(perm2)
86
        user.usergroups.append(usergroup)
87
        DBSession.add(user)
88
        DBSession.add(usergroup)
89
        DBSession.flush()
90

    
91
        user = User(
92
            user_name=u'limited_access',
93
            fullname=u'',
94
            email=u'user.has.no@access',
95
        )
96
        usergroup = UserGroup(group_name=u'users_with_limited_access')
97
        usergroup.permissions.append(perm)
98
        usergroup.permissions.append(perm2)
99
        user.usergroups.append(usergroup)
100
        DBSession.add(user)
101
        DBSession.add(usergroup)
102
        DBSession.flush()
103

    
104
    def test_index(self):
105
        """Test that access to the root webpage is restricted."""
106

    
107
        response = self.app.get('/', status=401)
108
        msg = 'Unauthorized'
109
        assert_true(msg in response)
110

    
111
    def test_update_host_correvents_status(self):
112
        """Màj du statut d'évènements corrélés causés par des hôtes"""
113

    
114
        # On peuple la BDD avec 2 hôtes, 2 services de bas niveau,
115
        # et un groupe d'hôtes et de services associés à ces items.
116
        (hosts, _services) = populate_DB()
117

    
118
        # On ajoute 2 évènements corrélés causés par ces hôtes
119
        timestamp = datetime.utcnow()
120
        correvent1_id = add_correvent_caused_by(hosts[0], timestamp)
121
        correvent2_id = add_correvent_caused_by(hosts[1], timestamp)
122
        transaction.commit()
123

    
124
        ### 1er cas : L'utilisateur n'est pas connecté.
125
        # On vérifie que le plugin retourne bien une erreur 401.
126
        response = self.app.post(
127
            '/update', {
128
                "id" : str(correvent1_id) + "," + str(correvent2_id),
129
                "last_modification": timegm(timestamp.utctimetuple()),
130
                "trouble_ticket" : u"foo",
131
                "ack" : u'NoChange',
132
            }, status = 401)
133

    
134
        ### 2ème cas : L'utilisateur utilisé pour
135
        # se connecter à Vigiboard est 'limited_access'.
136
        environ = {'REMOTE_USER': 'limited_access'}
137

    
138
        # On s'attend à ce qu'une erreur 302 soit renvoyée, et à
139
        # ce qu'un message d'erreur précise à l'utilisateur qu'il
140
        # n'a pas la permission de modifier ces évènements.
141
        response = self.app.post(
142
            '/update', {
143
                "id" : str(correvent1_id) + "," + str(correvent2_id),
144
                "ack" : u'NoChange',
145
                "trouble_ticket" : u"foo",
146
                "last_modification": timegm(timestamp.utctimetuple()),
147
            }, status = 302, extra_environ = environ)
148

    
149
        response = response.follow(status=200, extra_environ = environ)
150
        assert_true(response.lxml.xpath(
151
            '//div[@id="flash"]/div[@class="error"]'))
152

    
153
        ### 3ème cas : L'utilisateur utilisé pour
154
        # se connecter à Vigiboard est 'access'.
155
        environ = {'REMOTE_USER': 'access'}
156

    
157
        # On s'attend à ce que le statut de la requête soit 302,
158
        # et à ce qu'un message informe l'utilisateur que les
159
        # évènements corrélés sélectionnées ont bien été mis à jour.
160
        response = self.app.post(
161
            '/update', {
162
                "id" : str(correvent1_id) + "," + str(correvent2_id),
163
                "last_modification": timegm(timestamp.utctimetuple()),
164
                "trouble_ticket" : u"foo",
165
                "ack" : u'NoChange',
166
            }, status = 302, extra_environ = environ)
167

    
168
        response = response.follow(status=200, extra_environ = environ)
169
        assert_false(response.lxml.xpath(
170
            '//div[@id="flash"]/div[@class="error"]'))
171
        assert_true(response.lxml.xpath(
172
            '//div[@id="flash"]/div[@class="ok"]'))
173

    
174
        # On s'assure que le ticket de l'évènement corrélé
175
        # a bien été mis à jour dans la base de données.
176
        correvents = DBSession.query(
177
            CorrEvent.trouble_ticket
178
            ).filter(CorrEvent.idcorrevent.in_([correvent1_id, correvent2_id])
179
            ).all()
180

    
181
        assert_equal(correvents[0].trouble_ticket, u"foo")
182
        assert_equal(correvents[1].trouble_ticket, u"foo")
183

    
184
    def test_update_service_correvents_status(self):
185
        """Màj du statut d'évènements corrélés causés par des SBN"""
186

    
187
        # On peuple la BDD avec 2 hôtes, 2 services de bas niveau,
188
        # et un groupe d'hôtes et de services associés à ces items.
189
        (_hosts, services) = populate_DB()
190

    
191
        # On ajoute 2 évènements corrélés causés par ces hôtes
192
        timestamp = datetime.utcnow()
193
        correvent1_id = add_correvent_caused_by(services[0], timestamp)
194
        correvent2_id = add_correvent_caused_by(services[1], timestamp)
195

    
196
        transaction.commit()
197

    
198
        ### 1er cas : L'utilisateur n'est pas connecté.
199
        # On vérifie que le plugin retourne bien une erreur 401.
200
        response = self.app.post(
201
            '/update', {
202
                "id" : str(correvent1_id) + "," + str(correvent2_id),
203
                "last_modification": timegm(timestamp.utctimetuple()),
204
                "trouble_ticket" : u"foo",
205
                "ack" : u'NoChange',
206
            }, status = 401)
207

    
208
        ### 2ème cas : L'utilisateur utilisé pour
209
        # se connecter à Vigiboard est 'limited_access'.
210
        environ = {'REMOTE_USER': 'limited_access'}
211

    
212
        # On s'attend à ce qu'une erreur 302 soit renvoyée, et à
213
        # ce qu'un message d'erreur précise à l'utilisateur qu'il
214
        # n'a pas la permission de modifier ces évènements.
215
        response = self.app.post(
216
            '/update', {
217
                "id" : str(correvent1_id) + "," + str(correvent2_id),
218
                "last_modification": timegm(timestamp.utctimetuple()),
219
                "trouble_ticket" : u"foo",
220
                "ack" : u'NoChange',
221
            }, status = 302, extra_environ = environ)
222

    
223
        response = response.follow(status=200, extra_environ = environ)
224
        assert_true(response.lxml.xpath(
225
            '//div[@id="flash"]/div[@class="error"]'))
226

    
227
        ### 3ème cas : L'utilisateur utilisé pour
228
        # se connecter à Vigiboard est 'access'.
229
        environ = {'REMOTE_USER': 'access'}
230

    
231
        # On s'attend à ce que le statut de la requête soit 302,
232
        # et à ce qu'un message informe l'utilisateur que les
233
        # évènements corrélés sélectionnées ont bien été mis à jour.
234
        response = self.app.post(
235
            '/update', {
236
                "id" : str(correvent1_id) + "," + str(correvent2_id),
237
                "last_modification": timegm(timestamp.utctimetuple()),
238
                "trouble_ticket" : u"foo",
239
                "ack" : u'NoChange',
240
            }, status = 302, extra_environ = environ)
241

    
242
        response = response.follow(status=200, extra_environ = environ)
243
        assert_false(response.lxml.xpath(
244
            '//div[@id="flash"]/div[@class="error"]'))
245
        assert_true(response.lxml.xpath(
246
            '//div[@id="flash"]/div[@class="ok"]'))
247

    
248
        # On s'assure que le ticket de l'évènement corrélé
249
        # a bien été mis à jour dans la base de données.
250
        correvents = DBSession.query(
251
            CorrEvent.trouble_ticket
252
            ).filter(CorrEvent.idcorrevent.in_([correvent1_id, correvent2_id])
253
            ).all()
254
        assert_equal(correvents[0].trouble_ticket, u"foo")
255
        assert_equal(correvents[1].trouble_ticket, u"foo")
256

    
257
    def test_update_host_correvents_tickets(self):
258
        """Màj de tickets d'évènements corrélés causés par des hôtes"""
259

    
260
        # On peuple la BDD avec 2 hôtes, 2 services de bas niveau,
261
        # et un groupe d'hôtes et de services associés à ces items.
262
        (hosts, _services) = populate_DB()
263

    
264
        # On ajoute 2 évènements corrélés causés par ces hôtes
265
        timestamp = datetime.utcnow()
266
        correvent1_id = add_correvent_caused_by(hosts[0], timestamp)
267
        correvent2_id = add_correvent_caused_by(hosts[1], timestamp)
268
        transaction.commit()
269

    
270
        ### 1er cas : L'utilisateur n'est pas connecté.
271
        # On vérifie que le plugin retourne bien une erreur 401.
272
        response = self.app.post(
273
            '/update', {
274
                "id" : str(correvent1_id) + "," + str(correvent2_id),
275
                "last_modification": timegm(timestamp.utctimetuple()),
276
                "trouble_ticket" : "",
277
                "ack" : u'Acknowledged',
278
            }, status = 401)
279

    
280
        ### 2ème cas : L'utilisateur utilisé pour
281
        # se connecter à Vigiboard est 'limited_access'.
282
        environ = {'REMOTE_USER': 'limited_access'}
283

    
284
        # On s'attend à ce qu'une erreur 302 soit renvoyée, et à
285
        # ce qu'un message d'erreur précise à l'utilisateur qu'il
286
        # n'a pas la permission de modifier ces évènements.
287
        response = self.app.post(
288
            '/update', {
289
                "id" : str(correvent1_id) + "," + str(correvent2_id),
290
                "last_modification": timegm(timestamp.utctimetuple()),
291
                "trouble_ticket" : "",
292
                "ack" : u'Acknowledged',
293
            }, status = 302, extra_environ = environ)
294

    
295
        response = response.follow(status=200, extra_environ = environ)
296
        assert_true(response.lxml.xpath(
297
            '//div[@id="flash"]/div[@class="error"]'))
298

    
299
        ### 3ème cas : L'utilisateur utilisé pour
300
        # se connecter à Vigiboard est 'access'.
301
        environ = {'REMOTE_USER': 'access'}
302

    
303
        # On s'attend à ce que le statut de la requête soit 302,
304
        # et à ce qu'un message informe l'utilisateur que les
305
        # évènements corrélés sélectionnées ont bien été mis à jour.
306
        response = self.app.post(
307
            '/update', {
308
                "id" : str(correvent1_id) + "," + str(correvent2_id),
309
                "last_modification": timegm(timestamp.utctimetuple()),
310
                "trouble_ticket" : "",
311
                "ack" : u'Acknowledged',
312
            }, status = 302, extra_environ = environ)
313

    
314
        response = response.follow(status=200, extra_environ = environ)
315
        assert_false(response.lxml.xpath(
316
            '//div[@id="flash"]/div[@class="error"]'))
317
        assert_true(response.lxml.xpath(
318
            '//div[@id="flash"]/div[@class="ok"]'))
319

    
320
        # On s'assure que le statut de l'évènement corrélé
321
        # a bien été mis à jour dans la base de données.
322
        correvents = DBSession.query(
323
            CorrEvent.ack
324
            ).filter(CorrEvent.idcorrevent.in_([correvent1_id, correvent2_id])
325
            ).all()
326
        assert_equal(correvents[0].ack, CorrEvent.ACK_KNOWN)
327
        assert_equal(correvents[1].ack, CorrEvent.ACK_KNOWN)
328

    
329

    
330
    def test_update_service_correvents_tickets(self):
331
        """Màj de tickets d'évènements corrélés causés par des SBN"""
332

    
333
        # On peuple la BDD avec 2 hôtes, 2 services de bas niveau,
334
        # et un groupe d'hôtes et de services associés à ces items.
335
        (_hosts, services) = populate_DB()
336

    
337
        # On ajoute 2 évènements corrélés causés par ces hôtes
338
        timestamp = datetime.utcnow()
339
        correvent1_id = add_correvent_caused_by(services[0], timestamp)
340
        correvent2_id = add_correvent_caused_by(services[1], timestamp)
341

    
342
        transaction.commit()
343

    
344
        ### 1er cas : L'utilisateur n'est pas connecté.
345
        # On vérifie que le plugin retourne bien une erreur 401.
346
        response = self.app.post(
347
            '/update', {
348
                "id" : str(correvent1_id) + "," + str(correvent2_id),
349
                "last_modification": timegm(timestamp.utctimetuple()),
350
                "trouble_ticket" : "",
351
                "ack" : u'Acknowledged',
352
            }, status = 401)
353

    
354
        ### 2ème cas : L'utilisateur utilisé pour
355
        # se connecter à Vigiboard est 'limited_access'.
356
        environ = {'REMOTE_USER': 'limited_access'}
357

    
358
        # On s'attend à ce qu'une erreur 302 soit renvoyée, et à
359
        # ce qu'un message d'erreur précise à l'utilisateur qu'il
360
        # n'a pas la permission de modifier ces évènements.
361
        response = self.app.post(
362
            '/update', {
363
                "id" : str(correvent1_id) + "," + str(correvent2_id),
364
                "last_modification": timegm(timestamp.utctimetuple()),
365
                "trouble_ticket" : "",
366
                "ack" : u'Acknowledged',
367
            }, status = 302, extra_environ = environ)
368

    
369
        response = response.follow(status=200, extra_environ = environ)
370
        assert_true(response.lxml.xpath(
371
            '//div[@id="flash"]/div[@class="error"]'))
372

    
373
        ### 3ème cas : L'utilisateur utilisé pour
374
        # se connecter à Vigiboard est 'access'.
375
        environ = {'REMOTE_USER': 'access'}
376

    
377
        # On s'attend à ce que le statut de la requête soit 302,
378
        # et à ce qu'un message informe l'utilisateur que les
379
        # évènements corrélés sélectionnées ont bien été mis à jour.
380
        response = self.app.post(
381
            '/update', {
382
                "id" : str(correvent1_id) + "," + str(correvent2_id),
383
                "last_modification": timegm(timestamp.utctimetuple()),
384
                "trouble_ticket" : "",
385
                "ack" : u'Acknowledged',
386
            }, status = 302, extra_environ = environ)
387

    
388
        response = response.follow(status=200, extra_environ = environ)
389
        assert_false(response.lxml.xpath(
390
            '//div[@id="flash"]/div[@class="error"]'))
391
        assert_true(response.lxml.xpath(
392
            '//div[@id="flash"]/div[@class="ok"]'))
393

    
394
        # On s'assure que le statut de l'évènement corrélé
395
        # a bien été mis à jour dans la base de données.
396
        correvents = DBSession.query(
397
            CorrEvent.ack
398
            ).filter(CorrEvent.idcorrevent.in_([correvent1_id, correvent2_id])
399
            ).all()
400
        assert_equal(correvents[0].ack, CorrEvent.ACK_KNOWN)
401
        assert_equal(correvents[1].ack, CorrEvent.ACK_KNOWN)
402

    
403
    def test_update_while_data_have_changed(self):
404
        """Màj d'un évènement corrélé modifié entre temps."""
405

    
406
        # On peuple la BDD avec 2 hôtes, 2 services de bas niveau,
407
        # et un groupe d'hôtes et de services associés à ces items.
408
        (_hosts, services) = populate_DB()
409

    
410
        # On ajoute 2 évènements corrélés causés par ces hôtes
411
        timestamp = datetime.utcnow()
412
        correvent1_id = add_correvent_caused_by(services[0], timestamp)
413
        add_correvent_caused_by(services[1], timestamp)
414

    
415
        # Date de modification du premier évènement corrélé
416
        later_date = datetime.utcnow()
417
        # Date du chargement de la page
418
        date = timegm(later_date.utctimetuple()) - 42
419

    
420
        # On ajoute une entrée dans l'historique de l'évènement brut
421
        # causant le premier évènement corrélé, portant pour timestamp
422
        # une date postérieure à celle du chargement de la page.
423
        correvent1 = DBSession.query(
424
            CorrEvent.idcause
425
            ).filter(CorrEvent.idcorrevent == correvent1_id).one()
426
        DBSession.add(EventHistory(
427
            type_action = u'Nagios update state',
428
            idevent = correvent1.idcause,
429
            timestamp = later_date))
430
        DBSession.flush()
431

    
432
        transaction.commit()
433

    
434
        # L'utilisateur utilisé pour se connecter à Vigiboard est 'access'.
435
        environ = {'REMOTE_USER': 'access'}
436

    
437
        # On s'attend à ce que le statut de la requête soit 302, et
438
        # à ce qu'un message d'erreur avise l'utilisateur que des
439
        # changements sont intervenus depuis le chargement de la page.
440
        response = self.app.post(
441
            '/update', {
442
                "id" : str(correvent1_id),
443
                "last_modification" : date,
444
                "trouble_ticket" : "",
445
                "ack" : u'Acknowledged',
446
            }, status = 302, extra_environ = environ)
447

    
448
        response = response.follow(status=200, extra_environ = environ)
449
        assert_true(response.lxml.xpath(
450
            '//div[@id="flash"]/div[@class="warning"]'))
451

    
452
        # On s'assure que le statut de l'évènement corrélé
453
        # n'a pas été modifié dans la base de données.
454
        status = DBSession.query(
455
            CorrEvent.ack
456
            ).filter(CorrEvent.idcorrevent == correvent1_id
457
            ).scalar()
458
        assert_equal(status, CorrEvent.ACK_NONE)
459

    
460
    def test_close_last_page(self):
461
        """
462
        Suppression de tous les événements de la dernière page.
463

464
        Lorsqu'on supprime tous les événements de la page, on doit
465
        implicitement afficher le contenu de la page d'avant.
466
        """
467

    
468
        # On crée autant d'événements qu'on peut en afficher par page + 1,
469
        # afin d'avoir 2 pages dans le bac à événements.
470
        items_per_page = int(config['vigiboard_items_per_page'])
471
        for i in xrange(items_per_page + 1):
472
            host = Host(
473
                name = u'host%d' % (i + 1),
474
                snmpcommunity = u'public',
475
                hosttpl = u'/dev/null',
476
                address = u'192.168.1.%d' % i,
477
                snmpport = 42,
478
            )
479
            DBSession.add(host)
480
            DBSession.flush()
481
            add_correvent_caused_by(host, datetime.utcnow())
482
        transaction.commit()
483

    
484
        environ = {'REMOTE_USER': 'manager'}
485

    
486
        # On vérifie qu'on a 2 pages et que sur la 2ème page,
487
        # il n'y a qu'un seul événement.
488
        response = self.app.get('/?page=2', extra_environ=environ)
489
        current_page = response.lxml.xpath(
490
            '//span[@class="pager_curpage"]/text()')
491
        assert_equal(2, int(current_page[0]))
492
        assert_equal(len(self.get_rows(response)), 1)
493
        assert_true(len(self.get_cells(response)) > 1)
494

    
495
        # On force l'état de l'événement sur la 2ème page à 'OK'.
496
        # - Tout d'abord, on récupère l'identifiant de l'événement en question.
497
        idcorrevent = response.lxml.xpath(
498
            'string(//table[contains(concat(" ", @class, " "), " vigitable ")]'
499
            '/tbody/tr/td[@class="plugin_details"]/a/@href)')
500
        idcorrevent = int(idcorrevent.lstrip('#'))
501
        # - Puis, on met à jour son état (en le forçant à OK).
502
        # On s'attend à ce que le statut de la requête soit 302,
503
        # et à ce qu'un message informe l'utilisateur que les
504
        # évènements corrélés sélectionnées ont bien été mis à jour.
505
        response = self.app.post(
506
            '/update', {
507
                "id" : str(idcorrevent),
508
                "last_modification": timegm(datetime.utcnow().utctimetuple()),
509
                "trouble_ticket" : "",
510
                "ack" : u'Forced',
511
            }, status=302, extra_environ=environ)
512
        # - On s'assure que la mise à jour a fonctionné.
513
        response = response.follow(status=200, extra_environ=environ)
514
        assert_false(response.lxml.xpath(
515
            '//div[@id="flash"]/div[@class="error"]'))
516
        assert_true(response.lxml.xpath(
517
            '//div[@id="flash"]/div[@class="ok"]'))
518
        # - On vérifie qu'on se trouve à présent sur la 1ère page, et que
519
        # l'on y dénombre autant d'évènements qu'on peut en afficher par page
520
        current_page = response.lxml.xpath(
521
            '//span[@class="pager_curpage"]/text()')
522
        assert_equal(1, int(current_page[0]))
523
        assert_equal(len(self.get_rows(response)), items_per_page)
524
        assert_true(len(self.get_cells(response)) > 1)
525

    
526
        # Une requête sur la 2ème page doit désormais
527
        # afficher le contenu de la 1ère page.
528
        response = self.app.get('/?page=2', extra_environ=environ)
529
        current_page = response.lxml.xpath(
530
            '//span[@class="pager_curpage"]/text()')
531
        assert_equal(1, int(current_page[0]))
532
        assert_equal(len(self.get_rows(response)), items_per_page)