synapse 2.189.0__py311-none-any.whl → 2.191.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 (43) hide show
  1. synapse/common.py +9 -0
  2. synapse/cortex.py +26 -5
  3. synapse/datamodel.py +8 -1
  4. synapse/lib/cell.py +7 -7
  5. synapse/lib/msgpack.py +10 -3
  6. synapse/lib/storm.py +5 -1
  7. synapse/lib/stormhttp.py +3 -10
  8. synapse/lib/stormlib/model.py +37 -0
  9. synapse/lib/stormtypes.py +27 -17
  10. synapse/lib/version.py +2 -2
  11. synapse/lib/view.py +2 -5
  12. synapse/models/auth.py +2 -1
  13. synapse/models/base.py +20 -0
  14. synapse/models/crypto.py +5 -2
  15. synapse/models/economic.py +45 -11
  16. synapse/models/inet.py +78 -21
  17. synapse/models/person.py +11 -4
  18. synapse/models/risk.py +6 -0
  19. synapse/models/syn.py +22 -12
  20. synapse/models/telco.py +3 -1
  21. synapse/tests/test_cortex.py +73 -31
  22. synapse/tests/test_lib_agenda.py +1 -6
  23. synapse/tests/test_lib_cell.py +28 -4
  24. synapse/tests/test_lib_httpapi.py +2 -4
  25. synapse/tests/test_lib_lmdbslab.py +1 -4
  26. synapse/tests/test_lib_stormhttp.py +9 -0
  27. synapse/tests/test_lib_stormlib_cortex.py +1 -3
  28. synapse/tests/test_lib_stormlib_log.py +1 -6
  29. synapse/tests/test_lib_stormlib_model.py +28 -0
  30. synapse/tests/test_lib_stormtypes.py +17 -3
  31. synapse/tests/test_lib_trigger.py +2 -3
  32. synapse/tests/test_model_base.py +12 -2
  33. synapse/tests/test_model_inet.py +23 -0
  34. synapse/tests/test_model_person.py +2 -0
  35. synapse/tests/test_model_risk.py +5 -0
  36. synapse/tests/test_model_syn.py +198 -0
  37. synapse/tests/test_utils.py +23 -4
  38. synapse/tests/utils.py +39 -5
  39. {synapse-2.189.0.dist-info → synapse-2.191.0.dist-info}/METADATA +5 -5
  40. {synapse-2.189.0.dist-info → synapse-2.191.0.dist-info}/RECORD +43 -43
  41. {synapse-2.189.0.dist-info → synapse-2.191.0.dist-info}/LICENSE +0 -0
  42. {synapse-2.189.0.dist-info → synapse-2.191.0.dist-info}/WHEEL +0 -0
  43. {synapse-2.189.0.dist-info → synapse-2.191.0.dist-info}/top_level.txt +0 -0
@@ -115,6 +115,14 @@ class EchoAuthApi(s_cell.CellApi):
115
115
  await self._reqUserAllowed(path)
116
116
  return True
117
117
 
118
+ @s_cell.adminapi()
119
+ async def adminOnly(self):
120
+ return True
121
+
122
+ @s_cell.adminapi(log=True)
123
+ async def adminOnlyLog(self, arg1, arg2, **kwargs):
124
+ return arg1, arg2, kwargs
125
+
118
126
  class EchoAuth(s_cell.Cell):
119
127
  cellapi = EchoAuthApi
120
128
 
@@ -428,6 +436,16 @@ class CellTest(s_t_utils.SynTest):
428
436
  self.eq(info.get('user').get('name'), 'root')
429
437
  self.eq(info.get('user').get('iden'), root.iden)
430
438
 
439
+ # @adminApi methods are allowed
440
+ self.true(await proxy.adminOnly())
441
+ mesg = "Executing [EchoAuthApi.adminOnlyLog] as [root] with args [(1, 2)[{'three': 4}]"
442
+ with self.getStructuredAsyncLoggerStream('synapse.lib.cell', mesg) as stream:
443
+ self.eq(await proxy.adminOnlyLog(1, 2, three=4), (1, 2, {'three': 4}))
444
+ self.true(await stream.wait(timeout=10))
445
+ msgs = stream.jsonlines()
446
+ self.len(1, msgs)
447
+ self.eq('EchoAuthApi.adminOnlyLog', msgs[0].get('wrapped_func'))
448
+
431
449
  visi = await echo.auth.addUser('visi')
432
450
  await visi.setPasswd('foo')
433
451
  await visi.addRule((True, ('foo', 'bar')))
@@ -454,6 +472,15 @@ class CellTest(s_t_utils.SynTest):
454
472
  with self.raises(s_exc.NoSuchUser):
455
473
  await proxy.getUserInfo('newp')
456
474
 
475
+ # @adminApi methods are not allowed
476
+ with self.raises(s_exc.AuthDeny) as cm:
477
+ await proxy.adminOnly()
478
+ self.eq(cm.exception.get('mesg'), 'User is not an admin [visi]')
479
+ self.eq(cm.exception.get('user'), visi.iden)
480
+ self.eq(cm.exception.get('username'), visi.name)
481
+ with self.raises(s_exc.AuthDeny) as cm:
482
+ await proxy.adminOnlyLog(1, 2, three=4)
483
+
457
484
  # User cannot get authinfo for other items since they are
458
485
  # not an admin or do not have those roles.
459
486
  await self.asyncraises(s_exc.AuthDeny, proxy.getUserInfo('root'))
@@ -3064,10 +3091,7 @@ class CellTest(s_t_utils.SynTest):
3064
3091
  async with self.getTestCore(conf={'health:sysctl:checks': True}):
3065
3092
  pass
3066
3093
 
3067
- stream.seek(0)
3068
- data = stream.getvalue()
3069
- raw_mesgs = [m for m in data.split('\n') if m]
3070
- msgs = [json.loads(m) for m in raw_mesgs]
3094
+ msgs = stream.jsonlines()
3071
3095
 
3072
3096
  self.len(1, msgs)
3073
3097
 
@@ -1722,10 +1722,8 @@ class HttpApiTest(s_tests.SynTest):
1722
1722
 
1723
1723
  async def test_request_logging(self):
1724
1724
 
1725
- def get_mesg(stream):
1726
- data = stream.getvalue()
1727
- raw_mesgs = [m for m in data.split('\n') if m]
1728
- msgs = [json.loads(m) for m in raw_mesgs]
1725
+ def get_mesg(stream: s_tests.AsyncStreamEvent) -> dict:
1726
+ msgs = stream.jsonlines()
1729
1727
  self.len(1, msgs)
1730
1728
  return msgs[0]
1731
1729
 
@@ -1,5 +1,4 @@
1
1
  import os
2
- import json
3
2
  import asyncio
4
3
  import pathlib
5
4
  import multiprocessing
@@ -15,7 +14,6 @@ import synapse.lib.lmdbslab as s_lmdbslab
15
14
  import synapse.lib.thisplat as s_thisplat
16
15
 
17
16
  import synapse.tests.utils as s_t_utils
18
- from synapse.tests.utils import alist
19
17
 
20
18
  def getFileMapCount(filename):
21
19
  filename = str(filename)
@@ -360,8 +358,7 @@ class LmdbSlabTest(s_t_utils.SynTest):
360
358
  slab.put(b'\xff\xff\xff\xff' + s_common.guid(i).encode('utf8'), byts, db=foo)
361
359
  self.true(await stream.wait(timeout=1))
362
360
 
363
- data = stream.getvalue()
364
- msgs = [json.loads(m) for m in data.split('\\n') if m]
361
+ msgs = stream.jsonlines()
365
362
  self.gt(len(msgs), 0)
366
363
  self.nn(msgs[0].get('delta'))
367
364
  self.nn(msgs[0].get('path'))
@@ -464,6 +464,15 @@ class StormHttpTest(s_test.SynTest):
464
464
  self.isin('mesg', errinfo)
465
465
  self.eq('', errinfo.get('mesg')) # timeouterror has no mesg
466
466
 
467
+ q = '''
468
+ $params=({"foo": ["bar", "baz"], "key": [["valu"]]})
469
+ $resp = $lib.inet.http.request(GET, $url, params=$params, ssl_verify=$lib.false)
470
+ return ( $resp.json() )
471
+ '''
472
+ resp = await core.callStorm(q, opts=opts)
473
+ data = resp.get('result')
474
+ self.eq(data.get('params'), {'foo': ['bar', 'baz'], 'key': ["('valu',)"]})
475
+
467
476
  async def test_storm_http_post(self):
468
477
 
469
478
  async with self.getTestCore() as core:
@@ -304,9 +304,7 @@ $request.reply(206, headers=$headers, body=({"no":"body"}))
304
304
  resp = await sess.get(url)
305
305
  self.eq(resp.status, 200)
306
306
  self.true(await stream.wait(timeout=12))
307
- data = stream.getvalue()
308
- raw_mesgs = [m for m in data.split('\n') if m]
309
- msgs = [json.loads(m) for m in raw_mesgs]
307
+ msgs = stream.jsonlines()
310
308
  self.eq(msgs[0].get('httpapi'), echoiden)
311
309
  core.stormlog = False
312
310
 
@@ -1,5 +1,3 @@
1
- import json
2
-
3
1
  import synapse.exc as s_exc
4
2
 
5
3
  import synapse.tests.utils as s_test
@@ -49,10 +47,7 @@ class LogTest(s_test.SynTest):
49
47
  await core.callStorm('$lib.log.debug("struct1 message")')
50
48
  await core.callStorm('$lib.log.debug("struct2 message", extra=({"key": "valu"}))')
51
49
  self.true(await stream.wait(6))
52
-
53
- data = stream.getvalue()
54
- raw_mesgs = [m for m in data.split('\n') if m]
55
- msgs = [json.loads(m) for m in raw_mesgs]
50
+ msgs = stream.jsonlines()
56
51
  self.len(2, msgs)
57
52
  mesg = msgs[0]
58
53
  self.eq(mesg.get('logger').get('name'), 'synapse.storm.log')
@@ -704,3 +704,31 @@ class StormlibModelTest(s_test.SynTest):
704
704
  self.eq(nodes[0].get('cert'), cert3.ndef[1])
705
705
  self.isin('ssl.migration.three', nodes[0].tags)
706
706
  self.eq(nodes[0].nodedata, {'foo': None})
707
+
708
+ async def test_stormlib_model_migrations_inet_service_message_client(self):
709
+
710
+ async with self.getTestCore() as core:
711
+
712
+ await core.nodes('''[
713
+ (inet:service:message=* :client:address=1.2.3.4 :client=2.3.4.5)
714
+ (inet:service:message=* :client:address=3.4.5.6)
715
+ (inet:service:message=* :client=4.5.6.7)
716
+ ]''')
717
+
718
+ nodes = await core.nodes('''
719
+ inet:service:message
720
+ $lib.model.migration.s.inetServiceMessageClientAddress($node)
721
+ ''')
722
+
723
+ self.len(3, nodes)
724
+
725
+ for node in nodes:
726
+ self.none(node.get('client:address'))
727
+
728
+ exp = ['tcp://2.3.4.5', 'tcp://3.4.5.6', 'tcp://4.5.6.7']
729
+ self.sorteq(exp, [n.get('client') for n in nodes])
730
+
731
+ ndata = [n for n in nodes if await n.getData('migration:inet:service:message:client:address')]
732
+ self.len(1, ndata)
733
+ self.eq(ndata[0].get('client'), 'tcp://2.3.4.5')
734
+ self.eq(await ndata[0].getData('migration:inet:service:message:client:address'), 'tcp://1.2.3.4')
@@ -1665,8 +1665,23 @@ class StormTypesTest(s_test.SynTest):
1665
1665
  self.eq('bar', await core.callStorm('$foo = (foo, bar) return($foo.1)'))
1666
1666
  self.eq('foo', await core.callStorm('$foo = (foo, bar) return($foo."-2")'))
1667
1667
  self.eq('bar', await core.callStorm('$foo = (foo, bar) return($foo.pop())'))
1668
- with self.raises(s_exc.StormRuntimeError):
1668
+
1669
+ self.eq(3, await core.callStorm('$list = ([1, 2, 3, 4]) return($list.pop(2))'))
1670
+ self.eq(2, await core.callStorm('$list = ([1, 2, 3, 4]) return($list.pop(-3))'))
1671
+ self.eq(4, await core.callStorm('$list = ([1, 2, 3, 4]) $list.pop(2) return($list.pop(2))'))
1672
+ self.eq([1, 3, 4], await core.callStorm('$list = ([1, 2, 3, 4]) $list.pop(1) return($list)'))
1673
+
1674
+ with self.raises(s_exc.StormRuntimeError) as exc:
1669
1675
  await core.callStorm('$lib.list().pop()')
1676
+ self.eq(exc.exception.get('mesg'), 'pop from empty list')
1677
+
1678
+ with self.raises(s_exc.StormRuntimeError) as exc:
1679
+ await core.callStorm('$list = ([1, 2, 3, 4]) return($list.pop(13))')
1680
+ self.eq(exc.exception.get('mesg'), 'pop index out of range')
1681
+
1682
+ with self.raises(s_exc.StormRuntimeError) as exc:
1683
+ await core.callStorm('$list = ([1, 2, 3, 4]) return($list.pop(-5))')
1684
+ self.eq(exc.exception.get('mesg'), 'pop index out of range')
1670
1685
 
1671
1686
  somelist = ["foo", "bar", "baz", "bar"]
1672
1687
  q = '''
@@ -4905,8 +4920,7 @@ class StormTypesTest(s_test.SynTest):
4905
4920
  unixtime += 7 * MINSECS
4906
4921
  self.eq('m3', await getNextFoo())
4907
4922
  self.true(await stream.wait(6))
4908
- buf = stream.getvalue()
4909
- mesg = json.loads(buf.split('\n')[0])
4923
+ mesg = stream.jsonlines()[0]
4910
4924
  self.eq(mesg['message'], f'm3 cron {guid}')
4911
4925
  self.eq(mesg['iden'], guid)
4912
4926
 
@@ -1,5 +1,4 @@
1
1
  import os
2
- import json
3
2
  import synapse.exc as s_exc
4
3
  import synapse.common as s_common
5
4
 
@@ -253,8 +252,8 @@ class TrigTest(s_t_utils.SynTest):
253
252
  with self.getStructuredAsyncLoggerStream('synapse.storm.log', 'test trigger') as stream:
254
253
  await core.nodes('[ test:str=logit ]')
255
254
  self.true(await stream.wait(6))
256
- buf = stream.getvalue()
257
- mesg = json.loads(buf.split('\n')[-2])
255
+ msgs = stream.jsonlines()
256
+ mesg = [m for m in msgs if m.get('iden') == tdef.get('iden')][0]
258
257
  self.eq(mesg['message'], f'test trigger {tdef.get("iden")}')
259
258
  self.eq(mesg['iden'], tdef.get('iden'))
260
259
 
@@ -222,13 +222,23 @@ class BaseTest(s_t_utils.SynTest):
222
222
 
223
223
  async with self.getTestCore() as core:
224
224
 
225
- nodes = await core.nodes('[meta:source="*" :name="FOO Bar" :type=osint :url="https://foo.bar/index.html"]')
225
+ nodes = await core.nodes('''
226
+ [meta:source="*"
227
+ :name="FOO Bar"
228
+ :type=osint
229
+ :url="https://foo.bar/index.html"
230
+ :ingest:latest=20241205
231
+ :ingest:offset=17
232
+ ]
233
+ ''')
226
234
  self.len(1, nodes)
227
235
  sorc = nodes[0]
228
236
 
229
237
  self.eq(sorc.get('type'), 'osint')
230
238
  self.eq(sorc.get('name'), 'foo bar')
231
239
  self.eq(sorc.get('url'), 'https://foo.bar/index.html')
240
+ self.eq(sorc.get('ingest:offset'), 17)
241
+ self.eq(sorc.get('ingest:latest'), 1733356800000)
232
242
 
233
243
  valu = (sorc.ndef[1], ('inet:fqdn', 'woot.com'))
234
244
  nodes = await core.nodes('[meta:seen=$valu]', opts={'vars': {'valu': valu}})
@@ -330,7 +340,7 @@ class BaseTest(s_t_utils.SynTest):
330
340
  'tel:mob:telem:ipv6', 'tel:mob:telem:wifi', 'tel:mob:telem:wifi:ssid',
331
341
  'tel:mob:telem:wifi:bssid', 'tel:mob:telem:adid', 'tel:mob:telem:aaid',
332
342
  'tel:mob:telem:idfa', 'tel:mob:telem:name', 'tel:mob:telem:email',
333
- 'tel:mob:telem:acct', 'tel:mob:telem:app', 'tel:mob:telem:data',
343
+ 'tel:mob:telem:app', 'tel:mob:telem:data',
334
344
  'inet:http:request:response:time', 'inet:http:request:response:code',
335
345
  'inet:http:request:response:reason', 'inet:http:request:response:body',
336
346
  'gov:us:cage:street', 'gov:us:cage:city', 'gov:us:cage:state', 'gov:us:cage:zip',
@@ -2958,10 +2958,12 @@ class InetModelTest(s_t_utils.SynTest):
2958
2958
  :platform={ inet:service:platform=(slack,) }
2959
2959
  :url="https://v.vtx.lk/slack"
2960
2960
  :name="Synapse users slack"
2961
+ :tenant={[ inet:service:tenant=({"id": "VS-31337"}) ]}
2961
2962
  ]
2962
2963
  '''
2963
2964
  nodes = await core.nodes(q)
2964
2965
  self.len(1, nodes)
2966
+ self.nn(nodes[0].get('tenant'))
2965
2967
  self.eq(nodes[0].ndef, ('inet:service:instance', s_common.guid(('vertex', 'slack'))))
2966
2968
  self.eq(nodes[0].get('id'), 'T2XK1223Y')
2967
2969
  self.eq(nodes[0].get('platform'), platform.ndef[1])
@@ -2976,6 +2978,7 @@ class InetModelTest(s_t_utils.SynTest):
2976
2978
  :user=blackout
2977
2979
  :email=blackout@vertex.link
2978
2980
  :profile={ gen.ps.contact.email vertex.employee blackout@vertex.link }
2981
+ :tenant={[ inet:service:tenant=({"id": "VS-31337"}) ]}
2979
2982
  )
2980
2983
 
2981
2984
  (inet:service:account=(visi, account, vertex, slack)
@@ -2989,6 +2992,8 @@ class InetModelTest(s_t_utils.SynTest):
2989
2992
  accounts = await core.nodes(q)
2990
2993
  self.len(2, accounts)
2991
2994
 
2995
+ self.nn(accounts[0].get('tenant'))
2996
+
2992
2997
  profiles = await core.nodes('ps:contact')
2993
2998
  self.len(2, profiles)
2994
2999
  self.eq(profiles[0].get('email'), 'blackout@vertex.link')
@@ -3068,14 +3073,17 @@ class InetModelTest(s_t_utils.SynTest):
3068
3073
  [ inet:service:session=*
3069
3074
  :creator=$blckiden
3070
3075
  :period=(202405160900, 202405161055)
3076
+ :http:session=*
3071
3077
  ]
3072
3078
  '''
3073
3079
  opts = {'vars': {'blckiden': blckacct.ndef[1]}}
3074
3080
  nodes = await core.nodes(q, opts=opts)
3075
3081
  self.len(1, nodes)
3082
+ self.nn(nodes[0].get('http:session'))
3076
3083
  self.eq(nodes[0].get('creator'), blckacct.ndef[1])
3077
3084
  self.eq(nodes[0].get('period'), (1715850000000, 1715856900000))
3078
3085
  blcksess = nodes[0]
3086
+ self.len(1, await core.nodes('inet:service:session -> inet:http:session'))
3079
3087
 
3080
3088
  q = '''
3081
3089
  [ inet:service:login=*
@@ -3407,3 +3415,18 @@ class InetModelTest(s_t_utils.SynTest):
3407
3415
 
3408
3416
  with self.raises(s_exc.BadTypeValu):
3409
3417
  await core.nodes('[ inet:service:relationship=* :source={[it:dev:str=foo]} ]')
3418
+
3419
+ nodes = await core.nodes('''
3420
+ [ inet:service:subscription=*
3421
+ :level=vertex.synapse.enterprise
3422
+ :pay:instrument={[ econ:bank:account=* :contact={[ ps:contact=* :name=visi]} ]}
3423
+ :subscriber={[ inet:service:tenant=({"id": "VS-31337"}) ]}
3424
+ ]
3425
+ ''')
3426
+ self.len(1, nodes)
3427
+ self.eq('vertex.synapse.enterprise.', nodes[0].get('level'))
3428
+ self.eq('econ:bank:account', nodes[0].get('pay:instrument')[0])
3429
+ self.eq('inet:service:tenant', nodes[0].get('subscriber')[0])
3430
+ self.len(1, await core.nodes('inet:service:subscription -> inet:service:subscription:level:taxonomy'))
3431
+ self.len(1, await core.nodes('inet:service:subscription :pay:instrument -> econ:bank:account'))
3432
+ self.len(1, await core.nodes('inet:service:subscription :subscriber -> inet:service:tenant'))
@@ -278,12 +278,14 @@ class PsModelTest(s_t_utils.SynTest):
278
278
  :source:host=*
279
279
  :source:file=*
280
280
  :source:acct=(twitter.com, invisig0th)
281
+ :source:account=(twitter.com, invisig0th)
281
282
  ]''')
282
283
  self.len(1, nodes)
283
284
  self.len(1, await core.nodes('ps:contactlist -> it:host'))
284
285
  self.len(1, await core.nodes('ps:contactlist -> file:bytes'))
285
286
  self.len(2, await core.nodes('ps:contactlist -> ps:contact'))
286
287
  self.len(1, await core.nodes('ps:contactlist -> inet:web:acct'))
288
+ self.len(1, await core.nodes('ps:contactlist -> inet:service:account'))
287
289
 
288
290
  nodes = await core.nodes('''[
289
291
  ps:workhist = *
@@ -379,6 +379,7 @@ class RiskModelTest(s_t_utils.SynTest):
379
379
  :desc=VTX-APT1
380
380
  :tag=cno.threat.apt1
381
381
  :active=(2012,2023)
382
+ :activity=high
382
383
  :reporter=*
383
384
  :reporter:name=mandiant
384
385
  :reporter:discovered=202202
@@ -400,6 +401,7 @@ class RiskModelTest(s_t_utils.SynTest):
400
401
  self.len(1, nodes)
401
402
  self.eq('vtx-apt1', nodes[0].get('name'))
402
403
  self.eq('VTX-APT1', nodes[0].get('desc'))
404
+ self.eq(40, nodes[0].get('activity'))
403
405
  self.eq('apt1', nodes[0].get('org:name'))
404
406
  self.eq('ua', nodes[0].get('country:code'))
405
407
  self.eq('cn.shanghai', nodes[0].get('org:loc'))
@@ -546,11 +548,13 @@ class RiskModelTest(s_t_utils.SynTest):
546
548
  :cause=nature.earthquake
547
549
  :provider={[ ou:org=* :name="desert power" ]}
548
550
  :provider:name="desert power"
551
+ :attack={[ risk:attack=* ]}
549
552
  :reporter={ ou:org:name=vertex }
550
553
  :reporter:name=vertex
551
554
  ]
552
555
  ''')
553
556
  self.len(1, nodes)
557
+ self.nn(nodes[0].get('attack'))
554
558
  self.nn(nodes[0].get('reporter'))
555
559
  self.eq('the big one', nodes[0].get('name'))
556
560
  self.eq('vertex', nodes[0].get('reporter:name'))
@@ -559,6 +563,7 @@ class RiskModelTest(s_t_utils.SynTest):
559
563
  self.eq('nature.earthquake.', nodes[0].get('cause'))
560
564
  self.eq((1672531200000, 1704067200000), nodes[0].get('period'))
561
565
 
566
+ self.len(1, await core.nodes('risk:outage -> risk:attack'))
562
567
  self.len(1, await core.nodes('risk:outage -> risk:outage:cause:taxonomy'))
563
568
  self.len(1, await core.nodes('risk:outage :reporter -> ou:org +:name=vertex'))
564
569
  self.len(1, await core.nodes('risk:outage :provider -> ou:org +:name="desert power"'))
@@ -362,6 +362,93 @@ class SynModelTest(s_t_utils.SynTest):
362
362
  nodes = await core.nodes('syn:tagprop')
363
363
  self.len(0, nodes)
364
364
 
365
+ async with self.getTestCore() as core:
366
+ # Check we can iterate runt nodes while changing the underlying dictionary
367
+
368
+ numforms = len(core.model.forms)
369
+
370
+ q = '''
371
+ init {
372
+ $forms = ()
373
+ $count = (0)
374
+ }
375
+
376
+ syn:form
377
+
378
+ $forms.append(({'name': $node.repr(), 'doc': :doc }))
379
+
380
+ $count = ($count + 1)
381
+
382
+ if ($count = (2)) {
383
+ $info = ({"doc": "test taxonomy", "interfaces": ["meta:taxonomy"]})
384
+ $lib.model.ext.addForm(_test:taxonomy, taxonomy, ({}), $info)
385
+ }
386
+
387
+ spin |
388
+
389
+ fini { return($forms) }
390
+ '''
391
+
392
+ forms = await core.callStorm(q)
393
+ self.len(numforms, forms)
394
+ self.len(numforms + 1, core.model.forms)
395
+
396
+ numtypes = len(core.model.types)
397
+ q = '''
398
+ init {
399
+ $types = ()
400
+ $count = (0)
401
+ }
402
+
403
+ syn:type
404
+
405
+ $types.append(({'name': $node.repr(), 'doc': :doc }))
406
+
407
+ $count = ($count + 1)
408
+
409
+ if ($count = (2)) {
410
+ $typeopts = ({"lower": true, "onespace": true})
411
+ $typeinfo = ({"doc": "A test type doc."})
412
+ $lib.model.ext.addType(_test:type, str, $typeopts, $typeinfo)
413
+ }
414
+
415
+ spin |
416
+
417
+ fini { return($types) }
418
+ '''
419
+
420
+ types = await core.callStorm(q)
421
+ self.len(numtypes, types)
422
+ self.len(numtypes + 1, core.model.types)
423
+
424
+ q = '''
425
+ init {
426
+ $tagprops = ()
427
+ $count = (0)
428
+ $lib.model.ext.addTagProp(cypher, (str, ({})), ({}))
429
+ $lib.model.ext.addTagProp(trinity, (str, ({})), ({}))
430
+ $lib.model.ext.addTagProp(morpheus, (str, ({})), ({}))
431
+ }
432
+
433
+ syn:tagprop
434
+
435
+ $tagprops.append(({'name': $node.repr(), 'doc': :doc }))
436
+
437
+ $count = ($count + 1)
438
+
439
+ if ($count = (2)) {
440
+ $lib.model.ext.addTagProp(neo, (str, ({})), ({}))
441
+ }
442
+
443
+ spin |
444
+
445
+ fini { return($tagprops) }
446
+ '''
447
+
448
+ tagprops = await core.callStorm(q)
449
+ self.len(3, tagprops)
450
+ self.len(4, core.model.tagprops)
451
+
365
452
  async def test_syn_trigger_runts(self):
366
453
  async with self.getTestCore() as core:
367
454
  nodes = await core.nodes('syn:trigger')
@@ -465,6 +552,42 @@ class SynModelTest(s_t_utils.SynTest):
465
552
  pode = nodes[0].pack()
466
553
  self.eq(pode[0][1], iden)
467
554
 
555
+ async with self.getTestCore() as core:
556
+ # Check we can iterate runt nodes while changing the underlying dictionary
557
+
558
+ tdef = {'cond': 'node:add', 'form': 'it:dev:str', 'storm': '[inet:user=1] | testcmd'}
559
+ await core.view.addTrigger(tdef)
560
+
561
+ tdef = {'cond': 'node:add', 'form': 'it:dev:str', 'storm': '[inet:user=2] | testcmd'}
562
+ await core.view.addTrigger(tdef)
563
+
564
+ q = '''
565
+ init {
566
+ $trigs = ()
567
+ $count = (0)
568
+ }
569
+
570
+ syn:trigger
571
+
572
+ $trigs.append(({'name': $node.repr(), 'doc': :doc }))
573
+
574
+ $count = ($count + 1)
575
+
576
+ if ($count = (2)) {
577
+ $lib.trigger.add($tdef)
578
+ }
579
+
580
+ spin |
581
+
582
+ fini { return($trigs) }
583
+ '''
584
+
585
+ tdef = {'cond': 'node:add', 'form': 'it:dev:str', 'storm': '[inet:user=3] | testcmd'}
586
+ opts = {'vars': {'tdef': tdef}}
587
+ triggers = await core.callStorm(q, opts=opts)
588
+ self.len(2, triggers)
589
+ self.len(3, core.view.triggers.triggers)
590
+
468
591
  async def test_syn_cmd_runts(self):
469
592
 
470
593
  async with self.getTestDmon() as dmon:
@@ -554,6 +677,49 @@ class SynModelTest(s_t_utils.SynTest):
554
677
  self.eq(nodes[0].get('output'), ('inet:fqdn',))
555
678
  self.eq(nodes[0].get('nodedata'), (('foo', 'inet:ipv4'), ('bar', 'inet:fqdn')))
556
679
 
680
+ async with self.getTestCore() as core:
681
+ # Check we can iterate runt nodes while changing the underlying dictionary
682
+
683
+ numcmds = len(core.stormcmds)
684
+
685
+ stormpkg = {
686
+ 'name': 'stormpkg',
687
+ 'version': '1.2.3',
688
+ 'synapse_version': '>=2.8.0,<3.0.0',
689
+ 'commands': (
690
+ {
691
+ 'name': 'pkgcmd.old',
692
+ 'storm': '$lib.print(hi)',
693
+ },
694
+ ),
695
+ }
696
+
697
+ q = '''
698
+ init {
699
+ $cmds = ()
700
+ $count = (0)
701
+ }
702
+
703
+ syn:cmd
704
+
705
+ $cmds.append(({'name': $node.repr(), 'doc': :doc }))
706
+
707
+ $count = ($count + 1)
708
+
709
+ if ($count = (2)) {
710
+ $lib.pkg.add($pkgdef)
711
+ }
712
+
713
+ spin |
714
+
715
+ fini { return($cmds) }
716
+ '''
717
+
718
+ opts = {'vars': {'pkgdef': stormpkg}}
719
+ cmds = await core.callStorm(q, opts=opts)
720
+ self.len(numcmds, cmds)
721
+ self.len(numcmds + 1, core.stormcmds)
722
+
557
723
  async def test_syn_cron_runts(self):
558
724
 
559
725
  async with self.getTestCore() as core:
@@ -582,3 +748,35 @@ class SynModelTest(s_t_utils.SynTest):
582
748
  self.eq(nodes[0].ndef, ('syn:cron', iden))
583
749
  self.eq(nodes[0].get('doc'), 'hehe')
584
750
  self.eq(nodes[0].get('name'), 'haha')
751
+
752
+ async with self.getTestCore() as core:
753
+ # Check we can iterate runt nodes while changing the underlying dictionary
754
+
755
+ q = '''
756
+ init {
757
+ $appts = ()
758
+ $count = (0)
759
+
760
+ cron.add --hour 1 --day 1 {#foo} |
761
+ cron.add --hour 2 --day 1 {#foo} |
762
+ cron.add --hour 3 --day 1 {#foo}
763
+ }
764
+
765
+ syn:cron
766
+
767
+ $appts.append(({'name': $node.repr(), 'doc': :doc }))
768
+
769
+ $count = ($count + 1)
770
+
771
+ if ($count = (2)) {
772
+ cron.add --hour 4 --day 1 {#foo}
773
+ }
774
+
775
+ spin |
776
+
777
+ fini { return($appts) }
778
+ '''
779
+
780
+ appts = await core.callStorm(q)
781
+ self.len(3, appts)
782
+ self.len(4, core.agenda.appts)
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  import time
3
+ import json
3
4
  import logging
4
5
  import unittest
5
6
 
@@ -109,13 +110,13 @@ class TestUtils(s_t_utils.SynTest):
109
110
  def test_syntest_logstream_event(self):
110
111
 
111
112
  @s_common.firethread
112
- def logathing():
113
+ def logathing(mesg):
113
114
  time.sleep(0.01)
114
- logger.error('StreamEvent Test Message')
115
+ logger.error(mesg)
115
116
 
116
117
  logger.error('notthere')
117
118
  with self.getLoggerStream('synapse.tests.test_utils', 'Test Message') as stream:
118
- thr = logathing()
119
+ thr = logathing('StreamEvent Test Message')
119
120
  self.true(stream.wait(10))
120
121
  thr.join()
121
122
 
@@ -124,16 +125,34 @@ class TestUtils(s_t_utils.SynTest):
124
125
  self.isin('StreamEvent Test Message', mesgs)
125
126
  self.notin('notthere', mesgs)
126
127
 
128
+ with self.getLoggerStream('synapse.tests.test_utils', 'Test Message') as stream:
129
+ thr = logathing(json.dumps({'mesg': 'Test Message'}))
130
+ self.true(stream.wait(10))
131
+ thr.join()
132
+
133
+ msgs = stream.jsonlines()
134
+ self.len(1, msgs)
135
+ self.eq(msgs[0], {'mesg': 'Test Message'})
136
+
127
137
  def test_syntest_envars(self):
128
138
  os.environ['foo'] = '1'
129
139
  os.environ['bar'] = '2'
130
140
 
131
- with self.setTstEnvars(foo=1, bar='joke', baz=1234) as cm:
141
+ with self.setTstEnvars(foo=1, bar='joke', baz=1234, FOO_THING=1, BAR_THING=0) as cm:
132
142
  self.none(cm)
133
143
  self.eq(os.environ.get('foo'), '1')
134
144
  self.eq(os.environ.get('bar'), 'joke')
135
145
  self.eq(os.environ.get('baz'), '1234')
136
146
 
147
+ self.thisEnvMust('FOO_THING', 'baz')
148
+ self.thisEnvMustNot('BAR_THING', 'NEWP_THING')
149
+ with self.raises(unittest.SkipTest):
150
+ self.thisEnvMust('MEWP_THING')
151
+ with self.raises(unittest.SkipTest):
152
+ self.thisEnvMust('BAR_THING')
153
+ with self.raises(unittest.SkipTest):
154
+ self.thisEnvMustNot('FOO_THING')
155
+
137
156
  self.eq(os.environ.get('foo'), '1')
138
157
  self.eq(os.environ.get('bar'), '2')
139
158
  self.none(os.environ.get('baz'))