Test Failed
Pull Request — master (#22)
by Rogerio
06:15
created

TestGenericFlow.test_from_flow_stats__x01()   A

Complexity

Conditions 1

Size

Total Lines 37
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 30
dl 0
loc 37
rs 9.16
c 0
b 0
f 0
cc 1
nop 1
1
"""Module to test the main napp file."""
2
import json
3
from unittest import TestCase
4
from unittest.mock import MagicMock, Mock, patch
5
6
from kytos.lib.helpers import (
7
    get_controller_mock,
8
    get_test_client,
9
    get_kytos_event_mock,
10
    get_switch_mock,
11
)
12
from napps.amlight.flow_stats.main import GenericFlow, Main
13
from napps.kytos.of_core.v0x01.flow import Action as Action10
14
from napps.kytos.of_core.v0x04.flow import Action as Action40
15
from napps.kytos.of_core.v0x04.match_fields import (
16
    MatchDLVLAN,
17
    MatchFieldFactory,
18
)
19
from pyof.foundation.basic_types import UBInt32
20
from pyof.v0x04.common.flow_instructions import InstructionType
21
from pyof.v0x01.controller2switch.common import StatsType
22
from pyof.v0x04.controller2switch.common import MultipartType
23
24
25
# pylint: disable=too-many-public-methods, too-many-lines
26
class TestMain(TestCase):
27
    """Test the Main class."""
28
29
    def setUp(self):
30
        """Execute steps before each tests.
31
32
        Set the server_name_url_url from amlight/flow_stats
33
        """
34
        self.server_name_url = "http://localhost:8181/api/amlight/flow_stats"
35
36
        # The decorator run_on_thread is patched, so methods that listen
37
        # for events do not run on threads while tested.
38
        # Decorators have to be patched before the methods that are
39
        # decorated with them are imported.
40
        patch("kytos.core.helpers.run_on_thread", lambda x: x).start()
41
42
        self.napp = Main(get_controller_mock())
43
44
    @staticmethod
45
    def get_napp_urls(napp):
46
        """Return the amlight/flow_stats urls.
47
48
        The urls will be like:
49
50
        urls = [
51
            (options, methods, url)
52
        ]
53
54
        """
55
        controller = napp.controller
56
        controller.api_server.register_napp_endpoints(napp)
57
58
        urls = []
59
        for rule in controller.api_server.app.url_map.iter_rules():
60
            options = {}
61
            for arg in rule.arguments:
62
                options[arg] = f"[{0}]".format(arg)
63
64
            if f"{napp.username}/{napp.name}" in str(rule):
65
                urls.append((options, rule.methods, f"{str(rule)}"))
66
67
        return urls
68
69
    def test_verify_api_urls(self):
70
        """Verify all APIs registered."""
71
72
        expected_urls = [
73
            (
74
                {"dpid": "[dpid]"},
75
                {"OPTIONS", "HEAD", "GET"},
76
                "/api/amlight/flow_stats/flow/match/ ",
77
            ),
78
            (
79
                {"dpid": "[dpid]"},
80
                {"OPTIONS", "HEAD", "GET"},
81
                "/api/amlight/flow_stats/flow/stats/",
82
            ),
83
            (
84
                {"flow_id": "[flow_id]"},
85
                {"OPTIONS", "HEAD", "GET"},
86
                "/api/amlight/flow_stats/packet_count/",
87
            ),
88
            (
89
                {"flow_id": "[flow_id]"},
90
                {"OPTIONS", "HEAD", "GET"},
91
                "/api/amlight/flow_stats/bytes_count/",
92
            ),
93
            (
94
                {"dpid": "[dpid]"},
95
                {"OPTIONS", "HEAD", "GET"},
96
                "/api/amlight/flow_stats/packet_count/per_flow/",
97
            ),
98
            (
99
                {"dpid": "[dpid]"},
100
                {"OPTIONS", "HEAD", "GET"},
101
                "/api/amlight/flow_stats/packet_count/sum/",
102
            ),
103
            (
104
                {"dpid": "[dpid]"},
105
                {"OPTIONS", "HEAD", "GET"},
106
                "/api/amlight/flow_stats/bytes_count/per_flow/",
107
            ),
108
            (
109
                {"dpid": "[dpid]"},
110
                {"OPTIONS", "HEAD", "GET"},
111
                "/api/amlight/flow_stats/bytes_count/sum/",
112
            ),
113
        ]
114
        urls = self.get_napp_urls(self.napp)
115
        self.assertEqual(len(expected_urls), len(urls))
116
117
    def test_packet_count__fail(self):
118
        """Test packet_count rest call with wrong flow_id."""
119
        flow_id = "123456789"
120
        rest_name = "packet_count"
121
        response = self._get_rest_response(rest_name, flow_id)
122
123
        self.assertEqual(response.data, b"Flow does not exist")
124
125
    def test_packet_count(self):
126
        """Test packet_count rest call."""
127
        flow_id = "1"
128
        rest_name = "packet_count"
129
        self._patch_switch_flow(flow_id)
130
        response = self._get_rest_response(rest_name, flow_id)
131
132
        json_response = json.loads(response.data)
133
        self.assertEqual(json_response["flow_id"], flow_id)
134
        self.assertEqual(json_response["packet_counter"], 40)
135
        self.assertEqual(json_response["packet_per_second"], 2.0)
136
137
    def test_bytes_count_fail(self):
138
        """Test bytes_count rest call with wrong flow_id."""
139
        flow_id = "123456789"
140
        rest_name = "bytes_count"
141
        response = self._get_rest_response(rest_name, flow_id)
142
143
        self.assertEqual(response.data, b"Flow does not exist")
144
145
    def test_bytes_count(self):
146
        """Test bytes_count rest call."""
147
        flow_id = "1"
148
        rest_name = "bytes_count"
149
        self._patch_switch_flow(flow_id)
150
        response = self._get_rest_response(rest_name, flow_id)
151
152
        json_response = json.loads(response.data)
153
        self.assertEqual(json_response["flow_id"], flow_id)
154
        self.assertEqual(json_response["bytes_counter"], 10)
155
        self.assertEqual(json_response["bits_per_second"], 4.0)
156
157 View Code Duplication
    def test_packet_count_per_flow(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
158
        """Test packet_count_per_flow rest call."""
159
        flow_id = "1"
160
        rest_name = "packet_count/per_flow"
161
        self._patch_switch_flow(flow_id)
162
163
        dpid_id = 111
164
        response = self._get_rest_response(rest_name, dpid_id)
165
166
        json_response = json.loads(response.data)
167
        self.assertEqual(json_response[0]["flow_id"], flow_id)
168
        self.assertEqual(json_response[0]["packet_counter"], 40)
169
        self.assertEqual(json_response[0]["packet_per_second"], 2.0)
170
171
    def test_packet_count_sum(self):
172
        """Test packet_count_sum rest call."""
173
        flow_id = "1"
174
        rest_name = "packet_count/sum"
175
        self._patch_switch_flow(flow_id)
176
177
        dpid_id = 111
178
        response = self._get_rest_response(rest_name, dpid_id)
179
        json_response = json.loads(response.data)
180
181
        self.assertEqual(json_response, 40)
182
183 View Code Duplication
    def test_bytes_count_per_flow(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
184
        """Test bytes_count_per_flow rest call."""
185
        flow_id = "1"
186
        rest_name = "bytes_count/per_flow"
187
        self._patch_switch_flow(flow_id)
188
189
        dpid_id = 111
190
        response = self._get_rest_response(rest_name, dpid_id)
191
192
        json_response = json.loads(response.data)
193
        self.assertEqual(json_response[0]["flow_id"], flow_id)
194
        self.assertEqual(json_response[0]["bytes_counter"], 10)
195
        self.assertEqual(json_response[0]["bits_per_second"], 4.0)
196
197
    def test_bytes_count_sum(self):
198
        """Test bytes_count_sum rest call."""
199
        flow_id = "1"
200
        rest_name = "bytes_count/sum"
201
        self._patch_switch_flow(flow_id)
202
203
        dpid_id = 111
204
        response = self._get_rest_response(rest_name, dpid_id)
205
        json_response = json.loads(response.data)
206
207
        self.assertEqual(json_response, 10)
208
209
    @patch("napps.amlight.flow_stats.main.Main.match_flows")
210
    def test_flow_match(self, mock_match_flows):
211
        """Test flow_match rest call."""
212
        flow = GenericFlow()
213
        flow.actions = [
214
            Action10.from_dict(
215
                {
216
                    "action_type": "output",
217
                    "port": "1",
218
                }
219
            ),
220
        ]
221
        flow.version = "0x04"
222
        mock_match_flows.return_value = flow
223
224
        flow_id = "1"
225
        rest_name = "flow/match"
226
        self._patch_switch_flow(flow_id)
227
228
        dpid_id = "aa:00:00:00:00:00:00:11"
229
        response = self._get_rest_response(rest_name, dpid_id)
230
        json_response = json.loads(response.data)
231
232
        self.assertEqual(response.status_code, 200)
233
        self.assertEqual(json_response["actions"][0]["action_type"], "output")
234
        self.assertEqual(json_response["actions"][0]["port"], "1")
235
        self.assertEqual(json_response["version"], "0x04")
236
237
    @patch("napps.amlight.flow_stats.main.Main.match_flows")
238
    def test_flow_match_fail(self, mock_match_flows):
239
        """Test flow_match rest call."""
240
        mock_match_flows.return_value = None
241
242
        flow_id = "1"
243
        rest_name = "flow/match"
244
        self._patch_switch_flow(flow_id)
245
246
        dpid_id = "aa:00:00:00:00:00:00:11"
247
        response = self._get_rest_response(rest_name, dpid_id)
248
249
        self.assertEqual(response.status_code, 404)
250
251
    @patch("napps.amlight.flow_stats.main.Main.match_flows")
252
    def test_flow_stats(self, mock_match_flows):
253
        """Test flow_match rest call."""
254
        flow = GenericFlow()
255
        flow.actions = [
256
            Action10.from_dict(
257
                {
258
                    "action_type": "output",
259
                    "port": "1",
260
                }
261
            ),
262
        ]
263
        flow.version = "0x04"
264
        mock_match_flows.return_value = [flow]
265
266
        flow_id = "1"
267
        rest_name = "flow/stats"
268
        self._patch_switch_flow(flow_id)
269
270
        dpid_id = "aa:00:00:00:00:00:00:11"
271
        response = self._get_rest_response(rest_name, dpid_id)
272
        json_response = json.loads(response.data)
273
274
        self.assertEqual(response.status_code, 200)
275
        print(json_response)
276
        self.assertEqual(json_response[0]["actions"][0]["action_type"],
277
                         "output")
278
        self.assertEqual(json_response[0]["actions"][0]["port"], "1")
279
        self.assertEqual(json_response[0]["version"], "0x04")
280
281
    def _patch_switch_flow(self, flow_id):
282
        """Helper method to patch controller to return switch/flow data."""
283
        # patching the flow_stats object in the switch
284
        self.napp.controller.switches = MagicMock()
285
        flow = self._get_mocked_flow_stats()
286
        flow.id = flow_id
287
        switch = MagicMock()
288
        switch.generic_flows = [flow]
289
        self.napp.controller.switches.values.return_value = [switch]
290
        self.napp.controller.get_switch_by_dpid = MagicMock()
291
        self.napp.controller.get_switch_by_dpid.return_value = switch
292
293
    def _get_rest_response(self, rest_name, url_id):
294
        """Helper method to call a rest endpoint."""
295
        # call rest
296
        api = get_test_client(get_controller_mock(), self.napp)
297
        url = f"{self.server_name_url}/{rest_name}/{url_id}"
298
        response = api.get(url, content_type="application/json")
299
300
        return response
301
302
    # pylint: disable=no-self-use
303
    def _get_mocked_flow_stats(self):
304
        """Helper method to create a mock flow_stats object."""
305
        flow_stats = MagicMock()
306
        flow_stats.id = 123
307
        flow_stats.byte_count = 10
308
        flow_stats.duration_sec = 20
309
        flow_stats.duration_nsec = 30
310
        flow_stats.packet_count = 40
311
        return flow_stats
312
313
    def _get_mocked_flow_base(self):
314
        """Helper method to create a mock flow object."""
315
        flow = MagicMock()
316
        flow.id = 456
317
        flow.switch = None
318
        flow.table_id = None
319
        flow.match = None
320
        flow.priority = None
321
        flow.idle_timeout = None
322
        flow.hard_timeout = None
323
        flow.cookie = None
324
        flow.stats = self._get_mocked_flow_stats()
325
        return flow
326
327
    @patch("napps.amlight.flow_stats.main.GenericFlow.from_flow_stats")
328
    def test_handle_stats_reply(self, mock_from_flow):
329
        """Test handle_stats_reply rest call."""
330
        mock_from_flow.return_value = self._get_mocked_flow_base()
331
332
        def side_effect(event):
333
            self.assertTrue(f"{event}", "amlight/flow_stats.flows_updated")
334
            self.assertTrue(event.content["switch"], 111)
335
336
        self.napp.controller = MagicMock()
337
        self.napp.controller.buffers.app.put.side_effect = side_effect
338
339
        msg = MagicMock()
340
        msg.flags.value = 2
341
        msg.body = [self._get_mocked_flow_stats()]
342
        event_switch = MagicMock()
343
        event_switch.generic_flows = []
344
        event_switch.dpid = 111
345
        self.napp.handle_stats_reply(msg, event_switch)
346
347
        # Check if important trace dont trigger the event
348
        # It means that the CP trace is the same to the DP trace
349
        self.napp.controller.buffers.app.put.assert_called_once()
350
351
        # Check mocked flow id
352
        self.assertEqual(event_switch.generic_flows[0].id, 456)
353
354
    @patch("napps.amlight.flow_stats.main.Main.handle_stats_reply")
355
    def test_handle_stats_reply_0x01(self, mock_handle_stats):
356
        """Test handle stats reply."""
357
        mock_handle_stats.return_value = "ABC"
358
359
        flow_msg = MagicMock()
360
        flow_msg.body = "A"
361
        flow_msg.body_type = StatsType.OFPST_FLOW
362
363
        switch_v0x01 = get_switch_mock("00:00:00:00:00:00:00:01", 0x01)
364
365
        name = "kytos/of_core.v0x01.messages.in.ofpt_stats_reply"
366
        content = {"source": switch_v0x01.connection, "message": flow_msg}
367
        event = get_kytos_event_mock(name=name, content=content)
368
        self.napp.handle_stats_reply_0x01(event)
369
        mock_handle_stats.assert_called_once()
370
371
    @patch("napps.amlight.flow_stats.main.Main.handle_stats_reply")
372
    def test_handle_stats_reply_0x01__fail(self, mock_handle_stats):
373
        """Test handle stats reply."""
374
        mock_handle_stats.return_value = "ABC"
375
        flow_msg = MagicMock()
376
        flow_msg.body = "A"
377
        flow_msg.body_type = StatsType.OFPST_DESC
378
379
        switch_v0x01 = get_switch_mock("00:00:00:00:00:00:00:01", 0x01)
380
381
        name = "kytos/of_core.v0x01.messages.in.ofpt_stats_reply"
382
        content = {"source": switch_v0x01.connection, "message": flow_msg}
383
        event = get_kytos_event_mock(name=name, content=content)
384
        self.napp.handle_stats_reply_0x01(event)
385
        mock_handle_stats.assert_not_called()
386
387
    def test_handle_stats_reply_0x04(self):
388
        """Test handle stats reply."""
389
        self.napp.handle_stats_reply = Mock()
390
        flow_msg = MagicMock()
391
        flow_msg.body = "A"
392
        flow_msg.multipart_type = MultipartType.OFPMP_FLOW
393
394
        switch_v0x04 = get_switch_mock("00:00:00:00:00:00:00:01", 0x04)
395
396
        name = "kytos/of_core.v0x04.messages.in.ofpt_multipart_reply"
397
        content = {"source": switch_v0x04.connection, "message": flow_msg}
398
        event = get_kytos_event_mock(name=name, content=content)
399
        event.content["message"] = flow_msg
400
401
        self.napp.handle_stats_reply_0x04(event)
402
403
        self.napp.handle_stats_reply.assert_called_once()
404
405
    def test_handle_stats_reply_0x04_fail(self):
406
        """Test handle stats reply."""
407
        self.napp.handle_stats_reply = MagicMock()
408
        flow_msg = MagicMock()
409
        flow_msg.body = "A"
410
411
        flow_msg.multipart_type = MultipartType.OFPMP_PORT_DESC
412
413
        switch_v0x04 = get_switch_mock("00:00:00:00:00:00:00:01", 0x04)
414
415
        name = "kytos/of_core.v0x04.messages.in.ofpt_multipart_reply"
416
        content = {"source": switch_v0x04.connection, "message": flow_msg}
417
418
        event = get_kytos_event_mock(name=name, content=content)
419
        event.content["message"] = flow_msg
420
421
        self.napp.handle_stats_reply_0x04(event)
422
423
        self.napp.handle_stats_reply.assert_not_called()
424
425
426
# pylint: disable=too-many-public-methods, too-many-lines
427
class TestGenericFlow(TestCase):
428
    """Test the GenericFlow class."""
429
430
    # pylint: disable=no-member
431
    def test_from_flow_stats__x01(self):
432
        """Test from_flow_stats method 0x01 version."""
433
        flow_stats = MagicMock()
434
435
        flow_stats.actions = [
436
            Action10.from_dict(
437
                {
438
                    "action_type": "output",
439
                    "port": UBInt32(1),
440
                }
441
            ).as_of_action(),
442
        ]
443
444
        result = GenericFlow.from_flow_stats(flow_stats, version="0x01")
445
446
        self.assertEqual(result.idle_timeout, flow_stats.idle_timeout.value)
447
        self.assertEqual(result.hard_timeout, flow_stats.hard_timeout.value)
448
        self.assertEqual(result.priority, flow_stats.priority.value)
449
        self.assertEqual(result.table_id, flow_stats.table_id.value)
450
        self.assertEqual(result.duration_sec, flow_stats.duration_sec.value)
451
        self.assertEqual(result.packet_count, flow_stats.packet_count.value)
452
        self.assertEqual(result.byte_count, flow_stats.byte_count.value)
453
454
        exp_match = flow_stats.match
455
        self.assertEqual(result.match["wildcards"], exp_match.wildcards.value)
456
        self.assertEqual(result.match["in_port"], exp_match.in_port.value)
457
        self.assertEqual(result.match["eth_src"], exp_match.dl_src.value)
458
        self.assertEqual(result.match["eth_dst"], exp_match.dl_dst.value)
459
        self.assertEqual(result.match["vlan_vid"], exp_match.dl_vlan.value)
460
        self.assertEqual(result.match["vlan_pcp"], exp_match.dl_vlan_pcp.value)
461
        self.assertEqual(result.match["eth_type"], exp_match.dl_type.value)
462
        self.assertEqual(result.match["ip_tos"], exp_match.nw_tos.value)
463
        self.assertEqual(result.match["ipv4_src"], exp_match.nw_src.value)
464
        self.assertEqual(result.match["ipv4_dst"], exp_match.nw_dst.value)
465
        self.assertEqual(result.match["ip_proto"], exp_match.nw_proto.value)
466
        self.assertEqual(result.match["tcp_src"], exp_match.tp_src.value)
467
        self.assertEqual(result.match["tcp_dst"], exp_match.tp_dst.value)
468
469
    def test_from_flow_stats__x04_match(self):
470
        """Test from_flow_stats method 0x04 version with match."""
471
        flow_stats = MagicMock()
472
        flow_stats.actions = [
473
            Action10.from_dict(
474
                {
475
                    "action_type": "output",
476
                    "port": UBInt32(1),
477
                }
478
            ).as_of_action(),
479
        ]
480
481
        flow_stats.match.oxm_match_fields = [MatchDLVLAN(42).as_of_tlv()]
482
483
        result = GenericFlow.from_flow_stats(flow_stats, version="0x04")
484
485
        match_expect = MatchFieldFactory.from_of_tlv(
486
            flow_stats.match.oxm_match_fields[0]
487
        )
488
        self.assertEqual(result.match["vlan_vid"], match_expect)
489
490
    def test_from_flow_stats__x04_action(self):
491
        """Test from_flow_stats method 0x04 version with action."""
492
        flow_stats = MagicMock()
493
494
        action_dict = {
495
            "action_type": "output",
496
            "port": UBInt32(1),
497
        }
498
        instruction = MagicMock()
499
        instruction.instruction_type = InstructionType.OFPIT_APPLY_ACTIONS
500
        instruction.actions = [Action40.from_dict(action_dict).as_of_action()]
501
        flow_stats.instructions = [instruction]
502
503
        result = GenericFlow.from_flow_stats(flow_stats, version="0x04")
504
        self.assertEqual(result.actions[0].as_dict(), action_dict)
505
506
    def test_to_dict__x01(self):
507
        """Test to_dict method 0x01 version."""
508
        action = Action10.from_dict(
509
            {
510
                "action_type": "set_vlan",
511
                "vlan_id": 6,
512
            }
513
        )
514
        match = {}
515
        match["in_port"] = 11
516
517
        generic_flow = GenericFlow(
518
            version="0x01",
519
            match=match,
520
            idle_timeout=1,
521
            hard_timeout=2,
522
            duration_sec=3,
523
            packet_count=4,
524
            byte_count=5,
525
            priority=6,
526
            table_id=7,
527
            cookie=8,
528
            buffer_id=9,
529
            actions=[action],
530
        )
531
532
        result = generic_flow.to_dict()
533
534
        expected = {
535
            "version": "0x01",
536
            "idle_timeout": 1,
537
            "in_port": 11,
538
            "hard_timeout": 2,
539
            "priority": 6,
540
            "table_id": 7,
541
            "cookie": 8,
542
            "buffer_id": 9,
543
            "actions": [{"vlan_id": 6, "action_type": "set_vlan"}],
544
        }
545
546
        self.assertEqual(result, expected)
547
548
    def test_to_dict__x04(self):
549
        """Test to_dict method 0x04 version."""
550
        match = {}
551
        match["in_port"] = MagicMock()
552
        match["in_port"].value = 22
553
554
        generic_flow = GenericFlow(
555
            version="0x04",
556
            match=match,
557
        )
558
559
        result = generic_flow.to_dict()
560
        expected = {
561
            "version": "0x04",
562
            "in_port": 22,
563
            "idle_timeout": 0,
564
            "hard_timeout": 0,
565
            "priority": 0,
566
            "table_id": 255,
567
            "cookie": None,
568
            "buffer_id": None,
569
            "actions": [],
570
        }
571
572
        self.assertEqual(result, expected)
573
574
    def test_match_to_dict(self):
575
        """Test match_to_dict method for 0x04 version."""
576
        match = {}
577
        match["in_port"] = MagicMock()
578
        match["in_port"].value = 22
579
        match["vlan_vid"] = MagicMock()
580
        match["vlan_vid"].value = 123
581
582
        generic_flow = GenericFlow(
583
            version="0x04",
584
            match=match,
585
        )
586
        result = generic_flow.match_to_dict()
587
        expected = {"in_port": 22, "vlan_vid": 123}
588
589
        self.assertEqual(result, expected)
590
591
    def test_match_to_dict__empty_match(self):
592
        """Test match_to_dict method for 0x04 version, with empty matches."""
593
        generic_flow = GenericFlow(version="0x04")
594
        result = generic_flow.match_to_dict()
595
        self.assertEqual(result, {})
596
597
    def test_id__x01(self):
598
        """Test id method 0x01 version."""
599
        action = Action10.from_dict(
600
            {
601
                "action_type": "set_vlan",
602
                "vlan_id": 6,
603
            }
604
        )
605
        match = {}
606
        match["in_port"] = 11
607
608
        generic_flow = GenericFlow(
609
            version="0x01",
610
            match=match,
611
            idle_timeout=1,
612
            hard_timeout=2,
613
            priority=6,
614
            table_id=7,
615
            cookie=8,
616
            buffer_id=9,
617
            actions=[action],
618
        )
619
620
        self.assertEqual(generic_flow.id, "78d18f2cffd3eaa069afbf0995b90db9")
621
622
    def test_id__x04(self):
623
        """Test id method 0x04 version."""
624
        match = {}
625
        match["in_port"] = MagicMock()
626
        match["in_port"].value = 22
627
        match["vlan_vid"] = MagicMock()
628
        match["vlan_vid"].value = 123
629
630
        generic_flow = GenericFlow(
631
            version="0x04",
632
            match=match,
633
            idle_timeout=1,
634
            hard_timeout=2,
635
            priority=6,
636
            table_id=7,
637
            cookie=8,
638
            buffer_id=9,
639
        )
640
641
        self.assertEqual(generic_flow.id, "2d843f76b8b254fad6c6e2a114590440")
642