@@ -8,6 +8,7 @@ use datadog_api_client::datadogV2::api_workflow_automation::{
88
99use crate :: config:: Config ;
1010use crate :: formatter:: { self , Metadata } ;
11+ use crate :: raw_client;
1112use crate :: util;
1213use crate :: util_ext;
1314
@@ -54,6 +55,31 @@ pub async fn update(cfg: &Config, workflow_id: &str, file: &str) -> Result<()> {
5455 formatter:: output ( cfg, & resp)
5556}
5657
58+ pub async fn diff (
59+ cfg : & Config ,
60+ workflow_id : & str ,
61+ file : & str ,
62+ only : & [ String ] ,
63+ ignore : & [ String ] ,
64+ ) -> Result < ( ) > {
65+ let candidate: serde_json:: Value = util:: read_json_file ( file) ?;
66+ let live = raw_client:: raw_get ( cfg, & format ! ( "/api/v2/workflows/{workflow_id}" ) , & [ ] )
67+ . await
68+ . map_err ( |e| anyhow:: anyhow!( "failed to get workflow: {e:?}" ) ) ?;
69+
70+ let mut options = util_ext:: ResourceDiffOptions :: new (
71+ "workflows diff" ,
72+ "pup workflows update" ,
73+ "workflow" ,
74+ workflow_id,
75+ ) ;
76+ options. readonly_paths = util_ext:: READONLY_WORKFLOW_FIELDS ;
77+ options. only = only;
78+ options. ignore = ignore;
79+ options. no_changes_message = Some ( format ! ( "No changes - workflow {workflow_id} is in sync." ) ) ;
80+ util_ext:: format_resource_diff ( cfg, & live, & candidate, & options)
81+ }
82+
5783pub async fn delete ( cfg : & Config , workflow_id : & str ) -> Result < ( ) > {
5884 let api = make_api ( cfg) ;
5985 api. delete_workflow ( workflow_id. to_string ( ) )
@@ -270,6 +296,69 @@ mod tests {
270296
271297 use crate :: test_support:: * ;
272298
299+ #[ tokio:: test]
300+ async fn test_workflows_diff_detects_changes ( ) {
301+ let _lock = lock_env ( ) . await ;
302+ let mut server = mockito:: Server :: new_async ( ) . await ;
303+ let cfg = test_config ( & server. url ( ) ) ;
304+
305+ let live_body = r#"{
306+ "data": {
307+ "id": "wf-123",
308+ "type": "workflows",
309+ "attributes": {
310+ "action_id": "wf-123",
311+ "name": "Old workflow",
312+ "description": "Deploy service",
313+ "spec": {"steps": [{"name": "deploy", "timeout": 60}]},
314+ "updatedAt": "2024-01-01T00:00:00Z"
315+ }
316+ }
317+ }"# ;
318+ let _mock = mock_any ( & mut server, "GET" , live_body) . await ;
319+
320+ let candidate = r#"{
321+ "data": {
322+ "type": "workflows",
323+ "attributes": {
324+ "name": "New workflow",
325+ "description": "Deploy service",
326+ "spec": {"steps": [{"name": "deploy", "timeout": 90}]}
327+ }
328+ }
329+ }"# ;
330+ let path = write_temp_json ( "pup_workflows_diff_detects_changes.json" , candidate) ;
331+
332+ let result = super :: diff ( & cfg, "wf-123" , path. to_str ( ) . unwrap ( ) , & [ ] , & [ ] ) . await ;
333+ let _ = std:: fs:: remove_file ( path) ;
334+ assert ! ( result. is_ok( ) , "workflows diff failed: {:?}" , result. err( ) ) ;
335+ cleanup_env ( ) ;
336+ }
337+
338+ #[ tokio:: test]
339+ async fn test_workflows_diff_file_not_found ( ) {
340+ let cfg = test_config ( "http://unused.local" ) ;
341+ let result = super :: diff ( & cfg, "wf-123" , "/nonexistent/path.json" , & [ ] , & [ ] ) . await ;
342+ assert ! ( result. is_err( ) ) ;
343+ assert ! ( result
344+ . unwrap_err( )
345+ . to_string( )
346+ . contains( "failed to read file" ) ) ;
347+ }
348+
349+ #[ tokio:: test]
350+ async fn test_workflows_diff_invalid_json ( ) {
351+ let path = write_temp_json ( "pup_workflows_diff_invalid_json.json" , "not valid json {{{" ) ;
352+ let cfg = test_config ( "http://unused.local" ) ;
353+ let result = super :: diff ( & cfg, "wf-123" , path. to_str ( ) . unwrap ( ) , & [ ] , & [ ] ) . await ;
354+ let _ = std:: fs:: remove_file ( path) ;
355+ assert ! ( result. is_err( ) ) ;
356+ assert ! ( result
357+ . unwrap_err( )
358+ . to_string( )
359+ . contains( "failed to parse JSON" ) ) ;
360+ }
361+
273362 #[ tokio:: test]
274363 async fn test_connections_get ( ) {
275364 let _lock = lock_env ( ) . await ;
0 commit comments