/ src / settings-ui / Settings.UI.UnitTests / ModelsTests / BasePTModuleSettingsSerializationTests.cs
BasePTModuleSettingsSerializationTests.cs
  1  // Copyright (c) Microsoft Corporation
  2  // The Microsoft Corporation licenses this file to you under the MIT license.
  3  // See the LICENSE file in the project root for more information.
  4  
  5  using System;
  6  using System.Linq;
  7  using System.Reflection;
  8  using System.Text.Json;
  9  using System.Text.Json.Serialization;
 10  using Microsoft.PowerToys.Settings.UI.Library;
 11  using Microsoft.VisualStudio.TestTools.UnitTesting;
 12  
 13  namespace CommonLibTest
 14  {
 15      [TestClass]
 16      public class BasePTModuleSettingsSerializationTests
 17      {
 18          /// <summary>
 19          /// Test to verify that all classes derived from BasePTModuleSettings are registered
 20          /// in the SettingsSerializationContext for Native AOT compatibility.
 21          /// </summary>
 22          [TestMethod]
 23          public void AllBasePTModuleSettingsClasses_ShouldBeRegisteredInSerializationContext()
 24          {
 25              // Arrange
 26              var assembly = typeof(BasePTModuleSettings).Assembly;
 27              var settingsClasses = assembly.GetTypes()
 28                  .Where(t => typeof(BasePTModuleSettings).IsAssignableFrom(t) && !t.IsAbstract && t != typeof(BasePTModuleSettings))
 29                  .OrderBy(t => t.Name)
 30                  .ToList();
 31  
 32              Assert.IsTrue(settingsClasses.Count > 0, "No BasePTModuleSettings derived classes found. This test may be broken.");
 33  
 34              var jsonSerializerOptions = new JsonSerializerOptions
 35              {
 36                  TypeInfoResolver = SettingsSerializationContext.Default,
 37              };
 38  
 39              var unregisteredTypes = new System.Collections.Generic.List<string>();
 40  
 41              // Act & Assert
 42              foreach (var settingsType in settingsClasses)
 43              {
 44                  var typeInfo = jsonSerializerOptions.TypeInfoResolver?.GetTypeInfo(settingsType, jsonSerializerOptions);
 45  
 46                  if (typeInfo == null)
 47                  {
 48                      unregisteredTypes.Add(settingsType.FullName ?? settingsType.Name);
 49                  }
 50              }
 51  
 52              // Assert
 53              if (unregisteredTypes.Count > 0)
 54              {
 55                  var errorMessage = $"The following {unregisteredTypes.Count} settings class(es) are NOT registered in SettingsSerializationContext:\n" +
 56                                     $"{string.Join("\n", unregisteredTypes.Select(t => $"  - {t}"))}\n\n" +
 57                                     $"Please add [JsonSerializable(typeof(ClassName))] attribute to SettingsSerializationContext.cs for each missing type.";
 58                  Assert.Fail(errorMessage);
 59              }
 60  
 61              // Print success message with count
 62              Console.WriteLine($"✓ All {settingsClasses.Count} BasePTModuleSettings derived classes are properly registered in SettingsSerializationContext.");
 63          }
 64  
 65          /// <summary>
 66          /// Test to verify that calling ToJsonString() on an unregistered type throws InvalidOperationException
 67          /// with a helpful error message.
 68          /// </summary>
 69          [TestMethod]
 70          [ExpectedException(typeof(InvalidOperationException))]
 71          public void ToJsonString_UnregisteredType_ShouldThrowInvalidOperationException()
 72          {
 73              // Arrange
 74              var unregisteredSettings = new UnregisteredTestSettings
 75              {
 76                  Name = "UnregisteredModule",
 77                  Version = "1.0.0",
 78              };
 79  
 80              // Act - This should throw InvalidOperationException
 81              var jsonString = unregisteredSettings.ToJsonString();
 82  
 83              // Assert - Exception should be thrown, so this line should never be reached
 84              Assert.Fail("Expected InvalidOperationException was not thrown.");
 85          }
 86  
 87          /// <summary>
 88          /// Test to verify that the error message for unregistered types is helpful and contains
 89          /// necessary information for developers.
 90          /// </summary>
 91          [TestMethod]
 92          public void ToJsonString_UnregisteredType_ShouldHaveHelpfulErrorMessage()
 93          {
 94              // Arrange
 95              var unregisteredSettings = new UnregisteredTestSettings
 96              {
 97                  Name = "UnregisteredModule",
 98                  Version = "1.0.0",
 99              };
100  
101              // Act & Assert
102              try
103              {
104                  var jsonString = unregisteredSettings.ToJsonString();
105                  Assert.Fail("Expected InvalidOperationException was not thrown.");
106              }
107              catch (InvalidOperationException ex)
108              {
109                  // Verify the error message contains helpful information
110                  Assert.IsTrue(ex.Message.Contains("UnregisteredTestSettings"), "Error message should contain the type name.");
111                  Assert.IsTrue(ex.Message.Contains("SettingsSerializationContext"), "Error message should mention SettingsSerializationContext.");
112                  Assert.IsTrue(ex.Message.Contains("JsonSerializable"), "Error message should mention JsonSerializable attribute.");
113  
114                  Console.WriteLine($"✓ Error message is helpful: {ex.Message}");
115              }
116          }
117  
118          /// <summary>
119          /// Test class that is intentionally NOT registered in SettingsSerializationContext
120          /// to verify error handling for unregistered types.
121          /// </summary>
122          private sealed class UnregisteredTestSettings : BasePTModuleSettings
123          {
124              // Intentionally empty - this class should NOT be registered in SettingsSerializationContext
125          }
126      }
127  }