synapse 2.169.0__py311-none-any.whl → 2.171.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/cortex.py +99 -3
- synapse/datamodel.py +5 -0
- synapse/lib/ast.py +70 -12
- synapse/lib/cell.py +76 -7
- synapse/lib/layer.py +75 -6
- synapse/lib/lmdbslab.py +17 -0
- synapse/lib/node.py +7 -0
- synapse/lib/snap.py +22 -4
- synapse/lib/storm.py +1 -1
- synapse/lib/stormlib/cortex.py +1 -1
- synapse/lib/stormlib/model.py +339 -40
- synapse/lib/stormtypes.py +58 -1
- synapse/lib/types.py +36 -1
- synapse/lib/version.py +2 -2
- synapse/lib/view.py +94 -15
- synapse/models/files.py +40 -0
- synapse/models/inet.py +8 -4
- synapse/models/infotech.py +355 -17
- synapse/tests/files/cpedata.json +525034 -0
- synapse/tests/test_cortex.py +108 -0
- synapse/tests/test_lib_ast.py +66 -0
- synapse/tests/test_lib_cell.py +112 -0
- synapse/tests/test_lib_layer.py +52 -1
- synapse/tests/test_lib_lmdbslab.py +36 -0
- synapse/tests/test_lib_scrape.py +72 -71
- synapse/tests/test_lib_snap.py +16 -1
- synapse/tests/test_lib_storm.py +118 -0
- synapse/tests/test_lib_stormlib_cortex.py +15 -0
- synapse/tests/test_lib_stormlib_model.py +427 -0
- synapse/tests/test_lib_stormtypes.py +147 -15
- synapse/tests/test_lib_types.py +21 -0
- synapse/tests/test_lib_view.py +77 -0
- synapse/tests/test_model_files.py +52 -0
- synapse/tests/test_model_inet.py +63 -1
- synapse/tests/test_model_infotech.py +187 -26
- synapse/tests/utils.py +42 -9
- {synapse-2.169.0.dist-info → synapse-2.171.0.dist-info}/METADATA +1 -1
- {synapse-2.169.0.dist-info → synapse-2.171.0.dist-info}/RECORD +41 -40
- {synapse-2.169.0.dist-info → synapse-2.171.0.dist-info}/LICENSE +0 -0
- {synapse-2.169.0.dist-info → synapse-2.171.0.dist-info}/WHEEL +0 -0
- {synapse-2.169.0.dist-info → synapse-2.171.0.dist-info}/top_level.txt +0 -0
synapse/tests/test_cortex.py
CHANGED
|
@@ -2430,6 +2430,41 @@ class CortexTest(s_t_utils.SynTest):
|
|
|
2430
2430
|
msgs = await core.stormlist('test:int :loc -> test:newp')
|
|
2431
2431
|
self.stormIsInErr('No property named test:newp', msgs)
|
|
2432
2432
|
|
|
2433
|
+
# ndef pivots
|
|
2434
|
+
await core.nodes('''
|
|
2435
|
+
[
|
|
2436
|
+
( test:str=ndefpivdst )
|
|
2437
|
+
( test:str=ndefpivsrc :bar=(test:str, ndefpivdst) )
|
|
2438
|
+
( test:str=ndefpivprp :bar=(test:str, ndefpivdst) )
|
|
2439
|
+
]
|
|
2440
|
+
''')
|
|
2441
|
+
|
|
2442
|
+
nodes = await core.nodes('test:str=ndefpivsrc -> test:str')
|
|
2443
|
+
self.eq(['ndefpivdst'], [n.ndef[1] for n in nodes])
|
|
2444
|
+
|
|
2445
|
+
nodes = await core.nodes('test:str=ndefpivsrc -> test:str:bar')
|
|
2446
|
+
self.len(0, nodes)
|
|
2447
|
+
|
|
2448
|
+
nodes = await core.nodes('test:str=ndefpivdst -> test:str:bar')
|
|
2449
|
+
self.sorteq(['ndefpivprp', 'ndefpivsrc'], [n.ndef[1] for n in nodes])
|
|
2450
|
+
|
|
2451
|
+
nodes = await core.nodes('test:str=ndefpivsrc :bar -> * +test:str')
|
|
2452
|
+
self.eq(['ndefpivdst'], [n.ndef[1] for n in nodes])
|
|
2453
|
+
|
|
2454
|
+
nodes = await core.nodes('test:str=ndefpivsrc :bar -> test:str:bar')
|
|
2455
|
+
self.sorteq(['ndefpivprp', 'ndefpivsrc'], [n.ndef[1] for n in nodes])
|
|
2456
|
+
|
|
2457
|
+
nodes = await core.nodes('test:str=ndefpivsrc :bar -> test:str')
|
|
2458
|
+
self.eq(['ndefpivdst'], [n.ndef[1] for n in nodes])
|
|
2459
|
+
|
|
2460
|
+
nodes = await core.nodes('test:str=ndefpivsrc :bar -> test:int')
|
|
2461
|
+
self.len(0, nodes)
|
|
2462
|
+
|
|
2463
|
+
await core.nodes('test:str=ndefpivdst delnode')
|
|
2464
|
+
msgs = await core.stormlist('test:str=ndefpivsrc :bar -> test:str')
|
|
2465
|
+
self.len(0, [m for m in msgs if m[0] == 'node'])
|
|
2466
|
+
self.stormIsInWarn("Missing node corresponding to ndef ('test:str', 'ndefpivdst')", msgs)
|
|
2467
|
+
|
|
2433
2468
|
# Bad pivot syntax go here
|
|
2434
2469
|
for q in ['test:pivcomp :lulz <- *',
|
|
2435
2470
|
'test:pivcomp :lulz <+- *',
|
|
@@ -6257,12 +6292,14 @@ class CortexBasicTest(s_t_utils.SynTest):
|
|
|
6257
6292
|
|
|
6258
6293
|
# Can't delete the default view
|
|
6259
6294
|
await self.asyncraises(s_exc.SynErr, core.delView(core.view.iden))
|
|
6295
|
+
await self.asyncraises(s_exc.SynErr, core._delViewWithLayer(core.view.iden, None, None))
|
|
6260
6296
|
|
|
6261
6297
|
# Can't delete a layer in a view
|
|
6262
6298
|
await self.asyncraises(s_exc.SynErr, core.delLayer(core.view.layers[0].iden))
|
|
6263
6299
|
|
|
6264
6300
|
# Can't delete a nonexistent view
|
|
6265
6301
|
await self.asyncraises(s_exc.NoSuchView, core.delView('XXX'))
|
|
6302
|
+
await self.asyncraises(s_exc.NoSuchView, core.delViewWithLayer('XXX'))
|
|
6266
6303
|
|
|
6267
6304
|
# Can't delete a nonexistent layer
|
|
6268
6305
|
await self.asyncraises(s_exc.NoSuchLayer, core.delLayer('XXX'))
|
|
@@ -6275,6 +6312,77 @@ class CortexBasicTest(s_t_utils.SynTest):
|
|
|
6275
6312
|
await core.delView(view2_iden)
|
|
6276
6313
|
await self.asyncraises(s_exc.NoSuchView, core.delView(view2_iden))
|
|
6277
6314
|
|
|
6315
|
+
layr = await core.addLayer()
|
|
6316
|
+
layriden = layr['iden']
|
|
6317
|
+
vdef3 = {'layers': (layriden,)}
|
|
6318
|
+
view3_iden = (await core.addView(vdef3)).get('iden')
|
|
6319
|
+
|
|
6320
|
+
opts = {'view': view3_iden}
|
|
6321
|
+
await core.callStorm('$lib.view.get().set(protected, $lib.true)', opts=opts)
|
|
6322
|
+
|
|
6323
|
+
await self.asyncraises(s_exc.CantDelView, core.delViewWithLayer(view3_iden))
|
|
6324
|
+
|
|
6325
|
+
await core.callStorm('$lib.view.get().set(protected, $lib.false)', opts=opts)
|
|
6326
|
+
|
|
6327
|
+
view3 = core.getView(view3_iden)
|
|
6328
|
+
vdef4 = await view3.fork()
|
|
6329
|
+
|
|
6330
|
+
deadlayr = view3.layers[0].iden
|
|
6331
|
+
view4_iden = vdef4.get('iden')
|
|
6332
|
+
view4 = core.getView(view4_iden)
|
|
6333
|
+
|
|
6334
|
+
self.eq(view4.parent, view3)
|
|
6335
|
+
self.len(2, view4.layers)
|
|
6336
|
+
|
|
6337
|
+
await core.auth.rootuser.setPasswd('secret')
|
|
6338
|
+
host, port = await core.dmon.listen('tcp://127.0.0.1:0/')
|
|
6339
|
+
layr2 = await core.callStorm('$layer=$lib.layer.add() return($layer)')
|
|
6340
|
+
varz = {'iden': layriden, 'tgt': layr2.get('iden'), 'port': port}
|
|
6341
|
+
opts = {'vars': varz, 'view': view3_iden}
|
|
6342
|
+
|
|
6343
|
+
pullq = '$layer=$lib.layer.get($iden).addPull(`tcp://root:secret@127.0.0.1:{$port}/*/layer/{$tgt}`)'
|
|
6344
|
+
pushq = '$layer=$lib.layer.get($iden).addPush(`tcp://root:secret@127.0.0.1:{$port}/*/layer/{$tgt}`)'
|
|
6345
|
+
msgs = await core.stormlist(pullq, opts=opts)
|
|
6346
|
+
self.stormHasNoWarnErr(msgs)
|
|
6347
|
+
|
|
6348
|
+
msgs = await core.stormlist(pushq, opts=opts)
|
|
6349
|
+
self.stormHasNoWarnErr(msgs)
|
|
6350
|
+
|
|
6351
|
+
coros = len(core.activecoros)
|
|
6352
|
+
|
|
6353
|
+
layridens = [lyr.iden for lyr in view4.layers if lyr.iden != view3.layers[0].iden]
|
|
6354
|
+
events = [
|
|
6355
|
+
{'event': 'view:setlayers', 'info': {'iden': view4.iden, 'layers': layridens}},
|
|
6356
|
+
{'event': 'view:set', 'info': {'iden': view4.iden, 'name': 'parent', 'valu': None}}
|
|
6357
|
+
]
|
|
6358
|
+
task = core.schedCoro(s_t_utils.waitForBehold(core, events))
|
|
6359
|
+
|
|
6360
|
+
await core.delViewWithLayer(view3_iden)
|
|
6361
|
+
|
|
6362
|
+
await asyncio.wait_for(task, timeout=1)
|
|
6363
|
+
|
|
6364
|
+
# push/pull activecoros have been deleted
|
|
6365
|
+
self.len(coros - 2, core.activecoros)
|
|
6366
|
+
|
|
6367
|
+
self.none(view4.parent)
|
|
6368
|
+
self.len(1, view4.layers)
|
|
6369
|
+
self.none(core.getLayer(deadlayr))
|
|
6370
|
+
|
|
6371
|
+
vdef5 = await view4.fork()
|
|
6372
|
+
view5 = core.getView(vdef5.get('iden'))
|
|
6373
|
+
|
|
6374
|
+
usedlayr = view4.layers[0].iden
|
|
6375
|
+
vdef6 = {'layers': (usedlayr,)}
|
|
6376
|
+
view6 = core.getView((await core.addView(vdef6)).get('iden'))
|
|
6377
|
+
|
|
6378
|
+
await core.delViewWithLayer(view4_iden)
|
|
6379
|
+
|
|
6380
|
+
self.none(view5.parent)
|
|
6381
|
+
self.len(1, view5.layers)
|
|
6382
|
+
|
|
6383
|
+
self.nn(core.getLayer(usedlayr))
|
|
6384
|
+
self.eq([usedlayr], [lyr.iden for lyr in view6.layers])
|
|
6385
|
+
|
|
6278
6386
|
async def test_cortex_view_opts(self):
|
|
6279
6387
|
'''
|
|
6280
6388
|
Test that the view opts work
|
synapse/tests/test_lib_ast.py
CHANGED
|
@@ -670,6 +670,28 @@ class AstTest(s_test.SynTest):
|
|
|
670
670
|
self.len(1, nodes)
|
|
671
671
|
self.eq('geo:nloc', nodes[0].ndef[0])
|
|
672
672
|
|
|
673
|
+
await core.nodes('[ test:str=ndefs :ndefs=((it:dev:int, 1), (it:dev:int, 2)) ]')
|
|
674
|
+
await core.nodes('test:str=ndefs [ :ndefs += (inet:fqdn, woot.com) ]')
|
|
675
|
+
self.len(1, nodes)
|
|
676
|
+
|
|
677
|
+
nodes = await core.nodes('it:dev:int=1 -> test:str:ndefs')
|
|
678
|
+
self.len(1, nodes)
|
|
679
|
+
self.eq('ndefs', nodes[0].ndef[1])
|
|
680
|
+
self.eq(nodes[0].getNodeRefs(), [
|
|
681
|
+
('ndefs', ('it:dev:int', 1)),
|
|
682
|
+
('ndefs', ('it:dev:int', 2)),
|
|
683
|
+
('ndefs', ('inet:fqdn', 'woot.com'))
|
|
684
|
+
])
|
|
685
|
+
|
|
686
|
+
nodes = await core.nodes('[ test:str = norefs ]')
|
|
687
|
+
self.eq(nodes[0].getNodeRefs(), [])
|
|
688
|
+
|
|
689
|
+
self.len(1, await core.nodes('it:dev:int=1 -> test:str'))
|
|
690
|
+
self.len(3, await core.nodes('test:str=ndefs -> *'))
|
|
691
|
+
self.len(2, await core.nodes('test:str=ndefs -> it:dev:int'))
|
|
692
|
+
self.len(3, await core.nodes('test:str=ndefs :ndefs -> *'))
|
|
693
|
+
self.len(2, await core.nodes('test:str=ndefs :ndefs -> it:dev:int'))
|
|
694
|
+
|
|
673
695
|
async def test_ast_pivot(self):
|
|
674
696
|
# a general purpose pivot test. come on in!
|
|
675
697
|
async with self.getTestCore() as core:
|
|
@@ -2761,6 +2783,50 @@ class AstTest(s_test.SynTest):
|
|
|
2761
2783
|
self.stormHasNoWarnErr(msgs)
|
|
2762
2784
|
self.len(0, calls)
|
|
2763
2785
|
|
|
2786
|
+
async def test_ast_tag_optimization(self):
|
|
2787
|
+
calls = []
|
|
2788
|
+
origtag = s_snap.Snap.nodesByTag
|
|
2789
|
+
|
|
2790
|
+
async def checkTag(self, tag, form=None, reverse=False):
|
|
2791
|
+
calls.append(('tag', tag, form))
|
|
2792
|
+
async for node in origtag(self, tag, form=form, reverse=reverse):
|
|
2793
|
+
yield node
|
|
2794
|
+
|
|
2795
|
+
with mock.patch('synapse.lib.snap.Snap.nodesByTag', checkTag):
|
|
2796
|
+
async with self.getTestCore() as core:
|
|
2797
|
+
self.len(1, await core.nodes('[inet:asn=200 :name=visi]'))
|
|
2798
|
+
self.len(1, await core.nodes('[test:int=12 +#visi]'))
|
|
2799
|
+
self.len(1, await core.nodes('[test:int=99 +#visi]'))
|
|
2800
|
+
|
|
2801
|
+
nodes = await core.nodes('test:int +#$x', opts={'vars': {'x': 'visi'}})
|
|
2802
|
+
self.len(2, nodes)
|
|
2803
|
+
self.len(1, calls)
|
|
2804
|
+
self.eq(('tag', 'visi', 'test:int'), calls[0])
|
|
2805
|
+
|
|
2806
|
+
calls = []
|
|
2807
|
+
# not for non-runtsafe
|
|
2808
|
+
nodes = await core.nodes('inet:asn:name $valu=:name test:int +#$valu')
|
|
2809
|
+
self.len(2, nodes)
|
|
2810
|
+
self.len(0, calls)
|
|
2811
|
+
|
|
2812
|
+
nodes = await core.nodes('''
|
|
2813
|
+
$tag = 'yeyeyeyeyeyeyeyeye'
|
|
2814
|
+
if $x {
|
|
2815
|
+
$tag = 'visi'
|
|
2816
|
+
} else {
|
|
2817
|
+
$tag = 'lolnope'
|
|
2818
|
+
}
|
|
2819
|
+
test:int +#$tag
|
|
2820
|
+
''', opts={'vars': {'x': True}})
|
|
2821
|
+
self.len(2, nodes)
|
|
2822
|
+
self.len(1, calls)
|
|
2823
|
+
self.eq(('tag', 'visi', 'test:int'), calls[0])
|
|
2824
|
+
|
|
2825
|
+
calls = []
|
|
2826
|
+
nodes = await core.nodes('test:int +#$x', opts={'vars': {'x': 'v*'}})
|
|
2827
|
+
self.len(2, nodes)
|
|
2828
|
+
self.len(0, calls)
|
|
2829
|
+
|
|
2764
2830
|
async def test_ast_cmdoper(self):
|
|
2765
2831
|
|
|
2766
2832
|
async with self.getTestCore() as core:
|
synapse/tests/test_lib_cell.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import ssl
|
|
3
3
|
import sys
|
|
4
|
+
import json
|
|
4
5
|
import time
|
|
5
6
|
import base64
|
|
6
7
|
import signal
|
|
@@ -33,6 +34,7 @@ import synapse.lib.version as s_version
|
|
|
33
34
|
import synapse.lib.hiveauth as s_hiveauth
|
|
34
35
|
import synapse.lib.lmdbslab as s_lmdbslab
|
|
35
36
|
import synapse.lib.crypto.passwd as s_passwd
|
|
37
|
+
import synapse.lib.platforms.linux as s_linux
|
|
36
38
|
|
|
37
39
|
import synapse.tools.backup as s_tools_backup
|
|
38
40
|
|
|
@@ -2611,3 +2613,113 @@ class CellTest(s_t_utils.SynTest):
|
|
|
2611
2613
|
|
|
2612
2614
|
with self.raises(s_exc.NoSuchIden):
|
|
2613
2615
|
await cell.delUserApiKey(newp)
|
|
2616
|
+
|
|
2617
|
+
async def test_cell_check_sysctl(self):
|
|
2618
|
+
sysctls = s_linux.getSysctls()
|
|
2619
|
+
|
|
2620
|
+
sysvals = s_cell.Cell.SYSCTL_VALS.copy()
|
|
2621
|
+
sysvals['vm.dirty_expire_centisecs'] += 1
|
|
2622
|
+
sysvals['vm.dirty_writeback_centisecs'] += 1
|
|
2623
|
+
|
|
2624
|
+
# Detect and report incorrect values
|
|
2625
|
+
with self.getStructuredAsyncLoggerStream('synapse.lib.cell') as stream:
|
|
2626
|
+
with mock.patch.object(s_cell.Cell, 'SYSCTL_VALS', sysvals):
|
|
2627
|
+
async with self.getTestCore(conf={'health:sysctl:checks': True}):
|
|
2628
|
+
pass
|
|
2629
|
+
|
|
2630
|
+
stream.seek(0)
|
|
2631
|
+
data = stream.getvalue()
|
|
2632
|
+
raw_mesgs = [m for m in data.split('\\n') if m]
|
|
2633
|
+
msgs = [json.loads(m) for m in raw_mesgs]
|
|
2634
|
+
|
|
2635
|
+
self.len(1, msgs)
|
|
2636
|
+
|
|
2637
|
+
mesg = f'Sysctl values different than expected: {", ".join(sysvals)}. '
|
|
2638
|
+
mesg += 'See https://synapse.docs.vertex.link/en/latest/synapse/devopsguide.html#performance-tuning '
|
|
2639
|
+
mesg += 'for information about these sysctl parameters.'
|
|
2640
|
+
self.eq(msgs[0]['message'], mesg)
|
|
2641
|
+
self.eq(msgs[0]['sysctls'], [
|
|
2642
|
+
{'name': 'vm.dirty_expire_centisecs', 'expected': 21, 'actual': sysctls['vm.dirty_expire_centisecs']},
|
|
2643
|
+
{'name': 'vm.dirty_writeback_centisecs', 'expected': 21, 'actual': sysctls['vm.dirty_writeback_centisecs']},
|
|
2644
|
+
])
|
|
2645
|
+
|
|
2646
|
+
# Copy the current sysctl valus to the cell so the check passes
|
|
2647
|
+
sysvals = {
|
|
2648
|
+
'vm.dirty_expire_centisecs': sysctls['vm.dirty_expire_centisecs'],
|
|
2649
|
+
'vm.dirty_writeback_centisecs': sysctls['vm.dirty_writeback_centisecs'],
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
# Detect correct values and stop the task
|
|
2653
|
+
with self.getLoggerStream('synapse.lib.cell') as stream:
|
|
2654
|
+
with mock.patch.object(s_cell.Cell, 'SYSCTL_VALS', sysvals):
|
|
2655
|
+
async with self.getTestCore():
|
|
2656
|
+
pass
|
|
2657
|
+
|
|
2658
|
+
stream.seek(0)
|
|
2659
|
+
data = stream.read()
|
|
2660
|
+
self.len(0, data)
|
|
2661
|
+
|
|
2662
|
+
# Disable the sysctl check and don't check at all
|
|
2663
|
+
with self.getLoggerStream('synapse.lib.cell') as stream:
|
|
2664
|
+
conf = {'health:sysctl:checks': False}
|
|
2665
|
+
async with self.getTestCore(conf=conf):
|
|
2666
|
+
pass
|
|
2667
|
+
|
|
2668
|
+
stream.seek(0)
|
|
2669
|
+
data = stream.read()
|
|
2670
|
+
self.len(0, data, msg=data)
|
|
2671
|
+
|
|
2672
|
+
async def test_cell_version_regression(self):
|
|
2673
|
+
oldver = (0, 1, 0)
|
|
2674
|
+
newver = (0, 2, 0)
|
|
2675
|
+
|
|
2676
|
+
class TestCell(s_cell.Cell):
|
|
2677
|
+
VERSION = newver
|
|
2678
|
+
|
|
2679
|
+
with self.getTestDir() as dirn:
|
|
2680
|
+
async with self.getTestCell(TestCell, dirn=dirn):
|
|
2681
|
+
pass
|
|
2682
|
+
|
|
2683
|
+
with self.raises(s_exc.BadVersion) as exc:
|
|
2684
|
+
with mock.patch.object(TestCell, 'VERSION', oldver):
|
|
2685
|
+
with self.getLoggerStream('synapse.lib.cell') as stream:
|
|
2686
|
+
async with self.getTestCell(TestCell, dirn=dirn):
|
|
2687
|
+
pass
|
|
2688
|
+
|
|
2689
|
+
mesg = f'Cell version regression (testcell) is not allowed! Stored version: {newver}, current version: {oldver}.'
|
|
2690
|
+
self.eq(exc.exception.get('mesg'), mesg)
|
|
2691
|
+
self.eq(exc.exception.get('currver'), oldver)
|
|
2692
|
+
self.eq(exc.exception.get('lastver'), newver)
|
|
2693
|
+
|
|
2694
|
+
stream.seek(0)
|
|
2695
|
+
data = stream.read()
|
|
2696
|
+
self.isin(mesg, data)
|
|
2697
|
+
|
|
2698
|
+
async with self.getTestCell(TestCell, dirn=dirn):
|
|
2699
|
+
pass
|
|
2700
|
+
|
|
2701
|
+
with self.getTestDir() as dirn:
|
|
2702
|
+
async with self.getTestCell(s_cell.Cell, dirn=dirn):
|
|
2703
|
+
pass
|
|
2704
|
+
|
|
2705
|
+
synver = list(s_version.version)
|
|
2706
|
+
synver[1] -= 1
|
|
2707
|
+
synver = tuple(synver)
|
|
2708
|
+
|
|
2709
|
+
with self.raises(s_exc.BadVersion) as exc:
|
|
2710
|
+
with mock.patch.object(s_version, 'version', synver):
|
|
2711
|
+
with self.getLoggerStream('synapse.lib.cell') as stream:
|
|
2712
|
+
async with self.getTestCell(s_cell.Cell, dirn=dirn):
|
|
2713
|
+
pass
|
|
2714
|
+
|
|
2715
|
+
mesg = f'Synapse version regression (cell) is not allowed! Stored version: {s_version.version}, current version: {synver}.'
|
|
2716
|
+
self.eq(exc.exception.get('mesg'), mesg)
|
|
2717
|
+
self.eq(exc.exception.get('currver'), synver)
|
|
2718
|
+
self.eq(exc.exception.get('lastver'), s_version.version)
|
|
2719
|
+
|
|
2720
|
+
stream.seek(0)
|
|
2721
|
+
data = stream.read()
|
|
2722
|
+
self.isin(mesg, data)
|
|
2723
|
+
|
|
2724
|
+
async with self.getTestCell(s_cell.Cell, dirn=dirn):
|
|
2725
|
+
pass
|
synapse/tests/test_lib_layer.py
CHANGED
|
@@ -26,7 +26,7 @@ class LayerTest(s_t_utils.SynTest):
|
|
|
26
26
|
|
|
27
27
|
def checkLayrvers(self, core):
|
|
28
28
|
for layr in core.layers.values():
|
|
29
|
-
self.eq(layr.layrvers,
|
|
29
|
+
self.eq(layr.layrvers, 11)
|
|
30
30
|
|
|
31
31
|
async def test_layer_verify(self):
|
|
32
32
|
|
|
@@ -1530,6 +1530,57 @@ class LayerTest(s_t_utils.SynTest):
|
|
|
1530
1530
|
verbs = [verb async for verb in nodes0[0].iterEdgeVerbs(buid2)]
|
|
1531
1531
|
self.len(0, verbs)
|
|
1532
1532
|
|
|
1533
|
+
async def test_layer_v11(self):
|
|
1534
|
+
|
|
1535
|
+
try:
|
|
1536
|
+
|
|
1537
|
+
oldv = s_layer.MIGR_COMMIT_SIZE
|
|
1538
|
+
s_layer.MIGR_COMMIT_SIZE = 1
|
|
1539
|
+
|
|
1540
|
+
async with self.getRegrCore('layer-v11') as core:
|
|
1541
|
+
|
|
1542
|
+
wlyrs_byview = await core.callStorm('''
|
|
1543
|
+
$wlyrs = ({})
|
|
1544
|
+
for $view in $lib.view.list() {
|
|
1545
|
+
$wlyrs.($view.get(name)) = $view.layers.0.iden
|
|
1546
|
+
}
|
|
1547
|
+
return($wlyrs)
|
|
1548
|
+
''')
|
|
1549
|
+
self.len(8, wlyrs_byview)
|
|
1550
|
+
|
|
1551
|
+
layr = core.getLayer(iden=wlyrs_byview['default'])
|
|
1552
|
+
await self.agenlen(2, layr.getStorNodesByForm('test:str'))
|
|
1553
|
+
await self.agenlen(1, layr.getStorNodesByForm('syn:tag'))
|
|
1554
|
+
|
|
1555
|
+
layr = core.getLayer(iden=wlyrs_byview['prop'])
|
|
1556
|
+
await self.agenlen(1, layr.getStorNodesByForm('test:str'))
|
|
1557
|
+
|
|
1558
|
+
layr = core.getLayer(iden=wlyrs_byview['tags'])
|
|
1559
|
+
await self.agenlen(1, layr.getStorNodesByForm('test:str'))
|
|
1560
|
+
await self.agenlen(1, layr.getStorNodesByForm('syn:tag'))
|
|
1561
|
+
|
|
1562
|
+
layr = core.getLayer(iden=wlyrs_byview['tagp'])
|
|
1563
|
+
await self.agenlen(1, layr.getStorNodesByForm('test:str'))
|
|
1564
|
+
await self.agenlen(0, layr.getStorNodesByForm('syn:tag'))
|
|
1565
|
+
|
|
1566
|
+
layr = core.getLayer(iden=wlyrs_byview['n1eg'])
|
|
1567
|
+
await self.agenlen(1, layr.getStorNodesByForm('test:str'))
|
|
1568
|
+
await self.agenlen(1, layr.getStorNodesByForm('test:int'))
|
|
1569
|
+
|
|
1570
|
+
layr = core.getLayer(iden=wlyrs_byview['n2eg'])
|
|
1571
|
+
await self.agenlen(0, layr.getStorNodesByForm('test:str'))
|
|
1572
|
+
await self.agenlen(1, layr.getStorNodesByForm('test:int'))
|
|
1573
|
+
|
|
1574
|
+
layr = core.getLayer(iden=wlyrs_byview['data'])
|
|
1575
|
+
await self.agenlen(1, layr.getStorNodesByForm('test:str'))
|
|
1576
|
+
|
|
1577
|
+
layr = core.getLayer(iden=wlyrs_byview['noop'])
|
|
1578
|
+
await self.agenlen(0, layr.getStorNodes())
|
|
1579
|
+
await self.agenlen(0, layr.getStorNodesByForm('test:str'))
|
|
1580
|
+
|
|
1581
|
+
finally:
|
|
1582
|
+
s_layer.MIGR_COMMIT_SIZE = oldv
|
|
1583
|
+
|
|
1533
1584
|
async def test_layer_logedits_default(self):
|
|
1534
1585
|
async with self.getTestCore() as core:
|
|
1535
1586
|
self.true(core.getLayer().logedits)
|
|
@@ -649,6 +649,42 @@ class LmdbSlabTest(s_t_utils.SynTest):
|
|
|
649
649
|
self.eq((b'1', b'1'), next(it))
|
|
650
650
|
self.raises(StopIteration, next, it)
|
|
651
651
|
|
|
652
|
+
async def test_lmdbslab_scanback(self):
|
|
653
|
+
|
|
654
|
+
with self.getTestDir() as dirn:
|
|
655
|
+
|
|
656
|
+
path = os.path.join(dirn, 'test.lmdb')
|
|
657
|
+
|
|
658
|
+
async with await s_lmdbslab.Slab.anit(path, map_size=100000, growsize=10000) as slab:
|
|
659
|
+
|
|
660
|
+
foodup = slab.initdb('foodup', dupsort=True)
|
|
661
|
+
foonodup = slab.initdb('foonodup', dupsort=False)
|
|
662
|
+
|
|
663
|
+
for db in (foodup, foonodup):
|
|
664
|
+
slab.put(b'\x01', b'foo', db=db)
|
|
665
|
+
slab.put(b'\x01\x01', b'bar', db=db)
|
|
666
|
+
slab.put(b'\x01\x03', b'baz', db=db)
|
|
667
|
+
slab.put(b'\x02', b'faz', db=db)
|
|
668
|
+
|
|
669
|
+
items = list(slab.scanByPrefBack(b'\x01', db=foonodup))
|
|
670
|
+
self.eq(items, (
|
|
671
|
+
(b'\x01\x03', b'baz'),
|
|
672
|
+
(b'\x01\x01', b'bar'),
|
|
673
|
+
(b'\x01', b'foo')
|
|
674
|
+
))
|
|
675
|
+
|
|
676
|
+
self.eq((), list(slab.scanByPrefBack(b'\x00', db=foonodup)))
|
|
677
|
+
|
|
678
|
+
slab.put(b'\x01\x03', b'waz', db=foodup)
|
|
679
|
+
|
|
680
|
+
items = list(slab.scanByPrefBack(b'\x01', db=foodup))
|
|
681
|
+
self.eq(items, (
|
|
682
|
+
(b'\x01\x03', b'waz'),
|
|
683
|
+
(b'\x01\x03', b'baz'),
|
|
684
|
+
(b'\x01\x01', b'bar'),
|
|
685
|
+
(b'\x01', b'foo')
|
|
686
|
+
))
|
|
687
|
+
|
|
652
688
|
async def test_lmdbslab_count_empty(self):
|
|
653
689
|
|
|
654
690
|
with self.getTestDir() as dirn:
|
synapse/tests/test_lib_scrape.py
CHANGED
|
@@ -508,6 +508,77 @@ bad_uncs = [
|
|
|
508
508
|
|
|
509
509
|
unc_paths = '\n'.join(good_uncs + bad_uncs)
|
|
510
510
|
|
|
511
|
+
cpedata = r'''GOOD DATA
|
|
512
|
+
cpe:2.3:a:vendor:product:version:update:edition:lng:sw_edition:target_sw:target_hw:other
|
|
513
|
+
cpe:2.3:a:vendor:product:version:update:edition:lng:sw_edition:target_sw:target_hw:tspace non matched word
|
|
514
|
+
cpe:2.3:a:*:*:*:*:*:*:*:*:*:*
|
|
515
|
+
cpe:2.3:h:*:*:*:*:*:*:*:*:*:*
|
|
516
|
+
cpe:2.3:o:*:*:*:*:*:*:*:*:*:*
|
|
517
|
+
cpe:2.3:-:*:*:*:*:*:*:*:*:*:*
|
|
518
|
+
cpe:2.3:*:*:*:*:*:*:*:*:*:*:*
|
|
519
|
+
cpe:2.3:*:-:na:*:*:*:*:*:*:*:*
|
|
520
|
+
cpe:2.3:*:.:dot:*:*:*:*:*:*:*:*
|
|
521
|
+
cpe:2.3:*:_:underscore:*:*:*:*:*:*:*:*
|
|
522
|
+
|
|
523
|
+
A few quoted characters
|
|
524
|
+
cpe:2.3:*:\!:quoted:*:*:*:*:*:*:*:*
|
|
525
|
+
cpe:2.3:*:\?:quoted:*:*:*:*:*:*:*:*
|
|
526
|
+
cpe:2.3:*:\*:quoted:*:*:*:*:*:*:*:*
|
|
527
|
+
cpe:2.3:*:\\:escapeescape:*:*:*:*:*:*:*:*
|
|
528
|
+
cpe:2.3:*:langtest:*:*:*:*:-:*:*:*:*
|
|
529
|
+
cpe:2.3:*:langtest:*:*:*:*:*:*:*:*:*
|
|
530
|
+
cpe:2.3:*:langtest:*:*:*:*:en:*:*:*:*
|
|
531
|
+
cpe:2.3:*:langtest:*:*:*:*:usa:*:*:*:*
|
|
532
|
+
cpe:2.3:*:langtest:*:*:*:*:usa-en:*:*:*:*
|
|
533
|
+
cpe:2.3:*:langtest:*:*:*:*:usa-123:*:*:*:*
|
|
534
|
+
|
|
535
|
+
A few examples
|
|
536
|
+
cpe:2.3:a:ntp:ntp:4.2.8:p3:*:*:*:*:*:*
|
|
537
|
+
cpe:2.3:o:microsoft:windows_7:-:sp2:*:*:*:*:*:*
|
|
538
|
+
cpe:2.3:a:hp:insight:7.4.0.1570:-:*:*:online:win2003:x64:*
|
|
539
|
+
cpe:2.3:a:foo\\bar:big\$money_2010:*:*:*:*:special:ipod_touch:80gb:*
|
|
540
|
+
cpe:2.3:a:hp:openview_network_manager:7.51:*:*:*:*:linux:*:*
|
|
541
|
+
cpe:2.3:a:apple:swiftnio_http\/2:1.19.1:*:*:*:*:swift:*:*
|
|
542
|
+
|
|
543
|
+
Some quoted examples
|
|
544
|
+
cpe:2.3:a:fooo:bar_baz\:_beep_bpp_sys:1.1:*:*:*:*:ios:*:*
|
|
545
|
+
cpe:2.3:a:lemonldap-ng:apache\:\:session\:\:browsable:0.9:*:*:*:*:perl:*:*
|
|
546
|
+
cpe:2.3:a:daemon-ng:hurray\:\::0.x:*:*:*:*:*:*:*
|
|
547
|
+
cpe:2.3:a:microsoft:intern\^et_explorer:8.0.6001:beta:*:*:*:*:*:*
|
|
548
|
+
|
|
549
|
+
TEXT examples
|
|
550
|
+
Example double quoted cpe value "cpe:2.3:a:vertex:synapse:*:*:*:*:*:*:*:dquotes".
|
|
551
|
+
Example double quoted cpe value "cpe:2.3:a:vertex:synapse:*:*:*:*:*:*:*:MiXeDcAsE".
|
|
552
|
+
Example single quoted cpe value "cpe:2.3:a:vertex:synapse:*:*:*:*:*:*:*:squotes".
|
|
553
|
+
A CPE at the end of a sentence like this captures the period... cpe:2.3:a:*:*:*:*:*:*:*:*:*:hasperiod.
|
|
554
|
+
Some CPE are exciting! Like this cpe:2.3:a:*:*:*:*:*:*:*:*:*:noexclaim!
|
|
555
|
+
Some CPE are boring! Like this cpe:2.3:a:*:*:*:*:*:*:*:*:*:noslash\
|
|
556
|
+
Unicode endings are omitted cpe:2.3:a:*:*:*:*:*:*:*:*:*:unicodeend0ॐ
|
|
557
|
+
Unicode quotes “cpe:2.3:a:*:*:*:*:*:*:*:*:*:smartquotes”
|
|
558
|
+
cpe:2.3:*:?why??:*:*:*:*:*:*:*:*:*
|
|
559
|
+
cpe:2.3:*:*why*:*:*:*:*:*:*:*:*:*
|
|
560
|
+
|
|
561
|
+
EMBEDDED TEXT
|
|
562
|
+
wordscpe:2.3:a:vendor:product:version:update:edition:lng:sw_edition:target_sw:target_hw:otherxxx:newp
|
|
563
|
+
wordscpe:2.3:a:vendor:product:version:update:edition:lng:sw_edition:target_sw:target_hw:otherzzz:
|
|
564
|
+
|
|
565
|
+
BAD values
|
|
566
|
+
cpe:2.3:*:?:spec1:*:*:*:*:*:*:*:*
|
|
567
|
+
cpe:2.3:a:vertex:synapse:*:*:*:NEWP:*:*:*:*
|
|
568
|
+
cpe:2.3:a::::::::::
|
|
569
|
+
cpe:2.3:a:vendor:product:version:update:edition:lng:sw_edition:target_sw:ॐ:other
|
|
570
|
+
cpe:2.3:a:vendor:product:version:update:edition
|
|
571
|
+
cpe:2.3:a:opps:bad_quote\\/2:1.19.1:*:*:*:*:swift:*:*
|
|
572
|
+
|
|
573
|
+
# Bad languages
|
|
574
|
+
cpe:2.3:*:langtest:*:*:*:*:a:*:*:*:*
|
|
575
|
+
cpe:2.3:*:langtest:*:*:*:*:aaaa:*:*:*:*
|
|
576
|
+
cpe:2.3:*:langtest:*:*:*:*:usa-o:*:*:*:*
|
|
577
|
+
cpe:2.3:*:langtest:*:*:*:*:usa-omn:*:*:*:*
|
|
578
|
+
cpe:2.3:*:langtest:*:*:*:*:usa-12:*:*:*:*
|
|
579
|
+
cpe:2.3:*:langtest:*:*:*:*:usa-1234:*:*:*:*
|
|
580
|
+
'''
|
|
581
|
+
|
|
511
582
|
class ScrapeTest(s_t_utils.SynTest):
|
|
512
583
|
|
|
513
584
|
def test_scrape_basic(self):
|
|
@@ -1050,77 +1121,7 @@ class ScrapeTest(s_t_utils.SynTest):
|
|
|
1050
1121
|
self.eq(erv, fv)
|
|
1051
1122
|
|
|
1052
1123
|
def test_scrape_cpe(self):
|
|
1053
|
-
|
|
1054
|
-
GOOD DATA
|
|
1055
|
-
cpe:2.3:a:vendor:product:version:update:edition:lng:sw_edition:target_sw:target_hw:other
|
|
1056
|
-
cpe:2.3:a:vendor:product:version:update:edition:lng:sw_edition:target_sw:target_hw:tspace non matched word
|
|
1057
|
-
cpe:2.3:a:*:*:*:*:*:*:*:*:*:*
|
|
1058
|
-
cpe:2.3:h:*:*:*:*:*:*:*:*:*:*
|
|
1059
|
-
cpe:2.3:o:*:*:*:*:*:*:*:*:*:*
|
|
1060
|
-
cpe:2.3:-:*:*:*:*:*:*:*:*:*:*
|
|
1061
|
-
cpe:2.3:*:*:*:*:*:*:*:*:*:*:*
|
|
1062
|
-
cpe:2.3:*:-:na:*:*:*:*:*:*:*:*
|
|
1063
|
-
cpe:2.3:*:.:dot:*:*:*:*:*:*:*:*
|
|
1064
|
-
cpe:2.3:*:_:underscore:*:*:*:*:*:*:*:*
|
|
1065
|
-
|
|
1066
|
-
A few quoted characters
|
|
1067
|
-
cpe:2.3:*:\!:quoted:*:*:*:*:*:*:*:*
|
|
1068
|
-
cpe:2.3:*:\?:quoted:*:*:*:*:*:*:*:*
|
|
1069
|
-
cpe:2.3:*:\*:quoted:*:*:*:*:*:*:*:*
|
|
1070
|
-
cpe:2.3:*:\\:escapeescape:*:*:*:*:*:*:*:*
|
|
1071
|
-
cpe:2.3:*:langtest:*:*:*:*:-:*:*:*:*
|
|
1072
|
-
cpe:2.3:*:langtest:*:*:*:*:*:*:*:*:*
|
|
1073
|
-
cpe:2.3:*:langtest:*:*:*:*:en:*:*:*:*
|
|
1074
|
-
cpe:2.3:*:langtest:*:*:*:*:usa:*:*:*:*
|
|
1075
|
-
cpe:2.3:*:langtest:*:*:*:*:usa-en:*:*:*:*
|
|
1076
|
-
cpe:2.3:*:langtest:*:*:*:*:usa-123:*:*:*:*
|
|
1077
|
-
|
|
1078
|
-
A few examples
|
|
1079
|
-
cpe:2.3:a:ntp:ntp:4.2.8:p3:*:*:*:*:*:*
|
|
1080
|
-
cpe:2.3:o:microsoft:windows_7:-:sp2:*:*:*:*:*:*
|
|
1081
|
-
cpe:2.3:a:hp:insight:7.4.0.1570:-:*:*:online:win2003:x64:*
|
|
1082
|
-
cpe:2.3:a:foo\\bar:big\$money_2010:*:*:*:*:special:ipod_touch:80gb:*
|
|
1083
|
-
cpe:2.3:a:hp:openview_network_manager:7.51:*:*:*:*:linux:*:*
|
|
1084
|
-
cpe:2.3:a:apple:swiftnio_http\/2:1.19.1:*:*:*:*:swift:*:*
|
|
1085
|
-
|
|
1086
|
-
Some quoted examples
|
|
1087
|
-
cpe:2.3:a:fooo:bar_baz\:_beep_bpp_sys:1.1:*:*:*:*:ios:*:*
|
|
1088
|
-
cpe:2.3:a:lemonldap-ng:apache\:\:session\:\:browsable:0.9:*:*:*:*:perl:*:*
|
|
1089
|
-
cpe:2.3:a:daemon-ng:hurray\:\::0.x:*:*:*:*:*:*:*
|
|
1090
|
-
cpe:2.3:a:microsoft:intern\^et_explorer:8.0.6001:beta:*:*:*:*:*:*
|
|
1091
|
-
|
|
1092
|
-
TEXT examples
|
|
1093
|
-
Example double quoted cpe value "cpe:2.3:a:vertex:synapse:*:*:*:*:*:*:*:dquotes".
|
|
1094
|
-
Example double quoted cpe value "cpe:2.3:a:vertex:synapse:*:*:*:*:*:*:*:MiXeDcAsE".
|
|
1095
|
-
Example single quoted cpe value "cpe:2.3:a:vertex:synapse:*:*:*:*:*:*:*:squotes".
|
|
1096
|
-
A CPE at the end of a sentence like this captures the period... cpe:2.3:a:*:*:*:*:*:*:*:*:*:hasperiod.
|
|
1097
|
-
Some CPE are exciting! Like this cpe:2.3:a:*:*:*:*:*:*:*:*:*:noexclaim!
|
|
1098
|
-
Some CPE are boring! Like this cpe:2.3:a:*:*:*:*:*:*:*:*:*:noslash\
|
|
1099
|
-
Unicode endings are omitted cpe:2.3:a:*:*:*:*:*:*:*:*:*:unicodeend0ॐ
|
|
1100
|
-
Unicode quotes “cpe:2.3:a:*:*:*:*:*:*:*:*:*:smartquotes”
|
|
1101
|
-
cpe:2.3:*:?why??:*:*:*:*:*:*:*:*:*
|
|
1102
|
-
cpe:2.3:*:*why*:*:*:*:*:*:*:*:*:*
|
|
1103
|
-
|
|
1104
|
-
EMBEDDED TEXT
|
|
1105
|
-
wordscpe:2.3:a:vendor:product:version:update:edition:lng:sw_edition:target_sw:target_hw:otherxxx:newp
|
|
1106
|
-
wordscpe:2.3:a:vendor:product:version:update:edition:lng:sw_edition:target_sw:target_hw:otherzzz:
|
|
1107
|
-
|
|
1108
|
-
BAD values
|
|
1109
|
-
cpe:2.3:*:?:spec1:*:*:*:*:*:*:*:*
|
|
1110
|
-
cpe:2.3:a:vertex:synapse:*:*:*:NEWP:*:*:*:*
|
|
1111
|
-
cpe:2.3:a::::::::::
|
|
1112
|
-
cpe:2.3:a:vendor:product:version:update:edition:lng:sw_edition:target_sw:ॐ:other
|
|
1113
|
-
cpe:2.3:a:vendor:product:version:update:edition
|
|
1114
|
-
cpe:2.3:a:opps:bad_quote\\/2:1.19.1:*:*:*:*:swift:*:*
|
|
1115
|
-
|
|
1116
|
-
# Bad languages
|
|
1117
|
-
cpe:2.3:*:langtest:*:*:*:*:a:*:*:*:*
|
|
1118
|
-
cpe:2.3:*:langtest:*:*:*:*:aaaa:*:*:*:*
|
|
1119
|
-
cpe:2.3:*:langtest:*:*:*:*:usa-o:*:*:*:*
|
|
1120
|
-
cpe:2.3:*:langtest:*:*:*:*:usa-omn:*:*:*:*
|
|
1121
|
-
cpe:2.3:*:langtest:*:*:*:*:usa-12:*:*:*:*
|
|
1122
|
-
cpe:2.3:*:langtest:*:*:*:*:usa-1234:*:*:*:*
|
|
1123
|
-
'''
|
|
1124
|
+
|
|
1124
1125
|
nodes = sorted(set(s_scrape.scrape(cpedata, ptype='it:sec:cpe')))
|
|
1125
1126
|
nodes.remove(('it:sec:cpe', 'cpe:2.3:*:*:*:*:*:*:*:*:*:*:*'))
|
|
1126
1127
|
nodes.remove(('it:sec:cpe', 'cpe:2.3:*:-:na:*:*:*:*:*:*:*:*'))
|
synapse/tests/test_lib_snap.py
CHANGED
|
@@ -584,7 +584,10 @@ class SnapTest(s_t_utils.SynTest):
|
|
|
584
584
|
async def test_snap_editor(self):
|
|
585
585
|
|
|
586
586
|
async with self.getTestCore() as core:
|
|
587
|
-
|
|
587
|
+
|
|
588
|
+
await core.nodes('$lib.model.ext.addTagProp(test, (str, ({})), ({}))')
|
|
589
|
+
await core.nodes('[ media:news=63381924986159aff183f0c85bd8ebad +(refs)> {[ inet:fqdn=vertex.link ]} +#foo ]')
|
|
590
|
+
|
|
588
591
|
root = core.auth.rootuser
|
|
589
592
|
async with await core.view.snap(user=root) as snap:
|
|
590
593
|
async with snap.getEditor() as editor:
|
|
@@ -608,6 +611,14 @@ class SnapTest(s_t_utils.SynTest):
|
|
|
608
611
|
self.len(1, nodeedits)
|
|
609
612
|
self.len(1, nodeedits[0][2])
|
|
610
613
|
|
|
614
|
+
self.false(await news.hasData('foo'))
|
|
615
|
+
await news.setData('foo', 'bar')
|
|
616
|
+
self.true(await news.hasData('foo'))
|
|
617
|
+
|
|
618
|
+
self.false(news.hasTagProp('foo', 'test'))
|
|
619
|
+
await news.setTagProp('foo', 'test', 'bar')
|
|
620
|
+
self.true(news.hasTagProp('foo', 'test'))
|
|
621
|
+
|
|
611
622
|
async with snap.getEditor() as editor:
|
|
612
623
|
news = await editor.addNode('media:news', '63381924986159aff183f0c85bd8ebad')
|
|
613
624
|
|
|
@@ -629,6 +640,10 @@ class SnapTest(s_t_utils.SynTest):
|
|
|
629
640
|
self.false(await news.delEdge('pwns', 1))
|
|
630
641
|
self.false(await news.delEdge('pwns', 'bar'))
|
|
631
642
|
|
|
643
|
+
self.true(await news.hasData('foo'))
|
|
644
|
+
|
|
645
|
+
self.true(news.hasTagProp('foo', 'test'))
|
|
646
|
+
|
|
632
647
|
self.len(1, await core.nodes('media:news -(pwns)> *'))
|
|
633
648
|
|
|
634
649
|
self.len(1, await core.nodes('[ test:ro=foo :writeable=hehe :readable=haha ]'))
|