Project

General

Profile

Revision b6d2af46

IDb6d2af463fc4304258e9ad41cc485064fdf280f2
Parent c1b9f1da
Child 9b5ab541

Added by Vincent QUEMENER over 13 years ago

- Adaptation des tests de l'arbre de sélection des graphes dans VigiGraph ;
- Adaptation (incomplète) des tests de l'arbre de sélection des hôtes dans VigiGraph ;
- Correction de la méthode is_allowed_for() du modèle pour tenir compte de l'héritage des permissions entre les groupes ;
- Modification du fonctionnement du proxy pour tenir compte de l'héritage des permissions entre les groupes.

git-svn-id: https://vigilo-dev.si.c-s.fr/svn@5133 b22e2e97-25c9-44ff-b637-2e5ceca36478

View differences:

vigigraph/controllers/rpc.py
1 1
# -*- coding: utf-8 -*-
2 2
"""RPC controller for the combobox of vigigraph"""
3 3

  
4
import time, urlparse, urllib2
4
import time, urlparse
5 5
import logging
6 6

  
7 7
# La fonction parse_qsl a été déplacée dans Python 2.6.
......
17 17
from tg.decorators import paginate
18 18
from repoze.what.predicates import not_anonymous, has_permission, \
19 19
                                    in_group, Any, All
20
from formencode import validators, schema, foreach
20
from formencode import validators, schema
21 21
from sqlalchemy import or_
22 22
from sqlalchemy.sql.expression import literal_column
23 23

  
24 24
from vigilo.turbogears.controllers import BaseController
25 25
from vigilo.turbogears.helpers import get_current_user
26
from vigilo.turbogears.controllers.proxy import get_through_proxy
27 26

  
28 27
from vigilo.models.session import DBSession
29 28
from vigilo.models.tables import Host
......
181 180
    def graphsList(self, nocache=None, **kwargs):
182 181
        """
183 182
        Generation document avec url des graphes affiches
184
        (pour impression)
183
        (pour l impression )
185 184

  
186 185
        @param kwargs : arguments nommes
187 186
        @type kwargs  : dict
......
189 188
        @return: url de graphes
190 189
        @rtype: document html
191 190
        """
192
        # @TODO: le must serait de hot-patcher mootools pour que son serializer
193
        # d'URL utilise directement le format attendu par TurboGears
194
        # (notation pointée plutôt qu'avec des crochets)
195

  
196 191
        if not kwargs:
197 192
            return dict(graphslist=[])
198 193

  
......
200 195
        # n'accepte pas les chaînes Unicode en entrée.
201 196
        # TRANSLATORS: Format Python de date/heure, lisible par un humain.
202 197
        format = _("%a, %d %b %Y %H:%M:%S").encode('utf8')
203
        i = 0
204 198
        graphslist = []
205

  
206
        while True:
207
            try:
208
                host = kwargs['graphs[%d][host]' % i]
209
                graph = kwargs['graphs[%d][graph]' % i]
210
                start = int(kwargs.get('graphs[%d][start]' % i, time.time() - 86400))
211
                duration = int(kwargs.get('graphs[%d][duration]' % i))
212
                nocache = kwargs['graphs[%d][nocache]' % i]
213
            except KeyError:
214
                break
215

  
216
            if not (host and graph and duration and nocache):
217
                break
218

  
219
            graphslist.append({
220
                'host': host,
221
                'graph': graph,
222
                'start': start,
223
                'duration': duration,
224
                'nocache': nocache,
225
                'start_date': time.strftime(format,
226
                    time.localtime(start)).decode('utf8'),
227
                'end_date': time.strftime(format,
228
                    time.localtime(start + duration)).decode('utf8')
229
            })
230
            i += 1
231

  
199
        for url in kwargs.itervalues():
200
            parts = urlparse.urlparse(url)
201
            params = dict(parse_qsl(parts.query))
202

  
203
            graph = {}
204
            start = int(params.get('start', time.time() - 86400))
205
            duration = int(params.get('duration', 86400))
206

  
207
            graph['graph'] = params.get('graphtemplate')
208
            graph['start_date'] = time.strftime(format,
209
                time.localtime(start)).decode('utf8')
210
            graph['end_date'] = time.strftime(format,
211
                time.localtime(start + duration)).decode('utf8')
212
            graph['img_src'] = url
213
            graph['host'] = params['host']
214
            graphslist.append(graph)
232 215
        return dict(graphslist=graphslist)
233 216

  
234 217
    @expose('json')
......
273 256
        indicators = [ind.name for ind in indicators]
274 257
        return dict(items=indicators)
275 258

  
276
    class StartTimeSchema(schema.Schema):
277
        """Schéma de validation pour la méthode L{getIndicators}."""
278
        host = validators.String(not_empty=True)
279
        nocache = validators.String(if_missing=None)
280

  
281
    # @TODO définir un error_handler différent pour remonter l'erreur via JS.
282
    @validate(
283
        validators = StartTimeSchema(),
284
        error_handler = process_form_errors)
285
    @expose('json')
286
    def startTime(self, host, nocache):
287
        return get_through_proxy(
288
            'vigirrd', host,
289
            '/starttime?host=%s' % urllib2.quote(host, '')
290
        )
291 259

  
292 260
    class FullHostPageSchema(schema.Schema):
293 261
        """Schéma de validation pour la méthode L{fullHostPage}."""
......
564 532
        if not host.is_allowed_for(user):
565 533
            return dict(groups = [], graphs=[])
566 534

  
567
        # On récupère la liste des groupes de graphes associé à l'hôte
535
        # On récupère la liste des groupes de graphes associés à l'hôte
568 536
        host_graph_groups = DBSession.query(
569 537
            GraphGroup
570 538
        ).distinct(
......
579 547
            (SUPITEM_GROUP_TABLE, \
580 548
                SUPITEM_GROUP_TABLE.c.idsupitem == PerfDataSource.idhost),
581 549
        ).filter(PerfDataSource.idhost == host_id
582
        ).order_by(GraphGroup.name.asc())
550
        ).order_by(GraphGroup.name.asc()
551
        ).all()
583 552

  
584 553
        # Si l'identifiant du groupe parent n'est pas spécifié,
585 554
        # on récupère la liste des groupes de graphes racines.
......
598 567
                    GraphGroup.idgroup),
599 568
            ).filter(GroupHierarchy.hops == 1
600 569
            ).filter(GroupHierarchy.idparent == parent_id
601
            ).order_by(GraphGroup.name.asc())
570
            ).order_by(GraphGroup.name.asc()
571
            ).all()
602 572

  
603 573
        # On réalise l'intersection des deux listes
604 574
        groups = []
......
616 586
            db_graphs = DBSession.query(
617 587
                Graph.idgraph,
618 588
                Graph.name,
589
            ).distinct(
619 590
            ).join(
620 591
                (GRAPH_GROUP_TABLE,
621
                    GRAPH_GROUP_TABLE.c.idgraph == Graph.idgraph
622
                    ),
592
                    GRAPH_GROUP_TABLE.c.idgraph == Graph.idgraph),
593
                (GRAPH_PERFDATASOURCE_TABLE,
594
                    GRAPH_PERFDATASOURCE_TABLE.c.idgraph == Graph.idgraph),
595
                (PerfDataSource,
596
                    PerfDataSource.idperfdatasource == \
597
                        GRAPH_PERFDATASOURCE_TABLE.c.idperfdatasource),
623 598
            ).filter(GRAPH_GROUP_TABLE.c.idgroup == parent_id
599
            ).filter(PerfDataSource.idhost == host_id
624 600
            ).order_by(Graph.name.asc())
625 601
            for graph in db_graphs.all():
626 602
                graphs.append({
......
672 648
                'name' : group.name,
673 649
            })
674 650

  
675
        return dict(groups = groups, hosts=[])
676

  
677
    def get_root_graph_groups(self, host_id):
678
        """
679
        Retourne tous les groupes racines (c'est à dire n'ayant
680
        aucun parent) de graphes auquel l'utilisateur a accès et
681
        concernant l'hôte dont l'identifiant est passé en paramètre.
682

  
683
        @return: Un dictionnaire contenant la liste de ces groupes.
684
        @rtype : C{dict} of C{list} of C{dict} of C{mixed}
685
        """
686

  
687
        # On récupère tous les groupes qui ont un parent.
688
        children = DBSession.query(
689
            Graph,
690
        ).distinct(
691
        ).join(
692
            (GroupHierarchy, GroupHierarchy.idchild == SupItemGroup.idgroup)
693
        ).filter(GroupHierarchy.hops > 0)
694

  
695
        # On récupère tous les groupes racines de graphes portant sur l'hôte
696
        # souhaité, en éliminant les groupes récupérés à l'étape précédente
697
        graph_groups = DBSession.query(
698
            GraphGroup
699
        ).distinct(
700
        ).join(
701
            (GRAPH_GROUP_TABLE, \
702
                GRAPH_GROUP_TABLE.c.idgroup == GraphGroup.idgroup),
703
            (Graph, Graph.idgraph == GRAPH_GROUP_TABLE.c.idgraph),
704
            (GRAPH_PERFDATASOURCE_TABLE, \
705
                    GRAPH_PERFDATASOURCE_TABLE.c.idgraph == Graph.idgraph),
706
            (PerfDataSource, PerfDataSource.idperfdatasource == \
707
                    GRAPH_PERFDATASOURCE_TABLE.c.idperfdatasource),
708
            (SUPITEM_GROUP_TABLE, \
709
                SUPITEM_GROUP_TABLE.c.idsupitem == PerfDataSource.idhost),
710
        ).filter(PerfDataSource.idhost == host_id
711
#        ).except_(children
712
        ).order_by(GraphGroup.name.asc())
713

  
714
        # On filtre ces groupes racines afin de ne
715
        # retourner que ceux auquels l'utilisateur a accès
716
        user = get_current_user()
717
        is_manager = in_group('managers').is_met(request.environ)
718
        if not is_manager:
719
            user_groups = [ug[0] for ug in user.supitemgroups() if ug[1]]
720
            graph_groups = graph_groups.filter(
721
                SUPITEM_GROUP_TABLE.c.idgroup.in_(user_groups))
722

  
723
        groups = []
724
        for group in graph_groups.all():
725
            #if group.has_children() or len(group.graphs)>0:
726
            groups.append({
727
                'id'   : group.idgroup,
728
                'name' : group.name,
729
            })
730

  
731
        return dict(groups = groups, graphs=[])
651
        return dict(groups = groups, leaves=[])
652

  
653
#    def get_root_graph_groups(self, host_id):
654
#        """
655
#        Retourne tous les groupes racines (c'est à dire n'ayant
656
#        aucun parent) de graphes auquel l'utilisateur a accès et
657
#        concernant l'hôte dont l'identifiant est passé en paramètre.
658
#
659
#        @return: Un dictionnaire contenant la liste de ces groupes.
660
#        @rtype : C{dict} of C{list} of C{dict} of C{mixed}
661
#        """
662
#
663
#        # On récupère tous les groupes qui ont un parent.
664
#        children = DBSession.query(
665
#            Graph,
666
#        ).distinct(
667
#        ).join(
668
#            (GroupHierarchy, GroupHierarchy.idchild == SupItemGroup.idgroup)
669
#        ).filter(GroupHierarchy.hops > 0)
670
#
671
#        # On récupère tous les groupes racines de graphes portant sur l'hôte
672
#        # souhaité, en éliminant les groupes récupérés à l'étape précédente
673
#        graph_groups = DBSession.query(
674
#            GraphGroup
675
#        ).distinct(
676
#        ).join(
677
#            (GRAPH_GROUP_TABLE, \
678
#                GRAPH_GROUP_TABLE.c.idgroup == GraphGroup.idgroup),
679
#            (Graph, Graph.idgraph == GRAPH_GROUP_TABLE.c.idgraph),
680
#            (GRAPH_PERFDATASOURCE_TABLE, \
681
#                    GRAPH_PERFDATASOURCE_TABLE.c.idgraph == Graph.idgraph),
682
#            (PerfDataSource, PerfDataSource.idperfdatasource == \
683
#                    GRAPH_PERFDATASOURCE_TABLE.c.idperfdatasource),
684
#            (SUPITEM_GROUP_TABLE, \
685
#                SUPITEM_GROUP_TABLE.c.idsupitem == PerfDataSource.idhost),
686
#        ).filter(PerfDataSource.idhost == host_id
687
##        ).except_(children
688
#        ).order_by(GraphGroup.name.asc())
689
#
690
#        # On filtre ces groupes racines afin de ne
691
#        # retourner que ceux auquels l'utilisateur a accès
692
#        user = get_current_user()
693
#        is_manager = in_group('managers').is_met(request.environ)
694
#        if not is_manager:
695
#            user_groups = [ug[0] for ug in user.supitemgroups() if ug[1]]
696
#            graph_groups = graph_groups.filter(
697
#                SUPITEM_GROUP_TABLE.c.idgroup.in_(user_groups))
698
#
699
#        groups = []
700
#        for group in graph_groups.all():
701
#            #if group.has_children() or len(group.graphs)>0:
702
#            groups.append({
703
#                'id'   : group.idgroup,
704
#                'name' : group.name,
705
#            })
706
#
707
#        return dict(groups = groups, graphs=[])
732 708

  
733 709
    def getListIndicators(self, host, graph):
734 710
        """
......
757 733
                ).filter(Host.name == host
758 734
                ).all()
759 735
        return indicators
736

  
vigigraph/tests/functional/test_graph_selection_form.py
1 1
# -*- coding: utf-8 -*-
2 2
"""
3
Suite de tests du formulaire de sélection des graphes et groupes de graphes.
3
Suite de tests de l'arbre de sélection des graphes et groupes de graphes.
4 4
"""
5 5
import transaction
6 6

  
......
62 62
    add_graph2group(graph3, graphgroup3)
63 63

  
64 64

  
65
class TestGraphSelectionForm(TestController):
65
class TestGraphTree(TestController):
66 66
    """
67
    Teste le formulaire de sélection des
68
    graphes et groupes de graphes de Vigigraph.
67
    Teste l'arbre de sélection des graphes
68
    et groupes de graphes de Vigigraph.
69 69
    """
70 70

  
71 71
    def setUp(self):
72 72
        """Préparation de la base de données de tests."""
73 73

  
74 74
        # Initialisation de la base
75
        super(TestGraphSelectionForm, self).setUp()
75
        super(TestGraphTree, self).setUp()
76 76

  
77 77
        # Ajout de données de tests dans la base
78 78
        (host1, host2, host3) = populateDB()
......
121 121
        # Récupération des groupes de graphes de l'hôte
122 122
        # host1 accessibles à l'utilisateur 'manager'
123 123
        response = self.app.post(
124
        '/rpc/graphgroups?idhost=%s' % (host1.idhost, ), {
124
        '/rpc/graphtree?host_id=%s' % (host1.idhost, ), {
125 125
            }, extra_environ={'REMOTE_USER': 'manager'})
126 126
        json = response.json
127 127

  
128 128
        # On s'assure que la liste de groupes
129 129
        # de graphes retournée contient bien 'graphgroup1'
130 130
        self.assertEqual(
131
            json, {"items": [
132
                [graphgroup1.name, unicode(graphgroup1.idgroup)]
133
            ]}
131
            json, {
132
                'leaves': [],
133
                'groups': [{'id': graphgroup1.idgroup, 'name': graphgroup1.name}]
134
            }
134 135
        )
135 136

  
136 137
        # Récupération des groupes de graphes de l'hôte
137 138
        # host2 accessibles à l'utilisateur 'manager'
138 139
        response = self.app.post(
139
        '/rpc/graphgroups?idhost=%s' % (host2.idhost, ), {
140
        '/rpc/graphtree?host_id=%s' % (host2.idhost, ), {
140 141
            }, extra_environ={'REMOTE_USER': 'manager'})
141 142
        json = response.json
142 143

  
143 144
        # On s'assure que la liste de groupes
144 145
        # de graphes retournée contient bien 'graphgroup2'
145 146
        self.assertEqual(
146
            json, {"items": [
147
                [graphgroup2.name, unicode(graphgroup2.idgroup)]
148
            ]}
147
            json, {
148
                'leaves': [],
149
                'groups': [{'id': graphgroup2.idgroup, 'name': graphgroup2.name}]
150
            }
149 151
        )
150 152

  
151 153
        # Récupération des groupes de graphes de l'hôte
152 154
        # host1 accessibles à l'utilisateur 'poweruser'
153 155
        response = self.app.post(
154
        '/rpc/graphgroups?idhost=%s' % (host1.idhost, ), {
156
        '/rpc/graphtree?host_id=%s' % (host1.idhost, ), {
155 157
            }, extra_environ={'REMOTE_USER': 'poweruser'})
156 158
        json = response.json
157 159

  
158 160
        # On s'assure que la liste de groupes
159 161
        # de graphes retournée contient bien 'graphgroup1'
160 162
        self.assertEqual(
161
            json, {"items": [
162
                [graphgroup1.name, unicode(graphgroup1.idgroup)]
163
            ]}
163
            json, {
164
                'leaves': [],
165
                'groups': [{'id': graphgroup1.idgroup, 'name': graphgroup1.name}]
166
            }
164 167
        )
165 168

  
166 169
        # Récupération des groupes de graphes de l'hôte
167 170
        # host2 accessibles à l'utilisateur 'poweruser'
168 171
        response = self.app.post(
169
        '/rpc/graphgroups?idhost=%s' % (host2.idhost, ), {
172
        '/rpc/graphtree?host_id=%s' % (host2.idhost, ), {
170 173
            }, extra_environ={'REMOTE_USER': 'poweruser'})
171 174
        json = response.json
172 175

  
173 176
        # On s'assure que la liste de groupes
174 177
        # de graphes retournée contient bien 'graphgroup2'
175 178
        self.assertEqual(
176
            json, {"items": [
177
                [graphgroup2.name, unicode(graphgroup2.idgroup)]
178
            ]}
179
            json, {
180
                'leaves': [],
181
                'groups': [{'id': graphgroup2.idgroup, 'name': graphgroup2.name}]
182
            }
179 183
        )
180 184

  
181 185
        # Récupération des groupes de graphes de l'hôte
182 186
        # host2 accessibles à l'utilisateur 'user'
183 187
        response = self.app.post(
184
        '/rpc/graphgroups?idhost=%s' % (host2.idhost, ), {
188
        '/rpc/graphtree?host_id=%s' % (host2.idhost, ), {
185 189
            }, extra_environ={'REMOTE_USER': 'user'})
186 190
        json = response.json
187 191

  
188 192
        # On s'assure que la liste de groupes
189 193
        # de graphes retournée contient bien 'graphgroup2'
190 194
        self.assertEqual(
191
            json, {"items": [
192
                [graphgroup2.name, unicode(graphgroup2.idgroup)]
193
            ]}
195
            json, {
196
                'leaves': [],
197
                'groups': [{'id': graphgroup2.idgroup, 'name': graphgroup2.name}]
198
            }
194 199
        )
195 200

  
196 201
        # Récupération des groupes de graphes de l'hôte
197 202
        # host3 accessibles à l'utilisateur 'poweruser'
198 203
        response = self.app.post(
199
        '/rpc/graphgroups?idhost=%s' % (host3.idhost, ), {
204
        '/rpc/graphtree?host_id=%s' % (host3.idhost, ), {
200 205
            }, extra_environ={'REMOTE_USER': 'poweruser'})
201 206
        json = response.json
202 207

  
203 208
        # On s'assure que la liste de groupes
204 209
        # de graphes retournée contient bien 'graphgroup3'
205 210
        self.assertEqual(
206
            json, {"items": [
207
                [graphgroup3.name, unicode(graphgroup3.idgroup)]
208
            ]}
211
            json, {
212
                'leaves': [],
213
                'groups': [{'id': graphgroup3.idgroup, 'name': graphgroup3.name}]
214
            }
209 215
        )
210 216

  
211 217
    def test_get_graph_groups_when_not_allowed(self):
......
224 230
        # Récupération des groupes de graphes de l'hôte
225 231
        # host1 accessibles à l'utilisateur 'user'
226 232
        response = self.app.post(
227
        '/rpc/graphgroups?idhost=%s' % (host1.idhost, ), {
233
        '/rpc/graphtree?host_id=%s' % (host1.idhost, ), {
228 234
            }, extra_environ={'REMOTE_USER': 'user'})
229 235
        json = response.json
230 236

  
231 237
        # On s'assure que la liste de groupes
232 238
        # de graphes retournée est vide
233 239
        self.assertEqual(
234
            json, {"items": []}
240
            json, {
241
                'graphs': [],
242
                'groups': []
243
            }
235 244
        )
236 245

  
237 246
        # Récupération des groupes de graphes de l'hôte
238 247
        # host3 accessibles à l'utilisateur 'user'
239 248
        response = self.app.post(
240
        '/rpc/graphgroups?idhost=%s' % (host3.idhost, ), {
249
        '/rpc/graphtree?host_id=%s' % (host3.idhost, ), {
241 250
            }, extra_environ={'REMOTE_USER': 'user'})
242 251
        json = response.json
243 252

  
244 253
        # On s'assure que la liste de groupes
245 254
        # de graphes retournée est vide
246 255
        self.assertEqual(
247
            json, {"items": []}
256
            json, {
257
                'graphs': [],
258
                'groups': []
259
            }
248 260
        )
249 261

  
250 262
        # Récupération des groupes de graphes de l'hôte
251 263
        # host1 accessibles à l'utilisateur 'visitor'
252 264
        response = self.app.post(
253
        '/rpc/graphgroups?idhost=%s' % (host1.idhost, ), {
265
        '/rpc/graphtree?host_id=%s' % (host1.idhost, ), {
254 266
            }, extra_environ={'REMOTE_USER': 'visitor'})
255 267
        json = response.json
256 268

  
257 269
        # On s'assure que la liste de groupes
258 270
        # de graphes retournée est vide
259 271
        self.assertEqual(
260
            json, {"items": []}
272
            json, {
273
                'graphs': [],
274
                'groups': []
275
            }
261 276
        )
262 277

  
263 278
    def test_get_graph_groups_as_anonymous(self):
......
273 288
        # 'host1' par un utilisateur anonyme : le contrôleur
274 289
        # doit retourner une erreur 401 (HTTPUnauthorized)
275 290
        self.app.post(
276
            '/rpc/graphgroups?idhost=%s' % (host1.idhost, ), {
291
            '/rpc/graphtree?host_id=%s' % (host1.idhost, ), {
277 292
            }, status=401)
278 293

  
279 294
    def test_get_graph_groups_from_inexisting_host(self):
......
284 299
        # Récupération des groupes d'hôtes accessibles à l'utilisateur
285 300
        # 'visitor' et appartenant à un groupe principal inexistant
286 301
        response = self.app.post(
287
            '/rpc/graphgroups?idhost=6666666', {
302
            '/rpc/graphtree?host_id=6666666', {
288 303
            }, extra_environ={'REMOTE_USER': 'visitor'})
289 304
        json = response.json
290 305

  
291 306
        # On s'assure que la liste de groupes
292 307
        # de graphes retournée est vide
293 308
        self.assertEqual(
294
            json, {"items": []}
309
            json, {
310
                'graphs': [],
311
                'groups': []
312
            }
295 313
        )
296 314

  
297 315
##### Cinquième onglet déroulant du formulaire #####
......
346 364
        # Récupération des graphes du groupe de graphes
347 365
        # 'graphgroup1' accessibles à l'utilisateur 'manager'
348 366
        response = self.app.post(
349
        '/rpc/graphs?idgraphgroup=%s&idhost=%s' %
367
        '/rpc/graphtree?parent_id=%s&host_id=%s' %
350 368
            (graphgroup1.idgroup, host1.idhost), {},
351 369
            extra_environ={'REMOTE_USER': 'manager'})
352 370
        json = response.json
......
354 372
        # On s'assure que la liste de
355 373
        # graphes retournée contient 'graph1'
356 374
        self.assertEqual(
357
            json, {"items": [
358
                [graph1.name, unicode(graph1.idgraph)]
359
            ]}
375
            json, {
376
                'leaves': [{'id': graph1.idgraph, 'name': graph1.name}],
377
                'groups': []
378
            }
360 379
        )
361 380

  
362 381
        # Récupération des graphes du groupe de graphes
363 382
        # 'graphgroup2' accessibles à l'utilisateur 'manager'
364 383
        response = self.app.post(
365
        '/rpc/graphs?idgraphgroup=%s&idhost=%s' %
384
        '/rpc/graphtree?parent_id=%s&host_id=%s' %
366 385
            (graphgroup2.idgroup, host2.idhost), {},
367 386
            extra_environ={'REMOTE_USER': 'manager'})
368 387
        json = response.json
......
370 389
        # On s'assure que la liste de
371 390
        # graphes retournée contient 'graph2'
372 391
        self.assertEqual(
373
            json, {"items": [
374
                [graph2.name, unicode(graph2.idgraph)]
375
            ]}
392
            json, {
393
                'leaves': [{'id': graph2.idgraph, 'name': graph2.name}],
394
                'groups': []
395
            }
376 396
        )
377 397

  
378 398
        # Récupération des graphes du groupe de graphes
379 399
        # 'graphgroup1' accessibles à l'utilisateur 'poweruser'
380 400
        response = self.app.post(
381
        '/rpc/graphs?idgraphgroup=%s&idhost=%s' %
401
        '/rpc/graphtree?parent_id=%s&host_id=%s' %
382 402
            (graphgroup1.idgroup, host1.idhost), {},
383 403
            extra_environ={'REMOTE_USER': 'poweruser'})
384 404
        json = response.json
......
386 406
        # On s'assure que la liste de
387 407
        # graphes retournée contient 'graph1'
388 408
        self.assertEqual(
389
            json, {"items": [
390
                [graph1.name, unicode(graph1.idgraph)]
391
            ]}
409
            json, {
410
                'leaves': [{'id': graph1.idgraph, 'name': graph1.name}],
411
                'groups': []
412
            }
392 413
        )
393 414

  
394 415
        # Récupération des graphes du groupe de graphes
395 416
        # 'graphgroup2' accessibles à l'utilisateur 'poweruser'
396 417
        response = self.app.post(
397
        '/rpc/graphs?idgraphgroup=%s&idhost=%s' %
418
        '/rpc/graphtree?parent_id=%s&host_id=%s' %
398 419
            (graphgroup2.idgroup, host2.idhost), {},
399 420
            extra_environ={'REMOTE_USER': 'poweruser'})
400 421
        json = response.json
......
402 423
        # On s'assure que la liste de
403 424
        # graphes retournée contient 'graph2'
404 425
        self.assertEqual(
405
            json, {"items": [
406
                [graph2.name, unicode(graph2.idgraph)]
407
            ]}
426
            json, {
427
                'leaves': [{'id': graph2.idgraph, 'name': graph2.name}],
428
                'groups': []
429
            }
408 430
        )
409 431

  
410 432
        # Récupération des graphes du groupe de graphes
411 433
        # 'graphgroup2' accessibles à l'utilisateur 'user'
412 434
        response = self.app.post(
413
        '/rpc/graphs?idgraphgroup=%s&idhost=%s' %
435
        '/rpc/graphtree?parent_id=%s&host_id=%s' %
414 436
            (graphgroup2.idgroup, host2.idhost), {},
415 437
            extra_environ={'REMOTE_USER': 'user'})
416 438
        json = response.json
......
418 440
        # On s'assure que la liste de
419 441
        # graphes retournée contient 'graph2'
420 442
        self.assertEqual(
421
            json, {"items": [
422
                [graph2.name, unicode(graph2.idgraph)]
423
            ]}
443
            json, {
444
                'leaves': [{'id': graph2.idgraph, 'name': graph2.name}],
445
                'groups': []
446
            }
424 447
        )
425 448

  
426 449
        # Récupération des graphes du groupe de graphes
427 450
        # 'graphgroup3' accessibles à l'utilisateur 'poweruser'
428 451
        response = self.app.post(
429
        '/rpc/graphs?idgraphgroup=%s&idhost=%s' %
452
        '/rpc/graphtree?parent_id=%s&host_id=%s' %
430 453
            (graphgroup3.idgroup, host3.idhost), {},
431 454
            extra_environ={'REMOTE_USER': 'poweruser'})
432 455
        json = response.json
......
434 457
        # On s'assure que la liste de
435 458
        # graphes retournée contient 'graph3'
436 459
        self.assertEqual(
437
            json, {"items": [
438
                [graph3.name, unicode(graph3.idgraph)]
439
            ]}
460
            json, {
461
                'leaves': [{'id': graph3.idgraph, 'name': graph3.name}],
462
                'groups': []
463
            }
440 464
        )
441 465

  
442 466
    def test_get_graphs_when_not_allowed(self):
......
465 489
        # Récupération des graphes du groupe de graphes
466 490
        # graphgroup1 accessibles à l'utilisateur 'user'
467 491
        response = self.app.post(
468
        '/rpc/graphs?idgraphgroup=%s&idhost=%s' %
492
        '/rpc/graphtree?parent_id=%s&host_id=%s' %
469 493
            (graphgroup1.idgroup, host1.idhost), {},
470 494
            extra_environ={'REMOTE_USER': 'user'})
471 495
        json = response.json
......
473 497
        # On s'assure que la liste de groupes
474 498
        # de graphes retournée est vide
475 499
        self.assertEqual(
476
            json, {"items": []}
500
            json, {
501
                'graphs': [],
502
                'groups': []
503
            }
477 504
        )
478 505

  
479 506
        # Récupération des graphes du groupe de graphes
480 507
        # 'graphgroup1' accessibles à l'utilisateur 'visitor'
481 508
        response = self.app.post(
482
        '/rpc/graphs?idgraphgroup=%s&idhost=%s' %
509
        '/rpc/graphtree?parent_id=%s&host_id=%s' %
483 510
            (graphgroup1.idgroup, host1.idhost), {},
484 511
            extra_environ={'REMOTE_USER': 'visitor'})
485 512
        json = response.json
......
487 514
        # On s'assure que la liste de groupes
488 515
        # de graphes retournée est vide
489 516
        self.assertEqual(
490
            json, {"items": []}
517
            json, {
518
                'graphs': [],
519
                'groups': []
520
            }
491 521
        )
492 522

  
493 523
        # Récupération des graphes du groupe de graphes
494 524
        # 'graphgroup3' accessibles à l'utilisateur 'user'
495 525
        response = self.app.post(
496
        '/rpc/graphs?idgraphgroup=%s&idhost=%s' %
526
        '/rpc/graphtree?parent_id=%s&host_id=%s' %
497 527
            (graphgroup3.idgroup, host3.idhost), {},
498 528
            extra_environ={'REMOTE_USER': 'user'})
499 529
        json = response.json
......
501 531
        # On s'assure que la liste de groupes
502 532
        # de graphes retournée est vide
503 533
        self.assertEqual(
504
            json, {"items": []}
534
            json, {
535
                'graphs': [],
536
                'groups': []
537
            }
505 538
        )
506 539

  
507 540
    def test_get_graphs_as_anonymous(self):
......
522 555
        # 'graphgroup1' par un utilisateur anonyme : le contrôleur
523 556
        # doit retourner une erreur 401 (HTTPUnauthorized)
524 557
        self.app.post(
525
            '/rpc/graphs?idgraphgroup=%s&idhost=%s' %
558
            '/rpc/graphtree?parent_id=%s&host_id=%s' %
526 559
            (graphgroup1.idgroup, host1.idhost),
527 560
            {}, status=401)
528 561

  
......
534 567
        # Récupération des graphes accessibles à l'utilisateur
535 568
        # 'manager' et appartenant à un groupe inexistant
536 569
        response = self.app.post(
537
            '/rpc/graphs?idgraphgroup=6666666&idhost=6666666', {
570
            '/rpc/graphtree?parent_id=6666666&host_id=6666666', {
538 571
            }, extra_environ={'REMOTE_USER': 'manager'})
539 572
        json = response.json
540 573

  
541 574
        # On s'assure que la liste de
542 575
        # graphes retournée est vide
543 576
        self.assertEqual(
544
            json, {"items": []}
577
            json, {
578
                'graphs': [],
579
                'groups': []
580
            }
545 581
        )
vigigraph/tests/functional/test_host_selection_form.py
1 1
# -*- coding: utf-8 -*-
2 2
"""
3
Suite de tests du formulaire de sélection des hôtes et groupes d'hôtes.
3
Suite de tests de l'arbre de sélection des hôtes et groupes d'hôtes.
4 4
"""
5 5
import transaction
6 6

  
7 7
from vigigraph.tests import TestController
8 8
from vigilo.models.session import DBSession
9
from vigilo.models.tables import SupItemGroup, Permission
9
from vigilo.models.tables import Host, SupItemGroup, Permission
10 10
from vigilo.models.demo.functions import add_supitemgroup, \
11 11
    add_host, add_host2group, add_usergroup, add_user, \
12 12
    add_supitemgrouppermission, add_usergroup_permission
......
78 78
    return (host1, host2, host3)
79 79

  
80 80

  
81
class TestHostSelectionForm(TestController):
81
class TestHostTree(TestController):
82 82
    """
83
    Teste le formulaire de sélection des
84
    hôtes et groupes d'hôtes de Vigigraph.
83
    Teste l'arbre de sélection des hôtes
84
    et groupes d'hôtes de Vigigraph.
85 85
    """
86 86

  
87 87
    def setUp(self):
88 88
        """Préparation de la base de données de tests."""
89 89

  
90 90
        # Initialisation de la base
91
        super(TestHostSelectionForm, self).setUp()
91
        super(TestHostTree, self).setUp()
92 92

  
93 93
        # Ajout de données de tests dans la base
94 94
        populateDB()
......
111 111
        # Récupération des groupes d'hôtes principaux
112 112
        # accessibles à l'utilisateur 'manager'
113 113
        response = self.app.post(
114
            '/rpc/maingroups', {
114
            '/rpc/hosttree', {
115 115
            }, extra_environ={'REMOTE_USER': 'manager'})
116 116
        json = response.json
117 117

  
118 118
        # On s'assure que la liste de groupes
119 119
        # d'hôtes retournée contient bien 'mhg'
120 120
        self.assertEqual(
121
            json, {"items": [
122
                [mainhostgroup.name, unicode(mainhostgroup.idgroup)]
123
            ]}
121
            json, {
122
                'leaves': [], 'groups': [{
123
                    'id': mainhostgroup.idgroup,
124
                    'name': mainhostgroup.name
125
                }]
126
            }
124 127
        )
125 128

  
126 129
        # Récupération des groupes d'hôtes principaux
127 130
        # accessibles à l'utilisateur 'poweruser'
128 131
        response = self.app.post(
129
            '/rpc/maingroups', {
132
            '/rpc/hosttree', {
130 133
            }, extra_environ={'REMOTE_USER': 'poweruser'})
131 134
        json = response.json
132 135

  
133 136
        # On s'assure que la liste de groupes
134 137
        # d'hôtes retournée contient bien 'mhg'
135 138
        self.assertEqual(
136
            json, {"items": [
137
                [mainhostgroup.name, unicode(mainhostgroup.idgroup)]
138
            ]}
139
            json, {
140
                'leaves': [], 'groups': [{
141
                    'id': mainhostgroup.idgroup,
142
                    'name': mainhostgroup.name
143
                }]
144
            }
139 145
        )
140 146

  
141 147
        # Récupération des groupes d'hôtes principaux
142 148
        # accessibles à l'utilisateur 'user'
143 149
        response = self.app.post(
144
            '/rpc/maingroups', {
150
            '/rpc/hosttree', {
145 151
            }, extra_environ={'REMOTE_USER': 'user'})
146 152
        json = response.json
147 153

  
148 154
        # On s'assure que la liste de groupes
149 155
        # d'hôtes retournée contient bien 'mhg'
150 156
        self.assertEqual(
151
            json, {"items": [[
152
                mainhostgroup.name,
153
                unicode(mainhostgroup.idgroup)
154
            ]]}
157
            json, {
158
                'leaves': [], 'groups': [{
159
                    'id': mainhostgroup.idgroup,
160
                    'name': mainhostgroup.name
161
                }]
162
            }
155 163
        )
156 164

  
157 165
    def test_get_main_host_groups_when_not_allowed(self):
......
162 170
        # Récupération des groupes d'hôtes principaux
163 171
        # accessibles à l'utilisateur 'visitor'
164 172
        response = self.app.post(
165
            '/rpc/maingroups', {
173
            '/rpc/hosttree', {
166 174
            }, extra_environ={'REMOTE_USER': 'visitor'})
167 175
        json = response.json
168 176

  
169 177
        # On s'assure que la liste de groupes d'hôtes retournée est bien vide
170 178
        self.assertEqual(
171
            json, {"items": []}
179
            json, {'leaves': [], 'groups': []}
172 180
        )
173 181

  
174 182
    def test_get_main_host_groups_as_anonymous(self):
......
180 188
        # par un utilisateur anonyme : le contrôleur doit
181 189
        # retourner une erreur 401 (HTTPUnauthorized)
182 190
        self.app.post(
183
            '/rpc/maingroups', {
191
            '/rpc/hosttree', {
184 192
            }, status=401)
185 193

  
186 194
##### Deuxième onglet déroulant du formulaire #####
......
205 213
        # Récupération des groupes d'hôtes
206 214
        # accessibles à l'utilisateur 'manager'
207 215
        response = self.app.post(
208
            '/rpc/hostgroups?maingroupid=%s' % (mainhostgroup.idgroup, ), {
216
            '/rpc/hosttree?parent_id=%s' % (mainhostgroup.idgroup, ), {
209 217
            }, extra_environ={'REMOTE_USER': 'manager'})
210 218
        json = response.json
211 219

  
......
222 230
        # Récupération des groupes d'hôtes
223 231
        # accessibles à l'utilisateur 'poweruser'
224 232
        response = self.app.post(
225
            '/rpc/hostgroups?maingroupid=%s' % (mainhostgroup.idgroup, ), {
233
            '/rpc/hosttree?parent_id=%s' % (mainhostgroup.idgroup, ), {
226 234
            }, extra_environ={'REMOTE_USER': 'poweruser'})
227 235
        json = response.json
228 236

  
......
239 247
        # Récupération des groupes d'hôtes
240 248
        # accessibles à l'utilisateur 'user'
241 249
        response = self.app.post(
242
            '/rpc/hostgroups?maingroupid=%s' % (mainhostgroup.idgroup, ), {
250
            '/rpc/hosttree?parent_id=%s' % (mainhostgroup.idgroup, ), {
243 251
            }, extra_environ={'REMOTE_USER': 'user'})
244 252
        json = response.json
245 253

  
......
263 271
        # Récupération des groupes d'hôtes
264 272
        # accessibles à l'utilisateur 'visitor'
265 273
        response = self.app.post(
266
            '/rpc/hostgroups?maingroupid=%s' % (mainhostgroup.idgroup, ), {
274
            '/rpc/hosttree?parent_id=%s' % (mainhostgroup.idgroup, ), {
267 275
            }, extra_environ={'REMOTE_USER': 'visitor'})
268 276
        json = response.json
269 277

  
270
        # On s'assure que la liste de groupes d'hôtes
271
        # retournée contient uniquement 'No subgroups'
278
        # On s'assure que la liste de groupes d'hôtes retournée est bien vide
272 279
        self.assertEqual(
273
            json, {"items": [['No subgroup', '%s'
274
                % (mainhostgroup.idgroup, )]]}
280
            json, {'leaves': [], 'groups': []}
275 281
        )
276 282

  
277 283
    def test_get_host_groups_as_anonymous(self):
......
287 293
        # utilisateur anonyme : le contrôleur doit
288 294
        # retourner une erreur 401 (HTTPUnauthorized)
289 295
        self.app.post(
290
            '/rpc/hostgroups?maingroupid=%s' % (mainhostgroup.idgroup, ), {
296
            '/rpc/hosttree?parent_id=%s' % (mainhostgroup.idgroup, ), {
291 297
            }, status=401)
292 298

  
293 299
    def test_get_host_groups_from_inexisting_main_group(self):
......
298 304
        # Récupération des groupes d'hôtes accessibles à l'utilisateur
299 305
        # 'manager' et appartenant à un groupe principal inexistant
300 306
        response = self.app.post(
301
            '/rpc/hostgroups?maingroupid=6666666', {
307
            '/rpc/hosttree?parent_id=6666666', {
302 308
            }, extra_environ={'REMOTE_USER': 'manager'})
303 309
        json = response.json
304 310

  
......
323 329
        hostgroup1 = DBSession.query(SupItemGroup).filter(
324 330
            SupItemGroup.name == u'hg1').first()
325 331

  
332
        # Récupération du groupe d'hôtes 'hg2' dans la base de données
333
        hostgroup2 = DBSession.query(SupItemGroup).filter(
334
            SupItemGroup.name == u'hg2').first()
335

  
336
        # Récupération de l'hôte 'host1' dans la base de données
337
        host1 = DBSession.query(Host).filter(
338
            Host.name == u'host1').first()
339

  
340
        # Récupération de l'hôte 'host2' dans la base de données
341
        host2 = DBSession.query(Host).filter(
342
            Host.name == u'host2').first()
343

  
326 344
        # Récupération des hôtes du groupe 'mhg'
327 345
        # accessibles à l'utilisateur 'manager'
328 346
        response = self.app.post(
329
            '/rpc/hosts?othergroupid=%s' % (mainhostgroup.idgroup, ), {
347
            '/rpc/hosttree?parent_id=%s' % (mainhostgroup.idgroup, ), {
330 348
            }, extra_environ={'REMOTE_USER': 'manager'})
331 349
        json = response.json
332 350

  
333 351
        # On s'assure que la liste d'hôtes retournée contient bien 'host1'
334 352
        self.assertEqual(
335
            json, {"items": [
336
                ['host1', unicode(mainhostgroup.idgroup)],
337
            ]}
353
            json, {
354
                'leaves': [{'id': host1.idhost, 'name': host1.name}],
355
                'groups': [
356
                    {'id': hostgroup1.idgroup, 'name': hostgroup1.name},
357
                    {'id': hostgroup2.idgroup, 'name': hostgroup2.name}
358
                ]
359
            }
338 360
        )
339 361

  
340 362
        # Récupération des hôtes du groupe 'hg1'
341 363
        # accessibles à l'utilisateur 'manager'
342 364
        response = self.app.post(
343
            '/rpc/hosts?othergroupid=%s' % (hostgroup1.idgroup, ), {
365
            '/rpc/hosttree?parent_id=%s' % (hostgroup1.idgroup, ), {
344 366
            }, extra_environ={'REMOTE_USER': 'manager'})
345 367
        json = response.json
346 368

  
347 369
        # On s'assure que la liste d'hotes retournée contient bien 'host2'
348 370
        self.assertEqual(
349
            json, {"items": [
350
                ['host2', unicode(hostgroup1.idgroup)],
351
            ]}
371
            json, {
372
                'leaves': [{'id': host2.idhost, 'name': host2.name}],
373
                'groups': []
374
            }
352 375
        )
353 376

  
354 377
        # Récupération des hôtes du groupe 'mhg'
355 378
        # accessibles à l'utilisateur 'poweruser'
356 379
        response = self.app.post(
357
            '/rpc/hosts?othergroupid=%s' % (mainhostgroup.idgroup, ), {
380
            '/rpc/hosttree?parent_id=%s' % (mainhostgroup.idgroup, ), {
358 381
            }, extra_environ={'REMOTE_USER': 'poweruser'})
359 382
        json = response.json
360 383

  
361 384
        # On s'assure que la liste d'hôtes retournée contient bien 'host1'
362 385
        self.assertEqual(
363
            json, {"items": [
364
                ['host1', unicode(mainhostgroup.idgroup)],
365
            ]}
386
            json, {
387
                'leaves': [{'id': host1.idhost, 'name': host1.name}],
388
                'groups': [
389
                    {'id': hostgroup1.idgroup, 'name': hostgroup1.name},
390
                ]
391
            }
366 392
        )
367 393

  
368 394
        # Récupération des hôtes du groupe 'hg1'
369 395
        # accessibles à l'utilisateur 'poweruser'
370 396
        response = self.app.post(
371
            '/rpc/hosts?othergroupid=%s' % (hostgroup1.idgroup, ), {
397
            '/rpc/hosttree?parent_id=%s' % (hostgroup1.idgroup, ), {
372 398
            }, extra_environ={'REMOTE_USER': 'poweruser'})
373 399
        json = response.json
374 400

  
375 401
        # On s'assure que la liste d'hotes retournée contient bien 'host2'
376 402
        self.assertEqual(
377
            json, {"items": [
378
                ['host2', unicode(hostgroup1.idgroup)],
379
            ]}
403
            json, {
404
                'leaves': [{'id': host2.idhost, 'name': host2.name}],
405
                'groups': []
406
            }
380 407
        )
381 408

  
382 409
        # Récupération des hôtes du groupe 'hg1'
383 410
        # accessibles à l'utilisateur 'user'
384 411
        response = self.app.post(
385
            '/rpc/hosts?othergroupid=%s' % (hostgroup1.idgroup, ), {
412
            '/rpc/hosttree?parent_id=%s' % (hostgroup1.idgroup, ), {
386 413
            }, extra_environ={'REMOTE_USER': 'user'})
387 414
        json = response.json
388 415

  
389 416
        # On s'assure que la liste d'hôtes retournée contient bien 'host2'
390 417
        self.assertEqual(
391
            json, {"items": [
392
                ['host2', unicode(hostgroup1.idgroup)],
393
            ]}
418
            json, {
419
                'leaves': [{'id': host2.idhost, 'name': host2.name}],
420
                'groups': []
421
            }
394 422
        )
395 423

  
396 424
    def test_get_hosts_when_not_allowed(self):
......
415 443
        # Récupération des hôtes du groupe 'mhg'
416 444
        # accessibles à l'utilisateur 'user'
417 445
        response = self.app.post(
418
            '/rpc/hosts?othergroupid=%s' % (mainhostgroup.idgroup, ), {
446
            '/rpc/hosttree?parent_id=%s' % (mainhostgroup.idgroup, ), {
419 447
            }, extra_environ={'REMOTE_USER': 'user'})
420 448
        json = response.json
421 449

  
422
        # On s'assure que la liste
423
        # d'hôtes retournée est vide
450
        # On s'assure que la liste d'hôtes retournée est vide
424 451
        self.assertEqual(
425
            json, {"items": []}
452
            json, {'leaves': [], 'groups': []}
426 453
        )
427 454

  
428 455
        # Récupération des hôtes du groupe 'hg2'
429 456
        # accessibles à l'utilisateur 'user'
430 457
        response = self.app.post(
431
            '/rpc/hosts?othergroupid=%s' % (hostgroup2.idgroup, ), {
458
            '/rpc/hosttree?parent_id=%s' % (hostgroup2.idgroup, ), {
432 459
            }, extra_environ={'REMOTE_USER': 'user'})
433 460
        json = response.json
434 461

  
435
        # On s'assure que la liste
436
        # d'hôtes retournée est vide
462
        # On s'assure que la liste d'hôtes retournée est vide
437 463
        self.assertEqual(
438
            json, {"items": []}
464
            json, {'leaves': [], 'groups': []}
439 465
        )
440 466

  
441 467
        # Récupération des hôtes du groupe 'mhg'
442 468
        # accessibles à l'utilisateur 'visitor'
443 469
        response = self.app.post(
444
            '/rpc/hosts?othergroupid=%s' % (mainhostgroup.idgroup, ), {
470
            '/rpc/hosttree?parent_id=%s' % (mainhostgroup.idgroup, ), {
445 471
            }, extra_environ={'REMOTE_USER': 'visitor'})
446 472
        json = response.json
447 473

  
448
        # On s'assure que la liste
449
        # d'hôtes retournée est vide
474
        # On s'assure que la liste d'hôtes retournée est vide
450 475
        self.assertEqual(
451
            json, {"items": []}
476
            json, {'leaves': [], 'groups': []}
452 477
        )
453 478

  
454 479
        # Récupération des hôtes du groupe 'hg1'
455 480
        # accessibles à l'utilisateur 'visitor'
456 481
        response = self.app.post(
457
            '/rpc/hosts?othergroupid=%s' % (hostgroup1.idgroup, ), {
482
            '/rpc/hosttree?parent_id=%s' % (hostgroup1.idgroup, ), {
458 483
            }, extra_environ={'REMOTE_USER': 'visitor'})
459 484
        json = response.json
460 485

  
461 486
        # On s'assure que la liste d'hôtes retournée est vide
462 487
        self.assertEqual(
463
            json, {"items": []}
488
            json, {'leaves': [], 'groups': []}
464 489
        )
465 490

  
466 491
    def test_get_hosts_as_anonymous(self):
......
476 501
        # un utilisateur anonyme : le contrôleur doit
477 502
        # retourner une erreur 401 (HTTPUnauthorized)
478 503
        self.app.post(
479
            '/rpc/hosts?othergroupid=%s' % (mainhostgroup.idgroup, ), {
504
            '/rpc/hosttree?parent_id=%s' % (mainhostgroup.idgroup, ), {
480 505
            }, status=401)
481 506

  
482 507
    def test_get_hosts_from_inexisting_secondary_group(self):
......
487 512
        # Récupération des hôtes accessibles à l'utilisateur
488 513
        # 'manager' et appartenant à un groupe secondaire inexistant
489 514
        response = self.app.post(
490
            '/rpc/hosts?othergroupid=6666666', {
515
            '/rpc/hosttree?parent_id=6666666', {
491 516
            }, extra_environ={'REMOTE_USER': 'manager'})
492 517
        json = response.json
493 518

  
494 519
        # On s'assure que la liste retournée est vide
495 520
        self.assertEqual(
496
            json, {"items": []}
521
            json, {'leaves': [], 'groups': []}
497 522
        )

Also available in: Unified diff