synapse 2.152.0__py311-none-any.whl → 2.154.0__py311-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of synapse might be problematic. Click here for more details.
- synapse/axon.py +19 -16
- synapse/cortex.py +203 -15
- synapse/exc.py +0 -2
- synapse/lib/ast.py +42 -23
- synapse/lib/autodoc.py +2 -2
- synapse/lib/cache.py +16 -1
- synapse/lib/cell.py +5 -5
- synapse/lib/httpapi.py +198 -2
- synapse/lib/layer.py +5 -2
- synapse/lib/modelrev.py +36 -3
- synapse/lib/node.py +2 -5
- synapse/lib/parser.py +1 -1
- synapse/lib/schemas.py +51 -0
- synapse/lib/snap.py +10 -0
- synapse/lib/storm.lark +24 -4
- synapse/lib/storm.py +98 -19
- synapse/lib/storm_format.py +1 -1
- synapse/lib/stormhttp.py +11 -4
- synapse/lib/stormlib/auth.py +16 -2
- synapse/lib/stormlib/backup.py +1 -0
- synapse/lib/stormlib/basex.py +2 -0
- synapse/lib/stormlib/cell.py +7 -0
- synapse/lib/stormlib/compression.py +3 -0
- synapse/lib/stormlib/cortex.py +1168 -0
- synapse/lib/stormlib/ethereum.py +1 -0
- synapse/lib/stormlib/graph.py +2 -0
- synapse/lib/stormlib/hashes.py +5 -0
- synapse/lib/stormlib/hex.py +6 -0
- synapse/lib/stormlib/infosec.py +6 -1
- synapse/lib/stormlib/ipv6.py +1 -0
- synapse/lib/stormlib/iters.py +58 -1
- synapse/lib/stormlib/json.py +5 -0
- synapse/lib/stormlib/mime.py +1 -0
- synapse/lib/stormlib/model.py +19 -3
- synapse/lib/stormlib/modelext.py +1 -0
- synapse/lib/stormlib/notifications.py +2 -0
- synapse/lib/stormlib/pack.py +2 -0
- synapse/lib/stormlib/random.py +1 -0
- synapse/lib/stormlib/smtp.py +0 -7
- synapse/lib/stormlib/stats.py +223 -0
- synapse/lib/stormlib/stix.py +8 -0
- synapse/lib/stormlib/storm.py +1 -0
- synapse/lib/stormlib/version.py +3 -0
- synapse/lib/stormlib/xml.py +3 -0
- synapse/lib/stormlib/yaml.py +2 -0
- synapse/lib/stormtypes.py +250 -170
- synapse/lib/trigger.py +180 -4
- synapse/lib/types.py +1 -1
- synapse/lib/version.py +2 -2
- synapse/lib/view.py +55 -6
- synapse/models/inet.py +21 -6
- synapse/models/orgs.py +48 -2
- synapse/models/risk.py +126 -2
- synapse/models/syn.py +6 -0
- synapse/tests/files/stormpkg/badapidef.yaml +13 -0
- synapse/tests/files/stormpkg/storm/modules/apimod +10 -0
- synapse/tests/files/stormpkg/testpkg.yaml +23 -0
- synapse/tests/test_axon.py +7 -2
- synapse/tests/test_cortex.py +231 -35
- synapse/tests/test_lib_ast.py +138 -43
- synapse/tests/test_lib_autodoc.py +1 -1
- synapse/tests/test_lib_modelrev.py +9 -0
- synapse/tests/test_lib_node.py +55 -0
- synapse/tests/test_lib_storm.py +14 -1
- synapse/tests/test_lib_stormhttp.py +65 -6
- synapse/tests/test_lib_stormlib_auth.py +12 -3
- synapse/tests/test_lib_stormlib_cortex.py +1327 -0
- synapse/tests/test_lib_stormlib_iters.py +116 -0
- synapse/tests/test_lib_stormlib_stats.py +187 -0
- synapse/tests/test_lib_stormlib_storm.py +8 -0
- synapse/tests/test_lib_stormsvc.py +24 -1
- synapse/tests/test_lib_stormtypes.py +124 -69
- synapse/tests/test_lib_trigger.py +315 -0
- synapse/tests/test_lib_view.py +1 -2
- synapse/tests/test_model_base.py +26 -0
- synapse/tests/test_model_inet.py +22 -0
- synapse/tests/test_model_orgs.py +28 -0
- synapse/tests/test_model_risk.py +73 -0
- synapse/tests/test_tools_autodoc.py +25 -0
- synapse/tests/test_tools_genpkg.py +9 -3
- synapse/tests/utils.py +39 -0
- synapse/tools/autodoc.py +42 -2
- {synapse-2.152.0.dist-info → synapse-2.154.0.dist-info}/METADATA +2 -2
- {synapse-2.152.0.dist-info → synapse-2.154.0.dist-info}/RECORD +87 -79
- {synapse-2.152.0.dist-info → synapse-2.154.0.dist-info}/WHEEL +1 -1
- {synapse-2.152.0.dist-info → synapse-2.154.0.dist-info}/LICENSE +0 -0
- {synapse-2.152.0.dist-info → synapse-2.154.0.dist-info}/top_level.txt +0 -0
|
@@ -552,3 +552,318 @@ class TrigTest(s_t_utils.SynTest):
|
|
|
552
552
|
|
|
553
553
|
trig = await core.callStorm('return ($lib.trigger.get($iden))', opts=opts)
|
|
554
554
|
self.eq(trig.get('user'), derp.iden)
|
|
555
|
+
|
|
556
|
+
async def test_trigger_edges(self):
|
|
557
|
+
async with self.getTestCore() as core:
|
|
558
|
+
view = await core.callStorm('return ($lib.view.get().fork().iden)')
|
|
559
|
+
|
|
560
|
+
await self.asyncraises(s_exc.SchemaViolation, core.nodes('''
|
|
561
|
+
$tdef = $lib.dict(
|
|
562
|
+
cond="edge:add",
|
|
563
|
+
form="test:int",
|
|
564
|
+
storm="[+#asdfasdf]"
|
|
565
|
+
)
|
|
566
|
+
$lib.trigger.add($tdef)
|
|
567
|
+
'''))
|
|
568
|
+
|
|
569
|
+
await self.asyncraises(s_exc.SchemaViolation, core.nodes('''
|
|
570
|
+
$tdef = $lib.dict(
|
|
571
|
+
cond="edge:add",
|
|
572
|
+
form="test:int",
|
|
573
|
+
storm="[+#asdfasdf]",
|
|
574
|
+
verb=$lib.null
|
|
575
|
+
)
|
|
576
|
+
$lib.trigger.add($tdef)
|
|
577
|
+
'''))
|
|
578
|
+
|
|
579
|
+
# edge:add
|
|
580
|
+
tdef = {
|
|
581
|
+
'cond': 'edge:add',
|
|
582
|
+
'verb': 'refs',
|
|
583
|
+
'storm': '[ +#neato ] | spin | iden $auto.opts.n2iden | [ +#other ] | [ <(seen)+ { [ test:str=$auto.opts.verb ] } ]',
|
|
584
|
+
'view': view,
|
|
585
|
+
}
|
|
586
|
+
await core.nodes('$lib.trigger.add($tdef)', opts={'vars': {'tdef': tdef}}) # only verb
|
|
587
|
+
|
|
588
|
+
opts = {'view': view}
|
|
589
|
+
|
|
590
|
+
await core.nodes('trigger.add edge:add --verb refs --form test:int --query { [ +#burrito ] }', opts=opts) # n1 + edge
|
|
591
|
+
await core.nodes('trigger.add edge:add --verb refs --n2form test:int --query { [ +#ping ]}', opts=opts) # edge + n2
|
|
592
|
+
await core.nodes('trigger.add edge:add --verb refs --form test:int --n2form test:int --query { [ +#pong ]}', opts=opts) # n1 + verb + n2
|
|
593
|
+
|
|
594
|
+
await core.nodes('[ test:str=foo <(refs)+ { [ test:str=bar ] } ]', opts=opts) # fire the verb-only trigger
|
|
595
|
+
await core.nodes('[ test:int=123 +(refs)> { [ test:str=biz ] } ]', opts=opts) # fire the n1 trigger and the verb trigger
|
|
596
|
+
await core.nodes('[ test:int=456 <(refs)+ { [ test:str=baz ] } ]', opts=opts) # fire the n2 trigger and the verb trigger
|
|
597
|
+
await core.nodes('[ test:int=789 +(refs)> { [ test:int=0 ] } ]', opts=opts) # FIRE ALL THE CANNONS
|
|
598
|
+
|
|
599
|
+
await core.nodes('[ test:int=9876 +(refs)> { [ test:int=54321 ]}]', opts=opts) # explicitly hit the cache
|
|
600
|
+
|
|
601
|
+
node = await core.nodes('test:int=0', opts=opts)
|
|
602
|
+
self.len(1, node)
|
|
603
|
+
self.isin('other', node[0].tags)
|
|
604
|
+
|
|
605
|
+
node = await core.nodes('test:int=123', opts=opts)
|
|
606
|
+
self.len(1, node)
|
|
607
|
+
self.isin('neato', node[0].tags)
|
|
608
|
+
self.isin('burrito', node[0].tags)
|
|
609
|
+
|
|
610
|
+
node = await core.nodes('test:int=456', opts=opts)
|
|
611
|
+
self.len(1, node)
|
|
612
|
+
self.isin('other', node[0].tags)
|
|
613
|
+
|
|
614
|
+
node = await core.nodes('test:int=789', opts=opts)
|
|
615
|
+
self.len(1, node)
|
|
616
|
+
self.isin('neato', node[0].tags)
|
|
617
|
+
self.isin('burrito', node[0].tags)
|
|
618
|
+
self.isin('ping', node[0].tags)
|
|
619
|
+
self.isin('pong', node[0].tags)
|
|
620
|
+
|
|
621
|
+
node = await core.nodes('test:str=foo', opts=opts)
|
|
622
|
+
self.len(1, node)
|
|
623
|
+
self.isin('other', node[0].tags)
|
|
624
|
+
|
|
625
|
+
node = await core.nodes('test:str=bar', opts=opts)
|
|
626
|
+
self.len(1, node)
|
|
627
|
+
self.isin('neato', node[0].tags)
|
|
628
|
+
|
|
629
|
+
node = await core.nodes('test:str=biz', opts=opts)
|
|
630
|
+
self.len(1, node)
|
|
631
|
+
self.isin('other', node[0].tags)
|
|
632
|
+
|
|
633
|
+
node = await core.nodes('test:str=baz', opts=opts)
|
|
634
|
+
self.len(1, node)
|
|
635
|
+
self.isin('ping', node[0].tags)
|
|
636
|
+
self.isin('neato', node[0].tags)
|
|
637
|
+
|
|
638
|
+
node = await core.nodes('test:int=9876', opts=opts)
|
|
639
|
+
self.len(1, node)
|
|
640
|
+
self.isin('neato', node[0].tags)
|
|
641
|
+
self.isin('burrito', node[0].tags)
|
|
642
|
+
self.isin('ping', node[0].tags)
|
|
643
|
+
self.isin('pong', node[0].tags)
|
|
644
|
+
|
|
645
|
+
# invalidate the cache
|
|
646
|
+
await core.nodes('trigger.add edge:add --verb refs --form test:int --n2form test:int --query { [ +#invalid ]}', opts=opts) # n1 + verb + n2
|
|
647
|
+
|
|
648
|
+
node = await core.nodes('[test:int=2468 <(refs)+ { [test:int=1357] }]', opts=opts)
|
|
649
|
+
self.notin('invalid', node[0].tags)
|
|
650
|
+
|
|
651
|
+
node = await core.nodes('test:int=1357', opts=opts)
|
|
652
|
+
self.isin('invalid', node[0].tags)
|
|
653
|
+
|
|
654
|
+
nodes = await core.nodes('test:str=refs -(seen)> *', opts=opts) # collates all the n2 nodes
|
|
655
|
+
ndefs = set([
|
|
656
|
+
('test:int', 0),
|
|
657
|
+
('test:int', 456),
|
|
658
|
+
('test:str', 'foo'),
|
|
659
|
+
('test:str', 'biz'),
|
|
660
|
+
('test:int', 54321),
|
|
661
|
+
('test:int', 2468)
|
|
662
|
+
])
|
|
663
|
+
self.eq(ndefs, set([n.ndef for n in nodes]))
|
|
664
|
+
|
|
665
|
+
nodes = await core.nodes('syn:trigger:cond="edge:add"', opts=opts)
|
|
666
|
+
self.len(5, nodes)
|
|
667
|
+
n2 = 0
|
|
668
|
+
for n in nodes:
|
|
669
|
+
self.eq(n.props['verb'], 'refs')
|
|
670
|
+
if n.props.get('n2form') is not None:
|
|
671
|
+
n2 += 1
|
|
672
|
+
self.eq(n2, 3)
|
|
673
|
+
|
|
674
|
+
await core.nodes('for $trig in $lib.trigger.list() { $lib.trigger.del($trig.iden) }', opts=opts)
|
|
675
|
+
self.len(0, await core.nodes('syn:trigger', opts=opts))
|
|
676
|
+
|
|
677
|
+
# edge:del triggers
|
|
678
|
+
await core.nodes('trigger.add edge:del --verb refs --query { [ +#cookies ] | spin | iden $auto.opts.n2iden | [ +#milk ] }', opts=opts) # only edge
|
|
679
|
+
await core.nodes('trigger.add edge:del --verb refs --form test:int --query { [ +#cupcake ] }', opts=opts) # n1 form + edge
|
|
680
|
+
await core.nodes('trigger.add edge:del --verb refs --n2form test:int --query { [ +#icecream ] }', opts=opts) # edge + n2 form
|
|
681
|
+
await core.nodes('trigger.add edge:del --verb refs --form test:int --n2form test:int --query { [ +#croissant ] }', opts=opts) # n1 form + verb + n2 form
|
|
682
|
+
|
|
683
|
+
await core.nodes('test:str=foo [ <(refs)- { test:str=bar }]', opts=opts) # fire the verb-only trigger
|
|
684
|
+
await core.nodes('test:int=123 | edges.del *', opts=opts) # fire the n1 trigger and verb trigger
|
|
685
|
+
await core.nodes('test:int=456 | edges.del refs --n2', opts=opts) # fire the n2 trigger and verb trigger
|
|
686
|
+
await core.nodes('test:int=789 [ -(refs)> { test:int=0 } ]', opts=opts) # fire everything
|
|
687
|
+
|
|
688
|
+
await core.nodes('test:int=9876 [ -(refs)> { test:int=54321 } ]', opts=opts) # explicitly hit the cache
|
|
689
|
+
|
|
690
|
+
node = await core.nodes('test:int=0', opts=opts)
|
|
691
|
+
self.isin('milk', node[0].tags)
|
|
692
|
+
|
|
693
|
+
node = await core.nodes('test:int=123', opts=opts)
|
|
694
|
+
self.isin('cupcake', node[0].tags)
|
|
695
|
+
self.isin('cookies', node[0].tags)
|
|
696
|
+
|
|
697
|
+
# test:int=456 won't have anything on it, but test:str=baz will
|
|
698
|
+
node = await core.nodes('test:int=789', opts=opts)
|
|
699
|
+
self.isin('cookies', node[0].tags)
|
|
700
|
+
self.isin('icecream', node[0].tags)
|
|
701
|
+
self.isin('croissant', node[0].tags)
|
|
702
|
+
self.isin('cupcake', node[0].tags)
|
|
703
|
+
|
|
704
|
+
node = await core.nodes('test:str=foo', opts=opts)
|
|
705
|
+
self.isin('milk', node[0].tags)
|
|
706
|
+
|
|
707
|
+
node = await core.nodes('test:str=bar', opts=opts)
|
|
708
|
+
self.isin('cookies', node[0].tags)
|
|
709
|
+
|
|
710
|
+
node = await core.nodes('test:str=biz', opts=opts)
|
|
711
|
+
self.isin('milk', node[0].tags)
|
|
712
|
+
|
|
713
|
+
node = await core.nodes('test:str=baz', opts=opts)
|
|
714
|
+
self.isin('cookies', node[0].tags)
|
|
715
|
+
self.isin('icecream', node[0].tags)
|
|
716
|
+
|
|
717
|
+
node = await core.nodes('test:int=9876', opts=opts)
|
|
718
|
+
self.len(1, node)
|
|
719
|
+
self.isin('cookies', node[0].tags)
|
|
720
|
+
self.isin('icecream', node[0].tags)
|
|
721
|
+
self.isin('croissant', node[0].tags)
|
|
722
|
+
self.isin('cupcake', node[0].tags)
|
|
723
|
+
|
|
724
|
+
await core.nodes('trigger.add edge:del --verb refs --form test:int --n2form test:int --query { [ +#scone ] }', opts=opts) # n1 form + verb + n2 form
|
|
725
|
+
node = await core.nodes('test:int=1357 | [ -(refs)> { test:int=2468 } ]', opts=opts)
|
|
726
|
+
self.isin('scone', node[0].tags)
|
|
727
|
+
|
|
728
|
+
nodes = await core.nodes('syn:trigger:cond="edge:del"', opts=opts)
|
|
729
|
+
self.len(5, nodes)
|
|
730
|
+
n2 = 0
|
|
731
|
+
for n in nodes:
|
|
732
|
+
self.eq(n.props['verb'], 'refs')
|
|
733
|
+
if n.props.get('n2form') is not None:
|
|
734
|
+
n2 += 1
|
|
735
|
+
self.eq(n2, 3)
|
|
736
|
+
|
|
737
|
+
# make a pair of nodes in the base view, then the edge in the forked, and rip out one of the nodes
|
|
738
|
+
await core.nodes('[test:int=21701 test:int=23209]')
|
|
739
|
+
await core.nodes('test:int=21701 | [ <(refs)+ { test:int=23209 } ]', opts=opts)
|
|
740
|
+
await core.nodes('test:int=21701 | delnode')
|
|
741
|
+
|
|
742
|
+
await core.nodes('test:int=23209 | edges.del *', opts=opts)
|
|
743
|
+
node = await core.nodes('test:int=23209', opts=opts)
|
|
744
|
+
self.len(1, node)
|
|
745
|
+
self.isin('cookies', node[0].tags)
|
|
746
|
+
self.isin('cupcake', node[0].tags)
|
|
747
|
+
# the other two edge:del triggers cannot run because we can't get to n2 anymore
|
|
748
|
+
|
|
749
|
+
await core.nodes('for $trig in $lib.trigger.list() { $lib.trigger.del($trig.iden) }', opts=opts)
|
|
750
|
+
self.len(0, await core.nodes('syn:trigger', opts=opts))
|
|
751
|
+
|
|
752
|
+
async def test_trigger_edge_globs(self):
|
|
753
|
+
async with self.getTestCore() as core:
|
|
754
|
+
await core.nodes('trigger.add edge:add --verb foo* --query { [ +#foo ] | spin | iden $auto.opts.n2iden | [+#other] }')
|
|
755
|
+
await core.nodes('trigger.add edge:add --verb see* --form test:int --query { [ +#n1 ] }')
|
|
756
|
+
await core.nodes('trigger.add edge:add --verb r* --n2form test:int --query { [ +#n2 ] }')
|
|
757
|
+
await core.nodes('trigger.add edge:add --verb no** --form test:int --n2form test:str --query { [ +#both ] }')
|
|
758
|
+
|
|
759
|
+
async with core.enterMigrationMode():
|
|
760
|
+
nodes = await core.nodes('[test:int=123 +(foo:beep:boop)> { [test:str=neato] }]')
|
|
761
|
+
self.len(1, nodes)
|
|
762
|
+
self.notin('foo', nodes[0].tags)
|
|
763
|
+
|
|
764
|
+
nodes = await core.nodes('[test:int=123 +(foo:bar:baz)> { [test:str=neato] }]')
|
|
765
|
+
self.len(1, nodes)
|
|
766
|
+
self.isin('foo', nodes[0].tags)
|
|
767
|
+
|
|
768
|
+
nodes = await core.nodes('test:str=neato')
|
|
769
|
+
self.len(1, nodes)
|
|
770
|
+
self.isin('other', nodes[0].tags)
|
|
771
|
+
|
|
772
|
+
nodes = await core.nodes('[test:str=stuff +(see.saw)> { test:str=neato } ]')
|
|
773
|
+
self.len(1, nodes)
|
|
774
|
+
self.notin('n1', nodes[0].tags)
|
|
775
|
+
|
|
776
|
+
nodes = await core.nodes('[test:int=456 +(see.saw)> { test:str=neato } ]')
|
|
777
|
+
self.len(1, nodes)
|
|
778
|
+
self.isin('n1', nodes[0].tags)
|
|
779
|
+
|
|
780
|
+
nodes = await core.nodes('[test:str=neato +(ready)> { [ test:str=burrito ] } ]')
|
|
781
|
+
self.len(1, nodes)
|
|
782
|
+
self.notin('n2', nodes[0].tags)
|
|
783
|
+
|
|
784
|
+
nodes = await core.nodes('[test:int=456 +(ready)> { test:int=123 } ]')
|
|
785
|
+
self.len(1, nodes)
|
|
786
|
+
self.isin('n2', nodes[0].tags)
|
|
787
|
+
|
|
788
|
+
nodes = await core.nodes('[test:int=789 +(nope)> { test:int=123 } ]')
|
|
789
|
+
self.len(1, nodes)
|
|
790
|
+
self.notin('both', nodes[0].tags)
|
|
791
|
+
|
|
792
|
+
nodes = await core.nodes('[test:int=789 +(nope)> { test:str=burrito } ]')
|
|
793
|
+
self.len(1, nodes)
|
|
794
|
+
self.isin('both', nodes[0].tags)
|
|
795
|
+
|
|
796
|
+
await core.nodes('trigger.add edge:add --verb not* --form test:int --n2form test:str --query { [ +#cache.destroy ] }')
|
|
797
|
+
|
|
798
|
+
nodes = await core.nodes('[test:int=135 +(note)> { [ test:str=koolaidman ] } ]')
|
|
799
|
+
self.len(1, nodes)
|
|
800
|
+
self.isin('both', nodes[0].tags)
|
|
801
|
+
self.isin('cache.destroy', nodes[0].tags)
|
|
802
|
+
|
|
803
|
+
await core.nodes('for $trig in $lib.trigger.list() { $lib.trigger.del($trig.iden) }')
|
|
804
|
+
self.len(0, await core.nodes('syn:trigger'))
|
|
805
|
+
|
|
806
|
+
nodes = await core.nodes('[test:int=12345 +(note)> { [ test:str=scrambledeggs ] } ]')
|
|
807
|
+
self.len(1, nodes)
|
|
808
|
+
self.len(0, nodes[0].tags)
|
|
809
|
+
|
|
810
|
+
nodes = await core.nodes('[test:int=9876 +(foo:bar)> { test:str=neato }]')
|
|
811
|
+
self.len(1, nodes)
|
|
812
|
+
self.notin('foo', nodes[0].tags)
|
|
813
|
+
|
|
814
|
+
await core.nodes('trigger.add edge:del --verb foo* --query { [ +#del.none ] | spin | iden $auto.opts.n2iden | [+#del.other] }')
|
|
815
|
+
await core.nodes('trigger.add edge:del --verb see* --form test:int --query { [ +#del.one ] }')
|
|
816
|
+
await core.nodes('trigger.add edge:del --verb r* --n2form test:int --query { [ +#del.two ] }')
|
|
817
|
+
await core.nodes('trigger.add edge:del --verb no** --form test:int --n2form test:str --query { [ +#del.all ] }')
|
|
818
|
+
|
|
819
|
+
async with core.enterMigrationMode():
|
|
820
|
+
nodes = await core.nodes('test:int=123 | [ -(foo:beep:boop)> { test:str=neato } ]')
|
|
821
|
+
self.len(1, nodes)
|
|
822
|
+
self.notin('del.none', nodes[0].tags)
|
|
823
|
+
|
|
824
|
+
nodes = await core.nodes('test:int=123 | [ -(foo:bar:baz)> { test:str=neato } ]')
|
|
825
|
+
self.len(1, nodes)
|
|
826
|
+
self.isin('del.none', nodes[0].tags)
|
|
827
|
+
|
|
828
|
+
nodes = await core.nodes('test:str=neato')
|
|
829
|
+
self.len(1, nodes)
|
|
830
|
+
self.isin('del.other', nodes[0].tags)
|
|
831
|
+
|
|
832
|
+
nodes = await core.nodes('test:int=456 | [ -(see.saw)> {test:str=neato} ]')
|
|
833
|
+
self.len(1, nodes)
|
|
834
|
+
self.isin('del.one', nodes[0].tags)
|
|
835
|
+
|
|
836
|
+
nodes = await core.nodes('test:int=456 | [ -(ready)> {test:int=123}]')
|
|
837
|
+
self.len(1, nodes)
|
|
838
|
+
self.isin('del.two', nodes[0].tags)
|
|
839
|
+
|
|
840
|
+
nodes = await core.nodes('test:int=789 | [ -(nope)> { test:int=123 } ]')
|
|
841
|
+
self.len(1, nodes)
|
|
842
|
+
self.notin('del.all', nodes[0].tags)
|
|
843
|
+
|
|
844
|
+
nodes = await core.nodes('test:int=789 | [ -(nope)> { test:str=burrito } ]')
|
|
845
|
+
self.len(1, nodes)
|
|
846
|
+
self.isin('del.all', nodes[0].tags)
|
|
847
|
+
|
|
848
|
+
await core.nodes('trigger.add edge:del --verb no** --form test:int --n2form test:str --query { [ +#cleanup ] }')
|
|
849
|
+
|
|
850
|
+
nodes = await core.nodes('test:int=12345 | [ -(note)> { test:str=scrambledeggs } ]')
|
|
851
|
+
self.len(1, nodes)
|
|
852
|
+
self.isin('cleanup', nodes[0].tags)
|
|
853
|
+
self.isin('del.all', nodes[0].tags)
|
|
854
|
+
|
|
855
|
+
view = await core.callStorm('return ($lib.view.get().fork().iden)')
|
|
856
|
+
opts = {'view': view}
|
|
857
|
+
await core.nodes('trigger.add edge:del --verb no** --form test:str --query { [ +#coffee ] }', opts=opts)
|
|
858
|
+
await core.nodes('trigger.add edge:del --verb no** --form test:str --n2form test:str --query { [ +#oeis.a000668 ] }', opts=opts)
|
|
859
|
+
|
|
860
|
+
await core.nodes('[test:str=mersenne test:str=prime]')
|
|
861
|
+
await core.nodes('test:str=mersenne [ +(notes)> { test:str=prime } ]', opts=opts)
|
|
862
|
+
await core.nodes('test:str=prime | delnode')
|
|
863
|
+
node = await core.nodes('test:str=mersenne | edges.del *', opts=opts)
|
|
864
|
+
self.len(1, node)
|
|
865
|
+
self.len(1, node[0].tags)
|
|
866
|
+
self.isin('coffee', node[0].tags)
|
|
867
|
+
|
|
868
|
+
await core.nodes('for $trig in $lib.trigger.list() { $lib.trigger.del($trig.iden) }')
|
|
869
|
+
self.len(0, await core.nodes('syn:trigger'))
|
synapse/tests/test_lib_view.py
CHANGED
|
@@ -182,8 +182,7 @@ class ViewTest(s_t_utils.SynTest):
|
|
|
182
182
|
tmpiden = tmplayr['iden']
|
|
183
183
|
await self.asyncraises(s_exc.ReadOnlyLayer, core.view.addLayer(tmpiden))
|
|
184
184
|
await self.asyncraises(s_exc.ReadOnlyLayer, view2.addLayer(tmpiden))
|
|
185
|
-
await self.asyncraises(s_exc.
|
|
186
|
-
await self.asyncraises(s_exc.ReadOnlyLayer, view2.setLayers([tmpiden]))
|
|
185
|
+
await self.asyncraises(s_exc.BadArg, view2.setLayers([tmpiden]))
|
|
187
186
|
|
|
188
187
|
# You can't merge a non-forked view
|
|
189
188
|
await self.asyncraises(s_exc.SynErr, view2.core.view.merge())
|
synapse/tests/test_model_base.py
CHANGED
|
@@ -351,3 +351,29 @@ class BaseTest(s_t_utils.SynTest):
|
|
|
351
351
|
doc = ifdef.get('doc')
|
|
352
352
|
self.nn(doc)
|
|
353
353
|
self.ge(len(doc), 3)
|
|
354
|
+
|
|
355
|
+
async def test_model_doc_deprecated(self):
|
|
356
|
+
|
|
357
|
+
async with self.getTestCore() as core:
|
|
358
|
+
|
|
359
|
+
# Check properties that have "deprecated" in the doc string. Skip "isnow" because it's
|
|
360
|
+
# likely to have "deprecated" in the doc string due to what it does.
|
|
361
|
+
nodes = await core.nodes('syn:prop:doc~="(?i)deprecate"')
|
|
362
|
+
for node in nodes:
|
|
363
|
+
prop = core.model.prop(node.ndef[1])
|
|
364
|
+
if prop.name == 'isnow':
|
|
365
|
+
continue
|
|
366
|
+
|
|
367
|
+
self.true(prop.deprecated, msg=prop)
|
|
368
|
+
|
|
369
|
+
# Check types that have "deprecated" in the doc string.
|
|
370
|
+
nodes = await core.nodes('syn:type:doc="(?i)deprecate"')
|
|
371
|
+
for node in nodes:
|
|
372
|
+
typo = core.model.type(node.ndef[1])
|
|
373
|
+
self.true(typo.deprecated, msg=typo)
|
|
374
|
+
|
|
375
|
+
# Check forms that have "deprecated" in the doc string.
|
|
376
|
+
nodes = await core.nodes('syn:form:doc="(?i)deprecate"')
|
|
377
|
+
for node in nodes:
|
|
378
|
+
form = core.model.form(node.ndef[1])
|
|
379
|
+
self.true(form.deprecated, msg=form)
|
synapse/tests/test_model_inet.py
CHANGED
|
@@ -875,6 +875,18 @@ class InetModelTest(s_t_utils.SynTest):
|
|
|
875
875
|
self.eq(2851995905, norm)
|
|
876
876
|
self.eq(info.get('subs').get('type'), 'linklocal')
|
|
877
877
|
|
|
878
|
+
norm, info = t.norm('100.63.255.255')
|
|
879
|
+
self.eq(info.get('subs').get('type'), 'unicast')
|
|
880
|
+
|
|
881
|
+
norm, info = t.norm('100.64.0.0')
|
|
882
|
+
self.eq(info.get('subs').get('type'), 'shared')
|
|
883
|
+
|
|
884
|
+
norm, info = t.norm('100.127.255.255')
|
|
885
|
+
self.eq(info.get('subs').get('type'), 'shared')
|
|
886
|
+
|
|
887
|
+
norm, info = t.norm('100.128.0.0')
|
|
888
|
+
self.eq(info.get('subs').get('type'), 'unicast')
|
|
889
|
+
|
|
878
890
|
# Don't allow invalid values
|
|
879
891
|
with self.raises(s_exc.BadTypeValu):
|
|
880
892
|
t.norm(0x00000000 - 1)
|
|
@@ -1332,6 +1344,10 @@ class InetModelTest(s_t_utils.SynTest):
|
|
|
1332
1344
|
t = core.model.type(formname)
|
|
1333
1345
|
self.raises(s_exc.BadTypeValu, t.norm, 'http:///wat')
|
|
1334
1346
|
self.raises(s_exc.BadTypeValu, t.norm, 'wat') # No Protocol
|
|
1347
|
+
self.raises(s_exc.BadTypeValu, t.norm, "file://''") # Missing address/url
|
|
1348
|
+
self.raises(s_exc.BadTypeValu, t.norm, "file://#") # Missing address/url
|
|
1349
|
+
self.raises(s_exc.BadTypeValu, t.norm, "file://$") # Missing address/url
|
|
1350
|
+
self.raises(s_exc.BadTypeValu, t.norm, "file://%") # Missing address/url
|
|
1335
1351
|
|
|
1336
1352
|
self.raises(s_exc.BadTypeValu, t.norm, 'www.google\udcfesites.com/hehe.asp')
|
|
1337
1353
|
valu = t.norm('http://www.googlesites.com/hehe\udcfestuff.asp')
|
|
@@ -1440,6 +1456,12 @@ class InetModelTest(s_t_utils.SynTest):
|
|
|
1440
1456
|
nodes = await core.nodes(q)
|
|
1441
1457
|
self.len(0, nodes)
|
|
1442
1458
|
|
|
1459
|
+
nodes = await core.nodes('[ inet:url="https://+:80/woot" ]')
|
|
1460
|
+
self.len(1, nodes)
|
|
1461
|
+
|
|
1462
|
+
self.none(nodes[0].get('ipv4'))
|
|
1463
|
+
self.none(nodes[0].get('fqdn'))
|
|
1464
|
+
|
|
1443
1465
|
async def test_url_file(self):
|
|
1444
1466
|
|
|
1445
1467
|
async with self.getTestCore() as core:
|
synapse/tests/test_model_orgs.py
CHANGED
|
@@ -586,6 +586,34 @@ class OuModelTest(s_t_utils.SynTest):
|
|
|
586
586
|
self.len(1, nodes)
|
|
587
587
|
self.len(1, nodes[0].get('industries'))
|
|
588
588
|
|
|
589
|
+
nodes = await core.nodes('''[ ou:requirement=50b757fafe4a839ec499023ebcffe7c0
|
|
590
|
+
:name="acquire pizza toppings"
|
|
591
|
+
:text="The team must acquire ANSI standard pizza toppings."
|
|
592
|
+
:goal={[ ou:goal=* :name=pizza ]}
|
|
593
|
+
:issuer={[ ps:contact=* :name=visi ]}
|
|
594
|
+
:assignee={ gen.ou.org.hq ledos }
|
|
595
|
+
:optional=(true)
|
|
596
|
+
:priority=highest
|
|
597
|
+
:issued=20120202
|
|
598
|
+
:period=(2023, ?)
|
|
599
|
+
:active=(true)
|
|
600
|
+
:deps=(*, *)
|
|
601
|
+
:deps:min=1
|
|
602
|
+
]''')
|
|
603
|
+
self.len(1, nodes)
|
|
604
|
+
self.eq('acquire pizza toppings', nodes[0].get('name'))
|
|
605
|
+
self.eq('The team must acquire ANSI standard pizza toppings.', nodes[0].get('text'))
|
|
606
|
+
self.eq(1, nodes[0].get('deps:min'))
|
|
607
|
+
self.eq(50, nodes[0].get('priority'))
|
|
608
|
+
self.eq(True, nodes[0].get('optional'))
|
|
609
|
+
self.eq(1328140800000, nodes[0].get('issued'))
|
|
610
|
+
self.eq((1672531200000, 9223372036854775807), nodes[0].get('period'))
|
|
611
|
+
|
|
612
|
+
self.len(2, await core.nodes('ou:requirement=50b757fafe4a839ec499023ebcffe7c0 -> ou:requirement'))
|
|
613
|
+
self.len(1, await core.nodes('ou:requirement=50b757fafe4a839ec499023ebcffe7c0 -> ou:goal +:name=pizza'))
|
|
614
|
+
self.len(1, await core.nodes('ou:requirement=50b757fafe4a839ec499023ebcffe7c0 :issuer -> ps:contact +:name=visi'))
|
|
615
|
+
self.len(1, await core.nodes('ou:requirement=50b757fafe4a839ec499023ebcffe7c0 :assignee -> ps:contact +:orgname=ledos'))
|
|
616
|
+
|
|
589
617
|
async def test_ou_code_prefixes(self):
|
|
590
618
|
guid0 = s_common.guid()
|
|
591
619
|
guid1 = s_common.guid()
|
synapse/tests/test_model_risk.py
CHANGED
|
@@ -280,6 +280,7 @@ class RiskModelTest(s_t_utils.SynTest):
|
|
|
280
280
|
:url=https://vertex.link/alerts/WOOT-20
|
|
281
281
|
:ext:id=WOOT-20
|
|
282
282
|
:engine={[ it:prod:softver=* :name=visiware ]}
|
|
283
|
+
:host=*
|
|
283
284
|
]
|
|
284
285
|
''')
|
|
285
286
|
self.len(1, nodes)
|
|
@@ -289,6 +290,8 @@ class RiskModelTest(s_t_utils.SynTest):
|
|
|
289
290
|
self.eq(2554848000000, nodes[0].get('detected'))
|
|
290
291
|
self.eq('WOOT-20', nodes[0].get('ext:id'))
|
|
291
292
|
self.eq('https://vertex.link/alerts/WOOT-20', nodes[0].get('url'))
|
|
293
|
+
self.nn(nodes[0].get('host'))
|
|
294
|
+
self.len(1, await core.nodes('risk:alert -> it:host'))
|
|
292
295
|
self.len(1, await core.nodes('risk:alert -> risk:vuln'))
|
|
293
296
|
self.len(1, await core.nodes('risk:alert -> risk:attack'))
|
|
294
297
|
self.len(1, await core.nodes('risk:alert :engine -> it:prod:softver'))
|
|
@@ -299,6 +302,8 @@ class RiskModelTest(s_t_utils.SynTest):
|
|
|
299
302
|
:name = "Visi Wants Pizza"
|
|
300
303
|
:desc = "Visi wants a pepperoni and mushroom pizza"
|
|
301
304
|
:type = when.noms.attack
|
|
305
|
+
:url=https://vertex.link/pwned
|
|
306
|
+
:ext:id=PWN-00
|
|
302
307
|
:reporter = *
|
|
303
308
|
:reporter:name = vertex
|
|
304
309
|
:severity = 10
|
|
@@ -324,6 +329,8 @@ class RiskModelTest(s_t_utils.SynTest):
|
|
|
324
329
|
self.eq('Visi wants a pepperoni and mushroom pizza', nodes[0].get('desc'))
|
|
325
330
|
self.eq('when.noms.attack.', nodes[0].get('type'))
|
|
326
331
|
self.eq('vertex', nodes[0].get('reporter:name'))
|
|
332
|
+
self.eq('PWN-00', nodes[0].get('ext:id'))
|
|
333
|
+
self.eq('https://vertex.link/pwned', nodes[0].get('url'))
|
|
327
334
|
self.nn(nodes[0].get('target'))
|
|
328
335
|
self.nn(nodes[0].get('attacker'))
|
|
329
336
|
self.nn(nodes[0].get('campaign'))
|
|
@@ -394,6 +401,72 @@ class RiskModelTest(s_t_utils.SynTest):
|
|
|
394
401
|
self.len(1, nodes[0].get('techniques'))
|
|
395
402
|
self.len(1, await core.nodes('risk:threat:merged:isnow -> risk:threat'))
|
|
396
403
|
|
|
404
|
+
nodes = await core.nodes('''[ risk:leak=*
|
|
405
|
+
:name="WikiLeaks ACME Leak"
|
|
406
|
+
:desc="WikiLeaks leaked ACME stuff."
|
|
407
|
+
:disclosed=20231102
|
|
408
|
+
:owner={ gen.ou.org.hq acme }
|
|
409
|
+
:leaker={ gen.ou.org.hq wikileaks }
|
|
410
|
+
:type=public
|
|
411
|
+
:goal={[ ou:goal=* :name=publicity ]}
|
|
412
|
+
:compromise={[ risk:compromise=* :target={ gen.ou.org.hq acme } ]}
|
|
413
|
+
:public=(true)
|
|
414
|
+
:public:url=https://wikileaks.org/acme
|
|
415
|
+
:reporter={ gen.ou.org vertex }
|
|
416
|
+
:reporter:name=vertex
|
|
417
|
+
]''')
|
|
418
|
+
self.len(1, nodes)
|
|
419
|
+
self.eq('wikileaks acme leak', nodes[0].get('name'))
|
|
420
|
+
self.eq('WikiLeaks leaked ACME stuff.', nodes[0].get('desc'))
|
|
421
|
+
self.eq(1698883200000, nodes[0].get('disclosed'))
|
|
422
|
+
self.eq('public.', nodes[0].get('type'))
|
|
423
|
+
self.eq(1, nodes[0].get('public'))
|
|
424
|
+
self.eq('https://wikileaks.org/acme', nodes[0].get('public:url'))
|
|
425
|
+
self.eq('vertex', nodes[0].get('reporter:name'))
|
|
426
|
+
|
|
427
|
+
self.len(1, await core.nodes('risk:leak -> risk:leak:type:taxonomy'))
|
|
428
|
+
self.len(1, await core.nodes('risk:leak :owner -> ps:contact +:orgname=acme'))
|
|
429
|
+
self.len(1, await core.nodes('risk:leak :leaker -> ps:contact +:orgname=wikileaks'))
|
|
430
|
+
self.len(1, await core.nodes('risk:leak -> ou:goal +:name=publicity'))
|
|
431
|
+
self.len(1, await core.nodes('risk:leak -> risk:compromise :target -> ps:contact +:orgname=acme'))
|
|
432
|
+
self.len(1, await core.nodes('risk:leak :reporter -> ou:org +:name=vertex'))
|
|
433
|
+
|
|
434
|
+
nodes = await core.nodes('''[ risk:extortion=*
|
|
435
|
+
:demanded=20231102
|
|
436
|
+
:name="APT99 Extorted ACME"
|
|
437
|
+
:desc="APT99 extorted ACME for a zillion vertex coins."
|
|
438
|
+
:type=fingain
|
|
439
|
+
:attacker={[ ps:contact=* :name=agent99 ]}
|
|
440
|
+
:target={ gen.ou.org.hq acme }
|
|
441
|
+
:success=(true)
|
|
442
|
+
:enacted=(true)
|
|
443
|
+
:public=(true)
|
|
444
|
+
:public:url=https://apt99.com/acme
|
|
445
|
+
:compromise={[ risk:compromise=* :target={ gen.ou.org.hq acme } ]}
|
|
446
|
+
:demanded:payment:price=99.99
|
|
447
|
+
:demanded:payment:currency=VTC
|
|
448
|
+
:reporter={ gen.ou.org vertex }
|
|
449
|
+
:reporter:name=vertex
|
|
450
|
+
]''')
|
|
451
|
+
|
|
452
|
+
self.len(1, nodes)
|
|
453
|
+
self.eq('apt99 extorted acme', nodes[0].get('name'))
|
|
454
|
+
self.eq('APT99 extorted ACME for a zillion vertex coins.', nodes[0].get('desc'))
|
|
455
|
+
self.eq(1698883200000, nodes[0].get('demanded'))
|
|
456
|
+
self.eq('fingain.', nodes[0].get('type'))
|
|
457
|
+
self.eq(1, nodes[0].get('public'))
|
|
458
|
+
self.eq(1, nodes[0].get('success'))
|
|
459
|
+
self.eq(1, nodes[0].get('enacted'))
|
|
460
|
+
self.eq('https://apt99.com/acme', nodes[0].get('public:url'))
|
|
461
|
+
self.eq('99.99', nodes[0].get('demanded:payment:price'))
|
|
462
|
+
self.eq('vtc', nodes[0].get('demanded:payment:currency'))
|
|
463
|
+
self.eq('vertex', nodes[0].get('reporter:name'))
|
|
464
|
+
|
|
465
|
+
self.len(1, await core.nodes('risk:extortion :target -> ps:contact +:orgname=acme'))
|
|
466
|
+
self.len(1, await core.nodes('risk:extortion :attacker -> ps:contact +:name=agent99'))
|
|
467
|
+
self.len(1, await core.nodes('risk:extortion -> risk:compromise :target -> ps:contact +:orgname=acme'))
|
|
468
|
+
self.len(1, await core.nodes('risk:extortion :reporter -> ou:org +:name=vertex'))
|
|
469
|
+
|
|
397
470
|
async def test_model_risk_mitigation(self):
|
|
398
471
|
async with self.getTestCore() as core:
|
|
399
472
|
nodes = await core.nodes('''[
|
|
@@ -3,6 +3,8 @@ import synapse.common as s_common
|
|
|
3
3
|
import synapse.tests.files as s_t_files
|
|
4
4
|
import synapse.tests.utils as s_t_utils
|
|
5
5
|
|
|
6
|
+
import synapse.lib.autodoc as s_l_autodoc
|
|
7
|
+
|
|
6
8
|
import synapse.tools.autodoc as s_autodoc
|
|
7
9
|
|
|
8
10
|
class TestAutoDoc(s_t_utils.SynTest):
|
|
@@ -126,6 +128,7 @@ class TestAutoDoc(s_t_utils.SynTest):
|
|
|
126
128
|
self.isin('StormvarServiceCell Storm Service', s)
|
|
127
129
|
self.isin('This documentation is generated for version 0.0.1 of the service.', s)
|
|
128
130
|
self.isin('Storm Package\\: stormvar', s)
|
|
131
|
+
|
|
129
132
|
self.isin('.. _stormcmd-stormvar-magic:\n', s)
|
|
130
133
|
self.isin('magic\n-----', s)
|
|
131
134
|
self.isin('Test stormvar support', s)
|
|
@@ -136,6 +139,10 @@ class TestAutoDoc(s_t_utils.SynTest):
|
|
|
136
139
|
self.isin('nodedata with the following keys', s)
|
|
137
140
|
self.isin('``foo`` on ``inet:ipv4``', s)
|
|
138
141
|
|
|
142
|
+
self.isin('.. _stormmod-stormvar-apimod', s)
|
|
143
|
+
self.isin('status()', s)
|
|
144
|
+
self.notin('testmod', s)
|
|
145
|
+
|
|
139
146
|
async def test_tools_autodoc_stormpkg(self):
|
|
140
147
|
|
|
141
148
|
with self.getTestDir() as path:
|
|
@@ -153,6 +160,7 @@ class TestAutoDoc(s_t_utils.SynTest):
|
|
|
153
160
|
|
|
154
161
|
self.isin('Storm Package\\: testpkg', s)
|
|
155
162
|
self.isin('This documentation is generated for version 0.0.1 of the package.', s)
|
|
163
|
+
|
|
156
164
|
self.isin('This package implements the following Storm Commands.', s)
|
|
157
165
|
self.isin('.. _stormcmd-testpkg-testpkgcmd', s)
|
|
158
166
|
|
|
@@ -171,6 +179,23 @@ class TestAutoDoc(s_t_utils.SynTest):
|
|
|
171
179
|
self.isin('testpkg.baz', s)
|
|
172
180
|
self.isin("Help on baz opt (default: ('-7days', 'now'))", s)
|
|
173
181
|
|
|
182
|
+
self.isin('This package implements the following Storm Modules.', s)
|
|
183
|
+
self.isin('.. _stormmod-testpkg-apimod', s)
|
|
184
|
+
|
|
185
|
+
self.notin('testmod', s)
|
|
186
|
+
|
|
187
|
+
self.isin('search(text, mintime=-30days)', s)
|
|
188
|
+
self.isin('text (str): The text.', s)
|
|
189
|
+
self.isin('Yields:', s)
|
|
190
|
+
self.isin('The return type is ``node``.', s)
|
|
191
|
+
|
|
192
|
+
self.isin('status()', s)
|
|
193
|
+
|
|
194
|
+
# coverage for no apidefs
|
|
195
|
+
rst = s_l_autodoc.RstHelp()
|
|
196
|
+
await s_autodoc.processStormModules(rst, 'foo', [])
|
|
197
|
+
self.eq('\nStorm Modules\n=============\n\nThis package does not export any Storm APIs.\n', rst.getRstText())
|
|
198
|
+
|
|
174
199
|
async def test_tools_autodoc_stormtypes(self):
|
|
175
200
|
with self.getTestDir() as path:
|
|
176
201
|
|
|
@@ -69,6 +69,10 @@ class GenPkgTest(s_test.SynTest):
|
|
|
69
69
|
ymlpath = s_common.genpath(dirname, 'files', 'stormpkg', 'badjsonpkg.yaml')
|
|
70
70
|
await s_genpkg.main((ymlpath,))
|
|
71
71
|
|
|
72
|
+
with self.raises(s_exc.SchemaViolation):
|
|
73
|
+
ymlpath = s_common.genpath(dirname, 'files', 'stormpkg', 'badapidef.yaml')
|
|
74
|
+
await s_genpkg.main((ymlpath,))
|
|
75
|
+
|
|
72
76
|
ymlpath = s_common.genpath(dirname, 'files', 'stormpkg', 'testpkg.yaml')
|
|
73
77
|
async with self.getTestCore() as core:
|
|
74
78
|
|
|
@@ -98,10 +102,12 @@ class GenPkgTest(s_test.SynTest):
|
|
|
98
102
|
self.eq(pdef['version'], '0.0.1')
|
|
99
103
|
self.eq(pdef['modules'][0]['name'], 'testmod')
|
|
100
104
|
self.eq(pdef['modules'][0]['storm'], 'inet:ipv4\n')
|
|
101
|
-
self.eq(pdef['modules'][1]['name'], '
|
|
102
|
-
self.
|
|
103
|
-
self.eq(pdef['modules'][2]['name'], 'testpkg.
|
|
105
|
+
self.eq(pdef['modules'][1]['name'], 'apimod')
|
|
106
|
+
self.isin('function search', pdef['modules'][1]['storm'])
|
|
107
|
+
self.eq(pdef['modules'][2]['name'], 'testpkg.testext')
|
|
104
108
|
self.eq(pdef['modules'][2]['storm'], 'inet:fqdn\n')
|
|
109
|
+
self.eq(pdef['modules'][3]['name'], 'testpkg.testextfile')
|
|
110
|
+
self.eq(pdef['modules'][3]['storm'], 'inet:fqdn\n')
|
|
105
111
|
self.eq(pdef['commands'][0]['name'], 'testpkgcmd')
|
|
106
112
|
self.eq(pdef['commands'][0]['storm'], 'inet:ipv6\n')
|
|
107
113
|
|