synapse 2.153.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.

Files changed (69) hide show
  1. synapse/cortex.py +4 -4
  2. synapse/lib/ast.py +10 -5
  3. synapse/lib/autodoc.py +2 -2
  4. synapse/lib/cache.py +16 -1
  5. synapse/lib/layer.py +2 -1
  6. synapse/lib/modelrev.py +36 -3
  7. synapse/lib/node.py +2 -5
  8. synapse/lib/snap.py +10 -0
  9. synapse/lib/storm.py +80 -0
  10. synapse/lib/stormhttp.py +3 -0
  11. synapse/lib/stormlib/backup.py +1 -0
  12. synapse/lib/stormlib/basex.py +2 -0
  13. synapse/lib/stormlib/cell.py +7 -0
  14. synapse/lib/stormlib/compression.py +3 -0
  15. synapse/lib/stormlib/ethereum.py +1 -0
  16. synapse/lib/stormlib/graph.py +2 -0
  17. synapse/lib/stormlib/hashes.py +5 -0
  18. synapse/lib/stormlib/hex.py +6 -0
  19. synapse/lib/stormlib/infosec.py +4 -0
  20. synapse/lib/stormlib/ipv6.py +1 -0
  21. synapse/lib/stormlib/iters.py +2 -0
  22. synapse/lib/stormlib/json.py +5 -0
  23. synapse/lib/stormlib/mime.py +1 -0
  24. synapse/lib/stormlib/model.py +19 -3
  25. synapse/lib/stormlib/modelext.py +1 -0
  26. synapse/lib/stormlib/notifications.py +2 -0
  27. synapse/lib/stormlib/pack.py +2 -0
  28. synapse/lib/stormlib/random.py +1 -0
  29. synapse/lib/stormlib/smtp.py +0 -7
  30. synapse/lib/stormlib/stats.py +4 -0
  31. synapse/lib/stormlib/stix.py +8 -0
  32. synapse/lib/stormlib/storm.py +1 -0
  33. synapse/lib/stormlib/version.py +3 -0
  34. synapse/lib/stormlib/xml.py +3 -0
  35. synapse/lib/stormlib/yaml.py +2 -0
  36. synapse/lib/stormtypes.py +201 -29
  37. synapse/lib/trigger.py +180 -4
  38. synapse/lib/types.py +1 -1
  39. synapse/lib/version.py +2 -2
  40. synapse/lib/view.py +55 -6
  41. synapse/models/inet.py +16 -4
  42. synapse/models/orgs.py +47 -2
  43. synapse/models/risk.py +126 -2
  44. synapse/models/syn.py +6 -0
  45. synapse/tests/files/stormpkg/badapidef.yaml +13 -0
  46. synapse/tests/files/stormpkg/storm/modules/apimod +10 -0
  47. synapse/tests/files/stormpkg/testpkg.yaml +23 -0
  48. synapse/tests/test_cortex.py +50 -34
  49. synapse/tests/test_lib_ast.py +7 -0
  50. synapse/tests/test_lib_autodoc.py +1 -1
  51. synapse/tests/test_lib_modelrev.py +9 -0
  52. synapse/tests/test_lib_node.py +55 -0
  53. synapse/tests/test_lib_storm.py +13 -0
  54. synapse/tests/test_lib_stormlib_storm.py +8 -0
  55. synapse/tests/test_lib_stormsvc.py +24 -1
  56. synapse/tests/test_lib_stormtypes.py +105 -1
  57. synapse/tests/test_lib_trigger.py +315 -0
  58. synapse/tests/test_lib_view.py +1 -2
  59. synapse/tests/test_model_inet.py +22 -0
  60. synapse/tests/test_model_orgs.py +28 -0
  61. synapse/tests/test_model_risk.py +73 -0
  62. synapse/tests/test_tools_autodoc.py +25 -0
  63. synapse/tests/test_tools_genpkg.py +9 -3
  64. synapse/tools/autodoc.py +42 -2
  65. {synapse-2.153.0.dist-info → synapse-2.154.0.dist-info}/METADATA +1 -1
  66. {synapse-2.153.0.dist-info → synapse-2.154.0.dist-info}/RECORD +69 -67
  67. {synapse-2.153.0.dist-info → synapse-2.154.0.dist-info}/WHEEL +1 -1
  68. {synapse-2.153.0.dist-info → synapse-2.154.0.dist-info}/LICENSE +0 -0
  69. {synapse-2.153.0.dist-info → synapse-2.154.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,13 @@
1
+ name: testpkg
2
+ version: 0.0.1
3
+
4
+ modules:
5
+ - name: apimod
6
+ apidefs:
7
+ - name: status
8
+ desc: Get the status of the foo.
9
+ type:
10
+ type: function
11
+ returns:
12
+ type: 42
13
+ desc: A status dictionary.
@@ -0,0 +1,10 @@
1
+
2
+ function search(text, mintime="-30days") {
3
+ for $t in $text {
4
+ [ it:devstr=$t ]
5
+ }
6
+ }
7
+
8
+ function status() {
9
+ return(({"ok": true}))
10
+ }
@@ -7,6 +7,29 @@ logo:
7
7
 
8
8
  modules:
9
9
  - name: testmod
10
+ - name: apimod
11
+ apidefs:
12
+ - name: search
13
+ desc: |
14
+ Execute a search
15
+
16
+ This API will foo the bar.
17
+ type:
18
+ type: function
19
+ args:
20
+ - { name: text, type: str, desc: "The text." }
21
+ - { name: mintime, type: [str, int], desc: "The mintime.", default: "-30days" }
22
+ returns:
23
+ name: yields
24
+ type: node
25
+ desc: Yields it:dev:str nodes.
26
+ - name: status
27
+ desc: Get the status of the foo.
28
+ type:
29
+ type: function
30
+ returns:
31
+ type: dict
32
+ desc: A status dictionary.
10
33
 
11
34
  external_modules:
12
35
  - name: testext
@@ -6117,55 +6117,71 @@ class CortexBasicTest(s_t_utils.SynTest):
6117
6117
  self.true(layr.lockmemory)
6118
6118
 
6119
6119
  async def test_cortex_storm_lib_dmon(self):
6120
- async with self.getTestCoreAndProxy() as (core, prox):
6121
- nodes = await core.nodes('''
6122
6120
 
6123
- $lib.print(hi)
6121
+ with self.getTestDir() as dirn:
6124
6122
 
6125
- $tx = $lib.queue.add(tx)
6126
- $rx = $lib.queue.add(rx)
6123
+ async with self.getTestCoreAndProxy(dirn=dirn) as (core, prox):
6124
+ nodes = await core.nodes('''
6127
6125
 
6128
- $ddef = $lib.dmon.add(${
6126
+ $lib.print(hi)
6129
6127
 
6130
- $rx = $lib.queue.get(tx)
6131
- $tx = $lib.queue.get(rx)
6128
+ $tx = $lib.queue.add(tx)
6129
+ $rx = $lib.queue.add(rx)
6132
6130
 
6133
- $ipv4 = nope
6134
- for ($offs, $ipv4) in $rx.gets(wait=1) {
6135
- [ inet:ipv4=$ipv4 ]
6136
- $rx.cull($offs)
6137
- $tx.put($ipv4)
6138
- }
6139
- })
6131
+ $ddef = $lib.dmon.add(${
6140
6132
 
6141
- $tx.put(1.2.3.4)
6133
+ $rx = $lib.queue.get(tx)
6134
+ $tx = $lib.queue.get(rx)
6142
6135
 
6143
- for ($xoff, $xpv4) in $rx.gets(size=1, wait=1) { }
6136
+ $ipv4 = nope
6137
+ for ($offs, $ipv4) in $rx.gets(wait=1) {
6138
+ [ inet:ipv4=$ipv4 ]
6139
+ $rx.cull($offs)
6140
+ $tx.put($ipv4)
6141
+ }
6142
+ })
6144
6143
 
6145
- $lib.print(xed)
6144
+ $tx.put(1.2.3.4)
6146
6145
 
6147
- inet:ipv4=$xpv4
6146
+ for ($xoff, $xpv4) in $rx.gets(size=1, wait=1) { }
6148
6147
 
6149
- $lib.dmon.del($ddef.iden)
6148
+ $lib.print(xed)
6150
6149
 
6151
- $lib.queue.del(tx)
6152
- $lib.queue.del(rx)
6153
- ''')
6154
- self.len(1, nodes)
6155
- self.len(0, await prox.getStormDmons())
6150
+ inet:ipv4=$xpv4
6156
6151
 
6157
- with self.raises(s_exc.NoSuchIden):
6158
- await core.nodes('$lib.dmon.del(newp)')
6152
+ $lib.dmon.del($ddef.iden)
6159
6153
 
6160
- await core.stormlist('auth.user.add user')
6161
- user = await core.auth.getUserByName('user')
6162
- asuser = {'user': user.iden}
6154
+ $lib.queue.del(tx)
6155
+ $lib.queue.del(rx)
6156
+ ''')
6157
+ self.len(1, nodes)
6158
+ self.len(0, await prox.getStormDmons())
6163
6159
 
6164
- ddef = await core.callStorm('return($lib.dmon.add(${$lib.print(foo)}))')
6165
- iden = ddef.get('iden')
6160
+ with self.raises(s_exc.NoSuchIden):
6161
+ await core.nodes('$lib.dmon.del(newp)')
6166
6162
 
6167
- with self.raises(s_exc.AuthDeny):
6168
- await core.callStorm(f'$lib.dmon.del({iden})', opts=asuser)
6163
+ await core.stormlist('auth.user.add user')
6164
+ user = await core.auth.getUserByName('user')
6165
+ asuser = {'user': user.iden}
6166
+
6167
+ ddef = await core.callStorm('return($lib.dmon.add(${$lib.print(foo)}))')
6168
+ iden = ddef.get('iden')
6169
+ asuser['vars'] = {'iden': iden}
6170
+
6171
+ with self.raises(s_exc.AuthDeny):
6172
+ await core.callStorm(f'$lib.dmon.del($iden)', opts=asuser)
6173
+
6174
+ # remove the dmon without a nexus entry to verify recover works
6175
+ await core._delStormDmon(iden)
6176
+ self.none(await core.callStorm('return($lib.dmon.get($iden))', opts=asuser))
6177
+ self.eq('storm:dmon:add', (await core.nexsroot.nexslog.last())[1][1])
6178
+
6179
+ async with self.getTestCoreAndProxy(dirn=dirn) as (core, prox):
6180
+
6181
+ # nexus recover() previously failed on adding to the hive
6182
+ # although the dmon would get successfully started
6183
+ self.nn(await core.callStorm('return($lib.dmon.get($iden))', opts=asuser))
6184
+ self.nn(core.stormdmonhive.get(iden))
6169
6185
 
6170
6186
  async def test_cortex_storm_dmon_view(self):
6171
6187
 
@@ -2057,6 +2057,13 @@ class AstTest(s_test.SynTest):
2057
2057
  else { $lib.print(no) }
2058
2058
  '''
2059
2059
  msgs = await core.stormlist(q)
2060
+ self.stormIsInPrint('yes', msgs)
2061
+
2062
+ q = '''test:str $data=$node.value()
2063
+ if ($data ~= "(?-i:brown)") { $lib.print(yes) }
2064
+ else { $lib.print(no) }
2065
+ '''
2066
+ msgs = await core.stormlist(q)
2060
2067
  self.stormIsInPrint('no', msgs)
2061
2068
 
2062
2069
  q = '''test:str $data=$node.value()
@@ -62,7 +62,7 @@ shave'''
62
62
  self.eq(s_autodoc.getArgLines({}), [])
63
63
  lines = s_autodoc.getArgLines(rtype)
64
64
  self.eq(lines, ['\n', 'Args:', ' foo (str): The foos!', '\n',
65
- ' bar: The bar. The input type may one one of the following: ``str``, ``int``.',
65
+ ' bar: The bar. The input type may be one of the following: ``str``, ``int``.',
66
66
  '\n', ' \\*\\*kwargs (any): Extra foobars.', '\n'])
67
67
 
68
68
  lines = s_autodoc.getArgLines({'args': [{'name': 'cmplx', 'type': {}, 'desc': 'unsupported'}]})
@@ -419,3 +419,12 @@ class ModelRevTest(s_tests.SynTest):
419
419
 
420
420
  self.len(1, await core.nodes('risk:vulnname="woot woot"'))
421
421
  self.len(1, await core.nodes('risk:vuln:name="woot woot"'))
422
+
423
+ async def test_modelrev_0_2_22(self):
424
+
425
+ async with self.getRegrCore('model-0.2.22') as core:
426
+ nodes = await core.nodes('inet:ipv4=100.64.0.0/10')
427
+ self.len(257, nodes)
428
+
429
+ for node in nodes:
430
+ self.eq(node.props.get('type'), 'shared')
@@ -517,3 +517,58 @@ class NodeTest(s_t_utils.SynTest):
517
517
 
518
518
  msgs = await core.stormlist(edgeq)
519
519
  self.len(1, [m for m in msgs if m[0] == 'print'])
520
+
521
+ async def test_node_remove_missing_basetag(self):
522
+
523
+ async with self.getTestCore() as core:
524
+
525
+ base = await core.callStorm('return($lib.view.get().iden)')
526
+ fork = await core.callStorm('return($lib.view.get().fork().iden)')
527
+
528
+ await core.nodes('[test:str=neato +#foo.one]', opts={'view': base})
529
+ await core.nodes('test:str=neato | [ +#foo.two ]', opts={'view': fork})
530
+
531
+ await core.nodes('test:str=neato | [ -#foo ]', opts={'view': base})
532
+
533
+ othr = await core.nodes('test:str=neato', opts={'view': fork})
534
+ self.len(1, othr)
535
+ self.isin('foo.two', othr[0].tags)
536
+ self.notin('foo', othr[0].tags)
537
+
538
+ msgs = await core.stormlist('test:str=neato | [ -#foo ]', opts={'view': fork})
539
+ edits = [m[1] for m in msgs if m[0] == 'node:edits']
540
+ nodes = [m[1] for m in msgs if m[0] == 'node']
541
+ self.len(1, edits)
542
+ self.len(1, edits[0]['edits'][0][2])
543
+
544
+ self.len(1, nodes)
545
+ self.len(0, nodes[0][1]['tags'])
546
+
547
+ await core.nodes('[test:int=12 +#ping.pong.neato.burrito]', opts={'view': base})
548
+ await core.nodes('test:int=12 | [ +#ping.pong.awesome.possum ]', opts={'view': fork})
549
+
550
+ await core.nodes('test:int=12 | [ -#ping.pong]', opts={'view': base})
551
+
552
+ othr = await core.nodes('test:int=12', opts={'view': fork})
553
+ self.len(1, othr)
554
+ self.isin('ping', othr[0].tags)
555
+ self.isin('ping.pong.awesome', othr[0].tags)
556
+ self.isin('ping.pong.awesome.possum', othr[0].tags)
557
+
558
+ self.notin('ping.pong', othr[0].tags)
559
+
560
+ msgs = await core.stormlist('test:int=12 | [ -#ping.pong ]', opts={'view': fork})
561
+ edits = [m[1] for m in msgs if m[0] == 'node:edits']
562
+ nodes = [m[1] for m in msgs if m[0] == 'node']
563
+
564
+ self.len(1, edits)
565
+ self.len(2, edits[0]['edits'][0][2])
566
+
567
+ self.len(1, nodes)
568
+ self.len(1, nodes[0][1]['tags'])
569
+ self.isin('ping', nodes[0][1]['tags'])
570
+
571
+ nodes = await core.nodes('test:int=12 | [ -#p ]')
572
+ self.len(1, nodes)
573
+ self.len(1, nodes[0].tags)
574
+ self.isin('ping', nodes[0].tags)
@@ -1213,6 +1213,19 @@ class StormTest(s_t_utils.SynTest):
1213
1213
  self.len(1, nodes)
1214
1214
  self.eq(nodes[0].ndef, ('test:str', 'pluto\udcbaneptune'))
1215
1215
 
1216
+ nodes = await core.nodes('[ media:news=* :publisher:name=woot ] $name=:publisher:name [ :publisher={ gen.ou.org $name } ]')
1217
+ self.len(1, nodes)
1218
+ self.nn(nodes[0].get('publisher'))
1219
+
1220
+ # test regular expressions are case insensitive by default
1221
+ self.len(1, await core.nodes('test:str~=Pluto'))
1222
+ self.len(1, await core.nodes('test:str +test:str~=Pluto'))
1223
+ self.true(await core.callStorm('return(("Foo" ~= "foo"))'))
1224
+ self.len(0, await core.nodes('test:str~="(?-i:Pluto)"'))
1225
+ self.len(0, await core.nodes('test:str +test:str~="(?-i:Pluto)"'))
1226
+ self.false(await core.callStorm('return(("Foo" ~= "(?-i:foo)"))'))
1227
+ self.true(await core.callStorm('return(("Foo" ~= "(?-i:Foo)"))'))
1228
+
1216
1229
  async def test_storm_diff_merge(self):
1217
1230
 
1218
1231
  async with self.getTestCore() as core:
@@ -28,3 +28,11 @@ class LibStormTest(s_test.SynTest):
28
28
 
29
29
  # for coverage of forked call...
30
30
  self.nn(s_parser.parseEval('woot'))
31
+
32
+ # Readonly functionality is sane
33
+ msgs = await core.stormlist('$lib.print($lib.storm.eval( "{$lib.print(wow)}" ))')
34
+ self.stormIsInPrint('wow', msgs)
35
+ self.stormIsInPrint('$lib.null', msgs)
36
+
37
+ with self.raises(s_exc.IsReadOnly):
38
+ await core.callStorm('$lib.storm.eval( "{$lib.auth.users.add(readonly)}" )', opts={'readonly': True})
@@ -317,7 +317,30 @@ class StormvarService(s_cell.CellApi, s_stormsvc.StormSvc):
317
317
  $lib.print('my foo var is {f}', f=$fooz)
318
318
  ''',
319
319
  },
320
- )
320
+ ),
321
+ 'modules': (
322
+ {
323
+ 'name': 'testmod',
324
+ 'storm': '',
325
+ },
326
+ {
327
+ 'name': 'apimod',
328
+ 'storm': 'function status() { return($lib.true) }',
329
+ 'apidefs': (
330
+ {
331
+ 'name': 'status',
332
+ 'desc': 'Status of the foo.',
333
+ 'type': {
334
+ 'type': 'function',
335
+ 'returns': {
336
+ 'type': 'boolean',
337
+ 'desc': 'Where foo is ok',
338
+ },
339
+ },
340
+ },
341
+ ),
342
+ },
343
+ ),
321
344
  },
322
345
  )
323
346
 
@@ -613,6 +613,14 @@ class StormTypesTest(s_test.SynTest):
613
613
  'name': 'test',
614
614
  'storm': '$valu=$modconf.valu function getvalu() { return($valu) }',
615
615
  'modconf': {'valu': 'foo'},
616
+ },
617
+ {
618
+ 'name': 'test.danger',
619
+ 'storm': '''
620
+ init { $src=$lib.null }
621
+ function genSrc() { if $src { return ($src) } [meta:source=(s1,)] $src=$node return ($node) }
622
+ $genSrc()
623
+ '''
616
624
  }
617
625
  ],
618
626
  'commands': [
@@ -735,9 +743,13 @@ class StormTypesTest(s_test.SynTest):
735
743
 
736
744
  await core.callStorm('$test = $lib.import(test) $test.modconf.valu=bar')
737
745
  self.eq('foo', await core.callStorm('return($lib.import(test).getvalu())'))
746
+ self.eq('foo', await core.callStorm('return($lib.import(test).getvalu())', opts={'readonly': True}))
747
+
748
+ with self.raises(s_exc.IsReadOnly):
749
+ await core.callStorm('return($lib.import(test.danger).src)', opts={'readonly': True})
738
750
 
739
751
  mods = await core.getStormMods()
740
- self.len(1, mods)
752
+ self.len(2, mods)
741
753
  mods['test']['modconf']['valu'] = 'bar'
742
754
  mods = await core.getStormMods()
743
755
  self.eq('foo', mods['test']['modconf']['valu'])
@@ -1181,6 +1193,24 @@ class StormTypesTest(s_test.SynTest):
1181
1193
  '''
1182
1194
  self.eq(2, await core.callStorm(q))
1183
1195
 
1196
+ q = '''
1197
+ $q=${ return( $lib.auth.users.byname(root).name ) }
1198
+ return ( $q.exec() )
1199
+ '''
1200
+ self.eq('root', await core.callStorm(q, opts={'readonly': True}))
1201
+
1202
+ q = '''
1203
+ $q=${ test:int=1 }
1204
+ return ( $q.size() )
1205
+ '''
1206
+ self.eq(1, await core.callStorm(q, opts={'readonly': True}))
1207
+
1208
+ with self.raises(s_exc.IsReadOnly):
1209
+ await core.callStorm('$foo=${ [test:str=readonly] } return ( $foo.exec() )', opts={'readonly': True})
1210
+
1211
+ with self.raises(s_exc.IsReadOnly):
1212
+ await core.callStorm('$foo=${ [test:str=readonly] } return( $foo.size() )', opts={'readonly': True})
1213
+
1184
1214
  async def test_storm_lib_node(self):
1185
1215
  async with self.getTestCore() as core:
1186
1216
  nodes = await core.nodes('[ test:str=woot :tick=2001] [ test:int=$node.isform(test:str) ] +test:int')
@@ -1514,6 +1544,72 @@ class StormTypesTest(s_test.SynTest):
1514
1544
  q = '$foo=$lib.text(foo) $bar=$lib.text(bar) $v=($foo, aString, $bar,) $v.sort() return ($v)'
1515
1545
  await core.callStorm(q)
1516
1546
 
1547
+ q = '$l = (1, 2, (3), 4, 1, (3), 3, asdf) return ( $l.unique() )'
1548
+ self.eq(['1', '2', 3, '4', '3', 'asdf'], await core.callStorm(q))
1549
+
1550
+ q = '$a=$lib.text(hehe) $b=$lib.text(haha) $c=$lib.text(hehe) $foo=($a, $b, $c) return ($foo.unique())'
1551
+ self.eq(['hehe', 'haha'], await core.callStorm(q))
1552
+
1553
+ await core.addUser('lowuser1')
1554
+ await core.addUser('lowuser2')
1555
+ q = '''
1556
+ $a=$lib.auth.users.byname(lowuser1) $b=$lib.auth.users.byname(lowuser2) $r=$lib.auth.users.byname(root)
1557
+ $l = ($a, $r, $b, $b, $a ) $l2 = $l.unique() $l3 = ()
1558
+ for $user in $l2 {
1559
+ $l3.append($user.name)
1560
+ }
1561
+ return ( $l3 )'''
1562
+ self.eq(['lowuser1', 'root', 'lowuser2'], await core.callStorm(q))
1563
+
1564
+ q = '$l=(1, 2, 3, 3, $lib.queue) return ( $lib.len($l.unique()) )'
1565
+ self.eq(4, await core.callStorm(q))
1566
+
1567
+ # funcs are different class instances here
1568
+ q = '$l = (1, 2, 2, $lib.inet.http.get, $lib.inet.http.get) return ($lib.len($l.unique()))'
1569
+ self.eq(4, await core.callStorm(q))
1570
+
1571
+ # funcs are the same class instance
1572
+ q = '$hehe = $lib.inet.http $l = (1, 2, 2, $hehe.get, $hehe.get) return ($lib.len($l.unique()))'
1573
+ self.eq(3, await core.callStorm(q))
1574
+
1575
+ q = '''
1576
+ function foo() {}
1577
+ function bar() {}
1578
+ $l = ($foo, $bar)
1579
+ return ($lib.len($l.unique()))
1580
+ '''
1581
+ self.eq(2, await core.callStorm(q))
1582
+
1583
+ q = '''
1584
+ function foo() {}
1585
+ function bar() {}
1586
+ $l = ($bar, $foo, $bar)
1587
+ return ($lib.len($l.unique()))
1588
+ '''
1589
+ self.eq(2, await core.callStorm(q))
1590
+
1591
+ q = '''
1592
+ $q1 = $lib.queue.gen(hehe)
1593
+ $q2 = $lib.queue.get(hehe)
1594
+ $l = ($q1, $q2)
1595
+ return ( $lib.len($l.unique()) ) '''
1596
+ self.eq(1, await core.callStorm(q))
1597
+
1598
+ q = '''
1599
+ $q1 = $lib.queue.gen(hehe)
1600
+ $q2 = $lib.queue.get(hehe)
1601
+ $l = ($q1, $q2, $q2)
1602
+ return ( $lib.len($l.unique()) ) '''
1603
+ self.eq(1, await core.callStorm(q))
1604
+
1605
+ q = '''
1606
+ $q1 = $lib.queue.gen(hehe)
1607
+ $q2 = $lib.queue.get(hehe)
1608
+ $q3 = $lib.queue.get(hehe)
1609
+ $l = ($q1, $q2, $q3)
1610
+ return ( $lib.len($l.unique()) ) '''
1611
+ self.eq(1, await core.callStorm(q))
1612
+
1517
1613
  # Python Tuples can be treated like a List object for accessing via data inside of.
1518
1614
  q = '[ test:comp=(10,lol) ] $x=$node.ndef().index(1).index(1) [ test:str=$x ]'
1519
1615
  nodes = await core.nodes(q)
@@ -3465,10 +3561,18 @@ class StormTypesTest(s_test.SynTest):
3465
3561
  layrs0 = (layr0.iden, ldef1.get('iden'))
3466
3562
  layrs1 = (ldef1.get('iden'), layr0.iden)
3467
3563
 
3564
+ # fork the default view to test editing the root view layers
3565
+ fork00 = await core.callStorm('return($lib.view.get().fork().iden)')
3566
+ self.eq(2, await core.callStorm('return($lib.view.get().layers.size())', opts={'view': fork00}))
3567
+
3468
3568
  await core.callStorm('$lib.view.get().set(layers, $layers)', opts={'vars': {'layers': layrs0}})
3469
3569
  ldefs = await core.callStorm('return($lib.view.get().get(layers))')
3470
3570
  self.eq(layrs0, [x.get('iden') for x in ldefs])
3471
3571
 
3572
+ layers = await core.callStorm('return($lib.view.get().layers)', opts={'view': fork00})
3573
+ self.eq(layrs0, [layr['iden'] for layr in layers][-2:])
3574
+ self.len(3, layers)
3575
+
3472
3576
  await core.callStorm('$lib.view.get().set(layers, $layers)', opts={'vars': {'layers': layrs1}})
3473
3577
  ldefs = await core.callStorm('return($lib.view.get().get(layers))')
3474
3578
  self.eq(layrs1, [x.get('iden') for x in ldefs])