synapse 2.224.0__py311-none-any.whl → 2.226.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 (136) hide show
  1. synapse/axon.py +10 -5
  2. synapse/cortex.py +6 -8
  3. synapse/lib/agenda.py +28 -15
  4. synapse/lib/ast.py +4 -8
  5. synapse/lib/cell.py +1 -1
  6. synapse/lib/const.py +4 -0
  7. synapse/lib/multislabseqn.py +36 -1
  8. synapse/lib/nexus.py +67 -8
  9. synapse/lib/platforms/linux.py +2 -0
  10. synapse/lib/queue.py +4 -1
  11. synapse/lib/rstorm.py +2 -2
  12. synapse/lib/schemas.py +11 -1
  13. synapse/lib/slabseqn.py +28 -0
  14. synapse/lib/storm.py +16 -4
  15. synapse/lib/stormhttp.py +7 -1
  16. synapse/lib/stormlib/aha.py +3 -3
  17. synapse/lib/stormtypes.py +10 -3
  18. synapse/lib/types.py +20 -0
  19. synapse/lib/version.py +2 -2
  20. synapse/models/base.py +3 -0
  21. synapse/models/inet.py +62 -5
  22. synapse/models/infotech.py +18 -0
  23. synapse/models/media.py +4 -0
  24. synapse/models/risk.py +3 -0
  25. synapse/tests/test_cortex.py +115 -2
  26. synapse/tests/test_lib_agenda.py +141 -28
  27. synapse/tests/test_lib_cell.py +1 -1
  28. synapse/tests/test_lib_certdir.py +1 -1
  29. synapse/tests/test_lib_httpapi.py +1 -1
  30. synapse/tests/test_lib_layer.py +1 -1
  31. synapse/tests/test_lib_lmdbslab.py +2 -0
  32. synapse/tests/test_lib_multislabseqn.py +22 -0
  33. synapse/tests/test_lib_nexus.py +42 -1
  34. synapse/tests/test_lib_platforms_linux.py +4 -0
  35. synapse/tests/test_lib_slabseqn.py +30 -1
  36. synapse/tests/test_lib_storm.py +65 -1
  37. synapse/tests/test_lib_stormhttp.py +16 -0
  38. synapse/tests/test_lib_stormlib_aha.py +6 -2
  39. synapse/tests/test_lib_stormlib_oauth.py +1 -1
  40. synapse/tests/test_lib_stormsvc.py +1 -1
  41. synapse/tests/test_lib_trigger.py +1 -1
  42. synapse/tests/test_model_inet.py +37 -0
  43. synapse/tests/test_model_infotech.py +15 -1
  44. synapse/tests/test_model_media.py +4 -1
  45. synapse/tests/test_model_risk.py +2 -0
  46. synapse/tests/test_tools_aha.py +2 -2
  47. synapse/tests/{test_tools_axon2axon.py → test_tools_axon_copy.py} +4 -4
  48. synapse/tests/{test_tools_pullfile.py → test_tools_axon_get.py} +4 -4
  49. synapse/tests/{test_tools_pushfile.py → test_tools_axon_put.py} +7 -7
  50. synapse/tests/{test_tools_csvtool.py → test_tools_cortex_csv.py} +12 -3
  51. synapse/tests/{test_tools_feed.py → test_tools_cortex_feed.py} +2 -2
  52. synapse/tests/{test_tools_apikey.py → test_tools_service_apikey.py} +1 -4
  53. synapse/tests/{test_tools_backup.py → test_tools_service_backup.py} +5 -5
  54. synapse/tests/{test_tools_demote.py → test_tools_service_demote.py} +1 -1
  55. synapse/tests/{test_tools_healthcheck.py → test_tools_service_healthcheck.py} +1 -1
  56. synapse/tests/{test_tools_livebackup.py → test_tools_service_livebackup.py} +1 -1
  57. synapse/tests/{test_tools_modrole.py → test_tools_service_modrole.py} +1 -1
  58. synapse/tests/{test_tools_moduser.py → test_tools_service_moduser.py} +1 -1
  59. synapse/tests/{test_tools_promote.py → test_tools_service_promote.py} +1 -1
  60. synapse/tests/{test_tools_reload.py → test_tools_service_reload.py} +1 -1
  61. synapse/tests/{test_tools_shutdown.py → test_tools_service_shutdown.py} +1 -1
  62. synapse/tests/{test_tools_snapshot.py → test_tools_service_snapshot.py} +1 -1
  63. synapse/tests/{test_tools_storm.py → test_tools_storm_cli.py} +1 -1
  64. synapse/tests/{test_tools_pkgs_gendocs.py → test_tools_storm_pkg_doc.py} +12 -3
  65. synapse/tests/{test_tools_genpkg.py → test_tools_storm_pkg_gen.py} +1 -1
  66. synapse/tests/{test_tools_autodoc.py → test_tools_utils_autodoc.py} +1 -1
  67. synapse/tests/test_tools_utils_changelog.py +454 -0
  68. synapse/tests/{test_tools_easycert.py → test_tools_utils_easycert.py} +48 -46
  69. synapse/tests/{test_tools_guid.py → test_tools_utils_guid.py} +3 -3
  70. synapse/tests/{test_tools_json2mpk.py → test_tools_utils_json2mpk.py} +3 -3
  71. synapse/tests/{test_tools_rstorm.py → test_tools_utils_rstorm.py} +6 -1
  72. synapse/tests/utils.py +15 -1
  73. synapse/tools/aha/mirror.py +1 -1
  74. synapse/tools/apikey.py +4 -83
  75. synapse/tools/autodoc.py +3 -1031
  76. synapse/tools/axon/copy.py +44 -0
  77. synapse/tools/axon/get.py +64 -0
  78. synapse/tools/axon/put.py +122 -0
  79. synapse/tools/axon2axon.py +3 -36
  80. synapse/tools/backup.py +6 -176
  81. synapse/tools/changelog.py +3 -1098
  82. synapse/tools/cortex/csv.py +236 -0
  83. synapse/tools/cortex/feed.py +151 -0
  84. synapse/tools/csvtool.py +3 -227
  85. synapse/tools/demote.py +4 -40
  86. synapse/tools/docker/validate.py +3 -3
  87. synapse/tools/easycert.py +4 -129
  88. synapse/tools/feed.py +3 -140
  89. synapse/tools/genpkg.py +3 -307
  90. synapse/tools/guid.py +7 -6
  91. synapse/tools/healthcheck.py +3 -101
  92. synapse/tools/json2mpk.py +6 -38
  93. synapse/tools/livebackup.py +4 -27
  94. synapse/tools/modrole.py +3 -108
  95. synapse/tools/moduser.py +3 -179
  96. synapse/tools/pkgs/gendocs.py +3 -164
  97. synapse/tools/promote.py +4 -41
  98. synapse/tools/pullfile.py +3 -56
  99. synapse/tools/pushfile.py +3 -114
  100. synapse/tools/reload.py +4 -61
  101. synapse/tools/rstorm.py +3 -26
  102. synapse/tools/service/__init__.py +0 -0
  103. synapse/tools/service/apikey.py +90 -0
  104. synapse/tools/service/backup.py +181 -0
  105. synapse/tools/service/demote.py +47 -0
  106. synapse/tools/service/healthcheck.py +109 -0
  107. synapse/tools/service/livebackup.py +34 -0
  108. synapse/tools/service/modrole.py +116 -0
  109. synapse/tools/service/moduser.py +184 -0
  110. synapse/tools/service/promote.py +48 -0
  111. synapse/tools/service/reload.py +68 -0
  112. synapse/tools/service/shutdown.py +51 -0
  113. synapse/tools/service/snapshot.py +64 -0
  114. synapse/tools/shutdown.py +5 -45
  115. synapse/tools/snapshot.py +4 -57
  116. synapse/tools/storm/__init__.py +0 -0
  117. synapse/tools/storm/__main__.py +5 -0
  118. synapse/tools/{storm.py → storm/_cli.py} +0 -3
  119. synapse/tools/storm/pkg/__init__.py +0 -0
  120. synapse/tools/{pkgs/pandoc_filter.py → storm/pkg/_pandoc_filter.py} +1 -1
  121. synapse/tools/storm/pkg/doc.py +176 -0
  122. synapse/tools/storm/pkg/gen.py +315 -0
  123. synapse/tools/utils/__init__.py +0 -0
  124. synapse/tools/utils/autodoc.py +1040 -0
  125. synapse/tools/utils/changelog.py +1124 -0
  126. synapse/tools/utils/easycert.py +136 -0
  127. synapse/tools/utils/guid.py +11 -0
  128. synapse/tools/utils/json2mpk.py +46 -0
  129. synapse/tools/utils/rstorm.py +35 -0
  130. {synapse-2.224.0.dist-info → synapse-2.226.0.dist-info}/METADATA +1 -1
  131. {synapse-2.224.0.dist-info → synapse-2.226.0.dist-info}/RECORD +135 -106
  132. synapse/tests/test_tools_changelog.py +0 -196
  133. /synapse/tests/{test_tools_axon.py → test_tools_axon_dump_load.py} +0 -0
  134. {synapse-2.224.0.dist-info → synapse-2.226.0.dist-info}/WHEEL +0 -0
  135. {synapse-2.224.0.dist-info → synapse-2.226.0.dist-info}/licenses/LICENSE +0 -0
  136. {synapse-2.224.0.dist-info → synapse-2.226.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,454 @@
1
+ import os
2
+ import gzip
3
+
4
+ import synapse.common as s_common
5
+
6
+ import synapse.tests.utils as s_test_utils
7
+
8
+ import synapse.tools.utils.changelog as s_t_changelog
9
+
10
+ multiline_feature = '''---
11
+ desc: |
12
+ - This is a pre-formatted RST block as YAML literal scalar.
13
+
14
+ It has stuff in it already formatted all nice like.
15
+
16
+ +-------------------+---------------------------+
17
+ | Beep | Boop |
18
+ +===================+===========================+
19
+ | hahaha | lasers |
20
+ +-------------------+---------------------------+
21
+ | wowow | stufff |
22
+ +-------------------+---------------------------+
23
+
24
+ These are more lines. They can have things like RST string literals in
25
+ them ``like this``. You can even do a literal block!
26
+
27
+ ::
28
+
29
+ wow
30
+
31
+ So this is a example.
32
+ desc:literal: true
33
+ prs: []
34
+ type: feat
35
+ ...
36
+ '''
37
+
38
+ changelog_format_output = '''CHANGELOG ENTRY:
39
+
40
+
41
+ v0.1.22 - 2025-10-03
42
+ ====================
43
+
44
+ Automatic Migrations
45
+ --------------------
46
+ - Migrated the widget storage to use acme lasers.
47
+ - See :ref:`datamigration` for more information about automatic migrations.
48
+
49
+ Model Changes
50
+ -------------
51
+ - Added lasers to the sci model.
52
+
53
+ Features and Enhancements
54
+ -------------------------
55
+ - This is a pre-formatted RST block as YAML literal scalar.
56
+
57
+ It has stuff in it already formatted all nice like.
58
+
59
+ +-------------------+---------------------------+
60
+ | Beep | Boop |
61
+ +===================+===========================+
62
+ | hahaha | lasers |
63
+ +-------------------+---------------------------+
64
+ | wowow | stufff |
65
+ +-------------------+---------------------------+
66
+
67
+ These are more lines. They can have things like RST string literals in
68
+ them ``like this``. You can even do a literal block!
69
+
70
+ ::
71
+
72
+ wow
73
+
74
+ So this is a example.
75
+ - I am a earlier feature.
76
+ (`#1230 <https://github.com/vertexproject/synapse/pull/1230>`_)
77
+ - I am a feature.
78
+ (`#1234 <https://github.com/vertexproject/synapse/pull/1234>`_)
79
+
80
+ Bugfixes
81
+ --------
82
+ - I am a bug which has quite a large amount of text in it. The amount of text
83
+ will span across multiple lines after being formatted by the changelog tool.
84
+
85
+ Notes
86
+ -----
87
+ - I am a fancy note.
88
+
89
+ Improved documentation
90
+ ----------------------
91
+ - Documented the lasers.
92
+
93
+ Deprecations
94
+ ------------
95
+ - For widget maker has been deprecated in favor of the new acme corp laser
96
+ cannons.
97
+ '''
98
+
99
+ class ChangelogToolTest(s_test_utils.SynTest):
100
+
101
+ async def test_changelog_model_diff_class(self):
102
+ outp = self.getTestOutp()
103
+ old_fp = self.getTestFilePath('changelog', 'model_2.176.0_16ee721a6b7221344eaf946c3ab4602dda546b1a.yaml.gz')
104
+ new_fp = self.getTestFilePath('changelog', 'model_2.176.0_2a25c58bbd344716cd7cbc3f4304d8925b0f4ef2.yaml.gz')
105
+ oldmodel = s_t_changelog._getModelFile(old_fp)
106
+ newmodel = s_t_changelog._getModelFile(new_fp)
107
+
108
+ differ = s_t_changelog.ModelDiffer(newmodel.get('model'), oldmodel.get('model'))
109
+
110
+ self.eq(differ.cur_type2iface['it:host'], ['inet:service:object', 'inet:service:base'])
111
+ self.eq(differ.cur_type2iface['mat:type'], ['meta:taxonomy'])
112
+ self.eq(differ.cur_iface_to_allifaces['it:host:activity'], ['it:host:activity'])
113
+ self.eq(differ.cur_iface_to_allifaces['file:mime:msoffice'], ['file:mime:msoffice', 'file:mime:meta'])
114
+ changes = differ.diffModl(outp)
115
+
116
+ self.len(4, changes.get('edges').get('new_edges'))
117
+
118
+ form_chng = changes.get('forms')
119
+ self.isin('it:beeper:thingy', form_chng.get('new_forms'))
120
+ self.notin('it:beeper:name', form_chng.get('new_forms'))
121
+ updt_frms = form_chng.get('updated_forms')
122
+ self.isin('new_properties', updt_frms.get('file:mime:pe:resource'))
123
+ self.isin('new_properties_no_interfaces', updt_frms.get('file:mime:pe:resource'))
124
+ self.isin('new_properties', updt_frms.get('inet:http:request'))
125
+ self.notin('new_properties_no_interfaces', updt_frms.get('inet:http:request'))
126
+ self.isin('updated_properties', updt_frms.get('file:mime:macho:section'))
127
+ self.isin('deprecated_properties', updt_frms.get('file:mime:lnk'))
128
+ self.isin('deprecated_properties_no_interfaces', updt_frms.get('file:mime:lnk'))
129
+ self.isin('deprecated_properties', updt_frms.get('inet:http:request'))
130
+ self.notin('deprecated_properties_no_interfaces', updt_frms.get('inet:http:request'))
131
+ self.isin('new_properties', updt_frms.get('file:mime:lnk'))
132
+ self.isin('new_properties_no_interfaces', updt_frms.get('file:mime:lnk'))
133
+ self.isin('updated_properties', updt_frms.get('file:mime:lnk'))
134
+ self.isin('updated_properties_no_interfaces', updt_frms.get('file:mime:lnk'))
135
+ self.isin('updated_properties', updt_frms.get('inet:service:access'))
136
+ self.notin('updated_properties_no_interfaces', updt_frms.get('inet:service:access'))
137
+
138
+ self.isin('it:host:beeper', changes.get('interfaces').get('new_interfaces'))
139
+ updt_iface = changes.get('interfaces').get('updated_interfaces')
140
+ self.isin('deprecated_properties', updt_iface.get('it:host:activity'))
141
+ self.isin('new_properties', updt_iface.get('it:host:activity'))
142
+ self.isin('updated_properties', updt_iface.get('inet:service:base'))
143
+
144
+ self.eq(changes.get('tagprops'), {})
145
+
146
+ self.isin('it:reveng:function', changes.get('types').get('deprecated_types'))
147
+ self.isin('inet:port', changes.get('types').get('deprecated_types'))
148
+ self.isin('it:beeper:thingy', changes.get('types').get('new_types'))
149
+ self.isin('it:beeper:name', changes.get('types').get('new_types'))
150
+ uptd_types = changes.get('types').get('updated_types')
151
+ self.isin('updated_opts', uptd_types.get('it:query'))
152
+ self.isin('updated_interfaces', uptd_types.get('it:reveng:impfunc'))
153
+
154
+ rst = s_t_changelog._gen_model_rst('v2.177.0', 'userguide_model_v2_177_0',
155
+ changes, newmodel.get('model'), outp)
156
+ text = rst.getRstText()
157
+ self.isin('v2.177.0 Model Updates', text)
158
+ self.isin('''**************
159
+ New Interfaces
160
+ **************
161
+
162
+ ``it:host:beeper``
163
+ Properties common to instances of beepers.''', text)
164
+ self.isin('''*********
165
+ New Types
166
+ *********
167
+
168
+ ``it:beeper:name``
169
+ The friendly name of a beeper.
170
+ ''', text)
171
+ self.isin('''*********
172
+ New Forms
173
+ *********
174
+
175
+ ``it:beeper:thingy``
176
+ A beeper thingy.
177
+ ''', text)
178
+ self.isin('''**************
179
+ New Properties
180
+ **************
181
+
182
+ ``file:mime:lnk``
183
+ The form had the following properties added to it:
184
+
185
+
186
+ ``driveserial``
187
+ The drive serial number of the volume the link target is stored on.
188
+
189
+
190
+ ``iconindex``
191
+ A resource index for an icon within an icon location.
192
+ ''', text)
193
+ self.isin('''``inet:http:request``
194
+ The form had the following property added to it:
195
+
196
+ ``beep``
197
+ The time that the host went beep.
198
+ ''', text)
199
+ # Some updates will require manual intervention to rewrite.
200
+ self.isin('''******************
201
+ Updated Interfaces
202
+ ******************
203
+
204
+ ``inet:service:base``
205
+ The property ``id`` has been modified from ['str', {'strip': True}] to ['str',
206
+ {'lower': True, 'strip': True}].
207
+
208
+
209
+ ``it:host:activity``
210
+ The interface property ``time`` has been deprecated.
211
+
212
+
213
+ The property ``beep`` has been added to the interface.
214
+ ''', text)
215
+ self.isin('''*************
216
+ Updated Types
217
+ *************
218
+
219
+ ``it:query``
220
+ The type has been modified from {'enums': None, 'globsuffix': False, 'lower':
221
+ False, 'onespace': False, 'regex': None, 'replace': [], 'strip': True} to
222
+ {'enums': None, 'globsuffix': False, 'lower': True, 'onespace': False,
223
+ 'regex': None, 'replace': [], 'strip': True}.
224
+
225
+
226
+ ``it:reveng:impfunc``
227
+ The type interface has been modified from None to ['it:host:beeper'].
228
+ ''', text)
229
+ self.isin('''******************
230
+ Updated Properties
231
+ ******************
232
+
233
+ ``file:mime:lnk``
234
+ The form had the following property updated:
235
+
236
+
237
+ The property ``entry:icon`` has been modified from ['file:path', {}] to
238
+ ['time', {}].
239
+ ''', text)
240
+ self.isin('''***********
241
+ Light Edges
242
+ ***********
243
+
244
+ ``jenkies``
245
+ When used with a ``it:prod:soft`` node, the edge indicates The software uses
246
+ the jenkies technique.
247
+
248
+
249
+ ``loves``
250
+ The source node loves the target node.
251
+
252
+
253
+ ``sneaky``
254
+ When used with a ``ou:technique`` target node, the edge indicates The
255
+ technique referred to is sneakily used.
256
+
257
+
258
+ ``zoinks``
259
+ When used with a ``it:prod:soft`` and an ``ou:technique`` node, the edge
260
+ indicates The software uses the zoinks technique.
261
+ ''', text)
262
+ self.isin('''****************
263
+ Deprecated Types
264
+ ****************
265
+
266
+ The following types have been marked as deprecated:
267
+
268
+
269
+ * ``inet:port``
270
+
271
+ ''', text)
272
+ self.isin('''****************
273
+ Deprecated Types
274
+ ****************
275
+
276
+ The following forms have been marked as deprecated:
277
+
278
+
279
+ * ``it:reveng:function``
280
+ ''', text)
281
+ self.isin('''*********************
282
+ Deprecated Properties
283
+ *********************
284
+
285
+ ``file:mime:lnk``
286
+ The form had the following property deprecated:
287
+
288
+ ``target:attrs``
289
+ The attributes of the target file according to the LNK header.
290
+ ''', text)
291
+
292
+ async def test_changelog_model_diff_tool(self):
293
+ # diff the stored test asset model vs the current runtime model :fingers_crossed:
294
+ with self.getTestDir() as dirn:
295
+ cdir = s_common.gendir(s_common.genpath(dirn, 'changes'))
296
+ outp = self.getTestOutp()
297
+ argv = ['gen', '--cdir', cdir, '--verbose', '--type', 'feat', 'I am a mighty feature!']
298
+ self.eq(0, await s_t_changelog.main(argv, outp))
299
+
300
+ modl_dirn = s_common.gendir(s_common.genpath(dirn, 'model'))
301
+ old_fp = self.getTestFilePath('changelog', 'model_2.176.0_16ee721a6b7221344eaf946c3ab4602dda546b1a.yaml.gz')
302
+
303
+ argv = ['format', '--cdir', cdir, '--version', 'v0.1.2', '--date', '2025-10-03',
304
+ '--model-doc-dir', modl_dirn, '--model-ref', old_fp,
305
+ '--model-doc-no-git', '--verbose',
306
+ ]
307
+ self.eq(0, await s_t_changelog.main(argv, outp))
308
+ # We have many assertions we may run over outp where using outp.expect()
309
+ # not not be the most efficient
310
+ outp_str = str(outp)
311
+ # model diff data
312
+ self.isin('Not adding model changes to git.', outp_str)
313
+ # Changelog data
314
+ self.isin('v0.1.2 - 2025-10-03', outp_str)
315
+ self.isin('- See :ref:`userguide_model_v0_1_2` for more detailed model changes.', outp_str)
316
+ self.isin('- I am a mighty feature!', outp_str)
317
+
318
+ async def test_changelog_model_save_compare(self):
319
+ with self.getTestDir() as dirn:
320
+ outp = self.getTestOutp()
321
+ argv = ['model', '--cdir', dirn, '--save',]
322
+ self.eq(0, await s_t_changelog.main(argv, outp))
323
+ modelrefs_dirn = s_common.genpath(dirn, 'modelrefs')
324
+
325
+ files = os.listdir(modelrefs_dirn)
326
+ self.len(1, files)
327
+
328
+ model_fp = s_common.genpath(modelrefs_dirn, files[0])
329
+
330
+ with s_common.genfile(model_fp) as fd:
331
+ buf = fd.read()
332
+ buf = gzip.decompress(buf)
333
+ data = s_common.yamlloads(buf)
334
+ self.isin('commit', data)
335
+ self.isin('model', data)
336
+ self.isin('version', data)
337
+
338
+ # Test compare vs self - no changes
339
+ outp.clear()
340
+ argv = ['model', '-c', model_fp, '--verbose']
341
+ self.eq(0, await s_t_changelog.main(argv, outp))
342
+ outp.expect("'edges': {}")
343
+ outp.expect("'forms': {}")
344
+ outp.expect("'interfaces': {}")
345
+ outp.expect("'tagprops': {}")
346
+ outp.expect("'types': {}")
347
+ outp.expect("'univs': {}")
348
+
349
+ async def test_changelog_gen_format(self):
350
+ with self.getTestDir() as dirn:
351
+ outp = self.getTestOutp()
352
+ argv = ['gen', '--cdir', dirn, '--type', 'feat', '--pr', '1234', 'I am a feature.']
353
+ self.eq(0, await s_t_changelog.main(argv, outp))
354
+
355
+ outp = self.getTestOutp()
356
+ argv = ['gen', '--cdir', dirn, '--type', 'feat', '--pr', '1230', 'I am a earlier feature.']
357
+ self.eq(0, await s_t_changelog.main(argv, outp))
358
+
359
+ outp.clear()
360
+ desc = '''I am a bug which has quite a large amount of text in it. The amount of text will span across'''\
361
+ ''' multiple lines after being formatted by the changelog tool.'''
362
+ argv = ['gen', '--cdir', dirn, '--type', 'bug', desc]
363
+ self.eq(0, await s_t_changelog.main(argv, outp))
364
+
365
+ outp.clear()
366
+ desc = 'I am a fancy note.'
367
+ argv = ['gen', '--cdir', dirn, '--type', 'note', desc]
368
+ self.eq(0, await s_t_changelog.main(argv, outp))
369
+
370
+ outp.clear()
371
+ desc = 'For widget maker has been deprecated in favor of the new acme corp laser cannons.'
372
+ argv = ['gen', '--cdir', dirn, '--type', 'deprecation', desc,]
373
+ self.eq(0, await s_t_changelog.main(argv, outp))
374
+
375
+ outp.clear()
376
+ desc = 'Documented the lasers.'
377
+ argv = ['gen', '--cdir', dirn, '--type', 'doc', desc]
378
+ self.eq(0, await s_t_changelog.main(argv, outp))
379
+
380
+ outp.clear()
381
+ desc = 'Migrated the widget storage to use acme lasers.'
382
+ argv = ['gen', '--cdir', dirn, '--type', 'migration', desc]
383
+ self.eq(0, await s_t_changelog.main(argv, outp))
384
+
385
+ outp.clear()
386
+ desc = 'Added lasers to the sci model.'
387
+ argv = ['gen', '--cdir', dirn, '--type', 'model', desc]
388
+ self.eq(0, await s_t_changelog.main(argv, outp))
389
+
390
+ with s_common.genfile(dirn, f'{s_common.guid()}.yaml') as fd:
391
+ fd.write(multiline_feature.encode())
392
+
393
+ outp.clear()
394
+ argv = ['format', '--cdir', dirn, '--version', 'v0.1.22', '--date', '2025-10-03']
395
+ self.eq(0, await s_t_changelog.main(argv, outp))
396
+
397
+ self.eq(str(outp).strip(), changelog_format_output.strip())
398
+
399
+ # Sad path tests
400
+ with self.getTestDir() as dirn:
401
+ outp = self.getTestOutp()
402
+
403
+ fp = s_common.genpath(dirn, s_common.guid())
404
+ with s_common.genfile(fp) as fd:
405
+ fd.write('hello world'.encode())
406
+
407
+ argv = ['format', '--cdir', dirn, '--version', 'v0.1.22', '--date', '2025-10-03']
408
+ self.eq(1, await s_t_changelog.main(argv, outp))
409
+ outp.expect('Error running')
410
+
411
+ with s_common.genfile(fp) as fd:
412
+ fd.truncate(0)
413
+ fd.write(s_common.buid())
414
+ outp.clear()
415
+ argv = ['format', '--cdir', dirn, '--version', 'v0.1.22', '--date', '2025-10-03']
416
+ self.eq(1, await s_t_changelog.main(argv, outp))
417
+ outp.expect('No files passed validation')
418
+
419
+ with s_common.genfile(fp) as fd:
420
+ fd.truncate(0)
421
+ fd.write('''desc:literal: true
422
+ desc: |
423
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
424
+ type: feat
425
+ '''.encode())
426
+ outp.clear()
427
+ argv = ['format', '--cdir', dirn, '--version', 'v0.1.22', '--date', '2025-10-03']
428
+ self.eq(1, await s_t_changelog.main(argv, outp))
429
+ outp.expect('desc line 0 must start with "- "')
430
+
431
+ with s_common.genfile(fp) as fd:
432
+ fd.truncate(0)
433
+ fd.write('''desc:literal: true
434
+ desc: |
435
+ - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
436
+ xxxxxx
437
+ type: feat
438
+ '''.encode())
439
+ outp.clear()
440
+ argv = ['format', '--cdir', dirn, '--version', 'v0.1.22', '--date', '2025-10-03']
441
+ self.eq(1, await s_t_changelog.main(argv, outp))
442
+ outp.expect('desc line 1 must start with " "')
443
+
444
+ with s_common.genfile(fp) as fd:
445
+ fd.truncate(0)
446
+ fd.write('''desc:literal: true
447
+ desc: |
448
+ - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
449
+ type: feat
450
+ '''.encode())
451
+ outp.clear()
452
+ argv = ['format', '--cdir', dirn, '--version', 'v0.1.22', '--date', '2025-10-03']
453
+ self.eq(1, await s_t_changelog.main(argv, outp))
454
+ outp.expect('desc line 0 is too long, 79 > 79')