synapse 2.190.0__py311-none-any.whl → 2.192.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 (49) hide show
  1. synapse/axon.py +54 -23
  2. synapse/common.py +9 -0
  3. synapse/cortex.py +3 -2
  4. synapse/datamodel.py +8 -1
  5. synapse/lib/ast.py +6 -2
  6. synapse/lib/cell.py +55 -7
  7. synapse/lib/msgpack.py +10 -3
  8. synapse/lib/nexus.py +2 -1
  9. synapse/lib/stormhttp.py +32 -35
  10. synapse/lib/stormlib/model.py +37 -0
  11. synapse/lib/stormtypes.py +102 -20
  12. synapse/lib/version.py +2 -2
  13. synapse/models/auth.py +2 -1
  14. synapse/models/base.py +20 -0
  15. synapse/models/crypto.py +5 -2
  16. synapse/models/economic.py +45 -11
  17. synapse/models/inet.py +78 -21
  18. synapse/models/person.py +11 -4
  19. synapse/models/risk.py +6 -0
  20. synapse/models/syn.py +22 -12
  21. synapse/models/telco.py +3 -1
  22. synapse/tests/test_axon.py +10 -0
  23. synapse/tests/test_cortex.py +60 -17
  24. synapse/tests/test_lib_agenda.py +1 -6
  25. synapse/tests/test_lib_ast.py +6 -0
  26. synapse/tests/test_lib_cell.py +63 -4
  27. synapse/tests/test_lib_httpapi.py +11 -6
  28. synapse/tests/test_lib_lmdbslab.py +1 -4
  29. synapse/tests/test_lib_stormhttp.py +57 -12
  30. synapse/tests/test_lib_stormlib_cortex.py +1 -3
  31. synapse/tests/test_lib_stormlib_log.py +1 -6
  32. synapse/tests/test_lib_stormlib_model.py +28 -0
  33. synapse/tests/test_lib_stormtypes.py +1 -2
  34. synapse/tests/test_lib_trigger.py +2 -3
  35. synapse/tests/test_model_base.py +12 -2
  36. synapse/tests/test_model_inet.py +23 -0
  37. synapse/tests/test_model_person.py +2 -0
  38. synapse/tests/test_model_risk.py +5 -0
  39. synapse/tests/test_model_syn.py +198 -0
  40. synapse/tests/test_servers_univ.py +0 -12
  41. synapse/tests/test_tools_apikey.py +227 -0
  42. synapse/tests/test_utils.py +23 -4
  43. synapse/tests/utils.py +39 -5
  44. synapse/tools/apikey.py +93 -0
  45. {synapse-2.190.0.dist-info → synapse-2.192.0.dist-info}/METADATA +4 -4
  46. {synapse-2.190.0.dist-info → synapse-2.192.0.dist-info}/RECORD +49 -47
  47. {synapse-2.190.0.dist-info → synapse-2.192.0.dist-info}/LICENSE +0 -0
  48. {synapse-2.190.0.dist-info → synapse-2.192.0.dist-info}/WHEEL +0 -0
  49. {synapse-2.190.0.dist-info → synapse-2.192.0.dist-info}/top_level.txt +0 -0
@@ -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)
@@ -56,18 +56,6 @@ class UnivServerTest(s_t_utils.SynTest):
56
56
  async with await s_telepath.openurl(f'cell://{dirn}') as proxy:
57
57
  self.eq('cell', await proxy.getCellType())
58
58
 
59
- argv = [
60
- 'synapse.tests.test_lib_cell.EchoAuth',
61
- '--telepath', 'tcp://127.0.0.1:0/',
62
- '--https', '0',
63
- '--name', 'univtest',
64
- dirn,
65
- ]
66
- # Or start the Cortex off a a EchoAuth (don't do this in practice...)
67
- async with await s_s_univ.main(argv) as cell:
68
- async with await s_telepath.openurl(f'cell://{dirn}') as proxy:
69
- self.eq('echoauth', await proxy.getCellType())
70
-
71
59
  argv = ['synapse.lib.newp.Newp']
72
60
  with self.raises(s_exc.NoSuchCtor):
73
61
  async with await s_s_univ.main(argv) as core:
@@ -0,0 +1,227 @@
1
+ import datetime
2
+
3
+ import synapse.exc as s_exc
4
+ import synapse.common as s_common
5
+
6
+ import synapse.lib.time as s_time
7
+ import synapse.lib.output as s_output
8
+
9
+ import synapse.tests.utils as s_test
10
+ import synapse.tools.apikey as s_t_apikey
11
+
12
+ async def getApiKeyByName(core, name):
13
+ keys = {k.get('name'): k async for k in core.getApiKeys()}
14
+ return keys.get(name)
15
+
16
+ class ApiKeyTest(s_test.SynTest):
17
+
18
+ async def test_tools_apikey(self):
19
+ async with self.getTestCore() as core:
20
+
21
+ await core.auth.addUser('blackout')
22
+
23
+ rooturl = core.getLocalUrl()
24
+ blckurl = core.getLocalUrl(user='blackout')
25
+
26
+ # Add API keys
27
+ argv = (
28
+ '--svcurl', rooturl,
29
+ 'add',
30
+ 'rootkey00',
31
+ '-d', '120',
32
+ )
33
+ outp = s_output.OutPutStr()
34
+ self.eq(0, await s_t_apikey.main(argv, outp=outp))
35
+
36
+ self.isin('Successfully added API key with name=rootkey00.', str(outp))
37
+ rootkey00 = await getApiKeyByName(core, 'rootkey00')
38
+
39
+ self.isin(f'Iden: {rootkey00.get("iden")}', str(outp))
40
+ self.isin(' API Key: ', str(outp))
41
+ self.isin(' Name: rootkey00', str(outp))
42
+ self.isin(f' Created: {s_time.repr(rootkey00.get("created"))}', str(outp))
43
+ self.isin(f' Updated: {s_time.repr(rootkey00.get("updated"))}', str(outp))
44
+ self.isin(f' Expires: {s_time.repr(rootkey00.get("expires"))}', str(outp))
45
+ self.eq(rootkey00.get('expires'), rootkey00.get('created') + 120000)
46
+
47
+ argv = (
48
+ '--svcurl', rooturl,
49
+ 'add',
50
+ '-u', 'blackout',
51
+ 'blckkey00',
52
+ )
53
+ outp = s_output.OutPutStr()
54
+ self.eq(0, await s_t_apikey.main(argv, outp=outp))
55
+
56
+ self.isin('Successfully added API key with name=blckkey00.', str(outp))
57
+ blckkey00 = await getApiKeyByName(core, 'blckkey00')
58
+
59
+ self.isin(f'Iden: {blckkey00.get("iden")}', str(outp))
60
+ self.isin(' API Key: ', str(outp))
61
+ self.isin(' Name: blckkey00', str(outp))
62
+ self.isin(f' Created: {s_time.repr(blckkey00.get("created"))}', str(outp))
63
+ self.isin(f' Updated: {s_time.repr(blckkey00.get("updated"))}', str(outp))
64
+ self.notin(' Expires: ', str(outp))
65
+
66
+ argv = (
67
+ '--svcurl', blckurl,
68
+ 'add',
69
+ 'blckkey01',
70
+ )
71
+ outp = s_output.OutPutStr()
72
+ self.eq(0, await s_t_apikey.main(argv, outp=outp))
73
+
74
+ self.isin('Successfully added API key with name=blckkey01.', str(outp))
75
+ blckkey01 = await getApiKeyByName(core, 'blckkey01')
76
+
77
+ self.isin(f'Iden: {blckkey01.get("iden")}', str(outp))
78
+ self.isin(' API Key: ', str(outp))
79
+ self.isin(' Name: blckkey01', str(outp))
80
+ self.isin(f' Created: {s_time.repr(blckkey01.get("created"))}', str(outp))
81
+ self.isin(f' Updated: {s_time.repr(blckkey01.get("updated"))}', str(outp))
82
+ self.notin(' Expires: ', str(outp))
83
+
84
+ # List API keys
85
+ argv = (
86
+ '--svcurl', rooturl,
87
+ 'list',
88
+ )
89
+ outp = s_output.OutPutStr()
90
+ self.eq(0, await s_t_apikey.main(argv, outp=outp))
91
+
92
+ self.isin(f'Iden: {rootkey00.get("iden")}', str(outp))
93
+ self.notin(' API Key: ', str(outp))
94
+ self.isin(' Name: rootkey00', str(outp))
95
+ self.isin(f' Created: {s_time.repr(rootkey00.get("created"))}', str(outp))
96
+ self.isin(f' Updated: {s_time.repr(rootkey00.get("updated"))}', str(outp))
97
+ self.isin(f' Expires: {s_time.repr(rootkey00.get("expires"))}', str(outp))
98
+ self.eq(rootkey00.get('expires'), rootkey00.get('created') + 120000)
99
+
100
+ argv = (
101
+ '--svcurl', rooturl,
102
+ 'list',
103
+ '-u', 'blackout',
104
+ )
105
+ outp = s_output.OutPutStr()
106
+ self.eq(0, await s_t_apikey.main(argv, outp=outp))
107
+
108
+ self.isin(f'Iden: {blckkey00.get("iden")}', str(outp))
109
+ self.notin(' API Key: ', str(outp))
110
+ self.isin(' Name: blckkey00', str(outp))
111
+ self.isin(f' Created: {s_time.repr(blckkey00.get("created"))}', str(outp))
112
+ self.isin(f' Updated: {s_time.repr(blckkey00.get("updated"))}', str(outp))
113
+ self.notin(' Expires: ', str(outp))
114
+
115
+ self.isin(f'Iden: {blckkey01.get("iden")}', str(outp))
116
+ self.notin(' API Key: ', str(outp))
117
+ self.isin(' Name: blckkey01', str(outp))
118
+ self.isin(f' Created: {s_time.repr(blckkey01.get("created"))}', str(outp))
119
+ self.isin(f' Updated: {s_time.repr(blckkey01.get("updated"))}', str(outp))
120
+ self.notin(' Expires: ', str(outp))
121
+
122
+ argv = (
123
+ '--svcurl', blckurl,
124
+ 'list',
125
+ )
126
+ outp = s_output.OutPutStr()
127
+ self.eq(0, await s_t_apikey.main(argv, outp=outp))
128
+
129
+ self.isin(f'Iden: {blckkey00.get("iden")}', str(outp))
130
+ self.notin(' API Key: ', str(outp))
131
+ self.isin(' Name: blckkey00', str(outp))
132
+ self.isin(f' Created: {s_time.repr(blckkey00.get("created"))}', str(outp))
133
+ self.isin(f' Updated: {s_time.repr(blckkey00.get("updated"))}', str(outp))
134
+ self.notin(' Expires: ', str(outp))
135
+
136
+ self.isin(f'Iden: {blckkey01.get("iden")}', str(outp))
137
+ self.notin(' API Key: ', str(outp))
138
+ self.isin(' Name: blckkey01', str(outp))
139
+ self.isin(f' Created: {s_time.repr(blckkey01.get("created"))}', str(outp))
140
+ self.isin(f' Updated: {s_time.repr(blckkey01.get("updated"))}', str(outp))
141
+ self.notin(' Expires: ', str(outp))
142
+
143
+ # Delete API keys
144
+ rootiden00 = rootkey00.get('iden')
145
+ argv = (
146
+ '--svcurl', rooturl,
147
+ 'del',
148
+ rootiden00,
149
+ )
150
+ outp = s_output.OutPutStr()
151
+ self.eq(0, await s_t_apikey.main(argv, outp=outp))
152
+ self.isin(f'Successfully deleted API key with iden={rootiden00}.', str(outp))
153
+
154
+ blckiden00 = blckkey00.get('iden')
155
+ argv = (
156
+ '--svcurl', rooturl,
157
+ 'del',
158
+ blckiden00,
159
+ )
160
+ outp = s_output.OutPutStr()
161
+ self.eq(0, await s_t_apikey.main(argv, outp=outp))
162
+ self.isin(f'Successfully deleted API key with iden={blckiden00}.', str(outp))
163
+
164
+ blckiden01 = blckkey01.get('iden')
165
+ argv = (
166
+ '--svcurl', blckurl,
167
+ 'del',
168
+ blckiden01,
169
+ )
170
+ outp = s_output.OutPutStr()
171
+ self.eq(0, await s_t_apikey.main(argv, outp=outp))
172
+ self.isin(f'Successfully deleted API key with iden={blckiden01}.', str(outp))
173
+
174
+ # List API keys again
175
+ argv = (
176
+ '--svcurl', rooturl,
177
+ 'list',
178
+ )
179
+ outp = s_output.OutPutStr()
180
+ self.eq(0, await s_t_apikey.main(argv, outp=outp))
181
+ self.isin('No API keys found.', str(outp))
182
+
183
+ argv = (
184
+ '--svcurl', rooturl,
185
+ 'list',
186
+ '-u', 'blackout',
187
+ )
188
+ outp = s_output.OutPutStr()
189
+ self.eq(0, await s_t_apikey.main(argv, outp=outp))
190
+ self.isin('No API keys found.', str(outp))
191
+
192
+ argv = (
193
+ '--svcurl', blckurl,
194
+ 'list',
195
+ )
196
+ outp = s_output.OutPutStr()
197
+ self.eq(0, await s_t_apikey.main(argv, outp=outp))
198
+ self.isin('No API keys found.', str(outp))
199
+
200
+ # Check errors
201
+ argv = (
202
+ '--svcurl', rooturl,
203
+ 'list',
204
+ '-u', 'newp',
205
+ )
206
+ outp = s_output.OutPutStr()
207
+ self.eq(1, await s_t_apikey.main(argv, outp=outp))
208
+ self.isin('ERROR: NoSuchUser: No user named newp.', str(outp))
209
+
210
+ argv = (
211
+ '--svcurl', blckurl,
212
+ 'list',
213
+ '-u', 'root',
214
+ )
215
+ outp = s_output.OutPutStr()
216
+ self.eq(1, await s_t_apikey.main(argv, outp=outp))
217
+ self.isin('ERROR: AuthDeny: getUserInfo denied for non-admin and non-self', str(outp))
218
+
219
+ newpiden = s_common.guid()
220
+ argv = (
221
+ '--svcurl', rooturl,
222
+ 'del',
223
+ newpiden,
224
+ )
225
+ outp = s_output.OutPutStr()
226
+ self.eq(1, await s_t_apikey.main(argv, outp=outp))
227
+ self.isin(f"ERROR: NoSuchIden: User API key with iden='{newpiden}' does not exist.", str(outp))
@@ -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'))
synapse/tests/utils.py CHANGED
@@ -21,9 +21,11 @@ import io
21
21
  import os
22
22
  import sys
23
23
  import copy
24
+ import json
24
25
  import math
25
26
  import types
26
27
  import shutil
28
+ import typing
27
29
  import asyncio
28
30
  import hashlib
29
31
  import inspect
@@ -97,6 +99,10 @@ def norm(z):
97
99
  def deguidify(x):
98
100
  return regex.sub('[0-9a-f]{32}', '*' * 32, x)
99
101
 
102
+ def jsonlines(text: str):
103
+ lines = [k for k in text.split('\n') if k]
104
+ return [json.loads(line) for line in lines]
105
+
100
106
  async def waitForBehold(core, events):
101
107
  async for mesg in core.behold():
102
108
  for event in list(events):
@@ -789,6 +795,10 @@ class StreamEvent(io.StringIO, threading.Event):
789
795
  if self.mesg and self.mesg in s:
790
796
  self.set()
791
797
 
798
+ def jsonlines(self) -> typing.List[dict]:
799
+ '''Get the messages as jsonlines. May throw Json errors if the captured stream is not jsonlines.'''
800
+ return jsonlines(self.getvalue())
801
+
792
802
  class AsyncStreamEvent(io.StringIO, asyncio.Event):
793
803
  '''
794
804
  A combination of a io.StringIO object and an asyncio.Event object.
@@ -821,6 +831,10 @@ class AsyncStreamEvent(io.StringIO, asyncio.Event):
821
831
  return await asyncio.Event.wait(self)
822
832
  return await s_coro.event_wait(self, timeout=timeout)
823
833
 
834
+ def jsonlines(self) -> typing.List[dict]:
835
+ '''Get the messages as jsonlines. May throw Json errors if the captured stream is not jsonlines.'''
836
+ return jsonlines(self.getvalue())
837
+
824
838
  class HttpReflector(s_httpapi.Handler):
825
839
  '''Test handler which reflects get/post data back to the caller'''
826
840
 
@@ -1126,6 +1140,28 @@ class SynTest(unittest.TestCase):
1126
1140
  '''
1127
1141
  return TstOutPut()
1128
1142
 
1143
+ def thisEnvMust(self, *envvars):
1144
+ '''
1145
+ Requires a host must have environment variables set to truthy values.
1146
+
1147
+ Args:
1148
+ *envars: Environment variables to require being present.
1149
+ '''
1150
+ for envar in envvars:
1151
+ if not s_common.envbool(envar):
1152
+ self.skip(f'Envar {envar} is not set to a truthy value.')
1153
+
1154
+ def thisEnvMustNot(self, *envvars):
1155
+ '''
1156
+ Requires a host must not have environment variables set to truthy values.
1157
+
1158
+ Args:
1159
+ *envars: Environment variables to require being absent or set to falsey values.
1160
+ '''
1161
+ for envar in envvars:
1162
+ if s_common.envbool(envar):
1163
+ self.skip(f'Envar {envar} is set to a truthy value.')
1164
+
1129
1165
  def thisHostMust(self, **props): # pragma: no cover
1130
1166
  '''
1131
1167
  Requires a host having a specific property.
@@ -1719,7 +1755,7 @@ class SynTest(unittest.TestCase):
1719
1755
  slogger.setLevel(level)
1720
1756
 
1721
1757
  @contextlib.contextmanager
1722
- def getAsyncLoggerStream(self, logname, mesg=''):
1758
+ def getAsyncLoggerStream(self, logname, mesg='') -> contextlib.AbstractContextManager[StreamEvent, None, None]:
1723
1759
  '''
1724
1760
  Async version of getLoggerStream.
1725
1761
 
@@ -1764,7 +1800,7 @@ class SynTest(unittest.TestCase):
1764
1800
  slogger.setLevel(level)
1765
1801
 
1766
1802
  @contextlib.contextmanager
1767
- def getStructuredAsyncLoggerStream(self, logname, mesg=''):
1803
+ def getStructuredAsyncLoggerStream(self, logname, mesg='') -> contextlib.AbstractContextManager[AsyncStreamEvent, None, None]:
1768
1804
  '''
1769
1805
  Async version of getLoggerStream which uses structured logging.
1770
1806
 
@@ -1787,9 +1823,7 @@ class SynTest(unittest.TestCase):
1787
1823
  # Wait for the mesg to be written to the stream
1788
1824
  await stream.wait(timeout=10)
1789
1825
 
1790
- data = stream.getvalue()
1791
- raw_mesgs = [m for m in data.split('\n') if m]
1792
- msgs = [json.loads(m) for m in raw_mesgs]
1826
+ msgs = stream.jsonlines()
1793
1827
  # Do something with messages
1794
1828
 
1795
1829
  Returns: