From 74f4b6972b7adbb8230a4ec998b4f3b7fda185f5 Mon Sep 17 00:00:00 2001
From: Steve Lee <slee@microsoft.com>
Date: Wed, 5 Mar 2025 21:01:11 -0800
Subject: [PATCH 1/3] Fix when resource test returns '_inDesiredState', that
 takes precedence

---
 dsc/tests/dsc_config_test.tests.ps1           | 27 +++++++++++++++++++
 dsc_lib/locales/en-us.toml                    |  3 ++-
 dsc_lib/src/dscresources/command_resource.rs  | 22 +++++++++++++--
 .../dscindesiredstate.dsc.resource.json       | 26 ++++++++++++++++++
 tools/dsctest/src/args.rs                     |  7 +++++
 tools/dsctest/src/in_desired_state.rs         | 13 +++++++++
 tools/dsctest/src/main.rs                     | 16 +++++++++++
 7 files changed, 111 insertions(+), 3 deletions(-)
 create mode 100644 tools/dsctest/dscindesiredstate.dsc.resource.json
 create mode 100644 tools/dsctest/src/in_desired_state.rs

diff --git a/dsc/tests/dsc_config_test.tests.ps1 b/dsc/tests/dsc_config_test.tests.ps1
index 6b725467..be2db81d 100644
--- a/dsc/tests/dsc_config_test.tests.ps1
+++ b/dsc/tests/dsc_config_test.tests.ps1
@@ -32,4 +32,31 @@ Describe 'dsc config test tests' {
             $out.results[0].result.differingProperties | Should -Contain 'resources'
         }
     }
+
+    It '_inDesiredState returned is used when: <inDesiredState>' -TestCases @(
+        @{ inDesiredState = $true }
+        @{ inDesiredState = $false }
+    ) {
+        param($inDesiredState)
+
+        $configYaml = @"
+  `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
+  resources:
+    - name: Test
+      type: Test/InDesiredState
+      properties:
+        _inDesiredState: $inDesiredState
+        value: Hello
+"@
+
+        $out = dsc config test -i $configYaml | ConvertFrom-Json
+        $LASTEXITCODE | Should -Be 0
+        $out.results[0].result.inDesiredState | Should -Be $inDesiredState
+        if ($inDesiredState) {
+            $out.results[0].result.differingProperties | Should -BeNullOrEmpty
+        }
+        else {
+            $out.results[0].result.differingProperties | Should -Contain 'value'
+        }
+    }
 }
diff --git a/dsc_lib/locales/en-us.toml b/dsc_lib/locales/en-us.toml
index 17ce4540..6393e0fd 100644
--- a/dsc_lib/locales/en-us.toml
+++ b/dsc_lib/locales/en-us.toml
@@ -99,7 +99,7 @@ setUnexpectedOutput = "Command did not return expected actual output"
 setUnexpectedDiff = "Command did not return expected diff output"
 invokeTest = "Invoking test for '%{resource}'"
 testSyntheticTest = "Resource '%{resource}' does not implement test, performing synthetic test"
-invokeTestUsing = "Invoking test on '%{resource}' using '{executable}'"
+invokeTestUsing = "Invoking test on '%{resource}' using '%{executable}'"
 testVerifyOutput = "Verifying output of test on '%{resource}' using '%{executable}'"
 testGroupTestResponse = "Import resource kind, returning group test response"
 testNoActualState = "No actual state returned"
@@ -129,6 +129,7 @@ validateJson = "Validating against JSON: %{json}"
 resourceInvalidJson = "Resource reported input JSON is not valid"
 invalidArrayKey = "Unsupported array value for key '%{key}'.  Only string and number is supported."
 invalidKey = "Unsupported value for key '%{key}'.  Only string, bool, number, and array is supported."
+inDesiredStateNotBool = "'_inDesiredState' is not a boolean"
 
 [dscresources.dscresource]
 invokeGet = "Invoking get for '%{resource}'"
diff --git a/dsc_lib/src/dscresources/command_resource.rs b/dsc_lib/src/dscresources/command_resource.rs
index bd3442c0..9957e11d 100644
--- a/dsc_lib/src/dscresources/command_resource.rs
+++ b/dsc_lib/src/dscresources/command_resource.rs
@@ -283,11 +283,29 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Re
                     return Err(DscError::Operation(t!("dscresources.commandResource.failedParseJson", executable = &test.executable, stdout = stdout, stderr = stderr, err = err).to_string()))
                 }
             };
-            let diff_properties = get_diff(&expected_value, &actual_value);
+            // if actual state contains _inDesiredState, we use that to determine if the resource is in desired state
+            let mut in_desired_state: Option<bool> = None;
+            if let Some(in_desired_state_value) = actual_value.get("_inDesiredState") {
+                if let Some(desired_state) = in_desired_state_value.as_bool() {
+                    in_desired_state = Some(desired_state);
+                } else {
+                    return Err(DscError::Operation(t!("dscresources.commandResource.inDesiredStateNotBool").to_string()));
+                }
+            }
+
+            let mut diff_properties: Vec<String> = Vec::new();
+            match in_desired_state {
+                Some(true) => {
+                    // if _inDesiredState is true, we don't need to check for diff properties
+                },
+                Some(false) | None => {
+                    diff_properties = get_diff(&expected_value, &actual_value);
+                }
+            }
             Ok(TestResult::Resource(ResourceTestResponse {
                 desired_state: expected_value,
                 actual_state: actual_value,
-                in_desired_state: diff_properties.is_empty(),
+                in_desired_state: in_desired_state.unwrap_or(diff_properties.is_empty()),
                 diff_properties,
             }))
         },
diff --git a/tools/dsctest/dscindesiredstate.dsc.resource.json b/tools/dsctest/dscindesiredstate.dsc.resource.json
new file mode 100644
index 00000000..be077603
--- /dev/null
+++ b/tools/dsctest/dscindesiredstate.dsc.resource.json
@@ -0,0 +1,26 @@
+{
+    "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json",
+    "type": "Test/InDesiredState",
+    "version": "0.1.0",
+    "test": {
+        "executable": "dsctest",
+        "args": [
+            "in-desired-state",
+            {
+                "jsonInputArg": "--input",
+                "mandatory": true
+            }
+        ],
+        "return": "state"
+    },
+    "schema": {
+        "command": {
+            "executable": "dsctest",
+            "args": [
+                "schema",
+                "-s",
+                "in-desired-state"
+            ]
+        }
+    }
+}
diff --git a/tools/dsctest/src/args.rs b/tools/dsctest/src/args.rs
index 17127461..7b4e3f26 100644
--- a/tools/dsctest/src/args.rs
+++ b/tools/dsctest/src/args.rs
@@ -8,6 +8,7 @@ pub enum Schemas {
     Delete,
     Exist,
     ExitCode,
+    InDesiredState,
     Sleep,
     Trace,
     WhatIf,
@@ -41,6 +42,12 @@ pub enum SubCommand {
         input: String,
     },
 
+    #[clap(name = "in-desired-state", about = "Specify if the resource is in the desired state")]
+    InDesiredState {
+        #[clap(name = "input", short, long, help = "The input to the in desired state command as JSON")]
+        input: String,
+    },
+
     #[clap(name = "schema", about = "Get the JSON schema for a subcommand")]
     Schema {
         #[clap(name = "subcommand", short, long, help = "The subcommand to get the schema for")]
diff --git a/tools/dsctest/src/in_desired_state.rs b/tools/dsctest/src/in_desired_state.rs
new file mode 100644
index 00000000..2fb2c655
--- /dev/null
+++ b/tools/dsctest/src/in_desired_state.rs
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
+#[serde(deny_unknown_fields)]
+pub struct InDesiredState {
+    #[serde(rename = "_inDesiredState", skip_serializing_if = "Option::is_none")]
+    pub in_desired_state: Option<bool>,
+    pub value: String,
+}
diff --git a/tools/dsctest/src/main.rs b/tools/dsctest/src/main.rs
index 400fa2ad..7f4ce788 100644
--- a/tools/dsctest/src/main.rs
+++ b/tools/dsctest/src/main.rs
@@ -5,6 +5,7 @@ mod args;
 mod delete;
 mod exist;
 mod exit_code;
+mod in_desired_state;
 mod sleep;
 mod trace;
 mod whatif;
@@ -15,6 +16,7 @@ use schemars::schema_for;
 use crate::delete::Delete;
 use crate::exist::{Exist, State};
 use crate::exit_code::ExitCode;
+use crate::in_desired_state::InDesiredState;
 use crate::sleep::Sleep;
 use crate::trace::Trace;
 use crate::whatif::WhatIf;
@@ -65,6 +67,17 @@ fn main() {
             }
             input
         },
+        SubCommand::InDesiredState { input } => {
+            let mut in_desired_state = match serde_json::from_str::<in_desired_state::InDesiredState>(&input) {
+                Ok(in_desired_state) => in_desired_state,
+                Err(err) => {
+                    eprintln!("Error JSON does not match schema: {err}");
+                    std::process::exit(1);
+                }
+            };
+            in_desired_state.value = "SomethingElse".to_string();
+            serde_json::to_string(&in_desired_state).unwrap()
+        },
         SubCommand::Schema { subcommand } => {
             let schema = match subcommand {
                 Schemas::Delete => {
@@ -76,6 +89,9 @@ fn main() {
                 Schemas::ExitCode => {
                     schema_for!(ExitCode)
                 },
+                Schemas::InDesiredState => {
+                    schema_for!(InDesiredState)
+                },
                 Schemas::Sleep => {
                     schema_for!(Sleep)
                 },

From 618d5ed783fc337ec39bf65d9415a24e8a486c60 Mon Sep 17 00:00:00 2001
From: Steve Lee <slee@microsoft.com>
Date: Thu, 6 Mar 2025 13:52:55 -0800
Subject: [PATCH 2/3] make stateAndDiff also respect resource, enhance tests

---
 dsc/tests/dsc_config_test.tests.ps1          | 17 +++++----
 dsc_lib/src/dscresources/command_resource.rs | 37 +++++++++-----------
 tools/dsctest/src/in_desired_state.rs        |  6 +++-
 tools/dsctest/src/main.rs                    |  3 +-
 4 files changed, 34 insertions(+), 29 deletions(-)

diff --git a/dsc/tests/dsc_config_test.tests.ps1 b/dsc/tests/dsc_config_test.tests.ps1
index be2db81d..16e26582 100644
--- a/dsc/tests/dsc_config_test.tests.ps1
+++ b/dsc/tests/dsc_config_test.tests.ps1
@@ -33,11 +33,13 @@ Describe 'dsc config test tests' {
         }
     }
 
-    It '_inDesiredState returned is used when: <inDesiredState>' -TestCases @(
-        @{ inDesiredState = $true }
-        @{ inDesiredState = $false }
+    It '_inDesiredState returned is used when: inDesiredState = <inDesiredState> and same = <same>' -TestCases @(
+        @{ inDesiredState = $true; valueOne = 1; valueTwo = 2; same = $true }
+        @{ inDesiredState = $true; valueOne = 3; valueTwo = 4; same = $false }
+        @{ inDesiredState = $false; valueOne = 1; valueTwo = 2; same = $true }
+        @{ inDesiredState = $false; valueOne = 3; valueTwo = 4; same = $false }
     ) {
-        param($inDesiredState)
+        param($inDesiredState, $valueOne, $valueTwo)
 
         $configYaml = @"
   `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
@@ -46,17 +48,18 @@ Describe 'dsc config test tests' {
       type: Test/InDesiredState
       properties:
         _inDesiredState: $inDesiredState
-        value: Hello
+        valueOne: $valueOne
+        valueTwo: $valueTwo
 "@
 
         $out = dsc config test -i $configYaml | ConvertFrom-Json
         $LASTEXITCODE | Should -Be 0
         $out.results[0].result.inDesiredState | Should -Be $inDesiredState
-        if ($inDesiredState) {
+        if ($same) {
             $out.results[0].result.differingProperties | Should -BeNullOrEmpty
         }
         else {
-            $out.results[0].result.differingProperties | Should -Contain 'value'
+            $out.results[0].result.differingProperties | Should -Be @('valueOne', 'valueTwo')
         }
     }
 }
diff --git a/dsc_lib/src/dscresources/command_resource.rs b/dsc_lib/src/dscresources/command_resource.rs
index 9957e11d..7b9c6061 100644
--- a/dsc_lib/src/dscresources/command_resource.rs
+++ b/dsc_lib/src/dscresources/command_resource.rs
@@ -283,25 +283,8 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Re
                     return Err(DscError::Operation(t!("dscresources.commandResource.failedParseJson", executable = &test.executable, stdout = stdout, stderr = stderr, err = err).to_string()))
                 }
             };
-            // if actual state contains _inDesiredState, we use that to determine if the resource is in desired state
-            let mut in_desired_state: Option<bool> = None;
-            if let Some(in_desired_state_value) = actual_value.get("_inDesiredState") {
-                if let Some(desired_state) = in_desired_state_value.as_bool() {
-                    in_desired_state = Some(desired_state);
-                } else {
-                    return Err(DscError::Operation(t!("dscresources.commandResource.inDesiredStateNotBool").to_string()));
-                }
-            }
-
-            let mut diff_properties: Vec<String> = Vec::new();
-            match in_desired_state {
-                Some(true) => {
-                    // if _inDesiredState is true, we don't need to check for diff properties
-                },
-                Some(false) | None => {
-                    diff_properties = get_diff(&expected_value, &actual_value);
-                }
-            }
+            let in_desired_state = get_desired_state(&actual_value)?;
+            let diff_properties = get_diff(&expected_value, &actual_value);
             Ok(TestResult::Resource(ResourceTestResponse {
                 desired_state: expected_value,
                 actual_state: actual_value,
@@ -320,10 +303,11 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Re
                 return Err(DscError::Command(resource.resource_type.clone(), exit_code, t!("dscresources.commandResource.testNoDiff").to_string()));
             };
             let diff_properties: Vec<String> = serde_json::from_str(diff_properties)?;
+            let in_desired_state = get_desired_state(&actual_value)?;
             Ok(TestResult::Resource(ResourceTestResponse {
                 desired_state: expected_value,
                 actual_state: actual_value,
-                in_desired_state: diff_properties.is_empty(),
+                in_desired_state: in_desired_state.unwrap_or(diff_properties.is_empty()),
                 diff_properties,
             }))
         },
@@ -353,6 +337,19 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Re
     }
 }
 
+fn get_desired_state(actual: &Value) -> Result<Option<bool>, DscError> {
+    // if actual state contains _inDesiredState, we use that to determine if the resource is in desired state
+    let mut in_desired_state: Option<bool> = None;
+    if let Some(in_desired_state_value) = actual.get("_inDesiredState") {
+        if let Some(desired_state) = in_desired_state_value.as_bool() {
+            in_desired_state = Some(desired_state);
+        } else {
+            return Err(DscError::Operation(t!("dscresources.commandResource.inDesiredStateNotBool").to_string()));
+        }
+    }
+    Ok(in_desired_state)
+}
+
 fn invoke_synthetic_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Result<TestResult, DscError> {
     let get_result = invoke_get(resource, cwd, expected)?;
     let actual_state = match get_result {
diff --git a/tools/dsctest/src/in_desired_state.rs b/tools/dsctest/src/in_desired_state.rs
index 2fb2c655..868ecd48 100644
--- a/tools/dsctest/src/in_desired_state.rs
+++ b/tools/dsctest/src/in_desired_state.rs
@@ -4,10 +4,14 @@
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 
+#[allow(clippy::struct_field_names)]
 #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
 #[serde(deny_unknown_fields)]
 pub struct InDesiredState {
     #[serde(rename = "_inDesiredState", skip_serializing_if = "Option::is_none")]
     pub in_desired_state: Option<bool>,
-    pub value: String,
+    #[serde(rename = "valueOne")]
+    pub value_one: i32,
+    #[serde(rename = "valueTwo")]
+    pub value_two: i32,
 }
diff --git a/tools/dsctest/src/main.rs b/tools/dsctest/src/main.rs
index 7f4ce788..696308c4 100644
--- a/tools/dsctest/src/main.rs
+++ b/tools/dsctest/src/main.rs
@@ -75,7 +75,8 @@ fn main() {
                     std::process::exit(1);
                 }
             };
-            in_desired_state.value = "SomethingElse".to_string();
+            in_desired_state.value_one = 1;
+            in_desired_state.value_two= 2;
             serde_json::to_string(&in_desired_state).unwrap()
         },
         SubCommand::Schema { subcommand } => {

From ddc3f4ad0767cd59f274afb84d799c5771c2ff35 Mon Sep 17 00:00:00 2001
From: Steve Lee <slee@microsoft.com>
Date: Thu, 6 Mar 2025 13:55:14 -0800
Subject: [PATCH 3/3] fix whitespace

---
 tools/dsctest/src/main.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tools/dsctest/src/main.rs b/tools/dsctest/src/main.rs
index 696308c4..d9f85a0b 100644
--- a/tools/dsctest/src/main.rs
+++ b/tools/dsctest/src/main.rs
@@ -76,7 +76,7 @@ fn main() {
                 }
             };
             in_desired_state.value_one = 1;
-            in_desired_state.value_two= 2;
+            in_desired_state.value_two = 2;
             serde_json::to_string(&in_desired_state).unwrap()
         },
         SubCommand::Schema { subcommand } => {