synapse 2.208.0__py311-none-any.whl → 2.210.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 (60) hide show
  1. synapse/lib/cell.py +14 -4
  2. synapse/lib/coro.py +5 -0
  3. synapse/lib/json.py +48 -22
  4. synapse/lib/layer.py +23 -1
  5. synapse/lib/nexus.py +6 -0
  6. synapse/lib/rstorm.py +3 -1
  7. synapse/lib/storm.py +10 -2
  8. synapse/lib/stormlib/auth.py +6 -0
  9. synapse/lib/stormlib/notifications.py +12 -2
  10. synapse/lib/stormtypes.py +8 -0
  11. synapse/lib/version.py +2 -2
  12. synapse/lib/view.py +19 -6
  13. synapse/models/entity.py +26 -0
  14. synapse/models/inet.py +11 -0
  15. synapse/models/person.py +9 -2
  16. synapse/tests/test_cortex.py +3 -3
  17. synapse/tests/test_lib_aha.py +1 -1
  18. synapse/tests/test_lib_cell.py +28 -8
  19. synapse/tests/test_lib_coro.py +23 -0
  20. synapse/tests/test_lib_json.py +41 -16
  21. synapse/tests/test_lib_rstorm.py +4 -3
  22. synapse/tests/test_lib_stormtypes.py +135 -3
  23. synapse/tests/test_lib_view.py +22 -0
  24. synapse/tests/test_model_entity.py +21 -0
  25. synapse/tests/test_model_gov_intl.py +2 -2
  26. synapse/tests/test_model_inet.py +15 -0
  27. synapse/tests/test_model_media.py +1 -0
  28. synapse/tests/test_model_person.py +12 -0
  29. synapse/tests/test_telepath.py +52 -41
  30. synapse/tests/test_tools_aha.py +7 -8
  31. synapse/tests/utils.py +1 -1
  32. synapse/tools/aha/clone.py +7 -1
  33. synapse/tools/aha/easycert.py +37 -42
  34. synapse/tools/aha/enroll.py +7 -1
  35. synapse/tools/aha/list.py +60 -65
  36. synapse/tools/aha/mirror.py +7 -1
  37. synapse/tools/aha/provision/service.py +7 -1
  38. synapse/tools/aha/provision/user.py +7 -1
  39. synapse/tools/apikey.py +8 -1
  40. synapse/tools/axon2axon.py +7 -1
  41. synapse/tools/cellauth.py +6 -5
  42. synapse/tools/cmdr.py +2 -1
  43. synapse/tools/csvtool.py +7 -2
  44. synapse/tools/feed.py +8 -2
  45. synapse/tools/genpkg.py +7 -1
  46. synapse/tools/healthcheck.py +7 -1
  47. synapse/tools/livebackup.py +7 -3
  48. synapse/tools/modrole.py +7 -1
  49. synapse/tools/moduser.py +7 -2
  50. synapse/tools/promote.py +7 -3
  51. synapse/tools/pullfile.py +6 -1
  52. synapse/tools/pushfile.py +7 -1
  53. synapse/tools/reload.py +7 -4
  54. synapse/tools/snapshot.py +7 -1
  55. synapse/tools/storm.py +7 -1
  56. {synapse-2.208.0.dist-info → synapse-2.210.0.dist-info}/METADATA +2 -2
  57. {synapse-2.208.0.dist-info → synapse-2.210.0.dist-info}/RECORD +60 -59
  58. {synapse-2.208.0.dist-info → synapse-2.210.0.dist-info}/WHEEL +1 -1
  59. {synapse-2.208.0.dist-info → synapse-2.210.0.dist-info}/licenses/LICENSE +0 -0
  60. {synapse-2.208.0.dist-info → synapse-2.210.0.dist-info}/top_level.txt +0 -0
@@ -2238,7 +2238,7 @@ class CellTest(s_t_utils.SynTest):
2238
2238
  self.false(core00.isactive)
2239
2239
 
2240
2240
  modinfo = s_common.yamlload(core00.dirn, 'cell.mods.yaml')
2241
- self.isin('01.core', modinfo.get('mirror', ''))
2241
+ self.eq('aha://root@core...', modinfo.get('mirror', ''))
2242
2242
  modinfo = s_common.yamlload(core01.dirn, 'cell.mods.yaml')
2243
2243
  self.none(modinfo.get('mirror'))
2244
2244
 
@@ -2321,7 +2321,7 @@ class CellTest(s_t_utils.SynTest):
2321
2321
  self.false(core00.isactive)
2322
2322
 
2323
2323
  modinfo = s_common.yamlload(core00.dirn, 'cell.mods.yaml')
2324
- self.isin('01.core', modinfo.get('mirror', ''))
2324
+ self.eq('aha://root@core...', modinfo.get('mirror', ''))
2325
2325
  modinfo = s_common.yamlload(core01.dirn, 'cell.mods.yaml')
2326
2326
  self.none(modinfo.get('mirror'))
2327
2327
 
@@ -2333,7 +2333,7 @@ class CellTest(s_t_utils.SynTest):
2333
2333
  modinfo = s_common.yamlload(core00.dirn, 'cell.mods.yaml')
2334
2334
  self.none(modinfo.get('mirror'))
2335
2335
  modinfo = s_common.yamlload(core01.dirn, 'cell.mods.yaml')
2336
- self.isin('00.core', modinfo.get('mirror', ''))
2336
+ self.eq('aha://root@core...', modinfo.get('mirror', ''))
2337
2337
 
2338
2338
  # Backup the mirror (core01) which points to the core00
2339
2339
  async with await axon00.upload() as upfd:
@@ -3285,6 +3285,7 @@ class CellTest(s_t_utils.SynTest):
3285
3285
  dirn00 = s_common.genpath(dirn, '00.cell')
3286
3286
  dirn01 = s_common.genpath(dirn, '01.cell')
3287
3287
  dirn02 = s_common.genpath(dirn, '02.cell')
3288
+ dirn0002 = s_common.genpath(dirn, '00.02.cell')
3288
3289
 
3289
3290
  cell00 = await base.enter_context(self.addSvcToAha(aha, '00.cell', s_cell.Cell, dirn=dirn00))
3290
3291
  cell01 = await base.enter_context(self.addSvcToAha(aha, '01.cell', s_cell.Cell, dirn=dirn01,
@@ -3301,24 +3302,43 @@ class CellTest(s_t_utils.SynTest):
3301
3302
  await cell01.handoff('some://url')
3302
3303
  self.isin('01.cell is not the current leader', cm.exception.get('mesg'))
3303
3304
 
3304
- # Note: The following behavior may change when SYN-7659 is addressed and greater
3305
- # control over the topology update is available during the promotion process.
3306
- # Promote 02.cell -> Promote 01.cell -> Promote 00.cell -> BadState exception
3305
+ # Promote 02.cell -> Promote 01.cell -> Promote 00.cell, without breaking the configured topology
3307
3306
  await cell02.promote(graceful=True)
3308
3307
  self.false(cell00.isactive)
3308
+ self.eq(cell00.conf.get('mirror'), 'aha://root@cell...')
3309
3309
  self.false(cell01.isactive)
3310
+ self.eq(cell01.conf.get('mirror'), 'aha://root@cell...')
3310
3311
  self.true(cell02.isactive)
3312
+ self.none(cell02.conf.get('mirror'))
3311
3313
  await cell02.sync()
3312
3314
 
3313
3315
  await cell01.promote(graceful=True)
3314
3316
  self.false(cell00.isactive)
3317
+ self.eq(cell00.conf.get('mirror'), 'aha://root@cell...')
3315
3318
  self.true(cell01.isactive)
3319
+ self.none(cell01.conf.get('mirror'))
3316
3320
  self.false(cell02.isactive)
3321
+ self.eq(cell02.conf.get('mirror'), 'aha://root@cell...')
3317
3322
  await cell02.sync()
3318
3323
 
3324
+ await cell00.promote(graceful=True)
3325
+ self.true(cell00.isactive)
3326
+ self.none(cell00.conf.get('mirror'))
3327
+ self.false(cell01.isactive)
3328
+ self.eq(cell01.conf.get('mirror'), 'aha://root@cell...')
3329
+ self.false(cell02.isactive)
3330
+ self.eq(cell02.conf.get('mirror'), 'aha://root@cell...')
3331
+ await cell02.sync()
3332
+
3333
+ # A follower of a follower cannot be promoted up since its leader is not the active cell.
3334
+ cell0002 = await base.enter_context(self.addSvcToAha(aha, '00.02.cell', s_cell.Cell, dirn=dirn0002,
3335
+ provinfo={'mirror': '02.cell'}))
3336
+ self.false(cell0002.isactive)
3337
+ self.eq(cell0002.conf.get('mirror'), 'aha://root@02.cell...')
3319
3338
  with self.raises(s_exc.BadState) as cm:
3320
- await cell00.promote(graceful=True)
3321
- self.isin('02.cell is not the current leader', cm.exception.get('mesg'))
3339
+ await cell0002.promote(graceful=True)
3340
+ mesg = 'ahaname=02.cell is not the current leader and cannot handoff leadership to aha://00.02.cell.synapse.'
3341
+ self.isin(mesg, cm.exception.get('mesg'))
3322
3342
 
3323
3343
  async def test_cell_get_aha_proxy(self):
3324
3344
 
@@ -212,3 +212,26 @@ class CoroTest(s_t_utils.SynTest):
212
212
 
213
213
  with self.raises(Exception):
214
214
  await s_coro._parserforked(newp)
215
+
216
+ async def test_lib_coro_create_task(self):
217
+
218
+ async def sleep(n):
219
+ await asyncio.sleep(n)
220
+ if n == 0:
221
+ return 1 / 0
222
+ return n
223
+
224
+ s_coro.create_task(sleep(0.1))
225
+ s_coro.create_task(sleep(0.15))
226
+ s_coro.create_task(sleep(0.2))
227
+ self.len(3, s_coro.bgtasks)
228
+ results = await s_coro.await_bg_tasks()
229
+ self.eq(set(results), {0.1, 0.15, 0.2})
230
+ self.len(0, s_coro.bgtasks)
231
+ results = await s_coro.await_bg_tasks()
232
+ self.eq(results, [])
233
+
234
+ s_coro.create_task(sleep(0))
235
+ results = await s_coro.await_bg_tasks()
236
+ self.len(1, results)
237
+ self.isinstance(results[0], ZeroDivisionError)
@@ -1,7 +1,7 @@
1
1
  import io
2
2
  import json
3
3
 
4
- import orjson
4
+ import yyjson
5
5
 
6
6
  import synapse.exc as s_exc
7
7
  import synapse.common as s_common
@@ -44,9 +44,9 @@ class JsonTest(s_test.SynTest):
44
44
  inval = '{"a": "😀\ud83d\ude47"}'
45
45
  outval = {'a': '😀\ud83d\ude47'}
46
46
 
47
- # orjson.dumps fails because of the surrogate pairs
48
- with self.raises(orjson.JSONDecodeError):
49
- orjson.loads(inval)
47
+ # yyjson.loads fails because of the surrogate pairs
48
+ with self.raises(ValueError):
49
+ yyjson.loads(inval)
50
50
 
51
51
  # stdlib json.loads passes because of voodoo magic
52
52
  self.eq(outval, json.loads(inval))
@@ -63,9 +63,9 @@ class JsonTest(s_test.SynTest):
63
63
  inval = {'a': '😀\ud83d\ude47'}
64
64
  outval = b'{"a": "\\ud83d\\ude00\\ud83d\\ude47"}'
65
65
 
66
- # orjson.dumps fails because of the surrogate pairs
67
- with self.raises(TypeError):
68
- orjson.dumps(inval)
66
+ # yyjson.dumps fails because of the surrogate pairs
67
+ with self.raises(UnicodeEncodeError):
68
+ yyjson.dumps(inval)
69
69
 
70
70
  # stdlib json.dumps passes because of voodoo magic
71
71
  self.eq(outval.decode(), json.dumps(inval))
@@ -85,15 +85,15 @@ class JsonTest(s_test.SynTest):
85
85
 
86
86
  with self.raises(s_exc.MustBeJsonSafe) as exc:
87
87
  s_json.dumps({}.items())
88
- self.eq(exc.exception.get('mesg'), 'Type is not JSON serializable: dict_items')
88
+ self.eq(exc.exception.get('mesg'), "TypeError: Object of type 'dict_items' is not JSON serializable")
89
89
 
90
90
  with self.raises(s_exc.MustBeJsonSafe) as exc:
91
91
  s_json.dumps({1: 'foo'})
92
- self.eq(exc.exception.get('mesg'), 'Dict key must be str')
92
+ self.eq(exc.exception.get('mesg'), "TypeError: Dictionary keys must be strings")
93
93
 
94
94
  with self.raises(s_exc.MustBeJsonSafe) as exc:
95
95
  s_json.dumps({'\ud83d\ude47': {}.items()})
96
- self.eq(exc.exception.get('mesg'), 'Object of type dict_items is not JSON serializable')
96
+ self.eq(exc.exception.get('mesg'), "TypeError: Dictionary keys must be strings")
97
97
 
98
98
  self.eq(b'"dict_items([])"', s_json.dumps({}.items(), default=str))
99
99
 
@@ -111,6 +111,20 @@ class JsonTest(s_test.SynTest):
111
111
  s_json.dump({'c': 'd', 'a': 'b'}, buf)
112
112
  self.eq(b'{"c":"d","a":"b"}', buf.getvalue())
113
113
 
114
+ async def test_lib_json_large_integers(self):
115
+ valu = [
116
+ 1, 2,
117
+ -1, -2,
118
+ 1.0, 2.0,
119
+ -1.0, -2.0,
120
+ 2**63, -2**63, -2**63 - 1,
121
+ 2**64, -2**64, 2**64 + 1,
122
+ 2**128, 2**128 + 1,
123
+ -2**128, -2**128 - 1,
124
+ ]
125
+
126
+ self.eq(valu, s_json.loads(s_json.dumps(valu)))
127
+
114
128
  async def test_jsload(self):
115
129
  with self.getTestDir() as dirn:
116
130
  with s_common.genfile(dirn, 'jsload.json') as fp:
@@ -147,41 +161,52 @@ class JsonTest(s_test.SynTest):
147
161
 
148
162
  buf = io.BytesIO()
149
163
 
164
+ msg = "Object of type '_io.BytesIO' is not JSON serializable"
150
165
  with self.raises(s_exc.MustBeJsonSafe) as exc:
151
166
  s_json.reqjsonsafe(buf)
152
- self.isin('Type is not JSON serializable: _io.BytesIO', exc.exception.get('mesg'))
167
+ self.isin(msg, exc.exception.get('mesg'))
153
168
 
154
169
  with self.raises(s_exc.MustBeJsonSafe) as exc:
155
170
  s_json.reqjsonsafe({'foo': buf})
156
- self.isin('Type is not JSON serializable: _io.BytesIO', exc.exception.get('mesg'))
171
+ self.isin(msg, exc.exception.get('mesg'))
157
172
 
158
173
  with self.raises(s_exc.MustBeJsonSafe) as exc:
159
174
  s_json.reqjsonsafe(['foo', buf])
160
- self.isin('Type is not JSON serializable: _io.BytesIO', exc.exception.get('mesg'))
175
+ self.isin(msg, exc.exception.get('mesg'))
161
176
 
162
177
  items = (
163
178
  (None, None),
164
179
  (1234, None),
165
180
  ('1234', None),
181
+ ('1234"', None),
166
182
  ({'asdf': 'haha'}, None),
167
183
  ({'a': (1,), 'b': [{'': 4}, 56, None, {'t': True, 'f': False}, 'oh my']}, None),
168
184
  (b'1234', s_exc.MustBeJsonSafe),
185
+ (b'1234"', s_exc.MustBeJsonSafe),
186
+ ({'a': float('nan')}, s_exc.MustBeJsonSafe),
169
187
  ({'a': 'a', 2: 2}, s_exc.MustBeJsonSafe),
170
188
  ({'a', 'b', 'c'}, s_exc.MustBeJsonSafe),
171
189
  (s_common.novalu, s_exc.MustBeJsonSafe),
172
190
  )
173
191
  for (item, eret) in items:
174
192
  if eret is None:
175
- self.none(s_json.reqjsonsafe(item))
193
+ self.none(s_json.reqjsonsafe(item), msg=item)
176
194
  else:
177
195
  with self.raises(eret):
178
196
  s_json.reqjsonsafe(item)
179
197
 
180
- text = '😀\ud83d\ude47'
198
+ for text in ['😀', 'asdf', 'asdf"', '"asdf']:
199
+ s_json.reqjsonsafe(text)
200
+
201
+ text = ['😀\ud83d\ude47']
181
202
  s_json.reqjsonsafe(text)
182
203
  with self.raises(s_exc.MustBeJsonSafe) as exc:
183
204
  s_json.reqjsonsafe(text, strict=True)
184
- self.eq(exc.exception.get('mesg'), 'str is not valid UTF-8: surrogates not allowed')
205
+ self.eq(exc.exception.get('mesg'), "'utf-8' codec can't encode characters in position 1-2: surrogates not allowed")
206
+
207
+ with self.raises(s_exc.MustBeJsonSafe) as exc:
208
+ s_json.reqjsonsafe(b'1234', strict=True)
209
+ self.eq(exc.exception.get('mesg'), 'Object of type bytes is not JSON serializable')
185
210
 
186
211
  async def test_lib_json_data_at_rest(self):
187
212
  async with self.getRegrCore('json-data') as core:
@@ -119,7 +119,7 @@ clear_storm_opts = '''
119
119
  .. storm-cortex:: default
120
120
  .. storm-opts:: {"vars": {"foobar": "bar"}}
121
121
  .. storm-clear-http:: true
122
- .. storm:: $lib.print($lib.str.concat($foobar, "bizboz"))
122
+ .. storm:: $lib.print(`{$foobar}bizboz`)
123
123
  '''
124
124
 
125
125
  stormenv = '''
@@ -381,8 +381,9 @@ class RStormLibTest(s_test.SynTest):
381
381
  path = s_common.genpath(dirn, 'clear_storm_opts.rst')
382
382
  with s_common.genfile(path) as fd:
383
383
  fd.write(clear_storm_opts.encode())
384
- with self.raises(s_exc.StormRuntimeError):
385
- text = await get_rst_text(path)
384
+ with self.raises(s_exc.StormRuntimeError) as cm:
385
+ await get_rst_text(path)
386
+ self.eq('Missing variable: foobar', cm.exception.get('mesg'))
386
387
 
387
388
  # boom1 test
388
389
  path = s_common.genpath(dirn, 'boom1.rst')
@@ -3802,7 +3802,6 @@ class StormTypesTest(s_test.SynTest):
3802
3802
  async with self.getTestCore() as core:
3803
3803
  async with self.getTestCore() as core2:
3804
3804
 
3805
- await core2.nodes('[ inet:ipv4=1.2.3.4 ]')
3806
3805
  url = core2.getLocalUrl('*/layer')
3807
3806
 
3808
3807
  layriden = core2.view.layers[0].iden
@@ -3819,8 +3818,141 @@ class StormTypesTest(s_test.SynTest):
3819
3818
 
3820
3819
  layr = core.getLayer(uplayr)
3821
3820
 
3822
- evnt = await layr.waitUpstreamOffs(layriden, offs)
3823
- self.true(await asyncio.wait_for(evnt.wait(), timeout=6))
3821
+ async def query(q):
3822
+ '''
3823
+ Run a query on core2 and wait for it to sync to layr from core
3824
+ '''
3825
+ nodes = await core2.nodes(q)
3826
+ offs = await core2.view.layers[0].getEditIndx()
3827
+ evnt = await layr.waitUpstreamOffs(layriden, offs)
3828
+ self.true(await asyncio.wait_for(evnt.wait(), timeout=6))
3829
+ return nodes
3830
+
3831
+ vdef = {
3832
+ 'layers': [layr.iden]
3833
+ }
3834
+
3835
+ view00 = await core.addView(vdef)
3836
+ self.nn(view00)
3837
+
3838
+ # No foobar in core
3839
+ opts = {'view': view00.get('iden')}
3840
+ nodes = await core.nodes('it:dev:str=foobar', opts=opts)
3841
+ self.len(0, nodes)
3842
+
3843
+ # Add foobar in core2
3844
+ nodes = await query('[ it:dev:str=foobar ]')
3845
+ self.len(1, nodes)
3846
+
3847
+ # foobar shows up in core
3848
+ nodes = await core.nodes('it:dev:str=foobar', opts=opts)
3849
+ self.len(1, nodes)
3850
+
3851
+ self.len(1, layr.activetasks)
3852
+
3853
+ # The upstream key only accepts null
3854
+ q = f'layer.set {uplayr} upstream (true)'
3855
+ msgs = await core.stormlist(q)
3856
+ self.stormIsInErr('Layer only supports setting "mirror" and "upstream" to null.', msgs)
3857
+
3858
+ with self.raises(s_exc.BadOptValu) as exc:
3859
+ await layr.setLayerInfo('upstream', False)
3860
+ self.eq(exc.exception.get('mesg'), 'Layer only supports setting "mirror" and "upstream" to None.', msgs)
3861
+
3862
+ # Now remove the upstream configuration
3863
+ q = f'layer.set {uplayr} upstream (null)'
3864
+ msgs = await core.stormlist(q)
3865
+ self.stormHasNoWarnErr(msgs)
3866
+
3867
+ layr = core.getLayer(uplayr)
3868
+ self.len(0, layr.activetasks)
3869
+ self.none(layr.layrinfo.get('upstream'))
3870
+
3871
+ with self.raises(TimeoutError):
3872
+ await query('[ it:dev:str=newp ]')
3873
+
3874
+ # No newp in core because layer upstream is disabled
3875
+ nodes = await core.nodes('it:dev:str=newp', opts=opts)
3876
+ self.len(0, nodes)
3877
+
3878
+ async def test_storm_lib_layer_mirror(self):
3879
+ async with self.getTestCore() as core:
3880
+ async with self.getTestCore() as core2:
3881
+
3882
+ url = core2.getLocalUrl('*/layer')
3883
+
3884
+ layers = set(core.layers.keys())
3885
+ q = f'layer.add --mirror {url}'
3886
+ mesgs = await core.stormlist(q)
3887
+ uplayr = list(set(core.layers.keys()) - layers)[0]
3888
+
3889
+ q = f'layer.set {uplayr} name "woot woot"'
3890
+ mesgs = await core.stormlist(q)
3891
+ self.stormIsInPrint('(name: woot woot)', mesgs)
3892
+
3893
+ layr = core.getLayer(uplayr)
3894
+
3895
+ async def query(q):
3896
+ '''
3897
+ Run a query on core2 and wait for it to sync to layr from core
3898
+ '''
3899
+ nodes = await core2.nodes(q)
3900
+ offs = await core2.view.layers[0].getEditOffs()
3901
+ self.true(await layr.waitEditOffs(offs, timeout=10))
3902
+ return nodes
3903
+
3904
+ vdef = {
3905
+ 'layers': [layr.iden]
3906
+ }
3907
+
3908
+ view00 = await core.addView(vdef)
3909
+ self.nn(view00)
3910
+
3911
+ # No foobar in core
3912
+ opts = {'view': view00.get('iden')}
3913
+ nodes = await core.nodes('it:dev:str=foobar', opts=opts)
3914
+ self.len(0, nodes)
3915
+
3916
+ # Add foobar in core2
3917
+ nodes = await query('[ it:dev:str=foobar ]')
3918
+ self.len(1, nodes)
3919
+
3920
+ # foobar shows up in core
3921
+ nodes = await core.nodes('it:dev:str=foobar', opts=opts)
3922
+ self.len(1, nodes)
3923
+
3924
+ self.true(layr.ismirror)
3925
+ self.nn(layr.leadtask)
3926
+ self.nn(layr.leader)
3927
+ self.len(0, layr.activetasks)
3928
+
3929
+ # The mirror key only accepts null
3930
+ q = f'layer.set {uplayr} mirror (true)'
3931
+ msgs = await core.stormlist(q)
3932
+ self.stormIsInErr('Layer only supports setting "mirror" and "upstream" to null.', msgs)
3933
+
3934
+ with self.raises(s_exc.BadOptValu) as exc:
3935
+ await layr.setLayerInfo('mirror', False)
3936
+ self.eq(exc.exception.get('mesg'), 'Layer only supports setting "mirror" and "upstream" to None.', msgs)
3937
+
3938
+ # Now remove the mirror configuration
3939
+ q = f'layer.set {uplayr} mirror (null)'
3940
+ msgs = await core.stormlist(q)
3941
+ self.stormHasNoWarnErr(msgs)
3942
+
3943
+ layr = core.getLayer(uplayr)
3944
+ self.none(layr.layrinfo.get('mirror'))
3945
+ self.none(layr.leadtask)
3946
+ self.none(layr.leader)
3947
+ self.false(layr.ismirror)
3948
+
3949
+ # Add newp in core2
3950
+ nodes = await query('[ it:dev:str=newp ]')
3951
+ self.len(1, nodes)
3952
+
3953
+ # No newp in core because layer mirroring is disabled
3954
+ nodes = await core.nodes('it:dev:str=newp', opts=opts)
3955
+ self.len(0, nodes)
3824
3956
 
3825
3957
  async def test_storm_lib_view(self):
3826
3958
 
@@ -310,6 +310,28 @@ class ViewTest(s_t_utils.SynTest):
310
310
  # But not the same layer twice
311
311
  await self.asyncraises(s_exc.DupIden, core.view.addLayer(layriden))
312
312
 
313
+ # Nodes with a large number of edge edits may be chunked by iterLayerNodeEdits
314
+ await core.nodes('[ test:str=foo ] for $i in $lib.range(102) {[ test:int=$i ]}')
315
+
316
+ vdef2 = await core.view.fork()
317
+ opts = {'view': vdef2['iden']}
318
+ await core.nodes('[ test:str=foo +(refs)> { for $i in $lib.range(102) { test:int=$i } } ]', opts=opts)
319
+
320
+ strt = core.nexsroot.nexslog.index()
321
+ await core.nodes('$lib.view.get().merge()', opts=opts)
322
+
323
+ self.len(102, await core.nodes('test:str=foo -(refs)> test:int'))
324
+
325
+ edits = [edit async for edit in core.nexsroot.nexslog.iter(strt)]
326
+ self.len(1, edits)
327
+
328
+ nodeedit = edits[0][1][2][0]
329
+
330
+ # We should have two chunks of edits for the same buid due to the number of edges
331
+ self.eq(nodeedit[0][0], nodeedit[1][0])
332
+ self.len(100, nodeedit[0][2])
333
+ self.len(2, nodeedit[1][2])
334
+
313
335
  async def test_view_merge_ival(self):
314
336
 
315
337
  async with self.getTestCore() as core:
@@ -0,0 +1,21 @@
1
+ import synapse.tests.utils as s_test
2
+
3
+ class EntityModelTest(s_test.SynTest):
4
+
5
+ async def test_entity_relationship(self):
6
+
7
+ async with self.getTestCore() as core:
8
+
9
+ nodes = await core.nodes('''[
10
+ entity:relationship=*
11
+ :type=tasks
12
+ :period=(2022, ?)
13
+ :source={[ ou:org=({"name": "China Ministry of State Security (MSS)"}) ]}
14
+ :target={[ risk:threat=({"org:name": "APT34", "reporter:name": "vertex"}) ]}
15
+ ]''')
16
+
17
+ self.len(1, nodes)
18
+ self.eq(nodes[0].get('type'), 'tasks.')
19
+ self.eq(nodes[0].get('period'), (1640995200000, 9223372036854775807))
20
+ self.eq(nodes[0].get('source'), ('ou:org', '3332a704ed21dc3274d5731acc54a0ee'))
21
+ self.eq(nodes[0].get('target'), ('risk:threat', 'e15738ebae52273300b51c08eaad3a36'))
@@ -1,6 +1,6 @@
1
- from synapse.tests.utils import SynTest
1
+ import synapse.tests.utils as s_t_utils
2
2
 
3
- class IntlGovTest(SynTest):
3
+ class IntlGovTest(s_t_utils.SynTest):
4
4
 
5
5
  async def test_models_intl(self):
6
6
 
@@ -3164,6 +3164,7 @@ class InetModelTest(s_t_utils.SynTest):
3164
3164
  :creator=$visiiden
3165
3165
  :platform=$platiden
3166
3166
  :instance=$instiden
3167
+ :topic=' My Topic '
3167
3168
  ]
3168
3169
  '''
3169
3170
  opts = {'vars': {
@@ -3175,6 +3176,7 @@ class InetModelTest(s_t_utils.SynTest):
3175
3176
  self.len(1, nodes)
3176
3177
  self.eq(nodes[0].ndef, ('inet:service:channel', s_common.guid(('general', 'channel', 'vertex', 'slack'))))
3177
3178
  self.eq(nodes[0].get('name'), 'general')
3179
+ self.eq(nodes[0].get('topic'), 'my topic')
3178
3180
  self.eq(nodes[0].get('period'), (1420070400000, 9223372036854775807))
3179
3181
  self.eq(nodes[0].get('creator'), visiacct.ndef[1])
3180
3182
  self.eq(nodes[0].get('platform'), platform.ndef[1])
@@ -3243,12 +3245,18 @@ class InetModelTest(s_t_utils.SynTest):
3243
3245
  :group=$devsiden
3244
3246
  :public=$lib.false
3245
3247
  :repost=*
3248
+ :mentions=(
3249
+ (inet:service:group, $devsiden),
3250
+ (inet:service:account, $blckiden),
3251
+ (inet:service:account, $blckiden),
3252
+ )
3246
3253
  )
3247
3254
 
3248
3255
  (inet:service:message=(blackout, visi, 1715856900000, vertex, slack)
3249
3256
  :type=chat.direct
3250
3257
  :to=$visiiden
3251
3258
  :public=$lib.false
3259
+ :mentions?=((inet:service:message:attachment, $atchiden),)
3252
3260
  )
3253
3261
 
3254
3262
  (inet:service:message=(blackout, general, 1715856900000, vertex, slack)
@@ -3296,10 +3304,15 @@ class InetModelTest(s_t_utils.SynTest):
3296
3304
  self.eq(nodes[0].get('group'), devsgrp.ndef[1])
3297
3305
  self.false(nodes[0].get('public'))
3298
3306
  self.eq(nodes[0].get('type'), 'chat.group.')
3307
+ self.eq(
3308
+ nodes[0].get('mentions'),
3309
+ (('inet:service:account', blckacct.ndef[1]), ('inet:service:group', devsgrp.ndef[1]))
3310
+ )
3299
3311
 
3300
3312
  self.eq(nodes[1].get('to'), visiacct.ndef[1])
3301
3313
  self.false(nodes[1].get('public'))
3302
3314
  self.eq(nodes[1].get('type'), 'chat.direct.')
3315
+ self.none(nodes[1].get('mentions'))
3303
3316
 
3304
3317
  self.eq(nodes[2].get('channel'), gnrlchan.ndef[1])
3305
3318
  self.true(nodes[2].get('public'))
@@ -3397,6 +3410,7 @@ class InetModelTest(s_t_utils.SynTest):
3397
3410
  q = '''
3398
3411
  [ inet:service:message=(visi, says, relax)
3399
3412
  :title="Hehe Haha"
3413
+ :hashtags="#hehe,#haha,#hehe"
3400
3414
  :thread={[
3401
3415
  inet:service:thread=*
3402
3416
  :title="Woot Woot"
@@ -3410,6 +3424,7 @@ class InetModelTest(s_t_utils.SynTest):
3410
3424
  '''
3411
3425
  nodes = await core.nodes(q)
3412
3426
  self.len(1, nodes)
3427
+ self.eq(['#haha', '#hehe'], nodes[0].get('hashtags'))
3413
3428
  self.len(1, await core.nodes('inet:service:message=(visi, says, hello) -> inet:service:thread:message'))
3414
3429
  self.len(1, await core.nodes('''
3415
3430
  inet:service:message:title="hehe haha"
@@ -1,5 +1,6 @@
1
1
  import logging
2
2
 
3
+ import synapse.exc as s_exc
3
4
  import synapse.common as s_common
4
5
 
5
6
  import synapse.tests.utils as s_t_utils
@@ -151,6 +151,10 @@ class PsModelTest(s_t_utils.SynTest):
151
151
  'users': ('visi', 'invisigoth'),
152
152
  'crypto:address': 'btc/1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2',
153
153
  'langs': (lang00 := s_common.guid(),),
154
+ 'banner': file0,
155
+ 'passwd': 'hunter2',
156
+ 'website': 'https://blogs.vertex.link/brutus',
157
+ 'websites': ('https://foo.com', 'https://bar.com', 'https://foo.com'),
154
158
  }
155
159
  opts = {'vars': {'valu': con0, 'p': props}}
156
160
  q = '''[(ps:contact=$valu
@@ -172,6 +176,10 @@ class PsModelTest(s_t_utils.SynTest):
172
176
  :death:place=$p."death:place" :death:place:loc=$p."death:place:loc"
173
177
  :death:place:name=$p."death:place:name"
174
178
  :service:accounts=(*, *) :langs=$p.langs
179
+ :banner=$p.banner
180
+ :passwd=$p.passwd
181
+ :website=$p.website
182
+ :websites=$p.websites
175
183
  )]'''
176
184
  nodes = await core.nodes(q, opts=opts)
177
185
  self.len(1, nodes)
@@ -215,6 +223,10 @@ class PsModelTest(s_t_utils.SynTest):
215
223
  self.eq(node.get('death:place:loc'), 'us.va.reston')
216
224
  self.eq(node.get('birth:place:name'), 'reston, va, usa, earth, sol, milkyway')
217
225
  self.eq(node.get('death:place:name'), 'reston, va, usa, earth, sol, milkyway')
226
+ self.eq(node.get('banner'), file0)
227
+ self.eq(node.get('passwd'), 'hunter2')
228
+ self.eq(node.get('website'), 'https://blogs.vertex.link/brutus')
229
+ self.eq(node.get('websites'), ('https://bar.com', 'https://foo.com'))
218
230
  self.len(1, await core.nodes('ps:contact :birth:place -> geo:place'))
219
231
  self.len(1, await core.nodes('ps:contact :death:place -> geo:place'))
220
232
  self.len(2, await core.nodes('ps:contact :service:accounts -> inet:service:account'))