Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / tests / functional / test_root.py @ b373a5de

History | View | Annotate | Download (23.5 KB)

1
# -*- coding: utf-8 -*-
2
# Copyright (C) 2006-2011 CS-SI
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 nose import SkipTest
18
from datetime import datetime
19
from time import mktime
20
import transaction
21

    
22
from vigilo.models.session import DBSession
23
from vigilo.models.tables import Event, EventHistory, CorrEvent, User, \
24
                            Permission, StateName, Host, UserGroup, \
25
                            SupItemGroup, LowLevelService, DataPermission
26
from vigiboard.tests import TestController
27
from tg import config
28

    
29
def populate_DB():
30
    """ Peuple la base de données. """
31
    # On ajoute un groupe d'hôtes et un groupe de services.
32
    supitemmanagers = SupItemGroup(name = u'managersgroup', parent=None)
33
    DBSession.add(supitemmanagers)
34
    DBSession.flush()
35

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

    
44
    # On crée un 2 hôtes, et on les ajoute au groupe d'hôtes.
45
    host1 = Host(
46
        name = u'host1',
47
        checkhostcmd = u'halt',
48
        snmpcommunity = u'public',
49
        hosttpl = u'/dev/null',
50
        address = u'192.168.1.1',
51
        snmpport = 42,
52
        weight = 42,
53
    )
54
    DBSession.add(host1)
55
    supitemmanagers.supitems.append(host1)
56
    host2 = Host(
57
        name = u'host2',
58
        checkhostcmd = u'halt',
59
        snmpcommunity = u'public',
60
        hosttpl = u'/dev/null',
61
        address = u'192.168.1.1',
62
        snmpport = 42,
63
        weight = 42,
64
    )
65
    DBSession.add(host2)
66
    supitemmanagers.supitems.append(host2)
67
    DBSession.flush()
68

    
69
    # On crée 2 services de bas niveau, et on les ajoute au groupe de services.
70
    service1 = LowLevelService(
71
        host = host1,
72
        servicename = u'service1',
73
        command = u'halt',
74
        weight = 42,
75
    )
76
    DBSession.add(service1)
77
    supitemmanagers.supitems.append(service1)
78
    service2 = LowLevelService(
79
        host = host2,
80
        servicename = u'service2',
81
        command = u'halt',
82
        weight = 42,
83
    )
84
    DBSession.add(service2)
85
    supitemmanagers.supitems.append(service2)
86
    DBSession.flush()
87

    
88
    return ([host1, host2], [service1, service2])
89

    
90
def add_correvent_caused_by(supitem, timestamp,
91
        correvent_status=u"None", event_status=u"WARNING"):
92
    """
93
    Ajoute dans la base de données un évènement corrélé causé
94
    par un incident survenu sur l'item passé en paramètre.
95
    Génère un historique pour les tests.
96
    """
97

    
98
    # Ajout d'un événement
99
    event = Event(
100
        supitem = supitem,
101
        message = u'foo',
102
        current_state = StateName.statename_to_value(event_status),
103
        timestamp = datetime.now(),
104
    )
105
    DBSession.add(event)
106
    DBSession.flush()
107

    
108
    # Ajout d'un événement corrélé
109
    aggregate = CorrEvent(
110
        idcause = event.idevent,
111
        timestamp_active = timestamp,
112
        priority = 1,
113
        status = correvent_status)
114
    aggregate.events.append(event)
115
    DBSession.add(aggregate)
116
    DBSession.flush()
117

    
118
    return aggregate.idcorrevent
119

    
120

    
121
class TestRootController(TestController):
122
    """ Classe de test du root controller """
123
    def setUp(self):
124
        super(TestRootController, self).setUp()
125
        perm = Permission.by_permission_name(u'vigiboard-access')
126
        perm2 = Permission.by_permission_name(u'vigiboard-update')
127

    
128
        user = User(
129
            user_name=u'access',
130
            fullname=u'',
131
            email=u'user.has@access',
132
        )
133
        usergroup = UserGroup(group_name=u'users_with_access')
134
        usergroup.permissions.append(perm)
135
        usergroup.permissions.append(perm2)
136
        user.usergroups.append(usergroup)
137
        DBSession.add(user)
138
        DBSession.add(usergroup)
139
        DBSession.flush()
140

    
141
        user = User(
142
            user_name=u'limited_access',
143
            fullname=u'',
144
            email=u'user.has.no@access',
145
        )
146
        usergroup = UserGroup(group_name=u'users_with_limited_access')
147
        usergroup.permissions.append(perm)
148
        usergroup.permissions.append(perm2)
149
        user.usergroups.append(usergroup)
150
        DBSession.add(user)
151
        DBSession.add(usergroup)
152
        DBSession.flush()
153

    
154
    def test_index(self):
155
        """Test that access to the root webpage is restricted."""
156

    
157
        response = self.app.get('/', status=401)
158
        msg = 'Unauthorized'
159
        assert_true(msg in response)
160

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

    
164
        # On peuple la BDD avec 2 hôtes, 2 services de bas niveau,
165
        # et un groupe d'hôtes et de services associés à ces items.
166
        (hosts, services) = populate_DB()
167

    
168
        # On ajoute 2 évènements corrélés causés par ces hôtes
169
        timestamp = datetime.now()
170
        correvent1_id = add_correvent_caused_by(hosts[0], timestamp)
171
        correvent2_id = add_correvent_caused_by(hosts[1], timestamp)
172
        transaction.commit()
173

    
174
        ### 1er cas : L'utilisateur n'est pas connecté.
175
        # On vérifie que le plugin retourne bien une erreur 401.
176
        response = self.app.post(
177
            '/update', {
178
                "id" : str(correvent1_id) + "," + str(correvent2_id),
179
                "last_modification": mktime(timestamp.timetuple()),
180
                "trouble_ticket" : u"foo",
181
                "ack" : u'NoChange',
182
            }, status = 401)
183

    
184
        ### 2ème cas : L'utilisateur utilisé pour
185
        # se connecter à Vigiboard est 'limited_access'.
186
        environ = {'REMOTE_USER': 'limited_access'}
187

    
188
        # On s'attend à ce qu'une erreur 302 soit renvoyée, et à
189
        # ce qu'un message d'erreur précise à l'utilisateur qu'il
190
        # n'a pas la permission de modifier ces évènements.
191
        response = self.app.post(
192
            '/update', {
193
                "id" : str(correvent1_id) + "," + str(correvent2_id),
194
                "ack" : u'NoChange',
195
                "trouble_ticket" : u"foo",
196
                "last_modification": mktime(timestamp.timetuple()),
197
            }, status = 302, extra_environ = environ)
198

    
199
        response = response.follow(status=200, extra_environ = environ)
200
        assert_true(response.lxml.xpath(
201
            '//div[@id="flash"]/div[@class="error"]'))
202

    
203
        ### 3ème cas : L'utilisateur utilisé pour
204
        # se connecter à Vigiboard est 'access'.
205
        environ = {'REMOTE_USER': 'access'}
206

    
207
        # On s'attend à ce que le statut de la requête soit 302,
208
        # et à ce qu'un message informe l'utilisateur que les
209
        # évènements corrélés sélectionnées ont bien été mis à jour.
210
        response = self.app.post(
211
            '/update', {
212
                "id" : str(correvent1_id) + "," + str(correvent2_id),
213
                "last_modification": mktime(timestamp.timetuple()),
214
                "trouble_ticket" : u"foo",
215
                "ack" : u'NoChange',
216
            }, status = 302, extra_environ = environ)
217

    
218
        response = response.follow(status=200, extra_environ = environ)
219
        assert_false(response.lxml.xpath(
220
            '//div[@id="flash"]/div[@class="error"]'))
221
        assert_true(response.lxml.xpath(
222
            '//div[@id="flash"]/div[@class="ok"]'))
223

    
224
        # On s'assure que le ticket de l'évènement corrélé
225
        # a bien été mis à jour dans la base de données.
226
        correvents = DBSession.query(
227
            CorrEvent.trouble_ticket
228
            ).filter(CorrEvent.idcorrevent.in_([correvent1_id, correvent2_id])
229
            ).all()
230

    
231
        assert_equal(correvents[0].trouble_ticket, u"foo")
232
        assert_equal(correvents[1].trouble_ticket, u"foo")
233

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

    
237
        # On peuple la BDD avec 2 hôtes, 2 services de bas niveau,
238
        # et un groupe d'hôtes et de services associés à ces items.
239
        (hosts, services) = populate_DB()
240

    
241
        # On ajoute 2 évènements corrélés causés par ces hôtes
242
        timestamp = datetime.now()
243
        correvent1_id = add_correvent_caused_by(services[0], timestamp)
244
        correvent2_id = add_correvent_caused_by(services[1], timestamp)
245

    
246
        transaction.commit()
247

    
248
        ### 1er cas : L'utilisateur n'est pas connecté.
249
        # On vérifie que le plugin retourne bien une erreur 401.
250
        response = self.app.post(
251
            '/update', {
252
                "id" : str(correvent1_id) + "," + str(correvent2_id),
253
                "last_modification": mktime(timestamp.timetuple()),
254
                "trouble_ticket" : u"foo",
255
                "ack" : u'NoChange',
256
            }, status = 401)
257

    
258
        ### 2ème cas : L'utilisateur utilisé pour
259
        # se connecter à Vigiboard est 'limited_access'.
260
        environ = {'REMOTE_USER': 'limited_access'}
261

    
262
        # On s'attend à ce qu'une erreur 302 soit renvoyée, et à
263
        # ce qu'un message d'erreur précise à l'utilisateur qu'il
264
        # n'a pas la permission de modifier ces évènements.
265
        response = self.app.post(
266
            '/update', {
267
                "id" : str(correvent1_id) + "," + str(correvent2_id),
268
                "last_modification": mktime(timestamp.timetuple()),
269
                "trouble_ticket" : u"foo",
270
                "ack" : u'NoChange',
271
            }, status = 302, extra_environ = environ)
272

    
273
        response = response.follow(status=200, extra_environ = environ)
274
        assert_true(response.lxml.xpath(
275
            '//div[@id="flash"]/div[@class="error"]'))
276

    
277
        ### 3ème cas : L'utilisateur utilisé pour
278
        # se connecter à Vigiboard est 'access'.
279
        environ = {'REMOTE_USER': 'access'}
280

    
281
        # On s'attend à ce que le statut de la requête soit 302,
282
        # et à ce qu'un message informe l'utilisateur que les
283
        # évènements corrélés sélectionnées ont bien été mis à jour.
284
        response = self.app.post(
285
            '/update', {
286
                "id" : str(correvent1_id) + "," + str(correvent2_id),
287
                "last_modification": mktime(timestamp.timetuple()),
288
                "trouble_ticket" : u"foo",
289
                "ack" : u'NoChange',
290
            }, status = 302, extra_environ = environ)
291

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

    
298
        # On s'assure que le ticket de l'évènement corrélé
299
        # a bien été mis à jour dans la base de données.
300
        correvents = DBSession.query(
301
            CorrEvent.trouble_ticket
302
            ).filter(CorrEvent.idcorrevent.in_([correvent1_id, correvent2_id])
303
            ).all()
304
        assert_equal(correvents[0].trouble_ticket, u"foo")
305
        assert_equal(correvents[1].trouble_ticket, u"foo")
306

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

    
310
        # On peuple la BDD avec 2 hôtes, 2 services de bas niveau,
311
        # et un groupe d'hôtes et de services associés à ces items.
312
        (hosts, services) = populate_DB()
313

    
314
        # On ajoute 2 évènements corrélés causés par ces hôtes
315
        timestamp = datetime.now()
316
        correvent1_id = add_correvent_caused_by(hosts[0], timestamp)
317
        correvent2_id = add_correvent_caused_by(hosts[1], timestamp)
318
        transaction.commit()
319

    
320
        ### 1er cas : L'utilisateur n'est pas connecté.
321
        # On vérifie que le plugin retourne bien une erreur 401.
322
        response = self.app.post(
323
            '/update', {
324
                "id" : str(correvent1_id) + "," + str(correvent2_id),
325
                "last_modification": mktime(timestamp.timetuple()),
326
                "trouble_ticket" : "",
327
                "ack" : u'Acknowledged',
328
            }, status = 401)
329

    
330
        ### 2ème cas : L'utilisateur utilisé pour
331
        # se connecter à Vigiboard est 'limited_access'.
332
        environ = {'REMOTE_USER': 'limited_access'}
333

    
334
        # On s'attend à ce qu'une erreur 302 soit renvoyée, et à
335
        # ce qu'un message d'erreur précise à l'utilisateur qu'il
336
        # n'a pas la permission de modifier ces évènements.
337
        response = self.app.post(
338
            '/update', {
339
                "id" : str(correvent1_id) + "," + str(correvent2_id),
340
                "last_modification": mktime(timestamp.timetuple()),
341
                "trouble_ticket" : "",
342
                "ack" : u'Acknowledged',
343
            }, status = 302, extra_environ = environ)
344

    
345
        response = response.follow(status=200, extra_environ = environ)
346
        assert_true(response.lxml.xpath(
347
            '//div[@id="flash"]/div[@class="error"]'))
348

    
349
        ### 3ème cas : L'utilisateur utilisé pour
350
        # se connecter à Vigiboard est 'access'.
351
        environ = {'REMOTE_USER': 'access'}
352

    
353
        # On s'attend à ce que le statut de la requête soit 302,
354
        # et à ce qu'un message informe l'utilisateur que les
355
        # évènements corrélés sélectionnées ont bien été mis à jour.
356
        response = self.app.post(
357
            '/update', {
358
                "id" : str(correvent1_id) + "," + str(correvent2_id),
359
                "last_modification": mktime(timestamp.timetuple()),
360
                "trouble_ticket" : "",
361
                "ack" : u'Acknowledged',
362
            }, status = 302, extra_environ = environ)
363

    
364
        response = response.follow(status=200, extra_environ = environ)
365
        assert_false(response.lxml.xpath(
366
            '//div[@id="flash"]/div[@class="error"]'))
367
        assert_true(response.lxml.xpath(
368
            '//div[@id="flash"]/div[@class="ok"]'))
369

    
370
        # On s'assure que le statut de l'évènement corrélé
371
        # a bien été mis à jour dans la base de données.
372
        correvents = DBSession.query(
373
            CorrEvent.status
374
            ).filter(CorrEvent.idcorrevent.in_([correvent1_id, correvent2_id])
375
            ).all()
376
        assert_equal(correvents[0].status, u'Acknowledged')
377
        assert_equal(correvents[1].status, u'Acknowledged')
378

    
379

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

    
383
        # On peuple la BDD avec 2 hôtes, 2 services de bas niveau,
384
        # et un groupe d'hôtes et de services associés à ces items.
385
        (hosts, services) = populate_DB()
386

    
387
        # On ajoute 2 évènements corrélés causés par ces hôtes
388
        timestamp = datetime.now()
389
        correvent1_id = add_correvent_caused_by(services[0], timestamp)
390
        correvent2_id = add_correvent_caused_by(services[1], timestamp)
391

    
392
        transaction.commit()
393

    
394
        ### 1er cas : L'utilisateur n'est pas connecté.
395
        # On vérifie que le plugin retourne bien une erreur 401.
396
        response = self.app.post(
397
            '/update', {
398
                "id" : str(correvent1_id) + "," + str(correvent2_id),
399
                "last_modification": mktime(timestamp.timetuple()),
400
                "trouble_ticket" : "",
401
                "ack" : u'Acknowledged',
402
            }, status = 401)
403

    
404
        ### 2ème cas : L'utilisateur utilisé pour
405
        # se connecter à Vigiboard est 'limited_access'.
406
        environ = {'REMOTE_USER': 'limited_access'}
407

    
408
        # On s'attend à ce qu'une erreur 302 soit renvoyée, et à
409
        # ce qu'un message d'erreur précise à l'utilisateur qu'il
410
        # n'a pas la permission de modifier ces évènements.
411
        response = self.app.post(
412
            '/update', {
413
                "id" : str(correvent1_id) + "," + str(correvent2_id),
414
                "last_modification": mktime(timestamp.timetuple()),
415
                "trouble_ticket" : "",
416
                "ack" : u'Acknowledged',
417
            }, status = 302, extra_environ = environ)
418

    
419
        response = response.follow(status=200, extra_environ = environ)
420
        assert_true(response.lxml.xpath(
421
            '//div[@id="flash"]/div[@class="error"]'))
422

    
423
        ### 3ème cas : L'utilisateur utilisé pour
424
        # se connecter à Vigiboard est 'access'.
425
        environ = {'REMOTE_USER': 'access'}
426

    
427
        # On s'attend à ce que le statut de la requête soit 302,
428
        # et à ce qu'un message informe l'utilisateur que les
429
        # évènements corrélés sélectionnées ont bien été mis à jour.
430
        response = self.app.post(
431
            '/update', {
432
                "id" : str(correvent1_id) + "," + str(correvent2_id),
433
                "last_modification": mktime(timestamp.timetuple()),
434
                "trouble_ticket" : "",
435
                "ack" : u'Acknowledged',
436
            }, status = 302, extra_environ = environ)
437

    
438
        response = response.follow(status=200, extra_environ = environ)
439
        assert_false(response.lxml.xpath(
440
            '//div[@id="flash"]/div[@class="error"]'))
441
        assert_true(response.lxml.xpath(
442
            '//div[@id="flash"]/div[@class="ok"]'))
443

    
444
        # On s'assure que le statut de l'évènement corrélé
445
        # a bien été mis à jour dans la base de données.
446
        correvents = DBSession.query(
447
            CorrEvent.status
448
            ).filter(CorrEvent.idcorrevent.in_([correvent1_id, correvent2_id])
449
            ).all()
450
        assert_equal(correvents[0].status, u'Acknowledged')
451
        assert_equal(correvents[1].status, u'Acknowledged')
452

    
453
    def test_update_while_data_have_changed(self):
454
        """Màj d'un évènement corrélé modifié entre temps."""
455

    
456
        # On peuple la BDD avec 2 hôtes, 2 services de bas niveau,
457
        # et un groupe d'hôtes et de services associés à ces items.
458
        (hosts, services) = populate_DB()
459

    
460
        # On ajoute 2 évènements corrélés causés par ces hôtes
461
        timestamp = datetime.now()
462
        correvent1_id = add_correvent_caused_by(services[0], timestamp)
463
        correvent2_id = add_correvent_caused_by(services[1], timestamp)
464

    
465
        # Date de modification du premier évènement corrélé
466
        later_date = datetime.now()
467
        # Date du chargement de la page
468
        date = mktime(later_date.timetuple()) - 42
469

    
470
        # On ajoute une entrée dans l'historique de l'évènement brut
471
        # causant le premier évènement corrélé, portant pour timestamp
472
        # une date postérieure à celle du chargement de la page.
473
        correvent1 = DBSession.query(
474
            CorrEvent.idcause
475
            ).filter(CorrEvent.idcorrevent == correvent1_id).one()
476
        DBSession.add(EventHistory(
477
            type_action = u'Nagios update state',
478
            idevent = correvent1.idcause,
479
            timestamp = later_date))
480
        DBSession.flush()
481

    
482
        transaction.commit()
483

    
484
        # L'utilisateur utilisé pour se connecter à Vigiboard est 'access'.
485
        environ = {'REMOTE_USER': 'access'}
486

    
487
        # On s'attend à ce que le statut de la requête soit 302, et
488
        # à ce qu'un message d'erreur avise l'utilisateur que des
489
        # changements sont intervenus depuis le chargement de la page.
490
        response = self.app.post(
491
            '/update', {
492
                "id" : str(correvent1_id),
493
                "last_modification" : date,
494
                "trouble_ticket" : "",
495
                "ack" : u'Acknowledged',
496
            }, status = 302, extra_environ = environ)
497

    
498
        response = response.follow(status=200, extra_environ = environ)
499
        assert_true(response.lxml.xpath(
500
            '//div[@id="flash"]/div[@class="warning"]'))
501

    
502
        # On s'assure que le statut de l'évènement corrélé
503
        # n'a pas été modifié dans la base de données.
504
        status = DBSession.query(
505
            CorrEvent.status
506
            ).filter(CorrEvent.idcorrevent == correvent1_id
507
            ).scalar()
508
        assert_equal(status, u'None')
509

    
510
    def test_close_last_page(self):
511
        """
512
        Suppression de tous les événements de la dernière page.
513

514
        Lorsqu'on supprime tous les événements de la page, on doit
515
        implicitement afficher le contenu de la page d'avant.
516
        """
517

    
518
        # On crée autant d'événements qu'on peut en afficher par page + 1,
519
        # afin d'avoir 2 pages dans le bac à événements.
520
        items_per_page = int(config['vigiboard_items_per_page'])
521
        for i in xrange(items_per_page + 1):
522
            host = Host(
523
                name = u'host%d' % (i + 1),
524
                checkhostcmd = u'halt',
525
                snmpcommunity = u'public',
526
                hosttpl = u'/dev/null',
527
                address = u'192.168.1.%d' % i,
528
                snmpport = 42,
529
                weight = 42,
530
            )
531
            DBSession.add(host)
532
            DBSession.flush()
533
            add_correvent_caused_by(host, datetime.now())
534
        transaction.commit()
535

    
536
        environ = {'REMOTE_USER': 'manager'}
537

    
538
        # On vérifie qu'on a 2 pages et que sur la 2ème page,
539
        # il n'y a qu'un seul événement.
540
        response = self.app.get('/?page=2', extra_environ=environ)
541
        current_page = response.lxml.xpath(
542
            '//span[@class="pager_curpage"]/text()')
543
        assert_equal(2, int(current_page[0]))
544
        rows = response.lxml.xpath('//table[@class="vigitable"]/tbody/tr')
545
        assert_equal(len(rows), 1)
546
        cols = response.lxml.xpath('//table[@class="vigitable"]/tbody/tr/td')
547
        assert_true(len(cols) > 1)
548

    
549
        # On force l'état de l'événement sur la 2ème page à 'OK'.
550
        # - Tout d'abord, on récupère l'identifiant de l'événement en question.
551
        idcorrevent = response.lxml.xpath('string(//table[@class="vigitable"]/tbody/tr/td[@class="plugin_details"]/a/@href)')
552
        idcorrevent = int(idcorrevent.lstrip('#'))
553
        # - Puis, on met à jour son état (en le forçant à OK).
554
        # On s'attend à ce que le statut de la requête soit 302,
555
        # et à ce qu'un message informe l'utilisateur que les
556
        # évènements corrélés sélectionnées ont bien été mis à jour.
557
        response = self.app.post(
558
            '/update', {
559
                "id" : str(idcorrevent),
560
                "last_modification": mktime(datetime.now().timetuple()),
561
                "trouble_ticket" : "",
562
                "ack" : u'Forced',
563
            }, status=302, extra_environ=environ)
564
        # - On s'assure que la mise à jour a fonctionné.
565
        response = response.follow(status=200, extra_environ=environ)
566
        assert_false(response.lxml.xpath(
567
            '//div[@id="flash"]/div[@class="error"]'))
568
        assert_true(response.lxml.xpath(
569
            '//div[@id="flash"]/div[@class="ok"]'))
570
        # - On vérifie qu'on se trouve à présent sur la 1ère page, et que
571
        # l'on y dénombre autant d'évènements qu'on peut en afficher par page
572
        current_page = response.lxml.xpath(
573
            '//span[@class="pager_curpage"]/text()')
574
        assert_equal(1, int(current_page[0]))
575
        rows = response.lxml.xpath('//table[@class="vigitable"]/tbody/tr')
576
        assert_equal(len(rows), items_per_page)
577
        cols = response.lxml.xpath('//table[@class="vigitable"]/tbody/tr/td')
578
        assert_true(len(cols) > 1)
579

    
580
        # Une requête sur la 2ème page doit désormais
581
        # afficher le contenu de la 1ère page.
582
        response = self.app.get('/?page=2', extra_environ=environ)
583
        current_page = response.lxml.xpath(
584
            '//span[@class="pager_curpage"]/text()')
585
        assert_equal(1, int(current_page[0]))
586
        rows = response.lxml.xpath('//table[@class="vigitable"]/tbody/tr')
587
        assert_equal(len(rows), items_per_page)