Add test cases for testing additional Presets write and commit constr… (#35141)

* Add test cases for testing additional Presets write and commit constraints

- Add a test for adding a preset with a preset scenario not present in PresetTypes

- Add a test for testing addition of presets such that the total number of presets added is greater than the total number of presets supported

* Add rollback after test step 18

* Modify the number of presets supported test case to read the number of presets supported and build a preset list whose size exceeds that to test

* Modify the number of presets supported test case to read the number of presets supported and build a preset list whose size exceeds that to test

* Update thermostat-delegate-impl.h

* Address review comments

* Add support to check for numberOfPresets supported for each preset type and build the presets list with multiple presets of each type

* Restyled by autopep8

* Fix log line formatting

* Update src/python_testing/TC_TSTAT_4_2.py

Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>

* Fix test step 17 to find a preset scenario in PresetScenarioEnum that is not present in PresetTypes to run the test

- Fix test step 18 to build a presets list that exceeds the number of presets supported correctly

* Restyled by autopep8

* Fix lint errors

* Add a while loop to add more presets until max is reached

---------

Co-authored-by: Restyled.io <commits@restyled.io>
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
diff --git a/examples/thermostat/thermostat-common/include/thermostat-delegate-impl.h b/examples/thermostat/thermostat-common/include/thermostat-delegate-impl.h
index b57ee24..7726fc3 100644
--- a/examples/thermostat/thermostat-common/include/thermostat-delegate-impl.h
+++ b/examples/thermostat/thermostat-common/include/thermostat-delegate-impl.h
@@ -39,6 +39,10 @@
 // We will support only one preset of each preset type.
 static constexpr uint8_t kMaxNumberOfPresetsOfEachType = 1;
 
+// For testing the use case where number of presets added exceeds the number of presets supported, we will have the value of
+// kMaxNumberOfPresetsSupported < kMaxNumberOfPresetTypes * kMaxNumberOfPresetsOfEachType
+static constexpr uint8_t kMaxNumberOfPresetsSupported = kMaxNumberOfPresetTypes * kMaxNumberOfPresetsOfEachType - 1;
+
 class ThermostatDelegate : public Delegate
 {
 public:
diff --git a/examples/thermostat/thermostat-common/src/thermostat-delegate-impl.cpp b/examples/thermostat/thermostat-common/src/thermostat-delegate-impl.cpp
index 7991e48..da58c84 100644
--- a/examples/thermostat/thermostat-common/src/thermostat-delegate-impl.cpp
+++ b/examples/thermostat/thermostat-common/src/thermostat-delegate-impl.cpp
@@ -31,7 +31,7 @@
 
 ThermostatDelegate::ThermostatDelegate()
 {
-    mNumberOfPresets                   = kMaxNumberOfPresetTypes * kMaxNumberOfPresetsOfEachType;
+    mNumberOfPresets                   = kMaxNumberOfPresetsSupported;
     mNextFreeIndexInPresetsList        = 0;
     mNextFreeIndexInPendingPresetsList = 0;
 
@@ -87,6 +87,9 @@
         { .presetScenario     = PresetScenarioEnum::kVacation,
           .numberOfPresets    = kMaxNumberOfPresetsOfEachType,
           .presetTypeFeatures = to_underlying(PresetTypeFeaturesBitmap::kSupportsNames) },
+        { .presetScenario     = PresetScenarioEnum::kUserDefined,
+          .numberOfPresets    = kMaxNumberOfPresetsOfEachType,
+          .presetTypeFeatures = to_underlying(PresetTypeFeaturesBitmap::kSupportsNames) },
     };
     if (index < ArraySize(presetTypes))
     {
diff --git a/src/python_testing/TC_TSTAT_4_2.py b/src/python_testing/TC_TSTAT_4_2.py
index b133b1b..641c827 100644
--- a/src/python_testing/TC_TSTAT_4_2.py
+++ b/src/python_testing/TC_TSTAT_4_2.py
@@ -210,6 +210,10 @@
                      "Verify that the write request is rejected"),
             TestStep("16", "TH starts an atomic write, and before it's complete, TH2 removes TH's fabric; TH2 then opens an atomic write",
                      "Verify that the atomic request is successful"),
+            TestStep("17", "TH writes to the Presets attribute with a preset that has a presetScenario not present in PresetTypes attribute",
+                     "Verify that the write request returned CONSTRAINT_ERROR (0x87)."),
+            TestStep("18", "TH writes to the Presets attribute such that the total number of presets is greater than the number of presets supported",
+                     "Verify that the write request returned RESOURCE_EXHAUSTED (0x89)."),
         ]
 
         return steps
@@ -469,7 +473,86 @@
             # Roll back
             await self.send_atomic_request_rollback_command()
 
-        # TODO: Add tests for the total number of Presets exceeds the NumberOfPresets supported. Also Add tests for adding presets with preset scenario not present in PresetTypes.
+        self.step("17")
+        if self.pics_guard(self.check_pics("TSTAT.S.F08") and self.check_pics("TSTAT.S.A0050") and self.check_pics("TSTAT.S.Cfe.Rsp")):
+
+            # Read the PresetTypes to get the preset scenarios supported by the Thermostat.
+            presetTypes = await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=cluster.Attributes.PresetTypes)
+
+            scenarioNotPresent = None
+
+            # Find a preset scenario not present in PresetTypes to run this test.
+            for scenario in cluster.Enums.PresetScenarioEnum:
+                foundMatchingScenario = False
+                for presetType in presetTypes:
+                    if presetType.presetScenario == scenario:
+                        foundMatchingScenario = True
+                        break
+                if not foundMatchingScenario:
+                    scenarioNotPresent = scenario
+                    break
+
+            if scenarioNotPresent is None:
+                logger.info(
+                    "Couldn't run test step 17 since all preset types in PresetScenarioEnum are supported by this Thermostat")
+            else:
+                test_presets = new_presets_with_handle.copy()
+                test_presets.append(cluster.Structs.PresetStruct(presetHandle=NullValue, presetScenario=scenarioNotPresent,
+                                                                 name="Preset", coolingSetpoint=2500, heatingSetpoint=1700, builtIn=False))
+
+                # Send the AtomicRequest begin command
+                await self.send_atomic_request_begin_command()
+
+                await self.write_presets(endpoint=endpoint, presets=test_presets, expected_status=Status.ConstraintError)
+
+                # Clear state for next test.
+                await self.send_atomic_request_rollback_command()
+
+        self.step("18")
+        if self.pics_guard(self.check_pics("TSTAT.S.F08") and self.check_pics("TSTAT.S.A0050") and self.check_pics("TSTAT.S.Cfe.Rsp")):
+
+            # Read the numberOfPresets supported.
+            numberOfPresetsSupported = await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=cluster.Attributes.NumberOfPresets)
+
+            # Read the PresetTypes to get the preset scenarios to build the Presets list.
+            presetTypes = await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=cluster.Attributes.PresetTypes)
+
+            # Read the Presets to copy the existing presets into our testPresets list below.
+            presets = await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=cluster.Attributes.Presets)
+
+            # Calculate the length of the Presets list that could be created using the preset scenarios in PresetTypes and numberOfPresets supported for each scenario.
+            totalExpectedPresetsLength = 0
+            for presetType in presetTypes:
+                totalExpectedPresetsLength += presetType.numberOfPresets
+
+            if totalExpectedPresetsLength > numberOfPresetsSupported:
+                testPresets = []
+                for presetType in presetTypes:
+                    scenario = presetType.presetScenario
+
+                    # For each supported scenario, copy all the existing presets that match it, then add more presets
+                    # until we hit the cap on the number of presets for that scenario.
+                    presetsAddedForScenario = 0
+                    for preset in presets:
+                        if scenario == preset.presetScenario:
+                            testPresets.append(preset)
+                            presetsAddedForScenario = presetsAddedForScenario + 1
+
+                    while presetsAddedForScenario < presetType.numberOfPresets:
+                        testPresets.append(cluster.Structs.PresetStruct(presetHandle=NullValue, presetScenario=scenario,
+                                                                        name="Preset", coolingSetpoint=2500, heatingSetpoint=1700, builtIn=False))
+                        presetsAddedForScenario = presetsAddedForScenario + 1
+
+                # Send the AtomicRequest begin command
+                await self.send_atomic_request_begin_command()
+
+                await self.write_presets(endpoint=endpoint, presets=testPresets, expected_status=Status.ResourceExhausted)
+
+                # Clear state for next test.
+                await self.send_atomic_request_rollback_command()
+            else:
+                logger.info(
+                    "Couldn't run test step 18 since there are not enough preset types to build a Presets list that exceeds the number of presets supported")
 
 
 if __name__ == "__main__":