yamlgraph 0.1.1__py3-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 yamlgraph might be problematic. Click here for more details.

Files changed (111) hide show
  1. examples/__init__.py +1 -0
  2. examples/storyboard/__init__.py +1 -0
  3. examples/storyboard/generate_videos.py +335 -0
  4. examples/storyboard/nodes/__init__.py +10 -0
  5. examples/storyboard/nodes/animated_character_node.py +248 -0
  6. examples/storyboard/nodes/animated_image_node.py +138 -0
  7. examples/storyboard/nodes/character_node.py +162 -0
  8. examples/storyboard/nodes/image_node.py +118 -0
  9. examples/storyboard/nodes/replicate_tool.py +238 -0
  10. examples/storyboard/retry_images.py +118 -0
  11. tests/__init__.py +1 -0
  12. tests/conftest.py +178 -0
  13. tests/integration/__init__.py +1 -0
  14. tests/integration/test_animated_storyboard.py +63 -0
  15. tests/integration/test_cli_commands.py +242 -0
  16. tests/integration/test_map_demo.py +50 -0
  17. tests/integration/test_memory_demo.py +281 -0
  18. tests/integration/test_pipeline_flow.py +105 -0
  19. tests/integration/test_providers.py +163 -0
  20. tests/integration/test_resume.py +75 -0
  21. tests/unit/__init__.py +1 -0
  22. tests/unit/test_agent_nodes.py +200 -0
  23. tests/unit/test_checkpointer.py +212 -0
  24. tests/unit/test_cli.py +121 -0
  25. tests/unit/test_cli_package.py +81 -0
  26. tests/unit/test_compile_graph_map.py +132 -0
  27. tests/unit/test_conditions_routing.py +253 -0
  28. tests/unit/test_config.py +93 -0
  29. tests/unit/test_conversation_memory.py +270 -0
  30. tests/unit/test_database.py +145 -0
  31. tests/unit/test_deprecation.py +104 -0
  32. tests/unit/test_executor.py +60 -0
  33. tests/unit/test_executor_async.py +179 -0
  34. tests/unit/test_export.py +150 -0
  35. tests/unit/test_expressions.py +178 -0
  36. tests/unit/test_format_prompt.py +145 -0
  37. tests/unit/test_generic_report.py +200 -0
  38. tests/unit/test_graph_commands.py +327 -0
  39. tests/unit/test_graph_loader.py +299 -0
  40. tests/unit/test_graph_schema.py +193 -0
  41. tests/unit/test_inline_schema.py +151 -0
  42. tests/unit/test_issues.py +164 -0
  43. tests/unit/test_jinja2_prompts.py +85 -0
  44. tests/unit/test_langsmith.py +319 -0
  45. tests/unit/test_llm_factory.py +109 -0
  46. tests/unit/test_llm_factory_async.py +118 -0
  47. tests/unit/test_loops.py +403 -0
  48. tests/unit/test_map_node.py +144 -0
  49. tests/unit/test_no_backward_compat.py +56 -0
  50. tests/unit/test_node_factory.py +225 -0
  51. tests/unit/test_prompts.py +166 -0
  52. tests/unit/test_python_nodes.py +198 -0
  53. tests/unit/test_reliability.py +298 -0
  54. tests/unit/test_result_export.py +234 -0
  55. tests/unit/test_router.py +296 -0
  56. tests/unit/test_sanitize.py +99 -0
  57. tests/unit/test_schema_loader.py +295 -0
  58. tests/unit/test_shell_tools.py +229 -0
  59. tests/unit/test_state_builder.py +331 -0
  60. tests/unit/test_state_builder_map.py +104 -0
  61. tests/unit/test_state_config.py +197 -0
  62. tests/unit/test_template.py +190 -0
  63. tests/unit/test_tool_nodes.py +129 -0
  64. yamlgraph/__init__.py +35 -0
  65. yamlgraph/builder.py +110 -0
  66. yamlgraph/cli/__init__.py +139 -0
  67. yamlgraph/cli/__main__.py +6 -0
  68. yamlgraph/cli/commands.py +232 -0
  69. yamlgraph/cli/deprecation.py +92 -0
  70. yamlgraph/cli/graph_commands.py +382 -0
  71. yamlgraph/cli/validators.py +37 -0
  72. yamlgraph/config.py +67 -0
  73. yamlgraph/constants.py +66 -0
  74. yamlgraph/error_handlers.py +226 -0
  75. yamlgraph/executor.py +275 -0
  76. yamlgraph/executor_async.py +122 -0
  77. yamlgraph/graph_loader.py +337 -0
  78. yamlgraph/map_compiler.py +138 -0
  79. yamlgraph/models/__init__.py +36 -0
  80. yamlgraph/models/graph_schema.py +141 -0
  81. yamlgraph/models/schemas.py +124 -0
  82. yamlgraph/models/state_builder.py +236 -0
  83. yamlgraph/node_factory.py +240 -0
  84. yamlgraph/routing.py +87 -0
  85. yamlgraph/schema_loader.py +160 -0
  86. yamlgraph/storage/__init__.py +17 -0
  87. yamlgraph/storage/checkpointer.py +72 -0
  88. yamlgraph/storage/database.py +320 -0
  89. yamlgraph/storage/export.py +269 -0
  90. yamlgraph/tools/__init__.py +1 -0
  91. yamlgraph/tools/agent.py +235 -0
  92. yamlgraph/tools/nodes.py +124 -0
  93. yamlgraph/tools/python_tool.py +178 -0
  94. yamlgraph/tools/shell.py +205 -0
  95. yamlgraph/utils/__init__.py +47 -0
  96. yamlgraph/utils/conditions.py +157 -0
  97. yamlgraph/utils/expressions.py +111 -0
  98. yamlgraph/utils/langsmith.py +308 -0
  99. yamlgraph/utils/llm_factory.py +118 -0
  100. yamlgraph/utils/llm_factory_async.py +105 -0
  101. yamlgraph/utils/logging.py +127 -0
  102. yamlgraph/utils/prompts.py +116 -0
  103. yamlgraph/utils/sanitize.py +98 -0
  104. yamlgraph/utils/template.py +102 -0
  105. yamlgraph/utils/validators.py +181 -0
  106. yamlgraph-0.1.1.dist-info/METADATA +854 -0
  107. yamlgraph-0.1.1.dist-info/RECORD +111 -0
  108. yamlgraph-0.1.1.dist-info/WHEEL +5 -0
  109. yamlgraph-0.1.1.dist-info/entry_points.txt +2 -0
  110. yamlgraph-0.1.1.dist-info/licenses/LICENSE +21 -0
  111. yamlgraph-0.1.1.dist-info/top_level.txt +3 -0
@@ -0,0 +1,200 @@
1
+ """Tests for Phase 6.5: Generic Report Schema.
2
+
3
+ Tests for flexible GenericReport model that works for most analysis/summary tasks.
4
+ """
5
+
6
+ import pytest
7
+ from pydantic import ValidationError
8
+
9
+
10
+ class TestGenericReportSchema:
11
+ """Tests for GenericReport model."""
12
+
13
+ def test_generic_report_exists(self):
14
+ """GenericReport model is importable."""
15
+ from yamlgraph.models.schemas import GenericReport
16
+
17
+ assert GenericReport is not None
18
+
19
+ def test_minimal_report(self):
20
+ """Report works with just title and summary."""
21
+ from yamlgraph.models.schemas import GenericReport
22
+
23
+ report = GenericReport(
24
+ title="Test Report",
25
+ summary="A brief summary of findings.",
26
+ )
27
+
28
+ assert report.title == "Test Report"
29
+ assert report.summary == "A brief summary of findings."
30
+
31
+ def test_report_with_sections(self):
32
+ """Sections field accepts arbitrary dict content."""
33
+ from yamlgraph.models.schemas import GenericReport
34
+
35
+ report = GenericReport(
36
+ title="Report",
37
+ summary="Summary",
38
+ sections={
39
+ "overview": "First section content",
40
+ "details": {"nested": "data", "count": 42},
41
+ "items": ["a", "b", "c"],
42
+ },
43
+ )
44
+
45
+ assert report.sections["overview"] == "First section content"
46
+ assert report.sections["details"]["count"] == 42
47
+ assert len(report.sections["items"]) == 3
48
+
49
+ def test_report_with_findings(self):
50
+ """Findings field is list of strings."""
51
+ from yamlgraph.models.schemas import GenericReport
52
+
53
+ report = GenericReport(
54
+ title="Report",
55
+ summary="Summary",
56
+ findings=["Finding 1", "Finding 2", "Finding 3"],
57
+ )
58
+
59
+ assert len(report.findings) == 3
60
+ assert "Finding 1" in report.findings
61
+
62
+ def test_report_with_recommendations(self):
63
+ """Recommendations field is list of strings."""
64
+ from yamlgraph.models.schemas import GenericReport
65
+
66
+ report = GenericReport(
67
+ title="Report",
68
+ summary="Summary",
69
+ recommendations=["Action 1", "Action 2"],
70
+ )
71
+
72
+ assert len(report.recommendations) == 2
73
+
74
+ def test_report_with_metadata(self):
75
+ """Metadata field accepts arbitrary key-value data."""
76
+ from yamlgraph.models.schemas import GenericReport
77
+
78
+ report = GenericReport(
79
+ title="Report",
80
+ summary="Summary",
81
+ metadata={
82
+ "author": "Test Author",
83
+ "version": 1.0,
84
+ "tags": ["a", "b"],
85
+ },
86
+ )
87
+
88
+ assert report.metadata["author"] == "Test Author"
89
+ assert report.metadata["version"] == 1.0
90
+
91
+ def test_defaults_are_empty(self):
92
+ """Optional fields default to empty collections."""
93
+ from yamlgraph.models.schemas import GenericReport
94
+
95
+ report = GenericReport(title="Report", summary="Summary")
96
+
97
+ assert report.sections == {}
98
+ assert report.findings == []
99
+ assert report.recommendations == []
100
+ assert report.metadata == {}
101
+
102
+ def test_title_is_required(self):
103
+ """Title field is required."""
104
+ from yamlgraph.models.schemas import GenericReport
105
+
106
+ with pytest.raises(ValidationError) as exc_info:
107
+ GenericReport(summary="Summary")
108
+
109
+ errors = exc_info.value.errors()
110
+ assert any(e["loc"] == ("title",) for e in errors)
111
+
112
+ def test_summary_is_required(self):
113
+ """Summary field is required."""
114
+ from yamlgraph.models.schemas import GenericReport
115
+
116
+ with pytest.raises(ValidationError) as exc_info:
117
+ GenericReport(title="Title")
118
+
119
+ errors = exc_info.value.errors()
120
+ assert any(e["loc"] == ("summary",) for e in errors)
121
+
122
+ def test_model_serializes_to_dict(self):
123
+ """Report serializes to dictionary."""
124
+ from yamlgraph.models.schemas import GenericReport
125
+
126
+ report = GenericReport(
127
+ title="Test",
128
+ summary="Summary",
129
+ findings=["A", "B"],
130
+ )
131
+
132
+ data = report.model_dump()
133
+
134
+ assert data["title"] == "Test"
135
+ assert data["summary"] == "Summary"
136
+ assert data["findings"] == ["A", "B"]
137
+
138
+ def test_model_serializes_to_json(self):
139
+ """Report serializes to JSON string."""
140
+ import json
141
+
142
+ from yamlgraph.models.schemas import GenericReport
143
+
144
+ report = GenericReport(title="Test", summary="Summary")
145
+
146
+ json_str = report.model_dump_json()
147
+ data = json.loads(json_str)
148
+
149
+ assert data["title"] == "Test"
150
+
151
+
152
+ class TestGenericReportUseCases:
153
+ """Verify GenericReport works for common analysis patterns."""
154
+
155
+ def test_git_analysis_report(self):
156
+ """GenericReport works for git analysis output."""
157
+ from yamlgraph.models.schemas import GenericReport
158
+
159
+ report = GenericReport(
160
+ title="Git Repository Analysis",
161
+ summary="Analysis of recent activity in the repository.",
162
+ sections={
163
+ "commit_summary": "15 commits in last 7 days",
164
+ "authors": ["alice", "bob", "carol"],
165
+ },
166
+ findings=[
167
+ "High activity in src/core module",
168
+ "No tests for new features",
169
+ "Breaking changes in v2.0",
170
+ ],
171
+ recommendations=[
172
+ "Add tests for recent changes",
173
+ "Review breaking changes before release",
174
+ ],
175
+ metadata={
176
+ "repo": "langgraph-showcase",
177
+ "analyzed_at": "2024-01-01T00:00:00",
178
+ },
179
+ )
180
+
181
+ assert "Git Repository" in report.title
182
+ assert len(report.findings) == 3
183
+ assert report.metadata["repo"] == "langgraph-showcase"
184
+
185
+ def test_api_analysis_report(self):
186
+ """GenericReport works for API analysis output."""
187
+ from yamlgraph.models.schemas import GenericReport
188
+
189
+ report = GenericReport(
190
+ title="API Performance Report",
191
+ summary="Performance analysis of API endpoints.",
192
+ sections={
193
+ "latency": {"p50": 45, "p95": 120, "p99": 250},
194
+ "errors": {"rate": 0.02, "top_errors": ["500", "429"]},
195
+ },
196
+ findings=["High latency on /search endpoint"],
197
+ recommendations=["Add caching for /search"],
198
+ )
199
+
200
+ assert report.sections["latency"]["p95"] == 120
@@ -0,0 +1,327 @@
1
+ """Tests for universal graph runner (Phase 7.2).
2
+
3
+ TDD tests for `yamlgraph graph run <path>` command.
4
+ """
5
+
6
+ import argparse
7
+ from pathlib import Path
8
+ from unittest.mock import MagicMock, patch
9
+
10
+ import pytest
11
+
12
+ # =============================================================================
13
+ # graph subcommand tests
14
+ # =============================================================================
15
+
16
+
17
+ class TestGraphSubcommand:
18
+ """Tests for graph subcommand group."""
19
+
20
+ def test_graph_subparser_exists(self):
21
+ """graph subparser should be configured."""
22
+ from yamlgraph.cli import create_parser
23
+
24
+ parser = create_parser()
25
+ # Parse with graph command
26
+ args = parser.parse_args(["graph", "list"])
27
+ assert args.command == "graph"
28
+
29
+ def test_graph_run_subcommand_exists(self):
30
+ """graph run subcommand should exist."""
31
+ from yamlgraph.cli import create_parser
32
+
33
+ parser = create_parser()
34
+ args = parser.parse_args(
35
+ ["graph", "run", "graphs/yamlgraph.yaml", "--var", "topic=AI"]
36
+ )
37
+ assert args.graph_command == "run"
38
+ assert args.graph_path == "graphs/yamlgraph.yaml"
39
+
40
+ def test_graph_list_subcommand_exists(self):
41
+ """graph list subcommand should exist."""
42
+ from yamlgraph.cli import create_parser
43
+
44
+ parser = create_parser()
45
+ args = parser.parse_args(["graph", "list"])
46
+ assert args.graph_command == "list"
47
+
48
+ def test_graph_info_subcommand_exists(self):
49
+ """graph info subcommand should exist."""
50
+ from yamlgraph.cli import create_parser
51
+
52
+ parser = create_parser()
53
+ args = parser.parse_args(["graph", "info", "graphs/yamlgraph.yaml"])
54
+ assert args.graph_command == "info"
55
+ assert args.graph_path == "graphs/yamlgraph.yaml"
56
+
57
+
58
+ # =============================================================================
59
+ # graph run argument parsing tests
60
+ # =============================================================================
61
+
62
+
63
+ class TestGraphRunArgs:
64
+ """Tests for graph run argument parsing."""
65
+
66
+ def test_var_single_value(self):
67
+ """--var key=value should parse correctly."""
68
+ from yamlgraph.cli import create_parser
69
+
70
+ parser = create_parser()
71
+ args = parser.parse_args(
72
+ ["graph", "run", "graphs/test.yaml", "--var", "topic=AI"]
73
+ )
74
+ assert args.var == ["topic=AI"]
75
+
76
+ def test_var_multiple_values(self):
77
+ """Multiple --var flags should accumulate."""
78
+ from yamlgraph.cli import create_parser
79
+
80
+ parser = create_parser()
81
+ args = parser.parse_args(
82
+ [
83
+ "graph",
84
+ "run",
85
+ "graphs/test.yaml",
86
+ "--var",
87
+ "topic=AI",
88
+ "--var",
89
+ "style=casual",
90
+ ]
91
+ )
92
+ assert args.var == ["topic=AI", "style=casual"]
93
+
94
+ def test_thread_argument(self):
95
+ """--thread should set thread ID."""
96
+ from yamlgraph.cli import create_parser
97
+
98
+ parser = create_parser()
99
+ args = parser.parse_args(
100
+ ["graph", "run", "graphs/test.yaml", "--thread", "abc123"]
101
+ )
102
+ assert args.thread == "abc123"
103
+
104
+ def test_export_flag(self):
105
+ """--export flag should enable export."""
106
+ from yamlgraph.cli import create_parser
107
+
108
+ parser = create_parser()
109
+ args = parser.parse_args(["graph", "run", "graphs/test.yaml", "--export"])
110
+ assert args.export is True
111
+
112
+ def test_graph_path_required(self):
113
+ """graph run requires a path argument."""
114
+ from yamlgraph.cli import create_parser
115
+
116
+ parser = create_parser()
117
+ with pytest.raises(SystemExit):
118
+ parser.parse_args(["graph", "run"])
119
+
120
+
121
+ # =============================================================================
122
+ # parse_vars helper tests
123
+ # =============================================================================
124
+
125
+
126
+ class TestParseVars:
127
+ """Tests for --var parsing helper."""
128
+
129
+ def test_parse_single_var(self):
130
+ """Single var should parse to dict."""
131
+ from yamlgraph.cli.graph_commands import parse_vars
132
+
133
+ result = parse_vars(["topic=AI"])
134
+ assert result == {"topic": "AI"}
135
+
136
+ def test_parse_multiple_vars(self):
137
+ """Multiple vars should parse to dict."""
138
+ from yamlgraph.cli.graph_commands import parse_vars
139
+
140
+ result = parse_vars(["topic=AI", "style=casual", "count=5"])
141
+ assert result == {"topic": "AI", "style": "casual", "count": "5"}
142
+
143
+ def test_parse_empty_list(self):
144
+ """Empty list returns empty dict."""
145
+ from yamlgraph.cli.graph_commands import parse_vars
146
+
147
+ result = parse_vars([])
148
+ assert result == {}
149
+
150
+ def test_parse_none_returns_empty(self):
151
+ """None returns empty dict."""
152
+ from yamlgraph.cli.graph_commands import parse_vars
153
+
154
+ result = parse_vars(None)
155
+ assert result == {}
156
+
157
+ def test_parse_value_with_equals(self):
158
+ """Value containing = should preserve it."""
159
+ from yamlgraph.cli.graph_commands import parse_vars
160
+
161
+ result = parse_vars(["equation=a=b+c"])
162
+ assert result == {"equation": "a=b+c"}
163
+
164
+ def test_parse_invalid_format_raises(self):
165
+ """Invalid format (no =) should raise ValueError."""
166
+ from yamlgraph.cli.graph_commands import parse_vars
167
+
168
+ with pytest.raises(ValueError, match="Invalid"):
169
+ parse_vars(["invalid"])
170
+
171
+
172
+ # =============================================================================
173
+ # cmd_graph_run tests
174
+ # =============================================================================
175
+
176
+
177
+ class TestCmdGraphRun:
178
+ """Tests for cmd_graph_run function."""
179
+
180
+ def test_cmd_graph_run_exists(self):
181
+ """cmd_graph_run function should exist."""
182
+ from yamlgraph.cli.graph_commands import cmd_graph_run
183
+
184
+ assert callable(cmd_graph_run)
185
+
186
+ def test_graph_not_found_error(self):
187
+ """Should error if graph file doesn't exist."""
188
+ from yamlgraph.cli.graph_commands import cmd_graph_run
189
+
190
+ args = argparse.Namespace(
191
+ graph_path="nonexistent.yaml",
192
+ var=[],
193
+ thread=None,
194
+ export=False,
195
+ )
196
+
197
+ with pytest.raises(SystemExit):
198
+ cmd_graph_run(args)
199
+
200
+ @patch("yamlgraph.graph_loader.load_and_compile")
201
+ def test_invokes_graph_with_vars(self, mock_load):
202
+ """Should invoke graph with parsed vars as initial state."""
203
+ from yamlgraph.cli.graph_commands import cmd_graph_run
204
+
205
+ mock_graph = MagicMock()
206
+ mock_app = MagicMock()
207
+ mock_app.invoke.return_value = {"result": "success"}
208
+ mock_graph.compile.return_value = mock_app
209
+ mock_load.return_value = mock_graph
210
+
211
+ args = argparse.Namespace(
212
+ graph_path="graphs/yamlgraph.yaml",
213
+ var=["topic=AI", "style=casual"],
214
+ thread=None,
215
+ export=False,
216
+ )
217
+
218
+ # Mock Path.exists
219
+ with patch.object(Path, "exists", return_value=True):
220
+ cmd_graph_run(args)
221
+
222
+ mock_app.invoke.assert_called_once()
223
+ call_args = mock_app.invoke.call_args[0][0]
224
+ assert call_args["topic"] == "AI"
225
+ assert call_args["style"] == "casual"
226
+
227
+
228
+ # =============================================================================
229
+ # cmd_graph_list tests
230
+ # =============================================================================
231
+
232
+
233
+ class TestCmdGraphList:
234
+ """Tests for cmd_graph_list function."""
235
+
236
+ def test_cmd_graph_list_exists(self):
237
+ """cmd_graph_list function should exist."""
238
+ from yamlgraph.cli.graph_commands import cmd_graph_list
239
+
240
+ assert callable(cmd_graph_list)
241
+
242
+ @patch("yamlgraph.cli.graph_commands.Path")
243
+ def test_lists_yaml_files(self, mock_path):
244
+ """Should list all .yaml files in graphs/."""
245
+ from yamlgraph.cli.graph_commands import cmd_graph_list
246
+
247
+ mock_graphs_dir = MagicMock()
248
+ mock_path.return_value = mock_graphs_dir
249
+ mock_graphs_dir.exists.return_value = True
250
+ mock_graphs_dir.glob.return_value = [
251
+ Path("graphs/yamlgraph.yaml"),
252
+ Path("graphs/router-demo.yaml"),
253
+ ]
254
+
255
+ args = argparse.Namespace()
256
+
257
+ with patch("builtins.print") as mock_print:
258
+ cmd_graph_list(args)
259
+ # Check it printed something about the graphs
260
+ calls = [str(c) for c in mock_print.call_args_list]
261
+ assert any("yamlgraph" in c for c in calls)
262
+
263
+
264
+ # =============================================================================
265
+ # cmd_graph_info tests
266
+ # =============================================================================
267
+
268
+
269
+ class TestCmdGraphInfo:
270
+ """Tests for cmd_graph_info function."""
271
+
272
+ def test_cmd_graph_info_exists(self):
273
+ """cmd_graph_info function should exist."""
274
+ from yamlgraph.cli.graph_commands import cmd_graph_info
275
+
276
+ assert callable(cmd_graph_info)
277
+
278
+ def test_info_file_not_found(self):
279
+ """Should error if graph file doesn't exist."""
280
+ from yamlgraph.cli.graph_commands import cmd_graph_info
281
+
282
+ args = argparse.Namespace(graph_path="nonexistent.yaml")
283
+
284
+ with pytest.raises(SystemExit):
285
+ cmd_graph_info(args)
286
+
287
+
288
+ # =============================================================================
289
+ # cmd_graph_validate tests
290
+ # =============================================================================
291
+
292
+
293
+ class TestCmdGraphValidate:
294
+ """Tests for cmd_graph_validate function."""
295
+
296
+ def test_cmd_graph_validate_exists(self):
297
+ """cmd_graph_validate function should exist."""
298
+ from yamlgraph.cli.graph_commands import cmd_graph_validate
299
+
300
+ assert callable(cmd_graph_validate)
301
+
302
+ def test_validate_file_not_found(self):
303
+ """Should error if graph file doesn't exist."""
304
+ from yamlgraph.cli.graph_commands import cmd_graph_validate
305
+
306
+ args = argparse.Namespace(graph_path="nonexistent.yaml")
307
+
308
+ with pytest.raises(SystemExit):
309
+ cmd_graph_validate(args)
310
+
311
+ def test_validate_valid_graph(self):
312
+ """Should validate a correct graph without errors."""
313
+ from yamlgraph.cli.graph_commands import cmd_graph_validate
314
+
315
+ args = argparse.Namespace(graph_path="graphs/yamlgraph.yaml")
316
+
317
+ # Should not raise
318
+ cmd_graph_validate(args)
319
+
320
+ def test_validate_subparser_exists(self):
321
+ """graph validate subcommand should exist."""
322
+ from yamlgraph.cli import create_parser
323
+
324
+ parser = create_parser()
325
+ args = parser.parse_args(["graph", "validate", "graphs/yamlgraph.yaml"])
326
+ assert args.graph_command == "validate"
327
+ assert args.graph_path == "graphs/yamlgraph.yaml"