@@ -145,6 +145,165 @@ def test_validate_valid_graph(self):
145145
146146 self .assertIn ('Validation passed' , result .output )
147147 self .assertIn ('Workflow is valid' , result .output )
148+
149+ def test_validate_missing_source_file (self ):
150+ content = '''
151+ <graphml xmlns:y="http://www.yworks.com/xml/graphml">
152+ <graph id="G" edgedefault="directed">
153+ <node id="n0">
154+ <data key="d0"><y:NodeLabel>n0:missing.py</y:NodeLabel></data>
155+ </node>
156+ </graph>
157+ </graphml>
158+ '''
159+ filepath = self .create_graph_file ('workflow.graphml' , content )
160+ source_dir = Path (self .temp_dir ) / 'src'
161+ source_dir .mkdir ()
162+
163+ result = self .runner .invoke (cli , ['validate' , filepath , '--source' , str (source_dir )])
164+
165+ self .assertIn ('Validation failed' , result .output )
166+ self .assertIn ('Missing source file' , result .output )
167+
168+ def test_validate_with_existing_source_file (self ):
169+ content = '''
170+ <graphml xmlns:y="http://www.yworks.com/xml/graphml">
171+ <graph id="G" edgedefault="directed">
172+ <node id="n0">
173+ <data key="d0"><y:NodeLabel>n0:exists.py</y:NodeLabel></data>
174+ </node>
175+ </graph>
176+ </graphml>
177+ '''
178+ filepath = self .create_graph_file ('workflow.graphml' , content )
179+ source_dir = Path (self .temp_dir ) / 'src'
180+ source_dir .mkdir ()
181+ (source_dir / 'exists.py' ).write_text ('print("hello")' )
182+
183+ result = self .runner .invoke (cli , ['validate' , filepath , '--source' , str (source_dir )])
184+
185+ self .assertIn ('Validation passed' , result .output )
186+
187+ def test_validate_zmq_port_conflict (self ):
188+ content = '''
189+ <graphml xmlns:y="http://www.yworks.com/xml/graphml">
190+ <graph id="G" edgedefault="directed">
191+ <node id="n0">
192+ <data key="d0"><y:NodeLabel>n0:script1.py</y:NodeLabel></data>
193+ </node>
194+ <node id="n1">
195+ <data key="d0"><y:NodeLabel>n1:script2.py</y:NodeLabel></data>
196+ </node>
197+ <edge source="n0" target="n1">
198+ <data key="d1"><y:EdgeLabel>0x1234_portA</y:EdgeLabel></data>
199+ </edge>
200+ <edge source="n1" target="n0">
201+ <data key="d1"><y:EdgeLabel>0x1234_portB</y:EdgeLabel></data>
202+ </edge>
203+ </graph>
204+ </graphml>
205+ '''
206+ filepath = self .create_graph_file ('conflict.graphml' , content )
207+
208+ result = self .runner .invoke (cli , ['validate' , filepath ])
209+
210+ self .assertIn ('Validation failed' , result .output )
211+ self .assertIn ('Port conflict' , result .output )
212+
213+ def test_validate_reserved_port (self ):
214+ content = '''
215+ <graphml xmlns:y="http://www.yworks.com/xml/graphml">
216+ <graph id="G" edgedefault="directed">
217+ <node id="n0">
218+ <data key="d0"><y:NodeLabel>n0:script1.py</y:NodeLabel></data>
219+ </node>
220+ <node id="n1">
221+ <data key="d0"><y:NodeLabel>n1:script2.py</y:NodeLabel></data>
222+ </node>
223+ <edge source="n0" target="n1">
224+ <data key="d1"><y:EdgeLabel>0x50_data</y:EdgeLabel></data>
225+ </edge>
226+ </graph>
227+ </graphml>
228+ '''
229+ filepath = self .create_graph_file ('reserved.graphml' , content )
230+
231+ result = self .runner .invoke (cli , ['validate' , filepath ])
232+
233+ self .assertIn ('Port 80' , result .output )
234+ self .assertIn ('reserved range' , result .output )
235+
236+ def test_validate_cycle_detection (self ):
237+ content = '''
238+ <graphml xmlns:y="http://www.yworks.com/xml/graphml">
239+ <graph id="G" edgedefault="directed">
240+ <node id="n0">
241+ <data key="d0"><y:NodeLabel>n0:controller.py</y:NodeLabel></data>
242+ </node>
243+ <node id="n1">
244+ <data key="d0"><y:NodeLabel>n1:plant.py</y:NodeLabel></data>
245+ </node>
246+ <edge source="n0" target="n1">
247+ <data key="d1"><y:EdgeLabel>control_signal</y:EdgeLabel></data>
248+ </edge>
249+ <edge source="n1" target="n0">
250+ <data key="d1"><y:EdgeLabel>sensor_data</y:EdgeLabel></data>
251+ </edge>
252+ </graph>
253+ </graphml>
254+ '''
255+ filepath = self .create_graph_file ('cycle.graphml' , content )
256+
257+ result = self .runner .invoke (cli , ['validate' , filepath ])
258+
259+ self .assertIn ('cycles' , result .output )
260+ self .assertIn ('control loops' , result .output )
261+
262+ def test_validate_port_zero (self ):
263+ content = '''
264+ <graphml xmlns:y="http://www.yworks.com/xml/graphml">
265+ <graph id="G" edgedefault="directed">
266+ <node id="n0">
267+ <data key="d0"><y:NodeLabel>n0:script1.py</y:NodeLabel></data>
268+ </node>
269+ <node id="n1">
270+ <data key="d0"><y:NodeLabel>n1:script2.py</y:NodeLabel></data>
271+ </node>
272+ <edge source="n0" target="n1">
273+ <data key="d1"><y:EdgeLabel>0x0_invalid</y:EdgeLabel></data>
274+ </edge>
275+ </graph>
276+ </graphml>
277+ '''
278+ filepath = self .create_graph_file ('port_zero.graphml' , content )
279+
280+ result = self .runner .invoke (cli , ['validate' , filepath ])
281+
282+ self .assertIn ('Validation failed' , result .output )
283+ self .assertIn ('must be at least 1' , result .output )
284+
285+ def test_validate_port_exceeds_maximum (self ):
286+ content = '''
287+ <graphml xmlns:y="http://www.yworks.com/xml/graphml">
288+ <graph id="G" edgedefault="directed">
289+ <node id="n0">
290+ <data key="d0"><y:NodeLabel>n0:script1.py</y:NodeLabel></data>
291+ </node>
292+ <node id="n1">
293+ <data key="d0"><y:NodeLabel>n1:script2.py</y:NodeLabel></data>
294+ </node>
295+ <edge source="n0" target="n1">
296+ <data key="d1"><y:EdgeLabel>0x10000_toobig</y:EdgeLabel></data>
297+ </edge>
298+ </graph>
299+ </graphml>
300+ '''
301+ filepath = self .create_graph_file ('port_max.graphml' , content )
302+
303+ result = self .runner .invoke (cli , ['validate' , filepath ])
304+
305+ self .assertIn ('Validation failed' , result .output )
306+ self .assertIn ('exceeds maximum (65535)' , result .output )
148307
149308if __name__ == '__main__' :
150309 unittest .main ()
0 commit comments